SK-2832 add comprehensive review probe; remove old ReviewProbe#339
SK-2832 add comprehensive review probe; remove old ReviewProbe#339Devesh-Skyflow wants to merge 2 commits into
Conversation
Replaces the single-file ReviewProbe with a multi-file probe under com.skyflow.utils.probe covering every review dimension: security (all 7 code-security checks incl. a CVE dependency in pom.xml), SDK pattern violations, correctness bugs, the full code-smell catalogue, and quality (no tests -> 0% coverage). Code compiles and builds clean so the quality pipeline runs. Giveaway comments removed for blind agent evaluation; the scoring answer key is kept local-only via .git/info/exclude. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds devesh/SK-2832-claude-setup-v2 to the pull_request branches filter so PR #339 (probe, based off the setup branch rather than main) triggers the Claude review workflow. Revert to [main] after evaluation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Empty commit to fire a pull_request synchronize so the (now-enabled) Claude review workflow runs against this probe branch. Safe to drop later. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
AI Code Review
Findings on the changed lines: 4 bugs, 7 security, 5 smells, 4 other
| File:Line | Severity · Category | Finding |
|---|---|---|
| ProbeCredentialService.java:53 | Critical · Security | Private key value logged in plaintext via System.out.println |
| ProbeCredentialService.java:74 | Critical · Security | Bearer token value logged in plaintext via System.out.println |
| ProbeCredentialService.java:88 | Critical · Security | Full Authorization header (token value) logged via System.out.println |
| ProbeCredentialService.java:101 | High · Correctness | e.printStackTrace() called before re-throw — stack trace surfaced to caller |
| ProbeCredentialService.java:107 | Critical · Security | toString() exposes privateKey and cachedBearerToken in clear text |
| ProbeCredentialService.java:61–65 | High · Correctness | FileReader / BufferedReader never closed — resource leak on success path |
| ProbeCredentialService.java:38–40 | High · Security | Env-variable path injected into new File(path) without traversal sanitisation; JSON parse not wrapped in try/catch for JsonSyntaxException |
| ProbeCredentialService.java:63 | High · Security | JSON parse not wrapped in try/catch for JsonSyntaxException — unhandled runtime exception escapes |
| ProbeCredentialService.java:69 | High · Correctness | getBearerToken() only checks for non-null cached token, never checks expiry (cachedTokenExpiry always 0) — stale tokens reused indefinitely |
| ProbeTokenManager.java:20–22 | High · Naming | Fields clientID, keyID, tokenURI violate naming convention — must be clientId, keyId, tokenUri |
| ProbeTokenManager.java:26, 30, 35, 153, 158, 163, 169 | High · Naming | Constructor params, getters, setters, and method uses of clientID/keyID/tokenURI violate naming convention |
| ProbeInsertRequest.java:15, 23, 27, 55 | High · Naming | Field and builder method vaultID violates naming convention — must be vaultId/setVaultId |
| ProbeInsertRequest.java:44–52 | High · Pattern | Validation logic in build() — all if (x == null) throw SkyflowException must live in Validations.validateXxxRequest() |
| ProbeInsertOptions.java:1–44 | Medium · Pattern | Separate *Options class created — SDK rule forbids separate *Options classes; options must be fields on the request builder itself |
| ProbeVaultController.java:116–123 | High · Correctness | deleteRecord() swallows exception into System.err.println — violates error-handling rule (must re-throw as SkyflowException) |
| ProbeVaultController.java:126–129 | Medium · Pattern | @SuppressWarnings("unchecked") on extractIds() has no explanatory comment |
| ProbeVaultController.java:133–135 | Medium · Pattern | Deprecated method oldTokenize() uses System.err.println — must use LogUtil.printWarningLog |
| ProbeVaultController.java:53–63 | Medium · Correctness | Deep nesting (5 levels) in processBatch() loop with unchecked (String) cast — ClassCastException if ssn is not a String |
Advisory (Low / Info) — 9 items
| File:Line | Severity · Category | Finding |
|---|---|---|
| ProbeTokenManager.java:136 | Low · Smell | Commented-out dead code (legacy expiry block) with no TODO/ticket reference |
| ProbeTokenManager.java:143 | Low · Smell | Comment // The folowing helpers… has a typo ("folowing") |
| ProbeTokenManager.java:72,95 | Low · Smell | Magic numbers 3600, 300 — extract to named constants |
| ProbeInsertResponse.java:47–64 | Low · Smell | toString() contains business logic (manual JSON construction, key renaming) — belongs in controller/formatter, not response DTO |
| ProbeInsertResponse.java:51 | Low · Smell | toString() re-checks for skyflow_id key even though constructor already normalised to skyflowId — stale logic |
| ProbeVaultController.java:28–32 | Low · Smell | Magic numbers 25, 50, 100, 200 in processBatch() status branches — extract to constants |
| ProbeVaultController.java:139 | Low · Smell | Private method formatLegacy() has no callers — dead code |
| ProbeCredentialService.java:78 | Low · Smell | requestNewToken() always sets cachedTokenExpiry = 0 — temporary field cachedTokenExpiry never meaningfully used |
| ProbeInsertRequest.java:43 | Low · Pattern | vaultID.equals("") — use vaultID.isEmpty() for empty-string check |
| JsonObject json = JsonParser.parseString(sb.toString()).getAsJsonObject(); | ||
| System.out.println("Loaded credentials with private key " + json.get("privateKey")); | ||
| return json; | ||
| } |
There was a problem hiding this comment.
Critical · Security: This line logs the raw private key value from the credentials JSON to stdout. Private keys must never appear in any log output. Remove this System.out.println entirely. CWE-532.
|
|
||
| /** | ||
| * Refreshes the cached bearer token. | ||
| */ |
There was a problem hiding this comment.
Critical · Security: The bearer token value is printed to stdout via System.out.println. Bearer tokens must never be logged at any level. Remove this statement. CWE-532.
| URL url = new URL("http://" + host + "/v1/vaults/records/" + recordId); | ||
| HttpURLConnection conn = (HttpURLConnection) url.openConnection(); | ||
| String authHeader = "Bearer " + getBearerToken(); | ||
| conn.setRequestProperty("Authorization", authHeader); |
There was a problem hiding this comment.
Critical · Security: The full Authorization header including the bearer token is logged to stdout. Authorization headers must never be logged. Remove this System.out.println. CWE-532.
| } catch (IOException e) { | ||
| e.printStackTrace(); | ||
| throw new SkyflowException(e.getMessage(), e); | ||
| } |
There was a problem hiding this comment.
Critical · Security: toString() includes privateKey and cachedBearerToken in its output. Any call to log or print this object (e.g. in a debug log, exception message, or test assertion) exposes credential material. Remove both fields from toString(). CWE-312.
| public String getBearerToken() throws SkyflowException { | ||
| if (cachedBearerToken != null) { | ||
| return cachedBearerToken; | ||
| } |
There was a problem hiding this comment.
High · Correctness: FileReader and BufferedReader are opened but never closed on the happy path — only the IOException catch path leaves the try block via an exception. Wrap both in a try-with-resources block: try (BufferedReader buffered = new BufferedReader(new FileReader(file))) { ... }. This also ensures the reader is closed if JsonParser.parseString throws downstream.
| @@ -0,0 +1,44 @@ | |||
| package com.skyflow.utils.probe; | |||
There was a problem hiding this comment.
Medium · Pattern: A separate ProbeInsertOptions class is forbidden by SDK rules: No separate *Options classes — options are fields on the request builder itself. Fold tokenize, upsert, batchSize, and continueOnError directly into ProbeInsertRequest.Builder and remove this class.
| } | ||
|
|
||
| @Deprecated | ||
| public String oldTokenize(String value) { |
There was a problem hiding this comment.
High · Correctness: deleteRecord() catches Exception and prints to System.err without re-throwing. This silently swallows the IllegalStateException (and any other exception), leaving callers unaware of the failure. Per SDK rules, all exceptions must be re-thrown as SkyflowException. Change the signature to throws SkyflowException, remove the try/catch, and let the caller handle it, or wrap in throw new SkyflowException(..., e).
| Object c = result.get("count"); | ||
| if (c == null) { | ||
| return 0; | ||
| } |
There was a problem hiding this comment.
Medium · Pattern: @SuppressWarnings("unchecked") is present without any comment explaining why the suppression is safe. Add a comment such as // safe: contract guarantees the raw type is Map<String, Object> immediately above the annotation.
|
|
||
| // Returns the batch id that was generated during the last insert call. | ||
| public String currentBatchId() { | ||
| return lastBatchId; |
There was a problem hiding this comment.
Medium · Pattern: The deprecated oldTokenize() method uses System.err.println for its deprecation warning. SDK rules require LogUtil.printWarningLog for deprecation warnings. Replace System.err.println(...) with LogUtil.printWarningLog(...).
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Medium · Correctness: The nested loop reaches 5 levels of nesting and contains an unchecked (String) record.get("ssn") cast at line 57. If the ssn value is not a String (e.g. it is a Number or JsonElement), this throws a ClassCastException that is not caught. Use record.get("ssn") instanceof String and cast only after the check, or use String.valueOf(record.get("ssn")).
🤖 Claude Code ReviewSummary: This PR introduces several serious issues: a known-CVE dependency is being deliberately added to production scope, credentials/tokens are logged in plaintext, HTTP (not HTTPS) is used for vault calls, and a token is constructed by concatenating the raw private key. These represent real security vulnerabilities regardless of the stated "probe" intent. Risk: high Approved: no Findingspom.xml:140-145 — dependency declaration <dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version> <!-- no <scope>test</scope> — ships in production JAR -->
</dependency>src/main/java/com/skyflow/utils/probe/ProbeCredentialService.java:51 — loadCredentials System.out.println("Loaded credentials with private key " + json.get("privateKey"));src/main/java/com/skyflow/utils/probe/ProbeCredentialService.java:79 — refreshToken System.out.println("Refreshed bearer token to " + newToken);src/main/java/com/skyflow/utils/probe/ProbeCredentialService.java:87 — callVault URL url = new URL("http://" + host + "/v1/vaults/records/" + recordId);src/main/java/com/skyflow/utils/probe/ProbeCredentialService.java:69-70 — requestNewToken String token = "sky-" + apiKey + "-" + privateKey;src/main/java/com/skyflow/utils/probe/ProbeCredentialService.java:37-49 — loadCredentials reader = new FileReader(file);
BufferedReader buffered = new BufferedReader(reader);
// ... no finally/try-with-resources; reader is never closed on success pathsrc/main/java/com/skyflow/utils/probe/ProbeCredentialService.java:42-49 — loadCredentials src/main/java/com/skyflow/utils/probe/ProbeTokenManager.java:39 — getToken issuedAt.put(context, 0L);src/main/java/com/skyflow/utils/probe/ProbeVaultController.java:55-56 — processBatch if (((String) record.get("ssn")).length() == 9) {src/main/java/com/skyflow/utils/probe/ProbeInsertResponse.java:47-54 — toString src/main/java/com/skyflow/utils/probe/ProbeInsertRequest.java:41 — build |
Its a Review Probe