feat(security): add gated_commands TOTP gate for shell tool (phase 1, closes #3767)#5779
feat(security): add gated_commands TOTP gate for shell tool (phase 1, closes #3767)#5779DjaPy wants to merge 3 commits intozeroclaw-labs:masterfrom
Conversation
…loses zeroclaw-labs#3767) Add `security.otp.gated_commands` — a list of glob-style shell command patterns that require a valid TOTP code before execution, even when `shell` is in `auto_approve`. A trailing `*` acts as a suffix glob (e.g. `"sudo *"` gates any sudo subcommand); no wildcard means exact match. Changes: - `OtpConfig.gated_commands: Vec<String>` field with validation - `SecurityPolicy.gated_commands` + `is_command_gated()` method + `with_gated_commands()` builder - `matches_gated_pattern()` helper for word-boundary glob matching across pipe/chain segments - `ShellTool.otp_validator` field + `otp_code` schema param; gated commands return an error prompting for the TOTP code, validated via `OtpValidator` on retry - `all_tools()` builds `OtpValidator` from config and wires it into `ShellTool` - Orchestrator and gateway propagate `gated_commands` into `SecurityPolicy` - `prompt_summary()` surfaces gated patterns to the LLM system prompt - 8 unit tests covering pattern matching, builder, and pipeline segment detection `security.otp.enabled = false` (default) preserves existing behaviour. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Hey! CI is failing on Security Audit due to RUSTSEC-2026-0098 and RUSTSEC-2026-0099, two The fix landed in #5786 (merged today). Please merge git fetch upstream
git merge upstream/master
git pushSorry for the noise! |
singlerider
left a comment
There was a problem hiding this comment.
Comprehension summary: Adds security.otp.gated_commands — a glob-style pattern list that requires a TOTP code via a new otp_code shell parameter before executing matching commands. The gate is enforced at the tool level before execution, not bypassable via approved=true. Adds is_command_gated() to SecurityPolicy, wires OtpValidator into ShellTool, and extends OtpConfig schema. Blast radius: shell tool execution path, security policy, config schema, and gateway/orchestrator startup wiring.
Security/performance assessment: The TOTP gate is enforced before execution, not after approval. gated_commands = [] (default) and security.otp.enabled = false (default) both preserve all existing behavior. No timing attack mitigation visible in the TOTP comparison, but this is inherited from the existing OTP infrastructure. Routing to @JordanTheJet for full security review as previously noted. See inline comment for a credential-in-logs concern.
[blocking] CI Required Gate and Security Required Gate are failing. Security Audit is failing on RUSTSEC-2026-0098 and RUSTSEC-2026-0099 (repo-wide rustls-webpki advisories, not PR-specific). The fix landed in master via #5786. Please merge upstream master and push to clear CI.
| // Enforce gated_commands: commands matching configured patterns require | ||
| // a valid TOTP code supplied via the `otp_code` parameter. | ||
| if self.security.is_command_gated(command) { | ||
| let otp_code = args |
There was a problem hiding this comment.
[blocking] otp_code is part of the args: serde_json::Value passed to execute(). Several logging paths operate on the raw args object before this extraction:
ApprovalManager::record_decision()callssummarize_args(&args)which serializes all argument keys and values into the audit log. Theotp_codefield will appear in the audit trail.- PR fix(observability): complete OTEL trace pipeline for all agent session types #5804 (pending) adds OTEL
tool.argumentsspan attributes from raw tool args whenZEROCLAW_OTEL_TRACE_CONTENT=true. If that PR merges alongside this one, TOTP codes will appear in OTEL traces. - Any runtime JSONL trace that serializes tool arguments will include
otp_code.
TOTP codes are time-limited (30s window) but should still not persist in logs or traces. The fix is to strip otp_code from args before passing to any logging/audit path, or to add otp_code to an explicit redaction list in summarize_args. This must be resolved before merge.
Summary
mastersecurity.otp.gated_actionsgates the entireshelltool, but there is no way to allow shell access in general while requiring TOTP only for specific destructive/admin commands (e.g.sudo *,rm -rf *,systemctl restart *).security.otp.gated_commands— a glob-style pattern list that requires a TOTP code via the newotp_codeshell parameter before executing matching commands.gated_actions,gated_domains, estop, channel approval flow, Phase 2 (config immutability), Phase 3 (financial gating).security.otp.enabled = false(default) preserves all existing behaviour.Label Snapshot (required)
risk: low|medium|high):risk: mediumsize: XS|S|M|L|XL, auto-managed/read-only):size: Msecurity,tool,configtool: shell,config: otpChange Metadata
featuresecurityLinked Issue
Supersede Attribution (required when
Supersedes #is used)N/A
Validation Evidence (required)
Security Impact (required)
gated_commandsallows expressing "allow but require 2FA" for shell commandsgated_commands = [](default) orsecurity.otp.enabled = false, no behaviour changes. When configured, the gate is enforced before command execution at the tool level — not bypassable viaapproved=true. An attacker without the TOTP secret cannot satisfy the gate even with physical phone access.Privacy and Data Hygiene (required)
passCompatibility / Migration
gated_commandsdefaults to empty list; existing configs unaffectedsecurity.otp.gated_commands = [...]i18n Follow-Through (required when docs or user-facing wording changes)
Human Verification (required)
gated_commandsskips all gating;"sudo *"pattern blockssudo rebootwithout OTP code and allows it with valid code; pipelinels | sudo tee /etc/hostscorrectly gates;security.otp.enabled = falsedisables gating entirely"*"pattern; exact match without wildcard; multi-segment piped command; validator not configured but pattern set (returns config error)Side Effects / Blast Radius (required)
ShellTool,SecurityPolicy,OtpConfig, orchestrator startup, gateway startupgated_commandsis set butsecurity.otp.enabled = false, commands will fail with a clear error message instead of executing — operator must enable OTP to use the featureis_command_gatedunit tests; CI will catch regressionsAgent Collaboration Notes (recommended)
Rollback Plan (required)
security.otp.gated_commands = [](empty list) disables the feature without redeploy; or setsecurity.otp.enabled = falsesecurity.otp.enabledandsecurity.otp.gated_commandsRisks and Mitigations
gated_commandsto match too broadly (e.g."*") and gates all shell commandsgated_commands = []instantly disables; the"*"wildcard matching is documented in the config field docstring