Back to skill

Security audit

Oc Guard Skill

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly purpose-aligned, but it needs Review because it can change high-impact OpenClaw settings while storing sensitive proposal data in unsafe temporary files.

Review before installing on any multi-user or production host. Use only with trusted proposal files and avoid placing real tokens or app secrets in proposals until redaction and private-file handling are fixed. Confirm exact diffs before apply, especially changes under /tools, /plugins, /commands, /channels, /models, and /agents.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/oc-guard.py:105
Finding

Nested Secrets Are Exposed in Receipts and Predictable Temporary Artifacts

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/oc-guard.py:26
Finding

Predictable Shared Temporary Files Permit Symlink-Based File Clobbering

Content
View full analysis
None: line = f"[{dt.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {msg}" LOG_PATH.parent.mkdir(parents=True, exist_ok=True) with LOG_PATH.open("a", encoding="utf-8") as f: f.write(line + "\n") ``` ```python def fail(msg: str, code: int = 1, status: str = "failure") -> None: ERROR_PATH.write_text(msg + "\n", encoding="utf-8") log(f"ERROR: {msg}") raise GuardError(msg, status=status, code=code) ``` ```python OPENCODE_DEBUG_PATH.write_text( json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8", ) ``` ```python LAST_PROPOSAL_PATH.write_text( json.dumps(proposal, ensure_ascii=False, indent=2) + "\n", encoding="utf-8", ) LAST_PLAN_PATH.write_text( json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8", ) ``` ### Technical Analysis The CLI stores logs, errors, generated proposals, plans, and raw model output at globally predictable names in `/tmp`. The write operations use ordinary `open()`, `Path.write_text()`, and JSON file writes, all of which follow symbolic links. The implementation does not use exclusive creation, `O_NOFOLLOW`, ownership validation, regular-file validation, or a private per-user runtime directory. A local attacker can pre-create one of the paths as a symbolic link. When a more privileged victim invokes the CLI, the process can truncate or append to the link targe ...[truncated 1064 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/oc-guard.py:525
Finding

Command-Execution Confirmation Policy Can Be Disabled Without Explicit Confirmation

Content
View full analysis
str: high_prefixes = ( "/channels", "/bindings", "/models/providers", "/agents/defaults/model", "/agents/list", "/agents/by-id", ) medium_prefixes = ( "/messages", "/commands", "/gateway", "/plugins", "/tools", ) if path.startswith(high_prefixes): return "high" if path.startswith(medium_prefixes): return "medium" return "low" ``` ```python if risk == "high" and not confirm: fail("High-risk changes require --confirm", status="blocked") ``` ### Technical Analysis The guard allows `tools.exec.ask` to have the value `"off"`, which can disable command-execution prompts. However, every mutation under `/tools` is classified only as medium risk. The apply workflow requires `--confirm` only when the merged risk is high. As a result, a proposal that sets `/tools/exec/ask` to `"off"` can pass custom validation and be applied without the explicit confirmation required for high-risk changes. The risk model considers only the path prefix and does not compare the old and new values or determine whether the mutation weakens an existing security contro ...[truncated 1489 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/oc-guard.py:674
Finding

Post-Apply Canary Accepts Arbitrary Nonempty Output as Success

