Back to skill

Security audit

Openclaw Triage

Security checks for vulnerabilities and agentic risk

Overview

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.

Vulnerability Patterns
  • Tool Hijacking and SpoofingModifies or replaces tools so legitimate-looking calls execute attacker logic
  • 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 (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/triage.py:578
Finding

Path Traversal in Manifest-Based Critical File Restoration

Content
View full analysis
Remediation
View remediation

T07 · Tool Hijacking and Spoofing

Error
Location
scripts/triage.py:600
Finding

Execution of Unverified Security-Tool Scripts from the Investigated Workspace

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/triage.py:501
Finding

Destructive Skill Quarantine Triggered by Modification-Time Heuristics

Content
View full analysis
cutoff] ``` ```python def cmd_contain(ws): print("=" * 60); print("AUTOMATED CONTAINMENT"); print("=" * 60) print(f"Workspace: {ws}\nTimestamp: {now_iso()}\n") print("[1/4] Running threat assessment...") findings, files, sev = run_investigation(ws) actionable = [f for f in findings if f["sev"] != SEV_LOW] print(f" Severity: {sev} | {len(actionable)} actionable\n") if not actionable: print("No threats found.\n" + "=" * 60); sys.exit(0) qp = ws / QUARANTINE_DIR; qp.mkdir(parents=True, exist_ok=True) actions = [] # Quarantine skills print("[2/4] Quarantining flagged skills...") flagged = set() for f in findings: m = f.get("msg", "") if "Tampered signatures" in m: for n in m.split(": ", 1)[-1].split(", "): flagged.add(n.strip()) if "Skill file modified" in m: parts = Path(m.split(": ")[1].split(" ")[0]).parts if ": " in m else () if len(parts) >= 2 and parts[0] == "skills": flagged.add(parts[1]) qc = 0 for sn in sorted(flagged): sd = ws / "skills" / sn if sd.is_dir(): dest = qp / sn try: if dest.exists(): shutil.rmtree(dest) shutil.copytree(sd, dest); shutil.rmtree(sd); qc += 1 actions.append(f"Quarantined: {sn}"); print(f" Quarantined: {sn}") except (OSError, shutil.Error) as e: print(f" Failed: {s ...[truncated 2221 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (21)

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/triage.py (reported line 22)May include surrounding context.

python
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"}

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
94% confidence
Finding

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.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
95% confidence
Finding

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.

Content

No source excerpt is available for this finding.

Missing User Warnings

High
Category
Not specified by scanner
Confidence
97% confidence
Finding

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.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
95% confidence
Finding

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.

Content

No source excerpt is available for this finding.

Missing User Warnings

High
Category
Not specified by scanner
Confidence
97% confidence
Finding

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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

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.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding

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.

Content

No source excerpt is available for this finding.

Tainted flow: 'bk' from os.environ.get (line 539, credential/environment) → shutil.copy2 (file write)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/triage.py (reported line 541)May include surrounding context.

python
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}")

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

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.

Content

No source excerpt is available for this finding.

Tainted flow: 'ap' from os.environ.get (line 584, credential/environment) → shutil.copy2 (file write)

Medium
Category
Data Flow
Confidence
84% confidence
Finding

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.

Content

Scanner excerpt · scripts/triage.py (reported line 587)May include surrounding context.

python
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

Tainted flow: 'dest' from os.environ.get (line 855, credential/environment) → shutil.copy2 (file write)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/triage.py (reported line 596)May include surrounding context.

python
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

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
93% confidence
Finding

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.

Content

Scanner excerpt · scripts/triage.py (reported line 605)May include surrounding context.

python
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}")

Tainted flow: 'ws' from os.environ.get (line 78, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Confidence
90% confidence
Finding

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.

Content

Scanner excerpt · scripts/triage.py (reported line 605)May include surrounding context.

python
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}")

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
93% confidence
Finding

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.

Content

Scanner excerpt · scripts/triage.py (reported line 618)May include surrounding context.

python
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.")

Tainted flow: 'ws' from os.environ.get (line 78, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Confidence
90% confidence
Finding

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.

Content

Scanner excerpt · scripts/triage.py (reported line 618)May include surrounding context.

python
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.")

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
93% confidence
Finding

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.

Content

Scanner excerpt · scripts/triage.py (reported line 632)May include surrounding context.

python
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}")

Tainted flow: 'ws' from os.environ.get (line 78, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Confidence
90% confidence
Finding

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.

Content

Scanner excerpt · scripts/triage.py (reported line 632)May include surrounding context.

python
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}")

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
85% confidence
Finding

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.

Content

Scanner excerpt · scripts/triage.py (reported line 824)May include surrounding context.

python
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)

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

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.

Content

No source excerpt is available for this finding.

Tainted flow: 'bk' from os.environ.get (line 864, credential/environment) → shutil.copy2 (file write)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/triage.py (reported line 865)May include surrounding context.

python
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

Static analysis

No suspicious patterns detected.