How dcg Handles Recursive Shell Analysis for `bash -c` and Inline Code Invocations
The dcg (Destructive Command Guard) tool treats arguments of shell code flags like bash -c as inline code rather than static data, recursively extracting and scanning inner payloads against the same destructive pattern database used for top-level commands.
The dcg crate in the Dicklesworthstone/destructive_command_guard repository provides robust protection against hidden destructive commands by implementing recursive shell analysis that pierces through wrapper invocations. When a command uses inline code flags, dcg extracts the nested script and evaluates it independently, ensuring that dangerous operations cannot evade detection by simply wrapping them in a subshell.
Detecting Inline Code Flags in Context Parsing
The first step in recursive analysis occurs in src/context.rs, where the parser identifies when a shell is being invoked with a code-execution flag.
Identifying -c and -e Arguments
When parsing a command, Context::extract_inline_code walks the token list backward looking for known interpreter flags (-c, -e, -lc, etc.). If it finds a match, it records the span of the code argument rather than treating it as benign data:
// src/context.rs (excerpt)
/// Content of -c/-e flags (bash -c, python -c, node -e).
fn is_argument_data(cmd: &str, flag: Option<&str>) -> bool {
// Implementation distinguishes data from code arguments
}
This detection is exercised in the test suite (tests/security_regressions.rs, tests/heredoc_pack_gap.rs) to ensure that statements like bash -c "rm -rf /" are flagged for deeper inspection.
The is_argument_data Helper Function
The is_argument_data function determines whether a specific argument position contains executable code or merely data. This distinction prevents false positives while ensuring that the content following -c or -e is always marked for recursive evaluation.
Extracting Inner Scripts from Heredoc and Inline Constructs
Once a potential inline code flag is detected, src/heredoc.rs handles the actual extraction of the inner payload.
Parsing Quoted Payloads
The extract_inline_code function in src/heredoc.rs uses robust pattern matching to capture both double-quoted and single-quoted payloads, including compact forms like bash -c'rm -rf /':
// src/heredoc.rs (excerpt)
/// `bash -c "code"` → "bash"
fn extract_inline_code(command: &str) -> Option<InlineCodeSpan> {
// Regex captures quoted and unquoted inline code
}
The InlineCodeSpan Return Type
When a match is found, the function returns an InlineCodeSpan struct that points to the inner string (e.g., "rm -rf /"). This span includes metadata about the code's position within the original command, enabling accurate error reporting and denial attribution.
Recursive Evaluation of Extracted Code
After extraction, the evaluator processes the inner snippet as a standalone command.
The Evaluation Pipeline
In src/evaluator.rs, the extracted content is passed through the same evaluation pipeline as top-level commands:
// src/evaluator.rs (excerpt)
let inner = code_span.unwrap().content;
let result = evaluate(inner); // recursive evaluation
The same whitelist and blacklist tables (SAFE_PATTERNS, DESTRUCTIVE_PATTERNS) are consulted. If the inner command matches a destructive pattern (e.g., rm -rf /, git reset --hard), dcg produces a denial JSON that references the original outer command but attributes the rule to the inner payload.
Confidence Scoring for Inline Code
src/confidence.rs defines a specific confidence variant for these cases:
// src/confidence.rs (excerpt)
Self::InlineCodeSpan => "match is in inline code (bash -c, python -c, etc.)"
A match inside a -c payload receives the InlineCodeSpan confidence level, which influences the severity rating and remediation advice. This elevated confidence reflects the higher risk associated with obfuscated commands.
Blocking Dangerous Commands: Code Examples
Example 1: Blocking rm -rf Inside bash -c
The following Rust code demonstrates how dcg blocks a destructive command wrapped in a recursive shell call:
let input = r#"{"tool_name":"Bash","tool_input":{"command":"bash -c \"rm -rf /\"}}"#;
let output = dcg::run(input); // ← runs the hook
println!("{}", output); // → JSON denial with ruleId = core.filesystem:rm-rf-root
Internal execution flow:
Contextscans the outer command and identifies the-cflag.heredoc::extract_inline_codepulls outrm -rf /.evaluatormatches that against the destructive filesystem pattern.- A denial JSON is emitted via
src/output/denial.rs.
Example 2: Allowing Safe Commands
Safe commands inside inline code are permitted after evaluation:
let input = r#"{"tool_name":"Bash","tool_input":{"command":"bash -c \"echo hello\"}}"#;
let output = dcg::run(input); // allowed → no output, exit code 0
The same pipeline runs, but the extracted code (echo hello) matches only the SAFE_PATTERNS whitelist, resulting in silent approval.
Test Coverage Verification
Recursive shell handling is verified across multiple test files:
tests/security_regressions.rs: Confirmsbash -c 'echo {padding}; rm -rf /'is deniedtests/heredoc_pack_gap.rs: Validates denial of innerkubectl deletecommandstests/env_s_repro.rs: Ensures safe echo insidebash -cis not falsely flaggedtests/codex_hook_protocol.rs: Documents support for unwrappedsh -candbash -cpatterns
These tests exercise the full extraction → evaluation pipeline, guaranteeing that inner dangerous commands are caught even when hidden behind recursive shells.
Summary
- Recursive shell analysis in dcg extracts code arguments from flags like
-cand-ebefore evaluation. src/context.rsandsrc/heredoc.rscollaborate to identify and parse inline code spans usingextract_inline_code.src/evaluator.rsrecursively evaluates extracted snippets againstDESTRUCTIVE_PATTERNSandSAFE_PATTERNS.InlineCodeSpanconfidence scoring insrc/confidence.rselevates the severity of matches found inside wrapper commands.- The guard blocks obfuscated commands like
bash -c "rm -rf /"while allowing benign inline scripts.
Frequently Asked Questions
What shells and interpreters does dcg support for recursive analysis?
According to the source code in src/context.rs, dcg supports the -c and -e flags for common interpreters including bash, sh, python, and node. The is_argument_data function recognizes these flags and triggers recursive evaluation for any code arguments that follow them.
How does dcg distinguish between data arguments and code arguments?
The is_argument_data function in src/context.rs examines the command structure and flag context to determine whether an argument contains executable code. Arguments following known code flags (-c, -e, -lc) are treated as potential code and passed to extract_inline_code in src/heredoc.rs for further parsing, while other arguments are processed as static data.
Can dcg handle nested recursive calls like bash -c "bash -c 'rm -rf /'"?
Yes, the evaluator in src/evaluator.rs calls evaluate(inner) recursively on extracted code. If the inner extracted script contains another bash -c invocation, the parser will identify the new inline code span and evaluate it again, ensuring that deeply nested destructive commands are detected regardless of obfuscation depth.
What confidence level is assigned to matches inside inline code spans?
Matches found within -c or -e payloads receive the InlineCodeSpan confidence variant defined in src/confidence.rs. This specific confidence level indicates that the match occurred inside inline code (such as bash -c or python -c), resulting in higher severity ratings and more prominent remediation warnings compared to standard top-level command matches.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →