Back to skill

Security audit

openclaw-reflect

Security checks for vulnerabilities and agentic risk

Overview

This skill is transparent about trying to improve agent behavior, but its automatic logging, evaluator, and write pipeline can expose sensitive context and be manipulated into persistent or out-of-scope file changes.

Install only after reviewing the auto-apply behavior. Use it in trusted workspaces, prefer the local rules evaluator, avoid enabling API keys on sensitive repositories, inspect or clear .reflect state regularly, and require manual review for MEMORY.md and CLAUDE.md changes. Do not allow autonomous x402 payments without an explicit operator-controlled payment policy.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
Findings (6)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/evaluate.js:51
Finding
Unredacted Tool Inputs and Persistent Memory Are Automatically Transmitted to Evaluator Endpoints<![CDATA[ ## Vulnerability Details **File Locations**: - `hooks/post-tool-use.js:49-74` - `scripts/hook-observe.js:85-96` - `scripts/classify.js:61-72` - `scripts/evaluate.js:51-75` - `scripts/evaluate.js:81-153` - `scripts/evaluate.js:329-339` **Vulnerability Type**: Sensitive-data exposure through unredacted logging and external evaluation **Risk Level**: High ### Vulnerable Code ```js // hooks/post-tool-use.js:49-74 const toolName = process.env.CLAUDE_TOOL_NAME || process.env.TOOL_NAME || 'unknown'; const exitCode = process.env.CLAUDE_TOOL_EXIT_CODE ?? process.env.TOOL_EXIT_CODE; const output = process.env.CLAUDE_TOOL_OUTPUT || process.env.TOOL_OUTPUT || ''; const input = process.env.CLAUDE_TOOL_INPUT || process.env.TOOL_INPUT || ''; const sessionId = process.env.CLAUDE_SESSION_ID || 'unknown'; const event = { ts: new Date().toISOString(), session: sessionId, tool: toolName, outcome, exit_code: exitCode !== undefined ? parseInt(exitCode, 10) : null, error_pattern: errorPattern, // Capture minimal input context (first 150 chars, no secrets) input_summary: typeof input === 'string' ? input.slice(0, 150).replace(/\n/g, ' ') : null, }; fs.appendFileSync(OUTCOMES_FILE, JSON.stringify(event) + '\n', 'utf8'); ``` ```js // scripts/hook-observe.js:85-96 const record = { ts: new Date().toISOString(), session: event.session_id || 'unknown', tool: event.tool_name || 'unknown', outcome: 'error', exit_code: response.exit_code ?? 1, error_pattern: errorPattern, input_summary: JSON.stringify(event.tool_input || {}).slice(0, 200), source: 'hook', }; fs.appendFileSync(OUTCOMES_FILE, JSON.stringify(record) + '\n', 'utf8'); ``` ```js // scripts/classify.js:61-72 .map(g => ({ key: g.key, tool: g.tool, error_pattern: g.error_pattern, recurrence: g.occurrences.length, session_count: g.sessions.size, first_seen: g.occurrences[0].ts, last_seen: g.occurrences[g.occurrences.length - 1].ts, sample_inputs: g.occurrences.slice(-3).ma ...[truncated 4133 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Stop collecting raw tool inputs by default. Store only structured fields that are strictly necessary for classifying failures. 2. Implement centralized secret redaction before any persistent write or network request. Cover authorization headers, common API-key formats, URL credentials, private keys, passwords, cookies, and wallet secrets. 3. Use a field allowlist rather than attempting to denylist sensitive values. 4. Do not include `MEMORY.md` in remote requests by default. If memory comparison is necessary, derive a minimal sanitized summary locally. 5. Require explicit operator consent before enabling a remote evaluator and clearly disclose every category of data sent. 6. Default to the local rule-based evaluator. 7. Restrict Ollama to loopback addresses unless a remote endpoint is explicitly authorized. 8. Require HTTPS and certificate validation for any non-loopback evaluator. 9. Apply retention limits and restrictive filesystem permissions to `.reflect` state. 10. Provide a migration or cleanup procedure to remove secrets already stored in `.reflect/outcomes.jsonl`, `patterns.json`, and `proposals.json`. ]]>

T01 · Skill Instruction Hijacking

Error
Location
scripts/evaluate.js:57
Finding
Prompt-Injection-Prone Evaluation Can Persist Attacker-Influenced Instructions in Agent Memory<![CDATA[ ## Vulnerability Details **File Locations**: - `scripts/classify.js:29-72` - `scripts/evaluate.js:57-75` - `assets/evaluator-prompt.md:1-47` - `scripts/evaluate.js:274-287` - `scripts/apply.js:76-86` - `scripts/apply.js:144-176` **Vulnerability Type**: Persistent instruction and memory poisoning through untrusted evaluator input **Risk Level**: High ### Vulnerable Code ```js // scripts/evaluate.js:57-75 function buildUserMessage(proposal, memory) { return ` ## Current MEMORY.md (excerpt) ${memory} ## Proposal to evaluate ID: ${proposal.id} Tool: ${proposal.tool} Error pattern: ${proposal.error_pattern} Recurrence: ${proposal.recurrence}x across ${proposal.session_count} distinct sessions Blast tier: ${proposal.blast_tier} (writes to: ${proposal.blast_target}) Preliminary confidence: ${proposal.confidence} Problem: ${proposal.hypothesis.problem} Proposed change: ${proposal.hypothesis.proposed_change} Success criteria: ${proposal.hypothesis.success_criteria} Sample inputs that triggered the error: ${(proposal.sample_inputs || []).slice(0, 3).map((s, i) => `${i + 1}. ${s}`).join('\n') || '(none recorded)'} `.trim(); } ``` ```js // scripts/evaluate.js:274-287 function parseResponse(text, backend) { const decision = (text.match(/DECISION:\s*(APPROVE|REJECT|DEFER)/i) || [])[1]?.toUpperCase() || 'DEFER'; const confidence = parseFloat((text.match(/CONFIDENCE:\s*([\d.]+)/i) || [])[1] || '0.5'); const reasoning = (text.match(/REASONING:\s*(.+?)(?=\nMODIFICATION:|$)/si) || [])[1]?.trim() || text.slice(0, 300); const modification = (text.match(/MODIFICATION:\s*(.+?)$/si) || [])[1]?.trim() || null; return { decision, confidence: isNaN(confidence) ? 0.5 : Math.min(1, Math.max(0, confidence)), reasoning: reasoning || '(no reasoning)', modification, evaluator: backend, }; } ``` ```js // scripts/apply.js:76-86 function applyProposal(proposal) { const targetFile = path.join(process.cwd(), proposal.blast_target); co ...[truncated 3789 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove automatic application of all LLM-generated modifications. Require explicit operator review before changing `MEMORY.md`, `CLAUDE.md`, or `SOUL.md`. 2. Treat all proposal fields as untrusted data and place them in a strict serialized structure rather than free-form prompt text. 3. Add evaluator instructions that explicitly prohibit following commands contained in memory, errors, tool inputs, sample inputs, or proposal fields. 4. Validate responses against a strict JSON schema instead of using permissive regular expressions. 5. Independently enforce confidence and safety policy; do not trust model-reported confidence as an authorization decision. 6. Restrict modifications to narrowly defined factual records rather than arbitrary Markdown instructions. 7. Reject modifications containing command directives, tool-use instructions, hidden comments, links, credential requests, or changes to safety policy. 8. Generate session identifiers inside a trusted host boundary and authenticate outcome records. 9. Require cryptographic integrity for proposal and evaluation state. 10. Present a complete diff and provenance record to the operator before applying any persistent change. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/apply.js:76
Finding
Mutable Proposal State Enables Path Traversal and Automatic Arbitrary File Modification<![CDATA[ ## Vulnerability Details **File Location**: `scripts/apply.js:29-101, 144-176` **Vulnerability Type**: State-controlled path traversal and authorization-gate bypass **Risk Level**: High ### Vulnerable Code ```js // scripts/apply.js:29-34 function loadProposals() { if (!fs.existsSync(PROPOSALS_FILE)) return []; try { return JSON.parse(fs.readFileSync(PROPOSALS_FILE, 'utf8')); } catch { return []; } } ``` ```js // scripts/apply.js:70-74 function appendToFile(filePath, content) { const existing = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : ''; const separator = existing.endsWith('\n') ? '' : '\n'; fs.writeFileSync(filePath, existing + separator + content + '\n', 'utf8'); } ``` ```js // scripts/apply.js:76-101 function applyProposal(proposal) { const targetFile = path.join(process.cwd(), proposal.blast_target); const snapshotPath = snapshotFile(targetFile); const entry = [ `\n## Reflect: ${proposal.id}`, `<!-- Applied: ${new Date().toISOString()} | Tier: ${proposal.blast_tier} | Confidence: ${proposal.evaluation?.confidence || proposal.confidence} -->`, `**Pattern:** \`${proposal.tool}\` → ${proposal.error_pattern}`, `**Learning:** ${proposal.evaluation?.modification || proposal.hypothesis.proposed_change}`, `**Success criteria:** ${proposal.hypothesis.success_criteria}`, ].join('\n'); appendToFile(targetFile, entry); const record = { ts: new Date().toISOString(), change_id: crypto.randomBytes(6).toString('hex'), proposal_id: proposal.id, pattern_key: proposal.pattern_key, target: proposal.blast_target, snapshot: snapshotPath, content: entry, }; fs.appendFileSync(APPLIED_FILE, JSON.stringify(record) + '\n', 'utf8'); return record.change_id; } ``` ```js // scripts/apply.js:144-169 const approved = proposals.filter(p => p.status === 'approved'); const newPending = loadPending(); for (const proposal of approved) { const threshold = AUTO_APPLY_THRESHOLD ...[truncated 2452 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Never accept a target path from proposal state. 2. Derive the target internally from a fixed mapping, such as: - Tier 1 → `MEMORY.md` - Tier 2 → `CLAUDE.md` - Tier 3 → `SOUL.md` 3. Resolve the canonical path and require an exact match against an allowlist. 4. Verify that the target remains inside the expected workspace root after path normalization. 5. Reject absolute paths, `..` components, alternate path separators, symbolic links, and unexpected file types. 6. Validate proposals using a strict schema before processing. 7. Recompute approval eligibility from trusted records instead of trusting stored status and confidence. 8. Integrity-protect state files with signatures or authenticated records. 9. Use atomic, permission-restricted state updates. 10. Require operator confirmation for every write to persistent agent instruction files. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/rollback.js:72
Finding
Rollback Ledger Permits State-Controlled Arbitrary File Copy and Overwrite<![CDATA[ ## Vulnerability Details **File Location**: `scripts/rollback.js:17-92` **Vulnerability Type**: Arbitrary source and destination path trust in rollback operation **Risk Level**: Medium ### Vulnerable Code ```js // scripts/rollback.js:17-24 function loadApplied() { if (!fs.existsSync(APPLIED_FILE)) return []; return fs.readFileSync(APPLIED_FILE, 'utf8') .trim().split('\n').filter(Boolean) .map(l => { try { return JSON.parse(l); } catch { return null; } }) .filter(Boolean); } ``` ```js // scripts/rollback.js:72-92 if (args.id) { target = applied.find(e => e.change_id === args.id && !e.reverted_at); if (!target) { process.stdout.write(`change ${args.id} not found or already reverted`); return; } } else { target = [...applied].reverse().find(e => !e.reverted_at); if (!target) { process.stdout.write('no non-reverted changes found'); return; } } if (!target.snapshot || !fs.existsSync(target.snapshot)) { process.stdout.write( `snapshot not found for change ${target.change_id}. ` + `Target was: ${target.target}. Manual recovery required.` ); return; } const targetFile = path.join(process.cwd(), target.target); fs.copyFileSync(target.snapshot, targetFile); ``` ### Technical Analysis Rollback records are read from the writable `.reflect/applied.jsonl` ledger. The code trusts both `target.snapshot` and `target.target` without validation. The source path is not required to be beneath `.reflect/snapshots`, and the destination path is not required to be one of the supported persistent files. `path.join` does not prevent traversal outside the workspace. The ledger is described as append-only but is neither authenticated nor made immutable. Unlike the automatic apply path, exploitation requires the rollback command to be invoked. However, rollback is an explicitly documented operator command, making social or workflow-triggered execution plausible. ### Attack Path 1. Modify `.reflect/applied.jsonl`. ...[truncated 1002 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Store immutable snapshot identifiers rather than arbitrary source paths. 2. Resolve snapshots strictly beneath `.reflect/snapshots`. 3. Require rollback targets to exactly match a fixed allowlist. 4. Canonicalize source and destination paths and reject traversal or absolute paths. 5. Reject symbolic links for both source and destination. 6. Authenticate ledger records and verify their integrity before rollback. 7. Confirm that the snapshot filename and target correspond to the same original change. 8. Display the canonical source and destination and require explicit operator confirmation. 9. Use protected permissions for snapshots and ledger files. 10. Add tests for `../`, absolute-path, symlink, and forged-ledger attacks. ]]>

T07 · Tool Hijacking and Spoofing

Error
Location
hooks/session-end.js:15
Finding
Executable Maintenance Scripts Are Copied into Writable Runtime State and Recommended for Later Execution<![CDATA[ ## Vulnerability Details **File Locations**: - `hooks/session-end.js:15-29` - `SKILL.md:96-113` - `hooks/user-prompt-submit.js:38-61` **Vulnerability Type**: Local tool replacement through mutable script copies **Risk Level**: High ### Vulnerable Code ```js // hooks/session-end.js:15-29 function ensureScripts() { const dest = path.join(REFLECT_DIR, 'scripts'); if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true }); const scripts = ['observe.js', 'classify.js', 'propose.js', 'evaluate.js', 'apply.js', 'rollback.js', 'status.js']; for (const s of scripts) { const src = path.join(SCRIPTS_DIR, s); const dst = path.join(dest, s); if (fs.existsSync(src) && !fs.existsSync(dst)) { fs.copyFileSync(src, dst); } } } ``` ```js // hooks/user-prompt-submit.js:43-55 if (pendingCount > 0) { lines.push( `[reflect] ${pendingCount} improvement proposal${pendingCount > 1 ? 's' : ''} ` + `awaiting your approval. Run: node .reflect/scripts/status.js --pending` ); } if (recent.length > 0) { lines.push( `[reflect] ${recent.length} learning${recent.length > 1 ? 's' : ''} applied in the ` + `last 24h. Run: node .reflect/scripts/status.js --history` ); } ``` ```bash # SKILL.md:96-113 node .reflect/scripts/status.js node .reflect/scripts/status.js --pending node .reflect/scripts/apply.js --id <proposal-id> --approve node .reflect/scripts/apply.js --id <proposal-id> --reject node .reflect/scripts/rollback.js node .reflect/scripts/status.js --history ``` ### Technical Analysis The Skill copies executable JavaScript into `.reflect/scripts`, even though `.reflect/` is explicitly writable runtime state. Existing destination scripts are not refreshed or checked: ```js if (fs.existsSync(src) && !fs.existsSync(dst)) ``` Therefore, once a copied script exists, any process capable of modifying workspace state can replace it. The Skill documentation and prompt hook then direct the operator or ag ...[truncated 1326 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not copy executable code into `.reflect/` or any other writable state directory. 2. Execute maintenance scripts directly from the installed, integrity-verified Skill directory. 3. Keep code and runtime data in separate locations with different permissions. 4. Update all documentation and status messages to reference immutable installed script paths. 5. If script copying is unavoidable, verify cryptographic hashes before every execution and overwrite mismatched copies from a trusted source. 6. Refuse to execute symbolic links or files with unexpected ownership or permissions. 7. Sign released scripts and verify signatures at runtime. 8. Restrict write access to executable directories. 9. Remove existing `.reflect/scripts` copies during migration. ]]>

T07 · Tool Hijacking and Spoofing

Warning
Location
scripts/apply.js:51
Finding
Automatic Apply Executes an Unverified Workspace-Relative Warden Script<![CDATA[ ## Vulnerability Details **File Location**: `scripts/apply.js:51-65` **Vulnerability Type**: Unverified optional component execution **Risk Level**: Medium ### Vulnerable Code ```js // scripts/apply.js:51-65 function snapshotFile(filePath) { if (!fs.existsSync(filePath)) return null; if (!fs.existsSync(SNAPSHOTS_DIR)) fs.mkdirSync(SNAPSHOTS_DIR, { recursive: true }); const ts = new Date().toISOString().replace(/[:.]/g, '-'); const name = path.basename(filePath); const snapshotPath = path.join(SNAPSHOTS_DIR, `${name}.${ts}.bak`); fs.copyFileSync(filePath, snapshotPath); // Try warden snapshot too (best-effort) try { execFileSync(process.execPath, [path.join(process.cwd(), 'skills', 'openclaw-warden', 'warden.js'), 'snapshot'], { cwd: process.cwd(), timeout: 10000 } ); } catch {} return snapshotPath; } ``` ### Technical Analysis Before applying an approved change, the Skill attempts to execute: ```text <workspace>/skills/openclaw-warden/warden.js ``` The code does not verify the file's origin, version, ownership, signature, or cryptographic hash. Because the path is workspace-relative, a planted or modified script at that location is treated as the legitimate optional dependency. `execFileSync` safely avoids shell-string injection, but it still directly executes the selected JavaScript file through Node.js. The best-effort exception handler suppresses evidence of execution failure and does not mitigate malicious execution. Because `snapshotFile` is reached during automatic proposal application, exploitation does not necessarily require the operator to invoke the component manually. ### Attack Path 1. Create or replace `skills/openclaw-warden/warden.js` in the workspace. 2. Insert attacker-controlled JavaScript into that file. 3. Cause a proposal to reach the automatic apply threshold. 4. The SessionEnd pipeline invokes `apply.js --auto`. 5. `snapshotFile` runs before the target change is appended. 6. ...[truncated 565 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not discover executable dependencies from writable workspace-relative paths. 2. Require explicit configuration before enabling the optional warden integration. 3. Resolve the dependency from an immutable, trusted installation directory. 4. Pin a supported version and verify a cryptographic hash or digital signature before execution. 5. Validate ownership, permissions, file type, and canonical path. 6. Reject symbolic links and unexpected filesystem locations. 7. Prefer an authenticated library interface over executing arbitrary JavaScript files. 8. Log integration failures clearly rather than silently suppressing all exceptions. 9. Run optional components with a restricted environment and minimum filesystem permissions. ]]>
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
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (19)

Missing User Warnings

Medium
Confidence
95% confidence
Finding
This skill explicitly instructs autonomous agents to create and send a cryptocurrency payment to a local payment API, but it does not require an explicit operator confirmation or present a strong warning that real funds may be transferred. In an agent ecosystem, even 'voluntary' payment instructions can become unsafe because agents may interpret them as actionable operational steps and initiate unauthorized transfers if they have wallet capability.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The README explicitly states that tool outcomes are logged automatically and that approved proposals above a threshold are auto-applied, but it does not prominently warn operators that workspace data will be persisted and files may be modified without per-change interactive confirmation. In a self-modifying agent skill, that omission increases the chance of unintended data retention or silent configuration drift, especially because the documented behavior targets persistent files like MEMORY.md and CLAUDE.md.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The README says proposals are sent to a separate Claude evaluator invocation and later lists ANTHROPIC_API_KEY as a requirement, but it does not clearly warn that proposal content and possibly workspace-derived context may be transmitted to an external service. That creates a real privacy and security risk if logs, error messages, file excerpts, or other sensitive workspace content are included in evaluator requests without operator awareness.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill can automatically modify persistent files such as MEMORY.md and CLAUDE.md across sessions, but the description does not foreground this as a prominent warning. That creates a meaningful consent and integrity risk because operators may install or enable the skill without realizing it can alter long-lived behavioral instructions, potentially causing silent drift or persistence of bad changes.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The hook reads environment-derived values such as session identifiers, tool input, tool output, and exit metadata, then persists some of that information to disk. Environment variables in agent/tooling contexts often carry sensitive operational context, and storing them without warning or strict minimization can leak session correlation data and user activity history to anyone with filesystem access.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The hook persistently logs tool input summaries and output-derived error patterns to a local file without any disclosure, consent, or filtering robust enough to prevent sensitive data capture. Even though the code attempts to minimize data, the first 150 characters of tool input and normalized error lines can still contain secrets, personal data, file paths, prompts, or proprietary content, creating a privacy and data-retention risk.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
This hook automatically creates files in the workspace, copies executable scripts into .reflect/scripts, and later runs an apply step with '--auto' at session end without any explicit user confirmation. In a self-modifying or proposal-driven pipeline, that means code or workspace state can be changed implicitly, reducing user visibility and increasing the chance of unintended or unsafe modifications being persisted.

Intent-Code Divergence

Medium
Confidence
86% confidence
Finding
The script auto-applies changes from proposals.json solely based on status === 'approved' and a confidence threshold, but it does not verify who approved the proposal, whether the source file is trusted, or whether the proposal content is safe for the target path. In this skill context, that means any actor or upstream component able to write or tamper with .reflect/proposals.json can cause automatic modification of repository files, creating an integrity risk and a path for persistence or malicious instruction injection.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
This code auto-applies approved proposals by calling applyProposal when confidence exceeds a threshold, which results in modifying a target file. Although the header comment documents usage and behavior, there is no explicit runtime confirmation or user-facing warning before the write occurs, despite the operation changing user files automatically.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The script packages MEMORY.md excerpts and proposal content into prompts and may send them to Anthropic or OpenAI over the network, but there is no evident consent gate, redaction, or documented purpose limitation in the code path itself. Because proposal samples and memory may contain sensitive operational or user-derived data, this creates a real data-exfiltration risk to third-party services.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The code sends proposal data and MEMORY.md excerpts to remote model providers without any user-facing notice in this script, so operators may not realize local context is being disclosed externally. Lack of transparency increases the chance that sensitive project data is shared unintentionally and undermines informed consent and auditability.

Ssd 3

Medium
Confidence
95% confidence
Finding
The prompt builder broadly includes memory excerpts, proposal fields, and sample inputs, then passes that natural-language bundle to potentially external LLM backends. This significantly increases leakage risk because free-text samples often contain secrets, identifiers, internal procedures, or user content that are hard to sanitize once embedded in prompts.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The script reads API credentials from environment variables and is explicitly designed to contact external model providers and a local inference service. In a test harness this is not inherently malicious, but it does create a real data-exposure and unintended-network-egress risk if users run it without realizing proposal content and environment-derived details may be transmitted off-host.

Context-Inappropriate Capability

Medium
Confidence
98% confidence
Finding
These functions serialize constructed test proposals and send them directly to Anthropic and OpenAI over HTTPS. The payload includes local filesystem paths, platform details, sample commands, and operational context, so running the test can leak internal environment information to third parties without any inline notice or consent gate.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The built messages contain Windows usernames, local paths, localhost service details, and sample command inputs, and this script can send them to external APIs without a user-facing warning. Even though the data is synthetic test material, it embeds realistic environment identifiers that can disclose workstation structure and usage patterns.

Intent-Code Divergence

Low
Confidence
76% confidence
Finding
The line states that Tier 3 changes require operator approval always, but elsewhere the manifest explicitly grants `propose: SOUL.md`, which implies the skill can generate change proposals for that file. This is not a direct contradiction in executable code, but it is an intent-level inconsistency in the documented handling of high-impact changes.

Missing User Warnings

Low
Confidence
88% confidence
Finding
The code reads ANTHROPIC_API_KEY and OPENAI_API_KEY from the environment to enable remote evaluation, but provides no warning or disclosure that credentials are being consumed for outbound requests. Under this rule, access to sensitive environment variables should have some visible explanation when no other disclosure is present in the file.

Missing User Warnings

Low
Confidence
87% confidence
Finding
After evaluating pending proposals, the script writes updated results back to .reflect/proposals.json. Although this is part of the program flow, there is no explicit warning, confirmation, or prior disclosure near the write operation that local proposal state will be modified.

Context-Inappropriate Capability

Low
Confidence
80% confidence
Finding
The script probes a local Ollama endpoint for installed models and sends prompts to it over HTTP. Even though this is local-host communication, it is still an active network capability that is not justified by any available manifest purpose.

Static analysis

Detected: suspicious.dangerous_exec, suspicious.env_credential_access

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
hooks/session-end.js:35

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/apply.js:61

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/hook-pipeline.js:34

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/evaluate.js:30

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
test/run-eval-test.js:28