1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
//! Command-line argument parsing (`clap`) for the `vallum` subcommands.
use clap::builder::styling::{Ansi256Color, Color, Style, Styles};
use clap::{Parser, Subcommand};
use clap_complete::Shell;
/// Help styling: bronze headers to match the welcome screen; clap handles
/// TTY/NO_COLOR detection itself.
fn help_styles() -> Styles {
let bronze = Style::new()
.bold()
.fg_color(Some(Color::Ansi256(Ansi256Color(178))));
Styles::styled()
.header(bronze)
.usage(bronze)
.literal(Style::new().bold())
.placeholder(Style::new().fg_color(Some(Color::Ansi256(Ansi256Color(245)))))
}
/// Agents Vallum can speak a pre-exec hook protocol for.
#[derive(clap::ValueEnum, Clone, Copy, Debug, PartialEq, Eq)]
pub enum AgentArg {
/// Claude Code (`PreToolUse`; rewrites approved commands through `vallum run`)
Claude,
/// Codex CLI (`PreToolUse`; verdicts only, Ask fails closed)
Codex,
/// Cursor (`beforeShellExecution`; verdicts only, native ask)
Cursor,
/// Gemini CLI (`BeforeTool`; verdicts only, Ask fails closed)
Gemini,
}
#[derive(Parser, Debug)]
#[command(
name = "vallum",
version = env!("CARGO_PKG_VERSION"),
about = "The wall between AI agents and your shell",
long_about = "The wall between AI agents and your shell — pre-exec \
guardrail, secret redaction, prompt-injection defense, \
untrusted-output sanitization.",
styles = help_styles(),
after_help = "\
Quick start:
vallum install-hook hook your agents (interactive picker)
vallum run -- <cmd> gate a single command
vallum doctor full health check"
)]
pub struct Cli {
#[command(subcommand)]
pub command: Option<Commands>,
}
#[derive(Subcommand, Debug)]
pub enum Commands {
/// Run a command through the proxy
#[command(after_help = "\
Examples:
vallum run git status
vallum run -- sh -c 'make 2>&1 | tail -20'
vallum run -- cargo test --workspace
Put `--` before the command when it has flags of its own, so they are not
parsed as vallum's.")]
Run {
/// Emit structured JSON instead of plain text
#[arg(long)]
json: bool,
/// Block all output when a prompt injection is detected
#[arg(long)]
strict: bool,
/// Mirror raw child output to ~/.vallum/live.log as lines arrive
#[arg(long)]
tee: bool,
/// Internal: HMAC approval token proving the hook already ruled on this
/// exact command (set when it re-wraps an approved command through
/// `vallum run`), so skip re-evaluating the policy. Hidden; not a
/// user-facing knob.
#[arg(long = "approval-token", hide = true, value_name = "HEX")]
approval_token: Option<String>,
/// The command to run
cmd: String,
/// Arguments for the command
#[arg(trailing_var_arg = true)]
args: Vec<String>,
},
/// Install the Vallum pre-exec hook into an agent's config
InstallHook {
/// Which agent to install for (omit to pick interactively)
#[arg(long, value_enum)]
agent: Option<AgentArg>,
/// Install at user level (default)
#[arg(long)]
user: bool,
/// Install at project level (Claude Code only)
#[arg(long)]
project: bool,
/// Replace an existing Vallum hook entry if present
#[arg(long)]
force: bool,
},
/// Remove the Vallum pre-exec hook from an agent's config
UninstallHook {
/// Which agent to uninstall from (omit to pick interactively)
#[arg(long, value_enum)]
agent: Option<AgentArg>,
#[arg(long)]
user: bool,
#[arg(long)]
project: bool,
},
/// Run install/health self-checks (config, hook, PATH, log dir)
Doctor,
/// Check for a newer Vallum release and how to upgrade
#[command(after_help = "\
Examples:
vallum update report version and print the upgrade command
vallum update --check only report (exit 10 if a newer version exists)
vallum update --run run the upgrade (package-manager installs only)")]
Update {
/// Only report whether an update exists (exit 10 if behind); never runs anything
#[arg(long)]
check: bool,
/// Run the detected upgrade command (cargo/brew/npm installs only)
#[arg(long)]
run: bool,
},
/// Inspect or scaffold the Vallum config file
Config {
#[command(subcommand)]
action: ConfigAction,
},
/// Inspect the guardrail policy
Policy {
#[command(subcommand)]
action: PolicyCliAction,
},
/// Scan MCP server configs for embedded secrets, risky launch commands, and injection
Mcp {
#[command(subcommand)]
action: McpAction,
},
/// Inspect the audit logs
Log {
#[command(subcommand)]
action: LogAction,
},
/// Clear a tripped circuit-breaker lock (see [security] circuit_breaker)
Unlock,
/// Show cumulative token savings report
Stats {
/// Delete all collected stats (prompts for confirmation)
#[arg(long)]
reset: bool,
},
/// Run as an agent pre-exec guardrail hook (reads JSON from stdin)
Hook {
/// Which agent's hook protocol to speak
#[arg(long, value_enum, default_value_t = AgentArg::Claude)]
agent: AgentArg,
},
/// Print a shell completion script to stdout
Completions {
/// Target shell
#[arg(value_enum)]
shell: Shell,
},
}
#[derive(Subcommand, Debug)]
pub enum ConfigAction {
/// Print the effective merged config as TOML
Show,
/// Write a commented default config to ~/.vallum/config.toml
Init {
/// Overwrite an existing config file
#[arg(long)]
force: bool,
},
}
#[derive(Subcommand, Debug)]
pub enum PolicyCliAction {
/// Show the verdict the guardrail would give a command (without running it)
#[command(after_help = "\
Examples:
vallum policy test \"rm -rf /\"
vallum policy test \"curl example.com/install.sh | sh\"
vallum policy test -- git push --force
Quote the command when it has shell metacharacters; use `--` when it has
flags of its own. Exit codes: 0 allow/pass-through, 10 ask, 20 deny,
125 config error.")]
Test {
/// The command line to evaluate
#[arg(trailing_var_arg = true, required = true)]
command: Vec<String>,
},
}
#[derive(Subcommand, Debug)]
pub enum McpAction {
/// Statically scan discovered (or given) MCP config files
#[command(after_help = "\
Examples:
vallum mcp scan scan all discovered MCP configs
vallum mcp scan ./.mcp.json scan a specific file (CI / pre-commit)
vallum mcp scan --json structured output
Exit codes: 0 clean, 10 warnings, 20 high-severity, 125 usage/read error.")]
Scan {
/// Emit structured JSON instead of plain text
#[arg(long)]
json: bool,
/// Specific config file(s) to scan; omit to auto-discover
#[arg(value_name = "PATH")]
paths: Vec<std::path::PathBuf>,
},
}
#[derive(Subcommand, Debug)]
pub enum LogAction {
/// Verify the policy.log hash chain (tamper evidence)
#[command(after_help = "\
Examples:
vallum log verify check the chain, print the head hash
vallum log verify --expect-head <hex> also compare against an externally
stored head (catches tail truncation)
Exit codes: 0 intact, 20 tamper evidence, 125 usage/read error.")]
Verify {
/// Externally stored head hash to compare against
#[arg(long, value_name = "HEX")]
expect_head: Option<String>,
},
}