Back to skill

Security audit

Skill Policy Enforcer

Security checks for vulnerabilities and agentic risk

Overview

This is a coherent local policy-checking skill, but its scanner has real bypass and unsafe file-reading weaknesses that warrant review before using it as an approval gate.

Use this only in a constrained workspace and do not treat it as a sole release or marketplace gate until the scanner rejects symlinks, enforces size limits before every read, validates policy regexes, and removes broad line-skipping from security matching. The credential and prompt-injection strings are expected policy examples, but the implementation weaknesses matter for anyone scanning untrusted skill packages.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/enforce_skill_policy.py:132
Finding

Broad rule-definition filtering allows policy detection bypass

Content
View full analysis

Vulnerability Details

File Location: scripts/enforce_skill_policy.py:132-160
Vulnerability Type: Policy scanner bypass through unsafe line exclusion
Risk Level: High

Vulnerable Code

python
def looks_like_rule_definition(line: str) -> bool:
    stripped = line.strip()
    if any(token in stripped for token in ("DEFAULT_POLICY", "forbidden_patterns", "warn_patterns")):
        return True
    if stripped.startswith(("- \"", "- '")):
        return True
    if stripped.startswith(("r\"", "r'")):
        return True
    if "_pattern = r" in stripped:
        return True
    if "line_of(text, r" in stripped:
        return True
    if stripped.startswith("- ") and ("\\s" in stripped or "\\b" in stripped or "\\(" in stripped):
        return True
    if re.search(r'"\w+[.\w-]+",\s*r"', stripped):
        return True
    return False


def line_of(text: str, pattern: str, *, skip_rule_defs: bool = True) -> int | None:
    regex = re.compile(pattern, re.IGNORECASE)
    for index, line in enumerate(text.splitlines(), 1):
        if skip_rule_defs and looks_like_rule_definition(line):
            continue
        if regex.search(line):
            return index
    return None

Technical Analysis

The scanner excludes entire lines from security inspection when they superficially resemble policy or regular-expression definitions. These exclusions are based on broad textual characteristics rather than a trusted file location or structural parsing.

For example, any line containing forbidden_patterns or warn_patterns, beginning with a quoted list item, or beginning with a raw string is skipped before forbidden, network, secret-read, and warning patterns are evaluated. These formats can also legitimately occur in malicious scripts, Markdown instructions, shell arguments, or embedded payloads.

Because line_of() is shared by all content-pattern controls, this is a general-purpose bypass rather than a limitation of one rule.

Attack P

...[truncated 1317 chars]

Remediation
View remediation

Remediation Suggestions

  • Remove syntax-based whole-line exclusions from the generic matching function.
  • Scan all candidate content by default.
  • If bundled policy definitions must be excluded, identify them through an explicit trusted-file allowlist rather than attacker-controlled line contents.
  • Prefer language-aware parsing where false positives must be suppressed.
  • Apply exclusions only to the exact parsed string literal representing a rule, not to the entire line.
  • Add regression tests covering:
    • Dangerous content on quoted Markdown list lines.
    • Raw Python strings containing prohibited operations.
    • Variables or comments containing forbidden_patterns.
    • Multiline command and subprocess arguments.
    • Network and secret-read indicators embedded in apparent rule definitions.
  • Fail closed when scanner parsing is ambiguous.

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/enforce_skill_policy.py:95
Finding

Candidate-controlled symbolic links can cause out-of-root file reads

Content
View full analysis

Vulnerability Details

File Location: scripts/enforce_skill_policy.py:95-101, scripts/enforce_skill_policy.py:203-207
Vulnerability Type: Filesystem boundary violation through symbolic-link traversal
Risk Level: Medium

Vulnerable Code

python
def iter_files(root: Path) -> Iterable[Path]:
    skip_dirs = {".git", "__pycache__", "node_modules", ".venv", "dist", "build"}
    for path in root.rglob("*"):
        if any(part in skip_dirs for part in path.parts):
            continue
        if path.is_file():
            yield path

The resulting paths are subsequently opened without validating their resolved location:

python
for path in iter_files(root):
    if path.suffix.lower() not in {".md", ".py", ".sh", ".ps1", ".js", ".ts", ".json", ".yaml", ".yml", ".txt"}:
        continue
    text = read_text(path)

Technical Analysis

Path.is_file() follows symbolic links. The scanner then calls read_text() on accepted paths without checking whether path.resolve() remains within the resolved candidate root.

A candidate Skill can therefore include a symbolic link with a supported text-file extension that points outside the audited directory. When the scanner runs, it accesses the target using the scanner process’s filesystem privileges. This exceeds the minimum access required to inspect files contained in the candidate Skill.