Content
View full analysis
Remediation
View remediation
`. 2. Reject media-only payloads and responses containing additional or unrelated text. 3. Validate the responding agent's identity and routing metadata when OpenClaw exposes those fields. 4. Use a cryptographically random nonce in each canary to prevent replay or cached responses. 5. Verify that the nonce appears exactly once in the expected text field. 6. Add negative tests for arbitrary text, error messages, media URLs, wrong-agent responses, stale tokens, and prefixed or suffixed tokens. 7. Trigger rollback whenever the exact canary contract is not satisfied. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (18)

Env Variable Harvesting

High
Category
Data Exfiltration
Confidence
60% confidence
Finding

Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Content

Scanner excerpt · scripts/oc-guard.py (reported line 499)May include surrounding context.

python
temp_home = Path(td)
        temp_cfg = temp_home / "openclaw.json"
        dump_json(temp_cfg, cfg)
        env = os.environ.copy()
        env["OPENCLAW_CONFIG_PATH"] = str(temp_cfg)
        proc = run([str(OPENCLAW_BIN), "config", "validate", "--json"], timeout=30, check=False, env=env)
        out = ((proc.stdout or "") + (proc.stderr or "")).strip()

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The instruction "Use bilingual phrasing where repository already does so" imposes a language/locale requirement on agent behavior. The policy allows locale constraints only when users are given a choice or when the constraint is clearly justified as region-specific, which is not stated here.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · README.md (reported line 88)May include surrounding context.

bash
chmod +x scripts/oc-guard
mkdir -p ~/.local/bin
ln -sf "$PWD/scripts/oc-guard" ~/.local/bin/oc-guard

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill advertises operational commands that invoke local binaries and imply file and shell access, but it does not declare any explicit tool scope such as permissions or allowed-tools. That mismatch weakens policy enforcement and reviewability, increasing the risk that an agent can perform filesystem or command execution beyond what a caller expects.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The skill requires a fixed response format, including a Chinese fallback string and an execution receipt-first requirement, without user opt-in. While not directly enabling code execution, it can override user preferences and manipulate agent output behavior, which may obscure failures or reduce transparency in multilingual contexts.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The file embeds Chinese-language markers and consistently emits Chinese or bilingual strings in prompts, errors, receipts, and canary messages, without offering the user a language choice. That creates a locale/language policy issue because the skill enforces a specific language behavior rather than allowing opt-in or selection.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/oc-guard.py (reported line 91)May include surrounding context.

python
def run(cmd, timeout=60, check=True, env=None):
    return subprocess.run(cmd, capture_output=True, text=True, timeout=timeout, check=check, env=env)


def load_json(path: Path):

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
85% confidence
Finding

The code reads a secret from the OCGUARD_RECEIPT_SECRET environment variable and falls back to creating and storing a secret on disk, but there is no confirmation prompt or user-facing disclosure at the point of access. In this file, the command output and logs describe config operations, yet they do not warn that credential-like material may be read from environment state or persisted locally.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill sends natural-language requirements to an external AI tool and then applies the returned JSON proposal to live configuration after only structural validation. In this context, the AI-generated changes can alter channels, bindings, models, tools, and gateway behavior, so prompt manipulation or unsafe proposals may lead to misconfiguration, expanded access, or service disruption.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

Natural-language requirements are forwarded to the external opencode subprocess without an explicit warning or consent mechanism. In a security-sensitive config-management tool, this matters because operators may unintentionally expose internal infrastructure details, credentials, or business context to a separate AI toolchain.

Content

No source excerpt is available for this finding.

Tainted flow: 'payload' from os.environ.get (line 591, credential/environment) → pathlib.Path.write_text (file write)

Medium
Category
Data Flow
Confidence
81% confidence
Finding

When opencode returns invalid output, the script writes raw stdout/stderr into a world-accessible temporary path under /tmp. Since the external AI tool may echo prompts, requirements, config-derived content, or secrets, this creates a local information-disclosure risk to other users/processes on the same host.

Content

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

python
"stdout": stdout,
            "stderr": stderr,
        }
        OPENCODE_DEBUG_PATH.write_text(
            json.dumps(payload, ensure_ascii=False, indent=2) + "\n",
            encoding="utf-8",
        )

Tainted flow: 'payload' from os.environ.get (line 609, credential/environment) → pathlib.Path.write_text (file write)

Medium
Category
Data Flow
Confidence
82% confidence
Finding

On JSON parse failure, the script persists raw opencode stdout/stderr plus extracted JSON and parse errors to /tmp. Because this data may contain secrets, prompts, or internal configuration details, it can leak sensitive information to local adversaries or other processes able to read temporary files.

Content

Scanner excerpt · scripts/oc-guard.py (reported line 616)May include surrounding context.

python
"extracted_json": text,
            "parse_error": str(e),
        }
        OPENCODE_DEBUG_PATH.write_text(
            json.dumps(payload, ensure_ascii=False, indent=2) + "\n",
            encoding="utf-8",
        )

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The stated purpose is config planning/apply with receipts, but the code additionally sends probe messages to configured agents and validates their responses after apply. This operational agent-execution capability goes beyond merely planning and applying configuration and is not clearly disclosed in the manifest description.

Content

No source excerpt is available for this finding.

Tainted flow: 'backup_file' from os.environ.get (line 773, 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/oc-guard.py (reported line 775)May include surrounding context.

python
ts = dt.datetime.now().strftime("%Y%m%d_%H%M%S")
    backup_file = BACKUP_DIR / f"openclaw.json.{ts}.bak"
    dump_json(LAST_PROPOSAL_PATH, proposal)
    shutil.copy2(config_file, backup_file)
    dump_json(config_file, modified)

    log(f"Backup created: {backup_file}")

Tainted flow: 'backup_file' from os.environ.get (line 773, 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/oc-guard.py (reported line 784)May include surrounding context.

python
restarted = run([str(OPENCLAW_BIN), "gateway", "restart"], timeout=60, check=False)
    if restarted.returncode != 0:
        log("Gateway restart failed, rolling back")
        shutil.copy2(backup_file, config_file)
        run([str(OPENCLAW_BIN), "gateway", "restart"], timeout=60, check=False)
        fail("restart failed after apply; rolled back")

Tainted flow: 'backup_file' from os.environ.get (line 773, 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/oc-guard.py (reported line 791)May include surrounding context.

python
restarted = run([str(OPENCLAW_BIN), "gateway", "restart"], timeout=60, check=False)
    if restarted.returncode != 0:
        log("Gateway restart failed, rolling back")
        shutil.copy2(backup_file, config_file)
        run([str(OPENCLAW_BIN), "gateway", "restart"], timeout=60, check=False)
        fail("restart failed after apply; rolled back")

Tainted flow: 'backup_file' from os.environ.get (line 773, 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/oc-guard.py (reported line 799)May include surrounding context.

python
restarted = run([str(OPENCLAW_BIN), "gateway", "restart"], timeout=60, check=False)
    if restarted.returncode != 0:
        log("Gateway restart failed, rolling back")
        shutil.copy2(backup_file, config_file)
        run([str(OPENCLAW_BIN), "gateway", "restart"], timeout=60, check=False)
        fail("restart failed after apply; rolled back")

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
88% confidence
Finding

This manifest contains natural-language strings in both Chinese and English, such as the intent on L02 and the post-check message on L15. Because the file provides no indication that language choice is user-selectable or that the locale is intentionally constrained, it may violate organizational language/locale policy expectations.

Content

No source excerpt is available for this finding.