Skip to content

fix(coding-agent): resolve SDK bus tokens from trusted env - #3288

Merged
Yeachan-Heo merged 1 commit into
Yeachan-Heo:devfrom
10kH:fix/sdk-bus-token-trust
Jul 28, 2026
Merged

fix(coding-agent): resolve SDK bus tokens from trusted env#3288
Yeachan-Heo merged 1 commit into
Yeachan-Heo:devfrom
10kH:fix/sdk-bus-token-trust

Conversation

@10kH

@10kH 10kH commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Same trust-boundary class as #3276 / #3286, applied to the SDK notification bus.

Problem

site read role
sdk/bus/index.ts:1147 GJC_NOTIFICATIONS, GJC_NOTIFICATIONS_TOKEN notificationsEnabled() decides whether the session control/answer channel opens
sdk/bus/telegram-cli.ts:154, :171 GJC_TG_BOT_TOKEN, GJC_TG_CHAT_ID selects the bot that receives the session's notifications and the operator's replies

Bun.env === process.env, and the env module folds the caller's cwd/.env into it.

The bus read was an internal outlier. Its siblings already avoid the merged view:

// config.ts:432,474,498      -> input.env.GJC_NOTIFICATIONS_TOKEN   (injected)
// session-control.ts:428     -> this.#env.GJC_NOTIFICATIONS_TOKEN   (injected)
// index.ts:1147              -> process.env.GJC_NOTIFICATIONS_TOKEN (merged)  <-- this one

The Telegram client matters because it is normally run from inside a repository, so a planted bot token routes the session's notifications and the operator's replies to a bot the repository chose.

Measured with a project .env as the only input (HOME and agent dir neutral):

planted .env    -> {"enabled":false,"botToken":null}
inherited shell -> {"enabled":true,"botToken":"operator-bot"}

Fix

Both resolve through $credentialEnv. Explicit --bot-token / --chat-id arguments still take precedence, and shell / ~/.env / GJC-owned .env configuration is unchanged.

Tests

sdk-bus-token-trust.test.ts spawns a probe with a controlled cwd and neutral HOME / GJC_CODING_AGENT_DIR: disabled with no token by default, the project .env can enable the channel through neither GJC_NOTIFICATIONS_TOKEN nor GJC_NOTIFICATIONS=1, a planted bot token is ignored, inherited settings still apply, and a planted .env cannot override them.

Proof-first: reverting notificationsEnabled fails exactly the two enable cases (4 pass / 2 fail).

Verification

bun run check in packages/coding-agent (whole-package biome + tsc): exit 0. New suite: 6 pass / 0 fail. Notification-named suites: 574 pass / 0 fail.

@Yeachan-Heo Yeachan-Heo left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: APPROVE — exact head c9c8e34fe0c835e0046feb0232e6e57a820669cd

Two fixes: notificationsEnabled() now reads GJC_NOTIFICATIONS/GJC_NOTIFICATIONS_TOKEN via $credentialEnv (its siblings in config.ts/session-control.ts already used an injected env record — this was the outlier direct read), and the Telegram reference client's GJC_TG_BOT_TOKEN/GJC_TG_CHAT_ID fallbacks do the same.

Checks performed:

  • Notification reply/privacy routing: this is the primary risk surface for this PR. Before the fix, a repo-planted GJC_NOTIFICATIONS_TOKEN could open the session control/answer channel, and a repo-planted GJC_TG_BOT_TOKEN could route session notifications and the operator's replies to a bot the repository chose — verified in code and covered by dedicated test cases (ignores a Telegram bot token planted by the project .env, does not let the project .env enable the notifications channel via both the token and the =1 flag paths). This closes a real reply-privacy exfiltration path, not just a credential leak.
  • Trust-source precedence: consistent with $credentialEnv boundary used across the batch.
  • Overlap/order: shares the packages/coding-agent/CHANGELOG.md anchor with #3286/#3287 — same trivial note, no source-file overlap.
  • Tests: hermetic (HOME/GJC_CODING_AGENT_DIR both neutralized); covers default-disabled, project-cannot-enable (both paths), project-cannot-plant-bot-token, inherited-honored, and project-cannot-override-inherited.
  • CI: all 22 checks pass on the exact head (two shard/check jobs were still pending at first poll; rechecked and confirmed all-pass before this verdict).
  • Novelty: no other open PR touches sdk/bus/index.ts or sdk/bus/telegram-cli.ts; not a duplicate.
  • No automated reviewer comments present.

No merge action taken — post-#3281 dev CI 30253189244 is nonterminal and this batch is read-only review only.


[repo owner's gaebal-gajae (clawdbot) 🦞]

@10kH
10kH force-pushed the fix/sdk-bus-token-trust branch 2 times, most recently from afe7099 to 5ba3b09 Compare July 27, 2026 13:43

@Yeachan-Heo Yeachan-Heo left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: APPROVE — exact head 5ba3b0991b68fcfda2257dd7a928a91b76839e00

Rebase-only refresh of the prior exact-head review at c9c8e34fe0c835e0046feb0232e6e57a820669cd (APPROVE).

Checks performed:

  • Content identity confirmed, not assumed: diff <(git show c9c8e34f --format= -- packages/coding-agent/src/sdk/bus/index.ts packages/coding-agent/src/sdk/bus/telegram-cli.ts packages/coding-agent/test/fixtures/sdk-bus-token-probe.ts packages/coding-agent/test/sdk-bus-token-trust.test.ts) <(git show 5ba3b099 --format= -- packages/coding-agent/src/sdk/bus/index.ts packages/coding-agent/src/sdk/bus/telegram-cli.ts packages/coding-agent/test/fixtures/sdk-bus-token-probe.ts packages/coding-agent/test/sdk-bus-token-trust.test.ts) is empty — the PR's own commit is byte-for-byte identical to the version the prior review validated. CHANGELOG.md is intentionally excluded from this file set: its diff context shifted because independently-merged dev commits inserted new entries above the same insertion point, which is expected rebase churn, not a content change to this PR's own line.
  • Rebase mechanics verified: git merge-base confirms this PR carries the same single commit both before and after the rebase; every additional commit between the two heads is an independently-merged dev commit (own-PR-numbered), not new work folded into this PR.
  • Exact-head terminal CI: 17 success / 0 failure / 0 pending / 5 skipped (total 22). No pending/queued checks remain.

Prior technical analysis still applies verbatim to this head.


[repo owner's gaebal-gajae (clawdbot) 🦞]

@Yeachan-Heo Yeachan-Heo left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adversarial batch re-review — exact head 5ba3b0991b68fcfda2257dd7a928a91b76839e00

Verdict: MERGE_READY — held, do not merge (see gating).

Trace

Two independent fixes:

  1. sdk/bus/index.ts:notificationsEnabled()GJC_NOTIFICATIONS / GJC_NOTIFICATIONS_TOKEN move from raw process.env to $credentialEnv. The outlier claim is verified against the tree, not taken on trust: sdk/bus/config.ts:431-498 and sdk/bus/session-control.ts:424-428 both read an injected env record (input.env / this.#env) rather than the merged global view. This function was the only direct global read of that switch. Enabling it opens the session control/answer channel, so repository-reachable enablement was the real exposure.
  2. sdk/bus/telegram-cli.ts:main()GJC_TG_BOT_TOKEN / GJC_TG_CHAT_ID fallbacks move to $credentialEnv. Explicit --bot-token / --chat-id args keep precedence. This client is normally invoked from inside a repository, so the planted-token path was directly reachable; a planted bot token would have routed the session's notifications and the operator's replies to an attacker's bot.

Asymmetry at index.ts:4860 — checked, and correct as-is

process.env.GJC_NOTIFICATIONS === "0" is still read from the merged view. I probed this as a possible missed conversion and it is not one: that read is a hard opt-out. Leaving it on the merged view means a repository can only ever disable notifications, never enable them. Converting it would strictly reduce the fail-safe. The direction of trust is right — narrow the enabling read, leave the disabling read broad.

Tests / CI

sdk-bus-token-trust.test.ts uses the hermetic subprocess pattern with a controlled cwd. Exact-head CI green: check:@gajae-code/coding-agent, test:packages/coding-agent/test/tools/index.test.ts, native-build, cli-smoke, Telegram daemon generation guard, gjc-state-gates.

Merge gating

dev CI is currently red. Durable non-resident hold; sequencing only, no technical defect.


[repo owner's gaebal-gajae (clawdbot) 🦞]

@Yeachan-Heo
Yeachan-Heo force-pushed the fix/sdk-bus-token-trust branch from 5ba3b09 to beb64be Compare July 28, 2026 03:04
Two reads went through the merged view that includes the caller's cwd/.env:

  sdk/bus/index.ts:1147        GJC_NOTIFICATIONS / GJC_NOTIFICATIONS_TOKEN
  sdk/bus/telegram-cli.ts:154  GJC_TG_BOT_TOKEN (and :171 GJC_TG_CHAT_ID)

notificationsEnabled() decides whether the session control/answer channel opens
at all, so a repository could turn it on. Its siblings in config.ts and
session-control.ts already read an injected env record rather than the merged
view, so this direct read was the outlier rather than the pattern.

The Telegram reference client selects the bot that receives the session's
notifications and the operator's replies, and it is normally run from inside a
repository, so a planted token would have routed both to a bot the repository
chose. Explicit --bot-token / --chat-id arguments still take precedence.

Both now resolve through $credentialEnv.

Regression: a child process with a controlled cwd and neutral HOME/agent dir
proves the project .env can enable the channel through neither the token nor the
flag, cannot plant a bot token, and cannot override inherited settings.
Reverting notificationsEnabled fails exactly the two enable cases.
@Yeachan-Heo
Yeachan-Heo force-pushed the fix/sdk-bus-token-trust branch from beb64be to 6a1aa10 Compare July 28, 2026 03:58
@Yeachan-Heo
Yeachan-Heo merged commit e5713c6 into Yeachan-Heo:dev Jul 28, 2026
20 of 23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants