Skip to content

feat(vertex): accept credential JSON with secure marker lifecycle (carry #6843) - #7139

Merged
lidge-jun merged 37 commits into
devfrom
carry/6843-vertex-sa-json
Oct 15, 2026
Merged

lidge-jun merged 37 commits into
devfrom
carry/6843-vertex-sa-json

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Oct 15, 2026 •

Copy link
Copy Markdown
Owner

Summary

Closes #6842.

Carry GSL-R's #6843 (head 59c4e2c018becc3527e4cabbd36afb203174f809) onto dev 7640a942b3, preserving its complete commit history with merge 10d06e0718a8. The feature starts at 36b9c1ef80; all subsequent contributor and maintainer fixes remain in the ancestry. No changes from the separate #6937 round-robin proposal are included.

Vertex's dedicated API-key pool accepts GCP service-account or authorized-user JSON. Verified OS credential-store writes commit only gcp-sa: markers to config/backups, with transactional batch admission and shared-reference-aware cleanup. Provider create/edit and CLI add reject credential JSON and direct users to the pool endpoint.

Follow-up corrections reject structured JSON carriers, malformed and JSON-escaped credential material in literal ingestion/restore and keychain alias resolution; marker errors omit account identity; cached bearer use verifies the selected stored payload remains readable. CLI and management DELETE capture live/durable marker candidates under the same mutation lock as persistence, then clean up only after success. Added generated service-account tests exercise existing 401/429 failover between markers and ordinary API keys, plus management DTO redaction. No GUI, provider preset, dependency, or workflow changes.

Security: implementation-side credential audit and regression verification completed; independent bounded read-only code/security review PASS on 65a032c7065d0b9a53a32d429a681b2ce4a540ad, with no remaining P0–P2 findings. The reviewer inspected source only; it did not independently rerun tests or qualify OS stores. Explicit maintainer security review is required before merge under MAINTAINERS.md. Fake OS-store tests do not qualify real macOS Keychain, Linux Secret Service or Windows Credential Manager behavior. No real user configuration, credentials, keychains or accounts were accessed. The original PR was not edited, commented on, closed or merged.

Co-authored-by: GSL-R 174104911+GSL-R@users.noreply.github.com

Verification

Local runtime: isolated Bun 1.4.3 in ignored scratch space; temporary OPENCODEX_HOME/CODEX_HOME/CLOUDSDK_CONFIG and fake credential stores. Root bun install and cd docs-site && bun install --frozen-lockfile succeeded. No new dependency or lockfile changes.

bun test tests/adapters/google/gcp-credential-boundary.test.ts tests/adapters/google/gcp-sa-marker.test.ts tests/adapters/google/gcp-sa-adapter.test.ts tests/adapters/google/gcp-adc.test.ts tests/providers/provider-key-store.test.ts tests/providers/provider-api-keys.test.ts tests/adapters/key-failover.test.ts tests/server/management-gcp-credential-input.test.ts tests/server/management-provider-atomicity.test.ts tests/server/management-provider-validation.test.ts tests/lab/core-lab-boundary.test.ts tests/test-layout.test.ts tests/test-layout-tooling.test.ts tests/ci-workflows/file-size-ratchet.test.ts
# 391 pass, 0 fail, 2823 assertions at 525cbdeab0.

# JSON-escape correction at 5a92c2b7cf: reran the eight affected files.
bun test tests/adapters/google/gcp-credential-boundary.test.ts tests/adapters/google/gcp-sa-marker.test.ts tests/adapters/google/gcp-sa-adapter.test.ts tests/adapters/google/gcp-adc.test.ts tests/providers/provider-key-store.test.ts tests/providers/provider-api-keys.test.ts tests/adapters/key-failover.test.ts tests/server/management-gcp-credential-input.test.ts
# 173 pass, 0 fail, 793 assertions. Six unchanged guard/management files retain their prior passing evidence.

# Final structured-carrier correction at 65a032c706: affected files plus size guard.
bun test tests/adapters/google/gcp-credential-boundary.test.ts tests/adapters/google/gcp-sa-marker.test.ts tests/adapters/google/gcp-sa-adapter.test.ts tests/adapters/google/gcp-adc.test.ts tests/providers/provider-key-store.test.ts tests/providers/provider-api-keys.test.ts tests/adapters/key-failover.test.ts tests/server/management-gcp-credential-input.test.ts tests/ci-workflows/file-size-ratchet.test.ts
# 188 pass, 0 fail, 933 assertions across 9 files. Five unchanged guard/management files retain their earlier passing evidence.

bun run typecheck       # PASS, exit 0, final head
bun run structure:check # PASS, exit 0, final head
bun run privacy:scan    # PASS, exit 0, final head
git diff origin/dev...HEAD --check # PASS
(cd docs-site && bun run build) # PASS: 577 pages, 81,200 internal links

Red-to-green checks: wrapped/malformed input, restore and token removal initially 0 pass / 9 fail; overlapping deletion initially 13 pass / 2 fail; encoded credential classification initially 13 pass / 3 fail. A further combined escaped-and-wrapped regression initially passed 15 / failed 7; the final structured-carrier fix at 65a032c passes all 22 boundary cases. Earlier broad targeted attempt: 385 pass / 2 fail; one pinned-runner mismatch fixed by using Bun 1.4.3 and one error-guidance mismatch fixed in source, then the 391-case run above passed.

Full local bun run test, bun run prepush, and whole-directory suites were NOT RUN, per maintainer shared-host resource constraints. Broad repository, packaging, real OS credential-store, and cross-platform validation remain with CI. PR runs the fast Ubuntu subset; after it passes, dispatch gh workflow run ci.yml --ref carry/6843-vertex-sa-json -f lane=all for full cross-platform evidence. Exact-head PR run: https://github.com/lidge-jun/opencodex/actions/runs/38699863108 (pending/queued as of 2026-10-15 07:37 UTC). No broad-suite or platform pass is claimed. This PR remains draft pending hosted evidence and maintainer security review.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults; explicit maintainer security approval remains required.

GSL-R and others added 30 commits October 9, 2026 22:28
…6821)

Let the provider's API-key field take a Service Account JSON directly:

- gcp-adc: a `gcp-sa:<account>` marker resolves the credential from the OS
  keychain (same service + entry factory as `keychain:` references) and runs
  the existing service_account JWT / authorized_user flow. The marker sits
  above GOOGLE_APPLICATION_CREDENTIALS in the source priority; everything
  downstream (token cache, refresh skew, in-flight dedup) is untouched.
- api-keys: addProviderApiKey detects a pasted credential JSON (single,
  pretty-printed, or comma-separated for future pool rotation), verifies the
  "type" field, stores the secret in the OS keychain with a read-back check,
  and persists only the marker to config. File paths are rejected with
  paste guidance; unrecognized types are rejected before anything is stored.
- router: usableResolvedApiKey registers the active `gcp-sa:` marker with
  the ADC resolver, so a marker in any pool entry becomes the active source.
- adapters/google: vertex's API-key fast path skips `gcp-sa:` markers —
  they are not API-key material and must reach the ADC branch instead.

Markers keep resolveProviderApiKey synchronous (no signature changes) and
work naturally with the existing 429 key-pool failover: each pool entry can
independently hold a raw key, an ${ENV} ref, a keychain: ref, or a gcp-sa:
marker, and per-key cooldown rotation applies unchanged.

Closes #6821 (partially — live model discovery for vertex mode is a
separate discussion).

Co-Authored-By: Claude <noreply@anthropic.com>
…6842)

Address CodeRabbit findings:

- gcp-adc: remove the process-global activeMarkerAccount/registration
  (a module-level marker would race across concurrent requests for
  different providers and could resolve one call with another provider's
  credential). The marker account now travels per request via
  getVertexAccessToken(options.markerAccount) and flows through
  currentAdcSourceKey/loadAdcCredentials as an argument. The reconcile
  sweep passes no marker (no request context) — marker entries are
  pruned by expiry instead.
- adapters/google: pass gcpCredentialMarkerAccount(provider.apiKey)
  per request. router: drop the registration; the routed provider
  config carries the marker verbatim.
- api-keys: delete a just-written keychain entry ONLY when the write
  created it — on a failed commit, an upsert must keep the credential
  the previous reference still names.
- tests: narrow the adapter assertion to the ADC-shaped request (URL +
  Bearer header + no x-goog-api-key, with a stubbed fetch), and
  isolate the marker-source tests from host ADC state (empty
  CLOUDSDK_CONFIG, no GOOGLE_APPLICATION_CREDENTIALS, 404 fetch stub).

Co-Authored-By: Claude <noreply@anthropic.com>
…ng (#6842)

Address CodeRabbit round-2 findings:

- router: usableResolvedApiKey returns gcp-sa: marker values UNCHANGED —
  replacing a marker with undefined (it resolves to no key material) would
  silently drop it from the routed config and send the vertex adapter to
  host ADC instead of the configured service account.
- api-keys: storeGcpCredentialJson takes existedBefore and deletes the
  keychain entry after a read-back mismatch only when the entry did not
  exist before the write — a re-paste targets an account an existing
  marker already names, and deleting it would destroy the live credential.
- api-keys: splitCredentialPaste honors the parseGcpCredentialJson contract
  (a single-line brace-leading literal is never eaten — it falls back to a
  plain key row), tracks JSON string/escape state in the brace scanner (a
  } inside a string field no longer splits early), and rejects
  non-separator content before/between objects instead of discarding it.
- tests: router-level pass-through test (routedProviderConfig preserves
  the marker), end-to-end test (routed config feeds buildRequest, ADC
  branch wins), literal-key fallback + gap-rejection tests. Markers keep
  resolveProviderApiKey synchronous.

Co-Authored-By: Claude <noreply@anthropic.com>
…rkers; strict paste validation (#6842)

Address Prana review findings:

- adapters/google: a `gcp-sa:` marker means the operator named ONE credential
  explicitly — the GOOGLE_CLOUD_API_KEY env fallback no longer fires. Without
  this guard, a marker + env key would silently route the provider's inference
  through the ambient env key instead of the configured service account.
- providers/api-keys (capture): a parseable JSON object with an unrecognized
  "type" field (e.g. external_account) is REJECTED with guidance, not saved as
  a plaintext literal key — saving it would send it to the wire verbatim.
  Non-JSON brace values ({abc:def}) keep their key-compat behavior.
- providers/api-keys (tail): content after the LAST object's close up to EOF
  is validated like the mid-gaps — trailing garbage is rejected, not silently
  kept. (Covers captureTrailing otherwise.)

Registrations: per-finding regression tests added (explicit-credential-wins
for markers, JSON-with-unknown-type rejection, tail-gap rejection).

Co-Authored-By: Claude <noreply@anthropic.com>
…paste-dev

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
…re persistence

Co-Authored-By: Claude <noreply@anthropic.com>
Preserve contributor history through 59c4e2c on current dev 7640a94. Source feature starts at 36b9c1e; credential and lifecycle follow-ups remain intact.

Co-authored-by: GSL-R <174104911+GSL-R@users.noreply.github.com>
Reject wrapped or malformed credential material before literal persistence or restore, and prevent keychain aliases exposing GCP JSON as wire keys. Revalidate marker readability before cached-token use. Capture deletion candidates and persist CLI/management removal under one mutation lock, preserving post-commit shared-reference cleanup. Cover mixed SA/key failover and DTO redaction with synthetic credentials.

Co-authored-by: GSL-R <174104911+GSL-R@users.noreply.github.com>
Decode structured JSON before credential classification so escaped field and type names cannot enter literal key resolution or keychain restore.

Co-authored-by: GSL-R <174104911+GSL-R@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Oct 15, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

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

Reject object, array and quoted-string carriers directly so nested or escaped GCP credential material cannot bypass alias, ingestion or restore validation. Preserve non-JSON literal token handling and dedicated supported GCP-object ingestion.

Co-authored-by: GSL-R <174104911+GSL-R@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Deterministic PR hygiene checks passed.

@github-actions github-actions Bot added the enhancement New feature or request label Oct 15, 2026
lidge-jun and others added 6 commits October 16, 2026 00:16
# Conflicts:
#	scripts/test-layout/layout.json
#	tests/fixtures/test-layout-expected.json
Move credential-input rejection responses and locked provider deletion into a cohesive sibling module. Preserve validation responses, mutation ordering, save callback and post-commit cleanup behavior while keeping provider-routes.ts below the fixed 2000-line limit after dev integration.

Co-authored-by: GSL-R <174104911+GSL-R@users.noreply.github.com>
# Conflicts:
#	structure/config.md
#	structure/gui-and-management-api.md
#	structure/providers-and-adapters.md
# Conflicts:
#	structure/config.md
#	structure/gui-and-management-api.md
# Conflicts:
#	scripts/test-layout/layout.json
#	structure/gui-and-management-api.md
#	tests/fixtures/test-layout-expected.json
@lidge-jun

Copy link
Copy Markdown
Owner Author

Maintainer integration into dev (MAINTAINERS.md dev maintainer-integration policy; batch integration authorized by @lidge-jun on 2026-10-15).

  • Head: e6e7478886f49160f2c843d9abb9bbd066f7ef1c (tree 5ad4d98d6d4a8aa63b4841b5f1c7725cecc5947d) = this branch merged with origin/dev 17e307d87c. Integration note: auto-resolved union conflicts: scripts/test-layout/layout.json structure/gui-and-management-api.md tests/fixtures/test-layout-expected.json.
  • Exact-head evidence: GitHub-hosted Actions for this account have not been assigning runners since 2026-10-15 13:30 UTC, so by owner decision the evidence is a full bun run test (Bun 1.4.3 test runner, root and GUI dependencies installed, GUI built) of this exact tree on a maintainer host (macOS arm64): exit 1, 9 min, 2 failing test(s), each also failing in the same host's baseline run of dev 60bf4c7 (environment-only): connected Desktop uses hub IDs across a cold restart (stored profile=false), update stops the running proxy before replacing files > npm launcher restarts the stopped .
  • Local gates on the same tree: bun run typecheck, file-size ratchet, test-layout and structure:check.
  • Hosted CI checks for this head may still be queued; they are not claimed as evidence.

@lidge-jun
lidge-jun marked this pull request as ready for review October 15, 2026 21:00
@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner October 15, 2026 21:00
@lidge-jun
lidge-jun merged commit 49ffccd into dev Oct 15, 2026
5 of 6 checks passed
@lidge-jun
lidge-jun deleted the carry/6843-vertex-sa-json branch October 15, 2026 21:00
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-15T21:05:09.256143Z e6e7478 Draft marked ready
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@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: e6e7478886

ℹ️ 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 thread src/providers/api-keys.ts
const stored: Array<{ id: string; marker: string }> = [];
const created: string[] = [];
try {
const committed = commitProviderApiKeySelection(config, name, fresh => {

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 Restrict credential JSON ingestion to Vertex

When /api/providers/keys is called for any key-auth provider other than Vertex—for example, an OpenAI-compatible relay—this path still converts the JSON into a gcp-sa: marker and commits it as active. The router preserves that marker, but only the Google Vertex adapter resolves it; other adapters transmit the marker as their API key, so the endpoint returns success while replacing working authentication with an unusable value. Validate that the target is an effective Vertex provider before accepting credential JSON, matching the newly documented Vertex-only workflow.

AGENTS.md reference: docs-site/AGENTS.md:L7-L10

Useful? React with 👍 / 👎.

Comment thread src/providers/api-keys.ts
Comment on lines +245 to +248
const isJsonPaste = first !== undefined && typeof first !== "string";
if (!isJsonPaste) {
// Not a credential paste — fall through to the plain API-key path below. splitCredentialPaste
// has already thrown for file paths; a plain key lands here as a single literal row.

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 Reject marker strings from the credential ingestion endpoint

A gcp-sa: value takes this plain-key branch, so posting a typo or copied marker to /api/providers/keys returns 201 and activates it without verifying that its OS-store entry exists. Reposting an existing marker also hashes the marker text rather than the original credential JSON, appending the same marker under a second pool ID; rotation then attempts the same credential twice and deleting one row does not remove it. Reject marker strings here, or validate and upsert them by their existing pool entry instead of treating them as new secrets.

Useful? React with 👍 / 👎.

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

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants