Skip to content

fix(redact,responses): close the credential and lifecycle-identity variant gaps - #1038

Merged
lidge-jun merged 2 commits into
devfrom
codex/stack-06-redaction-and-identity-followup
Aug 5, 2026
Merged

fix(redact,responses): close the credential and lifecycle-identity variant gaps#1038
lidge-jun merged 2 commits into
devfrom
codex/stack-06-redaction-and-identity-followup

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Follow-up to the bug-stack campaign. An adversarial re-review of the merged stack found that two of the fixes in it were right in shape but too narrow, and named the exact variants that still fail. This closes both.

Redaction: the colon rule stopped at the first delimiter

redactSecretString gained a colon-labelled rule in #1020 so that an upstream error body quoting x-api-key: <value> back at the caller would not leak the credential. Its value class excluded quotes and stopped at whitespace and semicolons, which left every delimiter-bearing form intact. Probed against the merged tip:

Input Before
x-api-key: "quotedcredential123456" unchanged
Authorization: Basic dXNlcjpwYXNz Authorization: [REDACTED] dXNlcjpwYXNz
Cookie: session=secret-one; csrf=secret-two Cookie: [REDACTED]; csrf=secret-two

formatAnthropicErrorBody returns this text to a client, so those suffixes were reachable.

A credential header's value is the rest of the line, so that is what gets masked now. Bearer remains the one readable exception — an auth scheme is diagnostically useful and its token is already masked by the dedicated rule, so the scheme word survives and only what follows is consumed. Other schemes are masked whole, since their payload is the credential.

Ordinary diagnostics are unaffected: model: gpt-5.5, status: 429, request: ocx-abc123 pass through unchanged, and a multi-line body masks each credential line independently without collapsing the lines between them.

Snapshot repair: fail-closed covered one event, not its siblings

#1025 made a contradictory item_id on response.output_text.done fail closed. The same contradiction on response.content_part.done was still merely ignored — the item stayed open and the completed terminal went on to fabricate the entire closure sequence (content_part.addedoutput_text.donecontent_part.doneoutput_item.done) on a stream whose identity model we had already observed to be wrong. response.output_text.delta had the same hole, where reconstructing from only the accepted deltas would ship a message the upstream never assembled that way.

The identity contract now covers all of them. An omitted item_id stays legitimate and is still correlated by output_index — that is the common shape for gateways that do not echo ids, and a test asserts it so the guard cannot regress into failing closed on healthy streams.

Verification

  • bun run typecheck clean.
  • Focused suites green; the snapshot-repair suite is 27 pass / 0 fail.
  • Full suite on the Linux box.

Summary by CodeRabbit

  • Bug Fixes
    • Improved credential redaction for Bearer tokens, cookies, Basic authentication, quoted values, and other labeled credentials while preserving safe formatting.
    • Prevented response repair from injecting or reconstructing content when lifecycle events contain mismatched item identifiers.
    • Continued supporting valid events that omit item identifiers when output correlation is available.
  • Tests
    • Added coverage for credential masking and response-stream repair edge cases.

…ixes

Re-review of the merged stack found both fixes were correct in shape but too
narrow, and named the exact variants that still fail.

Redaction: the colon rule tokenized the value on quotes, spaces, and
semicolons, so every delimiter-bearing form still leaked —
`x-api-key: "quoted…"` kept the whole quoted secret,
`Authorization: Basic dXNlcjpwYXNz` kept the payload after the scheme, and
`Cookie: a=1; b=2` kept everything after the first `;`. A credential
header's value is the rest of the line, so that is what is masked now.
`Bearer` stays the readable exception: the scheme word survives and only its
token is consumed.

Snapshot repair: fail-closed on a contradictory item_id was applied to
output_text.done but not to its siblings. A foreign content_part.done was
ignored, the item stayed open, and the terminal fabricated the entire closure
sequence anyway — the same failure family, one event over. The same identity
contract now covers content_part.added/done and output_text.delta. An OMITTED
item_id remains legitimate and is still correlated by output_index, which is
asserted so the guard cannot regress into failing closed on healthy streams.
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The changes improve credential redaction and response snapshot repair. Redaction preserves Bearer formatting and masks complete credential lines. Snapshot repair rejects mismatched item_id values while retaining valid output_index correlation.

Changes

Credential Redaction

