Skip to content

audit follow-up: antigravity + vibe credential paths spawn secret-tool; antigravity refresh requires the agy binary #1825

Open

Description

@dezren39

Audit follow-up — antigravity + vibe credential paths spawn secret-tool; antigravity refresh requires the agy binary

Source: 2026-11-06 provider audit (Audit B: binary-required providers). The wire-only doctrine ("no provider the fleet uses requires a local binary") does not hold on the credential path for the two keyring-based doors. Whole-tree check: grep -rnE "child_process|Bun.spawn|execa" over packages/ai/src/{providers,protocols,route} returns exactly two files — these two.

antigravity-direct (source id antigravity) — hard dependency

Inference is wire (POST daily-cloudcode-pa.googleapis.com/v1internal:streamGenerateContent). The credential path is not:

  • antigravity-auth.ts:103-111: every credential load dynamically imports node:child_process and execFiles secret-tool lookup service gemini username antigravity (15s timeout). findSecretTool() searches $SECRET_TOOL_PATH, $PATH, /nix/store/*libsecret*/bin/secret-tool and throws secret-tool not found — no non-binary credential tier (static override aside).
  • Token refresh requires the agy binary file: findAgyBinary() probes $AGY_BIN, ~/.gemini/bin/agy, /run/current-system/sw/bin/agy; binaryIntel() reads its bytes to extract GOCSPX OAuth client-secret candidates; refresh() fails hard (could not extract OAuth client secret from agy binary) when absent. A still-valid token works without it; an expired one cannot be refreshed. (The binary is read, never executed; the CLI also owns writing the keyring credential.)
  • Same pattern in the quota layer: quota/providers/antigravity.ts:124 also execFiles secret-tool (graceful null there).

vibe-direct (source id vibe) — fallback tier that is the deployed tier

