Skip to content

feat(credentials): configurable phantom format for prefix-sniffing clients - #1489

Open
kipz wants to merge 1 commit into
nolabs-ai:mainfrom
kipz:kipz/oauth-phantom-format
Open

feat(credentials): configurable phantom format for prefix-sniffing clients#1489
kipz wants to merge 1 commit into
nolabs-ai:mainfrom
kipz:kipz/oauth-phantom-format

Conversation

@kipz

@kipz kipz commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Linked Issue

Closes #1486

Summary

Adds an optional format template 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's apiKey.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 both oauth_capture response fields (kind: opaque) and ambient command_policies credentials, sharing one PhantomTemplate and one egress-rewrite helper. A format/token-shape mismatch warns but still resolves.

Reviewer note: relaxed nonce guard

This PR removes the is_nonce shape check from resolve_env_entry and resolve_nonce in TokenBroker, 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 no nono_ 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 checks GrantSet::admits(consumer).

Agent Disclosure (if applicable)

Generated by an AI agent (Claude Code). Reuses the existing credential_format {} convention and the NonceResolver trait; all new logic is original. Confirmed compliant with the repository's coding and security requirements.

Test Plan

  • make ci clean (clippy -D warnings -D clippy::unwrap_used, fmt, tests).
  • New unit tests cover 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; TokenBroker templated store/issue/env-resolve/scan_and_reissue with consumer gating; profile + command-policy validation.
  • Verified end-to-end downstream: a coding agent's Anthropic OAuth login now mints an sk-ant-oat01-<64hex> phantom, is classified as an OAuth bearer, and resolves on egress (previously misrouted to x-api-key401 invalid x-api-key).

Checklist

  • An issue exists and is linked above
  • All commits are signed-off, using DCO
  • All new code follows the project's coding standards (CLAUDE.md) and is covered by tests
  • Public-facing changes are paired with documentation updates
  • Release note has been added to CHANGELOG.md if needed

Agent Compliance Check (Required for AI/Automated PRs)

  • I am not prohibited from contributing under this policy
  • An issue already exists
  • I disclosed that I am an agent in the issue discussion
  • I described my intent and approach in the issue discussion
  • I reviewed repository coding and security rules for the affected area
  • I provided required attribution for reused or adapted code
  • I did not use forbidden patterns such as unwrap/expect
  • I used NonoError where required
  • I validated and canonicalized all relevant paths
  • This PR matches the approved or disclosed issue scope

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

PR Review Summary

Size

Metric Value
Lines added +1688
Lines removed -301
Total changed 1989
Classification Large (> 300 lines)

Affected crates

  • crates/nono-proxydownstream consumers depend on this crate. API or behaviour changes will affect external callers; treat any breaking change with extra scrutiny.
  • crates/nono-cli — CLI changes. Verify argument parsing, flag documentation, and UX behaviour across supported platforms.

Blast radius — Broad

This PR touches: source code,documentation,configuration / policy files


Updated automatically on each push to this PR.

kipz added a commit to kipz/nono that referenced this pull request Jul 22, 2026
…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
kipz added a commit to kipz/nono that referenced this pull request Jul 23, 2026
kipz added a commit to kipz/nono that referenced this pull request Jul 23, 2026
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>
@kipz
kipz force-pushed the kipz/oauth-phantom-format branch from 1615791 to 22e71e7 Compare July 24, 2026 15:33
@lukehinds lukehinds added this to the 0.70 milestone Jul 27, 2026
kipz added a commit to kipz/nono that referenced this pull request Aug 3, 2026
@kipz
kipz force-pushed the kipz/oauth-phantom-format branch from 22e71e7 to ae26840 Compare August 3, 2026 13:44
@kipz
kipz force-pushed the kipz/oauth-phantom-format branch from ae26840 to 0cf4a92 Compare August 3, 2026 14:56
@kipz
kipz force-pushed the kipz/oauth-phantom-format branch from 0cf4a92 to 1116ea2 Compare August 3, 2026 21:44
@kipz
kipz force-pushed the kipz/oauth-phantom-format branch from 1116ea2 to a2bad8e Compare August 6, 2026 10:30
@kipz
kipz force-pushed the kipz/oauth-phantom-format branch from a2bad8e to 3dc0f88 Compare August 9, 2026 11:14
@kipz
kipz force-pushed the kipz/oauth-phantom-format branch from 3dc0f88 to 00204ee Compare August 9, 2026 12:17
kipz added a commit to kipz/nono that referenced this pull request Aug 9, 2026
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
kipz added a commit to kipz/nono that referenced this pull request Aug 9, 2026
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>
kipz added a commit to kipz/nono that referenced this pull request Aug 9, 2026
@kipz
kipz force-pushed the kipz/oauth-phantom-format branch from 00204ee to fee2ad8 Compare August 10, 2026 10:59
…ients

Signed-off-by: James Carnegie <me@kipz.org>
@kipz
kipz force-pushed the kipz/oauth-phantom-format branch from fee2ad8 to 12d77f7 Compare August 10, 2026 14:49
@kipz
kipz marked this pull request as ready for review August 11, 2026 09:18

@nogent-nolabs-ai nogent-nolabs-ai 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.

nogent code review

1 potential bug; 1 recommendation.

Automated code + security review. CI already covers clippy, rustfmt, tests, cargo-audit and commit-lint.

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> {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

oauth_capture: pi rejects Anthropic OAuth phantom as "invalid x-api-key" — needs configurable phantom prefix

2 participants