Back to skill

Security audit

idempotent-rebuild-verification

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly does what it claims, but it needs Review because some explicit write and audit features are under-scoped and can affect more local files than users may expect.

Install only if you are comfortable with a local verification tool that can read chosen files and write chosen output directories. Use private, newly created output directories rather than shared paths like /tmp/steps or /tmp/fix, and avoid pointing wipe-audit at broad roots containing sensitive project structure.

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)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/rebuild_verify.py:354
Finding
Predictable Output Files Follow Symbolic Links and Permit Unintended File Overwrites<![CDATA[ ## Vulnerability Details **File Location**: `scripts/rebuild_verify.py:354-370` and `scripts/rebuild_verify.py:521-536` **Vulnerability Type**: Symbolic-link file overwrite through unsafe output-file creation **Risk Level**: Medium ### Vulnerable Code The `extract-steps --write-steps` command creates predictable filenames inside a potentially pre-existing directory: ```python if args.write_steps: os.makedirs(args.write_steps, exist_ok=True) manifest = [] for s in steps: b = next(b for b in blocks if b["start"] == s["start_line"] and b["end"] == s["end_line"]) name = "step_%02d.%s" % (s["index"], {"bash": "sh", "sh": "sh", "shell": "sh", "python": "py"}.get(s["lang"], "txt")) p = os.path.join(args.write_steps, name) payload = "\n".join(b["content"]) + ("\n" if b["content"] else "") with open(p, "w", encoding="utf-8", newline="") as f: f.write(payload) manifest.append({"index": s["index"], "file": name, "bytes": s["bytes"], "sha256": s["sha256"], "status": s["status"]}) with open(os.path.join(args.write_steps, "steps.json"), "w", encoding="utf-8") as f: json.dump(manifest, f, ensure_ascii=False, indent=1) out["written"] = args.write_steps ``` The fixture generator has the same issue with predictable output names: ```python def cmd_genfixtures(args): root = os.path.abspath(args.dir) os.makedirs(root, exist_ok=True) canon = make_canonical(1116) assert len(canon) == 1116 and canon.endswith(b"\n") and not canon.endswith(b"\n\n") fixtures = { "canonical.txt": canon, "drift_no_nl.txt": canon[:-1], "drift_3nl.txt": canon + b"\n\n", "truncated.txt": canon[:500], "html404.txt": make_html404(), "same_size_diff.txt": canon[:100] + bytes([canon[100] + 1]) + canon[101:], "crlf.txt": canon.replace(b"\n", b"\r\n"), } asser ...[truncated 2560 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require output directories to be newly created by the process rather than silently reusing existing directories. 2. If existing directories must be supported, inspect every destination with `os.lstat()` and reject symbolic links and non-regular files. 3. Open output files through `os.open()` with restrictive flags: - `O_WRONLY` - `O_CREAT` - `O_EXCL` - `O_NOFOLLOW`, where available 4. Apply a restrictive file mode such as `0o600` unless broader access is explicitly required. 5. Create output in securely generated temporary files and use an atomic rename only after validation. 6. Resolve and validate the parent directory, while recognizing that `realpath()` checks alone do not eliminate time-of-check/time-of-use races. 7. Avoid fixed shared temporary paths in documentation. Recommend `tempfile.mkdtemp()` or a private directory with mode `0o700`. 8. Add regression tests that pre-create each output filename as a symbolic link and verify that the command fails without modifying the linked target. ]]>

T05 · Unauthorized Access and Privilege Escalation

Note
Location
scripts/rebuild_verify.py:386
Finding
wipe-audit Recursively Enumerates Files Beyond Its Documented Scan Depth<![CDATA[ ## Vulnerability Details **File Location**: `scripts/rebuild_verify.py:386-422`; documented limits in `references/snapshot_semantics.md:12-18` **Vulnerability Type**: Excessive filesystem traversal and path disclosure **Risk Level**: Low ### Vulnerable Code The implementation recursively walks every accessible descendant of the supplied root until the global entry limit is exceeded: ```python def cmd_wipe(args): root = os.path.abspath(args.dir) counts = {"excluded_dirs_present": [], "scripts": [], "bins": [], "models": [], "shim": []} n_entries = 0 for dirpath, dirnames, filenames in os.walk(root): if n_entries > 5000: break n_entries += len(dirnames) + len(filenames) rel = os.path.relpath(dirpath, root) for fn in filenames: fp = os.path.join(dirpath, fn) if fn in ("Makefile",) or fn.endswith((".sh", ".py")): counts["scripts"].append(os.path.relpath(fp, root)) if fn.endswith((".gguf", ".safetensors")) or (fn.endswith(".bin") and "model" in fp.lower()): counts["models"].append(os.path.relpath(fp, root)) bin_hits = [dn for dn in dirnames if os.path.basename(os.path.normpath(dirpath)) == "bin" and dirpath != os.path.join(root, "bin")] for dn in bin_hits: dp = os.path.join(dirpath, dn) for x in os.listdir(dp)[:20]: counts["bins"].append(os.path.relpath(os.path.join(dp, x), root)) for dn in dirnames: if dn in snapshot_excluded(): counts["excluded_dirs_present"].append(os.path.relpath(os.path.join(dirpath, dn), root)) if rel in (".", ""): for special in (".shim",): if os.path.isdir(os.path.join(dirpath, special)): counts["shim"].append(special) if os.path.isdir(os.path.join(dirpath, "node_modules", ".bin")): counts[" ...[truncated 2544 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Calculate the relative depth for each visited directory and enforce the documented category-specific boundaries. 2. Prune traversal in place by modifying `dirnames[:]` once the maximum relevant depth is reached. 3. Avoid collecting or emitting full relative paths when only counts or Boolean presence checks are needed. 4. Validate the supplied root and warn or refuse when it is unexpectedly broad, such as the filesystem root. 5. Decide whether symbolic directory traversal is intended and document the policy explicitly. 6. Make the entry limit strict by checking the prospective count before processing another directory. 7. Align `references/snapshot_semantics.md` with the actual implementation if recursive scanning is intentionally retained. 8. Add tests containing matching files below the documented maximum depth and verify that those files neither affect the verdict nor appear in output. ]]>
Vulnerability Patterns
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (1)

Lp3

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding
The skill advertises shell, file read/write, and environment-variable use in its documented commands and behavior, but does not declare permissions. That mismatch weakens security review and sandbox enforcement because operators may approve or run the skill without understanding its actual capability footprint, especially since it can write to disk via --write-steps and gen-fixtures.

Static analysis

No suspicious patterns detected.