Inference is wire (MistralChat route, api.mistral.ai/v1); no vibe subprocess exists. But vibe.ts:133 wires VibeAuth.vibeAuth, whose tier-4 resolution (vibe-auth.ts:105-118) execFiles secret-tool lookup. It is nominally a fallback (env → ~/.vibe/.env → keyring, failures fall through) — except the fleet config deliberately keeps MISTRAL_API_KEY out of the server env (#1653) so the deployed door falls through to the keyring sign-in key: in actual fleet use, the credential is obtained by spawning secret-tool.

Desired end state

Credential resolution that reads what the one-time CLI sign-in provisioned without spawning a helper binary — e.g. read the keyring via a Secret Service D-Bus library binding instead of the secret-tool subprocess, or make a file/env tier the deployed default with the keyring tier removed. claude-code / commandcode already prove the read-in-place pattern (file read only, zero spawns). For antigravity refresh, the agy-binary byte-extraction should be replaced by a provisioned client-secret source (vaulted at sign-in time) so refresh survives the CLI's absence.

Related: muse-direct's layer-2 dependency (its local muse-proxy spawns the Muse CLI) is tracked with the proxy decommission pass in developing-today/code, not here.

Acceptance criteria

  • The credential paths for the antigravity and vibe providers no longer hard-require a local binary (secret-tool / agy) without a documented fallback tier, OR the wire-only doctrine is amended in writing to name these two credential-path exceptions explicitly.
  • On a host without secret-tool, a credential load for each affected provider either succeeds via the fallback tier or fails with an error that names the missing binary and the supported alternatives (no opaque throw).
  • Regression coverage exists for the no-binary path (simulated missing secret-tool) for both providers.

Activity

  1. dezren39 commented on Nov 6, 2026

    @dezren39
    Author

    🤖 Triage evidence · issue-sweep-rotating (Make feeder)

    • Checked: this issue against current main 22c0a6ddbee19878e8e51bde432e995f75ccdd2a (verified x2 via gh api), reading the named files raw via gh api contents.
    • Gap CONFIRMED still present: the credential paths still spawn a helper binary; inference being wire-only does not extend to credentials:
      • packages/ai/src/providers/antigravity-auth.ts:99 — findSecretTool() throws secret-tool not found when no binary is located
      • antigravity-auth.ts:103-111 — every credential load dynamically imports node:child_process and execFiles secret-tool lookup service gemini username antigravity (15s timeout)
      • antigravity-auth.ts:128 / :145 / :333 — refresh goes through findAgyBinary() + binaryIntel(): the OAuth client-secret candidates are extracted from the agy binary's bytes, so an expired token cannot be refreshed where the binary is absent
      • packages/ai/src/providers/vibe-auth.ts:105-118 — tier-4 resolution execFiles secret-tool lookup; per the issue (and vibe provider: native wire-protocol emulation of the Mistral Vibe binary (no ACP host, no subprocess) #1653 keeping MISTRAL_API_KEY out of the server env) this fallback tier is the deployed tier for the vibe door
      • packages/ai/src/quota/providers/antigravity.ts:124 — the quota layer also execFiles secret-tool (graceful null there)
    • What "done" looks like: credential resolution reads what the one-time CLI sign-in provisioned without spawning a helper binary (e.g. a Secret Service D-Bus library binding instead of the secret-tool subprocess, or a file/env tier made the deployed default with the keyring subprocess tier removed); antigravity refresh no longer depends on extracting the client secret from the agy binary's bytes (provisioned/vaulted at sign-in time); the claude-code / commandcode read-in-place pattern (file read only, zero spawns) is the shape to match.
    • Next: enqueuing a repo-task/v1 triage packet into repo-health-opencode-pilot from this feeder run; the queue lane owns the fix work.
  2. dezren39 commented on Nov 6, 2026

    @dezren39
    Author

    🤖 Agent note · queued by triage feeder

    • Agent: issue sweep (issue-sweep-rotating, Make feeder)
    • Session / ID: issue-sweep-rotating run 2026-11-06 04:24 CST
    • Found via: feeder rotation (prev rotating line dezren39/darwin-nix 2026-11-06T09:27Z -> developing-today-forks/opencode)
    • Task: Enqueued repo-task/v1 triage packet as task 10989 in queue repo-health-opencode-pilot (kind triage, priority 50, dedupe repo-health:v1:triage:developing-today/opencode:issue:1825). Packet: hidden_files/packet-opencode-1825.json.
    • Evidence: feeder triage comment audit follow-up: antigravity + vibe credential paths spawn secret-tool; antigravity refresh requires the agy binary #1825 (comment) — gap CONFIRMED on main 22c0a6ddbee1 (verified x2): antigravity-auth.ts:103-111 execFiles secret-tool, refresh depends on agy binary bytes (:128/:145/:333); vibe-auth.ts:105-118 tier-4 does the same.
  3. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    source/humanFiled directly by a human
    priority/normalOrdinary priority (the default)
    effort/mMedium: expected under 1 day of work
    meta/enrichedIntake review done: acceptance criteria, links, and normalized labels added
    on Nov 6, 2026
  4. dezren39 commented on Nov 6, 2026

    @dezren39
    Author

    Intake review

    🤖 Fleet monitor intake (repo-fleet-monitor), 2026-11-06.

  5. dezren39 commented on Nov 6, 2026

    @dezren39
    Author

    🤖 Agent note · triage verdict: CONFIRMED, routed as split

    • Agent: queue worker (repo-worker-opencode), lane opencode-a
    • Session / ID: task 10989
    • Found via: queue claim

    Verified fresh on current origin/main (laptop git-show + gh raw reads):

    • Whole-tree grep for child_process/Bun.spawn/execa over packages/ai/src/{providers,protocols,route}: exactly two files — providers/antigravity-auth.ts and providers/vibe-auth.ts. Claim holds.
    • antigravity-auth: findSecretTool() throws 'secret-tool not found (set SECRET_TOOL_PATH)'; the keyring credential load has no non-binary tier (static override aside). refresh() fails hard with 'could not extract OAuth client secret from agy binary' (two sites) when the agy binary / GOCSPX extraction is unavailable.
    • quota/providers/antigravity.ts: execFiles secret-tool but degrades gracefully (no tool → null).
    • vibe-auth: tier-4 keyring lookup already degrades gracefully — every failure resolves undefined, falls through, and the resolver's final absence error names every location checked. Vibe therefore already meets acceptance chore(deps): bump @modelcontextprotocol/client from 2.0.0 to 2.2.0 in /packages/core #2; its exposure is that the deployed fleet tier IS the keyring tier (vibe provider: native wire-protocol emulation of the Mistral Vibe binary (no ACP host, no subprocess) #1653 keeps MISTRAL_API_KEY out of the server env).

    Route (split):

    1. Small fix enqueued: antigravity-auth no-binary error quality — when secret-tool (or the agy binary) is missing, the surfaced error names the missing binary AND the supported alternatives (SECRET_TOOL_PATH / install libsecret, static or env credential tier, running the CLI sign-in), with regression coverage simulating a missing secret-tool on the antigravity load path. Vibe needs no error change.
    2. Architectural remainder — NOT enqueued as a fix, because the desired end state is a genuine design/policy choice: (a) Secret Service D-Bus library binding replacing the subprocess, (b) file/env tier as the deployed default with the keyring tier removed (revisits vibe provider: native wire-protocol emulation of the Mistral Vibe binary (no ACP host, no subprocess) #1653's deliberate env exclusion), (c) provisioned client-secret source vaulted at sign-in so antigravity refresh survives the CLI's absence, or (d) amending the wire-only doctrine in writing to name these exceptions. Recorded in the repo backlog as BLOCKED on Drew's choice among (a)-(d).
  6. added
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    work/assignedAn agent holds this item via the queue (claimed task)
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 6, 2026
  7. 13 remaining items

  8. dezren39 commented on Nov 6, 2026

    @dezren39
    Author

    codex-proxy (:18744) — RETIRED 2026-11-06 (Drew, proxy-infra decommission)

    Closing the loop on this audit's codex item: the openai provider was verified wire-only here, and Drew's question — "is this not [what] the openai direct provider replaces?" — is now answered in production state, not just in the audit.

    Replacement proof

    • Fork transport (packages/core/src/plugin/provider/openai.ts): direct HTTPS — issuer https://auth.openai.com, ChatGPT/Codex plan base https://chatgpt.com/backend-api/codex, plan auth in-process; no subprocess, no proxy hop, nothing references :18744.
    • The door's second job was also re-homed in-fork: credential/codex-sync.ts (feat(core): import Codex CLI credentials in-fork with true expiry #1468, the Door front migration: codex-proxy (18744) callers repoint to the façade #1154 cutover) reads the Muse auth file into the credential store — it never contacts :18744.
    • Production store (route_attempt, laptop server): ~198k successful attempts in 7 days via native openai / openai.main / openai.alternate; the proxy-fronting ids (chatgpt, openai-codex) last attempted 2026-10-24, definitions deleted 2026-11-03, legs re-seated onto openai-plan.

    Consumer census at kill time (all negative): live config chain carries zero :18744 values (comments only, no {env:CODEX_PROXY_PORT} template left); the socket had never activated the service this boot (no journal entries, NRestarts=0); OpenClaw held only an After= mention (its model route is the :13420 facade); the quota gatherer is in-binary; the PATH 1add729 binary reads the same config with no :18744 values to consume.

    Kill: socket stopped + disabled (disable also unlinked the HM unit symlink → not-found), service inactive, tailscale serve :18744 forward removed, port closed on loopback + tailnet. Post-kill probe: 37/37 openai.main attempts succeeded in the 3 minutes after the kill, port closed throughout.

    Repo removal: developing-today/code PR anomalyco#6596 (merge bba05833b5) — bin program + suite deleted, HM service/socket/session-var/launcher/codexProxyPort let removed, guard row dropped, catalog-sync registry trimmed, OpenClaw/PORTS/docs swept; 200/200 bin tests, port-guard script green, both edited default.nix parse clean. HM side takes effect at the next switch (anomalyco#5647); the live disable holds until then.

    One deliberate survivor (also noted on the PR): provider-catalog-sync's chatgpt source entry remains as the enumeration machinery's worked example for the anomalyco#3449/anomalyco#4588 CLI fixtures — dormant (no live config names a chatgpt provider); removing it is a fixture redesign, not a decommission step.

  9. added a commit that references this issue on Nov 6, 2026
  10. dezren39 commented on Nov 6, 2026

    @dezren39
    Author

    Coordination note for the muse-direct lane: the fastlane cutover at 2026-11-06 13:07 CST (fork main 1b27880fb0, drop-in 20-fastlane.conf, GC root fastlane-gcroot-1b27880) already carries #1872 (muse-direct native Meta wire) — it was deployed together with the #1873/#1875 wire-identity fixes in a single build/restart, so no separate muse-direct deploy is needed. Full record: #1876 (comment 6693505058). Note the build required the nix/hashes.json nodeModules restamp (stale pin blocked all builds of main); that fix has since landed upstream as PR #1880.

  11. dezren39 commented on Nov 6, 2026

    @dezren39
    Author

    Addendum: after the note above was posted, the record was completed by this lane's own 13:17 CST cutover to the pure 08eca393d6d1 build (see #1876 supersession note) — production now runs that build, which carries #1872 as noted. No action needed; this just keeps the sequence accurate.

  12. dezren39 commented on Nov 6, 2026

    @dezren39
    Author

    muse-direct audit item — CLOSED with evidence (2026-11-06)

    Drew's order: "what part of muse proxy sounds direct." muse-direct is now a native fork provider; muse-proxy is dead.

    Design (Option B, as built)

    New native provider in the fork, id muse-direct (unchanged), model ids unchanged (muse-spark-1.3-contributor, muse-wonder-1.0-contributor); routes/seats untouched. Three new files:

    • packages/ai/src/providers/muse-direct-auth.ts — in-process exchange mirroring the proxy's direct mode exactly: reads ~/.config/muse/auth.json, providers.meta.access_token → POST https://api.meta.ai/muse-code/key (Authorization: Bearer <account token>, x-api-version: 1.0.0, {"onboard": false}) → minted key; 10-min in-memory cache; is_subs_active === false / require_payment === true are hard errors with the proxy's verbatim messages; api_key-only auth files used directly; missing credential fails closed. No subprocess, no CLI, no PAYG fallback.
    • packages/ai/src/protocols/muse-direct-responses.ts — wrapper around the MetaResponses protocol carrying the tool-name attestation aliasing (shell→run_command, bash→terminal, with the collision + prototype guards) at the protocol layer, since session traffic never passes through core Generate (whose aliasing is scoped to provider meta). Plus a 401-retry transport (invalidate → re-exchange → retry exactly once). Route defaults store: false, include: ["reasoning.encrypted_content"], mirroring meta.
    • packages/ai/src/providers/muse-direct.ts — provider package mirroring meta.ts/claude-code.ts (single Responses route, endpoint https://api.meta.ai/v1).

    Pool discipline preserved: the meta provider (PAYG door) is untouched; the muse provider id stays disabled; muse-direct ≠ meta (different credentials, routes, ids).

    Effort parity defect — found in scratch, fixed in the same PR

    Recorder capture showed session bodies with reasoning: null for variant sessions. Root cause is fork-wide: config variants in the v1 dialect ({options: {reasoning_effort: "xhigh"}}) migrate to variant settings {options: {reasoning_effort}}, and the native dialect resolver only reads flat camelCase reasoningEffort — the door honored the dialect (the proxy maps it at muse-proxy.mjs:1252), so cutting over naively would have silently stripped the seats' effort. muse-direct's fromSettings now normalizes reasoningEffort / reasoning_effort / options.reasoningEffort / options.reasoning_effort → reasoningEffort (the antigravity provider's top-level reasoning_effort is the house precedent). The same latent drop affects every native provider whose config variants use the v1 dialect — the meta provider block included. Flagged here as a fork-wide follow-up; this change fixes muse-direct only. Proven end to end: scratch session bodies carry reasoning: {effort: "xhigh"} with the production-shaped config. Tests: packages/ai/test/provider/muse-direct.test.ts 10/10; ai+core typecheck clean; full ai suite failure set identical to the pristine-main baseline (12 pre-existing recorded-fixture failures).

    PRs / merges

    Scratch verification (recording listener in the path)

    Isolated scratch server (worktree source) with a recording forwarder to api.meta.ai: 11 requests, all POST https://api.meta.ai/v1/responses; pid-attributed socket sampling showed the server process connected ONLY to the recorder — zero connections to :18747. Wire auth fingerprint matched the auth file's stored key (Meta accepted it — the answer was a quota wall, not a 401/403). Tool aliasing proven on real session traffic (18 function tools, no bash/shell, run_command present).

    Production cutover + probes (verbatim)

    Fastlane repoint to /nix/store/dy8kimkyrdb9byppbwnbd4x4hpapqxm0-opencode-2.0.30+08eca39-wrapped (rollback: the 5e9b135 drop-in backup). Both pinpoint seats probed on production, route attempts recorded:

    • muse-direct/muse-spark-1.3-contributor#xhigh → 429 "Subscription quota exhausted. Your usage window resets at 2026-11-09T00:00:00Z."
    • muse-direct/muse-wonder-1.0-contributor#xhigh → same 429 verbatim.

    Meta's full body: {"error":{"code":"rate_limit_exceeded","message":"Subscription quota exhausted. Your usage window resets at 2026-11-09T00:00:00Z.","param":null,"resets_at":1794182400,"type":"rate_limit_error"}} — a wall answer BY META over the direct wire is the pass condition while the pool is walled; no PAYG funds spent. Zero connections to :18747 from the server process during the probes (pid-attributed sampling).

    Quota

    opencode quota (new build) keeps reading the Meta pool: the quota provider (packages/ai/src/quota/providers/meta.ts) has no proxy dependency — CDP portal read primary, fallback POSTs muse-code/key with the auth.json account token. Gatherer output (quota-current.json, 2026-11-06T19:15:29Z, post-cutover) carries meta with state ok, weekly 100%, resetsAt 2026-11-09T00:00:00Z, credits $148.57. (The human text view omits the Meta section under BOTH the old and new builds — pre-existing; the JSON the gatherer consumes is complete.)

    muse-proxy death certificate

    • systemctl --user stop muse-proxy + disable: unit not-found, inactive; port 18747 not listening (connection refused).
    • Post-kill probe: wonder seat re-probed with the proxy dead → same Meta 429 via the native provider (route attempt recorded).
    • Repo definition removed in code PR feat: Add kotlin lsp integration anomalyco/opencode#6601 (feat: require double Ctrl+C to exit when input is empty anomalyco/opencode#6494 pattern): bin/muse-proxy.mjs, bin/muse-proxy-start.sh, bin/muse-proxy.test.mjs deleted; museProxyPort let, systemd.user.services.muse-proxy, and the MUSE_PROXY_PORT session variable removed from home/common/default.nix (dated retirement notes left); port-guard row removed (guard passes vacuously, stays wired); PORTS.md row retired; OpenClaw TOOLS.md row removed, QUOTAS.md + prompt/let comments corrected; historical route-narration comments left as history.
    • Config layering note: the fleet config lives at the repo path (symlinked from ~/.config/opencode/) and /home/user/.opencode/opencode.jsonc — the latter proved to be the same inode (hardlink) as the repo file, so it tracks the repo automatically; the runtime overlay contains no muse-direct.
    • Durability: the nix removal lands at Drew's next nixos-rebuild switch; until then the disabled state holds (unit file removed, nothing restarts it).
  13. dezren39 commented on Nov 7, 2026

    @dezren39
    Author

    Survivor closed: the dormant chatgpt catalog entry is removed

    Drew ruled on the one disclosed survivor of the codex-proxy kill (recorded above, comment 6692223967): remove it and redesign the fixtures (2026-11-06 19:18 CST).

    Landed as developing-today/code#6657, merged d13f07c7:

    • bin/provider-catalog-sync.mjs — PROVIDER_SOURCES no longer contains the chatgpt proxy entry (dead :18744). (Note: provider-catalog-sync lives in developing-today/code's bin/, not in this fork.)
    • The enumeration machinery's worked example is now a deliberately synthetic source, 'fixture-proxy' (fixture port :18799, nothing listens there by design). Rationale: every surviving real source is crosscheck/unverifiable kind, and the source table has no injection point, so the proxy-kind validated-addition path can only be exercised through a proxy-kind entry — the fixture now says so in its name instead of borrowing a dead product's.
    • Fixtures redesigned in bin/provider-catalog-sync.test.mjs: the runSync snapshot fixtures and the CLI control fixtures (fix: $ invocation not .quiet() anomalyco/opencode#3449/Input area to clunky (for me) in 1.0.85 anomalyco/opencode#4588, incl. the overlay regression test) run through fixture-proxy / 18799.json; tests 37/37 pass (node --test).
    • Residue sweep (code repo + this fork): no other catalog residue. Remaining chatgpt strings are unrelated living features/history (openai-cred-watchdog credential-method names, the chatgpt-desktop nix package, a VS Code extension id, historical removal comments in .opencode/opencode.jsonc, upstream console i18n copy) — untouched, no follow-up issue needed.
  14. dezren39 commented on Nov 7, 2026

    @dezren39
    Author

    Correction to the comment above: the merge commit for developing-today/code#6657 is 82c715a3a45994eb0c98ed78be141625d83a93b3 (merged 2026-11-07T01:21:23Z, per the merge command's own readback), not d13f07c7 as misstated above. Everything else in that comment — the change, the fixture design, the 37/37 test result, and the residue sweep — stands as written.

  15. added
    work/assignedAn agent holds this item via the queue (claimed task)
    queue/claimedA queue worker has claimed the task for this item
    on Nov 8, 2026
  16. dezren39 commented on Nov 8, 2026

    @dezren39
    Author

    🤖 Agent note · triage revalidation: CONFIRMED, routed — resume of task 10989 (attempt 2)

    • Agent: queue worker (repo-worker-opencode), lane opencode-a
    • Session / ID: task 10989

    Revalidated fresh on current origin/main b3778ebdda6d1fc04d33717b12384625f849b402 (gh api + laptop git show, 2026-11-08T02:15Z). Issue open, no held / user-owned / do-not-merge / do-not-auto-edit labels.

    Spawn sites still present, with current file:line:

    • packages/ai/src/providers/antigravity-auth.ts:112 findSecretTool(); :156-164 every credential load execFiles secret-tool lookup service gemini username antigravity (15s timeout); :175 findAgyBinary() + :192-270 binaryIntel() extract OAuth client-secret candidates (GOCSPX) from the agy binary's bytes; refresh depends on them at :316 and :414.
    • packages/ai/src/providers/vibe-auth.ts:118-131 — tier-4 execFiles secret-tool; this is the deployed tier (vibe provider: native wire-protocol emulation of the Mistral Vibe binary (no ACP host, no subprocess) #1653 keeps MISTRAL_API_KEY out of the server env).
    • packages/ai/src/quota/providers/antigravity.ts:1,124 — imports execFile from node:child_process and execFiles secret-tool (graceful null).

    Routing (per Drew's ruling, comment 6686794578 — no provider may depend on running a binary command for auth):

    Note: a first enqueue of the same packet landed in the wrong queue (repo-health-code-shadow, task 11784) because no explicit --queue flag was passed; it was cancelled and superseded by 11785 in the correct queue.

  17. removed
    queue/claimedA queue worker has claimed the task for this item
    work/assignedAn agent holds this item via the queue (claimed task)
    on Nov 8, 2026
  18. dezren39 commented on Nov 9, 2026

    @dezren39
    Author

    🤖 Agent note · pointer (from task 14259 triage, lane opencode-a)

    The fork-wide v1-dialect reasoning_effort follow-up flagged on this issue ("the same latent drop affects every native provider whose config variants use the v1 dialect — the meta provider block included") is now owned by #2203. Triage on #2203 (2026-11-09) audited every native provider's effort settings path: meta, vercel-ai-gateway, alibaba, and amazon-bedrock-mantle drop the v1 spellings; antigravity and commandcode conform for top-level spellings only; the recommended fix normalizes once in Provider.nativeSettings (packages/core/src/provider.ts). This issue remains scoped to the secret-tool audit follow-up only.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    effort/mMedium: expected under 1 day of workkind/bugSomething isn't working (prefixed form of the classic bug label)meta/enrichedIntake review done: acceptance criteria, links, and normalized labels addedpriority/normalOrdinary priority (the default)source/humanFiled directly by a human

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions