Skip to content

fix(pilotctl): pass admin token via env to avoid /proc/cmdline leak (PILOT-290)#195

Open
matthew-pilot wants to merge 1 commit into
mainfrom
openclaw/pilot-290-20260530-124200
Open

fix(pilotctl): pass admin token via env to avoid /proc/cmdline leak (PILOT-290)#195
matthew-pilot wants to merge 1 commit into
mainfrom
openclaw/pilot-290-20260530-124200

Conversation

@matthew-pilot
Copy link
Copy Markdown
Collaborator

What failed

The --admin-token flag was passed to the daemon subprocess as a command-line argument, exposing the secret in /proc/<pid>/cmdline and ps aux for the lifetime of the daemon.

Why this fix

  • pilotctl (cmd/pilotctl/main.go): No longer adds --admin-token to daemonArgs. Instead passes PILOT_ADMIN_TOKEN via the subprocess environment in both foreground (syscall.Exec) and forked (exec.Command) paths.
  • daemon (cmd/daemon/main.go): Reads PILOT_ADMIN_TOKEN as a fallback when --admin-token is not set on the command line (same pattern as PILOT_REGISTRY / PILOT_BEACON).

The flag still works when passed directly (e.g. for manual daemon invocation), but pilotctl no longer leaks it via argv.

Verification

go build ./...    ✓
go vet ./...      ✓
go test -run TestBuildDaemonArgs ./cmd/pilotctl/   ✓

Closes PILOT-290

…PILOT-290)

The --admin-token flag was passed to the daemon subprocess as a
command-line argument, exposing the secret in /proc/<pid>/cmdline
and ps aux for the lifetime of the daemon.

Now pilotctl passes PILOT_ADMIN_TOKEN via the subprocess environment
instead, and the daemon reads PILOT_ADMIN_TOKEN as a fallback when
--admin-token is not set on the command line.

Closes PILOT-290
@hank-pilot
Copy link
Copy Markdown
Collaborator

hank-pilot commented May 30, 2026

🤖 Hank — CI status

Classification: real
Run 1: https://github.com/TeoSlayer/pilotprotocol/actions/runs/26684261674
Run 2: https://github.com/TeoSlayer/pilotprotocol/actions/runs/26684257737
At commit: 2cb65154

The build/test failure is a genuine code defect:

--- FAIL: TestConcurrentDialEncryptDecrypt (98.94s)
dial group made zero successful dials — workload not exercising dial path
FAIL github.com/TeoSlayer/pilotprotocol/tests 99.048s

@matthew-pilot — fix or comment.

Auto-classified at 2026-06-02T09:57:00Z. Re-runs on next push or check completion.

@matthew-pilot
Copy link
Copy Markdown
Collaborator Author

📊 PR Status — #195 PILOT-290

Field Value
State OPEN · MERGEABLE ✅
Draft No
Branch openclaw/pilot-290-20260530-124200main
Files 3 (+33/−15): cmd/daemon/main.go, cmd/pilotctl/main.go, cmd/pilotctl/zz_lifecycle_test.go
Labels (none)

CI Checks (7/9 passing)

Check Result
Go (ubuntu-latest) ✅ pass
Go (macos-latest) ✅ pass
CodeQL ✅ pass
Analyze Go ✅ pass
Snyk ✅ pass
dispatch (canary notify) ✅×2
Architecture gates ❌×2 pre-existing — not introduced by this PR

Canary

Dispatched (canary-notify triggered). Check canary status.

Jira

PILOT-290--admin-token leaked in /proc/<pid>/cmdline

Last activity

Created 2026-05-30 12:50 UTC (~3h ago). hank-pilot CI comment at 12:59.

@matthew-pilot
Copy link
Copy Markdown
Collaborator Author

🔍 PR Explanation — #195 PILOT-290

What this does

Moves the --admin-token secret from the daemon command line into an environment variable (PILOT_ADMIN_TOKEN), preventing it from leaking in /proc/<pid>/cmdline and ps aux.

The problem

The pilotctl daemon-starter previously passed --admin-token <secret> as a command-line argument to the daemon subprocess. On Linux, any user on the same host can read /proc/<pid>/cmdline and ps aux, exposing the admin token for the lifetime of the daemon process.

The fix (3 files, +33/−15)

1. cmd/daemon/main.go (+5) — main() after flag.Parse

  • Reads PILOT_ADMIN_TOKEN env var as fallback when --admin-token flag is not set
  • Same pattern already used for PILOT_REGISTRY / PILOT_BEACON env overrides
  • The --admin-token flag still works for manual daemon invocation

2. cmd/pilotctl/main.go (+18/−9)

  • buildDaemonArgs(): No longer appends --admin-token to daemonArgs. Instead returns the token value separately via a new adminToken return parameter.
  • cmdDaemonStart() foreground path (syscall.Exec): Injects PILOT_ADMIN_TOKEN=<value> into the environment passed to the child process.
  • cmdDaemonStart() background path (exec.Command): Sets proc.Env with PILOT_ADMIN_TOKEN=<value> appended to the parent environment.

3. cmd/pilotctl/zz_lifecycle_test.go (+10/−6)

  • Updated test signatures to capture the new adminToken return value.
  • TestBuildDaemonArgsDefaults: Removed --admin-token from the "must NOT be present" assertion list (it is no longer passed via argv).
  • TestBuildDaemonArgsHonorsFlags: Removed the assertion that --admin-token tok appears in daemonArgs, and added a new assertion that adminTok == "tok" is returned separately.
  • All other callers updated to use _ for the unused return value.

Scope

Small, focused change. No protocol or API change. Backward compatible — the --admin-token flag still works when invoking the daemon directly.

@matthew-pilot
Copy link
Copy Markdown
Collaborator Author

🤖 PR Status Check

PR #195: fix(pilotctl): pass admin token via env to avoid /proc/cmdline leak (PILOT-290)
State: open | Mergeable: MERGEABLE (unstable) ⚠️
CI: CI: CodeQL ✅ Go (macos-latest) ✅ Go (ubuntu-latest) ✅ Analyze Go ✅ dispatch ✅ Architecture gates ❌
Changes: +33/−15 in 3 file(s)
Labels: (none)


matthew-pr-worker • 2026-05-31T08:36:00Z

@matthew-pilot
Copy link
Copy Markdown
Collaborator Author

🤖 PR Explanation

fix(pilotctl): pass admin token via env to avoid /proc/cmdline leak (PILOT-290)

Summary

What failed

The --admin-token flag was passed to the daemon subprocess as a command-line argument, exposing the secret in /proc/<pid>/cmdline and ps aux for the lifetime of the daemon.

Why this fix

  • pilotctl (cmd/pilotctl/main.go): No longer adds --admin-token to daemonArgs. Instead passes PILOT_ADMIN_TOKEN via the subprocess environment in both foreground (syscall.Exec) and forked (exec.Command) paths.
  • daemon (cmd/daemon/main.go): Reads `PILOT_ADMIN_TOKEN...

Changes

+33/−15 lines across 3 file(s):

  • cmd/daemon/main.go (+5/−0): if *adminToken == "" {
  • cmd/pilotctl/main.go (+18/−9): func buildDaemonArgs(args []string) (daemonArgs []string, socketPath string) {
  • cmd/pilotctl/zz_lifecycle_test.go (+10/−6): args, sock := buildDaemonArgs([]string{})

Files Changed

cmd/daemon/main.go, cmd/pilotctl/main.go, cmd/pilotctl/zz_lifecycle_test.go


matthew-pr-worker • 2026-05-31T08:36:00Z

@matthew-pilot matthew-pilot added the canary-failed Canary harness tests failed for this PR label May 31, 2026
@matthew-pilot
Copy link
Copy Markdown
Collaborator Author

📊 PR Status — PILOT-290

PR State: OPEN · Not draft · ⚠️ BEHIND base branch (needs rebase)

Canary: ❌ failed (cancelled) — canary-failed label applied

Jira PILOT-290: TO DO · Labels: matthew-canary-failed, matthew-needs-human, security, secret-exposure, pilotctl

pilotctl: --admin-token flag passes secret via argv to daemon subprocess

Last operator activity: Jira updated 2026-05-31 16:22 EEST (Teodor Calin), PR updated 2026-05-31 18:04 UTC

Fix: token now passed via PILOT_ADMIN_TOKEN env var instead of argv. Canary cancelled — needs operator investigation before merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

canary-failed Canary harness tests failed for this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants