Back to skill

Security audit

Improvement Gate

Security checks for vulnerabilities and agentic risk

Overview

This skill mostly matches a quality-gate workflow, but its scripts can automatically restore and write files in paths controlled by input artifacts, while the documentation says the gate only verifies changes.

Review before installing. Use only in a tightly scoped workspace with trusted ranking and execution artifacts, and do not run it with access to sensitive files until rollback path containment and review ID validation are fixed.

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 (2)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/gate.py:333
Finding

Artifact-Controlled Rollback Allows Arbitrary File Replacement

Content
View full analysis

Vulnerability Details

File Location: scripts/gate.py:333-348
Vulnerability Type: Unrestricted filesystem path use during backup restoration
Risk Level: High

Vulnerable Code

python
def maybe_restore(execution_artifact: dict) -> dict:
    result = execution_artifact.get("result", {})
    rollback = result.get("rollback_pointer")
    if not rollback or not result.get("modified"):
        return {"attempted": False, "success": False, "reason": "no modified file to restore"}
    backup_path = Path(rollback["backup_path"]).expanduser().resolve()
    target_path = Path(rollback["target_path"]).expanduser().resolve()
    restore_backup(backup_path, target_path)
    return {
        "attempted": True,
        "success": True,
        "backup_path": str(backup_path),
        "target_path": str(target_path),
    }

Rollback is automatically invoked from several decision paths, including required gate failures:

python
if not layer_verdict["all_passed"]:
    rollback_outcome = {"attempted": False, "success": False, "reason": "not_needed"}
    if execution_result.get("modified"):
        decision = "revert"
        rollback_outcome = maybe_restore(execution_artifact)

Technical Analysis

The backup_path and target_path values are obtained directly from the supplied execution artifact. Calling resolve() canonicalizes a path but does not establish that it is authorized.

The implementation does not verify that:

  • The target is inside an approved project or workspace directory.
  • The target matches the candidate's declared target_path.
  • The backup was created by the trusted executor for the current run.
  • The target or its parent directories do not traverse symbolic links.
  • The backup is a regular file rather than an unexpected filesystem object.

Consequently, an attacker who can provide or alter an execution artifact can choose both the source and destination of the restoration operation. When the process runs with ...[truncated 1529 chars]

Remediation
View remediation

Remediation Suggestions

  1. Configure an explicit, trusted workspace root and require both rollback paths to remain within approved directories.
  2. Use canonical containment checks after resolving paths:
python
workspace = configured_workspace.resolve()
target = Path(rollback["target_path"]).resolve(strict=False)

if target != workspace and workspace not in target.parents:
    raise ValueError("Rollback target is outside the approved workspace")
  1. Require the canonical rollback target to exactly match the canonical target recorded in the trusted candidate or executor state.
  2. Do not trust a backup path supplied solely through the execution artifact. Record backups in a trusted server-side registry and reference them using an opaque identifier.
  3. Require backup files to reside in a dedicated backup directory with restrictive permissions.
  4. Reject symbolic links and verify relevant filesystem objects using lstat() immediately before restoration.
  5. Restore files atomically using a securely created temporary file in the destination directory, followed by an atomic replacement.
  6. Run the gate under a least-privileged account that cannot write system, credential, or deployment-control files.
  7. Add negative tests for absolute paths, .. traversal, symlink escapes, mismatched candidate targets, and unregistered backup files.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/review.py:65
Finding

Unvalidated Review Identifiers Permit Path Traversal in Review-State Operations

Content
View full analysis

Vulnerability Details

File Locations: scripts/review.py:65-80; scripts/gate.py:486-489; identifier validation at scripts/gate.py:66-67
Vulnerability Type: Path traversal through attacker-controlled filenames
Risk Level: Medium

Vulnerable Code

The review completion function directly incorporates the CLI-controlled request identifier into a path:

python
reviews_dir = _reviews_dir(state_root)
review_path = reviews_dir / f"{request_id}.json"
if not review_path.exists():
    raise FileNotFoundError(f"Review request not found: {review_path}")

data = read_json(review_path)
if data.get("status") == "completed":
    raise ValueError(f"Review {request_id} is already completed (decision: {data.get('decision')})")

data["status"] = "completed"
data["decision"] = decision
data["reviewer"] = reviewer
data["comments"] = reason
data["completed_at"] = utc_now_iso()
write_json(review_path, data)

