T06 · System Persistence
Error
- Location
- scripts/deploy.sh:79
- Finding
- Persistent Gateway Startup Hook Promotes User-Writable JavaScript into the Control UI<![CDATA[ ## Vulnerability Details **File Location**: `scripts/deploy.sh:79-85`, `hooks/handler.ts:7-15`, `hooks/inject.sh:27-34` **Vulnerability Type**: Persistent startup code execution **Risk Level**: High ### Vulnerable Code #### `scripts/deploy.sh:79-85` ```bash # 3) Install gateway startup hook (survives openclaw update) HOOK_DIR="$HOME/.openclaw/hooks/voice-input-inject" mkdir -p "$HOOK_DIR" cp -f "$SKILL_DIR/hooks/handler.ts" "$HOOK_DIR/handler.ts" cp -f "$SKILL_DIR/hooks/inject.sh" "$HOOK_DIR/inject.sh" cp -f "$SKILL_DIR/hooks/HOOK.md" "$HOOK_DIR/HOOK.md" chmod +x "$HOOK_DIR/inject.sh" ``` #### `hooks/handler.ts:7-15` ```ts const handler = async (event: any) => { if (event.type !== "gateway" || event.action !== "startup") return; // SECURITY: execFileSync with array args — no shell interpolation. // Script path is derived from __dirname (relative to this file), not user input. const script = join(__dirname, "inject.sh"); try { execFileSync("bash", [script], { timeout: 10_000, ``` #### `hooks/inject.sh:27-34` ```bash cp -f "$SRC" "$ASSET_DIR/voice-input.js" if ! grep -q "$MARKER" "$INDEX" 2>/dev/null; then # SECURITY: sed uses only hardcoded strings — no variable interpolation. sed -i 's|</body>| <script src="./assets/voice-input.js"></script>\n </body>|' "$INDEX" echo "[voice-input-inject] Injected into $INDEX" else echo "[voice-input-inject] Already present in $INDEX" ``` ### Technical Analysis Deployment creates a persistent OpenClaw gateway startup hook under `~/.openclaw/hooks/voice-input-inject`. Whenever the gateway emits a startup event, `handler.ts` invokes `inject.sh` through Bash. The script then copies `~/.openclaw/workspace/voice-input/voice-input.js` into the trusted OpenClaw Control UI asset directory and ensures that `index.html` loads it. Although `execFileSync` avoids shell-argument injection, it does not address the trust-boundary problem. The startup hook treats a workspace file as truste ...[truncated 2157 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Remove automatic startup reinjection** - Do not execute a Bash reinjection script on every gateway startup. - Prefer an officially supported OpenClaw extension or plugin mechanism that preserves clear package ownership and lifecycle controls. 2. **Use an immutable or package-controlled source** - Do not copy executable browser code from a general workspace directory. - Keep the canonical asset in an installation directory that is not writable by unrelated workspace operations. 3. **Verify integrity before every deployment** - Pin an expected SHA-256 digest for `voice-input.js`. - Refuse to copy the file when its digest differs from the approved value. - For updateable distributions, verify a trusted digital signature rather than relying only on a locally writable checksum. 4. **Apply restrictive permissions** - Ensure the hook and source asset are owned by the expected user. - Reject symbolic links and files with unexpected ownership or permissions. - Open and validate the source safely before copying it to reduce path-substitution risks. 5. **Limit persistence** - Require explicit user confirmation before restoring UI modifications after an update. - Record reinjection activity and integrity-check failures in an auditable log. - Provide a supported mechanism to disable or remove the hook without executing additional unverified workspace content. 6. **Deploy atomically** - Copy a verified asset to a temporary file in the destination directory. - Revalidate it and atomically rename it into place. - Preserve a known-good backup of `index.html` and avoid broad text substitution when a structured extension mechanism is available. ]]>
