Back to skill

Security audit

Restart Recovery

Security checks for vulnerabilities and agentic risk

Overview

This restart-recovery skill is mostly purpose-aligned, but its checkpoint script can write outside the intended checkpoint directory and its resume path does not validate checkpoints as documented.

Review this skill before installing. It is not showing exfiltration or hidden remote code, but only use it in trusted workspaces and avoid running its checkpoint tool with untrusted workflow IDs or untrusted checkpoint files until path confinement and resume validation are fixed.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/checkpoint_tool.py:18
Finding

Path Traversal Enables Writes Outside the Checkpoint Directory

Content
View full analysis

Vulnerability Details

File Location: scripts/checkpoint_tool.py, lines 14-23, 37-57, and 145-147
Vulnerability Type: Path traversal and arbitrary file overwrite
Risk Level: High

Vulnerable Code

python
ROOT = Path.cwd() / "memory" / "checkpoints"
ROOT.mkdir(parents=True, exist_ok=True)


def cp_path(workflow_id: str) -> Path:
    return ROOT / f"{workflow_id}.json"


def lock_path(workflow_id: str) -> Path:
    return ROOT / f"{workflow_id}.lock"
python
def cmd_start(args):
    p = cp_path(args.workflow)
    if p.exists() and not args.force:
        raise SystemExit(f"checkpoint exists: {p} (use --force)")
    obj = {
        "schemaVersion": "1.0",
        "workflowId": args.workflow,
        "agentId": args.agent,
        "status": "in_progress",
        "currentStep": args.step,
        "completedSteps": [],
        "remainingSteps": args.remaining,
        "handoffTo": args.handoff,
        "notes": args.notes or "",
        "idempotencyKey": args.idempotency,
        "updatedAt": now_iso(),
        "expiresAt": (datetime.now(timezone.utc) + timedelta(days=args.ttl_days)).isoformat(),
    }
    obj["checkpointHash"] = calc_hash({k: v for k, v in obj.items() if k != "checkpointHash"})
    atomic_write(p, obj)
    print(p)
python
s.add_argument("--workflow", required=True)
...
s.add_argument("--force", action="store_true")

Technical Analysis

The value supplied through --workflow is inserted directly into a filesystem path without validating that it is a simple identifier. Python's pathlib preserves traversal components such as ../; an absolute path component can also replace the intended base path during path composition.

Consequently, cp_path() does not guarantee that the resulting checkpoint path remains below memory/checkpoints. The same defect affects lock_path(). The atomic_write() operation creates a temp ...[truncated 2049 chars]

Remediation
View remediation

Remediation Suggestions

  • Restrict workflow identifiers to a conservative allowlist, such as ^[A-Za-z0-9_-]+$.

  • Explicitly reject absolute paths, path separators, . and .. components, and empty identifiers.

  • Resolve every generated destination and verify that it remains under the resolved checkpoint root before reading or writing:

    python
    import re
    
    WORKFLOW_ID = re.compile(r"^[A-Za-z0-9_-]+$")
    
    def safe_path(workflow_id: str, suffix: str) -> Path:
        if not WORKFLOW_ID.fullmatch(workflow_id):
            raise ValueError("Invalid workflow identifier")
    
        root = ROOT.resolve()
        destination = (root / f"{workflow_id}{suffix}").resolve()
    
        if destination.parent != root:
            raise ValueError("Checkpoint path escapes the checkpoint directory")
    
        return destination
    
  • Apply the same validation to checkpoint and lock paths.

  • Revalidate workflowId values read from checkpoint files rather than trusting persisted data.

  • Avoid exposing overwrite behavior unless it is operationally necessary. If replacement is needed, verify that the destination is a valid checkpoint owned by the expected workflow before replacing it.

  • Add tests using ../, nested paths, absolute paths, platform-specific separators, symbolic links, and existing external files.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/checkpoint_tool.py:100
Finding