Review request creation similarly derives a filename from candidate-controlled data:

python
review_dir = state_root / "state" / "reviews"
review_dir.mkdir(parents=True, exist_ok=True)
review_path = review_dir / f"{human_review_request['request_id']}.json"
write_json(review_path, human_review_request)

The request identifier is derived from the candidate ID:

python
"request_id": f"review-{candidate.get('id', 'unknown')}",

Candidate schema validation only requires a non-empty string:

python
if not isinstance(candidate.get("id"), str) or not candidate["id"]:
    errors.append("id must be a non-empty string")

Technical Analysis

Neither request_id nor candidate id is restricted to a filename-safe character set. Path separators, traversal components, and platform-specific special path syntax are accepted.

Joining an attacker-controlled identifier with reviews_dir does not guarantee containment. The implementation also does not resolve the resulting path and verify that it remains beneath the canonical review di ...[truncated 2358 chars]

Remediation
View remediation

Remediation Suggestions

  1. Restrict candidate IDs and request IDs to an explicit allowlist:
python
import re

_ID_PATTERN = re.compile(r"^[A-Za-z0-9_-]{1,128}$")

if not _ID_PATTERN.fullmatch(request_id):
    raise ValueError("Invalid review request ID")
  1. Apply the same validation when candidate IDs first enter the system and again at every filesystem boundary.
  2. Resolve the review directory and destination and enforce containment:
python
reviews_dir = _reviews_dir(state_root).resolve()
review_path = (reviews_dir / f"{request_id}.json").resolve(strict=False)

if review_path.parent != reviews_dir:
    raise ValueError("Review path escapes the review directory")
  1. Reject identifiers containing /, \, .., null characters, drive prefixes, or platform-specific reserved names.
  2. Prefer generated opaque identifiers, such as UUIDs, rather than deriving filenames from externally supplied candidate IDs.
  3. Use atomic, no-follow file operations and reject symbolic-link targets.
  4. Do not accept arbitrary existing JSON files as review requests. Validate the complete review schema and ensure the document's internal request_id exactly matches the requested identifier.
  5. Add traversal tests covering POSIX separators, Windows separators, absolute paths, nested traversal, encoded separators, symlinks, and identifiers exceeding the maximum length.
Vulnerability Patterns
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (7)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The declared purpose presents this as an end-to-end post-change quality gate, yet the available behavior appears limited to manual review queue management and CLI state handling. In a security-sensitive pipeline, that gap can create a control failure: downstream systems or humans may assume a candidate passed compile/regression/review gates even though no such validation was performed.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The declared purpose presents this as an end-to-end post-change quality gate, yet the available behavior appears limited to manual review queue management and CLI state handling. In a security-sensitive pipeline, that gap can create a control failure: downstream systems or humans may assume a candidate passed compile/regression/review gates even though no such validation was performed.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding

Without declared permissions the skill's intent is opaque and cannot be validated.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The primary manifest description is written entirely in Chinese, while the rest of the document is mixed-language, and there is no statement that users may choose their preferred language or locale. This can constitute a language-policy issue because the skill presentation implicitly constrains language without opt-in or justification.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The manifest trigger list includes generic phrases such as "quality gate", "validate candidate", "gate check", and especially "human review" without contextual constraints. These phrases are broad enough to match ordinary discussion or unrelated workflows, and the file does not provide trigger scope limits or negative examples in the trigger definition itself.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

This code writes multiple receipt and review-request JSON files and appends pending state as part of its control flow, but there is no confirmation prompt or user-facing disclosure at the point of mutation. While the script's internal comments describe the behavior for developers, they do not provide an explicit runtime warning to users that execution will persist files under the state tree.

Content

No source excerpt is available for this finding.

Skill Enumeration

Medium
Category
Agent Snooping
Confidence
80% confidence
Finding

Skill enumerates or reads other installed skills. Access to other skills' SKILL.md files or the skills directory reveals prompt instructions, capabilities, and secrets that should be invisible to peer skills.

Content

Scanner excerpt · tests/test_gate.py (reported line 47)May include surrounding context.

python
"execution_plan": {"steps": []},
        "recommendation": "accept_for_execution",
        "panel_result": {"cognitive_label": "CONSENSUS"},
        "target_path": "skills/example/SKILL.md",
    }
    base.update(overrides)
    return base