Skill Indicators
Every point in a skill's risk score traces back to one of the indicators below. This page is the full catalog — grouped by family, with each indicator's severity (static indicators only), default weight, and what it detects.
Weights shown here are shipped defaults. Every weight, and the review/blocked band thresholds, are operator-tunable — see Tune skill risk weights. An indicator firing does not by itself mean a skill is blocked; its weight is only one input to the combined score described in Skill Risk.
How to read this table
- ID — the identifier that appears in a skill's indicator list and in
object.skill.indicatorsfor CEL policy matching. - Severity — assigned only to static indicators (
critical/high/medium/low), reflecting how dangerous the detected pattern is taken at face value. Behavioral indicators don't carry a graded severity — see Behavioral family for why. - Default weight — the point value the indicator contributes toward the 0–100 risk score before any operator override.
- Detects — the specific pattern or condition that fires the indicator.
Shell & script execution
Patterns indicating a skill runs, downloads, obfuscates, or drops executable code.
| ID | Severity | Default weight | Detects |
|---|---|---|---|
SHELL_CURL_PIPE_SH | critical | 40 | curl | sh / bash / zsh — downloads and immediately executes remote content |
SHELL_WGET_PIPE_SH | critical | 40 | Same pattern via wget |
SHELL_BASE64_DECODE_EXEC | critical | 40 | Base64-decoded content piped straight into a shell — obfuscated execution |
BINARY_DROP_EXEC | critical | 40 | Download-then-chmod +x chain, or chmod +x/755 against a file in /tmp |
POWERSHELL_INVOKE_EXPRESSION | high | 40 | PowerShell Invoke-Expression / iex |
UNSAFE_CODE_EVAL | high | 25 | Dynamic code evaluation (eval(, exec(, compile(, __import__(, new Function) |
UNSAFE_DESERIALIZATION | high | 25 | Unsafe deserialization (pickle.load, yaml.unsafe_load, marshal.load, jsonpickle.decode) |
INLINE_INTERPRETER_EXEC | medium | 15 | Inline interpreter invocation (python -c, node -e, bash -c, powershell -Command, ...) |
UNEXPECTED_EXECUTABLE | medium | 15 | A bundle file has the executable bit set but isn't a recognized script type |
SCRIPT_NO_ERROR_HANDLING | medium | 0 | A shell script has no set -e / set -eo pipefail in its first 20 lines |
SCRIPT_WRITES_TO_SKILL_DIR | medium | 0 | A script writes output into the skill's own directory instead of the caller's working directory |
UNICODE_BIDI_CONTROL | critical | 35 | Unicode bidirectional control characters — can render code to look different from what it does |
Credential & secrets access
Patterns indicating a skill reads credential material.
| ID | Severity | Default weight | Detects |
|---|---|---|---|
HARDCODED_CREDENTIALS | high | 35 | An inline API key, password, bearer token, or cloud access key literal |
SECRETS_CREDENTIAL_FILE | critical | 35 | Access to a well-known credential file (~/.aws/credentials, ~/.docker/config.json, ~/.npmrc, ~/.netrc, etc.) |
SECRETS_KEYCHAIN_ACCESS | critical | 35 | macOS Keychain or platform credential-store access |
SECRETS_GIT_CREDENTIALS | critical | 35 | Git credential helper configuration or .git-credentials access |
TOUCHES_KUBECONFIG | high | 20 | Reference to KUBECONFIG or ~/.kube/config |
TOUCHES_SSH_KEYS | high | 20 | Reference to SSH private key material |
Network & exfiltration
Patterns indicating a skill reaches out to the network, or moves data toward the network.
| ID | Severity | Default weight | Detects |
|---|---|---|---|
DATA_EXFIL_PATTERN | critical | 35 | File contents piped directly into a network client (cat file | curl ...) |
NET_EXFIL_SINK | critical | 25 | A known anonymous-drop destination (webhook.site, requestbin, ngrok, pastebin, a raw Slack/Discord webhook URL) |
HARDCODED_CREDENTIALS (network context) | — | — | (see Credential & secrets access above) |
SUSPICIOUS_ENV_EXFIL | high | 20 | Environment variables piped into a network client (env | curl ...) |
NET_INSECURE_HTTP | high | 25 | A plaintext http:// URL (excluding localhost) |
NET_RAW_IP_DEST | high | 25 | A raw IP-literal destination, bypassing DNS-based egress controls |
NETWORK_ACCESS_PATTERN | high | 0 | Any HTTP client call (curl, wget, requests.*, urllib, Net::HTTP, ...) |
MCP_TOOL_REFERENCE | medium | 0 | A reference to another MCP tool, extending the skill's effective reach beyond its own declared scope |
Prompt injection & instruction manipulation
Patterns indicating a skill's own content tries to manipulate the agent that loads it.
| ID | Severity | Default weight | Detects |
|---|---|---|---|
PROMPT_INJECTION_PATTERN | critical | 35 | Classic injection phrasing ("ignore previous instructions," "do not tell the user," "jailbreak," ...) |
INSTRUCTION_MANIPULATION | critical | 35 | Directives to bypass safety/security checks, or conditional instructions to lie if asked about the skill's behavior |
SKILL_FRONTMATTER_XML_TAGS | medium | 10 | XML/HTML tags in the name or description frontmatter fields — a possible injection vector if rendered verbatim into a system prompt |
Privilege escalation & tampering
Patterns indicating a skill elevates its own privileges or disables host security controls.
| ID | Severity | Default weight | Detects |
|---|---|---|---|
SECURITY_DISABLE_FIREWALL | critical | 40 | A command that disables the host firewall (ufw disable, iptables -F, pfctl -d, ...) |
SECURITY_DISABLE_GATEKEEPER | critical | 40 | A command that disables macOS Gatekeeper or SIP |
SECURITY_DISABLE_AV | critical | 40 | A command that disables or tampers with antivirus/EDR (Windows Defender, CrowdStrike, SentinelOne) |
PRIVILEGE_ESCALATION_SUDO | critical | 35 | sudo, su, or doas |
PRIVILEGE_ESCALATION_SETUID | critical | 35 | chmod +s, an octal mode granting setuid/setgid, chown root, or setcap |
PRIVILEGE_ESCALATION_SYSTEM_WRITE | critical | 35 | A write to a protected system path (/etc/, /Library/LaunchDaemons, /etc/systemd/system, ...) |
Persistence
Patterns indicating a skill installs itself to survive beyond the current session.
| ID | Severity | Default weight | Detects |
|---|---|---|---|
PERSISTENCE_SHELL_PROFILE | critical | 35 | A write to a shell profile (.bashrc, .zshrc, .profile, ...) |
PERSISTENCE_SCHEDULED_TASK | critical | 35 | Scheduled-task or service installation (crontab, launchctl, systemctl enable, ...) |
PERSISTENCE_GIT_HOOKS | critical | 35 | Modification of .git/hooks/ or core.hooksPath |
ENV_LOADER_INJECTION | critical | 30 | LD_PRELOAD / DYLD_INSERT_LIBRARIES loader injection |
ENV_PATH_HIJACK | high | 30 | PATH rewritten to prepend a temp or hidden directory |
Container & cluster
Patterns indicating container-escape techniques or unpinned cluster-changing commands.
| ID | Severity | Default weight | Detects |
|---|---|---|---|
CONTAINER_ESCAPE_DOCKER_SOCK | high | 25 | Access to the Docker socket (/var/run/docker.sock) |
CONTAINER_ESCAPE_NAMESPACE | high | 25 | Namespace-escape tooling (nsenter, unshare, --privileged) |
CONTAINER_ESCAPE_HOST_MOUNT | high | 25 | A host-root filesystem mount into a container |
REMOTE_MANIFEST_APPLY | high | 20 | kubectl apply/create -f against a remote URL, unpinned |
REMOTE_HELM_INSTALL | high | 20 | helm install/upgrade against a remote chart URL, unpinned |
Dependency hygiene
Patterns indicating a skill's dependency manifests are unpinned or unverifiable.
| ID | Severity | Default weight | Detects |
|---|---|---|---|
DEPS_UNPINNED | medium | 15 | A dependency manifest using a version range, *, or latest instead of a pinned version |
DEPS_NO_LOCKFILE | medium | 15 | A dependency manifest present with no corresponding lockfile in the bundle |
Spec conformance
Structural checks against the Agent Skills specification's frontmatter schema — these are hygiene indicators (see the note below), not security threat signals, so all four default to weight 0.
| ID | Severity | Default weight | Detects |
|---|---|---|---|
SKILL_MD_MISSING | critical | 0 | SKILL.md absent from the bundle entirely |
SKILL_NAME_FORMAT_INVALID | low | 0 | name isn't 1–64 lowercase alphanumeric characters and hyphens |
SKILL_NAME_DIR_MISMATCH | low | 0 | name doesn't match the bundle's directory name |
SKILL_DESCRIPTION_INVALID | low | 0 | description missing or over 1024 characters |
Indicators are also tagged with a kind of security or hygiene in the Risk Scoring settings UI (Settings → Security & Privacy → Risk Scoring → Skills — see Tune risk weights). Hygiene indicators — the four above, plus SCRIPT_NO_ERROR_HANDLING, SCRIPT_WRITES_TO_SKILL_DIR, NETWORK_ACCESS_PATTERN, and MCP_TOOL_REFERENCE from the tables above — are spec-conformance and code-quality signals rather than security threats, so they default to weight 0: detected and shown as evidence, but not scored, unless an operator re-enables them.
Two additional indicator IDs, SYMLINK_OUTSIDE_ROOT and SYMLINK_INTERNAL, are reserved in the weight table for symlink-escape detection but do not currently fire — bundle content is captured as a flat file map with no filesystem symlink metadata. They're listed here only so an operator tuning weights sees the full space; they carry no live risk contribution today.
Behavioral family
Behavioral indicators fire from what agents were observed doing while a skill was active, aggregated over a rolling window — not from the skill's declared content. They're the compensating control for content that reads as safe but behaves otherwise (see Divergence and drift).
Behavioral indicators don't carry a graded severity the way static ones do — there's no equivalent to "this pattern is obviously critical at a glance," because the same underlying action (say, contacting a new host) can be perfectly legitimate or a live exfiltration attempt depending entirely on context the aggregate signal doesn't carry alone. Their entire risk signal is expressed through weight, and several have an operator-tunable trigger threshold.
| ID | Default weight | Tunable threshold | Detects |
|---|---|---|---|
BEHAV_STATIC_DIVERGENCE | 35 | — | Observed behavior (network, exec, credential access, or writes) falls outside every category the static scan indicated — the strongest single signal, since it can't be gamed by patient behavior alone |
BEHAV_CRED_ACCESS | 35 | — | An attributed tool call read a credential-shaped path (kubeconfig, SSH keys, .env, cloud credentials) |
BEHAV_EXFIL_COMBO | 35 | — | Credential access and an unrecognized egress host observed in the same window — the two individually-moderate signals compound |
BEHAV_EGRESS_UNKNOWN_HOST | 30 | — | A contacted host isn't present in the AI tool catalogue / egress allowlist |
BEHAV_TOOL_SCOPE_VIOLATION | 30 | — | The skill declared an allowed-tools scope in its frontmatter, and an attributed call used a tool outside it |
BEHAV_DRIFT | 25 | — | New tool categories or new hosts appear versus this digest's own established baseline (see Divergence and drift) |
BEHAV_WRITE_BREADTH | 20 | 5 distinct write targets | The skill wrote to more distinct resources in the window than the threshold |
BEHAV_POLICY_FRICTION | 15 | 3 denials/approvals | Attributed calls were denied or routed to human approval more than the threshold in the window |
BEHAV_TOOL_BREADTH | 15 | 4 distinct tools | The skill used more distinct tools in the window than the threshold |
BEHAV_CALL_RATE | 10 | 20 calls/invocation | Calls-per-invocation exceeded the threshold — unusually chatty relative to a typical single activation |