SW-CFG-007: Hook decodes and runs an encoded payload (malicious indicator)
Last updated 7 November 2026
| Fact | Value |
|---|---|
| Severity | Critical |
| Checks | Configuration file |
| Malicious indicator | Yes. The scanner marks this rule as a malicious indicator. Its README says that an open finding from a rule of this kind quarantines the package, and a baseline entry that accepts such a finding may last 30 days at most, against 365 for other rules. The sources describe the quarantine for skill packages; for a hook in a configuration file they show the flag on the finding, malicious_indicator in the report. |
| 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 the Claude Code settings files and of local plugins, as SW-CFG-005 does, with commands joined over line continuations. A command fires when it holds a decoder and an executor together, in any case. The decoders are base64 -d, base64 --decode, base64 -D, b64decode(, atob(, openssl enc -d, xxd -r, gunzip -c and zcat.
The executors are a pipe into sh, bash, zsh, python, node, perl or ruby (with an optional sudo), and the words eval(, exec(, subprocess, os.system, child_process, new Function( and sh -c. A decoder without an executor is quiet, and so is an executor without a decoder. The rule does not decode the text or judge what it says: the evidence is the event and the command as written.
Why it matters
A hook runs when an event occurs, without anyone choosing to run it that time. A command that hides its own content from the reader and then executes it is the clearest sign of a planted payload that a configuration file can show: honest automation names what it runs, because the next maintainer has to read it. 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. The rule is critical and marked as a malicious indicator for that reason.
Example
The hook below decodes a base64 string and passes the result to sh before every call of the shell tool. The string decodes to the harmless echo hi, and the rule does not look at what it says:
.claude/settings.json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "echo ZWNobyBoaQ== | base64 -d | sh"
}
]
}
]
}
}The finding is on the line of the command, and the evidence reads PreToolUse and the command.
How to fix
Remove the hook and find out where it came from. Decode the string without running it, for example by writing the result to a file that you open as text, to learn what it would have done, and rotate anything the machine could reach if it has run.
If the hook has a real job, write the command out in plain text, or move it to a script file in the repository that a reviewer can read.
If it is a false positive
A hook that decodes a value only to store it, such as a base64 certificate written to a file, is quiet unless an executor word is in the same command. A command that remains, for instance a Python one-liner that decodes a setting and then calls subprocess to start a formatter, matches by design. Read the decoded content; if you wrote it, accept it in the baseline file with a reason, for 30 days at most.
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.