pingdotgg / pingdotgg/t3code

MCP bearer token passed via environment is captured in systemd-coredump journal entries on provider crashes

Open
#12,031 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

accepted bug codex via-triage
Dominant language
TypeScript
Stars
23k
Forks
5.9k
Avg merge
11h 14m
Merged PRs (30d)
357

Description

Summary

T3_MCP_BEARER_TOKEN is passed to spawned provider processes through the
environment. On Linux, systemd-coredump records the entire environment of
any process that dumps core
into the systemd journal as COREDUMP_ENVIRON,
in plaintext. So every provider crash writes that session's MCP bearer token to
a log readable by the account the providers run as.

Requesting a file- or fd-based alternative, in line with the --bootstrap-fd
mechanism this project already has.

Where it happens

From the shipped bundle:

environment: {
  ...(options?.environment ?? process.env),
  T3_MCP_BEARER_TOKEN: mcpSession.authorizationHeader.replace(/^Bearer\s+/, "")
}

paired with the Codex MCP client config:

-c 'mcp_servers.t3-code.bearer_token_env_var="T3_MCP_BEARER_TOKEN"'

Measured impact on one host

Every core dump over a two-week window on a machine running a provider fleet:

dumps: 18 | with full environment recorded: 18 | carrying a secret-shaped value: 17

COREDUMP_ENVIRON is readable by root, the systemd-journal group, and —
via the journal directory ACL — the user the providers run as. Where multiple
provider sessions share one Unix account, any session that can run
journalctl can read the MCP token of any other session that has crashed.

journalctl --output=json omits the field by default because it is large;
--all reveals it. That makes the exposure easy to miss entirely.

Scope, stated honestly

mcpSession.authorizationHeader reads as a per-session token, and that
matches observation: a token recovered from a dump returned 401 against both
/mcp and /api/orchestration/snapshot after its session ended, while a freshly
minted token returned 200 on the same route. So this is not a long-lived
credential leak.

The exposure is therefore bounded to the lifetime of the session whose provider
crashed — but within that window it is a usable token sitting in a world-of-the-
account-readable log, and provider crashes are not rare on a busy host.

I would call this moderate rather than critical, and I would rather describe it
accurately than inflate it.

Why the environment is the wrong channel specifically

Not a general "env vars are bad" argument — this is a concrete Linux behaviour:

  • /proc/<pid>/environ is readable for the lifetime of the process.
  • systemd-coredump persists the whole block on any crash, and it outlives the
    core file
    , since Storage= controls the core, not the journal metadata.
  • coredump.conf on systemd 261 exposes only Storage, ProcessSizeMax,
    SizeMax, ExternalSizeMax, MaxUse, Compress, EnterNamespace,
    CoredumpReceivethere is no way for an operator to exclude that field.

So a deployment cannot mitigate this without disabling crash capture entirely,
which costs the diagnostics operators need for exactly the crashes that leak.

Requested change

A non-environment path for the MCP bearer token, e.g.:

  • write it to a 0600 file and pass the path, or
  • pass it on an inherited fd — the same shape as the existing
    --bootstrap-fd flag ("Read one-time bootstrap secrets from the given file
    descriptor"), which shows the project already treats fd-passing as the right
    channel for secrets.

A constraint I cannot resolve from outside: the Codex MCP client is
configured here via bearer_token_env_var, which implies the client itself
expects an environment variable. If that is the only mechanism Codex supports,
this may need a client-side change first, or a T3-side mitigation instead
(shorter session TTL, or revoking the MCP session on provider exit so a
recovered token is dead on arrival).

Environment

t3code-bin 0.0.38-1 (Arch), runtime 0.0.42
Arch Linux, kernel 7.1.9, systemd 261
providers spawned under a single user service; multiple sessions share one UID

Not asking for

No mitigation is needed on my side beyond what an operator can already do. Filing
this because the leak is structural to the channel rather than to any
deployment's configuration, and because no operator-side setting can close it.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by tracing the shipped bundle's provider environment construction and the Codex MCP client configuration using bearer_token_env_var. Check whether Codex supports a file- or fd-based token path alongside the existing --bootstrap-fd mechanism. Done means the bearer token is no longer present in provider environments or systemd-coredump journal entries while provider authentication remains functional.

Written by the indexing model from the issue text.

Assessment

Tech stack
linux, typescript
Domain
authentication, security
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.