Summary
The block list in executor/intercept/callhook.go is implemented as:
joined := strings.Join(args, " ")
for _, pattern := range cfg.BlockList {
if strings.HasPrefix(joined, pattern) {
// blocked
}
}
strings.HasPrefix on the joined argument string means the check is trivially bypassed by:
- Using the full binary path instead of the base name
- Reordering arguments
- Using backslash-escaped command names
Affected code
Bypass demonstration (code-level)
BlockList: ["rm -rf /"]
BLOCKED:
args = ["rm", "-rf", "/"] → joined = "rm -rf /" → HasPrefix = true ✓
NOT BLOCKED (bypasses):
args = ["/bin/rm", "-rf", "/"] → joined = "/bin/rm -rf /" → HasPrefix = false ✗
args = ["\\rm", "-rf", "/"] → joined = "\\rm -rf /" → HasPrefix = false ✗
args = ["rm", "/", "-rf"] → joined = "rm / -rf" → HasPrefix = false ✗
Verified against Go's strings.HasPrefix semantics:
/bin/rm -rf / → blocked=False ← BYPASS
\rm -rf / → blocked=False ← BYPASS
rm / -rf → blocked=False ← BYPASS
rm -rf / → blocked=True (correctly blocked)
Impact
Block lists intended as a last-resort safety net (e.g., blocking dd if=/dev/zero, mkfs, rm -rf /) can be bypassed by any caller who knows the full binary path. This gives a false sense of security to operators who configure block lists expecting them to actually block the operation.
Fix
Match filepath.Base(args[0]) separately from the arguments, or use a more structured matching approach:
// Match command basename + args separately
base := filepath.Base(args[0])
argsStr := strings.Join(args[1:], " ")
for _, pattern := range cfg.BlockList {
parts := strings.SplitN(pattern, " ", 2)
if base == parts[0] && (len(parts) == 1 || strings.HasPrefix(argsStr, parts[1])) {
// blocked
}
}
Or document explicitly that the block list is advisory/audit-only, not a security boundary.
Severity
Medium — the block list is not the primary security mechanism, but its bypass is non-obvious to operators who configure it expecting real protection.
Summary
The block list in
executor/intercept/callhook.gois implemented as:strings.HasPrefixon the joined argument string means the check is trivially bypassed by:Affected code
executor/intercept/callhook.go:55-62Bypass demonstration (code-level)
Verified against Go's
strings.HasPrefixsemantics:Impact
Block lists intended as a last-resort safety net (e.g., blocking
dd if=/dev/zero,mkfs,rm -rf /) can be bypassed by any caller who knows the full binary path. This gives a false sense of security to operators who configure block lists expecting them to actually block the operation.Fix
Match
filepath.Base(args[0])separately from the arguments, or use a more structured matching approach:Or document explicitly that the block list is advisory/audit-only, not a security boundary.
Severity
Medium — the block list is not the primary security mechanism, but its bypass is non-obvious to operators who configure it expecting real protection.