Back to skill

Security audit

rl-runtime-guard

Security checks for vulnerabilities and agentic risk

Overview

It is a guardrail tool, but it can steer the agent through high-priority instructions and one advertised off switch is not implemented in the included default hook.

Install only if you are comfortable with a hook that can add high-priority instructions to agent requests. Prefer using the environment variable disable path for testing or incident response, and verify your OpenClaw integration actually loads any config file before relying on file-based disable or threshold settings.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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)

T01 · Skill Instruction Hijacking

Error
Location
handler.mjs:235
Finding
Runtime guard directives are injected into trusted system-level prompt context<![CDATA[ ## Vulnerability Details **File Location**: `handler.mjs:235-267` **Related Directive Builders**: `handler.mjs:109-151` **Vulnerability Type**: System-level instruction injection **Risk Level**: High ### Complete Code Snippet ```javascript // Build combined prompt let guardPrompt = ''; if (decisions.includes('complex_task')) { guardPrompt += buildComplexTaskPrompt(config) + '\n\n'; } if (decisions.includes('retry_loop')) { guardPrompt += buildRetryLoopPrompt(config) + '\n\n'; } if (decisions.includes('platform_path_hint') || decisions.includes('complex_task')) { guardPrompt += buildToolGuardPrompt(config) + '\n\n'; } guardPrompt = guardPrompt.trim(); // Inject (same pattern as the production hook) if (Array.isArray(requestData.messages)) { const msgs = requestData.messages; const lastSystemIdx = [...msgs].reverse().findIndex(m => m.role === 'system'); const insertIdx = lastSystemIdx >= 0 ? msgs.length - 1 - lastSystemIdx + 1 : 0; msgs.splice(insertIdx, 0, { role: 'system', content: guardPrompt, name: 'rl-runtime-guard', }); } else if (typeof requestData.systemPrompt === 'string') { requestData.systemPrompt = guardPrompt + '\n\n' + requestData.systemPrompt; } else if (typeof requestData.context === 'object' && requestData.context !== null) { if (typeof requestData.context.text === 'string') { requestData.context.text = guardPrompt + '\n\n' + requestData.context.text; } else if (typeof requestData.context.system === 'string') { requestData.context.system = guardPrompt + '\n\n' + requestData.context.system; } } else if (typeof requestData.context === 'string') { requestData.context = guardPrompt + '\n\n' + requestData.context; } ``` ### Technical Analysis The handler converts package-authored advisory text into a trusted system message or prepends it directly to system-oriented request fields. The prompt builders at `handler.mjs:109-151` contain mandatory behavioral directives, forced response proced ...[truncated 2139 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not represent advisory guard output as a `system` message. 2. Return guard decisions through a structured, host-defined metadata or policy interface that does not alter prompt authority. 3. If prompt-based advice is unavoidable, place it in a clearly identified low-trust advisory channel and ensure that higher-priority host and user instructions take precedence. 4. Replace mandatory language, prohibitions, and unsupported enforcement claims with accurate advisory wording. 5. Require explicit operator or per-session consent before activating prompt augmentation. 6. Allow each guard to be independently enabled or disabled through validated configuration. 7. Add integration tests verifying that guard advice cannot supersede host safety policy, alter unrelated tasks, or claim enforcement that the implementation does not provide. 8. Update the documentation to disclose that the Skill changes the request prompt and can influence agent behavior. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
handler.mjs:291
Finding
The default hook ignores the documented configuration file and global disable setting<![CDATA[ ## Vulnerability Details **File Location**: `handler.mjs:291-306` **Related Documentation**: `README.md:92-101`, `SKILL.md:73-89`, `references/disabling.md:28-43` **Vulnerability Type**: Configuration control bypass **Risk Level**: Medium ### Complete Code Snippet ```javascript const hookSessionStore = new Map(); export default async function requestBeforeHandler(ctx) { try { return applyGuards(ctx, hookSessionStore); } catch (err) { // Never break the request pipeline try { const cfg = DEFAULT_CONFIG; logDecision({ sessionKey: ctx?.sessionKey, error: err.message, }, cfg); } catch {} return []; } } ``` ### Technical Analysis The default exported hook invokes `applyGuards` without supplying an operator configuration. As a result, `applyGuards` uses a fresh copy of `DEFAULT_CONFIG`. No code in the shipped default hook reads or parses the documented file at `~/.openclaw/hooks/rl-runtime-guard/config.json`. This makes the documented file-based controls ineffective for this entry point. In particular, setting `"enabled": false`, adjusting thresholds, or configuring `auditLogPath` in that file does not affect the default handler. The environment variable `RL_GUARD_DISABLED=1` remains effective because it is checked directly inside `applyGuards`, but that does not correct the misleading file-based control path. The same problem affects operational expectations around audit logging. The default configuration has an empty audit path, so placing an audit path in the documented configuration file will not enable logging unless an external integration explicitly reads that file and passes the resulting object to `applyGuards`. ### Attack Path 1. An operator installs the Skill and relies on its default exported hook. 2. The operator follows the documentation and writes `"enabled": false` to the documented configuration file. 3. The hook starts or continues running, but never reads that file. ...[truncated 839 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Load the documented configuration file in the default hook before calling `applyGuards`. 2. Parse the file using strict JSON handling and validate every property against an explicit schema. 3. Merge validated values over `DEFAULT_CONFIG`, rejecting invalid types, non-finite numbers, negative window sizes, and out-of-range similarity thresholds. 4. Treat an explicit `"enabled": false` as authoritative and fail closed with respect to prompt augmentation if configuration loading is ambiguous. 5. Define clear precedence among the environment variable, configuration file, and programmatic overrides. 6. Report configuration-loading failures through a safe operational channel without exposing user content. 7. Add automated tests demonstrating that file-based disabling, threshold changes, and audit-path changes affect the default exported handler. 8. Update the documentation if configuration loading is intentionally delegated to an external host integration rather than implemented by this package. ]]>
Vulnerability Patterns
  • 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
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep

Static analysis

No suspicious patterns detected.