feat(credentials): configurable phantom format for prefix-sniffing clients - #1489
feat(credentials): configurable phantom format for prefix-sniffing clients#1489kipz wants to merge 1 commit into
Conversation
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
PR Review SummarySize
Affected crates
Blast radius — BroadThis PR touches: source code,documentation,configuration / policy files Updated automatically on each push to this PR. |
…ing clients # Conflicts: # crates/nono-cli/src/proxy_runtime.rs # crates/nono-cli/src/tool-sandbox/token_broker.rs # crates/nono-proxy/src/server.rs # crates/nono-proxy/src/tls_intercept/handle.rs # crates/nono-proxy/src/token.rs
…at for prefix-sniffing clients
Add format (nolabs-ai#1489) and upgrades (nolabs-ai#1443) fields to vault-oidc (nolabs-ai#1476) test initializers that only one side of the merge reconciled. Signed-off-by: James Carnegie <me@kipz.org>
1615791 to
22e71e7
Compare
22e71e7 to
ae26840
Compare
ae26840 to
0cf4a92
Compare
0cf4a92 to
1116ea2
Compare
1116ea2 to
a2bad8e
Compare
a2bad8e to
3dc0f88
Compare
3dc0f88 to
00204ee
Compare
Signed-off-by: James Carnegie <me@kipz.org> # Conflicts: # crates/nono-cli/src/proxy_runtime.rs # crates/nono-cli/src/tool-sandbox/token_broker.rs # crates/nono-proxy/src/server.rs # crates/nono-proxy/src/tls_intercept/h2_forward.rs # crates/nono-proxy/src/tls_intercept/handle.rs # crates/nono-proxy/src/token.rs
Resolutions: - websocket.rs: kept nolabs-ai#1469's bounded line reads, added RFC 6455 Sec-WebSocket-Accept validation from nolabs-ai#1443. - handle.rs: adopted nolabs-ai#1443's run_websocket_tunnel split; kept the 4-arg resolve_nonce_in_header_value (redeem_phantoms) from nolabs-ai#1469 and nolabs-ai#1489's templated-phantom rewrite path. - token.rs: CRLF/NUL fail-closed guard moved into rewrite_first_phantom so the grant-set/templated path is covered too. - reverse.rs: header_pairs replaced by http1::parse_header_fields. - Deduplicated RouteConfig 'upgrades' fields introduced twice by the merge. Signed-off-by: James Carnegie <me@kipz.org>
…at for prefix-sniffing clients
00204ee to
fee2ad8
Compare
…ients Signed-off-by: James Carnegie <me@kipz.org>
fee2ad8 to
12d77f7
Compare
| impl PhantomTemplate { | ||
| /// Parse a template containing exactly one `{}`. Control bytes are rejected: a | ||
| /// rendered phantom lands in env-var entries, where CR/LF smuggles headers. | ||
| pub fn parse(template: &str) -> std::result::Result<Self, String> { |
There was a problem hiding this comment.
Warning
🐛 [MEDIUM · bug] Allowing a PhantomTemplate with an empty prefix and suffix (e.g. "{}") causes any 64-character hex string (such as a commit hash or SHA-256 digest) to match the template. This causes contains_phantom to return true on benign inputs containing hashes, which can lead to unexpected HTTP parsing errors and request rejections in rewrite_form_request_body. Enforce that the template prefix must be non-empty in PhantomTemplate::parse to prevent this.
Linked Issue
Closes #1486
Summary
Adds an optional
formattemplate to credential phantoms (e.g."sk-ant-oat01-{}",{}= random 64-hex body) so the visible placeholder follows a literal shape a client can sniff to classify the credential — e.g.pi'sapiKey.includes("sk-ant-oat"). The template is stripped on egress before the real credential is substituted, so it never reaches upstream, and the body is always random (no real-token bytes copied). Supported on bothoauth_captureresponse fields (kind: opaque) and ambientcommand_policiescredentials, sharing onePhantomTemplateand one egress-rewrite helper. A format/token-shape mismatch warns but still resolves.Reviewer note: relaxed nonce guard
This PR removes the
is_nonceshape check fromresolve_env_entryandresolve_nonceinTokenBroker, so redemption now relies on the map lookup alone. That is correct — map keys are only ever phantoms the broker itself minted, and a templated phantom has nonono_marker to check for — but it is a security-relevant relaxation, so flagging it explicitly rather than leaving it buried in the diff. Grant-set enforcement is unchanged: every redemption path still checksGrantSet::admits(consumer).Agent Disclosure (if applicable)
Generated by an AI agent (Claude Code). Reuses the existing
credential_format{}convention and theNonceResolvertrait; all new logic is original. Confirmed compliant with the repository's coding and security requirements.Test Plan
make ciclean (clippy-D warnings -D clippy::unwrap_used, fmt, tests).PhantomTemplate(parse/render/matches/find_in incl. empty-prefix, short/non-hex body, UTF-8 boundary safety),find_bare_nonce,rewrite_first_phantom; OAuth-capture templated round-trip, drift-warn, multi-template, jwt-kind rejection;TokenBrokertemplated store/issue/env-resolve/scan_and_reissuewith consumer gating; profile + command-policy validation.sk-ant-oat01-<64hex>phantom, is classified as an OAuth bearer, and resolves on egress (previously misrouted tox-api-key→401 invalid x-api-key).Checklist
CHANGELOG.mdif neededAgent Compliance Check (Required for AI/Automated PRs)