Skip to content

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

Open

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:1521 — Workspace 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:1242 — fs::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

Activity

  1. willwashburn commented on Jul 29, 2026

    @willwashburn
    MemberAuthor

    PR A is #1380 (CLI output/error/argv surfaces; companion AgentWorkforce/relayfile#380 must merge and release first). Follow-ups surfaced by its adversarial review, for PR B/C or later in this train:

    • printJson (packages/cli/src/cli/lib/sdk-command.ts:64) is still not a redaction boundary — 40+ call sites, three hand-masked. A value-based pass (with an explicit opt-out for deliberate secret sinks like cloud room session --json) would make masking a property of the display path.
    • The MCP create_workspace tool result returns the raw workspace key (packages/cli/src/cli/agent-relay-mcp.ts:437-441), and plugins/codex-relay-skill/SKILL.md + plugins/gemini-relay-extension/GEMINI.md instruct agents to print raw keys inside observer URLs. The skills repo has the same instruction in relay-team/relay-pipeline/relay-fanout.
    • Three mask implementations now exist (packages/cli maskSecret, packages/cloud redactCredentialValues, packages/harness-driver private maskWorkspaceKey) with diverging prefix lists; @agent-relay/utils is the natural shared home.
    • node status offers no way to obtain a working observer URL post-masking (template only); workspace key --reveal-secrets covers key retrieval, but a --reveal-secrets on status (or an observer-token-based URL, which is the correct auth model anyway) would restore the click-to-observe flow.

    🤖 Generated with Claude Code

  2. khaliqgant commented on Sep 6, 2026

    @khaliqgant
    Member

    Fresh live confirmation during Fleet cleanup safety inspection (2026-09-06 UTC): ordinary process-table inspection inside multiple Daytona Fleet nodes exposed Relay workspace/API and agent credentials in Claude/Codex --mcp-config argv. No credential values are included here. This is not theoretical or confined to logs; any same-sandbox process able to read the process table can observe them. The planned PR C argv-to-file work remains a release blocker, and fleet qualification should add a redacted ps//proc absence assertion after that fix.

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

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions