fix(sidecar_init): strip EGRESS_TOKEN_* from non-egress daemons' env (issue #84)
Pipelock was 403-blocking legitimate egress cred-injected traffic with 'blocked: request header contains secret'. The chain is `agent → egress → pipelock → internet`: egress injects `Authorization: Bearer <token>` for routes with an `auth_scheme`, then forwards upstream to pipelock. Pipelock has `scan_env: true` + `scan_headers: true` + `header_mode: all`, and the bundle supervisor spawned every daemon (egress, pipelock, git-gate, supervise) inheriting the bundle container's full env — including the `EGRESS_TOKEN_<n>` slots set via `docker run -e`. So pipelock had the token value egress injected sitting in its own env, matched it in the request headers, and blocked. The agent itself runs in a different machine and never sees `EGRESS_TOKEN_*`, so stripping these from non-egress daemons' env loses no DLP coverage — pipelock can't catch the exfil of a value the agent doesn't have in the first place. New helper `_env_for_daemon(name, base_env)` returns the unchanged base for `egress` and a copy with `EGRESS_TOKEN_*` filtered for everyone else. `_spawn` now passes the scoped env to `subprocess.Popen`. Prefix-based filter (not exact-match) so future egress-only env slots don't have to update this code. Tests: - `TestEnvForDaemon`: egress gets full env, pipelock / git-gate / supervise lose `EGRESS_TOKEN_0` + `EGRESS_TOKEN_1` but keep `PATH`, `EGRESS_UPSTREAM_PROXY`, `SUPERVISE_PORT`. - Independent-dict invariant locked so callers can't accidentally mutate the supervisor's env. 642 unit tests pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit was merged in pull request #85.
This commit is contained in:
@@ -54,6 +54,33 @@ class _DaemonSpec:
|
||||
argv: Sequence[str]
|
||||
|
||||
|
||||
# Env-var name prefixes that carry egress-only credentials.
|
||||
# `egress_apply.py` assigns `EGRESS_TOKEN_<n>` slots that egress
|
||||
# reads to inject `Authorization` headers on configured routes;
|
||||
# every other daemon in the bundle (especially pipelock with
|
||||
# `scan_env: true`) MUST NOT see these values or it'll match the
|
||||
# injected token in the request egress just sent and 403-block
|
||||
# the legitimate traffic (issue #84). The agent itself runs in a
|
||||
# different machine and never has access to these slots in the
|
||||
# first place, so stripping them from non-egress daemons loses no
|
||||
# DLP coverage — pipelock can't catch the exfil of a value the
|
||||
# agent doesn't have.
|
||||
_EGRESS_ONLY_ENV_PREFIXES: tuple[str, ...] = ("EGRESS_TOKEN_",)
|
||||
|
||||
|
||||
def _env_for_daemon(name: str, base_env: dict[str, str]) -> dict[str, str]:
|
||||
"""Egress sees the full bundle env. Everyone else gets a copy
|
||||
with `EGRESS_TOKEN_*` (and any other future egress-only
|
||||
credential slots) stripped. Returns a fresh dict — callers
|
||||
can mutate without affecting `base_env`."""
|
||||
if name == "egress":
|
||||
return dict(base_env)
|
||||
return {
|
||||
k: v for k, v in base_env.items()
|
||||
if not any(k.startswith(p) for p in _EGRESS_ONLY_ENV_PREFIXES)
|
||||
}
|
||||
|
||||
|
||||
# Order matters only for first-launch race-window reasons: egress
|
||||
# starts first so pipelock's upstream connect succeeds during
|
||||
# pipelock's own startup. git-gate and supervise are independent.
|
||||
@@ -116,6 +143,7 @@ def _spawn(spec: _DaemonSpec) -> subprocess.Popen:
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
bufsize=0,
|
||||
env=_env_for_daemon(spec.name, dict(os.environ)),
|
||||
)
|
||||
threading.Thread(
|
||||
target=_pump, args=(spec.name, proc.stdout), daemon=True
|
||||
|
||||
Reference in New Issue
Block a user