main · last commit
13 days ago ·
7g0stsfu
ah-tls [SECURITY] the agent pane inherits the daemon's full environment — every token is readable with a bare `env`
Lined Up
bd update ah-tls --claim
bd close ah-tls
| Created by | Eugene Blikh |
| Owner | bigbes@gmail.com |
| Created | 2026-08-05T00:03:32Z |
| Updated | 2026-08-05T00:03:32Z |
Found while refreshing SPEC §15 (ah-e5l). §15 previously CLAIMED 'the pi process inherits only the env it needs (PATH, HOME, ...)'. That claim is false, and the spec now records the reality instead. Nothing anywhere sets cmd.Env: - internal/runner/runner.go:984 runCmd uses exec.CommandContext with a nil Env, so the zellij SERVER created by zellijEnsureSession inherits agenthubd's full environment; - every pane spawned into that session inherits the server's env; - cmd/ahub-run/main.go:281 syscall.Execs the keep-alive shell with os.Environ(). So every ${VAR} the daemon expands at config load — the Vikunja API token, the notifier token (Telegram bot token or ntfy token), the Yonote BOT token, the mem0 API key — is readable from inside the agent's own pane with a bare 'env'. The agent is an LLM running attacker-influenced content (card descriptions, Yonote doc text, comments from anyone with board access), and it has a shell. Blast radius is the whole external surface: board write access, notification spoofing, publishing/editing Yonote docs as the bot, and reading/writing the memory store. This is strictly worse than the per-task bearer token the same system carefully scopes to three calls on one task — that token is 256-bit, attempt-bound, in-memory and invalidated at finalize, while the credentials that actually matter sit in plain view next to it. Filed P1 as the highest-severity finding of the 2026-08-05 sweep. Note this is a homelab single-user deployment, so it is not an active incident — but it is the one defect that makes every other credential control decorative.
Set an explicit allowlist Env on the processes the runner spawns rather than trying to scrub. Three places, and the ZELLIJ SERVER is the one that actually matters — a pane inherits the server's env, not the run-client's, so passing a clean Env only to the pane command is NOT enough (this is the same server-env-inheritance property that F12 already forced absolute binary paths for; see the LookPath comments in runner.Start). Sketch: build the child env from an explicit list — PATH, HOME, USER, SHELL, TERM, LANG/LC_*, TMPDIR, the proxy vars the notifier lane needs, plus whatever pi itself requires for its provider config — and nothing else. Then decide deliberately what pi needs to reach LiteLLM: if its key comes from the environment, that one credential has to be present, and the honest statement becomes 'the agent can read the LLM key and nothing else', which is a defensible position worth writing into §15. Watch out for: (a) an already-running zellij server will keep its old env, so this needs a server restart to take effect and the deploy lane should be told; (b) ahub-run's keep-pane syscall.Exec passes os.Environ() — by then it is already inside the pane, so it inherits whatever the pane got, but it should not re-add anything; (c) the e2e harness stubs may rely on inherited env.
A pane spawned for a task shows none of vikunja.token, the notifier token, yonote.token or mem0.api_key in 'env'. SPEC §15 states the resulting inheritance rule and names any credential deliberately left reachable. Covered by a runner test asserting the constructed Env is the allowlist and not os.Environ().
No outgoing dependencies.
Nothing depends on this issue.
| id | ah-tls |
| content_hash | 4f7e99393b69a2de3f7a923407d49b7d6f2a27a93f722df4ac25187f40adfd53 |
| title | [SECURITY] the agent pane inherits the daemon's full environment — every token is readable with a bare `env` |
| description | Found while refreshing SPEC §15 (ah-e5l). §15 previously CLAIMED 'the pi process inherits only the env it needs (PATH, HOME, ...)'. That claim is false, and the spec now records the reality instead. Nothing anywhere sets cmd.Env: - internal/runner/runner.go:984 runCmd uses exec.CommandContext with a nil Env, so the zellij SERVER created by zellijEnsureSession inherits agenthubd's full environment; - every pane spawned into that session inherits the server's env; - cmd/ahub-run/main.go:281 syscall.Execs the keep-alive shell with os.Environ(). So every ${VAR} the daemon expands at config load — the Vikunja API token, the notifier token (Telegram bot token or ntfy token), the Yonote BOT token, the mem0 API key — is readable from inside the agent's own pane with a bare 'env'. The agent is an LLM running attacker-influenced content (card descriptions, Yonote doc text, comments from anyone with board access), and it has a shell. Blast radius is the whole external surface: board write access, notification spoofing, publishing/editing Yonote docs as the bot, and reading/writing the memory store. This is strictly worse than the per-task bearer token the same system carefully scopes to three calls on one task — that token is 256-bit, attempt-bound, in-memory and invalidated at finalize, while the credentials that actually matter sit in plain view next to it. Filed P1 as the highest-severity finding of the 2026-08-05 sweep. Note this is a homelab single-user deployment, so it is not an active incident — but it is the one defect that makes every other credential control decorative. |
| design | Set an explicit allowlist Env on the processes the runner spawns rather than trying to scrub. Three places, and the ZELLIJ SERVER is the one that actually matters — a pane inherits the server's env, not the run-client's, so passing a clean Env only to the pane command is NOT enough (this is the same server-env-inheritance property that F12 already forced absolute binary paths for; see the LookPath comments in runner.Start). Sketch: build the child env from an explicit list — PATH, HOME, USER, SHELL, TERM, LANG/LC_*, TMPDIR, the proxy vars the notifier lane needs, plus whatever pi itself requires for its provider config — and nothing else. Then decide deliberately what pi needs to reach LiteLLM: if its key comes from the environment, that one credential has to be present, and the honest statement becomes 'the agent can read the LLM key and nothing else', which is a defensible position worth writing into §15. Watch out for: (a) an already-running zellij server will keep its old env, so this needs a server restart to take effect and the deploy lane should be told; (b) ahub-run's keep-pane syscall.Exec passes os.Environ() — by then it is already inside the pane, so it inherits whatever the pane got, but it should not re-add anything; (c) the e2e harness stubs may rely on inherited env. |
| acceptance_criteria | A pane spawned for a task shows none of vikunja.token, the notifier token, yonote.token or mem0.api_key in 'env'. SPEC §15 states the resulting inheritance rule and names any credential deliberately left reachable. Covered by a runner test asserting the constructed Env is the allowlist and not os.Environ(). |
| notes | |
| status | open |
| priority | 1 |
| issue_type | bug |
| assignee | NULL |
| estimated_minutes | NULL |
| created_at | 2026-08-05T00:03:32Z |
| created_by | Eugene Blikh |
| owner | bigbes@gmail.com |
| updated_at | 2026-08-05T00:03:32Z |
| closed_at | NULL |
| closed_by_session | |
| external_ref | NULL |
| spec_id | |
| compaction_level | 0 |
| compacted_at | NULL |
| compacted_at_commit | NULL |
| original_size | NULL |
| sender | |
| ephemeral | 0 |
| wisp_type | |
| pinned | 0 |
| is_template | 0 |
| mol_type | |
| work_type | |
| source_system | |
| metadata | �{} |
| source_repo | |
| close_reason | |
| event_kind | |
| actor | |
| target | |
| payload | |
| await_type | |
| await_id | |
| timeout_ns | 0 |
| waiters | |
| hook_bead | |
| role_bead | |
| agent_state | |
| last_activity | NULL |
| role_type | |
| rig | |
| due_at | NULL |
| defer_until | NULL |
| no_history | 0 |
| started_at | NULL |
| is_blocked | 0 |
| issue_id | ah-tls |
| label | milestone:v0.2.0 |
| id | 019fcf3b-833a-7af0-b8b1-028b145b20b3 |
| issue_id | ah-tls |
| event_type | created |
| actor | Eugene Blikh |
| old_value | |
| new_value | |
| comment | NULL |
| created_at | 2026-08-05T03:03:31Z |
| id | 019fcf42-14f6-71c9-9bc1-8b3d929b1b95 |
| issue_id | ah-tls |
| event_type | label_added |
| actor | Eugene Blikh |
| old_value | NULL |
| new_value | NULL |
| comment | Added label: milestone:v0.2.0 |
| created_at | 2026-08-05T03:10:42Z |
No comments.