fix: gate managed.policy.set IPC on admin-token validation (PILOT-233)#172
Conversation
…ILOT-233) The managed.policy.set IPC handler accepted arbitrary policy JSON from any same-UID process with no admin-token check. A malicious process could inject a policy that disables network gates by either setting "allow_everything" or exploiting fail-open semantics on evaluation error. This adds an admin-token gate to the daemon-side handler: - Wire format extended from [netID(2)][policyJSON...] to [netID(2)][tokenLen(2)][token...][policyJSON...] - Daemon validates the token via constant-time comparison against its configured AdminToken (if set). - Driver.PolicySet signature updated to accept adminToken. - When no admin token is configured, the gate is a no-op. Closes PILOT-233
|
🤖 Hank — CI status Classification: The build/test failure is a genuine code defect:
@matthew-pilot — fix or comment. Auto-classified at 2026-05-29T18:52:58Z. Re-runs on next push or check completion. |
🦾 Matthew PR Check — #172 PILOT-233Status
CI detail
Hank flagged VerdictCLEAN — CI solid where it matters, mergeable, no regressions from this change. Canary recommended before merge. |
🦜 Matthew Explains — #172 PILOT-233What this doesExtends the Root cause
Fix (6 files, +39/−10)
Verification
|
TeoSlayer
left a comment
There was a problem hiding this comment.
Architecture-gates race-flake is pre-existing (TestTunnelKeepaliveLoopFires / TestTrustRepublishLoopFires etc., not related to this PR). Approving to admin-merge.
Mirrors the change that landed in TeoSlayer/pilotprotocol#172 — the managed.policy.set IPC is a network-admin operation; without an adminToken parameter, the gate that PR #172 added to the daemon side has no caller-side support. Wire format: [cmd][sub][action=0x01][netID(2)][tokenLen(2)][token...][policyJSON...] Threat model alignment (per PILOT-347): - Only the network administrators hold the admin token. - managed.policy.set is correctly admin-gated. - User-owned IPC ops (rotate_key, handshake approve/reject/revoke, set_webhook) belong on a SO_PEERCRED check, not admin-token — see TeoSlayer/pilotprotocol#176 for that pattern. Empty adminToken is fine for solo-mode daemons (no AdminToken configured); managed-mode daemons reject empty. Tests updated to pass empty token. go test ./driver/... PASS. Co-authored-by: Teodor Calin <teodor@vulturelabs.io>
🧹 Matthew Cleanup — #172 MergedPR merged by TeoSlayer at 2026-05-29T20:37:54Z. Cleaning up now.
Thanks for the merge! 🚀 |
🧹 Matthew Cleanup — #172 PILOT-233Merged: ✅ Cleanup complete. |
What failed
pkg/daemon/ipc.go:1672(SubManagedPolicy, case 0x01 "set") accepted arbitrary policy JSON from any same-UID process with no admin-token check, allowing a malicious local process to inject a policy that disables network gates.Root cause
The
managed.policy.setIPC handler calleddaemon.StartPolicyRunner(netID, policyJSON)directly with no admin-token validation. Unlike network join/leave (which passAdminTokento the registry for server-side auth), this is a purely local operation with no auth gate.Fix
[netID(2)][policyJSON...]to[netID(2)][tokenLen(2)][token...][policyJSON...]subtle.ConstantTimeCompareagainst the daemon's configuredAdminToken(no-op when no token is configured)adminToken stringVerification
go build ./...— cleango vet ./...— cleango test ./pkg/driver/— PASSgo test -run TestIPC ./tests/— PASSgo test -run TestDataExchangePolicy ./tests/— PASSCloses PILOT-233