destructive_command_guard

Dicklesworthstone活跃维护

用 Rust 写的高速安全钩子,在 AI 编程代理执行前拦截危险的 shell、git 和文件系统命令,内置 50+ 安全包并支持 CI 扫描与解释模式。

A fast Rust safety hook that blocks destructive shell, git, and filesystem commands before AI coding agents run them. 50+ security packs (databases, Kubernetes, Docker, cloud, Terraform), heredoc/inline-script scanning, CI scan mode, and explain mode; works with Claude Code, Codex, Gemini, Copilot, Cursor, and more.

v0.16.0发布于 2026-11-01 09:26 UTC6,325star +31 / 7 天268fork同步于 2026-11-10 17:35 UTC

怎么上手

由 README 提炼,执行前请确认
brew install dicklesworthstone/tap/dcg
dcg install
硬件要求按 README 说明

README 未说明硬件要求;支持 Linux、macOS 和 Windows(原生 PowerShell 安装或 WSL),无 GPU 相关要求

有 Docker 镜像
下载:源码 zip·GitHub Releases(安装包)本站不镜像安装包

项目说明

由 README 自动翻译提炼

在 AI 编码代理执行前拦截破坏性 shell、git 和文件系统命令的 Rust 安全钩子

用 Rust 写的高速安全钩子,在 AI 编程代理执行前拦截危险的 shell、git 和文件系统命令,内置 50+ 安全包并支持 CI 扫描与解释模式。

核心能力

  • 拦截 git reset --hard、rm -rf 等危险命令并给出替代建议
  • 内置 50+ 安全包覆盖数据库、K8s、Docker、云和 Terraform
  • 扫描 heredoc 与内联脚本,识别 python -c 中的删除调用
  • 区分数据与执行上下文,不误拦 grep "rm -rf"
  • 提供 CI 扫描模式与 dcg explain 解释拦截原因

容易踩的坑

Homebrew 只安装 dcg 二进制,需再执行 dcg install 才会配置各代理钩子;验证安装脚本前需先用系统包管理器装好 minisign;Codex CLI 需 0.125.0+ 才支持原生钩子

内容由模型根据 README 生成,可能不完整或不准确,以官方文档为准。看原文 README

最近发版

5 个版本v0.16.0
  1. v0.16.0最新正式版2026-11-01 · 2026-11-01 09:26 UTC

    This release closes commands that ran unguarded (writes hidden by IFS splitting, by shell variables or by git rev-parse paths, redirections written against the command word, upper-case program names, ssh_config command options, OpenCode v2's shell tool, and Python, Deno, Bun and awk process calls dcg did not read) and adds Amp, Muse Code and Factory Droid integrations. It also fixes a long list of false positives reported against 0.15.3, most of them in quoted Python and Node heredocs, and makes a configuration file that fails to load block commands instead of being skipped.

    A configuration file that fails to load now blocks every shell command (#568). In 0.15.3 a user or system config.toml that did not parse, held an invalid policy value or could not be read was skipped, and dcg ran with its defaults, without that file's block rules. Now the hook denies every shell command with a reason that names the file and points to dcg doctor --strict, and dcg test exits 3 with a configuration_error. DCG_FAIL_CLOSED and DCG_UNVERIFIED_DECISION cannot open it up again. A DCG_CONFIG that names a missing file is handled the same way. A malformed repository .dcg.toml that dcg found on its own is still not fatal. After upgrading, run dcg test 'git status' once to confirm your configuration loads (5b1d5bf3).

    OpenCode and Oh My Pi users: regenerate the plugin or bridge after upgrading. OpenCode v2 calls its shell tool shell, which the plugin generated by earlier versions skipped, so every v2 shell command ran unguarded (#547). The Oh My Pi bridge treated a dcg exit without a readable verdict as an allow (#564). Both fixes live in the generated files. Run dcg install --opencode --force or dcg install --omp --force, or let dcg update do it (it does when its installer detects the agent, unless you pass --no-configure), then restart the agent (e31cc145, 450a2897).

    Claude Code users who set CLAUDE_CONFIG_DIR: run dcg install or dcg doctor --fix after upgrading (#541). Earlier versions never read that variable and put the hook in ~/.claude/settings.json, so the profile Claude Code actually used may have no dcg hook, and nothing reported it (65004aa6, 4911e6f3).

    Custom pack authors: check your packs after upgrading. A rule that can only match zero width now stops its pack from loading, safe patterns now match the original command with its environment assignments and wrappers, and packs.disabled now turns off packs loaded from custom_paths. See Behaviour changes below (11b3f00a, e0554cd0, c490820f).

    Two heredoc checks are looser than in 0.15.3, on purpose. Quoted interpreter heredocs are now judged in the interpreter's language, and a substitution in an unquoted heredoc body is judged as the command inside it. Some text that 0.15.3 refused because it could not verify it is now allowed. See Behaviour changes and Known issues below (f9e87482, e4e06296).

    Behaviour changes you may notice

    • Quoted interpreter heredocs are judged in the interpreter's language (#544, #560). 0.15.3 also read a body given to python3 -, node, ruby or perl as shell, and refused what it could not prove harmless. When dcg proves the interpreter reads the body as its program, that shell reading is gone, so a backtick or $(…) in the body is no longer a substitution. This loosens fail-closed behaviour. In a sample of the rows this change moved, 160 went from deny to allow, 9 from allow to indeterminate and 2 from deny to indeterminate; those are counts from the sample, not totals. In a wider synthetic comparison with 0.15.3, across about 40,000 heredoc forms, 847 that 0.15.3 denied are allowed here: 153 are 144 unquoted and 9 quoted bodies whose 0.15.3 rule was heredoc.shell:launcher-unverified (the unquoted ones are the next entry), 76 hold commands that are allowed when typed plainly, and 618 cannot run what they contain, because Python rejects the body or the text
  2. v0.15.32026-10-08 · 2026-10-08 06:15 UTC
    查看更新说明

    This release closes commands that ran unguarded (Claude Code's Monitor tool, wrapper and remote-shell redirects, GNU tar helper programs, archive extraction into credential or .git paths) and a long list of false positives reported against 0.15.2.

    Claude Code users: refresh the hook after upgrading. The hook matcher is now Bash|PowerShell|Monitor. Run dcg install (or dcg doctor --fix); hook self-healing does the same on its next run. Existing Bash or Bash|PowerShell dcg entries are migrated in place, and unrelated hooks keep their matchers.

    Behaviour changes you may notice

    • A temporary-path variable proof stops at an unmodelled assignment. 0.15.2 proved > $SP/out.txt safe from an earlier SP=/tmp/... even when a later assignment took its value from a command dcg does not model (N=$(python3 -c ...), N=$(basename "$BIN" | cut -c1-16)). Such redirects are now denied as core.filesystem:redirect-truncate-dynamic-path. Redirect before that assignment, or write the literal path. In a replay of 6,000 real agent commands this changed three verdicts from allow to deny; the #536 fixes below changed one from deny to allow.
    • GNU tar helper programs are judged. --checkpoint-action=exec=, --to-command, --use-compress-program/-I, --info-script/-F, --new-volume-script and --rmt-command run code. Their payloads now go through the packs; a helper dcg cannot verify is denied as core.filesystem:tar-exec-unverified.
    • rm of a path inside .git names the real risk. rm .git/index.lock is still denied under core.filesystem:rm-protected-file, but the reason now says it deletes repository state, not a credential or login-startup file, and how to clear a stale index.lock safely. A command that also removes a credential keeps the credential reason (#533).
    • git branch -d is reported as core.git:branch-delete; see Git branch rules below (#509).

    Agent integrations

    • Claude Code: Monitor scripts are judged (#529). Claude Code's Monitor tool runs its command as a POSIX shell script. The hook matcher did not include it, so those scripts never reached dcg. dcg install, the installers, dcg doctor --fix and hook self-healing now write Bash|PowerShell|Monitor.
    • Codex: exact-command review for denials (#537). A Codex denial now carries its allow-once code in permissionDecisionReason, keeping Codex's three-field denial JSON. The grant matches only the exact original request, is single-use, and cannot be consumed by a nested payload or an altered wrapper, substitution or whitespace.
    • pi: the recipe evaluates with --dialect posix (#532). Without it, pi's commands were read with zsh-only rules and some were denied falsely.
    • OpenCode: the plugin loads again (#516). OpenCode 1.3.4 and later read a default export that has an id as a plugin module and refuse it without server(). The plugin generated by 0.15.0 through 0.15.2 exported { id, setup } for OpenCode v2, so current OpenCode (1.18.33 in the report) refused the whole file at startup and ran every bash command unguarded. The default export now also carries server(), which returns the same hook map as the named DcgGuard export; v2 ignores the extra key. Verified against a real OpenCode 1.18.33 run. Regenerate the plugin (dcg install --opencode --force, or dcg update) and restart OpenCode; dcg doctor reports an old plugin as outdated.
    • Cursor: the bridge fails closed (#517). The beforeShellExecution bridge allowed every command it could not check. On Windows that was every command: cursor-agent sends a UTF-8 BOM, Windows PowerShell 5.1 decoded it with the OEM code page, the JSON parse failed, and the bridge allowed. Both bridges (PowerShell and the Unix Python one) now read stdin as bytes and drop a BOM, send dcg UTF-8, and ask dcg for an explicit allow, so an unreadable payload or a dcg that ra
  3. v0.15.22026-10-01 · 2026-10-01 13:39 UTC
    查看更新说明

    Security fix release. v0.15.1 can fail open under load. Please upgrade.

    On a busy machine the v0.15.1 hook could exit without printing a verdict for some destructive commands. Every agent reads a hook that exits silently as "allow", so those commands ran. With about sixteen hook processes running at once, 3-9% of requests for commands such as watch 'git reset' --hard (commands that only dcg's embedded-script reader can judge) were let through this way. Run one at a time, the same commands were always denied. In v0.15.2 a check that runs out of time is retried with the time the hook has left, and if it still cannot finish the answer is ask (or deny under unverified_decision = "deny"), never a silent allow.

    OpenCode and Oh My Pi users: refresh the plugin or bridge after upgrading. Both now block a command when dcg crashes, is killed or exits without a verdict. That change is in the generated plugin/bridge file, so an existing file keeps the old fail-open behaviour until it is rewritten:

    • dcg update rewrites it for you when its installer detects the agent, unless you pass --no-configure.
    • Otherwise run dcg install --opencode --force or dcg install --omp --force (add --project for a project-scoped Oh My Pi bridge).
    • DCG_BRIDGE_CRASH_DECISION=allow keeps the old fail-open behaviour for crashes. A dcg that cannot be started at all still fails open, unless DCG_UNVERIFIED_DECISION=deny is set.

    Fixed in the fail-open path

    • Silent exit under load (18be8e9). The embedded-code extractor's 50 ms budget ran out on a busy machine, its fallback found nothing, and the unread script was treated as quoted data. A timed-out extraction or AST match is now read again within the hook's deadline; a body that still cannot be read is unverified, not clean.
    • A panic in the hook let the command through (18be8e9). Release builds abort on panic, which agents treat as a non-blocking error. In hook mode a panic now writes the ask/deny answer for the request's protocol before exiting.
    • Binary-looking command text skipped the embedded-code checks (18be8e9). A NUL byte or a run of control characters could hide watch 'git reset' --hard. Such input is now denied under the incomplete-analysis rule, which can be allowlisted after review.
    • [confidence] enabled = true crashed the hook on some wrapped commands (18be8e9), and scored some matches against unrelated text (42414a6), which could downgrade a deny to a warning. A span that does not address the command now keeps the deny.
    • The OpenCode plugin and the Oh My Pi bridge allowed a command when dcg crashed or gave no verdict (8fa6aeb). See the refresh note above.
    • The hook crashed on very deeply nested input (929cce8), which agents also treat as allow. The parse-tree walks no longer recurse.

    Other changes

    • Fewer false denials for notes written through an unquoted heredoc (<<EOF). When dcg can prove the output only lands in a plain file, on the terminal or in read-only text tools, only the parts the shell runs while reading the body are checked.
    • New denials for heredoc bodies fed to something that runs them (a shell, ssh, sudo sh, docker exec … sh, at, eval), with either delimiter, and for more disguised spellings: brace-expanded command words, trap/PROMPT_COMMAND/PS*/BASH_ENV commands, escaped or assembled program names, quoted options, relative writes after cd /, and cmd.exe caret escapes when the line also holds unrelated keywords (#499, #500). A workflow that relied on one of these spellings will now be denied.
    • DCG_LOG=<filter> prints hook-mode tracing to stderr.

    Full detail: CHANGELOG.md.

    Assets

    Six archives, each holding only the dcg binary (`d

  4. v0.15.12026-09-29 · 2026-09-29 15:48 UTC
    查看更新说明

    Two fixes. One made the Oh My Pi bridge let a command through unjudged; the other made the installers skip signature checking with some distro builds of cosign.

    Oh My Pi users: refresh the bridge after upgrading. The #504 fix is in the generated dcg-guard.ts file, not only in the dcg binary, so an existing bridge keeps the old behaviour until it is rewritten.

    • dcg update rewrites your user/profile bridge for you when omp is on your PATH (it runs the new installer, which runs dcg install --omp --force). It does not if you pass --no-configure.
    • If you upgraded any other way (package manager, manual download, dcg update --no-configure), run dcg install --omp --force. Without --force the command sees the existing bridge and leaves it alone.
    • A project-scoped bridge (dcg install --omp --project) is never touched by dcg update. Run dcg install --omp --force --project in that project, or dcg doctor --fix there.
    • dcg doctor reports an old bridge as "OUTDATED OR DAMAGED".

    Restart omp afterwards so it loads the new file.

    Fixed

    • The OMP bridge judged nothing when the command's working directory did not exist (#504, 38de715). The bridge started dcg inside that directory; when it was missing, starting dcg failed with an ENOENT error naming the dcg binary, and the command ran with no verdict and no audit record, even with DCG_UNVERIFIED_DECISION=deny. The bridge now starts dcg from the nearest existing parent directory, so the same project config, allowlists and Git branch apply, and passes the real working directory along. Directory-scoped allowlist entries are not applied when that directory does not exist. If dcg still cannot start, the log names the binary and both directories, and DCG_UNVERIFIED_DECISION=deny blocks the command; without that setting the command is allowed, as before.
    • The installers skipped signature verification with cosign builds that add a version suffix (#505, 6ba4a76). install.sh and install.ps1 could not read a cosign version such as Arch's v3.1.3+dirty, treated it as older than the CVE-2026-22703 fixes, and skipped the Sigstore bundle check with a warning. Both now read the version as SemVer: build metadata is ignored, and a pre-release such as v3.0.4-rc.1 still counts as older than v3.0.4.

    Full detail: CHANGELOG.md.

    Assets

    Six archives, each holding only the dcg binary (dcg.exe on Windows): x86_64-unknown-linux-musl (static), aarch64-unknown-linux-gnu (glibc 2.28 or newer), x86_64-apple-darwin, aarch64-apple-darwin, x86_64-pc-windows-msvc, aarch64-pc-windows-msvc. Every archive and both installers have a .sha256, a minisign signature (key 69B3955C8D2E62A8, the key pinned in install.sh, install.ps1 and release/minisign.pub) and a key-based Sigstore bundle (.sigstore.json, the installers' pinned cosign key). SHA256SUMS is signed with the same minisign key. Built on self-hosted machines with the dsr release process; GitHub Actions were not used.

    V=v0.15.1; T=aarch64-apple-darwin   # or x86_64-unknown-linux-musl, etc.
    curl -fLO "https://github.com/Dicklesworthstone/destructive_command_guard/releases/download/$V/dcg-$T.tar.xz"
    curl -fLO "https://github.com/Dicklesworthstone/destructive_command_guard/releases/download/$V/dcg-$T.tar.xz.minisig"
    minisign -Vm "dcg-$T.tar.xz" -P RWSoYi6NXJWzaRs1mJmOwwXrZfPWcq6MXnQlNMLBYKzlIQTLwuVQG6uO
    
  5. v0.15.02026-09-29 · 2026-09-29 11:04 UTC
    查看更新说明

    A safety release. Most of this release fixes commands that v0.14.4 allowed and should have blocked. The minor version bump is because several of those fixes change verdicts you may see day to day; nothing was removed from the configuration or the hook protocols.

    In short:

    • A warning no longer hides a later block (#498). A rule set to warn, log or ask used to be the whole answer for the command line, so git stash drop && git reset --hard was allowed. dcg now keeps looking past a non-blocking match and reports the strictest result.
    • Credential and startup files are protected wherever the home directory lives (#502). Synology (/volume1/homes/<u>, /var/services/homes/<u>), /var/home, /usr/home, /export/home, a container's own $HOME, macOS firmlinks, WSL and Cygwin mounts of a Windows profile, and many spellings of those paths (quotes, ./.., globs, brace lists, ANSI-C escapes) are now recognised. Review rounds on this fix found and closed a long list of further bypass classes, listed under Security below.
    • The PowerShell profile check stops warning "Hook missing" when the hook is installed (#503). Running dcg install rewrites an old profile block in place, and paths containing '' (for example O''Brien) are read correctly.
    • Commands handed to another program to run are judged. watch '…', su -c '…', parallel ::: '…', env -S'…', flock -c, nix-shell --run, ssh host …, docker exec and similar runners and wrappers pass their command through dcg the way sh -c '…' always did.
    • A redirect or option no longer hides sh -c's script. sh 2>/dev/null -c '…', bash -c -e '…', sh <<<x -c '…', powershell 2>&1 -EncodedCommand … and similar spellings are judged.
    • Pathological input fails closed quickly instead of stalling the hook. Very long pipelines, long runs of unclosed brackets, deep glob or $var paths and exponential glob patterns are bounded.

    Behaviour changes you may notice:

    • A pipeline of more than 1,024 stages is denied without being parsed (heredoc.shell:analysis-bounds).
    • A write target whose path has more than 64 components that the shell can rewrite (/*/*/…, /$x/$x/…) is denied as a possible credential-file write.
    • ssh host git commit -m 'rm -rf x' is now denied. ssh joins its arguments and the remote shell re-parses them, so the remote side really runs rm -rf x. Quote the whole remote command (ssh host "git commit -m 'rm -rf x'") if you mean the message.
    • An unquoted heredoc body that contains a command-string runner, such as cat > notes.md <<EOF with watch 'git reset --hard' inside, can be denied, as sh -c '…' in the same place already was. Quote the delimiter (<<'EOF') for text that is only data.
    • Rules that were warn-only in your [policy] no longer let later deny rules on the same line through. A line that only matches a warn rule is still a warn.

    The complete list of fixes, with the exact spellings each review round found, is in CHANGELOG.md.

    Assets

    Six archives, each holding only the dcg binary (dcg.exe on Windows): x86_64-unknown-linux-musl (static), aarch64-unknown-linux-gnu (glibc 2.28 or newer), x86_64-apple-darwin, aarch64-apple-darwin, x86_64-pc-windows-msvc, aarch64-pc-windows-msvc. Every archive and both installers have a .sha256, a minisign signature (key 69B3955C8D2E62A8, the key pinned in install.sh, install.ps1 and release/minisign.pub) and a key-based Sigstore bundle (.sigstore.json, the installers' pinned cosign key). SHA256SUMS is signed with the same minisign key.

    V=v0.15.0; T=aarch64-apple-darwin   # or x86_64-unknown-linux-musl, etc.
    curl -fLO "https://github.com/Dicklesworthstone/destructive_command_guard/releases/download/$V/dcg-$T.tar.xz"
    curl -fLO "https://github.c
    

用户点评

登录用户的真实使用记录,可按版本和硬件筛选
共 0 条

    还没有人点评。第一个说出真实体验的人最有价值。

    README 原文

    未翻译

    dcg (Destructive Command Guard)

    Coverage License: custom

    A high-performance hook for AI coding agents that blocks destructive commands before they execute, protecting your work from accidental deletion across Claude Code, Codex CLI, Gemini CLI, Copilot CLI, VS Code Copilot Chat, Cursor, Hermes Agent, Grok (xAI), Posit Assistant, Oh My Pi, Factory Droid's Execute tool, and related tools.

    Supported: Claude Code, Codex CLI 0.125.0+, Gemini CLI, GitHub Copilot CLI, VS Code Copilot Chat, Cursor IDE, Hermes Agent, Posit Assistant (Positron/RStudio extension, standalone server, and pa terminal client), Grok (xAI) (native ~/.grok/hooks/ plus Claude compatibility layer), Antigravity CLI (agy) (native ~/.gemini/config/hooks.json via dcg install --agy), OpenCode (native tool.execute.before plugin via dcg install --opencode — see docs/opencode-integration.md), Oh My Pi (omp) (native tool_call extension via dcg install --omp), Crush (native hooks.PreToolUse entry in crush.json via dcg install --crush — see docs/crush-integration.md), Reasonix (native hooks.PreToolUse entry in settings.json via dcg install --reasonix — see docs/reasonix-integration.md), Muse Code (native hooks.PreToolUse matcher group in ~/.config/muse/settings.json via dcg install --muse — see docs/muse-integration.md), Amp (native tool.call plugin via dcg install --amp — see docs/amp-integration.md), Pi (via extension recipe), Aider (limited—git hooks only), Continue (detection only)

    Factory Droid's Execute tool is supported through dcg install --droid; see coverage and Script-mode limitations.

    Install a verified release on Linux, macOS, or WSL. The copyable recipe authenticates the pinned installer before running it and supports normal, easy-mode, and system-wide installation.

    Install a verified release with PowerShell. This recipe checks the installer's pinned SHA256 and Minisign signature before execution, then requires Minisign verification of the native binary too.


    TL;DR

    The Problem: AI coding agents (Claude, Codex, Gemini, Copilot, etc.) occasionally run catastrophic commands like git reset --hard, rm -rf ./src, or DROP TABLE users—destroying hours of uncommitted work in seconds.

    The Solution: dcg is a high-performance hook that intercepts destructive commands before they execute, blocking them with clear explanations and safer alternatives.

    Why Use dcg?

    FeatureWhat It Does
    Zero-Config ProtectionBlocks dangerous git/filesystem commands out of the box
    50+ Security PacksDatabases, Kubernetes, Docker, AWS/GCP/Azure, Terraform, and more
    Sub-Millisecond LatencySIMD-accelerated filtering—you won't notice it's there
    Heredoc/Inline Script ScanningCatches python -c "os.remove(...)" and embedded shell scripts
    Smart Context DetectionWon't block grep "rm -rf" (data) but will block rm -rf / (execution)
    Rich Terminal OutputHuman-readable denial panels, rule context, and suggestions on stderr
    Agent-Safe StreamsMachine-readable hook output stays on stdout while rich UI stays on stderr
    Native Codex SupportCodex CLI 0.125.0+ receives a minimal stdout JSON denial that current clients enforce reliably
    Graceful DegradationPlain output for CI, pipes, dumb terminals, and no-color environments
    Scan Mode for CIPre-commit hooks and CI integration to catch dangerous commands in code review
    Bounded Failure PolicyAnalysis timeouts become explicit review/block outcomes; malformed raw hook envelopes remain auditable and configurable
    Explain Modedcg explain "command" shows exactly why something is blocked

    Quick Example

    # AI agent tries to run:
    $ git reset --hard HEAD~5
    
    # dcg intercepts and blocks:
    ════════════════════════════════════════════════════════════════
    BLOCKED  dcg
    ────────────────────────────────────────────────────────────────
    Reason:  git reset --hard destroys uncommitted changes
    
    Command: git reset --hard HEAD~5
    
    Tip: Consider using 'git stash' first to save your changes.
    ════════════════════════════════════════════════════════════════
    

    Enable More Protection

    # ~/.config/dcg/config.toml
    [packs]
    enabled = [
        "database.postgresql",    # Blocks DROP TABLE, TRUNCATE
        "kubernetes.kubectl",     # Blocks kubectl delete namespace
        "cloud.aws",              # Blocks aws ec2 terminate-instances
        "containers.docker",      # Blocks docker system prune
    ]
    

    Agent-Specific Profiles

    dcg automatically detects which AI coding agent is invoking it and can apply agent-specific configuration. The trust_level field is an advisory label recorded in JSON output and logs — it does not directly change rule evaluation. Behavioral differences come from the other profile fields:

    OptionEffect
    disabled_packsRemoves rule packs from evaluation
    extra_packsAdds rule packs to evaluation
    additional_allowlistAdds command patterns that bypass deny rules
    disabled_allowlistWhen true, ignores all allowlist entries
    # Trust Claude Code more — wider allowlist, fewer packs
    [agents.claude-code]
    trust_level = "high"
    additional_allowlist = ["npm run build", "cargo test"]
    disabled_packs = ["kubernetes"]
    
    # Oh My Pi has its own canonical profile (distinct from legacy Pi)
    [agents.omp]
    trust_level = "medium"
    extra_packs = ["strict_git"]
    
    # Restrict unknown agents — extra rules, no allowlist bypass
    [agents.unknown]
    trust_level = "low"
    extra_packs = ["strict_git", "database"]  # real pack / category IDs (see `dcg packs`)
    disabled_allowlist = true
    

    extra_packs/disabled_packs take the same pack and category IDs as [packs] enabled/disabled — a category ID like "database" expands to every database.* sub-pack. Use IDs listed by dcg packs or in docs/packs/README.md; "paranoid" is a graduation mode, not a pack, so enable the real strict_git pack for stricter git rules.

    See docs/agents.md for full documentation on supported agents, trust levels, and configuration options.

    Codex Support

    dcg now treats Codex CLI as a first-class hook target, not just a Claude-shaped compatibility path. The installer configures Codex CLI 0.125.0+ automatically when it detects codex on PATH or an existing ~/.codex/ directory.

    Codex behaviordcg handling
    Hook configMerges a PreToolUse Bash hook into ~/.codex/hooks.json
    Denied commandExits 0 with a minimal hookSpecificOutput denial on stdout; human warning stays on stderr
    Allowed commandExits 0 with empty stdout and stderr
    Existing hooksPreserves coexisting hooks, keeps dcg first for Bash, and refuses to overwrite malformed JSON
    ValidationCovered by subprocess protocol tests plus an opt-in real Codex E2E harness

    Codex's hook input is intentionally close to Claude Code's, but Codex rejects unknown fields in hook output. dcg detects Codex payloads from the non-empty turn_id field and emits only Codex's documented denial fields so a blocked command is reported as blocked rather than as a failed hook. See docs/codex-integration.md for protocol details, manual probes, and troubleshooting.


    Origins & Authors

    This project began as a Python script by Jeffrey Emanuel, who recognized that AI coding agents, while incredibly useful, occasionally run catastrophic commands that destroy hours of uncommitted work. The original implementation was a simple but effective hook that intercepted dangerous git and filesystem commands before execution.

    • Jeffrey Emanuel - Original concept and Python implementation (source); substantially expanded the Rust version with the modular pack system (50+ security packs), heredoc/inline-script scanning, the three-tier architecture, context classification, allowlists, scan mode, and the dual regex engine
    • Darin Gordon - Initial Rust port with performance optimizations

    The initial Rust port by Darin maintained pattern compatibility with the original Python implementation while adding sub-millisecond execution through SIMD-accelerated filtering and lazy-compiled regex patterns. Jeffrey subsequently expanded the Rust codebase dramatically to add the features described above.

    Escape Hatch / Bypass

    If dcg is blocking something you genuinely need to run:

    MethodScopeHow
    Env var bypassSingle commandDCG_BYPASS=1 <command>
    Allow-once codeSingle commandCopy the short code from the block message, run dcg allow-once <code>
    Permanent allowlistRule or commanddcg allowlist add core.git:reset-hard -r "reason"
    Remove the hookAll commandsDelete or comment out the dcg entry in ~/.claude/settings.json (or equivalent for your agent)

    DCG_BYPASS=1 disables all protection for that invocation. Use it sparingly and prefer allowlists for recurring needs.

    Modular Pack System

    dcg uses a modular "pack" system to organize destructive command patterns by category. Packs can be enabled or disabled in the configuration file.

    Category IDs expand to their sub-packs. Listing a bare category in enabled turns on every pack under it: enabled = ["database"] activates database.postgresql, database.mysql, and the rest of that category. You can still drop a single sub-pack with disabled = ["database.redis"]. The same expansion applies to agent-profile extra_packs / disabled_packs. Always use real pack or category IDs from dcg packs / docs/packs/README.md — a name like "paranoid" is a graduation mode, not a pack.

    • Full pack ID index: docs/packs/README.md
    • Canonical descriptions + pattern counts: dcg packs --verbose

    Enabled by default (no config file)

    With no config file present, dcg enables only the packs that guard against the most catastrophic, unrecoverable mistakes:

    • core.filesystem - Dangerous recursive rm operations and equivalent filesystem destruction outside literal temp subdirectories (always enabled; cannot be removed from evaluation)
    • core.git - Destructive git commands that lose uncommitted work, rewrite history, or destroy stashes (always enabled; cannot be removed from evaluation)
    • system.disk - mkfs, dd-to-device, fdisk, parted, mdadm, lvm removal, wipefs (on by default; opt out with disabled = ["system.disk"])

    "Cannot be removed" is not the same as "cannot be relaxed." A core.* pack always evaluates, so disabled = ["core.filesystem"] is ignored — but what dcg does with a match is policy, and policy is yours.

    Relaxing a critical rule takes a per-rule entry. A broad warn or log — whether written as [policy.packs] or as [policy] default_mode — is silently raised back to deny for any rule whose severity is critical, and dcg does not report that it ignored the setting. Most of what core.filesystem and core.git exist to stop is exactly that severity, so the broad form alone will not do what it looks like it does:

    # Relaxes only the high/medium rules. `rm -rf ~/work` still hard-denies,
    # because core.filesystem:rm-rf-root-home is critical.
    [policy.packs]
    "core.filesystem" = "warn"
    
    # Relaxes that one critical rule. This is the form that actually works.
    [policy.rules]
    "core.filesystem:rm-rf-root-home" = "warn"
    

    warn lets the command run and records the decision; log does the same silently; ask requests operator review where the hook protocol supports it. Use dcg explain --format json '<command>' and read mode to confirm which mode a rule actually resolved to before relying on it. See Configuration for the constraint and Graduated Response for the severity ladder.

    On native Windows, two additional packs are on by default so a fresh install blocks catastrophic native-Windows operations with no config. The hook also activates these two packs on Linux/macOS for a request identified as Cmd or PowerShell by its tool label or recognized Windows command syntax:

    • windows.filesystem - cmd del /s, rd /s, format <drive>: and PowerShell Remove-Item -Recurse (with or without -Force; aliases included), Clear-Content, Clear-RecycleBin (opt out with disabled = ["windows.filesystem"] or ["windows"])
    • windows.system - vssadmin delete shadows / wmic shadowcopy delete (Volume Shadow Copy destruction), diskpart, Format-Volume, Clear-Disk, Remove-Partition, cipher /w, bcdedit /delete (opt out with disabled = ["windows.system"] or ["windows"])

    For example, a powershell hook payload or a Bash-labeled rmdir /s /q d:\ request activates these protections even on Linux/macOS. Ordinary POSIX requests keep the three base defaults above; a generic tool label or a Windows-looking string alone does not establish the executing shell. Explicit pack exclusions still apply to this automatic activation. See Windows protection for shell metadata and quoting guidance.

    The broader windows.misc (reg delete, net user /delete, wsl --unregister, robocopy /MIR) and windows.powershell (registry/provider deletes, Remove-LocalUser, Disable-ComputerRestore, Remove-VM) packs are opt-in on every platform. To select Windows protection explicitly (e.g. to scan committed .ps1/.cmd scripts in CI), use [packs] enabled = ["windows"] or name the required sub-packs.

    Every other pack — including database.postgresql and containers.docker — is opt-in and is not active until a config file enables it. Running dcg init writes a starter ~/.config/dcg/config.toml whose [packs] enabled list turns on database.postgresql and containers.docker as common examples, but that is a generated starter config, not the no-config default. Enable any pack below by adding it to [packs] enabled — see Enable More Protection.

    Storage Packs

    • storage.s3 - Protects against destructive S3 operations like bucket removal, recursive deletes, and sync --delete.
    • storage.gcs - Protects against destructive GCS operations like bucket removal, object deletion, and recursive deletes.
    • storage.minio - Protects against destructive MinIO Client (mc) operations like bucket removal, object deletion, and admin operations.
    • storage.azure_blob - Protects against destructive Azure Blob Storage operations like container deletion, blob deletion, and azcopy remove.

    Remote Packs

    • remote.rsync - Protects against destructive rsync operations like --delete and its variants.
    • remote.scp - Protects against destructive SCP operations like overwrites to system paths.
    • remote.ssh - Protects against destructive SSH operations like remote command execution and key management.

    Database Packs

    • database.postgresql - Protects against destructive PostgreSQL operations like DROP DATABASE, TRUNCATE, and dropdb.
    • database.mysql - MySQL/MariaDB guard.
    • database.mongodb - Protects against destructive MongoDB operations like dropDatabase, dropCollection, and remove without criteria.
    • database.redis - Protects against destructive Redis operations like FLUSHALL, FLUSHDB, and mass key deletion.
    • database.sqlite - Protects against destructive SQLite operations like DROP TABLE, DELETE without WHERE, and accidental data loss.
    • database.snowflake - Protects modern snow sql inline queries, files, stdin, nested sources, destructive data operations, pipelines, warehouses, and account privileges.
    • database.supabase - Protects against destructive Supabase CLI operations including database resets, migration rollbacks, function/secret/storage deletion, project removal, and infrastructure changes.
    • database.databricks - Protects against destructive Databricks CLI operations like account workspace deletion, bundle destroy, recursive workspace/fs deletion, permanent cluster deletion, secret-scope removal, and arbitrary REST DELETE calls.
    • database.bigquery - Protects the bq CLI and GoogleSQL against dataset drops (DROP SCHEMA), table overwrites, unfiltered DML (WHERE TRUE is GoogleSQL's full-table idiom), and settings that shorten the time-travel recovery window.

    Container Packs

    • containers.docker - Protects against destructive Docker operations like system prune, volume prune, and force removal.
    • containers.compose - Protects against destructive Docker Compose operations like down -v which removes volumes.
    • containers.podman - Protects against destructive Podman operations like system prune, volume prune, and force removal.

    Kubernetes Packs

    • kubernetes.kubectl - Protects against destructive kubectl operations like delete namespace, drain, and mass deletion.
    • kubernetes.helm - Protects against destructive Helm operations like uninstall and rollback without dry-run.
    • kubernetes.kustomize - Protects against destructive Kustomize operations when combined with kubectl delete or applied without review.

    Cloud Provider Packs

    • cloud.aws - Protects against destructive AWS CLI operations like terminate-instances, delete-db-instance, and s3 rm --recursive.
    • cloud.azure - Protects against destructive Azure CLI operations like vm delete, storage account delete, and resource group delete.
    • cloud.gcp - Protects against destructive gcloud operations like instances delete, sql instances delete, and gsutil rm -r.

    CDN Packs

    • cdn.cloudflare_workers - Protects against destructive Cloudflare Workers, KV, R2, and D1 operations via the Wrangler CLI.
    • cdn.cloudfront - Protects against destructive AWS CloudFront operations like deleting distributions, cache policies, and functions.
    • cdn.fastly - Protects against destructive Fastly CLI operations like service, domain, backend, and VCL deletion.

    API Gateway Packs

    • apigateway.apigee - Protects against destructive Google Apigee CLI and apigeecli operations.
    • apigateway.aws - Protects against destructive AWS API Gateway CLI operations for both REST APIs and HTTP APIs.
    • apigateway.kong - Protects against destructive Kong Gateway CLI, deck CLI, and Admin API operations.

    Infrastructure Packs

    • infrastructure.ansible - Protects against destructive Ansible operations like dangerous shell commands and unchecked playbook runs.
    • infrastructure.atmos - Protects against destructive Atmos operations like terraform deploy (auto-approve), clean, destroy, state rm/taint, and helmfile destroy.
    • infrastructure.pulumi - Protects against destructive Pulumi operations like destroy and up with -y (auto-approve).
    • infrastructure.terraform - Protects against destructive Terraform operations like destroy, taint, and apply with -auto-approve.

    System Packs

    • system.disk - Protects against destructive disk operations including dd to devices, mkfs, partition table modifications (fdisk/parted), RAID management (mdadm), btrfs filesystem operations, device-mapper (dmsetup), network block devices (nbd-client), and LVM commands (pvremove, vgremove, lvremove, lvreduce, pvmove).
    • system.permissions - Protects against dangerous permission changes like chmod 777, recursive chmod/chown on system directories.
    • system.services - Protects against dangerous service operations like stopping critical services and modifying init configuration.

    When system.permissions is enabled, POSIX chmod, chown, and chgrp checks use the complete invocation: recursive options can occur after targets or in flag bundles, and option values such as --reference="chmod 777 notes" remain filename data. The ordinary relative-file chmod exemption requires every target to qualify and no recursive option. Thus chmod 755 notes -vR /etc and chmod 777 notes /etc/shadow require review. Independent rule matches stay separate: granting chmod-777 does not also grant chmod-recursive-root, and granting chmod-setuid does not exempt a simultaneous chmod-setgid match. Unsupported or dynamic arguments retain conservative pattern checks.

    CI/CD Packs

    • cicd.circleci - Protects against destructive CircleCI operations like deleting contexts, removing secrets, deleting orbs/namespaces, or removing pipelines.
    • cicd.github_actions - Protects against destructive GitHub Actions operations like deleting secrets/variables or using gh api DELETE against /actions endpoints.
    • cicd.gitlab_ci - Protects against destructive GitLab CI/CD operations like deleting variables, removing artifacts, and unregistering runners.
    • cicd.jenkins - Protects against destructive Jenkins CLI/API operations like deleting jobs, nodes, credentials, or build history.

    Secrets Management Packs

    • secrets.aws_secrets - Protects against destructive AWS Secrets Manager and SSM Parameter Store operations like delete-secret and delete-parameter.
    • secret_disclosure - Exact opt-in protection against secret-manager commands that expose credential values through agent-visible output or agent-chosen files; injection commands such as infisical run, op run, and doppler run remain allowed. It is intentionally outside the secrets.* category so existing enabled = ["secrets"] configurations do not change policy on upgrade.
    • secrets.doppler - Protects against destructive Doppler CLI operations like deleting secrets, configs, environments, or projects.
    • secrets.infisical - Protects against deleting Infisical secrets, folders, and dynamic-secret leases, plus resetting local Infisical configuration.
    • secrets.onepassword - Protects against destructive 1Password CLI operations like deleting items, documents, users, groups, and vaults.
    • secrets.vault - Protects against destructive Vault CLI operations like deleting secrets, disabling auth/secret engines, revoking leases/tokens, and deleting policies.

    Provider packs preserve dcg's default destructive-operation scope: read commands remain allowed. Teams that also treat transcript disclosure as destructive can enable the separate policy explicitly:

    [packs]
    enabled = ["secrets.infisical", "secret_disclosure"]
    

    With secret_disclosure enabled, value-emitting reads such as infisical secrets get, infisical export, op read, doppler secrets download, vault kv get, aws secretsmanager get-secret-value, aws secretsmanager batch-get-secret-value, and decrypted SSM reads are blocked. Metadata inspection, CLI help, and direct process injection remain available. The opt-in careful_company_running_windows preset also includes both new packs as deliberate members of its pinned secret-store policy.

    Platform Packs

    • platform.azure_devops - Protects against destructive azure-devops Azure CLI extension operations across az devops, az repos, az pipelines and az boards: deleting team projects, repositories, refs, branch policies, pipelines, variable groups, wikis, teams, service connections and work items, removing users and group memberships, resetting permission ACLs, and issuing arbitrary state-changing az devops invoke REST calls. az artifacts exposes no destructive command and carries no rule. Read-only verbs and ordinary development flow are untouched.
    • platform.github - Protects against destructive GitHub CLI operations like changing repository visibility or deleting repositories, gists, releases, or SSH keys.
    • platform.gitlab - Protects against destructive GitLab platform operations like deleting projects, releases, protected branches, and webhooks.
    • platform.kamal - Protects against destructive Kamal 2.x operations that tear down the stack (kamal remove), delete accessory data directories (kamal accessory remove), drop proxy routing, take the app offline, or prune the images that kamal rollback relies on.
    • platform.modal - Protects against destructive Modal serverless platform operations like recursive volume removal, app stops with --force, and secret deletion.
    • platform.railway - Protects against destructive Railway CLI and Public API operations that can delete projects, environments, services, functions, volumes, variables, or deployments.

    DNS Packs

    • dns.cloudflare - Protects against destructive Cloudflare DNS operations like record deletion, zone deletion, and targeted Terraform destroy.
    • dns.generic - Protects against destructive or risky DNS tooling usage (nsupdate deletes, zone transfers).
    • dns.route53 - Protects against destructive AWS Route53 DNS operations like hosted zone deletion and record set DELETE changes.

    Email Packs

    • email.mailgun - Protects against destructive Mailgun API operations like domain deletion, route deletion, and mailing list removal.
    • email.postmark - Protects against destructive Postmark API operations like server deletion, template deletion, and sender signature removal.
    • email.sendgrid - Protects against destructive SendGrid API operations like template deletion, API key deletion, and domain authentication removal.
    • email.ses - Protects against destructive AWS Simple Email Service operations like identity deletion, template deletion, and configuration set removal.

    Feature Flag Packs

    • featureflags.flipt - Protects against destructive Flipt CLI and API operations.
    • featureflags.launchdarkly - Protects against destructive LaunchDarkly CLI and API operations.
    • featureflags.split - Protects against destructive Split.io CLI and API operations.
    • featureflags.unleash - Protects against destructive Unleash CLI and API operations.

    Load Balancer Packs

    • loadbalancer.elb - Protects against destructive AWS Elastic Load Balancing (ELB/ALB/NLB) operations like deleting load balancers, target groups, or deregistering targets from live traffic.
    • loadbalancer.haproxy - Protects against destructive HAProxy load balancer operations like stopping the service or disabling backends via runtime API.
    • loadbalancer.nginx - Protects against destructive nginx load balancer operations like stopping the service or deleting config files.
    • loadbalancer.traefik - Protects against destructive Traefik load balancer operations like stopping containers, deleting config, or API deletions.

    Messaging Packs

    • messaging.kafka - Protects against destructive Kafka CLI operations like deleting topics, removing consumer groups, resetting offsets, and deleting records.
    • messaging.nats - Protects against destructive NATS/JetStream operations like deleting streams, consumers, key-value entries, objects, and accounts.
    • messaging.rabbitmq - Protects against destructive RabbitMQ operations like deleting queues/exchanges, purging queues, deleting vhosts, and resetting cluster state.
    • messaging.sqs_sns - Protects against destructive AWS SQS and SNS operations like deleting queues, purging messages, deleting topics, and removing subscriptions.

    Monitoring Packs

    • monitoring.datadog - Protects against destructive Datadog CLI/API operations like deleting monitors and dashboards.
    • monitoring.newrelic - Protects against destructive New Relic CLI/API operations like deleting entities or alerting resources.
    • monitoring.pagerduty - Protects against destructive PagerDuty CLI/API operations like deleting services and schedules (which can break incident routing).
    • monitoring.prometheus - Protects against destructive Prometheus/Grafana operations like deleting time series data or dashboards/datasources.
    • monitoring.splunk - Protects against destructive Splunk CLI/API operations like index removal and REST API DELETE calls.

    Payment Packs

    • payment.braintree - Protects against destructive Braintree/PayPal payment operations like deleting customers or cancelling subscriptions via API/SDK calls.
    • payment.square - Protects against destructive Square CLI/API operations like deleting catalog objects or customers (which can break payment flows).
    • payment.stripe - Protects against destructive Stripe CLI/API operations like deleting webhook endpoints and customers, or rotating API keys without coordination.

    Search Engine Packs

    • search.algolia - Protects against destructive Algolia operations like deleting indices, clearing objects, removing rules/synonyms, and deleting API keys.
    • search.elasticsearch - Protects against destructive Elasticsearch REST API operations like index deletion, delete-by-query, index close, and cluster setting changes.
    • search.meilisearch - Protects against destructive Meilisearch REST API operations like index deletion, document deletion, delete-batch, and API key removal.
    • search.opensearch - Protects against destructive OpenSearch REST API operations and AWS CLI domain deletions.

    Backup Packs

    • backup.borg - Protects against destructive borg operations like delete, prune, compact, and recreate.
    • backup.rclone - Protects against destructive rclone operations like sync, delete, purge, dedupe, and move.
    • backup.restic - Protects against destructive restic operations like forgetting snapshots, pruning data, removing keys, and cache cleanup.
    • backup.velero - Protects against destructive velero operations like deleting backups, schedules, and locations.

    Windows Packs

    Native-Windows (cmd.exe + PowerShell) destructive-command protection. windows.filesystem and windows.system are default-on on native Windows and activate for recognized Cmd/PowerShell hook payloads on other hosts; windows.misc and windows.powershell are opt-in everywhere. Ordinary POSIX requests do not activate the Windows packs. All patterns are case-insensitive.

    • windows.filesystem - Recursive/forced filesystem destruction: cmd del /s, rd /s/rmdir /s, format <drive>:; PowerShell Remove-Item -Recurse (with or without -Force; -Force only broadens coverage to hidden/read-only items; aliases rm/del/rd/ri included), Clear-Content, Clear-RecycleBin. Whitelists PowerShell -WhatIf previews only on cmdlets/aliases that honor it, plus temp-dir deletes.
    • windows.system - Catastrophic disk/system operations: vssadmin delete shadows and wmic shadowcopy delete (Volume Shadow Copy destruction — a ransomware hallmark), diskpart, Format-Volume, Clear-Disk, Remove-Partition, Initialize-Disk/Reset-PhysicalDisk, cipher /w, bcdedit /delete.
    • windows.misc - Registry/account/service/WSL/copy destruction: reg delete, net user|localgroup /delete, sc delete, schtasks /delete, wsl --unregister (destroys a WSL distro), robocopy /MIR (mirror-delete).
    • windows.powershell - Destructive PowerShell cmdlets: registry/provider deletes (Remove-Item HKLM:\, Remove-ItemProperty, Remove-PSDrive), Remove-LocalUser/Remove-LocalGroup, Unregister-ScheduledTask, Disable-ComputerRestore, forced Stop-Computer/Restart-Computer, Remove-VM/Remove-AppxPackage.

    Careful Company (Windows) Preset

    Every other pack answers "will this command destroy something?". This preset also answers "is this command sending our data somewhere, or switching off the controls that watch it?" — the question that matters once an agent runs on a Windows workstation with tool-permission prompts disabled. The same policy is applied to statically inspectable commands submitted through either PowerShell or cmd.exe, including Cmd's caret escaping, control prefixes, nested cmd /c / call, and command chaining. It is opt-in on every platform, and one line enables the whole posture:

    [packs]
    enabled = ["careful_company_running_windows"]
    

    With this exact preset ID enabled, the hook evaluation deadline defaults to 3000 ms instead of the ordinary 1000 ms unless config or DCG_HOOK_TIMEOUT_MS explicitly supplies another value. This changes only the time available to reach the same fail-closed decision. Inspect the effective value and source with dcg config --format json.

    That turns on the six sub-packs below and the existing destruction coverage the same posture needs: the current windows.*, database.* (including Snowflake), storage.*, remote.*, backup.*, secrets.*, and cloud.* packs. Membership is an explicit pinned list rather than a prefix rule, so a future pack added to one of those reused categories does not silently join this security posture — it has to be added deliberately. (A future careful_company_running_windows.* sub-pack does join, through ordinary category expansion.) Any member can be dropped individually with disabled = ["remote.rsync"].

    • careful_company_running_windows.email - Sending mail from the workstation: Send-MailMessage, System.Net.Mail.SmtpClient, Outlook COM automation, Microsoft Graph sendMail, transactional mail-API send endpoints, aws ses send-email, SMTP CLI tools (blat, swaks, msmtp, git send-email, curl --mail-rcpt), and persistent forwarding rules (New-InboxRule -ForwardTo, Set-Mailbox -ForwardingSmtpAddress).
    • careful_company_running_windows.chat - Chat and webhook destinations: Slack incoming webhooks and Web API writes, Teams connectors and Power Automate triggers, Discord, Telegram, Google Chat, Twilio, Zapier/IFTTT, PagerDuty, and request catchers such as webhook.site and interact.sh.
    • careful_company_running_windows.upload - HTTP file-upload primitives (-InFile, -Form, curl -T, -F field=@file, --data-binary @file, --post-file, WebClient.UploadFile, GetRequestStream, MultipartFormDataContent, BITS uploads), file-drop/paste services, gh gist create, certreq -Post, and request bodies built from file or clipboard contents.
    • careful_company_running_windows.transfer - Outbound file transfer: scp/sftp/WinSCP to a remote destination, scripted FTP, tftp put, rsync and rclone to a remote, cloud-storage uploads (aws s3 cp local→s3://, az storage blob upload, azcopy, gsutil cp→gs://, b2/s3cmd/mc/wrangler r2), peer-to-peer senders, WebDAV mounts, and copy LOLBins (esentutl /y, print /D:).
    • careful_company_running_windows.tunnel - Channels that expose the workstation or bypass inspection: ngrok, cloudflared, devtunnel/code tunnel, localtunnel, tailscale funnel, ssh -R/-D, chisel/frp, ncat/netcat/socat, PowerShell raw sockets, netsh interface portproxy, DNS tunnels, and out-of-band callback domains.
    • careful_company_running_windows.guardrails - Turning off the safety net: Defender (Set-MpPreference -Disable*/-ExclusionPath), the firewall, EDR and event-log services, BitLocker, Set-ExecutionPolicy Bypass, script-block logging, event-log clearing, dcg's own DCG_BYPASS, dcg uninstall, allowlist grants (dcg allowlist add, dcg allow-once), runtime config overrides (DCG_DISABLE/DCG_PACKS/DCG_CONFIG), and the agent's hook config, plus unreviewed remote code (iwr | iex, powershell -EncodedCommand, mshta/regsvr32 remote payloads). Diagnosis stays open: dcg explain, dcg allowlist list, and dcg allowlist validate are whitelisted.

    False positives are the design constraint. Rules require positive evidence of egress — an attached file, a known egress host, a mutating method — so ordinary GETs, -OutFile/curl -o downloads, and every package-manager install pass through untouched (fetching from a known file-drop or paste host is the one exception, and it warns rather than blocks). Requests whose destinations are all internal (loopback, RFC1918, *.internal/*.corp/*.local, bare intranet hostnames) are whitelisted, with the cloud metadata endpoints (169.254.169.254, metadata.google.internal) deliberately excluded from that allowance. Searching for a token (Select-String "Send-MailMessage" *.ps1) and dcg explain "<command>" are never blocked. git push to a named remote is untouched, and SMB copies to a corporate share are out of scope.

    Genuinely ambiguous cases warn instead of blocking (Medium severity: the command runs and the decision is recorded) — a POST with an inline body is a GraphQL query as often as an exfiltration. Promote them when your posture calls for it:

    [policy.rules]
    "careful_company_running_windows.upload:cli-http-mutating-request" = "deny"
    "careful_company_running_windows.upload:ps-http-mutating-request" = "deny"
    

    This preset carries one built-in trust boundary you should know about. While any careful_company_running_windows.* pack is enabled, a command whose executable is hfdt (optionally path-qualified) is allowed without evaluating any pack at all — not just this preset's. hfdt rm -rf /data is permitted with the preset on and denied with it off. The exemption is structural rather than textual: it requires hfdt to be the actual executable of the whole command and refuses chains, redirection, and process substitution, so hfdt …; Invoke-RestMethod … and hfdt $(…) are evaluated normally. If you do not run that tool, this never fires; if you do, treat it as an explicit decision to trust it completely. See docs/careful-company-windows.md.

    Other first-party internal tooling gets no such exemption and should be allowlisted, which keeps the grant narrow and recorded:

    dcg allowlist add-command "mytool publish --to https://artifacts.corp.internal" \
      -r "First-party internal publisher" --user
    

    Other Packs

    • package_managers - Protects against dangerous package manager operations like publishing packages and removing critical system packages.
    • strict_git - Stricter git protections: blocks all force pushes, rebases, and history rewriting operations.

    Enable packs in ~/.config/dcg/config.toml:

    [packs]
    enabled = [
        # Databases
        "database.postgresql",
        "database.redis",
        "database.supabase",
    
        # Containers and orchestration
        "containers.docker",
        "kubernetes",  # Enables all kubernetes sub-packs
    
        # Cloud providers
        "cloud.aws",
        "cloud.gcp",
    
        # Secrets management
        "secrets.aws_secrets",
        "secrets.vault",
    
        # CI/CD
        "cicd.jenkins",
        "cicd.gitlab_ci",
    
        # Messaging
        "messaging.kafka",
        "messaging.sqs_sns",
    
        # Search engines
        "search.elasticsearch",
    
        # Backup
        "backup.restic",
    
        # Platform
        "platform.github",
        "platform.railway",
    
        # Monitoring
        "monitoring.splunk",
    ]
    

    Custom Packs

    Create your own organization-specific security packs using YAML files. Custom packs let you define patterns for internal tools, deployment scripts, and proprietary systems without modifying dcg.

    [packs]
    custom_paths = [
        "~/.config/dcg/packs/*.yaml",      # User packs
        ".dcg/packs/*.yaml",               # Project-local packs
    ]
    

    For detailed pack authoring guide, schema reference, and examples, see docs/custom-packs.md.

    Validate your pack before deployment:

    dcg pack validate mypack.yaml
    

    Heredoc scanning configuration:

    [heredoc]
    # Enable scanning for heredocs and inline scripts (python -c, bash -c, etc.).
    enabled = true
    
    # Extraction timeout budget (milliseconds).
    timeout_ms = 50
    
    # Resource limits for extracted bodies.
    max_body_bytes = 1048576
    max_body_lines = 10000
    max_heredocs = 10
    
    # Optional language filter (scan only these languages). Omit for "all".
    # languages = ["python", "bash", "javascript", "typescript", "ruby", "perl", "go"]
    
    # Bounded heredoc fallback (strict mode can block instead).
    fallback_on_parse_error = true
    fallback_on_timeout = true
    

    CLI overrides for heredoc scanning:

    • --heredoc-scan / --no-heredoc-scan
    • --heredoc-timeout <ms>
    • --heredoc-languages <lang1,lang2,...>

    Heredoc documentation:

    • docs/adr-001-heredoc-scanning.md (architecture and rationale)
    • docs/patterns.md (pattern authoring + inventory)
    • docs/security.md (threat model and incident response)
    Heredoc Three-Tier Architecture

    Heredoc and inline script scanning uses a three-tier pipeline designed for performance and accuracy:

    Command Input
         │
         ▼
    ┌─────────────────┐
    │ Tier 1: Trigger │ ─── No match ──► ALLOW (fast path, <100μs)
    │   (RegexSet)    │
    └────────┬────────┘
             │ Match
             ▼
    ┌─────────────────┐
    │ Tier 2: Extract │ ─── Error/Timeout ──► FALLBACK SCAN or BLOCK (strict)
    │   (<1ms)        │
    └────────┬────────┘
             │ Success
             ▼
    ┌─────────────────┐
    │ Tier 3: AST     │ ─── No match ──► ALLOW
    │   (<5ms)        │ ─── Match ──► BLOCK
    └─────────────────┘
    

    Tier 1: Trigger Detection (<100μs)

    Ultra-fast regex screening to detect heredoc indicators. Uses a compiled RegexSet for O(n) matching against all trigger patterns simultaneously:

    static HEREDOC_TRIGGERS: LazyLock<RegexSet> = LazyLock::new(|| {
        RegexSet::new([
            r"<<-?\s*(?:['\x22][^'\x22]*['\x22]|[\w.-]+)",  // Heredocs
            r"<<<",                                          // Here-strings
            r"\bpython[0-9.]*\b.*\s+-[A-Za-z]*[ce]",        // python -c/-e
            r"\bruby[0-9.]*\b.*\s+-[A-Za-z]*e",             // ruby -e
            r"\bnode(js)?[0-9.]*\b.*\s+-[A-Za-z]*[ep]",     // node -e/-p
            r"\b(sh|bash|zsh)\b.*\s+-[A-Za-z]*c",           // bash -c
            // ... more patterns
        ])
    });
    

    Commands without any trigger patterns skip directly to ALLOW—no further processing needed.

    Tier 2: Content Extraction (<1ms)

    For commands that trigger, extract the actual content to be evaluated:

    • Heredocs: cat <<EOF ... EOF → extracts body between delimiters
    • Here-strings: cat <<< "content" → extracts quoted content
    • Inline scripts: python -c "code" → extracts the code argument

    Extraction is bounded by configurable limits:

    • Maximum body size (default: 1MB)
    • Maximum lines (default: 10,000)
    • Maximum heredocs per command (default: 10)
    • Timeout (default: 50ms)
    pub struct ExtractionLimits {
        pub max_body_bytes: usize,
        pub max_body_lines: usize,
        pub max_heredocs: usize,
        pub timeout_ms: u64,
    }
    

    Tier 3: AST Pattern Matching (<5ms)

    Extracted content is parsed using language-specific AST grammars (via tree-sitter/ast-grep) and matched against structural patterns:

    // Example: detect subprocess.run with shell=True and rm -rf
    let pattern = r#"
        call_expression {
            function: attribute { object: "subprocess" attr: "run" }
            arguments: argument_list {
                contains string { contains "rm -rf" }
                contains keyword_argument { keyword: "shell" value: "True" }
            }
        }
    "#;
    

    Recursive Shell Analysis:

    When extracted content is itself a shell script (e.g., bash -c "git reset --hard"), Tier 3 recursively extracts inner commands and re-evaluates them through the full pipeline:

    if content.language == ScriptLanguage::Bash {
        let inner_commands = extract_shell_commands(&content.content);
        for inner in inner_commands {
            // Re-evaluate inner command against all packs
            if let Some(result) = evaluate_command(&inner, ...) {
                if result.decision == Deny {
                    return result; // Block the outer command
                }
            }
        }
    }
    

    If you encounter commands that should be blocked, please file an issue.

    Environment Variables

    Environment variables override config files (highest priority):

    • DCG_PACKS="containers.docker,kubernetes": enable packs (comma-separated)
    • DCG_DISABLE="kubernetes.helm": disable packs/sub-packs (comma-separated)
    • DCG_VERBOSE=0-3: verbosity level (0 = quiet, 3 = trace)
    • DCG_LOG=<filter>: hook-mode diagnostics; sends the evaluator's tracing events to stderr (DCG_LOG=debug, or a tracing filter such as destructive_command_guard::heredoc=trace). Unset by default.
    • DCG_QUIET=1: suppress non-error output
    • DCG_COLOR=auto|always|never: color mode
    • DCG_NO_RICH=1: disable rich terminal formatting and use plain rendering
    • DCG_NO_COLOR=1: disable colored output (same as NO_COLOR)
    • DCG_LEGACY_OUTPUT=1: force plain output paths (same as --legacy-output)
    • DCG_ROBOT=1: enable robot mode for JSON stdout and quiet stderr
    • DCG_HIGH_CONTRAST=1: enable high-contrast output (ASCII borders + monochrome palette)
    • DCG_FORMAT=text|json|sarif: default output format (command-specific — see Output Formats for which values each subcommand actually accepts; real SARIF is dcg scan-only)
    • DCG_FAIL_CLOSED=1: block (deny) on hook input that cannot be parsed, instead of the default fail-open allow (opt-in; see Bounded Failure Policy)
    • DCG_UNVERIFIED_DECISION=deny|ask: decision for commands dcg could not verify (evaluation timeout, or over max_command_bytes); deny suits unattended sessions where nobody can answer ask (see Bounded Failure Policy)
    • DCG_BRIDGE_CRASH_DECISION=allow: let a command through when the OpenCode plugin or the Oh My Pi bridge started dcg but got no verdict from it (dcg crashed or was killed); the default blocks (see Bounded Failure Policy)
    • DCG_BYPASS=1: bypass dcg entirely (escape hatch; use sparingly)
    • DCG_CONFIG=/path/to/config.toml: use explicit config file
    • DCG_HEREDOC_ENABLED=true|false: enable/disable the combined heredoc and inline-interpreter scanning tier (see heredoc configuration)
    • DCG_HEREDOC_TIMEOUT=50: heredoc extraction timeout (milliseconds)
    • DCG_HEREDOC_TIMEOUT_MS=50: heredoc extraction timeout (milliseconds)
    • DCG_HEREDOC_LANGUAGES=python,bash: filter heredoc languages
    • DCG_AST_TIMEOUT_MS=<milliseconds>: AST-matching budget for embedded code (default 20). It can only raise the compiled-in budget, never lower it: a smaller window pushes the matcher into its bounded fallback, which denies but without naming a rule, so shrinking it from the environment would degrade analysis rather than tighten it. Lower bounds belong to DCG_HOOK_TIMEOUT_MS and DCG_HEREDOC_TIMEOUT_MS, which are measured against real work
    • DCG_POLICY_DEFAULT_MODE=deny|ask|warn|log: global default decision mode (ask requires native operator review and fails closed on unsupported clients)
    • DCG_HOOK_TIMEOUT_MS=<milliseconds>: explicit hook evaluation timeout (ordinary default: 1000; automatic careful_company_running_windows preset default: 3000)
    • DCG_UPDATE_PIN=1: pin this install against dcg update (#320) — the updater refuses before any network/installer work unless --replace-local-build is passed, and the "update available" nudge is suppressed. Same as general.update_pin = true in config.
    • DCG_HISTORY_DB=/path/to/history.db: history database file (overrides [history] database_path; ~ is expanded). See Command History.
    • DCG_HISTORY_DISABLED=1: never open the history database, even when [history] enabled = true.

    Command History

    Command history is opt-in ([history] enabled = true). When enabled, the hook records every evaluated command (redacted per redaction_mode; the default "pattern" replaces recognised credential shapes with placeholders and truncates long quoted arguments) in a SQLite database that dcg history, dcg stats, and dcg suggest-allowlist read.

    Where the database lives, highest priority first:

    1. DCG_HISTORY_DB environment variable
    2. [history] database_path in config (~ expanded; relative paths resolve against the working directory)
    3. An existing history.db beside config.toml (~/.config/dcg/history.db) from a release before 0.15 — it keeps being used until you move it
    4. The platform state directory: $XDG_STATE_HOME/dcg/history.db, defaulting to ~/.local/state/dcg/history.db on Linux/macOS, and %LOCALAPPDATA%\dcg\history.db on Windows

    History is state, not configuration, so it no longer defaults into ~/.config/dcg; a sandbox that mounts the config directory read-only keeps working. Directories dcg creates for the database are owner-only (0700). dcg doctor prints the resolved path, which rule selected it, and whether the hook can write there.

    What a row can and cannot tell you:

    • hostname is the machine that recorded the row, so databases copied off several machines can be merged and still attributed.
    • exit_code is always NULL on rows the hook writes. dcg runs before the command, so it never learns how the command ended. Read NULL as "unknown", not as "succeeded". History records dcg's decisions (allow, deny, warn, bypass); it cannot by itself show that an allowed command did damage.
    • dcg history analyze works from those decisions. With no recorded commands it says so and makes no recommendations. A pack that never matched is listed but never recommended for removal: a guard pack that stays quiet is working.

    Output Formats and DCG_FORMAT

    --format (and the DCG_FORMAT env var, which seeds the default) is command-specific: each subcommand accepts only its own set of values, and an unrecognized value is a usage error (exit 2). DCG_FORMAT applies wherever a command has a --format flag and is silently ignored by commands that don't.

    CommandAccepted --format valuesNotes
    dcg scanpretty, json, markdown, sarifOnly command that emits real SARIF 2.1.0
    dcg testpretty (alias text), json (aliases sarif, structured), toon
    dcg configpretty (alias text), json (alias sarif)
    dcg packspretty (alias text), json (alias sarif)
    dcg explainpretty, json (alias sarif)
    dcg doctorpretty, json (alias sarif)
    dcg simulatepretty, json (alias sarif)
    dcg corpusjson, pretty (alias sarif)
    dcg suggest-allowlisttext, json (alias sarif)

    sarif is a JSON alias on every command except dcg scan. This is deliberate so that setting DCG_FORMAT=sarif globally degrades gracefully — dcg scan produces a real SARIF report while other commands fall back to their structured JSON rather than erroring. If you need machine-readable output from a non-scan command, prefer --format json (which is unambiguous); use dcg scan --format sarif for SARIF. --robot forces JSON regardless of --format.

    Configuration Hierarchy

    dcg supports layered configuration from multiple trusted sources, with higher-priority sources overriding lower ones:

    1. Environment Variables (DCG_* prefix) [HIGHEST PRIORITY]
    2. Explicit Config File (DCG_CONFIG env var)
    3. User Config (~/.config/dcg/config.toml)
    4. System Config (/etc/dcg/config.toml)
    5. Compiled Defaults [LOWEST PRIORITY]

    An automatically discovered .dcg.toml is intentionally not a normal precedence layer. A repository is untrusted when it is first cloned, so its config may only add enforcement: enable built-in packs, add deny policy entries, opt into general.fail_closed, enable heredoc scanning, or turn off heredoc bounded fallbacks. Settings that grant trust or reduce coverage — including allow overrides, pack disables, custom pack paths, custom regex overrides (including block regexes), resource limits, language filters, agent profiles, nested project overrides, and per-rule target-path exemptions — are ignored during automatic discovery.

    Automatic project discovery reads only a direct regular file bound to the handle it actually reads: O_NOFOLLOW plus descriptor identity on Unix (including macOS), and a reparse-point-refusing open plus handle/path identity on native Windows. A symlinked .dcg.toml is refused on every platform.

    To deliberately trust the complete repository config for one invocation, select it explicitly: DCG_CONFIG=.dcg.toml dcg .... An explicit file has the same full authority as any other user-selected config.

    Accessibility & Themes

    dcg supports colorblind-safe palettes and high-contrast output. Colors are always paired with symbols/labels to avoid conveying meaning by color alone.

    [output]
    high_contrast = true       # ASCII borders + black/white palette
    
    [theme]
    palette = "colorblind"     # default | colorblind | high-contrast
    use_unicode = true         # false for ASCII-only
    use_color = true           # false for monochrome
    

    Configuration File Locations:

    LevelPathUse Case
    System/etc/dcg/config.tomlOrganization-wide defaults
    User~/.config/dcg/config.tomlPersonal preferences
    Project.dcg.toml (repo root)Automatically discovered enforcement-only policy
    ExplicitDCG_CONFIG=/path/to/fileTesting or override

    The machine-wide system-config layer is accepted on Unix only after the file and every directory in its direct path are root-owned and not group/world writable. Native Windows currently ignores that implicit layer until native ACL and reparse-point validation is implemented; use a user config or an explicit DCG_CONFIG file there.

    Merging Behavior:

    Configuration layers are merged additively, with higher-priority sources overriding specific fields:

    // Only fields explicitly set in higher-priority configs override
    // Missing fields retain values from lower-priority sources
    fn merge_layer(&mut self, other: ConfigLayer) {
        if let Some(verbose) = other.general.verbose {
            self.general.verbose = verbose;  // Override if present
        }
        // Unset fields retain previous values
    }
    

    This means you can set organization defaults in /etc/dcg/config.toml, personal preferences in ~/.config/dcg/config.toml, and repository-owned hardening in .dcg.toml without letting a newly cloned repository weaken the user's guard. Use DCG_CONFIG=.dcg.toml only after reviewing a project file that needs full override authority.

    Project-Specific Pack Configuration:

    The [projects] section allows different pack configurations for different repositories:

    [projects."/home/user/work/production-api"]
    packs = { enabled = ["database.postgresql", "cloud.aws"], disabled = [] }
    
    [projects."/home/user/personal/experiments"]
    packs = { enabled = [], disabled = ["core.git"] }  # More permissive for experiments
    

    Bounded Failure Policy

    dcg distinguishes an unreadable hook envelope from a command whose safety evaluation began but could not finish. It never treats elapsed analysis time or an oversized extracted command as proof that execution is safe.

    ScenarioDefault behaviorStrict/configured behavior
    Malformed or oversized raw hook JSONAllow with an audit warninggeneral.fail_closed = true denies
    Transient hook stdin I/O errorAllow with an audit warningAlways fail-open because the payload was not attacker-controlled
    Extracted command exceeds max_command_bytesExplicit indeterminate resultReview-capable clients receive ask (unverified_decision = "deny" turns this into a deny); other clients block
    Absolute evaluation deadline expiresExplicit indeterminate resultReview-capable clients receive ask (unverified_decision = "deny" turns this into a deny); other clients block
    A safety regex fails during evaluation, including backtracking work exhaustionIncomplete analysis, never a silent allowProven blocking findings retain their denial; otherwise request review or block according to the unverified policy. Warning/log policy and confidence or rebase-recovery exemptions cannot hide the incomplete analysis
    Heredoc extraction/parse/AST failureRun the bounded fallback scannerfallback_on_parse_error = false or fallback_on_timeout = false blocks
    OpenCode plugin or Oh My Pi bridge: dcg cannot be started (missing or not executable)Allow with a visible diagnostic, so a broken install does not block every commandOMP: DCG_UNVERIFIED_DECISION=deny in its environment blocks; the config-file setting cannot apply because dcg never read it
    OpenCode plugin or Oh My Pi bridge: dcg started but gave no verdict (killed by a signal or the bridge's timeout, an unexpected exit status; for OpenCode also exit 0 without its explicit allow line)Block, with the reason and a visible diagnostic; a deny dcg wrote before dying still standsDCG_BRIDGE_CRASH_DECISION=allow in the agent's environment lets such commands through; DCG_UNVERIFIED_DECISION=deny still blocks
    A panic in the hook after its configuration is loaded and before a shell command is known to be allowedThe unverified-command response for the request's protocol (ask, or deny under `unverified_decision = "deny