Check Catalog¶
aisecscan ships 31 checks across seven categories. Each check is identified by an AS-<CATEGORY>-NNN ID and declares a severity, a description, and a framework mapping (OWASP LLM Top 10, OWASP Top 10 for Agentic Applications (ASI01–ASI10), NIST AI RMF).
Most checks catch misconfiguration (over-broad permissions, missing pins, hardcoded secrets). A smaller set targets agentic attack patterns specifically — combinations of capabilities that enable goal hijacking, memory poisoning, or rogue-agent behavior even when each individual setting looks reasonable on its own: AS-AGENT-002 (ASI01 Agent Goal Hijack) and AS-HOOK-005 (ASI06 Memory & Context Poisoning). These don't require reading a skill's bundled script content — they're detectable from the same frontmatter/config aisecscan already parses, by looking at combinations of declared capabilities rather than any single one.
Severities: CRITICAL › HIGH › MEDIUM › LOW
Hooks (AS-HOOK-*)¶
Hooks are arbitrary shell commands that run at 30+ Claude Code lifecycle events. They are the highest-risk artifact in the config surface.
| ID | Severity | What it catches |
|---|---|---|
AS-HOOK-001 |
CRITICAL | Hook fetches & executes remote code (curl\|sh, eval $(curl)) |
AS-HOOK-002 |
HIGH | Hook runs a script from a relative / world-writable path |
AS-HOOK-003 |
MEDIUM | Context-injecting hook (SessionStart/UserPromptSubmit) makes network calls |
AS-HOOK-004 |
LOW | Hook has no timeout |
AS-HOOK-005 |
HIGH | Hook fetches external content and writes it into persistent memory/context (CLAUDE.md, MEMORY.md, .claude/memory/*) |
Framework mapping: OWASP LLM05 (Improper Output Handling), Agentic Tool Misuse; AS-HOOK-005 maps to OWASP Agentic AI Top 10 ASI06 (Memory & Context Poisoning).
AS-HOOK-005 is distinct from AS-HOOK-003: AS-HOOK-003 flags a context-injecting hook making a network call, which only affects the current session's context. AS-HOOK-005 requires the command to also redirect (>>, >, tee) that fetched content into a persistent memory path — a narrower, higher-confidence pattern because the poisoned content survives and re-influences every future session, not just this one.
Permissions (AS-PERM-*)¶
Permission rules decide what Claude Code may do without asking the user. Over-broad or bypassed rules remove the human-in-the-loop.
| ID | Severity | What it catches |
|---|---|---|
AS-PERM-001 |
HIGH | defaultMode: bypassPermissions / acceptEdits weakens prompts |
AS-PERM-002 |
HIGH | Overly broad Bash allow (Bash(*), Bash(:*)) |
AS-PERM-003 |
MEDIUM | Dangerous command allowed unscoped (curl, sudo, rm, eval, …) |
Framework mapping: OWASP LLM06 (Excessive Agency), NIST AI RMF GOVERN/MANAGE.
MCP Servers (AS-MCP-*)¶
MCP servers are arbitrary processes (stdio) or remote endpoints (HTTP/SSE) given privileged access to the agent's context. Supply-chain and transport risks are high.
| ID | Severity | What it catches |
|---|---|---|
AS-MCP-001 |
HIGH | Plaintext secret in MCP server env |
AS-MCP-002 |
HIGH | Remote MCP server over cleartext http:// |
AS-MCP-003 |
HIGH | enableAllProjectMcpServers: true (auto-trust all project MCP) |
AS-MCP-004 |
MEDIUM | stdio MCP pulls an unpinned remote package (npx -y pkg) |
AS-MCP-005 |
HIGH* | stdio MCP pins a package with a known vulnerability (live OSV.dev lookup) |
AS-MCP-007 |
LOW** | stdio MCP server has no explicit timeout |
Framework mapping: OWASP LLM03 (Supply Chain), MCP security guidance; AS-MCP-007 maps to OWASP LLM10 (Unbounded Consumption).
* AS-MCP-005 severity is taken from the matched OSV advisory (CRITICAL/HIGH/MEDIUM/LOW), not fixed.
** AS-MCP-007 only applies to stdio (command-launched) servers — a remote (url-based) server already gets a sane ~60-second default per-request timer. A stdio server with no timeout falls back to MCP_TOOL_TIMEOUT, which defaults to roughly 28 hours — long enough that a hung process is, in practice, unbounded. AS-MCP-006 (MCP server not on an explicit allowlist) remains a separate, unimplemented roadmap item — not to be confused with this check.
Environment & Secrets (AS-ENV-*, AS-SECRET-*)¶
Endpoint redirect and hardcoded credentials are both exfiltration and MITM vectors.
| ID | Severity | What it catches |
|---|---|---|
AS-ENV-001 |
HIGH | API endpoint/token redirected away from Anthropic (ANTHROPIC_BASE_URL, ANTHROPIC_AUTH_TOKEN) |
AS-SECRET-001 |
HIGH | Hardcoded secret/API key in a config file |
Framework mapping: OWASP LLM02 (Sensitive Information Disclosure).
Dependencies (AS-DEP-*)¶
Skills can bundle a requirements.txt or package.json. A pinned dependency with a known CVE is a supply-chain risk independent of whether the pin itself is "good practice" (see AS-MCP-004, which flags the opposite problem — no pin at all).
| ID | Severity | What it catches |
|---|---|---|
AS-DEP-001 |
HIGH* | Skill dependency pinned to a version with a known vulnerability (live OSV.dev lookup) |
Framework mapping: OWASP LLM03 (Supply Chain).
* Severity is taken from the matched OSV advisory. Only pinned versions (pkg==1.2.3) are checked — OSV lookups need a concrete version to test against an affected range. Unpinned dependencies aren't silently treated as safe; they're simply out of scope for this check.
Network and offline behavior: this check calls OSV.dev (no API key required) and caches results to ~/.cache/aisecscan/osv_cache.json for 24h. Pass --offline to skip the lookup entirely — the check then finds nothing rather than reporting stale or fabricated results. Unlike scanners that ship a bundled "offline CVE list," aisecscan does not: a static list goes stale the day it's written and makes an offline scan look identical to a fully-informed one. --offline makes the tradeoff explicit instead of hiding it.
Agents & Subagents (AS-AGENT-*)¶
Agents inherit permissions and can spawn further agents. Over-privilege compounds across the call chain.
| ID | Severity | What it catches |
|---|---|---|
AS-AGENT-001 |
HIGH | Over-privileged agent/skill (bypassPermissions, tools: *) |
AS-AGENT-002 |
MEDIUM* | Agent/skill combines an untrusted-input tool (WebFetch, WebSearch, mail/calendar/feed-like MCP tools) with a high-impact action tool (Bash, write/send/pay/deploy/delete-like tools) |
AS-AGENT-005 |
LOW** | Agent frontmatter has no effective maxTurns bound — missing entirely, or present but not a positive integer |
AS-AGENT-006 |
CRITICAL | Agent sets permissionMode: bypassPermissions/acceptEdits (no approval checkpoint) AND has no effective maxTurns — unattended and unbounded at the same time |
AS-AGENT-007 |
HIGH | Agent sets background: true (runs detached) AND has no effective maxTurns — unobserved and unbounded at the same time |
Framework mapping: OWASP LLM06 (Excessive Agency); AS-AGENT-002 maps to OWASP Agentic AI Top 10 ASI01 (Agent Goal Hijack) and LLM01 (Prompt Injection); AS-AGENT-005/AS-AGENT-006/AS-AGENT-007 map to OWASP LLM10 (Unbounded Consumption).
* AS-AGENT-002 escalates to HIGH when permissionMode is bypassPermissions/acceptEdits (no approval checkpoint at all). This check flags a combination of capabilities, not a single bad setting — an agent with WebFetch + Bash is common and often legitimate; the finding is a prompt to add a human-approval or agent-separation checkpoint per OWASP ASI01 guidance, not necessarily a bug.
AS-AGENT-006 and AS-AGENT-007 are compound checks in the same spirit as AS-AGENT-002: AS-AGENT-001 already flags bypassPermissions alone, and AS-AGENT-005 already flags a missing maxTurns alone, but the combination is worse than either — an agent that can act without approval and never stops, or one that runs detached and never stops, with no per-agent or repo-wide safety net to fall back on (there is no settings.json-level default for either maxTurns or a spend cap — see below).
What this can't catch (checked, not just unimplemented): two related ideas were considered and ruled out as not statically checkable, rather than deferred to a roadmap —
- A
maxBudgetUsd-style spend cap in agent frontmatter. No such field exists on the.claude/agents/*.md/ pluginagents/*.mdsurface. Budget capping (maxBudgetUsd/max_budget_usd) is an Agent SDKquery()-time option only — a runtime call parameter, never persisted to a file — so there is nothing in any repo for a static scanner to read. - A repo-wide
settings.jsondefault formaxTurnsor spend, so individual agents wouldn't be the only line of defense.settings.json's schema (project/user/managed) has no such key at any scope; per-agentmaxTurnsin each agent's own frontmatter is the only lever, with no floor/ceiling settable from one place.AS-AGENT-005/006/007are therefore the full extent of what static analysis can offer here — there's no missing-global-setting to also flag.
** AS-AGENT-005 fires unconditionally (like AS-HOOK-004's missing-timeout check) — there is no platform default cap on agent turns, so a missing maxTurns genuinely means unbounded. It escalates from LOW to MEDIUM when the field is present but broken (non-integer, zero, negative, or a bool) — that's worse than missing, since the config looks safe but doesn't actually bound anything.
Prompts & Steering (AS-PROMPT-*)¶
CLAUDE.md, skills, and agents are untrusted text that steers the model. Malicious contributions can inject instructions or hide payloads.
| ID | Severity | What it catches |
|---|---|---|
AS-PROMPT-001 |
MEDIUM | Prompt-injection / hidden-unicode indicators in steering files |
AS-PROMPT-002 |
MEDIUM | Instructions tell the agent to disregard turn limits, resist interruption, or run indefinitely |
Framework mapping: OWASP LLM01 (Prompt Injection); AS-PROMPT-002 maps to OWASP LLM10 (Unbounded Consumption).
AS-PROMPT-002 is distinct from AS-PROMPT-001: that check targets untrusted content trying to redirect the agent; this one targets the agent's own authored instructions telling the model to disregard turn limits or resist interruption. maxTurns is a hard harness-level stop regardless of what the prompt says, so this can't actually defeat the cap — but it makes hitting that cap on every single invocation far more likely instead of the agent stopping when the task is genuinely done, and it reads as an attempt to override a safety backstop even where the backstop itself can't be overridden. Deliberately narrow phrasing (specific co-occurring words, not generic "keep iterating until done" language, which is common and benign) to keep false positives low.
Skills (AS-SKILL-*)¶
Skills extend Claude Code's capabilities. Malicious or misconfigured skills can grant shell access, bypass sandboxing, or hide obfuscated payloads.
| ID | Severity | What it catches |
|---|---|---|
AS-SKILL-001 |
CRITICAL | Skill requests write access to agent identity files |
AS-SKILL-002 |
HIGH | Skill has a social-engineering Prerequisites section with pipe-to-shell |
AS-SKILL-003 |
HIGH | Universal-Format skill missing a cryptographic signature |
AS-SKILL-004 |
HIGH | Skill sets permissions.network: true (binary boolean, not a domain allowlist) |
AS-SKILL-005 |
HIGH | Skill declares explicit shell access |
AS-SKILL-006 |
HIGH | Skill risk_tier contradicts declared permissions (risk-tier spoofing) |
AS-SKILL-007 |
CRITICAL | Skill file contains YAML unsafe-execution tags |
AS-SKILL-008 |
HIGH | Skill explicitly disables sandboxed execution |
AS-SKILL-009 |
MEDIUM | Universal-Format skill missing version field (update-drift risk) |
AS-SKILL-010 |
MEDIUM | Skill body contains a standalone base64-encoded block (obfuscated payload) |
AS-SKILL-011 |
MEDIUM | Universal-Format skill missing publisher field (governance gap) |
AS-SKILL-012 |
MEDIUM | Multi-platform skill missing a signature (security metadata lost in translation) |
Framework mapping: OWASP LLM03 (Supply Chain), LLM06 (Excessive Agency), LLM01 (Prompt Injection).
Model sensitivity¶
Some checks flag content whose real-world exploitability depends on which model executes it — prompt injection, social-engineering framing, obfuscated payloads, and injected hook context are all things a frontier model is more likely to resist than a smaller/less-aligned one. These checks are marked model_sensitive: AS-PROMPT-001, AS-PROMPT-002, AS-HOOK-003, AS-SKILL-002, AS-SKILL-010.
Static (default, deterministic, offline):
Bumps model_sensitive findings one severity level (capped at CRITICAL), reflecting that the content is riskier if it ends up running on a smaller model. --model-tier high (the default) leaves severities unchanged. This is a static adjustment based on general model-capability reasoning — it does not test any specific model.
Dynamic (optional, live, costs tokens — aisecscan probe):
export ANTHROPIC_API_KEY=sk-ant-...
aisecscan probe . --model claude-haiku-4-5 --model claude-sonnet-5
Actually sends each model_sensitive finding's content to the named model(s), using your own API key, and has a judge model assess whether the target complied with an embedded instruction rather than just describing it. Reports VULNERABLE / RESISTANT / INCONCLUSIVE per model per finding.
This is a fundamentally different kind of check from everything else in aisecscan: it is non-deterministic (the same content can probe differently across runs), spends tokens, and is never invoked by scan. Treat a probe result as a signal to investigate, not a definitive verdict — a single judge call can be wrong. It exists because a static scanner cannot answer "does this specific model actually fall for this," only "does this content look like something that would try."
AIVSS scoring¶
Every check also carries an OWASP AIVSS-Agentic score, surfaced with aisecscan scan --aivss. This layers agent-specific risk amplification (autonomy, tool use, memory, multi-agent interaction, etc.) on top of Severity — see AIVSS Scoring for the formula, what's official-spec vs. our own mapping, and how to reproduce a number by hand.
Suppressing findings¶
Add an inline directive on the offending line to suppress a specific check:
Or pass --skip-check AS-PERM-001 on the CLI to skip globally for a run.
Roadmap¶
Checks planned for v1.x / v2:
| ID | Description |
|---|---|
AS-ENV-002 |
Code-exec helpers (apiKeyHelper, statusLine, awsCredentialExport) pointing to external scripts |
AS-HOOK-006 |
Unsafe tool_input interpolation in hook commands (shell injection via agent output) |
AS-MCP-006 |
MCP server not on an explicit allowlist |
AS-XFILE-001 |
Project scope re-allows a managed/user-scope deny (cross-file pass) |
AS-XFILE-002 |
Committed permissive settings.local.json not gitignored |
AS-AGENT-003 |
Skill declares both permissions.network: true and write access to a memory-persisted path (ASI06, skill-level companion to AS-HOOK-005) |
AS-AGENT-004 |
Multi-agent config with no message-integrity/authentication between agents (ASI07 Insecure Inter-Agent Communication) |