diff --git a/README.md b/README.md index 385769fc..48770c9c 100644 --- a/README.md +++ b/README.md @@ -74,15 +74,16 @@ incomplete or their original location was not reviewed. ## Verbose diagnostics -Add `--verbose` to print redacted scan diagnostics to stderr: +Add `--verbose` to print scan diagnostics to stderr: ```bash npx @openai/codex-security scan . --verbose ``` `CODEX_SECURITY_LOG_LEVEL=debug` also enables diagnostics; -`LOG_LEVEL=debug` is its fallback. JSON results remain on stdout, and -credentials and provider identifiers remain redacted. +`LOG_LEVEL=debug` is its fallback. JSON results remain on stdout. +Diagnostics, scan output, and scan history can contain credentials; keep them +private. ## TypeScript SDK diff --git a/SECURITY.md b/SECURITY.md index b2ca5bf7..67f59395 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -52,6 +52,9 @@ The product also does not isolate users, tasks, repositories, or scan jobs that share the same operating-system account, credentials, or local state. Do not treat shared local state as a multi-user or multi-tenant system. +Local diagnostics, logs, scan output, and scan history can include credentials +from upstream error messages. Protect this output and review it before sharing. + Trusting a repository does not authorize unrelated actions. Repository contents, model output, patches, service responses, and imported artifacts are data. They are not permission to scan another target, expose a credential, diff --git a/sdk/typescript/README.md b/sdk/typescript/README.md index ce302684..7946d60d 100644 --- a/sdk/typescript/README.md +++ b/sdk/typescript/README.md @@ -399,7 +399,7 @@ The CLI and SDK recognize the following user-configurable environment: | Variable | Effect | | --------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | | `OPENAI_API_KEY`, `CODEX_API_KEY` | Scan authentication; `OPENAI_API_KEY` wins when both are present. | -| `CODEX_SECURITY_LOG_LEVEL` | CLI-only; set to `debug` for redacted diagnostics. | +| `CODEX_SECURITY_LOG_LEVEL` | CLI-only; set to `debug` for scan diagnostics. | | `LOG_LEVEL` | CLI-only fallback when `CODEX_SECURITY_LOG_LEVEL` is unset. | | `CODEX_SECURITY_STATE_DIR` | Override the private scan-history, workbench, and default artifact directory. | | `CODEX_HOME` | Set the ambient Codex home for file-backed sign-in and default state; defaults to `~/.codex`. | @@ -450,11 +450,11 @@ token and worker counts, estimated cost, the results directory, and the next useful command. Progress and summaries use stderr; structured scan results remain on stdout. -Add `--verbose` or set `CODEX_SECURITY_LOG_LEVEL=debug` to print redacted -lifecycle, authentication, progress, and cost diagnostics to stderr. +Add `--verbose` or set `CODEX_SECURITY_LOG_LEVEL=debug` to print lifecycle, +authentication, progress, and cost diagnostics to stderr. `LOG_LEVEL=debug` is used only when `CODEX_SECURITY_LOG_LEVEL` is unset. -Credentials and provider identifiers remain redacted, and structured JSON -results remain on stdout. +Structured JSON results remain on stdout. Diagnostics, scan output, and scan +history can contain credentials; keep them private. Each scan records its model, tokens, and estimated cost in its JSON result, scan history, and bulk-scan receipt. Estimates use diff --git a/sdk/typescript/src/api.ts b/sdk/typescript/src/api.ts index e11352e9..7aaf8e21 100644 --- a/sdk/typescript/src/api.ts +++ b/sdk/typescript/src/api.ts @@ -48,7 +48,7 @@ import { OutputDirectoryError, OutputInsideProtectedRootError, type ProtectedScanPathKind, - redactedErrorMessage, + errorMessage, ScanCostLimitExceededError, ScanInterruptedError, } from "./errors.js"; @@ -1081,10 +1081,8 @@ export class CodexSecurity { "fail-scan", "--scan-id", activeScan.id, - // Redact before truncating: the stored message is read back by - // `scans show` and travels inside the results directory. "--message", - redactedErrorMessage(failure).slice(0, 2400), + errorMessage(failure).slice(0, 2400), ...(snapshot?.cost ? ["--cost-json", JSON.stringify(snapshot.cost)] : []), diff --git a/sdk/typescript/src/cli.ts b/sdk/typescript/src/cli.ts index a97868fb..8f9ef8ba 100644 --- a/sdk/typescript/src/cli.ts +++ b/sdk/typescript/src/cli.ts @@ -65,7 +65,7 @@ import { OutputDirectoryError, OutputInsideProtectedRootError, PluginPythonUnavailableError, - redactedErrorMessage, + errorMessage, ScanCostLimitExceededError, ScanInterruptedError, } from "./errors.js"; @@ -730,7 +730,7 @@ export async function main( try { return await select(await dependencies.runWorkbench(args)); } catch (error) { - errorOutput.write(`codex-security: ${redactedErrorMessage(error)}\n`); + errorOutput.write(`codex-security: ${errorMessage(error)}\n`); exitCode = 2; return undefined; } @@ -936,7 +936,7 @@ export async function main( scanArguments = scanArgumentsFromRecipe(recipe, args.scanId); scanArguments.verbose = options.verbose; } catch (error) { - const message = redactedErrorMessage(error); + const message = errorMessage(error); errorOutput.write(`codex-security: ${message}\n`); exitCode = 2; return incurError({ @@ -999,7 +999,7 @@ export async function main( format, ); } catch (error) { - errorOutput.write(`codex-security: ${redactedErrorMessage(error)}\n`); + errorOutput.write(`codex-security: ${errorMessage(error)}\n`); exitCode = 2; return undefined; } @@ -1052,7 +1052,7 @@ export async function main( verbose: z .boolean() .default(false) - .describe("Print redacted scan diagnostics to stderr."), + .describe("Print scan diagnostics to stderr."), path: z .array(optionValue("--path")) .default([]) @@ -1313,7 +1313,7 @@ export async function main( failOnSeverity: options.failOnSeverity, }; } catch (error) { - errorOutput.write(`codex-security: ${redactedErrorMessage(error)}\n`); + errorOutput.write(`codex-security: ${errorMessage(error)}\n`); exitCode = 2; return undefined; } @@ -1502,7 +1502,7 @@ export async function main( onProgress: ({ repository, status, attempt, error, warning }) => { const detail = error ?? warning; errorOutput.write( - `codex-security: ${repository} ${status} (attempt ${attempt})${detail === undefined ? "" : `: ${redactedErrorMessage(detail)}`}\n`, + `codex-security: ${repository} ${status} (attempt ${attempt})${detail === undefined ? "" : `: ${errorMessage(detail)}`}\n`, ); }, }); @@ -1516,7 +1516,7 @@ export async function main( (error instanceof Error && error.name === "ExitPromptError" ? 130 : 2); - errorOutput.write(`codex-security: ${redactedErrorMessage(error)}\n`); + errorOutput.write(`codex-security: ${errorMessage(error)}\n`); } finally { dependencies.removeSignalListener("SIGINT", onInterrupt); dependencies.removeSignalListener("SIGTERM", onTerminate); @@ -1620,7 +1620,7 @@ export async function main( ); } catch (error) { exitCode = 2; - errorOutput.write(`codex-security: ${redactedErrorMessage(error)}\n`); + errorOutput.write(`codex-security: ${errorMessage(error)}\n`); } }, }) @@ -1656,7 +1656,7 @@ export async function main( ); } catch (error) { exitCode = 2; - errorOutput.write(`codex-security: ${redactedErrorMessage(error)}\n`); + errorOutput.write(`codex-security: ${errorMessage(error)}\n`); } }, }) @@ -1845,7 +1845,7 @@ export async function main( if (frameworkExit !== undefined) { if (exitCode !== 0) return exitCode; errorOutput.write( - `codex-security: ${redactedErrorMessage(incurErrorMessage(frameworkOutput))}\n`, + `codex-security: ${errorMessage(incurErrorMessage(frameworkOutput))}\n`, ); return 2; } @@ -1854,7 +1854,7 @@ export async function main( await writeCliOutput(output, renderedHistory ?? frameworkOutput); return exitCode; } catch (error) { - errorOutput.write(`codex-security: ${redactedErrorMessage(error)}\n`); + errorOutput.write(`codex-security: ${errorMessage(error)}\n`); return 2; } } @@ -2651,7 +2651,7 @@ async function runExport( } return 0; } catch (error) { - errorOutput.write(`codex-security: ${redactedErrorMessage(error)}\n`); + errorOutput.write(`codex-security: ${errorMessage(error)}\n`); return 2; } } @@ -2659,7 +2659,7 @@ async function runExport( type VerboseDiagnosticValue = string | number | boolean | null | undefined; function sanitizeDiagnosticValue(value: unknown): string { - return redactedErrorMessage(value) + return errorMessage(value) .replaceAll( /(\b(?:tenant(?:[_-]?id)?|org(?:anization)?(?:[_-]?id)?|project(?:[_-]?id)?|(?:x[_-]?)?(?:request|trace|correlation)[_-]?id)\b(?:\\*["'])?\s*[:=]\s*)(?!\[redacted\])(?:(\\*)(['"])(?:(?!(? displayed.length ? `, +${arguments_.paths.length - displayed.length} more` : ""}`; } @@ -3477,7 +3474,7 @@ function printScanSummary( ? 33 : 36; errorOutput.write( - `\n ${paint("REPORT", "1;36")} ${paint(redactedErrorMessage(result.reportPath), 4)}\n\n` + + `\n ${paint("REPORT", "1;36")} ${paint(errorMessage(result.reportPath), 4)}\n\n` + ` ${paint("FINDINGS", 1)} ${paint(`${findingCount}${severitySummary === "" ? "" : ` (${severitySummary})`}`, findingColor)}\n` + ` ${paint("COVERAGE", 1)} ${result.coverage.completeness}\n` + ` ${paint("ELAPSED", 1)} ${duration}\n`, @@ -3493,7 +3490,7 @@ function printScanSummary( ); } errorOutput.write( - ` ${paint("RESULTS", 1)} ${redactedErrorMessage(result.scanDir)}\n`, + ` ${paint("RESULTS", 1)} ${errorMessage(result.scanDir)}\n`, ); } @@ -3835,7 +3832,7 @@ function interruptedExit( errorOutput.write( scanDir === null ? "codex-security: No partial output was kept.\n" - : `codex-security: Partial output was kept at ${redactedErrorMessage(scanDir)}.\n`, + : `codex-security: Partial output was kept at ${errorMessage(scanDir)}.\n`, ); return ctrlC ? 130 : 143; } @@ -3861,7 +3858,7 @@ if (invokedAsMain()) { process.exitCode = exitCode; }, (error: unknown) => { - process.stderr.write(`codex-security: ${redactedErrorMessage(error)}\n`); + process.stderr.write(`codex-security: ${errorMessage(error)}\n`); process.exitCode = 2; }, ); diff --git a/sdk/typescript/src/errors.ts b/sdk/typescript/src/errors.ts index a8face06..132d36ce 100644 --- a/sdk/typescript/src/errors.ts +++ b/sdk/typescript/src/errors.ts @@ -1,72 +1,7 @@ import { formatUsd, type ScanCost } from "./cost.js"; -/** Returns an error message with credential-shaped substrings redacted. */ -export function redactedErrorMessage(error: unknown): string { - const message = error instanceof Error ? error.message : String(error); - const withoutPrivateKeys = message.replaceAll( - /(\b[A-Za-z0-9_-]{0,64}private[_-]?key(?:[_-][A-Za-z0-9_-]{1,64}|(?:value|data|token|secret|credential|password|header|field|id|key)[A-Za-z0-9_-]{0,48})?\b(?:\\?["'])?\s*[:=]\s*)(?:\\?["'])?-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z0-9 ]*PRIVATE KEY-----(?:\\?["'])?|$)/giu, - "$1[redacted]", - ); - return redactQuotedCredentialValues(withoutPrivateKeys) - .replaceAll( - /(\b[A-Za-z0-9_-]{0,64}(?:authorization|auth)(?:[_-][A-Za-z0-9_-]{1,64}|(?:value|data|token|secret|credential|password|header|field|id|key)[A-Za-z0-9_-]{0,48})?\b(?:\\?["'])?\s*[:=]\s*)([A-Za-z][A-Za-z0-9._~-]{0,63})((?:\s|%20|\+)+)(?!\[redacted\]|(?!key\s*=)[A-Za-z_][A-Za-z0-9_-]{0,64}\s*[:=]\s*(?=[^=\s"',;}&\\\]]))[^\s"',;}&\\\]]+/giu, - "$1$2$3[redacted]", - ) - .replaceAll( - /(\b[A-Za-z0-9_-]{0,64}(?:api[_-]?key|access[_-]?key(?:[_-]?id)?|private[_-]?key|authorization|auth|token|secret|credential|signature|sig|password|passwd)(?:[_-][A-Za-z0-9_-]{1,64}|(?:value|data|token|secret|credential|password|header|field|id|key)[A-Za-z0-9_-]{0,48})?\b(?:\\?["'])?\s*[:=]\s*(?:\\?["'])?)(?!\[redacted\]|[A-Za-z][A-Za-z0-9._~-]{0,63}(?:\s|%20|\+)+\[redacted\])[^\s"',;}&\\\]]+/giu, - "$1[redacted]", - ) - .replaceAll(/sk-(?:proj-)?[A-Za-z0-9_*=-]{8,}/gu, "[redacted]") - .replaceAll(/(?:github_pat_|gh[pousr]_)[A-Za-z0-9_-]{8,}/giu, "[redacted]") - .replaceAll(/npm_[A-Za-z0-9_-]{8,}/giu, "[redacted]") - .replaceAll( - /(^|%20|[^A-Za-z0-9_])(Bearer|Basic|Token)((?:\s|%20|\+)+)[A-Za-z0-9.%_~+/*=-]+/giu, - "$1$2$3[redacted]", - ) - .replaceAll(/((?:https?|ssh|git\+ssh):\/\/)[^\s/@]+@/giu, "$1[redacted]@") - .replaceAll( - /((?:[?&]|%3F|%26)(?:(?!%3F|%26|%3D)(?:[A-Za-z0-9_.%-]|\[|\])){0,64}(?:api[_-]?key|access(?:[_-]|%5F|%2D)?key(?:(?:[_-]|%5F|%2D)?id)?|private(?:[_-]|%5F|%2D)?key|authorization|auth|token|secret|credential|signature|sig|password|passwd)(?:(?:[_-]|%5F|%2D)[A-Za-z0-9_.%-]{1,64}|(?:value|data|token|secret|credential|password|header|field|id|key)[A-Za-z0-9_.%-]{0,48})?(?:\]|%5D)?(?:=|%3D))(?:(?!%26)[^&\s])+/giu, - "$1[redacted]", - ); -} - -function redactQuotedCredentialValues(message: string): string { - const assignment = - /(\b[A-Za-z0-9_-]{0,64}(?:api[_-]?key|access[_-]?key(?:[_-]?id)?|private[_-]?key|authorization|auth|token|secret|credential|signature|sig|password|passwd)(?:[_-][A-Za-z0-9_-]{1,64}|(?:value|data|token|secret|credential|password|header|field|id|key)[A-Za-z0-9_-]{0,48})?\b(?:\\*["'])?\s*[:=]\s*)(\\*)(["'])/giu; - let output = ""; - let consumed = 0; - for ( - let match = assignment.exec(message); - match !== null; - match = assignment.exec(message) - ) { - const openingSlashes = match[2]!.length; - const quote = match[3]!; - let position = assignment.lastIndex; - let closed = false; - while (position < message.length) { - const delimiter = message.indexOf(quote, position); - if (delimiter < 0) break; - let preceding = delimiter; - while (preceding > position && message[preceding - 1] === "\\") { - preceding -= 1; - } - if (delimiter - preceding === openingSlashes) { - output += `${message.slice(consumed, assignment.lastIndex)}[redacted]${message.slice(preceding, delimiter + 1)}`; - consumed = delimiter + 1; - assignment.lastIndex = consumed; - closed = true; - break; - } - position = delimiter + 1; - } - if (!closed) { - output += `${message.slice(consumed, assignment.lastIndex)}[redacted]`; - consumed = message.length; - break; - } - } - return output + message.slice(consumed); +export function errorMessage(error: unknown): string { + return error instanceof Error ? error.message : String(error); } /** Base error for Codex Security SDK failures. */ diff --git a/sdk/typescript/src/multiscan.ts b/sdk/typescript/src/multiscan.ts index 9704014c..ec178cba 100644 --- a/sdk/typescript/src/multiscan.ts +++ b/sdk/typescript/src/multiscan.ts @@ -19,7 +19,7 @@ import Papa from "papaparse"; import type { CodexSecurity } from "./api.js"; import type { CodexSecurityConfig } from "./config.js"; import type { ScanCost } from "./cost.js"; -import { redactedErrorMessage } from "./errors.js"; +import { errorMessage } from "./errors.js"; import type { CoverageDocument } from "./models.js"; import type { ScanMode } from "./targets.js"; import { resolveTrustedExecutable } from "./trusted-executable.js"; @@ -246,7 +246,7 @@ async function runCampaign( } } catch (error) { if (options.signal?.aborted === true) options.signal.throwIfAborted(); - failure = redactedErrorMessage(error); + failure = errorMessage(error); } finally { await rm(checkout, { recursive: true, force: true }); } diff --git a/sdk/typescript/src/runtime.ts b/sdk/typescript/src/runtime.ts index 476eaacf..293c12a1 100644 --- a/sdk/typescript/src/runtime.ts +++ b/sdk/typescript/src/runtime.ts @@ -43,7 +43,6 @@ import { OutputDirectoryError, PluginBootstrapError, PluginPythonUnavailableError, - redactedErrorMessage, } from "./errors.js"; import type { JsonObject } from "./config.js"; import { resolveTrustedExecutable } from "./trusted-executable.js"; @@ -283,10 +282,7 @@ function windowsCredentialAclFailure(error: unknown): string { : error instanceof Error ? error.message : String(error); - const sanitized = redactedErrorMessage(detail) - .replace(/\s+/gu, " ") - .trim() - .slice(0, 512); + const sanitized = detail.replace(/\s+/gu, " ").trim().slice(0, 512); return sanitized === "" ? "" : `. ${sanitized}`; } diff --git a/sdk/typescript/src/scan-dashboard.ts b/sdk/typescript/src/scan-dashboard.ts index 26713d07..293d3a45 100644 --- a/sdk/typescript/src/scan-dashboard.ts +++ b/sdk/typescript/src/scan-dashboard.ts @@ -45,7 +45,6 @@ interface ScanDashboardOptions { maxCostUsd?: number; clock: DashboardClock; color?: boolean; - sanitize?: (value: string) => string; input?: DashboardInput; onInterrupt?: () => void; } @@ -310,10 +309,7 @@ export class ScanDashboard { lines .map((line, index) => { const text = typeof line === "string" ? line : line.text; - const clean = fitLine( - this.#options.sanitize?.(text) ?? text, - width, - ); + const clean = fitLine(text, width); const colored = this.#options.color === true ? styleLine( @@ -333,7 +329,6 @@ export class ScanDashboard { ? styleInlineCode(colored, line.code, line.kind) : colored, line.links, - this.#options.sanitize, ); return `${ERASE_LINE}${formatted}`; }) @@ -449,10 +444,9 @@ function styleInlineCode( function linkActivity( value: string, links: readonly DashboardActivityLink[] | undefined, - sanitize: ((value: string) => string) | undefined, ): string { for (const { label, target } of links ?? []) { - const safe = safeHyperlinkTarget(sanitize?.(target) ?? target); + const safe = safeHyperlinkTarget(target); if (safe !== undefined) { value = value.replace( label, diff --git a/sdk/typescript/tests-ts/api.test.ts b/sdk/typescript/tests-ts/api.test.ts index 2ea5b331..fcb977f5 100644 --- a/sdk/typescript/tests-ts/api.test.ts +++ b/sdk/typescript/tests-ts/api.test.ts @@ -50,7 +50,7 @@ import { setCodexSecurityCredentialLogout, } from "../src/runtime.js"; import { normalizeTarget } from "../src/targets.js"; -import { REDACTED_CREDENTIALS, SYNTHETIC_CREDENTIALS } from "./cli-fixtures.js"; +import { SYNTHETIC_CREDENTIALS } from "./cli-fixtures.js"; import { INTEGRATION_TARGET, PLUGIN_ROOT } from "./plugin-root.js"; type ScanObserverName = Parameters< @@ -3145,7 +3145,7 @@ describe("CodexSecurity orchestration", () => { await client.close(); }); - test("redacts credentials from the stored scan failure message", async () => { + test("preserves the original stored scan failure message", async () => { const root = await temporaryDirectory(); const repository = join(root, "repository"); const codexHome = join(root, "codex-home"); @@ -3164,7 +3164,7 @@ describe("CodexSecurity orchestration", () => { const quotedCredential = JSON.stringify({ client_secret_value: "SYNTHETIC correct horse battery staple", }); - const redactedFailure = `${REDACTED_CREDENTIALS} {"client_secret_value":"[redacted]"}`; + const failureMessage = `${SYNTHETIC_CREDENTIALS} ${quotedCredential}`; const client = new TestClient( {}, { @@ -3200,14 +3200,12 @@ describe("CodexSecurity orchestration", () => { }, ); - // The in-memory error keeps its original text; only what leaves the process - // is redacted, so the CLI can still classify the upstream failure. await expect(client.run(repository)).rejects.toThrow(SYNTHETIC_CREDENTIALS); const failure = commands.find((args) => args[0] === "fail-scan"); const scanId = failure?.[2] ?? ""; expect(scanId).toMatch(/^[0-9a-f-]{36}$/); expect(failure?.[3]).toBe("--message"); - expect(failure?.[4]).toBe(redactedFailure); + expect(failure?.[4]).toBe(failureMessage); // `scans show` reads the stored message back through get-scan. const context = await runWorkbench( @@ -3216,13 +3214,11 @@ describe("CodexSecurity orchestration", () => { ); expect(context["scan"]).toMatchObject({ progress: { status: "failed" }, - failureMessage: redactedFailure, + failureMessage, }); - // Every synthetic credential is tagged SYNTHETIC, so the database file - // itself proves nothing was persisted anywhere on the failure path. const database = await readFile(join(stateDirectory, "workbench.sqlite3")); - expect(database.toString("latin1")).not.toContain("SYNTHETIC"); + expect(database.toString("latin1")).toContain(failureMessage); await client.close(); }); diff --git a/sdk/typescript/tests-ts/cli-export.test.ts b/sdk/typescript/tests-ts/cli-export.test.ts index aacfb329..dc784495 100644 --- a/sdk/typescript/tests-ts/cli-export.test.ts +++ b/sdk/typescript/tests-ts/cli-export.test.ts @@ -16,7 +16,6 @@ import { describe, expect, test } from "bun:test"; import { exportEnvironment, main } from "../src/cli.js"; import { CodexSecurityError } from "../src/index.js"; import { - REDACTED_CREDENTIALS, SYNTHETIC_CREDENTIALS, capture, dependencies, @@ -583,7 +582,7 @@ describe("CLI", () => { ); }); - test("redacts credentials from caught export failures", async () => { + test("preserves caught export failures", async () => { const stdout = capture(); const stderr = capture(); const deps = dependencies(); @@ -601,7 +600,7 @@ describe("CLI", () => { ).toBe(2); expect(stdout.text()).toBe(""); expect(stderr.text()).toBe( - `codex-security: export failed ${REDACTED_CREDENTIALS}\n`, + `codex-security: export failed ${SYNTHETIC_CREDENTIALS}\n`, ); }); }); diff --git a/sdk/typescript/tests-ts/cli-fixtures.ts b/sdk/typescript/tests-ts/cli-fixtures.ts index 24040954..644e8db9 100644 --- a/sdk/typescript/tests-ts/cli-fixtures.ts +++ b/sdk/typescript/tests-ts/cli-fixtures.ts @@ -21,109 +21,7 @@ import type { UpdateNotice } from "../src/version.js"; type MainDependencies = NonNullable[3]>; -export const SYNTHETIC_CREDENTIALS = [ - "sk-proj-SYNTHETIC_KEY_123", - "Bearer SYNTHETIC_TOKEN_123", - "Authorization: Basic SYNTHETIC_BASIC_123", - "Authorization: Token SYNTHETIC_HEADER_TOKEN_123", - "Authorization: Bearer%20SYNTHETIC%2FENCODED%2BTOKEN_123", - "Authorization%3A%20Bearer%20SYNTHETIC_FULLY_ENCODED_TOKEN_123", - "https://SYNTHETIC_USER:SYNTHETIC_PASSWORD@example.test/private", - "ssh://SYNTHETIC_USER:SYNTHETIC_SSH_PASSWORD@example.test/private", - "git+ssh://SYNTHETIC_USER:SYNTHETIC_GIT_PASSWORD@example.test/private", - "github_pat_SYNTHETIC_GITHUB_PAT_123", - "ghs_SYNTHETIC_GITHUB_TOKEN_123", - "OPENAI_API_KEY=SYNTHETIC_OPENAI_VALUE_123", - "CODEX_API_KEY=SYNTHETIC_CODEX_VALUE_123", - "CODEX_ACCESS_TOKEN=SYNTHETIC_CODEX_ACCESS_TOKEN_123", - "GITHUB_TOKEN=SYNTHETIC_GITHUB_VALUE_123", - "GH_TOKEN=SYNTHETIC_GH_VALUE_123", - '{"OPENAI_API_KEY":"SYNTHETIC_JSON_OPENAI_123","CODEX_API_KEY":"SYNTHETIC_JSON_CODEX_123"}', - '{\\"OPENAI_API_KEY\\":\\"SYNTHETIC_ESCAPED_OPENAI_123\\",\\"CODEX_API_KEY\\":\\"SYNTHETIC_ESCAPED_CODEX_123\\"}', - '{"refresh_token":"SYNTHETIC_REFRESH_TOKEN_123","id_token":"SYNTHETIC_ID_TOKEN_123","clientSecret":"SYNTHETIC_CLIENT_SECRET_123","dbPassword":"SYNTHETIC_PASSWORD_123","passwd":"SYNTHETIC_PASSWD_123"}', - '{\\"refreshToken\\":\\"SYNTHETIC_ESCAPED_REFRESH_123\\",\\"idToken\\":\\"SYNTHETIC_ESCAPED_ID_123\\",\\"clientSecret\\":\\"SYNTHETIC_ESCAPED_SECRET_123\\",\\"password\\":\\"SYNTHETIC_ESCAPED_PASSWORD_123\\"}', - "AWS_SECRET_ACCESS_KEY=SYNTHETIC_AWS_SECRET_123", - "AWS_ACCESS_KEY_ID=SYNTHETIC_AWS_ID_123", - "AWS_SESSION_TOKEN=SYNTHETIC_AWS_SESSION_123", - "NODE_AUTH_TOKEN=SYNTHETIC_NODE_AUTH_123", - "NPM_TOKEN=SYNTHETIC_NPM_TOKEN_123", - "OPENAI_API_KEY=sk-proj-SYNTHETIC_NAMED_OPENAI_123", - "GITHUB_TOKEN=ghs_SYNTHETIC_NAMED_GITHUB_123", - "NPM_TOKEN=npm_SYNTHETIC_NAMED_NPM_123", - "ACTIONS_ID_TOKEN_REQUEST_TOKEN=SYNTHETIC_ACTIONS_TOKEN_123", - "ACTIONS_RUNTIME_TOKEN=SYNTHETIC_ACTIONS_RUNTIME_123", - "GITLAB_TOKEN=SYNTHETIC_GITLAB_TOKEN_123", - "HF_TOKEN=SYNTHETIC_HF_TOKEN_123", - "SLACK_BOT_TOKEN=SYNTHETIC_SLACK_TOKEN_123", - "//registry.npmjs.org/:_authToken=SYNTHETIC_NPMRC_TOKEN_123", - "x-api-key: SYNTHETIC_HEADER_KEY_123", - "access_token=SYNTHETIC_ACCESS_TOKEN_123", - "npm_SYNTHETIC_BARE_TOKEN_123", - "https://example.test/?token=SYNTHETIC_QUERY_123&safe=1", - "https://example.test/?credential=SYNTHETIC_CREDENTIAL_123&safe=1", - "https://example.test/?AWS_ACCESS_KEY_ID=SYNTHETIC_QUERY_AWS_ID_123&safe=1", - "https://example.test/?AWS%5FACCESS%5FKEY%5FID=SYNTHETIC_ENCODED_AWS_ID_123&AWS%2DACCESS%2DKEY%2DID=SYNTHETIC_ENCODED_AWS_DASH_ID_123&safe=1", - "https://example.test/?service-api-key=SYNTHETIC_QUERY_API_KEY_123&service-access-token=SYNTHETIC_QUERY_ACCESS_TOKEN_123&service-token=SYNTHETIC_QUERY_TOKEN_123&service-secret=SYNTHETIC_QUERY_SECRET_123&signature=SYNTHETIC_SIGNATURE_123&safe=1", - "https://example.test/?X-Amz-Signature=SYNTHETIC_AMZ_SIGNATURE_123&X-Amz-Credential=SYNTHETIC_AMZ_CREDENTIAL_123&X-Amz-Security-Token=SYNTHETIC_AMZ_TOKEN_123&safe=1", - "https://example.test/?X-Goog-Signature=SYNTHETIC_GOOG_SIGNATURE_123&X-Goog-Credential=SYNTHETIC_GOOG_CREDENTIAL_123&safe=1", - "https://example.test/?sv=2026-01-01&sig=SYNTHETIC_AZURE_SIG_123&safe=1", - "https://example.test/?password=SYNTHETIC_QUERY_PASSWORD_123&passwd=SYNTHETIC_QUERY_PASSWD_123&safe=1", - "https://example.test/?oauth.refreshToken=SYNTHETIC_DOTTED_TOKEN_123&auth[token]=SYNTHETIC_BRACKET_TOKEN_123&auth%5BclientSecret%5D=SYNTHETIC_ENCODED_SECRET_123&safe=1", - "https://example.test/?access_token%3DSYNTHETIC_ENCODED_ACCESS_123&client_secret%3DSYNTHETIC_ENCODED_CLIENT_123&safe=1", - "https://example.test/?redirect_uri=https%3A%2F%2Finner.test%2Fcb%3Frefresh_token%3DSYNTHETIC_NESTED_REFRESH_123%26password%3DSYNTHETIC_NESTED_PASSWORD_123%26safe%3D1", -].join(" "); - -export const REDACTED_CREDENTIALS = [ - "[redacted]", - "Bearer [redacted]", - "Authorization: Basic [redacted]", - "Authorization: Token [redacted]", - "Authorization: Bearer%20[redacted]", - "Authorization%3A%20Bearer%20[redacted]", - "https://[redacted]@example.test/private", - "ssh://[redacted]@example.test/private", - "git+ssh://[redacted]@example.test/private", - "[redacted]", - "[redacted]", - "OPENAI_API_KEY=[redacted]", - "CODEX_API_KEY=[redacted]", - "CODEX_ACCESS_TOKEN=[redacted]", - "GITHUB_TOKEN=[redacted]", - "GH_TOKEN=[redacted]", - '{"OPENAI_API_KEY":"[redacted]","CODEX_API_KEY":"[redacted]"}', - '{\\"OPENAI_API_KEY\\":\\"[redacted]\\",\\"CODEX_API_KEY\\":\\"[redacted]\\"}', - '{"refresh_token":"[redacted]","id_token":"[redacted]","clientSecret":"[redacted]","dbPassword":"[redacted]","passwd":"[redacted]"}', - '{\\"refreshToken\\":\\"[redacted]\\",\\"idToken\\":\\"[redacted]\\",\\"clientSecret\\":\\"[redacted]\\",\\"password\\":\\"[redacted]\\"}', - "AWS_SECRET_ACCESS_KEY=[redacted]", - "AWS_ACCESS_KEY_ID=[redacted]", - "AWS_SESSION_TOKEN=[redacted]", - "NODE_AUTH_TOKEN=[redacted]", - "NPM_TOKEN=[redacted]", - "OPENAI_API_KEY=[redacted]", - "GITHUB_TOKEN=[redacted]", - "NPM_TOKEN=[redacted]", - "ACTIONS_ID_TOKEN_REQUEST_TOKEN=[redacted]", - "ACTIONS_RUNTIME_TOKEN=[redacted]", - "GITLAB_TOKEN=[redacted]", - "HF_TOKEN=[redacted]", - "SLACK_BOT_TOKEN=[redacted]", - "//registry.npmjs.org/:_authToken=[redacted]", - "x-api-key: [redacted]", - "access_token=[redacted]", - "[redacted]", - "https://example.test/?token=[redacted]&safe=1", - "https://example.test/?credential=[redacted]&safe=1", - "https://example.test/?AWS_ACCESS_KEY_ID=[redacted]&safe=1", - "https://example.test/?AWS%5FACCESS%5FKEY%5FID=[redacted]&AWS%2DACCESS%2DKEY%2DID=[redacted]&safe=1", - "https://example.test/?service-api-key=[redacted]&service-access-token=[redacted]&service-token=[redacted]&service-secret=[redacted]&signature=[redacted]&safe=1", - "https://example.test/?X-Amz-Signature=[redacted]&X-Amz-Credential=[redacted]&X-Amz-Security-Token=[redacted]&safe=1", - "https://example.test/?X-Goog-Signature=[redacted]&X-Goog-Credential=[redacted]&safe=1", - "https://example.test/?sv=2026-01-01&sig=[redacted]&safe=1", - "https://example.test/?password=[redacted]&passwd=[redacted]&safe=1", - "https://example.test/?oauth.refreshToken=[redacted]&auth[token]=[redacted]&auth%5BclientSecret%5D=[redacted]&safe=1", - "https://example.test/?access_token%3D[redacted]&client_secret%3D[redacted]&safe=1", - "https://example.test/?redirect_uri=https%3A%2F%2Finner.test%2Fcb%3Frefresh_token%3D[redacted]%26password%3D[redacted]%26safe%3D1", -].join(" "); +export const SYNTHETIC_CREDENTIALS = "sk-proj-SYNTHETIC_KEY_123"; export function capture(isTTY = false): { stream: Pick & diff --git a/sdk/typescript/tests-ts/cli-launcher.test.ts b/sdk/typescript/tests-ts/cli-launcher.test.ts index 703256df..92a0e777 100644 --- a/sdk/typescript/tests-ts/cli-launcher.test.ts +++ b/sdk/typescript/tests-ts/cli-launcher.test.ts @@ -13,7 +13,7 @@ import { join } from "node:path"; import { pathToFileURL } from "node:url"; import { describe, expect, test } from "bun:test"; import { VERSION } from "../src/index.js"; -import { REDACTED_CREDENTIALS, SYNTHETIC_CREDENTIALS } from "./support/cli.js"; +import { SYNTHETIC_CREDENTIALS } from "./support/cli.js"; const packageRoot = join(import.meta.dir, ".."); @@ -40,7 +40,7 @@ describe("CLI launcher", () => { } }); - test("maps unexpected source-entrypoint failures to exit 2 and redacts credentials", async () => { + test("maps unexpected source-entrypoint failures to exit 2", async () => { const root = await mkdtemp(join(tmpdir(), "codex-security-cli-failure-")); try { const preload = join(root, "unavailable-cwd.mjs"); @@ -57,7 +57,7 @@ describe("CLI launcher", () => { expect(child.status).toBe(2); expect(child.stdout).toBe(""); expect(child.stderr).toBe( - `working directory is unavailable: ${REDACTED_CREDENTIALS}\n`, + `working directory is unavailable: ${SYNTHETIC_CREDENTIALS}\n`, ); } finally { await rm(root, { recursive: true, force: true }); diff --git a/sdk/typescript/tests-ts/cli-workbench.test.ts b/sdk/typescript/tests-ts/cli-workbench.test.ts index aea574ba..5d6c559e 100644 --- a/sdk/typescript/tests-ts/cli-workbench.test.ts +++ b/sdk/typescript/tests-ts/cli-workbench.test.ts @@ -3,12 +3,7 @@ import { describe, expect, test } from "bun:test"; import type { CodexSecurityConfig, JsonObject } from "../src/index.js"; import { DiffTarget } from "../src/index.js"; import { main } from "../src/cli.js"; -import { - capture, - dependencies, - REDACTED_CREDENTIALS, - SYNTHETIC_CREDENTIALS, -} from "./support/cli.js"; +import { capture, dependencies, SYNTHETIC_CREDENTIALS } from "./support/cli.js"; describe("CLI workbench", () => { test("lists repository and scan-root history without starting Codex", async () => { @@ -619,7 +614,7 @@ describe("CLI workbench", () => { } }); - test("redacts workbench failures and does not initialize Codex", async () => { + test("reports workbench failures and does not initialize Codex", async () => { const stderr = capture(); let started = false; expect( @@ -637,8 +632,7 @@ describe("CLI workbench", () => { }), ), ).toBe(2); - expect(stderr.text()).toContain(REDACTED_CREDENTIALS); - expect(stderr.text()).not.toContain("SYNTHETIC_KEY_123"); + expect(stderr.text()).toContain(SYNTHETIC_CREDENTIALS); expect(started).toBe(false); }); }); diff --git a/sdk/typescript/tests-ts/cli.test.ts b/sdk/typescript/tests-ts/cli.test.ts index 5475ef49..b726520b 100644 --- a/sdk/typescript/tests-ts/cli.test.ts +++ b/sdk/typescript/tests-ts/cli.test.ts @@ -22,7 +22,6 @@ import type { ScanOptions, ScanPreflight, } from "../src/index.js"; -import { redactedErrorMessage } from "../src/errors.js"; import { BUNDLED_PLUGIN_VERSION, CodexSecurityError, @@ -45,7 +44,6 @@ import { } from "../src/config.js"; import { FakeSignals, - REDACTED_CREDENTIALS, SYNTHETIC_CREDENTIALS, capture, dependencies, @@ -499,7 +497,7 @@ describe("CLI", () => { expect(workbenchCalled).toBe(false); }); - test("redacts false-positive workbench failures", async () => { + test("reports false-positive workbench failures", async () => { const stdout = capture(); const stderr = capture(); let started = false; @@ -529,8 +527,7 @@ describe("CLI", () => { ), ).toBe(2); expect(stdout.text()).toBe(""); - expect(stderr.text()).toContain(REDACTED_CREDENTIALS); - expect(stderr.text()).not.toContain("SYNTHETIC_KEY_123"); + expect(stderr.text()).toContain(SYNTHETIC_CREDENTIALS); expect(started).toBe(false); }); @@ -837,7 +834,7 @@ describe("CLI", () => { }, ); - test("preserves the bulk-scan failure summary and redacts progress errors", async () => { + test("preserves the bulk-scan failure summary and progress errors", async () => { const root = await mkdtemp(join(tmpdir(), "codex-security-cli-multiscan-")); try { await multiscanInventory(root); @@ -871,8 +868,7 @@ describe("CLI", () => { skipped: 0, }); expect(stderr.text()).toContain("sample failed (attempt 1)"); - expect(stderr.text()).toContain("[redacted]"); - expect(stderr.text()).not.toContain("SYNTHETIC_KEY_123"); + expect(stderr.text()).toContain("SYNTHETIC_KEY_123"); } finally { await rm(root, { recursive: true, force: true }); } @@ -1688,8 +1684,9 @@ describe("CLI", () => { ); expect(text).not.toContain("thinking ·"); expect(text).not.toContain("said ·"); - expect(text).toContain('curl -H "Authorization: Bearer [redacted]"'); - expect(text).not.toContain("SYNTHETIC_OPENAI_VALUE_123"); + expect(text).toContain( + 'curl -H "Authorization: Bearer sk-proj-SYNTHETIC_OPENAI_VALUE_123"', + ); expect(text).not.toContain("Building the file inventory"); expect(text).not.toContain("Running a scan command"); expect(text).toContain("3 / 1,258 reviewed"); @@ -3334,7 +3331,7 @@ describe("CLI", () => { } }); - test("redacts verbose output paths and observer diagnostics", async () => { + test("preserves verbose output paths and observer diagnostics", async () => { const stdout = capture(); const stderr = capture(); const deps = dependencies(); @@ -3366,16 +3363,15 @@ describe("CLI", () => { ).toBe(0); expect(JSON.parse(stdout.text())).toEqual(fakeResult().toJSON()); expect(stderr.text()).toContain( - 'codex-security: debug: scan.output_archived archive_dir="/tmp/archive_[redacted]"', + 'codex-security: debug: scan.output_archived archive_dir="/tmp/archive_sk-proj-SYNTHETIC_ARCHIVE_SECRET_123"', ); expect(stderr.text()).toContain( - 'codex-security: debug: scan.output_ready scan_dir="/tmp/scan_[redacted]"', + 'codex-security: debug: scan.output_ready scan_dir="/tmp/scan_sk-proj-SYNTHETIC_OUTPUT_SECRET_123"', ); expect(stderr.text()).toContain( 'codex-security: debug: scan.observer_failed observer="onWorkerStatus"', ); - expect(stderr.text()).toContain("[redacted]"); - expect(stderr.text()).not.toContain("SYNTHETIC"); + expect(stderr.text()).toContain("SYNTHETIC_KEY_123"); }); test("excludes observer failure context from verbose diagnostics", async () => { @@ -3692,7 +3688,7 @@ describe("CLI", () => { } }); - test("redacts credentials in underlying network errors", async () => { + test("preserves underlying network errors", async () => { const stdout = capture(); const stderr = capture(); const deps = dependencies(); @@ -3711,106 +3707,12 @@ describe("CLI", () => { ).toBe(2); expect(stdout.text()).toBe(""); expect(stderr.text()).toContain( - `network failure ECONNRESET ${REDACTED_CREDENTIALS}`, + `network failure ECONNRESET ${SYNTHETIC_CREDENTIALS}`, ); - expect(stderr.text()).not.toContain("SYNTHETIC_KEY_123"); + expect(stderr.text()).toContain("SYNTHETIC_KEY_123"); expect(stderr.text()).not.toContain("model service could not be reached"); }); - test("redacts quoted multiword credentials and private-key assignments", () => { - expect( - redactedErrorMessage( - 'password="correct horse battery staple" private_key=SYNTHETIC_PRIVATE_KEY_123', - ), - ).toBe('password="[redacted]" private_key=[redacted]'); - expect( - redactedErrorMessage( - '{"client_secret_value":"correct horse battery staple","safe":"visible"}', - ), - ).toBe('{"client_secret_value":"[redacted]","safe":"visible"}'); - expect( - redactedErrorMessage( - '{"clientSecretValue":"camel case secret","accessTokenValue":"camel case token"}', - ), - ).toBe( - '{"clientSecretValue":"[redacted]","accessTokenValue":"[redacted]"}', - ); - expect( - redactedErrorMessage( - "clientSecretValue=SYNTHETIC_CAMEL_SECRET accessTokenValue=SYNTHETIC_CAMEL_TOKEN https://example.test/?clientSecretValue=SYNTHETIC_CAMEL_QUERY", - ), - ).toBe( - "clientSecretValue=[redacted] accessTokenValue=[redacted] https://example.test/?clientSecretValue=[redacted]", - ); - expect( - redactedErrorMessage( - '{\\"access_token_value\\":\\"another horse battery staple\\"}', - ), - ).toBe('{\\"access_token_value\\":\\"[redacted]\\"}'); - expect( - redactedErrorMessage( - 'authorization="opaque secret value" _auth=Zm9vOmJhcg== https://example.test/?authorization=opaque%20query%20secret', - ), - ).toBe( - 'authorization="[redacted]" _auth=[redacted] https://example.test/?authorization=[redacted]', - ); - for (const [authorization, redacted] of [ - [ - "Authorization: ApiKey SYNTHETIC_APIKEY_SECRET", - "Authorization: ApiKey [redacted]", - ], - ["auth=Custom%20SYNTHETIC_CUSTOM_SECRET", "auth=Custom%20[redacted]"], - [ - "Authorization: Digest+SYNTHETIC_DIGEST_SECRET", - "Authorization: Digest+[redacted]", - ], - [ - "client_authorization_value=ApiKey SYNTHETIC_SUFFIXED_SECRET", - "client_authorization_value=ApiKey [redacted]", - ], - ["Authorization: ApiKey dGVzdA==", "Authorization: ApiKey [redacted]"], - ["Authorization: ApiKey dGVzdA=", "Authorization: ApiKey [redacted]"], - ["Authorization: ApiKey key=SECRET", "Authorization: ApiKey [redacted]"], - ["auth=Custom key=SECRET", "auth=Custom [redacted]"], - [ - "client_auth_token=Custom dGVzdA==", - "client_auth_token=Custom [redacted]", - ], - ] as const) { - expect(redactedErrorMessage(authorization)).toBe(redacted); - } - expect(redactedErrorMessage('password="correct horse battery staple')).toBe( - 'password="[redacted]', - ); - let encoded: string | { password: string } = { - password: 'foo "bar" baz', - }; - for (let depth = 1; depth <= 3; depth += 1) { - encoded = JSON.stringify(encoded); - const redacted = redactedErrorMessage(encoded); - expect(redacted).not.toContain("foo"); - expect(redacted).not.toContain("bar"); - expect(redacted).not.toContain("baz"); - let decoded: unknown = redacted; - for (let layer = 0; layer < depth; layer += 1) { - decoded = JSON.parse(decoded as string); - } - expect(decoded).toEqual({ password: "[redacted]" }); - } - for (const separator of ["\n", "\\n"]) { - expect( - redactedErrorMessage( - `private_key=-----BEGIN PRIVATE KEY-----${separator}MII_SYNTHETIC_PRIVATE_KEY${separator}-----END PRIVATE KEY----- safe=value`, - ), - ).toBe("private_key=[redacted] safe=value"); - expect( - redactedErrorMessage( - `private_key=-----BEGIN PRIVATE KEY-----${separator}MII_SYNTHETIC_TRUNCATED_PRIVATE_KEY`, - ), - ).toBe("private_key=[redacted]"); - } - }); - test("reports database connection failures without claiming the model network failed", async () => { const stdout = capture(); const stderr = capture(); @@ -3835,7 +3737,7 @@ describe("CLI", () => { expect(stderr.text()).toContain("unable to open database file"); expect(stderr.text()).not.toContain("model service could not be reached"); expect(stderr.text()).not.toContain("Check your network connection"); - expect(stderr.text()).not.toContain("SYNTHETIC_DATABASE_SECRET"); + expect(stderr.text()).toContain("SYNTHETIC_DATABASE_SECRET"); }); test("prints only the completion summary for default scans", async () => { @@ -4020,7 +3922,7 @@ describe("CLI", () => { } }); - test("emits redacted scan warnings in verbose diagnostics", async () => { + test("emits scan warnings in verbose diagnostics", async () => { const stdout = capture(); const stderr = capture(); const deps = dependencies(); @@ -4045,12 +3947,11 @@ describe("CLI", () => { ).toBe(0); expect(JSON.parse(stdout.text())).toEqual(fakeResult().toJSON()); expect(stderr.text()).toContain( - 'codex-security: debug: scan.warning message="Repository HEAD changed during the scan: [redacted]"', + 'codex-security: debug: scan.warning message="Repository HEAD changed during the scan: sk-proj-SYNTHETIC_WARNING_SECRET_123"', ); expect(stderr.text()).toContain( - "codex-security: warning: Repository HEAD changed during the scan: [redacted]", + "codex-security: warning: Repository HEAD changed during the scan: sk-proj-SYNTHETIC_WARNING_SECRET_123", ); - expect(stderr.text()).not.toContain("SYNTHETIC_WARNING_SECRET"); }); test("prints granted trusted cyber access without warning or corrupting JSON scans", async () => { @@ -4175,9 +4076,8 @@ describe("CLI", () => { ).toBe(0); expect(JSON.parse(stdout.text())).toEqual(fakeResult().toJSON()); expect(stderr.text()).toContain( - `codex-security: warning: onWorkerStatus observer failed: status observer failed ${REDACTED_CREDENTIALS}`, + `codex-security: warning: onWorkerStatus observer failed: status observer failed ${SYNTHETIC_CREDENTIALS}`, ); - expect(stderr.text()).not.toContain("SYNTHETIC_OPENAI_VALUE_123"); }); test("maps failed scan stdout writes to the runtime-error exit code", async () => { @@ -4481,7 +4381,7 @@ describe("CLI", () => { expect(JSON.parse(stdout.text())).toEqual(result.toJSON()); }); - test("keeps scan progress scope and completion paths redacted", async () => { + test("preserves scan progress scope and completion paths", async () => { const stdout = capture(); const stderr = capture(); const result = fakeResult(); @@ -4503,10 +4403,12 @@ describe("CLI", () => { dependencies({ result }), ), ).toBe(0); - expect(stderr.text()).not.toContain("SYNTHETIC_SCOPE_KEY_123"); - expect(stderr.text()).not.toContain("SYNTHETIC_OUTPUT_KEY_123"); - expect(stderr.text()).toContain("src/[redacted]"); - expect(stderr.text()).toContain("/tmp/scan_[redacted]"); + expect(stderr.text()).toContain("SYNTHETIC_SCOPE_KEY_123"); + expect(stderr.text()).toContain("SYNTHETIC_OUTPUT_KEY_123"); + expect(stderr.text()).toContain("src/sk-proj-SYNTHETIC_SCOPE_KEY_123"); + expect(stderr.text()).toContain( + "/tmp/scan_sk-proj-SYNTHETIC_OUTPUT_KEY_123", + ); }); test("reports parent fallback when delegated workers cannot start", async () => { @@ -4649,7 +4551,7 @@ describe("CLI", () => { expect(stderr.text()).not.toContain("Running scan"); }); - test("keeps redacted archive notices on stderr for JSON scans", async () => { + test("keeps archive notices on stderr for JSON scans", async () => { const stdout = capture(); const stderr = capture(); expect( @@ -4682,9 +4584,9 @@ describe("CLI", () => { expect(JSON.parse(stdout.text())).toEqual(fakeResult().toJSON()); expect(stderr.text()).toContain( "[00:00] Preparing scan\n" + - "Moved existing results to: /tmp/[redacted]/results.previous-20260721T031422-1234abcd\n", + "Moved existing results to: /tmp/sk-proj-SYNTHETIC_ARCHIVE_KEY_123/results.previous-20260721T031422-1234abcd\n", ); - expect(stderr.text()).not.toContain("SYNTHETIC_ARCHIVE_KEY_123"); + expect(stderr.text()).toContain("SYNTHETIC_ARCHIVE_KEY_123"); }); test("reports findings by severity and applies the requested policy", async () => { @@ -4953,7 +4855,7 @@ describe("CLI", () => { expect(stderr.text()).not.toContain("codex-security:"); }); - test("redacts credentials embedded in protected-root diagnostics", async () => { + test("preserves protected-root diagnostics", async () => { const stdout = capture(); const stderr = capture(); const protectedRoot = @@ -4978,16 +4880,16 @@ describe("CLI", () => { ).toBe(2); expect(stdout.text()).toBe(""); expect(stderr.text()).toContain( - "Resolved path: /private/tmp/worktree_[redacted]/results_[redacted]", + "Resolved path: /private/tmp/worktree_sk-proj-SYNTHETIC_ROOT_KEY_123/results_sk-proj-SYNTHETIC_OUTPUT_KEY_123", ); expect(stderr.text()).toContain( - "Protected root: /private/tmp/worktree_[redacted]", + "Protected root: /private/tmp/worktree_sk-proj-SYNTHETIC_ROOT_KEY_123", ); - expect(stderr.text()).not.toContain("SYNTHETIC_ROOT_KEY"); - expect(stderr.text()).not.toContain("SYNTHETIC_OUTPUT_KEY"); + expect(stderr.text()).toContain("SYNTHETIC_ROOT_KEY"); + expect(stderr.text()).toContain("SYNTHETIC_OUTPUT_KEY"); }); - test("redacts credentials from caught scan and interruption failures", async () => { + test("preserves caught scan and interruption failures", async () => { for (const failure of [ new CodexSecurityError(`scan failed ${SYNTHETIC_CREDENTIALS}`), new ScanInterruptedError( @@ -5011,12 +4913,12 @@ describe("CLI", () => { ).toBe(2); expect(stdout.text()).toBe(""); expect(stderr.text()).toBe( - "[00:00] Preparing scan\n" + `scan failed ${REDACTED_CREDENTIALS}\n`, + "[00:00] Preparing scan\n" + `scan failed ${SYNTHETIC_CREDENTIALS}\n`, ); } }); - test("redacts embedded credentials from retained partial-output paths", async () => { + test("preserves retained partial-output paths", async () => { const path = "/private/tmp/scan_sk-proj-SYNTHETIC_PATH_KEY_123/results"; for (const [signal, expectedExit] of [ [null, 2], @@ -5044,9 +4946,9 @@ describe("CLI", () => { ).toBe(expectedExit); expect(stdout.text()).toBe(""); expect(stderr.text()).toContain( - "Partial output was kept at /private/tmp/scan_[redacted]/results.", + "Partial output was kept at /private/tmp/scan_sk-proj-SYNTHETIC_PATH_KEY_123/results.", ); - expect(stderr.text()).not.toContain("SYNTHETIC_PATH_KEY"); + expect(stderr.text()).toContain("SYNTHETIC_PATH_KEY"); } }, 30_000); diff --git a/sdk/typescript/tests-ts/multiscan.test.ts b/sdk/typescript/tests-ts/multiscan.test.ts index 3ef41a0b..ebd67fc1 100644 --- a/sdk/typescript/tests-ts/multiscan.test.ts +++ b/sdk/typescript/tests-ts/multiscan.test.ts @@ -1223,22 +1223,6 @@ describe("multiscan", () => { const source = await repository(paths.root, "retry"); const secret = "sk-proj-SYNTHETIC_MULTISCAN_SECRET_123"; const knowledgeBasePaths = ["architecture.md"]; - const proxyUrl = - "https://SYNTHETIC_USER:SYNTHETIC_MULTISCAN_PASSWORD@proxy.test/v1/responses"; - const queryUrl = - "https://proxy.test/v1/responses?api_key=SYNTHETIC_MULTISCAN_QUERY_123&safe=1"; - const shortAuthorization = "Bearer abc123"; - const suffixedSecret = "SYNTHETIC_SUFFIXED_CLIENT_SECRET_123"; - const suffixedToken = "SYNTHETIC_SUFFIXED_ACCESS_TOKEN_123"; - const suffixedQuery = "SYNTHETIC_SUFFIXED_QUERY_SECRET_123"; - const quotedSecret = "SYNTHETIC correct horse battery staple"; - const opaqueAuthorization = "SYNTHETIC opaque authorization secret"; - const npmAuthorization = "SYNTHETIC_NPM_AUTH_VALUE_123"; - const customAuthorization = "SYNTHETIC_CUSTOM_AUTHORIZATION_123"; - const suffixedAuthorization = "SYNTHETIC_SUFFIXED_AUTHORIZATION_123"; - const paddedAuthorization = "SYNTHETIC_PADDED_AUTHORIZATION_TOKEN=="; - const keyedAuthorization = "SYNTHETIC_KEYED_AUTHORIZATION_SECRET_123"; - const camelCaseSecret = "SYNTHETIC_CAMEL_CASE_CLIENT_SECRET_123"; await writeFile( paths.input, `id,repository,revision\nretry,${source.path},${source.revision}\n`, @@ -1251,11 +1235,7 @@ describe("multiscan", () => { client(async (_repository, scanOptions = {}) => { expect(scanOptions.knowledgeBasePaths).toEqual(knowledgeBasePaths); attempts += 1; - if (attempts === 1) { - throw new Error( - `temporary failure ${secret} ${shortAuthorization} client_secret_value=${suffixedSecret} access_token_value=${suffixedToken} ${JSON.stringify({ client_secret_value: quotedSecret })} authorization="${opaqueAuthorization}" _auth=${npmAuthorization} Authorization: ApiKey ${customAuthorization} client_authorization_value=ApiKey ${suffixedAuthorization} auth=ApiKey ${paddedAuthorization} Authorization: Custom key=${keyedAuthorization} clientSecretValue=${camelCaseSecret} sending request for url (${proxyUrl}) and ${queryUrl}&client_secret_value=${suffixedQuery}`, - ); - } + if (attempts === 1) throw new Error(`temporary failure ${secret}`); return await completedScan(scanOptions.outputDir!); }), { knowledgeBasePaths }, @@ -1268,23 +1248,7 @@ describe("multiscan", () => { { id: "retry", status: "failed", attempt: 1 }, { id: "retry", status: "completed", attempt: 2 }, ]); - const ledger = await readFile(summary.resultsPath, "utf8"); - expect(ledger).not.toContain(secret); - expect(ledger).not.toContain("SYNTHETIC_MULTISCAN_PASSWORD"); - expect(ledger).not.toContain("SYNTHETIC_MULTISCAN_QUERY_123"); - expect(ledger).not.toContain(suffixedSecret); - expect(ledger).not.toContain(suffixedToken); - expect(ledger).not.toContain(suffixedQuery); - expect(ledger).not.toContain(quotedSecret); - expect(ledger).not.toContain(opaqueAuthorization); - expect(ledger).not.toContain(npmAuthorization); - expect(ledger).not.toContain(customAuthorization); - expect(ledger).not.toContain(suffixedAuthorization); - expect(ledger).not.toContain(paddedAuthorization); - expect(ledger).not.toContain(keyedAuthorization); - expect(ledger).not.toContain(camelCaseSecret); - expect(ledger).not.toContain(shortAuthorization); - expect(ledger).toContain("https://[redacted]@proxy.test/v1/responses"); + expect(await readFile(summary.resultsPath, "utf8")).toContain(secret); }); test("resumes complete bundles, repairs missing output, and rejects manifest drift", async () => { diff --git a/sdk/typescript/tests-ts/runtime.test.ts b/sdk/typescript/tests-ts/runtime.test.ts index 65a26b24..665a377e 100644 --- a/sdk/typescript/tests-ts/runtime.test.ts +++ b/sdk/typescript/tests-ts/runtime.test.ts @@ -2790,7 +2790,7 @@ describe("runtime directories and plugin Python boundary", () => { ).toBe(false); }); - test("surfaces redacted Windows ACL subprocess failures", async () => { + test("surfaces Windows ACL subprocess failures", async () => { const root = await temporaryDirectory(); const home = join(root, "home"); await mkdir(home); @@ -2812,9 +2812,8 @@ describe("runtime directories and plugin Python boundary", () => { } catch (error) { expect(error).toBeInstanceOf(Error); expect((error as Error).message).toContain("core types"); - expect((error as Error).message).toContain("token=[redacted]"); - expect((error as Error).message).not.toContain( - "SYNTHETIC_WINDOWS_ACL_SECRET", + expect((error as Error).message).toContain( + "token=sk-proj-SYNTHETIC_WINDOWS_ACL_SECRET_123", ); expect((error as Error).cause).toBe(underlying); } diff --git a/sdk/typescript/tests-ts/scan-dashboard.test.ts b/sdk/typescript/tests-ts/scan-dashboard.test.ts index 19ec8772..a2071979 100644 --- a/sdk/typescript/tests-ts/scan-dashboard.test.ts +++ b/sdk/typescript/tests-ts/scan-dashboard.test.ts @@ -554,7 +554,7 @@ describe("live scan dashboard", () => { dashboard.stop(); }); - test("redacts external Markdown link targets and rejects unsafe links", () => { + test("preserves external Markdown link targets and rejects unsafe links", () => { const stderr = capture(true); const dashboard = new ScanDashboard( { ...stderr.stream, columns: 120, rows: 18 }, @@ -562,7 +562,6 @@ describe("live scan dashboard", () => { repository: "/code/juice-shop", color: false, clock: fakeClock(), - sanitize: (value) => value.replaceAll("secret-token", "[redacted]"), }, ); @@ -579,9 +578,8 @@ describe("live scan dashboard", () => { const frame = lastFrame(stderr); expect(frame).toContain("See report, unsafe, and control."); expect(stderr.text()).toContain( - "\u001B]8;;https://example.com/report?token=[redacted]\u0007report\u001B]8;;\u0007", + "\u001B]8;;https://example.com/report?token=secret-token\u0007report\u001B]8;;\u0007", ); - expect(stderr.text()).not.toContain("secret-token"); expect(stderr.text()).not.toContain("javascript:"); expect(stderr.text()).not.toContain("spoof"); expect(stderr.text()).not.toContain("\u001B]8;;javascript:");