NAME
policy-demo - show shell-metacharacter argv rejection
SYNOPSIS
policy-demo
DESCRIPTION
policy-demo exercises cli-guard's argv pre-validation gate. Every call into a wrapped Action passes through policy.ValidateArg before execve. The gate rejects any string containing one of these bytes:
` $ ; & | < > ( ) { } \ \n \r \t
Why this exists, even though cli-guard always builds an explicit argv slice and never invokes /bin/sh: a non-trivial fraction of downstream tools hand their last positional argument to a remote shell. Examples:
ssh user@host '<remote-command>'
kubectl exec pod -- sh -c '<command>'
git config --global core.editor '<editor-cmd>'
If the agent driving cli-guard never sanitizes inputs and the wrapped tool unsplats argv into a shell on the other side, a single semicolon in an argument turns a benign verb into a chained injection. Rejecting the metacharacters at the coily boundary keeps that one-layer leak from becoming an execution surprise two hops downstream.
Operating model for an agent calling these commands:
- Any rejected input fails the verb deterministically with
exitcode.PolicyDenied (2). The argv never reaches execve.
- The error format is stable and parseable:
policy: shell metacharacter rejected: arg <name> contains '<char>' at index <i>
- On rejection, DO NOT retry with a quoted or escaped variant. The
input is hostile by definition. Surface the rejection to the
operator.
- The gate is content-only. It does not check semantics. "rm -rf /"
with no metacharacters passes the gate; whether the wrapped tool
should run it is a separate verb-allowlist decision.
The two leaves below share an identical Action body. The only difference is the input the operator types. That is the point: the gate is the gate, not the leaf.
Usage:
policy-demo [GLOBAL OPTIONS] [command [COMMAND OPTIONS]] [ARGUMENTS...]
COMMANDS
safe
validates a single positional arg
unsafe
demonstrate the rejection path