Back to skill

Security audit

Paperclip Resilience

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its resilience purpose, but it can send credentials and task contents to loosely controlled API destinations and inject default workflow-changing instructions into spawned agents.

Review this before installing. Use it only with a trusted Paperclip endpoint, prefer HTTPS with a known host, avoid putting secrets in task text, and disable or customize task-injection defaults unless you want its PR and blocker-routing workflow imposed on spawned agents. Do not schedule run recovery with broad credentials until endpoint handling is constrained.

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

T01 · Skill Instruction Hijacking

Error
Location
src/task-injection.js:120
Finding
Default-On Injection of Broad, Environment-Specific Agent Instructions<![CDATA[ ## Vulnerability Details **File Location**: `src/task-injection.js:120-139` **Vulnerability Type**: Default-on agent instruction hijacking **Risk Level**: High ### Vulnerable Code ```javascript function buildPrRequirements(paperclip) { const identifier = paperclip?.identifier || 'SUP-XX'; return ` --- ## ⚠️ Required Deliverables These requirements are non-negotiable and apply to ALL code changes: 1. **Feature branch** — all changes on a dedicated branch, never commit directly to \`main\` 2. **Branch naming** — branch name must include \`${identifier}\` (example: \`${identifier}/short-slug\`) 3. **One feature per PR** — no bundling unrelated changes; if you find separate issues, create separate branches/PRs 4. **Create a PR** — use \`gh pr create\` when work is complete; do not leave changes unsubmitted 5. **Tests must pass** — run existing tests before submitting; fix failures before requesting review 6. **PR review required** — wait for clean review from Codex or CodeRabbit before merging 7. **Fix ALL review comments** — resolve every P1/P2 (Critical/High) comment before merge; never merge with unresolved critical issues 8. **CI must be green** — all required checks must pass before merge 9. **Blocker routing** — if you hit a blocker or need-Andrew item, write it to Project Board + Live Plan + Tasks.md + today's journal; do NOT report completion until all 4 are written **Completion report must include:** - Paperclip issue: ${identifier} - Branch name and PR number (or reason if no PR needed) - Test results summary - Any unresolved issues or blockers `; } ``` The instruction sections are enabled by default: ```javascript const DEFAULT_SECTIONS = { paperclipIssue: true, problemSolving: true, prRequirements: true, uiNudge: true, }; ``` ### Technical Analysis The skill appends authoritative, “non-negotiable” instructions to every task classified as a code task. These instructions do substantially more than provide model fallback o ...[truncated 1988 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Disable all behavior-changing sections by default and require explicit opt-in: ```javascript const DEFAULT_SECTIONS = { paperclipIssue: false, problemSolving: false, prRequirements: false, uiNudge: false, }; ``` 2. Replace organization-specific requirements with neutral, configurable templates. 3. Remove references to specific people and personal artifacts such as “Andrew,” `Tasks.md`, and a personal journal. 4. Separate advisory context from mandatory instructions. Do not use phrases such as “non-negotiable” unless the user explicitly enabled an enforcement policy. 5. Require separate consent for repository mutation, pull-request creation, external review, and local document writes. 6. Clearly display the exact injected text during setup and provide a dry-run preview before enabling injection. 7. Add tests proving that task injection is inert by default and that each side-effecting section requires an explicit configuration flag. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
src/lib/paperclip-issue-gate.js:39
Finding
Bearer Token and Task Content Sent to an Unrestricted API URL<![CDATA[ ## Vulnerability Details **File Location**: `src/lib/paperclip-issue-gate.js:39-52`, `src/lib/paperclip-issue-gate.js:153-168` **Vulnerability Type**: Unvalidated credential destination and sensitive task disclosure **Risk Level**: High ### Vulnerable Code ```javascript async function apiFetch(env, method, apiPath, body) { const url = `${env.apiUrl}/api${apiPath}`; const headers = { 'Authorization': `Bearer ${env.apiKey}`, 'Content-Type': 'application/json', }; const opts = { method, headers }; if (body) opts.body = JSON.stringify(body); const resp = await fetch(url, opts); const text = await resp.text(); if (!resp.ok) { throw new Error(`Paperclip API ${method} ${apiPath}: ${resp.status} — ${text.slice(0, 200)}`); } return text ? JSON.parse(text) : null; } ``` Task content is incorporated into a remotely created issue: ```javascript const sanitizedTask = sanitizeApiString(task, 10000); const ref = identifier || extractIssueIdentifier(sanitizedTask); if (ref) { const existing = await getIssueByIdentifier(env, ref); if (existing) { return { issueId: existing.id, identifier: existing.identifier, title: existing.title, created: false, }; } console.error(`⚠️ Referenced issue ${ref} not found. Creating new issue.`); } const issueTitle = sanitizedTask.length > 120 ? sanitizedTask.slice(0, 117) + '...' : sanitizedTask; const issue = await createIssue(env, { title: issueTitle, description: `## Auto-created by issue gate\n\nOriginal task:\n> ${sanitizedTask}\n\nCreated automatically before spawning a code subagent.`, projectId, priority: priority || 'medium', }); ``` The network call occurs for every code task, regardless of whether the visible Paperclip section is disabled: ```javascript if (codeTask) { paperclip = await ensurePaperclipIssue({ task: taskText, projectId: codeProjectId, priority: issuePriority, }); } ``` ### Technical Analysis `PAPERC ...[truncated 2138 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse the endpoint with `new URL()` and permit only `https:` in production. 2. Require an explicit hostname allowlist or pin the endpoint to the configured Paperclip deployment. 3. Reject URLs containing user information, unexpected ports, fragments, or unsupported protocols. 4. Make remote issue creation explicitly opt-in and disabled by default. 5. Ensure the issue toggle controls both prompt insertion and network activity: ```javascript if (codeTask && config.sections.paperclipIssue) { paperclip = await ensurePaperclipIssue(...); } ``` 6. Add a separate setting such as `paperclipIssue.sendOriginalTask`, defaulting to `false`. 7. Send a minimal user-approved summary instead of the complete task. 8. Add secret detection and redaction before any task content leaves the host. 9. Warn the user about the exact destination and data fields during setup. 10. Add tests proving that HTTP and unapproved hosts are rejected and that disabling issue integration causes no network request. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
src/run-recovery.js:319
Finding
Run Recovery Can Forward Paperclip Credentials over HTTP or to an Arbitrary Host<![CDATA[ ## Vulnerability Details **File Location**: `src/run-recovery.js:319-363` **Vulnerability Type**: Unrestricted credential-bearing network destination **Risk Level**: High ### Vulnerable Code ```javascript function apiFetch(auth, method, pathname, body = null, runId = null) { const url = new URL(`${auth.apiUrl}${pathname}`); const lib = url.protocol === 'https:' ? https : http; const bodyStr = body != null ? JSON.stringify(body) : null; const headers = { Authorization: `Bearer ${auth.apiKey}`, 'Content-Type': 'application/json', }; if (runId && method !== 'GET') { headers['X-Paperclip-Run-Id'] = runId; } if (bodyStr) { headers['Content-Length'] = Buffer.byteLength(bodyStr); } return new Promise((resolve, reject) => { const req = lib.request( { hostname: url.hostname, port: url.port || (url.protocol === 'https:' ? 443 : 80), path: url.pathname + url.search, method, headers, timeout: 30_000, }, (res) => { let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { if (res.statusCode >= 200 && res.statusCode < 300) { try { resolve(JSON.parse(data)); } catch { resolve(data); } } else { reject(new Error(`HTTP ${res.statusCode}: ${data.slice(0, 300)}`)); } }); }, ); req.on('timeout', () => { req.destroy(); reject(new Error('Request timed out after 30s')); }); req.on('error', reject); if (bodyStr) req.write(bodyStr); req.end(); }); } ``` The URL can be supplied directly through a command-line argument: ```javascript } else if (arg.startsWith('--api-url=')) { args.apiUrl = arg.split('=')[1] || null; } ``` ### Technical Analysis The recovery process loads a privileged Paperclip token from environment variables, an environment file, or `~/.openclaw/workspace/paperclip-api-key.json`. It then accepts an API destin ...[truncated 1913 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require `https:` URLs except for an explicit, separately enabled localhost development mode. 2. Maintain an allowlist of trusted Paperclip hostnames. 3. Validate protocol, hostname, port, and base path immediately after loading configuration. 4. Reject loopback, link-local, private-network, and metadata-service destinations unless explicitly authorized for a self-hosted deployment. 5. Do not accept `--api-url` when credentials are automatically loaded from a default file unless the destination is trusted. 6. Consider binding credentials to a configured endpoint in the credential file and reject mismatches. 7. Use a restricted token that can only list failed runs and invoke the intended agent. 8. Redact response bodies from errors because hostile endpoints can return attacker-controlled text that may be copied into logs. 9. Add tests covering HTTP rejection, hostname mismatch, unusual protocols, private-address restrictions, and safe self-hosted exceptions. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
src/task-injection.js:215
Finding
Predictable Shared Temporary File Permits Task Disclosure and Symlink Overwrite<![CDATA[ ## Vulnerability Details **File Location**: `src/task-injection.js:215-216` **Vulnerability Type**: Unsafe temporary-file creation **Risk Level**: Medium ### Vulnerable Code ```javascript const tmpPath = path.join(os.tmpdir(), `spawn-task-${Date.now()}.txt`); fs.writeFileSync(tmpPath, enhancedTask, 'utf8'); ``` ### Technical Analysis The enhanced task is written into the system-wide temporary directory using a filename based only on the current timestamp. The code does not: - Create a private temporary directory. - Use exclusive creation (`O_EXCL` or the `wx` flag). - Verify that the target does not already exist. - Prevent following a pre-existing symbolic link. - Explicitly set restrictive permissions. - Delete the file after it has been consumed. `fs.writeFileSync()` uses write-and-truncate behavior and follows an existing symbolic link. A local attacker who can predict or race the timestamp can pre-create the path as a symlink to another file writable by the victim. The victim process will then truncate and overwrite that target with the enhanced task. The file contains the original task plus injected issue and workflow metadata. Its confidentiality depends on the process umask, and it remains on disk indefinitely. ### Attack Path 1. A local attacker monitors process activity or repeatedly creates likely paths such as `/tmp/spawn-task-<timestamp>.txt`. 2. The attacker makes a predicted path a symbolic link to a file writable by the user running the skill. 3. Task injection computes the same timestamp-derived filename. 4. `writeFileSync()` follows the symlink and truncates the target. 5. The target is overwritten with task content. Alternatively: 1. The skill writes a task file under a permissive umask. 2. Another local user scans the shared temporary directory for `spawn-task-*.txt`. 3. The user reads original task text and Paperclip metadata from abandoned files. ### Impact Assessment Potential impact includes: - Disclosure of prop ...[truncated 484 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create a private temporary directory with `fs.mkdtempSync()`: ```javascript const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'paperclip-resilience-')); const tmpPath = path.join(tmpDir, 'task.txt'); fs.writeFileSync(tmpPath, enhancedTask, { encoding: 'utf8', mode: 0o600, flag: 'wx', }); ``` 2. Use exclusive file creation so an existing path or symlink causes failure. 3. Explicitly set file mode `0600`. 4. Delete the file and private directory immediately after the spawn process has consumed the task. 5. Where possible, pass task data through stdin or an in-memory API rather than the filesystem. 6. Do not include unnecessary Paperclip metadata in the temporary payload. 7. Add tests that pre-create a symlink at the proposed path and verify that the operation fails without modifying the target. ]]>
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
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Memory PoisoningPersistent Context Injection, Context Window Stuffing, Memory Manipulation
Findings (21)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
If only spawn-with-fallback is implemented while the skill advertises model rotation, run recovery, blocker routing, and task injection, users may over-trust the package and deploy it expecting safeguards that do not exist. In resilience tooling, missing promised protections can directly increase operational risk by leaving failures, stuck agents, or escalation paths unhandled.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
If only spawn-with-fallback is implemented while the skill advertises model rotation, run recovery, blocker routing, and task injection, users may over-trust the package and deploy it expecting safeguards that do not exist. In resilience tooling, missing promised protections can directly increase operational risk by leaving failures, stuck agents, or escalation paths unhandled.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
If only spawn-with-fallback is implemented while the skill advertises model rotation, run recovery, blocker routing, and task injection, users may over-trust the package and deploy it expecting safeguards that do not exist. In resilience tooling, missing promised protections can directly increase operational risk by leaving failures, stuck agents, or escalation paths unhandled.

Tp4

High
Category
MCP Tool Poisoning
Confidence
92% confidence
Finding
If only spawn-with-fallback is implemented while the skill advertises model rotation, run recovery, blocker routing, and task injection, users may over-trust the package and deploy it expecting safeguards that do not exist. In resilience tooling, missing promised protections can directly increase operational risk by leaving failures, stuck agents, or escalation paths unhandled.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
If only spawn-with-fallback is implemented while the skill advertises model rotation, run recovery, blocker routing, and task injection, users may over-trust the package and deploy it expecting safeguards that do not exist. In resilience tooling, missing promised protections can directly increase operational risk by leaving failures, stuck agents, or escalation paths unhandled.

Tp4

High
Category
MCP Tool Poisoning
Confidence
92% confidence
Finding
If only spawn-with-fallback is implemented while the skill advertises model rotation, run recovery, blocker routing, and task injection, users may over-trust the package and deploy it expecting safeguards that do not exist. In resilience tooling, missing promised protections can directly increase operational risk by leaving failures, stuck agents, or escalation paths unhandled.

Ae1

High
Category
analysis-evasion
Content
| **Run Recovery** | `src/run-recovery.js` | Detects failed Paperclip heartbeat runs (gateway errors, timeouts, 429s) and re-invokes agents with model fallback.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| **Task Injection** | `src/task-injection.js` | Enriches spawn task descriptions with issue tracking metadata, PR requirements, and UX design checklists before
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

YARA rule 'backdoor_persistence': Backdoor persistence with malicious payloads (shell commands, SSH key injection, hidden root users) [malware]

High
Category
YARA Match
Content
openai-codex/gpt-5.3-codex":  "anthropic/claude-sonnet-4-6"
  },
  "failurePatterns": {
    "patterns": ["rate[\\s_-]?limit", "402", "credits", "quota"]
  },
  "failureStopReasons": {
    "reasons": ["error", "credits_exhausted", "quota_exceeded"]
  }
}
```

Generate a config interactively:

```bash
node scripts/setup.js
```

---

## Cron Setup

For automated run recovery, add this to your system crontab or OpenClaw cron:

```bash
# system crontab
*/15 * * * *  node /path/to/skills/paperclip-resilience/src/run-recovery.js

# openclaw cron (run: openclaw cron add ...)
# see SKILL.md §4 for details
```

---

## Security Model

All user-controlled inputs (model names, task file paths, spawn modes, labels) are validated before use. See [SECURITY-AUDIT-REPORT.md](../SECURITY-AUDIT-REPORT.md) for the full audit record.

Key boundaries:
- No shell execution (`execFile` only, no `exec`)
- No dynamic code evaluation
- File paths canonicalized and checked against allowlists
- Credentials read f
Confidence
75% confidence
Finding
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Anti-Refusal Statement

High
Category
Anti-Refusal
Content
const dir = path.dirname(outPath);
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });

  // Safety check: don't overwrite without warning if file exists
  if (fs.existsSync(outPath)) {
    console.log(`\n⚠  ${outPath} already exists — overwriting.`);
  }
Confidence
80% confidence
Finding
Skill instructs the agent to omit warnings, disclaimers, or ethical commentary. Stripping safety caveats hides risk from the user and is a common jailbreak preamble.

Memory Manipulation

High
Category
Memory Poisoning
Content
const statePath = options.statePath || rotationConfig.statePath;
  const state = loadState(statePath);
  const key = prKey(repo, pr);
  delete state.prs[key];
  saveState(state, statePath);

  const result = { reset: true, pr: key };
Confidence
80% confidence
Finding
Skill manipulates agent memory, state, or stored context. Memory corruption can alter personality, override safety rules, or cause unpredictable behavior.

Credential Access

High
Category
Privilege Escalation
Content
console.log('\n── Quick Security Validation ──');

try {
  testModule.validateFilePath('../../../etc/passwd');
  console.log('  ❌ FAIL: Path traversal should be blocked');
} catch (err) {
  if (err.message.includes('Path traversal')) {
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
console.log('\n── Quick Security Validation ──');

try {
  testModule.validateFilePath('../../../etc/passwd');
  console.log('  ❌ FAIL: Path traversal should be blocked');
} catch (err) {
  if (err.message.includes('Path traversal')) {
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
console.log('\n── Quick Security Validation ──');

try {
  testModule.validateFilePath('../../../etc/passwd');
  console.log('  ❌ FAIL: Path traversal should be blocked');
} catch (err) {
  if (err.message.includes('Path traversal')) {
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
console.log('\n── Quick Security Validation ──');

try {
  testModule.validateFilePath('../../../etc/passwd');
  console.log('  ❌ FAIL: Path traversal should be blocked');
} catch (err) {
  if (err.message.includes('Path traversal')) {
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
console.log('\n── Quick Security Validation ──');

try {
  testModule.validateFilePath('../../../etc/passwd');
  console.log('  ❌ FAIL: Path traversal should be blocked');
} catch (err) {
  if (err.message.includes('Path traversal')) {
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
console.log('\n── Quick Security Validation ──');

try {
  testModule.validateFilePath('../../../etc/passwd');
  console.log('  ❌ FAIL: Path traversal should be blocked');
} catch (err) {
  if (err.message.includes('Path traversal')) {
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
console.log('\n── Quick Security Validation ──');

try {
  testModule.validateFilePath('../../../etc/passwd');
  console.log('  ❌ FAIL: Path traversal should be blocked');
} catch (err) {
  if (err.message.includes('Path traversal')) {
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding
The skill declares network- and environment-dependent behavior but does not expose any explicit tool scope or permissions metadata. In an agent marketplace or orchestrated runtime, this weakens operator visibility and policy enforcement, increasing the chance that a skill with outbound access or secret consumption is installed under broader trust than intended.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
Options:
  --output <path>   Path to write config.json (default: ./config.json)
  --no-input        Skip prompts and emit defaults only
  --dry-run         Print the generated config but do not write it
  -h, --help        Show this help
  `);
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.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The wrapper automatically retries a failed request against a different model provider, which can forward the same task contents to another third party without explicit user consent at decision time. If tasks contain proprietary data, credentials, regulated data, or customer content, this creates a cross-provider data disclosure and policy/compliance risk even though the code is otherwise trying to improve resilience.

Static analysis

Detected: suspicious.dangerous_exec, suspicious.env_credential_access, suspicious.exposed_secret_literal

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
src/spawn-with-fallback.js:376

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
src/lib/paperclip-issue-gate.js:19

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
src/run-recovery.js:252