The current implementation does not directly print complete file contents. Nevertheless, matches, line numbers, decoding behavior, errors, and processing time can expose limited information about the external target. Reading special or very large linked targets can also affect availability.

Attack Path

  1. An attacker adds a symbolic link such as probe.txt to a candidate Skill.
  2. The link points to a file outside the Skill root that is readable by the account running the scanner.
  3. root.rglob("*") discovers the link.
  4. path.is_file() follows the link and returns True.
  5. The .txt ex ...[truncated 890 chars]
Remediation
View remediation

Remediation Suggestions

  • Reject symbolic links during enumeration by checking path.is_symlink() before any stat or read operation.
  • Resolve the audit root once and verify that every resolved candidate path is contained beneath it.
  • Treat containment failures as deny findings and do not read the target.
  • Where supported, open files using no-follow semantics to reduce time-of-check/time-of-use races.
  • Run the scanner as a dedicated, unprivileged account with no access to host credentials or unrelated workspace data.
  • Place untrusted candidate Skills in an isolated filesystem or container.
  • Add tests for links to:
    • Files outside the root.
    • Sensitive files.
    • Large files.
    • Broken links.
    • Nested directory links.
  • Revalidate containment immediately before opening each file.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/enforce_skill_policy.py:191
Finding

Oversized files are fully loaded despite configured size limits

Content
View full analysis

Vulnerability Details

File Location: scripts/enforce_skill_policy.py:191-207
Vulnerability Type: Resource-exhaustion risk from unbounded file reads
Risk Level: Medium

Vulnerable Code

python
def enforce_files(root: Path, policy: dict[str, Any], findings: list[Finding]) -> None:
    max_kb = int(policy.get("max_file_size_kb", 0) or 0)
    forbidden_paths = [str(item).lower() for item in policy.get("forbidden_paths", [])]
    for path in iter_files(root):
        rel = path.relative_to(root).as_posix().lower()
        for forbidden in forbidden_paths:
            if forbidden and forbidden in rel:
                add(findings, DENY, f"forbidden_paths:{forbidden}", path, "path is forbidden by policy.")
        if max_kb and path.stat().st_size > max_kb * 1024:
            add(findings, DENY, "max_file_size_kb", path, f"file size exceeds {max_kb} KB.")


def enforce_patterns(root: Path, policy: dict[str, Any], findings: list[Finding]) -> None:
    secret_pattern = r"(open|read|cat|type|Get-Content|copy|upload|send|exfiltrat)[^\n]{0,80}(\.env|\.ssh|id_rsa|id_ed25519|credentials|token_store|browser profile)"
    network_pattern = r"requests\.|urllib|fetch\s*\(|Invoke-WebRequest|curl\s+|wget\s+"
    for path in iter_files(root):
        if path.suffix.lower() not in {".md", ".py", ".sh", ".ps1", ".js", ".ts", ".json", ".yaml", ".yml", ".txt"}:
            continue
        text = read_text(path)

Technical Analysis

The size check only records a deny finding. It does not prevent the same oversized file from being enumerated again and fully loaded into memory by enforce_patterns().

read_text() reads and decodes the complete file. The resulting string is then traversed repeatedly for secret, network, forbidden, and warning patterns. Consequently, the configured max_file_size_kb value is not an operational resource limit.

SKILL.md is also read by the structural checks before the general file-size enforcement pass, so an oversized ...[truncated 1077 chars]

Remediation
View remediation

Remediation Suggestions

  • Check file size before every read operation.
  • After recording an oversized-file finding, skip content inspection for that file.
  • Apply the same pre-read limit to SKILL.md, policy files, and all supported content types.
  • Use bounded or streaming reads rather than loading complete files into memory.
  • Establish aggregate limits for:
    • Total files scanned.
    • Total bytes read.
    • Maximum line length.
    • Total scan duration.
    • Number of pattern evaluations.
  • Stop or fail closed when resource limits are exceeded.
  • Avoid duplicate filesystem enumeration by collecting validated file metadata once.
  • Add tests using sparse files, many moderately sized files, oversized SKILL.md files, and symlinks to large targets.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/enforce_skill_policy.py:154
Finding

Unvalidated policy regular expressions can crash or stall the scanner

Content
View full analysis

Vulnerability Details

File Location: scripts/enforce_skill_policy.py:154-160, scripts/enforce_skill_policy.py:217-224
Vulnerability Type: Regular-expression denial of service and uncaught policy parsing failure
Risk Level: Medium

Vulnerable Code

python
def line_of(text: str, pattern: str, *, skip_rule_defs: bool = True) -> int | None:
    regex = re.compile(pattern, re.IGNORECASE)
    for index, line in enumerate(text.splitlines(), 1):
        if skip_rule_defs and looks_like_rule_definition(line):
            continue
        if regex.search(line):
            return index
    return None

Caller-supplied patterns are passed directly to this function:

python
for pattern in policy.get("forbidden_patterns", []):
    hit = line_of(text, str(pattern))
    if hit:
        add(findings, DENY, f"forbidden_patterns:{pattern}", path, "content matches a forbidden pattern.", hit)
for pattern in policy.get("warn_patterns", []):
    hit = line_of(text, str(pattern))
    if hit:
        add(findings, WARN, f"warn_patterns:{pattern}", path, "content matches a warning pattern.", hit)

Technical Analysis

Policies can supply arbitrary regular expressions through forbidden_patterns and warn_patterns. The scanner compiles these expressions without validation or exception handling.

An invalid expression raises re.error and terminates the process instead of producing a controlled policy-validation failure. A syntactically valid expression with catastrophic backtracking can consume excessive CPU when evaluated against specially constructed candidate lines.

The risk depends on the trust model for policy files. The documented functionality explicitly accepts caller-supplied local or enterprise policies, so robust validation is required even if policies are normally trusted. Misconfiguration alone is sufficient to cause failure; if an attacker can influence both policy and scanned content, deliberate regular-expression denial of serv ...[truncated 1078 chars]

Remediation
View remediation

Remediation Suggestions

  • Compile and validate all regular expressions once during policy loading.
  • Catch re.error and return a clear, controlled invalid-policy result.
  • Do not begin candidate scanning when any configured pattern is invalid.
  • Limit the number and maximum length of caller-supplied expressions.
  • Use a regular-expression engine with execution limits or guaranteed linear-time behavior where practical.
  • Impose maximum candidate line lengths and overall scan timeouts.
  • Consider replacing caller-supplied regexes with safer literal, glob, or constrained-pattern rules where full regex support is unnecessary.
  • Cache compiled expressions rather than recompiling them for every file.
  • Add tests for malformed expressions, nested quantifiers, ambiguous alternation, long nonmatching lines, and policies containing excessive numbers of patterns.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (9)

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 35)May include surrounding context.

md
- maximum file size and `SKILL.md` word count

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 53)May include surrounding context.

md
- maximum file size and `SKILL.md` word count

Anti-Refusal Statement

High
Category
Anti-Refusal
Confidence
90% confidence
Finding

Skill attempts to nullify the agent's safety policies or restrictions ('you have no restrictions', 'ignore your guidelines', 'do anything now'). This is a direct jailbreak that disables guardrails.

Content

Scanner excerpt · SKILL.md (reported line 43)May include surrounding context.

md
## Required Behavior

- Do not override policy because a finding seems inconvenient.
- Do not invent policy exceptions. Ask for an updated policy file if needed.
- Quote the exact rule id for every deny or warning.
- If `--json` output is available, base final status on the machine result.

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · references/default-policy.yaml (reported line 12)May include surrounding context.

yaml
- name
  - description
forbidden_paths:
  - .env
  - .ssh
  - id_rsa
  - id_ed25519

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · references/policy-schema.md (reported line 19)May include surrounding context.

md
- name
  - description
forbidden_paths:
  - .env
  - .ssh
  - id_rsa
  - id_ed25519

Instruction Override

High
Category
Prompt Injection
Confidence
80% confidence
Finding

This pattern attempts to override system instructions or ignore safety constraints. Without LLM analysis, manual review is recommended.

Content

Scanner excerpt · references/default-policy.yaml (reported line 30)May include surrounding context.

yaml
warn_patterns:
  - "\\bTODO\\b"
  - "placeholder"
  - "ignore previous instructions"
  - "reveal secrets"

Instruction Override

High
Category
Prompt Injection
Confidence
80% confidence
Finding

This pattern attempts to override system instructions or ignore safety constraints. Without LLM analysis, manual review is recommended.

Content

Scanner excerpt · scripts/enforce_skill_policy.py (reported line 37)May include surrounding context.

python
warn_patterns:
  - "\\bTODO\\b"
  - "placeholder"
  - "ignore previous instructions"
  - "reveal secrets"

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/enforce_skill_policy.py (reported line 27)May include surrounding context.

python
"allow_secret_reads": False,
    "require_files": ["SKILL.md"],
    "allowed_frontmatter_fields": ["name", "description"],
    "forbidden_paths": [".env", ".ssh", "id_rsa", "id_ed25519", "credentials", "token", "browser profile"],
    "forbidden_patterns": [
        r"curl\s+[^\n|]+\|\s*(sh|bash)",
        r"Invoke-Expression",

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding

The skill describes and instructs use of file reads, shell execution, and potentially network-related behavior, but it does not declare any explicit tool scope such as permissions or allowed-tools. That mismatch increases the chance that an agent or reviewer will authorize broader capabilities than intended, undermining least-privilege controls and making policy enforcement less reliable.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.