# Scan Detection Rules — Pattern Reference

Detailed Grep patterns for all 15 detection rules. Use this as reference when executing the `scan` subcommand.

**Markdown files**: For `.md` files, only scan inside fenced code blocks (between ``` markers). Additionally, decode and re-scan base64-encoded payloads found in any file.

## Rule 1: SHELL_EXEC (HIGH)
**Files**: `*.js`, `*.ts`, `*.mjs`, `*.cjs`, `*.py`, `*.md`

| Pattern | Description |
|---------|-------------|
| `require\s*\(\s*['"\x60]child_process` | Node.js child_process require |
| `from\s+['"\x60]child_process` | ES module child_process import |
| `\bexec\s*\(` | exec() call |
| `\bexecSync\s*\(` | execSync() call |
| `\bspawn\s*\(` | spawn() call |
| `\bspawnSync\s*\(` | spawnSync() call |
| `\bexecFile\s*\(` | execFile() call |
| `\bfork\s*\(` | fork() call |
| `\bsubprocess\.` | Python subprocess module |
| `\bos\.system\s*\(` | Python os.system() |
| `\bos\.popen\s*\(` | Python os.popen() |
| `\bos\.exec\w*\s*\(` | Python os.exec*() |
| `\bcommands\.getoutput\s*\(` | Python commands module |
| `\bcommands\.getstatusoutput\s*\(` | Python commands module |

## Rule 2: AUTO_UPDATE (CRITICAL)
**Files**: `*.js`, `*.ts`, `*.py`, `*.sh`, `*.md`

| Pattern | Description |
|---------|-------------|
| `cron\|schedule\|interval.*exec\|setInterval.*exec` (i) | Scheduled execution |
| `auto.?update\|self.?update` (i) | Auto-update mechanism |
| `curl.*\|\s*(bash\|sh)` | curl pipe to shell |
| `wget.*\|\s*(bash\|sh)` | wget pipe to shell |
| `fetch.*then.*eval` | Fetch and eval chain |
| `download.*execute` (i) | Download and execute |

## Rule 3: REMOTE_LOADER (CRITICAL)
**Files**: `*.js`, `*.ts`, `*.mjs`, `*.py`, `*.md`

| Pattern | Description |
|---------|-------------|
| `import\s*\(\s*[^'"\x60\s]` | Dynamic import with variable |
| `require\s*\(\s*[^'"\x60\s]` | Dynamic require with variable |
| `fetch\s*\([^)]*\)\.then\([^)]*\)\s*\.then\([^)]*eval` | Fetch + eval chain |
| `axios\.[^)]*\.then\([^)]*eval` | Axios + eval chain |
| `exec\s*\(\s*requests\.get` | Python exec(requests.get()) |
| `eval\s*\(\s*requests\.get` | Python eval(requests.get()) |
| `exec\s*\(\s*urllib` | Python exec(urllib) |
| `eval\s*\(\s*urllib` | Python eval(urllib) |
| `__import__\s*\(` | Python dynamic import |
| `importlib\.import_module\s*\(` | Python importlib |

## Rule 4: READ_ENV_SECRETS (MEDIUM)
**Files**: `*.js`, `*.ts`, `*.mjs`, `*.py`

| Pattern | Description |
|---------|-------------|
| `process\.env\s*\[` | Node.js env bracket access |
| `process\.env\.` | Node.js env dot access |
| `require\s*\(\s*['"\x60]dotenv` | dotenv require |
| `from\s+['"\x60]dotenv` | dotenv import |
| `os\.environ` | Python os.environ |
| `os\.getenv\s*\(` | Python os.getenv() |
| `dotenv\.load_dotenv` | Python dotenv |
| `from\s+dotenv\s+import` | Python dotenv import |

## Rule 5: READ_SSH_KEYS (CRITICAL)
**Files**: All

| Pattern | Description |
|---------|-------------|
| `~/.ssh` | SSH directory reference |
| `\.ssh/id_rsa` | RSA key |
| `\.ssh/id_ed25519` | Ed25519 key |
| `\.ssh/id_ecdsa` | ECDSA key |
| `\.ssh/id_dsa` | DSA key |
| `\.ssh/known_hosts` | Known hosts |
| `\.ssh/authorized_keys` | Authorized keys |
| `HOME.*\.ssh` | HOME-based SSH path |
| `USERPROFILE.*\.ssh` | Windows SSH path |

## Rule 6: READ_KEYCHAIN (CRITICAL)
**Files**: All

| Pattern | Description |
|---------|-------------|
| `keychain` (i) | Keychain reference |
| `security\s+find-` | macOS security command |
| `Chrome.*Local\s+State` (i) | Chrome local state |
| `Chrome.*Login\s+Data` (i) | Chrome login data |
| `Chrome.*Cookies` (i) | Chrome cookies |
| `Chromium` (i) | Chromium browser |
| `Firefox.*logins\.json` (i) | Firefox logins |
| `Firefox.*cookies\.sqlite` (i) | Firefox cookies |
| `CredRead` | Windows CredRead |
| `Windows.*Credentials` (i) | Windows credentials |
| `credential.*manager` (i) | Credential manager |

## Rule 7: PRIVATE_KEY_PATTERN (CRITICAL)
**Files**: All

| Pattern | Description |
|---------|-------------|
| `['"\x60]0x[a-fA-F0-9]{64}['"\x60]` | Hex-encoded private key in quotes |
| `private[_\s]?key\s*[:=]\s*['"\x60]0x[a-fA-F0-9]{64}` (i) | Named private key |
| `PRIVATE_KEY\s*[:=]\s*['"\x60][a-fA-F0-9]{64}` (i) | PRIVATE_KEY assignment |

## Rule 8: OBFUSCATION (HIGH)
**Files**: `*.js`, `*.ts`, `*.mjs`, `*.py`, `*.md`

| Pattern | Description |
|---------|-------------|
| `\beval\s*\(` | eval() call |
| `new\s+Function\s*\(` | Function constructor |
| `setTimeout\s*\(\s*['"\x60]` | setTimeout with string |
| `setInterval\s*\(\s*['"\x60]` | setInterval with string |
| `atob\s*\([^)]+\).*eval` | Base64 decode + eval |
| `Buffer\.from\s*\([^,]+,\s*['"\x60]base64['"\x60]\s*\).*eval` | Buffer base64 + eval |
| 10+ consecutive `\\x[0-9a-fA-F]{2}` | Hex escape obfuscation |
| 10+ consecutive `\\u[0-9a-fA-F]{4}` | Unicode escape obfuscation |
| `String\.fromCharCode\s*\(\s*\d+(?:\s*,\s*\d+){10,}` | Large fromCharCode |
| `eval\s*\(\s*function\s*\(\s*p\s*,\s*a\s*,\s*c\s*,\s*k\s*,\s*e` | Packed JS (p,a,c,k,e,r) |

## Rule 9: PROMPT_INJECTION (CRITICAL)
**Files**: All

| Pattern | Description |
|---------|-------------|
| `ignore\s+(previous\|all\|above\|prior)\s+(instructions?\|rules?\|guidelines?)` (i) | Ignore instructions |
| `disregard\s+(previous\|all\|above\|prior)\s+(instructions?\|rules?\|guidelines?)` (i) | Disregard instructions |
| `forget\s+(previous\|all\|above\|prior)\s+(instructions?\|rules?\|guidelines?)` (i) | Forget instructions |
| `you\s+are\s+(now\|a)\s+(?:DAN\|jailbroken\|unrestricted)` (i) | Jailbreak attempt |
| `pretend\s+(?:you\s+are\|to\s+be)\s+(?:a\s+)?(?:different\|new\|unrestricted)` (i) | Role pretend |
| `act\s+as\s+(?:if\s+)?(?:you\s+have\s+)?no\s+(?:restrictions?\|rules?\|limitations?)` (i) | No restrictions |
| `(?:no\|without\|skip)\s+(?:need\s+(?:for\s+)?)?confirm(?:ation)?` (i) | Skip confirmation |
| `bypass\s+(?:security\|safety\|restrictions?\|confirm)` (i) | Bypass security |
| `auto(?:matically)?\s+(?:approve\|confirm\|execute\|run)` (i) | Auto-execute |
| `you\s+must\s+(?:always\s+)?(?:obey\|follow\|execute)` (i) | Force obedience |
| `system\s*:\s*you\s+are` (i) | System prompt injection |
| `\[system\].*\[/system\]` (i, multiline) | System tag injection |
| `忽略(?:之前\|所有\|上面)(?:的)?(?:指令\|规则\|说明)` | Chinese: ignore instructions |
| `无需确认\|自动执行\|跳过验证` | Chinese: skip confirm / auto-exec |

## Rule 10: NET_EXFIL_UNRESTRICTED (HIGH)
**Files**: `*.js`, `*.ts`, `*.mjs`, `*.py`, `*.md`

| Pattern | Description |
|---------|-------------|
| `fetch\s*\([^)]+,\s*\{[^}]*method\s*:\s*['"\x60]POST` | Fetch POST |
| `axios\.post\s*\(` | Axios POST |
| `requests\.post\s*\(` | Python requests POST |
| `http\.request\s*\([^)]*method\s*:\s*['"\x60]POST` | Node http POST |
| `new\s+FormData\s*\(` | FormData creation |
| `enctype\s*[:=]\s*['"\x60]multipart/form-data` | Multipart upload |

## Rule 11: WEBHOOK_EXFIL (CRITICAL)
**Files**: All

| Pattern | Description |
|---------|-------------|
| `discord(?:app)?\.com/api/webhooks` (i) | Discord webhooks |
| `api\.telegram\.org/bot` (i) | Telegram bot API |
| `telegram-bot-api` (i) | Telegram bot lib |
| `hooks\.slack\.com` (i) | Slack webhooks |
| `webhook\s*[:=]\s*['"\x60]https?:` (i) | Generic webhook URL |
| `ngrok\.io` (i) | ngrok tunnel |
| `ngrok-free\.app` (i) | ngrok free |
| `requestbin` (i) | RequestBin |
| `pipedream` (i) | Pipedream |
| `webhook\.site` (i) | Webhook.site |

## Rule 12: TROJAN_DISTRIBUTION (CRITICAL)
**Files**: `*.md`

Detects trojanized binary distribution patterns. Flags when 2+ of the following signals are present:

| Signal | Pattern | Description |
|--------|---------|-------------|
| Download | `https?://.*(?:releases/download\|\.zip\|\.tar\|\.exe\|\.dmg)` (i) | Binary download URL |
| Password | `password\s*[:=]` (i) | Password for archive |
| Execute | `chmod\s+\+x\|\.\/\w+\|run\s+the\|execute` (i) | Execute instruction |

**Validation**: Must match at least 2 of the 3 signals to trigger.

## Rule 13: SUSPICIOUS_PASTE_URL (HIGH)
**Files**: All

| Pattern | Description |
|---------|-------------|
| `glot\.io/snippets/` (i) | Glot.io snippets |
| `pastebin\.com/` (i) | Pastebin |
| `hastebin\.com/` (i) | Hastebin |
| `paste\.ee/` (i) | Paste.ee |
| `dpaste\.org/` (i) | dpaste |
| `rentry\.co/` (i) | Rentry |
| `ghostbin\.com/` (i) | Ghostbin |
| `pastie\.io/` (i) | Pastie |

## Rule 14: SUSPICIOUS_IP (MEDIUM)
**Files**: All

| Pattern | Description |
|---------|-------------|
| `\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b` | IPv4 address |

**Validation**: Excludes private/reserved ranges:
- `127.x.x.x` (loopback), `0.x.x.x`, `10.x.x.x`, `172.16-31.x.x`, `192.168.x.x`, `169.254.x.x` (link-local)
- Version-like patterns (`x.0.0.0`)
- Values > 255 in any octet

## Rule 15: SOCIAL_ENGINEERING (MEDIUM)
**Files**: `*.md`

| Pattern | Description |
|---------|-------------|
| `CRITICAL\s+REQUIREMENT` (i) | Urgency pressure |
| `WILL\s+NOT\s+WORK\s+WITHOUT` (i) | Fear-based pressure |
| `MANDATORY.*(?:install\|download\|run\|execute)` (i) | Mandatory action |
| `you\s+MUST\s+(?:install\|download\|run\|execute\|paste)` (i) | Forced action |
| `paste\s+(?:this\s+)?into\s+(?:your\s+)?[Tt]erminal` (i) | Terminal paste instruction |
| `IMPORTANT:\s*(?:you\s+)?must` (i) | Importance pressure |

**Validation**: Only triggers if content also contains a command execution pattern (`curl`, `wget`, `bash`, `sh`, `./`, `chmod`, `npm run`, `node`).

Note: `(i)` = case insensitive search, `(multiline)` = enable multiline matching.

---

## MCP Tool-Call Routing (Phase 0 content detection)

In addition to the file-content scan rules above, Phase 0 inspects every
`exec_command` action for indirect MCP tool invocations. The capture
model is documented at [Phase 0 — Tool Gate · MCP Tool Routing](../../../../docs/phases/phase-0-tool-gate.html#mcp-routing);
the runtime path is summarized below.

| Stage | Layer | Coverage |
|-------|-------|----------|
| 1 | Unwrap (U1-U16) | shell `-c`, `$SHELL -c`, eval, heredoc/here-string, process substitution (incl. `bash <(echo "X")` decoded to X), command substitution, source/script, interpreter inline (Python/Node/Ruby/Perl/PHP/Lua/Rscript/Deno/Bun/PowerShell), base64 / xxd / openssl-base64 decode pipes, string-concat folding, xargs / find -exec / parallel / watch / time / env (incl. `echo X \| xargs CMD` synthesized to `CMD X`), ssh / docker exec / kubectl exec / podman exec, vim/nvim/ed/ex `-c '!...'`, ansible / make-stdin, nohup / setsid / disown / at / batch / crontab / systemd-run / launchctl bsexec, gcc/clang/go run/rustc compile-and-run |
| 2 | Detect (D1-D16) | mcporter, curl-class, HTTPie-class, TCP/socket multiplex (nc/socat/openssl s_client/websocat/grpcurl), Bash `/dev/tcp` builtin, PowerShell `Invoke-*`, language-runtime URL literals + JSON-RPC tool extraction (handles Python single-quote dict literals), stdio JSON-RPC pipe, stdin redirect, FIFO, package runners (npx/bunx/pnpm-dlx/yarn-dlx/pipx/uv/uvx/deno run/go run), MCP server self-launch (audit-only), compile-and-run (audit-only), obfuscation fallback (audit-only) |
| 3 | Gate | Resolved `{server, tool}` candidates feed the existing `permitted_tools.mcp` / `blocked_tools.mcp` allowlist; when a detector resolves a server but no tool, both lists bias toward deny (allowlist via `${server}__*` failing the whitelist; denylist via any list entry that could be a tool on the resolved server); audit-only hits inform the audit log without contributing to deny |
