A security-aware git wrapper that detects and neutralises supply-chain attack vectors before you open, switch to, or pull into a repository or branch.
Cloning a repository has always felt safe. That changed when the Miasma worm targeted developers by planting auto-executing payloads inside AI coding agent configs, VS Code tasks, and NPM scripts — triggered the moment a folder is opened, not when a package is installed. git-moat stands at the drawbridge: it clones, checks out, or pulls the branch, scans every known attack surface in a temporary worktree, and blocks or remediates threats before any tool can fire them.
curl -fsSL https://raw.githubusercontent.com/JBGamond/git-moat/main/scripts/install.sh | bashInstalls to ~/.local/bin by default. Override with INSTALL_DIR:
INSTALL_DIR=/usr/local/bin curl -fsSL https://raw.githubusercontent.com/JBGamond/git-moat/main/scripts/install.sh | bashPin a specific release with VERSION=v0.2.0.
Pre-built binaries are available for:
| Platform | Architecture |
|---|---|
| Linux | x86_64, aarch64 |
git clone https://github.com/JBGamond/git-moat
cd git-moat
make install # builds release binary, installs to ~/.local/bin + shell completionsRequires Rust ≥ 1.70.
Installed automatically by make install or scripts/install.sh (when run from a cloned repo). To enable manually:
# zsh — copy to a directory in $fpath
cp completions/_git-moat ~/.local/share/zsh/site-functions/_git-moat
autoload -Uz compinit && compinit
# bash — source or copy to completions dir
cp completions/git-moat.bash ~/.local/share/bash-completion/completions/git-moat
source ~/.local/share/bash-completion/completions/git-moatDrop-in replacement for git clone. All standard git flags are forwarded transparently.
git-moat clone https://github.com/org/repo
git-moat clone --depth 1 -b main https://github.com/org/repo ./local-dirScan a branch before switching to it. git-moat checks out the target branch into a temporary detached worktree, runs all threat rules against it, removes the worktree, then performs the real switch only if the branch is safe.
If the local branch is behind its remote tracking branch, git-moat scans the remote's latest commits and fast-forwards only if they are clean.
If the target branch is already active, the worktree step is skipped and git-moat simply checks whether a pull is needed.
git-moat checkout feature/new-api- Critical or High threats found → checkout is blocked, threats are printed, exit 1.
- Medium threats found → checkout proceeds with a warning.
- No threats → switches normally.
Tab-completion for branch names works in both bash and zsh.
Secure equivalent of git pull. Fetches the remote tracking branch, scans the incoming commits in a temporary worktree, then fast-forwards only if clean.
git-moat pullAlways acts on the current branch. No arguments needed.
Passthrough to git fetch. Fetch only downloads refs and never modifies the working tree, so no scan is required.
git-moat fetch
git-moat fetch --all --prune
git-moat fetch origin mainTab-completion offers remote names and common flags.
git-moat — secure clone v0.1.0
Cloning https://github.com/org/repo ...
Scanning for threats in ./repo
-> [1/8] Scanning for direct payloads and droppers (.github/setup.js)...
-> [2/8] Checking agent session start hooks (.claude & .gemini)...
-> [3/8] Checking VS Code task folderOpen auto-runs...
-> [4/8] Validating Cursor rules (.mdc alwaysApply configurations)...
-> [5/8] Inspecting package.json scripts and lifecycles...
-> [6/8] Checking binding.gyp silent build expansions...
-> [7/8] Inspecting git history, signatures, and backdated logs...
-> [8/8] Scanning build/dependency manifests for auto-executing hook vectors...
[CRITICAL] Miasma Dropper
File: .github/setup.js
Found '.github/setup.js' matching Miasma Worm IOCs: Caesar-shift eval harness
(shift-value-agnostic), oversized file (>500 KB).
Action: DELETED
[CRITICAL] Claude Session Hook Injection
File: .claude/settings.json
Found Claude settings with a SessionStart hook executing shell command(s):
["node .github/setup.js"]
Action: DELETED
2 threat(s) found and neutralised. Repository is safe to open.
====================================================
git-moat Scanning branch: feature/backdoored
git-moat Repository: /home/user/my-project
====================================================
Scanning branch in temporary worktree — working tree untouched...
⚠️ SECURITY ALERT: CHECKOUT BLOCKED ⚠️
Branch contains Critical/High threats. Checkout was aborted.
Remove the threat vectors from the branch before switching.
1. [CRITICAL] Claude Session Hook Injection (.claude/settings.json)
Description: Found Claude settings with a SessionStart hook executing
shell command(s): ["node .github/setup.js"]
Remediation: i Logged only (scan-only or commit-log anomaly).
git-moat scans eight attack surfaces documented in the Miasma worm campaign and extended to cover the broader supply-chain landscape:
| Rule | File(s) | Vector |
|---|---|---|
| Miasma Dropper | .github/setup.js |
Caesar-shift eval harness (shift-value-agnostic), AES-128-GCM loader, Bun runtime download, oversized JS file |
| AI Agent Hooks | .claude/settings.json, .gemini/settings.json |
SessionStart command hooks; cross-correlation of identical commands across both agents (exact Miasma fingerprint) |
| VS Code Auto-run | .vscode/tasks.json |
folderOpen tasks fire without user interaction |
| Cursor Prompt Injection | .cursor/rules/*.mdc |
alwaysApply: true + execution keywords (social-engineers the agent) |
| NPM Script Hijack | package.json |
preinstall, postinstall, prepare, test and related hooks |
| node-gyp Expansion | binding.gyp |
<!(...) command expansions run silently at config time |
| Git History Anomaly | git log | Unsigned github-actions commits, [skip ci] + dependency wording, backdated timestamps |
| Build Hook Auto-run | composer.json, Gemfile, *.gemspec, Makefile, Podfile, setup.py, pyproject.toml, pom.xml, build.gradle, build.rs |
Auto-executing hooks across PHP, Ruby, Make, iOS, Python, Java, and Rust build systems |
| Level | Meaning |
|---|---|
| CRITICAL | Matches a known Miasma/Shai-Hulud IOC or direct dropper pattern — remove immediately |
| HIGH | Auto-execution confirmed, payload unrecognised — high confidence of malice |
| MEDIUM | Auto-execution capability present — verify before opening |
git-moat removes threats in place rather than deleting the entire clone:
- Dropper files (
.github/setup.js, AI agent configs, VS Code tasks) are deleted package.jsonhas the malicious scripts stripped and the file rewritten cleanly- Git history anomalies are logged (no on-disk remediation possible)
- All other flagged files are deleted
git-moat is built around a hexagonal architecture with a strict domain / ports / adapters separation:
src/
├── domain/
│ ├── rules/ # One file per threat-detection rule (ThreatRule trait)
│ ├── service.rs # Use-case orchestrator: clone/checkout/pull → scan → remediate
│ └── threat.rs # Pure domain types (Threat, ThreatLevel, ScanReport)
├── ports/ # Trait interfaces (GitClient, ThreatAnalyzer, Sanitizer)
└── adapters/ # Concrete implementations (real git, filesystem, rules engine)
Adding a new detection rule is a single file implementing ThreatRule::check(&self, dir: &Path) -> Vec<Threat> — no other code needs to change.
On 3 June 2026 the Miasma worm hit 123+ GitHub repositories across dozens of accounts (including Microsoft Azure and major npm packages). A single unsigned commit planted six files — one dropper, five triggers — wiring the same 4.3 MB payload to fire automatically in Claude Code, Gemini CLI, Cursor, VS Code, and npm test. The attack detonates when a developer opens the cloned folder, not when they install packages.
Full write-up: Miasma Worm Targets AI Coding Agents via GitHub Repos — SafeDep, June 2026.
Threat landscapes evolve — new rules are welcome. The bar for a new rule:
- Create
src/domain/rules/<name>.rsimplementingThreatRule - Register it in
src/domain/rules/mod.rsandsrc/adapters/threat_analyzer.rs - Add an integration test in
tests/integration.rs
cargo test # all tests must pass
cargo clippy # no warningsMIT — see LICENSE.