Layer / File(s) Summary
Redaction rules and coverage
src/lib/redact.ts, tests/redact.test.ts
Bearer redaction preserves the scheme and whitespace. Colon-labelled redaction supports additional names, preserves spacing, masks the full line, and skips Bearer or already-redacted values. Tests cover quoted values, Basic authentication, cookies, and independent line processing.

Response Snapshot Repair

Layer / File(s) Summary
Item correlation safeguards
src/server/responses-snapshot-repair.ts, tests/responses-snapshot-repair.test.ts
Content-part and output-text events with conflicting item_id values now stop repair and reconstruction. Events without item_id remain correlated by output_index. Regression tests cover both paths.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested reviewers: wibias, ingwannu

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes both main changes: credential redaction fixes and lifecycle identity validation fixes.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/stack-06-redaction-and-identity-followup

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the bug Something isn't working label Aug 5, 2026
Follow-up within this PR. Masking the whole colon-labelled value also ate the
Bearer form, which broke a Vertex diagnostic: the error text quotes a header in
prose (`… Authorization: Bearer <tok> at /path/file.json`) and the trailing
path marker disappeared with the token.

Bearer is now excluded from the colon rule by a lookahead placed BEFORE the
whitespace is consumed — a greedy whitespace class inside the capture moved the
lookahead's evaluation point past the scheme word, which is why the first two
attempts silently kept matching. The dedicated Bearer rule handles that case
and now preserves the space it matched, so `Authorization:` keeps its
separator.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 30360ea60a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +379 to +381
if (open && itemId !== undefined && itemId !== open.itemId) {
taintAndRelease();
return [changed ? jsonBlock(nextEvent) : block];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Check content-part identity before validating part

When a sparse/malformed provider sends response.content_part.done or .added with a wrong item_id but omits or mangles part, this new mismatch check never runs because the whole block is gated by isPlainObject(event.part). The tracked item then stays open, so a later valid delta plus response.completed can still synthesize the closure sequence this patch is trying to suppress; move the identity check ahead of the part structural check or taint malformed content-part events that carry a foreign item_id.

Useful? React with 👍 / 👎.

Comment thread src/lib/redact.ts Outdated
// useful and the dedicated rule above already masks its token, so the scheme
// word is preserved and only what follows is consumed here. Other schemes
// (Basic, Digest, …) are masked whole, since their payload is the credential.
[/\b((?:x-api-key|x-goog-api-key|x-amz-security-token|api[_-]?key|apiKey|access[_-]?token|accessToken|refresh[_-]?token|refreshToken|id[_-]?token|client[_-]?secret|clientSecret|authorization|proxy-authorization|cookie|set-cookie|password|secret|token)\s*:\s*(?:Bearer\s+)?)(?!\s*$)[^\r\n]+/gi, `$1${REDACTED_SECRET}`],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve trailing JSON after redacting colon labels

When redactSecretString is given raw JSON/error text containing a colon-labelled credential inside a string, this end-of-line match consumes the closing quote and the rest of the JSON line; for example {"error":{"message":"x-api-key: secret"}} becomes an unterminated {"error":{"message":"x-api-key: [REDACTED]. Several callers redact raw upstream error text before returning diagnostics, so this can mangle otherwise parseable provider payloads; keep the whole-header behavior only for actual header lines or preserve trailing JSON punctuation.

Useful? React with 👍 / 👎.

Comment on lines +417 to +419
if (open && itemId !== undefined && itemId !== open.itemId) {
taintAndRelease();
return [changed ? jsonBlock(nextEvent) : block];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Taint foreign deltas even when the delta is malformed

This foreign-item_id guard only exists inside the typeof event.delta === "string" branch, so a malformed response.output_text.delta with the tracked output_index but a different item_id leaves the original item open. A later completed terminal can still synthesize output_text.done/output_item.done for that item, even though the stream already contradicted its identity; perform the mismatch check for any response.output_text.delta with an output_index before validating the delta payload.

Useful? React with 👍 / 👎.

Comment thread src/lib/redact.ts Outdated
// useful and the dedicated rule above already masks its token, so the scheme
// word is preserved and only what follows is consumed here. Other schemes
// (Basic, Digest, …) are masked whole, since their payload is the credential.
[/\b((?:x-api-key|x-goog-api-key|x-amz-security-token|api[_-]?key|apiKey|access[_-]?token|accessToken|refresh[_-]?token|refreshToken|id[_-]?token|client[_-]?secret|clientSecret|authorization|proxy-authorization|cookie|set-cookie|password|secret|token)\s*:\s*(?:Bearer\s+)?)(?!\s*$)[^\r\n]+/gi, `$1${REDACTED_SECRET}`],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Cover set-cookie2 in colon-labelled redaction

This replacement adds set-cookie to the colon-labelled header list but leaves set-cookie2 out, even though the rest of the redaction module already treats set-cookie2 as sensitive. When an upstream error echoes a Set-Cookie2: session=...; ... line, the delimiter-bearing cookie value still survives into client-visible diagnostics; include set-cookie2 alongside set-cookie or derive this list from the sensitive-header set.

AGENTS.md reference: AGENTS.md:L218-L223

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/redact.ts`:
- Line 39: Add set-cookie2 to the colon-labelled credential alternative list in
the redaction regex used by redactSecretString, preserving the existing
case-insensitive matching and replacement behavior. Add a regression assertion
in the redactSecretString tests confirming that “Set-Cookie2: session=secret” is
redacted.
- Line 6: Update the Bearer redaction pattern in redact.ts to match all token
lengths and terminate without the trailing word boundary, covering short,
quoted, and padding-character values. In the redaction logic near the existing
Bearer skip condition, skip only the exact Bearer [REDACTED] form and include
set-cookie2 among the recognized key alternatives. Add regression coverage for
short, quoted, and padded Bearer values plus Set-Cookie2 lines.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a1cb50a7-ba17-4d2c-bcc3-1fb9ff554b74

📥 Commits

Reviewing files that changed from the base of the PR and between 2d7aa90 and 753781d.

📒 Files selected for processing (4)
  • src/lib/redact.ts
  • src/server/responses-snapshot-repair.ts
  • tests/redact.test.ts
  • tests/responses-snapshot-repair.test.ts

Comment thread src/lib/redact.ts

const SECRET_VALUE_PATTERNS: Array<[RegExp, string]> = [
[/\bBearer\s+[A-Za-z0-9._~+/=-]{8,}\b/gi, `Bearer ${REDACTED_SECRET}`],
[/\b(Bearer)(\s+)[A-Za-z0-9._~+/=-]{8,}\b/gi, `$1$2${REDACTED_SECRET}`],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- src/lib/redact.ts ---'
cat -n src/lib/redact.ts

printf '%s\n' '--- relevant tests ---'
cat -n tests/redact.test.ts | sed -n '1,155p'

printf '%s\n' '--- standalone regex behavior probe ---'
python3 - <<'PY'
import re

bearer = re.compile(r'\b(Bearer)(\s+)[A-Za-z0-9._~+/=-]{8,}\b', re.I)
redacted = '[REDACTED]'

def bearer_pass(value):
    first = bearer.sub(lambda m: f'{m.group(1)}{m.group(2)}{redacted}', value)
    # Model the colon-labelled skip described in the review: preserve the
    # whole value when it starts with Bearer.
    m = re.match(r'(?i)([^:\r\n]+):([ \t]*)(Bearer\b.*)$', value)
    return value if m else first

cases = [
    'Authorization: Bearer abc',
    'Authorization: Bearer abcdefgh',
    'Authorization: Bearer abcdefgh=',
    'Authorization: Bearer "abcdefgh"',
    'Authorization: Bearer abcdefgh,',
]
for case in cases:
    print(case, '=>', bearer_pass(case))
PY

Repository: lidge-jun/opencodex

Length of output: 15775


🏁 Script executed:

#!/bin/bash
set -eu

cat -n src/lib/redact.ts
cat -n tests/redact.test.ts | sed -n '1,155p'

python3 - <<'PY'
import re

pattern = re.compile(r'\b(Bearer)(\s+)[A-Za-z0-9._~+/=-]{8,}\b', re.I)

for value in [
    'Authorization: Bearer abc',
    'Authorization: Bearer abcdefgh',
    'Authorization: Bearer abcdefgh=',
    'Authorization: Bearer "abcdefgh"',
]:
    print(value, '=>', pattern.sub(r'\1\2[REDACTED]', value))
PY

Repository: lidge-jun/opencodex

Length of output: 15623


🏁 Script executed:

#!/bin/bash
set -eu

node - <<'JS'
const REDACTED = "[REDACTED]";

const current = [
  [/\b(Bearer)(\s+)[A-Za-z0-9._~+/=-]{8,}\b/gi, "$1$2" + REDACTED],
  [/\b((?:x-api-key|x-goog-api-key|x-amz-security-token|api[_-]?key|apiKey|access[-_]?token|accessToken|refresh[-_]?token|refreshToken|id[-_]?token|client[-_]?secret|clientSecret|authorization|proxy-authorization|cookie|set-cookie|password|secret|token)\s*:)(?![^\S\r\n]*(?:Bearer\b|\[REDACTED\]|\r?\n|$))([^\S\r\n]*)[^\r\n]+/gi, "$1$2" + REDACTED],
];

const proposed = [
  [/\b(Bearer)(\s+)[A-Za-z0-9._~+/=-]+/gi, "$1$2" + REDACTED],
  [/\b((?:x-api-key|x-goog-api-key|x-amz-security-token|api[_-]?key|apiKey|access[-_]?token|accessToken|refresh[-_]?token|refreshToken|id[-_]?token|client[-_]?secret|clientSecret|authorization|proxy-authorization|cookie|set-cookie|password|secret|token)\s*:)(?![^\S\r\n]+(?:Bearer\b[^\S\r\n]+\[REDACTED\]|\[REDACTED\]|\r?\n|$))([^\S\r\n]*)[^\r\n]+/gi, "$1$2" + REDACTED],
];

function apply(rules, input) {
  return rules.reduce((value, [pattern, replacement]) =>
    value.replace(pattern, replacement), input);
}

const cases = [
  "Authorization: Bearer abc",
  "Authorization: Bearer abcdefgh12345678",
  "Authorization: Bearer abcdefgh=",
  'Authorization: Bearer "abcdefgh"',
  "Authorization: Bearer abcdefgh,",
  "Set-Cookie2: session=secret",
];

for (const input of cases) {
  console.log(JSON.stringify({input, current: apply(current, input), proposed: apply(proposed, input)}));
}
JS

Repository: lidge-jun/opencodex

Length of output: 948


🏁 Script executed:

#!/bin/bash
set -eu

node - <<'JS'
const REDACTED = "[REDACTED]";
const pattern = /\b((?:x-api-key|x-goog-api-key|x-amz-security-token|api[_-]?key|apiKey|access[-_]?token|accessToken|refresh[-_]?token|refreshToken|id[-_]?token|client[-_]?secret|clientSecret|authorization|proxy-authorization|cookie|set-cookie|password|secret|token)\s*:)(?![^\S\r\n]*(?:Bearer\b|\[REDACTED\]|\r?\n|$))([^\S\r\n]*)[^\r\n]+/gi;

for (const input of [
  "Authorization: Bearer abc",
  "Authorization: Bearer abcdefgh=",
  'Authorization: Bearer "abcdefgh"',
  "Set-Cookie2: session=secret",
]) {
  const bearer = input.replace(
    /\b(Bearer)(\s+)[A-Za-z0-9._~+/=-]{8,}\b/gi,
    "$1$2[REDACTED]",
  );
  console.log(JSON.stringify({
    input,
    afterBearerRule: bearer,
    final: bearer.replace(pattern, "$1$2[REDACTED]"),
  }));
}
JS

Repository: lidge-jun/opencodex

Length of output: 692


Sensitive Data Exposure (CWE-200): Exposure of Sensitive Information to an Unauthorized Actor

Reachability: External · Exploitability: Moderate

Reachability path
● Entry
  src/server/responses-snapshot-repair.ts:45
  isPlainObject
│
▼
● Sink
  src/lib/redact.ts

Complete Bearer and Set-Cookie2 redaction.

  • In src/lib/redact.ts:6, remove {8,} and the trailing \b. The current rule leaks short tokens, quoted values, and padding such as =.
  • In src/lib/redact.ts:39, skip only Bearer [REDACTED], and add set-cookie2 to the key alternatives. Add regression tests for short, quoted, padded Bearer values and Set-Cookie2 lines.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/redact.ts` at line 6, Update the Bearer redaction pattern in
redact.ts to match all token lengths and terminate without the trailing word
boundary, covering short, quoted, and padding-character values. In the redaction
logic near the existing Bearer skip condition, skip only the exact Bearer
[REDACTED] form and include set-cookie2 among the recognized key alternatives.
Add regression coverage for short, quoted, and padded Bearer values plus
Set-Cookie2 lines.

Source: Path instructions

Comment thread src/lib/redact.ts
// already replaced `Bearer <tok>` with `Bearer [REDACTED]`. Skipping a value
// that is already redacted keeps this rule from eating that result — and from
// eating the trailing diagnostics after it.
[/\b((?:x-api-key|x-goog-api-key|x-amz-security-token|api[_-]?key|apiKey|access[_-]?token|accessToken|refresh[_-]?token|refreshToken|id[_-]?token|client[_-]?secret|clientSecret|authorization|proxy-authorization|cookie|set-cookie|password|secret|token)\s*:)(?![^\S\r\n]*(?:Bearer\b|\[REDACTED\]|\r?\n|$))([^\S\r\n]*)[^\r\n]+/gi, `$1$2${REDACTED_SECRET}`],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- src/lib/redact.ts ---'
sed -n '1,180p' src/lib/redact.ts

printf '%s\n' '--- relevant redact tests ---'
sed -n '1,155p' tests/redact.test.ts

printf '%s\n' '--- focused behavioral probe of the redaction rules ---'
python3 - <<'PY'
from pathlib import Path
import re

text = Path("src/lib/redact.ts").read_text()
line = next(
    line.strip()
    for line in text.splitlines()
    if "x-api-key|x-goog-api-key" in line and "set-cookie" in line
)
pattern_text = line.split("[/", 1)[1].split("/gi", 1)[0]
pattern = re.compile(pattern_text, re.IGNORECASE)
replacement = r"\1\2[REDACTED]"

for value in [
    "Set-Cookie: session=secret",
    "Set-Cookie2: session=secret",
    "set-cookie2: session=secret\nstatus: 429",
]:
    print(repr(value), "=>", repr(pattern.sub(replacement, value)))
PY

Repository: lidge-jun/opencodex

Length of output: 13590


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- src/lib/redact.ts ---'
sed -n '1,180p' src/lib/redact.ts

printf '%s\n' '--- relevant redact tests ---'
sed -n '1,155p' tests/redact.test.ts

printf '%s\n' '--- focused behavioral probe ---'
python3 - <<'PY'
from pathlib import Path
import re

text = Path("src/lib/redact.ts").read_text()
line = next(line.strip() for line in text.splitlines()
            if "x-api-key|x-goog-api-key" in line and "set-cookie" in line)
pattern_text = line.split("[/", 1)[1].split("/gi", 1)[0]
pattern = re.compile(pattern_text, re.IGNORECASE)

for value in [
    "Set-Cookie: session=secret",
    "Set-Cookie2: session=secret",
]:
    print(repr(value), "=>", repr(pattern.sub(r"\1\2[REDACTED]", value)))
PY

Repository: lidge-jun/opencodex

Length of output: 13566


🏁 Script executed:

#!/bin/bash
set -eu

node - <<'JS'
const REDACTED_SECRET = "[REDACTED]";
const patterns = [
  [/\b(Bearer)(\s+)[A-Za-z0-9._~+/=-]{8,}\b/gi, `$1$2${REDACTED_SECRET}`],
  [/\b(sk-[A-Za-z0-9][A-Za-z0-9._-]{6,})\b/g, REDACTED_SECRET],
  [/\b(gh[pousr]_[A-Za-z0-9_]{8,}|github_pat_[A-Za-z0-9_]{20,})\b/g, REDACTED_SECRET],
  [/\btid=[A-Za-z0-9-]+(?:;[A-Za-z0-9_.-]+=[^;\s"']*)+(?::[A-Za-z0-9+/=_-]+)?/g, REDACTED_SECRET],
  [/\b((?:api[_-]?key|access[_-]?token|refresh[_-]?token|id[_-]?token|client[_-]?secret|refreshToken|accessToken|clientSecret|apiKey)=)([^&\s"',;]+)/gi, `$1${REDACTED_SECRET}`],
  [/\b((?:x-api-key|x-goog-api-key|x-amz-security-token|api[_-]?key|apiKey|access[_-]?token|accessToken|refresh[_-]?token|refreshToken|id[_-]?token|client[_-]?secret|clientSecret|authorization|proxy-authorization|cookie|set-cookie|password|secret|token)\s*:)(?![^\S\r\n]*(?:Bearer\b|\[REDACTED\]|\r?\n|$))([^\S\r\n]*)[^\r\n]+/gi, `$1$2${REDACTED_SECRET}`],
  [/(("token"\s*:\s*"))([^"]+)(")/gi, `$1${REDACTED_SECRET}$4`],
];

function redact(value) {
  return patterns.reduce((result, [pattern, replacement]) =>
    result.replace(pattern, replacement), value);
}

for (const input of [
  "Set-Cookie: session=secret",
  "Set-Cookie2: session=secret",
  "set-cookie2: session=secret\nstatus: 429",
]) {
  const output = redact(input);
  console.log(JSON.stringify({ input, output, leaked: output.includes("session=secret") }));
}
JS

Repository: lidge-jun/opencodex

Length of output: 457


🏁 Script executed:

#!/bin/bash
set -eu

node - <<'JS'
const REDACTED_SECRET = "[REDACTED]";
const pattern = /\b((?:x-api-key|x-goog-api-key|x-amz-security-token|api[_-]?key|apiKey|access[_-]?token|accessToken|refresh[_-]?token|refreshToken|id[_-]?token|client[_-]?secret|clientSecret|authorization|proxy-authorization|cookie|set-cookie|password|secret|token)\s*:)(?![^\S\r\n]*(?:Bearer\b|\[REDACTED\]|\r?\n|$))([^\S\r\n]*)[^\r\n]+/gi;

for (const input of [
  "Set-Cookie: session=secret",
  "Set-Cookie2: session=secret",
  "set-cookie2: session=secret\nstatus: 429",
]) {
  const output = input.replace(pattern, `$1$2${REDACTED_SECRET}`);
  console.log(JSON.stringify({ input, output, leaked: output.includes("session=secret") }));
}
JS

Repository: lidge-jun/opencodex

Length of output: 457


Sensitive Data Exposure (CWE-200): Exposure of Sensitive Information to an Unauthorized Actor

Reachability: External

Reachability path
● Entry
  src/server/responses-snapshot-repair.ts:45
  isPlainObject
│
▼
● Sink
  src/lib/redact.ts

Add set-cookie2 to the colon-labelled credential rule.

redactSecretString("Set-Cookie2: session=secret") currently returns the secret unchanged. Add set-cookie2 to the alternative list in src/lib/redact.ts:39 and add a regression assertion in tests/redact.test.ts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/redact.ts` at line 39, Add set-cookie2 to the colon-labelled
credential alternative list in the redaction regex used by redactSecretString,
preserving the existing case-insensitive matching and replacement behavior. Add
a regression assertion in the redactSecretString tests confirming that
“Set-Cookie2: session=secret” is redacted.

Source: Path instructions

@lidge-jun
lidge-jun merged commit 4cfdc71 into dev Aug 5, 2026
22 checks passed
lidge-jun added a commit that referenced this pull request Aug 5, 2026
…isory

All thirteen campaign PRs are merged and every merge commit is an ancestor of
dev. The two review follow-ups (#1038, #1040) are recorded with the reason they
exist, and the one thing deliberately left unfinished — UTS #39 confusable
coverage — points at the draft security advisory rather than a public issue,
since it describes a redaction weakness and not a shipped fix.
Wibias pushed a commit to Wibias/opencodex that referenced this pull request Aug 5, 2026
Re-review of lidge-jun#1038. Exempting the bare scheme word from the colon rule meant a
credential only had to be prefixed with `Bearer` to pass through untouched,
because the dedicated Bearer rule matches a single opaque
`[A-Za-z0-9._~+/=-]{8,}` token and nothing else. Anything it could not parse
survived:

- `x-api-key: Bearer "smuggledcredential123456"` (quoted)
- `Authorization: Bearer custom:credential123456` (punctuation)
- `x-api-key: Bearer short` (under the length floor)

All three were reachable through formatErrorBody on the sidecar bridge.

The exemption now matches only the SANITIZED result — `Bearer [REDACTED]` —
so a value the Bearer rule could not sanitize is masked whole by the colon
rule. The readable case is unchanged: `Authorization: Bearer <token>` still
renders as `Bearer [REDACTED]` with trailing diagnostics intact.
@lidge-jun
lidge-jun deleted the codex/stack-06-redaction-and-identity-followup branch August 5, 2026 09:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant