Skip to content

Live credentials exposed on process argv, in plaintext logs/stdout, and in unredacted JSON output #1379

Description

@willwashburn

Workspace keys (rk_live_), agent tokens (at_live_), node tokens (nt_live_), and the cloud access token leak through four surfaces. This blocks fleet-wide credential rotation — rotated keys would re-leak immediately. Closes when the platform fix (credentials via env/file + redaction at the serialization boundary + explicit file modes) lands.

1. Secrets on child argv (visible in ps to any local user)

  • The broker builds the agent-relay MCP config with at_live_ embedded (crates/broker/src/snippets.rs:326-334) and splices rk_live_ in for Claude (inject_api_key_into_mcp_json, snippets.rs:407), then pushes the whole JSON as one argv element: --mcp-config <json> (snippets.rs:837-850).
  • Codex gets one --config key="value" argv pair per secret (snippets.rs:867-955).
  • Exposure is doubled: the broker re-execs itself (crates/broker/src/worker.rs:310), so the JSON appears on the pty-worker argv and again on the real harness argv.
  • agent-relay-broker also takes --api-key/--agent-token as CLI flags (crates/broker/src/cli/mod.rs:187) and cli_mcp_args.rs:30 prints the computed config including secrets to stdout.
  • File-based writers already exist as the fix seam: ensure_agent_relay_mcp_config() (snippets.rs:69, currently referenced only by its own tests), ensure_opencode_config_with_result(), ensure_cursor_mcp_config(). Note .claude/rules/mcp-injection.md currently mandates inline embedding and must change with the fix; .cursor/mcp.json is not gitignored despite carrying live keys.

2. node up / node status print keys in plaintext

  • packages/cli/src/cli/lib/broker-lifecycle.ts:1521Workspace Key: ${relay.workspaceKey} unconditionally on every node up (pinned by seven test assertions in core.test.ts).
  • node status repeats it (broker-lifecycle.ts:1795-1796) and prints an observer URL with the key as a query param.
  • --workspace-key is passed on the broker argv redundantly (packages/harness-driver/src/spawn-config.ts:125 — the same key is already in the env block), and that argv is folded into every startup error string (broker-process.ts:148).
  • --verbose prints the handshake message containing the key and the full spawn command line (packages/harness-driver/src/client.ts:466, :373).
  • Neither log sink redacts anything: packages/utils/src/logger.ts (0644 append, bare JSON.stringify) and the Rust tracing appenders (crates/broker/src/runtime/util.rs:144-182, 0644).

3. Unredacted JSON output

  • agent-relay cloud session --json emits the raw cloud access token (packages/cli/src/cli/commands/cloud.ts:540). Sibling commands redact by explicit design (cloud enroll --json at cloud.ts:729-735 — "Never print the node token, even in JSON mode"; cloud worker status --json at cloud-worker.ts:361), so this is an oversight, not policy.
  • workspace active --json prints key + relaycastApiKey (workspace.ts:44-47); workspace create prints the key (workspace.ts:68); cloud-room.ts:561 prints a bare invite token; cloud.ts:720 dumps a record including nodeToken to stderr on a failure path.
  • Redaction helpers exist but are effectively dead: crates/broker/src/redact.rs:13 has zero call sites; the only helper that knows the live-key prefixes is private to node_control.rs (:418, one call site); packages/cli/src/cli/lib/redact.ts:24 fires once and is value-blind (an rk_live_ under a key named url/env/args passes through). No masking utility (last4-style) exists anywhere.

4. World-readable credential files at rest

  • nt_live_ node tokens: crates/broker/src/node_control.rs:1242fs::write with no mode → 0644 in a 0755 dir (~/Library/Application Support/agent-relay/node-tokens/); 91 such files observed on one dev machine.
  • opencode.json and .cursor/mcp.json (both carrying rk_live_ + at_live_): 0644 via write_pretty_json (snippets.rs:1486).
  • connection.json is 0600 only by accident of tempfile's default (crates/broker/src/runtime/init.rs:153-170) — no test asserts the mode.
  • ~/.agentworkforce/relayhistory/auth.json (access + refresh tokens) has a 0644 window before chmod (packages/cli/src/cli/commands/reflex.ts:117-119).
  • The .agentworkforce/relay parent dir is created without a mode at four of five call sites, so the mode: 0o700 at project-workspace-key.ts:67 is a no-op in practice — observed drwxr-xr-x on disk.
  • The TypeScript cloud store does this right (0700 dir + 0600 file + atomic rename, packages/cloud/src/auth.ts:139-160); the Rust side has zero explicit mode arguments anywhere.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions