fix(logs): record the configured shadow prefix, not the caller's model string - #2170
Conversation
…l string The release audit of origin/main..origin/dev found this: the shadow-call intercept matches by prefix, so a caller can send "gpt-5.6-luna" followed by arbitrary text and still be intercepted -- and the whole raw string was recorded as shadowCallRewrittenFrom, which is persisted to usage.jsonl and served from /api/logs. The sanitizer on that path strips control characters BEFORE redacting, so "gpt-5.6-luna\nBearer sk-..." became "gpt-5.6-lunaBearer sk-...", losing the word boundary the Bearer rule needs. Worse, the runtime redactor is a deny-list: an AIza-shaped Google key has no rule at all and survived verbatim into the log file. Verified against the shipped sanitizer. Recording the operator-configured prefix that matched removes the class instead of adding another pattern to the deny-list. No caller-controlled string reaches the field, so the value is always one the operator chose.
|
✅ Deterministic PR hygiene checks passed. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
Included review availability: Your plan provides up to 10 included reviews per hour; 7 remain after this review. 📝 WalkthroughWalkthroughThe change adds configured source-model prefix matching and uses it for shadow-call rewrite logging. Request-path tests verify that raw model suffixes, including newline-appended credential-like content, are not recorded. ChangesShadow-call attribution
Estimated code review effort: 2 (Simple) | ~10 minutes Merge Risk: ⚪ Minimal · up to The change limits persisted shadow-call log values to configured prefixes and has passing targeted, type-check, full-suite, and privacy-scan verification; no actionable merge-blocking risk remains beyond normal checks. Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
리뷰 · 우선순위 74 / 80실화임. 섀도 인터셉트가 prefix 매칭이라 호출자가 픽이 맞음. 호출자 문자열이 아니라 매칭된 점수는 74임. 2.28 블로커. #2166 sanitizer 위에 있는 구멍이라 그거 넣었다고 안 막힘. 해결방안: 머지. AIza 밀수 테스트랑 non-default prefix 테스트 유지. deny-list 확장으로 대체하지 말 것. 이 댓글은 grok-bot이 작성했습니다 |
Summary
Release audit of
origin/main..origin/devfound a credential-leak path in the shadow-call logging that landed in this range. This is the fix.The defect. The shadow-call intercept matches by prefix, so a caller can send
gpt-5.6-lunafollowed by arbitrary text and still be intercepted. The whole raw string was then recorded asshadowCallRewrittenFrom, which is persisted tousage.jsonland served from/api/logs.The sanitizer on that path is not sufficient protection, for two independent reasons — both verified against the shipped code:
Bearerrule looks for a word boundary.AIza-shaped Google key has no rule at all and survives verbatim into the log file.The fix. Record the operator-configured prefix that matched, not the caller's string.
shadowSourceModelPrefix()returns the entry fromsourceModelsthat produced the match, so the field can only ever hold a value the operator configured. That removes the class rather than adding one more pattern to a deny-list — which is the right shape here, because the next unrecognized credential family would reopen it.Verification
src/lib/shadow-call.tsandsrc/server/responses/core.tsfails exactly the two new tests (15 pass / 2 fail). The first pins theAIzasmuggling case; the second pins that a non-default configured prefix is recorded as itself, so the fix cannot degrade into a hardcoded string.bun x tsc --noEmit— exit 0 (onssh lidge).bun test --isolate tests/responses-shadow-intercept.test.ts— 17 pass / 0 fail.bun run testonssh lidge— 13713 pass / 15 skip / 0 fail across 866 files.bun run privacy:scan— passed.Checklist
This is the security-sensitive change: it narrows what can reach a persisted log field. No credential is logged by the new code, and the field's value set is now bounded by operator configuration.
Summary by CodeRabbit
Security & Privacy
Bug Fixes
Tests