Back to skill

Security audit

X Bookmarks Archiver

Security checks for vulnerabilities and agentic risk

Overview

The skill is coherent but needs review because it processes private X bookmarks, may send bookmark URLs and tweet text to OpenAI when an API key is present, and has an overbroad URL-expansion request path.

Install only if you are comfortable giving the skill access to your X bookmarks through bird and, when OPENAI_API_KEY is set, sending bookmark URLs and tweet text to OpenAI for summarization. Prefer running without OPENAI_API_KEY for local-only fallback summaries, review the generated X-knowledge files and state directory permissions, and treat the t.co expansion behavior as needing a fix before use in sensitive networks.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/process.cjs:50
Finding

Arbitrary HTTPS Requests Caused by Inadequate t.co Host Validation

Content
View full analysis
{ if (!url.includes('t.co/')) { resolve(url); return; } const req = https.get(url, { method: 'HEAD' }, (res) => { resolve(res.headers.location || url); }); req.on('error', () => resolve(url)); req.setTimeout(10000, () => { req.destroy(); resolve(url); }); }); } ``` ### Technical Analysis The function is intended to expand links hosted by the `t.co` URL-shortening service. However, it verifies the URL with a substring search: ```javascript url.includes('t.co/') ``` This does not establish that the parsed hostname is actually `t.co`. The string can appear in a path, user-information component, query string, or attacker-controlled hostname. For example, the following URL passes the check even though its host is not `t.co`: ```text https://internal.example/t.co/probe ``` The function then passes the complete untrusted URL to `https.get()`, causing the host running the Skill to issue a HEAD request to the attacker-selected HTTPS destination. Because response bodies are not read and redirects are not automatically followed by this code, the exposure is narrower than a fully featured SSRF vulnerability. Nevertheless, it provides a network-probing primitive and exceeds the minimum privilege needed to contact only the legitimate `t.co` service. ### Attack Path 1. An attacker causes a crafted URL to appear in an X bookmark or writes a crafted bookmark object into the pending state file. 2. `extractUrl()` extracts the attacker-controlled URL. 3. The URL contains the substring `t.co/`, but its actual hostname points to an internal or otherwise attacker-selected service. 4. `expandUrl()` accepts the URL because it uses ...[truncated 893 chars]
Remediation
View remediation
{ let parsed; try { parsed = new URL(url); } catch { resolve(url); return; } if ( parsed.protocol !== 'https:' || parsed.hostname.toLowerCase() !== 't.co' || parsed.username || parsed.password ) { resolve(url); return; } const req = https.get(parsed, { method: 'HEAD' }, (res) => { const location = res.headers.location; if (!location) { resolve(url); return; } try { resolve(new URL(location, parsed).toString()); } catch { resolve(url); } }); req.on('error', () => resolve(url)); req.setTimeout(10000, () => { req.destroy(); resolve(url); }); }); } ``` Additional hardening should include: 1. Resolve the destination host and reject loopback, private, link-local, multicast, and reserved address ranges. 2. Revalidate the remote address after DNS resolution to mitigate DNS rebinding. 3. Reject embedded credentials and non-HTTPS protocols. 4. Limit redirects and validate every redirect destination before contacting it. 5. Add tests for deceptive values such as: - `https://example.com/t.co/test` - `https://t.co.attacker.example/test` - `https://t.co@attacker.example/test` - private and loopback destinations 6. Prefer a dedicated URL-expansion implementation with a strict outbound-host policy. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/process.cjs:180
Finding

Untrusted AI Metadata Is Written to Markdown Without Structural Sanitization

