T09 · Insecure Skill Coding Practices
- Location
scripts/triage.py:578- Finding
Path Traversal in Manifest-Based Critical File Restoration
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This is a local incident-response tool, but it includes underdocumented automated remediation paths that can move or delete skills, overwrite files, and run scripts from the workspace being investigated.
Install only if you intentionally want an active incident-response tool with workspace write authority. Prefer the read-only investigation, timeline, scope, status, and evidence commands first; avoid contain, remediate, and protect on an untrusted or compromised workspace unless you have backups, have reviewed the exact actions, and can run the tool with least-privilege filesystem access. Treat generated evidence bundles as sensitive because they may contain source code, hashes, audit data, and security-tool state.
scripts/triage.py:578Path Traversal in Manifest-Based Critical File Restoration
scripts/triage.py:600Execution of Unverified Security-Tool Scripts from the Investigated Workspace
scripts/triage.py:501Destructive Skill Quarantine Triggered by Modification-Time Heuristics
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
TRIAGE_DIR, TRIAGE_STATE = ".triage", "state.json"
QUARANTINE_DIR, BACKUPS_DIR = ".triage/quarantine", ".triage/backups"
SKIP_DIRS = {".git", ".svn", ".hg", "__pycache__", "node_modules", ".triage",
".integrity", ".ledger", ".signet", ".sentinel", ".venv", "venv", ".env"}
SELF_SKILL_DIRS = {"openclaw-triage", "openclaw-triage"}
CRITICAL_FILES = {"SOUL.md", "AGENTS.md", "IDENTITY.md", "USER.md", "TOOLS.md", "HEARTBEAT.md"}
CONFIG_EXTS = {".json", ".yaml", ".yml", ".toml"}
Although presented as a triage suite, the file also performs destructive actions: quarantining skills, renaming hook configs, restoring files, and rebuilding baselines. This mismatch increases the chance that a user invokes a seemingly investigative tool that silently modifies the workspace during a sensitive response scenario.
The containment path can delete skill directories after copying them to quarantine and can disable Claude hook configuration by renaming files. Those are powerful state-changing actions that can disrupt legitimate tooling or be misused if triggered on false positives, especially without a stronger trust model.
Automated containment performs destructive operations without any confirmation prompt: quarantining skills and disabling hook files by renaming them. In an incident-response context, false positives can cause operational damage, and an attacker may intentionally trigger detections to induce self-inflicted denial of service.
The triage skill extends itself from analysis into executing other workspace tools during remediation. In this context, the added execution capability is especially dangerous because incident-response tooling should not trust code inside the potentially compromised environment it is investigating.
Protect mode automatically investigates, may quarantine skills, backs up files, writes evidence archives, and generates incident reports without prior confirmation. That makes it a highly stateful and potentially disruptive command whose side effects can be triggered too easily in a hostile or noisy environment.
The README explicitly promotes evidence collection that snapshots the full workspace and copies security-tool data, but it does not warn that these archives may contain secrets, sensitive source code, audit trails, or personal data. In an incident-response context, users may run this command during stress and then store or share the resulting bundle insecurely, increasing the risk of secondary data exposure.
The skill exposes operational commands that invoke a local Python script with broad workspace access, including reading environment-derived paths, traversing files, collecting evidence, and writing output, but it does not declare any explicit tool scope or permissions. That mismatch is dangerous because a caller or hosting platform may not realize the skill needs shell, file read, file write, and environment access, increasing the chance of overbroad execution and unsafe use on sensitive workspaces.
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
if classify(e["rel"]) == "critical":
bk = bp / e["rel"].replace("/", "_")
try:
shutil.copy2(e["abs"], bk)
bk.chmod(stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH); lc += 1
actions.append(f"Locked: {e['rel']}"); print(f" Locked: {e['rel']}")
except (OSError, PermissionError) as e2: print(f" Failed: {e2}")
Remediation modifies critical files and triggers subprocess-based repair actions without a clear upfront warning or approval checkpoint. During recovery from compromise, this can overwrite evidence, alter trusted state, or execute attacker-influenced components before the user understands the consequences.
This restores files into paths taken from the warden manifest, which is read from the workspace and therefore potentially attacker-controlled. If an attacker can tamper with the manifest, remediation may overwrite arbitrary files within the workspace tree and restore attacker-chosen content.
ap = ws / rel
for sf in [sd / rel.replace("/", "_"), sd / rel]:
if sf.is_file() and sha256_file(ap) != info.get("sha256", "") if ap.is_file() else True:
try: shutil.copy2(sf, ap); rc += 1; actions.append(f"Restored: {rel}"); print(f" Restored: {rel}")
except OSError as e: print(f" Failed: {e}")
break
bp = ws / BACKUPS_DIR
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
if bf.is_file() and bf.name in CRITICAL_FILES:
dest = ws / bf.name
if not dest.is_file():
try: shutil.copy2(bf, dest); rc += 1; actions.append(f"Restored (backup): {bf.name}")
except OSError: pass
print(f" {rc} file(s) restored\n")
# Re-sign with signet
The script executes another Python script from the workspace (skills/openclaw-signet/scripts/signet.py) via subprocess.run. Even though shell injection is avoided by passing an argument list, this still creates a code-execution path into potentially untrusted workspace content, which is dangerous in a security tool because a compromised skill can run arbitrary code during remediation.
for d in ("openclaw-signet", "openclaw-signet")]:
if cand.is_file():
try:
r = subprocess.run([sys.executable, str(cand), "sign", "--workspace", str(ws)],
capture_output=True, text=True, timeout=30)
if r.returncode == 0: actions.append("Re-signed skills"); print(" Done.")
else: print(f" Code {r.returncode}")
The workspace path can come from OPENCLAW_WORKSPACE, and that path is then used to locate and execute a workspace-local script. The core issue is not argument injection but trust of environment-selected, attacker-controlled code locations for execution.
for d in ("openclaw-signet", "openclaw-signet")]:
if cand.is_file():
try:
r = subprocess.run([sys.executable, str(cand), "sign", "--workspace", str(ws)],
capture_output=True, text=True, timeout=30)
if r.returncode == 0: actions.append("Re-signed skills"); print(" Done.")
else: print(f" Code {r.returncode}")
This invokes a ledger helper script located inside the workspace. That expands the triage tool's privileges into arbitrary code execution if an attacker has modified or planted that helper, especially because remediation workflows are likely run under heightened trust during incident response.
for d in ("openclaw-ledger", "openclaw-ledger")]:
if cand.is_file():
try:
r = subprocess.run([sys.executable, str(cand), "record", "--action", "remediation",
"--detail", f"Triage Pro remediation {now_iso()}", "--workspace", str(ws)],
capture_output=True, text=True, timeout=30)
if r.returncode == 0: actions.append("Recorded in ledger"); print(" Done.")
This command executes a helper discovered beneath a workspace path that may be environment-controlled. In an incident-response context, that means a hostile workspace can influence what code runs during ledger recording, turning a logging step into code execution.
for d in ("openclaw-ledger", "openclaw-ledger")]:
if cand.is_file():
try:
r = subprocess.run([sys.executable, str(cand), "record", "--action", "remediation",
"--detail", f"Triage Pro remediation {now_iso()}", "--workspace", str(ws)],
capture_output=True, text=True, timeout=30)
if r.returncode == 0: actions.append("Recorded in ledger"); print(" Done.")
The tool runs a workspace-local warden.py during baseline rebuild. In the context of incident response, executing code from the potentially compromised workspace is unsafe because an attacker can turn the remediation path into arbitrary code execution and persistence.
for d in ("openclaw-warden", "openclaw-warden")]:
if cand.is_file():
try:
r = subprocess.run([sys.executable, str(cand), "scan", "--workspace", str(ws)],
capture_output=True, text=True, timeout=60)
if r.returncode == 0: actions.append("Rebuilt baselines"); print(" Done.")
else: print(f" Code {r.returncode}")
The remediation flow executes a scanner located through the workspace path, which may be chosen via environment variable. That makes the scanner invocation unsafe because a compromised or attacker-selected workspace can supply malicious code that runs with the user's privileges.
for d in ("openclaw-warden", "openclaw-warden")]:
if cand.is_file():
try:
r = subprocess.run([sys.executable, str(cand), "scan", "--workspace", str(ws)],
capture_output=True, text=True, timeout=60)
if r.returncode == 0: actions.append("Rebuilt baselines"); print(" Done.")
else: print(f" Code {r.returncode}")
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.
if cmd: print(f" Command: python3 triage.py {cmd}")
print()
print(f"{'='*60}\nPlaybook: {scenario} | Steps: {len(pb['steps'])}")
print(f"Auto-execute containment: triage.py protect\n{'='*60}")
def cmdtect(ws):
print("=" * 60); print("AUTOMATED FULLTECTION SWEEP"); print("=" * 60)
The playbook message frames protect as auto-executing containment, but the command also performs broader evidence collection and reporting writes. This is a security-relevant mismatch because users may consent to one class of action while the tool performs several others on a potentially sensitive workspace.
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
if classify(e["rel"]) == "critical":
try:
bk = bp / e["rel"].replace("/", "_")
shutil.copy2(e["abs"], bk)
bk.chmod(stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)
ca.append(f"Locked: {e['rel']}")
except (OSError, PermissionError): pass
No suspicious patterns detected.