Resume Accepts Checkpoints Without Schema or Integrity Validation

Content
View full analysis

Vulnerability Details

File Location: scripts/checkpoint_tool.py, lines 100-121
Vulnerability Type: Untrusted checkpoint deserialization and missing integrity validation
Risk Level: Medium

Vulnerable Code

python
def cmd_resume(args):
    now = datetime.now(timezone.utc)
    for p in sorted(ROOT.glob("*.json")):
        if p.name == "CHECKPOINT_SCHEMA.json":
            continue
        obj = load(p)
        if obj.get("status") not in {"in_progress", "blocked"}:
            continue
        exp = obj.get("expiresAt")
        if exp:
            try:
                if datetime.fromisoformat(exp) < now:
                    continue
            except Exception:
                pass
        l = lock_path(obj["workflowId"])
        if l.exists() and not args.steal_lock:
            continue
        l.write_text(now_iso(), encoding="utf-8")
        print(json.dumps(obj, indent=2))
        return
    print("NO_CHECKPOINT")

The project documentation promises validation that is not implemented:

md
On startup, check `memory/checkpoints/*.json` for unfinished workflows. If found, acquire resume lock, validate checkpoint schema/hash, and continue from the last completed idempotent step.

The bundled schema is also permissive:

json
"additionalProperties": true

Technical Analysis

The tool calculates and stores a SHA-256 checkpointHash when creating or updating a checkpoint, but cmd_resume() never recomputes or compares that hash. It also never loads or applies the bundled JSON schema.

Any syntactically valid JSON object with an in_progress or blocked status may therefore be treated as resumable. Required properties, field types, workflow identifier safety, timestamp validity, and unexpected properties are not comprehensively checked. Invalid expiration timestamps are silently ignored, allowing malformed expiration data to bypass expiry f ...[truncated 2174 chars]

Remediation
View remediation

Remediation Suggestions

  • Validate every loaded checkpoint against references/checkpoint-schema.json before reading operational fields or creating a lock.
  • Change the schema to reject unspecified properties by setting additionalProperties to false.
  • Add minimum lengths, maximum lengths, identifier patterns, and array-size bounds for attacker-controlled fields.
  • Recompute checkpointHash over all fields except the hash itself and compare it with the stored value using hmac.compare_digest().
  • Reject checkpoints with missing hashes, malformed timestamps, invalid expiration values, unsupported schema versions, or unsafe workflow identifiers.
  • Treat notes, step names, handoff values, and other free-text fields as untrusted data rather than executable instructions.
  • If protection against malicious file modification is required, replace the unkeyed hash with an HMAC whose key is stored outside the checkpoint directory and protected with appropriate permissions.
  • Acquire locks atomically using exclusive file creation rather than checking existence and then writing in separate operations.
  • Fail closed on malformed checkpoint files and report validation errors instead of silently accepting or skipping security-relevant parsing failures.
  • Add tests covering modified hashes, omitted required fields, unexpected properties, invalid timestamps, expired checkpoints, unsafe workflow identifiers, and maliciously altered workflow state.
Vulnerability Patterns
  • 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
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (2)

Tp4

High
Category
MCP Tool Poisoning
Confidence
90% confidence
Finding

The skill claims restart recovery, stale-checkpoint monitoring, wake/resume handoff, schema/hash validation, and resume locking, but the described behavior appears incomplete or only partially implemented. When recovery logic is overstated, operators may rely on safety properties that do not actually exist, leading to duplicate side effects, unsafe resume behavior, stale state reuse, or continuation from an incorrect checkpoint after restart.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill explicitly instructs use of local scripts and checkpoint files, which implies file read/write capability, but it does not declare any tool scope or permissions boundary. This creates an authorization and review gap: an agent may gain broader file access than operators expect, increasing the chance of unintended writes, unsafe checkpoint handling, or use in environments that require explicit tool allowlisting.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.