Content
View full analysis
`"${t}"`).join(', ')}] --- ${metadata.summary} `; ``` ### Technical Analysis Bookmark text is untrusted input and can contain instructions designed to manipulate the language model. The prompt does not isolate untrusted content or explicitly require the model to disregard instructions contained in the tweet. Although the API requests JSON output, the parsed response is not validated against a strict schema. The implementation assumes that: - `title` is a string - `summary` is a string - `tags` is an array of safe strings - None of the values contains line breaks or YAML/Markdown control syntax Only double quotes in the title are escaped. Newline characters and other YA ...[truncated 1964 chars]
Remediation
View remediation
typeof tag === 'string')) { throw new Error('Invalid metadata schema'); } return { title: value.title.slice(0, 60), summary: value.summary.slice(0, 2000), tags: value.tags.slice(0, 5).map(tag => tag.slice(0, 50)) }; } ``` 2. Use a maintained YAML serializer rather than manually constructing frontmatter. Serialization must safely quote line breaks, quotation marks, backslashes, and YAML control characters. 3. Normalize scalar fields by removing control characters. For fields intended to occupy one YAML line, reject or replace carriage returns and line feeds. 4. Sanitize or escape the summary according to the capabilities of the downstream Markdown renderer. Disable raw HTML unless it is explicitly required. 5. Treat bookmarked text as data, not instructions. Delimit it clearly and tell the model not to follow instructions found inside it. This reduces, but does not eliminate, prompt-injection risk. 6. Validate model output independently of model instructions. Do not rely on `response_format` alone as a security boundary. 7. Add adversarial tests covering: - Titles containing newlines and `---` - Tags containing quotes, brackets, commas, and newlines - Summaries containing raw HTML and external-resource syntax - Tweet text that instructs the model to emit additional frontmatter - Non-string and oversized model response fields ]]>
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
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (8)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The skill says OpenAI is used for AI-generated summaries, but it does not clearly warn users that bookmark content, linked URLs, tweet text, and related metadata may be transmitted to a third-party API. Because bookmarks can contain sensitive research, private interests, or confidential work links, this omission can cause unintended data disclosure during normal use.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The script sends bookmark URLs and associated tweet text to a third-party OpenAI-compatible API for summarization. Even if intended for functionality, this is a real data exposure risk because bookmark contents may include sensitive links, private research topics, or personal context, and the transfer happens automatically when an API key is present.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
89% confidence
Finding

The fetch call to api.openai.com performs a direct external transmission of bookmark-derived content. In this skill's context, outbound transfer is more sensitive because the script processes user bookmarks and tweet text, which may reveal interests, projects, or internal references not meant for third parties.

Content

Scanner excerpt · scripts/process.cjs (reported line 141)May include surrounding context.

js
}`;

  try {
    const result = await fetch('https://api.openai.com/v1/chat/completions', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${apiKey}`,

External Transmission

Medium
Category
Data Exfiltration
Confidence
89% confidence
Finding

The fetch call to api.openai.com performs a direct external transmission of bookmark-derived content. In this skill's context, outbound transfer is more sensitive because the script processes user bookmarks and tweet text, which may reveal interests, projects, or internal references not meant for third parties.

Content

Scanner excerpt · scripts/process.cjs (reported line 141)May include surrounding context.

js
}`;

  try {
    const result = await fetch('https://api.openai.com/v1/chat/completions', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${apiKey}`,

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

Bookmark content is transmitted to an external AI service without an in-context warning or consent check at the point of use. This is dangerous because users may not realize their saved URLs and tweet text are leaving the local workspace, which can violate privacy expectations or organizational handling rules.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
91% confidence
Finding

The documentation describes output paths and state files, but it does not present this behavior as a clear operational warning before execution. Users may not realize that running the skill persistently writes categorized markdown archives and tracking JSON files to local workspace and state directories, which can expose browsing/bookmark history to other local users, backups, or synced folders.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
76% confidence
Finding

The module reads an environment variable to determine where bookmark state will be stored and then uses that path for persistent files, but the code provides no user-facing warning or disclosure about this behavior. For safety auditing, environment-based filesystem persistence should be made visible to users when it affects local data storage.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

The code persists bookmark state to local JSON files using fs.writeFileSync, but there is no confirmation prompt, user-facing log message, or other runtime disclosure that bookmark data is being written to the user's home directory. The surrounding comments are developer-facing and do not warn the user at execution time.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.dangerous_exec, suspicious.env_credential_access, suspicious.install_untrusted_source

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/fetch.cjs:18

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/run.cjs:18

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
tests/test-all.cjs:26

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/process.cjs:21

Install source points to URL shortener or raw IP.

Warn
Code
suspicious.install_untrusted_source
Location
tests/fixtures/sample-bookmarks.json:4