Back to skill

Security audit

Linear Webhook

Security checks for vulnerabilities and agentic risk

Overview

This Linear webhook skill has a real automation purpose, but it mixes untrusted Linear content, local credential reads, and shell/JavaScript command execution in ways that require review before use.

Install only after tightening this skill: remove the mandatory shell/node -e postback instructions, treat Linear content as untrusted data, use a fixed trusted postback path with validation and approval, avoid ~/.linear_api_key reads in favor of scoped secret injection, and limit which Linear projects/users can trigger agents.

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
  • 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
Findings (5)

T01 · Skill Instruction Hijacking

Error
Location
linear-transform.js:119
Finding
Untrusted Linear Content Is Converted into Authoritative Agent Instructions## Vulnerability Details **File Location**: `linear-transform.js`, lines 119–147 **Vulnerability Type**: Prompt injection through untrusted webhook fields **Risk Level**: High ### Vulnerable Code ```javascript const taskLines = [ `🔗 **LINEAR WEBHOOK TASK** 🔗`, ``, `**Issue:** ${issue.identifier} - ${issue.title}`, `**Requested by:** ${commentor}`, `**Mention:** ${mention}`, ``, `**Issue Details:**`, `- Status: ${state}`, `- Priority: ${priority}`, `- Assignee: ${assignee}`, `- Labels: ${labels}`, `- URL: ${issue.url}`, ``, `**Issue Description:**`, `${issue.description || 'No description provided.'}`, ``, `**Comment/Task:**`, `${comment.body}`, ``, `---`, `**⚠️ MANDATORY: After responding, run this command to post back to Linear:**`, `\`\`\``, `LINEAR_API_KEY=$(cat ~/.linear_api_key) node -e "const {postLinearComment} = require('/home/sven/clawd-mason/skills/linear-webhook/linear-transform.js'); postLinearComment('${issue.id}', \\\`YOUR_RESPONSE_HERE\\\`, '${AGENT_NAMES[agentSession] || agentSession}');"`, `\`\`\``, `Replace YOUR_RESPONSE_HERE with your actual response text.`, `**Issue ID for postLinearComment:** ${issue.id}`, `**Agent Name:** ${AGENT_NAMES[agentSession] || agentSession}`, ]; return taskLines.join('\n'); ``` ### Technical Analysis The issue description, title, labels, user name, and comment body originate from the webhook payload and are inserted verbatim into an agent task. They are not delimited as untrusted data, normalized, or governed by a policy instructing the agent not to follow embedded directives. The resulting prompt combines attacker-controlled content with authoritative text, including a mandatory instruction to execute a local command. An attacker able to submit or forge an applicable webhook can place instructions in the comment or issue description that attempt to override the intended task, ...[truncated 1658 chars]
Remediation
## Remediation Suggestions - Treat every webhook field as untrusted quoted data rather than executable instructions. - Wrap external content in explicit data delimiters and add a higher-priority policy stating that instructions inside those delimiters must never be followed. - Require explicit human approval before the agent invokes shell, file-write, credential, or external-posting tools. - Verify Linear webhook signatures using the raw request body and reject missing, invalid, or stale signatures. - Restrict accepted organization, team, issue, and commenter identifiers through allowlists. - Remove the mandatory command-execution instruction from the generated prompt. - Apply field length limits and avoid logging complete issue descriptions or comments. - Add adversarial tests containing prompt-override instructions, tool requests, and fabricated webhook identities.

T09 · Insecure Skill Coding Practices

Error
Location
linear-transform.js:135
Finding
Generated node -e Command Embeds Untrusted Values as JavaScript Source## Vulnerability Details **File Location**: `linear-transform.js`, lines 135–145 **Vulnerability Type**: JavaScript code injection through generated command text **Risk Level**: Critical ### Vulnerable Code ```javascript `---`, `**⚠️ MANDATORY: After responding, run this command to post back to Linear:**`, `\`\`\``, `LINEAR_API_KEY=$(cat ~/.linear_api_key) node -e "const {postLinearComment} = require('/home/sven/clawd-mason/skills/linear-webhook/linear-transform.js'); postLinearComment('${issue.id}', \\\`YOUR_RESPONSE_HERE\\\`, '${AGENT_NAMES[agentSession] || agentSession}');"`, `\`\`\``, `Replace YOUR_RESPONSE_HERE with your actual response text.`, `**Issue ID for postLinearComment:** ${issue.id}`, `**Agent Name:** ${AGENT_NAMES[agentSession] || agentSession}`, ``` ### Technical Analysis The generated command constructs executable JavaScript source by concatenating an issue ID and an eventual agent response into quoted JavaScript literals. No JavaScript-string or shell escaping is applied. A single quote in the issue ID can terminate its string literal. Backticks, `${...}` interpolation, quotes, or other JavaScript syntax in the response can escape the intended template literal. If the command is executed as instructed, injected JavaScript runs with the privileges of the agent process. The command also reads `~/.linear_api_key` and exports it into the spawned process, placing a sensitive credential in the same execution context as attacker-influenced code. The undefined `agentSession` reference currently causes task construction to fail during the normal transform flow. The injection sink nevertheless remains in the shipped implementation and becomes reachable if that scope defect is corrected without redesigning the command. ### Attack Path 1. Attacker-controlled content influences the issue data or the response generated by the agent. 2. The content includes syntax that terminates the intended string or tem ...[truncated 653 chars]
Remediation
## Remediation Suggestions - Remove the generated `node -e` command entirely. - Invoke `postLinearComment` directly from trusted application code after the agent response is complete. - If a subprocess is necessary, use `execFile` or `spawn` with a fixed executable and argument array, with shell processing disabled. - Transfer response text through structured JSON on standard input rather than embedding it in source code or command text. - Validate issue IDs against the exact format returned by Linear. - Keep the Linear credential outside attacker-influenced execution contexts and use a narrowly scoped integration token. - Add tests using quotes, backticks, `${...}`, newlines, semicolons, and shell metacharacters.

T09 · Insecure Skill Coding Practices

Error
Location
post-response.js:21
Finding
Shell Command Injection in Session-History Retrieval## Vulnerability Details **File Location**: `post-response.js`, lines 21–35 **Vulnerability Type**: OS command injection **Risk Level**: Critical ### Vulnerable Code ```javascript async function getSessionHistory(sessionKey) { // This would use Clawdbot API or session logs // For now, placeholder that reads from sessions_send or logs // Example: Use clawdbot CLI const { exec } = require('child_process'); const { promisify } = require('util'); const execAsync = promisify(exec); try { const { stdout } = await execAsync(`clawdbot sessions history ${sessionKey} --json`); return JSON.parse(stdout); } catch (error) { console.error('Failed to fetch session history:', error.message); return null; } } ``` The caller applies the following insufficient validation: ```javascript const match = sessionKey.match(/^linear:(\w+):(.+)$/); ``` ### Technical Analysis `child_process.exec` passes the constructed string through a shell. The externally supplied `sessionKey` is interpolated directly into that string. Although the caller checks the general session-key structure, the issue-ID component uses `(.+)`, which accepts spaces, semicolons, command substitutions, redirection operators, and other shell metacharacters. The regex therefore does not prevent command injection. ### Attack Path 1. An attacker or unsafe automation invokes `post-response.js` with a crafted session key. 2. The value starts with a syntactically accepted prefix such as `linear:mason:`. 3. The remaining value contains shell syntax, for example a command separator followed by another command. 4. The regular expression accepts the value because `(.+)` permits the metacharacters. 5. `execAsync` passes the interpolated command to the shell. 6. The shell executes both the intended `clawdbot` command and the injected command. ### Impact Assessment Successful exploitation provides arbitrary command ...[truncated 229 chars]
Remediation
## Remediation Suggestions - Replace `exec` with `execFile` or `spawn`: ```javascript const { execFile } = require('child_process'); execFile('clawdbot', ['sessions', 'history', sessionKey, '--json'], options, callback); ``` - Do not enable shell processing. - Allowlist valid agent names instead of accepting arbitrary `\w+` values. - Validate issue IDs using a narrow character set and explicit maximum length. - Reject whitespace, control characters, and unexpected delimiters. - Run the response poster under a dedicated least-privileged account. - Add regression tests for semicolons, command substitution, pipes, redirection, newlines, and argument-injection strings.

T09 · Insecure Skill Coding Practices

Error
Location
post-to-linear.sh:5
Finding
Arbitrary JavaScript Injection in post-to-linear.sh## Vulnerability Details **File Location**: `post-to-linear.sh`, lines 5–19 **Vulnerability Type**: JavaScript source injection through shell arguments **Risk Level**: Critical ### Vulnerable Code ```bash ISSUE_ID="$1" AGENT_NAME="$2" RESPONSE="$3" if [ -z "$ISSUE_ID" ] || [ -z "$AGENT_NAME" ] || [ -z "$RESPONSE" ]; then echo "Usage: ./post-to-linear.sh <issue_id> <agent_name> <response_text>" exit 1 fi LINEAR_API_KEY=$(cat ~/.linear_api_key) node -e " const {postLinearComment} = require('/home/sven/clawd-mason/skills/linear-webhook/linear-transform.js'); postLinearComment('$ISSUE_ID', \`$RESPONSE\`, '$AGENT_NAME'); " ``` ### Technical Analysis Quoting shell variables during assignment does not make them safe for insertion into JavaScript source. `ISSUE_ID` and `AGENT_NAME` are inserted into single-quoted JavaScript strings, while `RESPONSE` is inserted into a JavaScript template literal. An argument containing a matching quote or backtick can terminate the expected literal and append arbitrary JavaScript. Template-literal interpolation in `RESPONSE` can also execute JavaScript expressions when Node evaluates the generated program. The script reads `~/.linear_api_key` immediately before launching the attacker-influenced JavaScript program, increasing the sensitivity of the execution context. ### Attack Path 1. An attacker influences any argument supplied to `post-to-linear.sh`. 2. The crafted value contains JavaScript syntax that exits the surrounding string or template literal. 3. Bash expands the value into the multiline `node -e` program. 4. Node interprets the injected syntax as executable code. 5. The injected code executes under the invoking user and can access local files and credentials. ### Impact Assessment Exploitation results in arbitrary JavaScript execution with the privileges of the user invoking the helper. The resulting process may read the Linear API key, manipulate pr ...[truncated 116 chars]
Remediation
## Remediation Suggestions - Delete the inline `node -e` construction. - Implement a fixed Node entry point that reads a JSON object from standard input. - Parse and validate the JSON as data without evaluating it. - Alternatively, pass arguments through `process.argv` to a fixed script, without constructing JavaScript source. - Validate issue IDs and agent names against strict allowlists. - Avoid reading secrets in shell wrappers; obtain the credential through a protected secret provider inside the fixed application. - Restrict credential permissions and use a dedicated Linear integration token with only required scopes.

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
linear-transform.js:167
Finding
Excessive and Unnecessary Access to Local Linear Credentials## Vulnerability Details **File Location**: `linear-transform.js`, lines 167–210 **Vulnerability Type**: Excessive credential access and failure to enforce least privilege **Risk Level**: Medium ### Vulnerable Code ```javascript function loadAgentTokens() { const fs = require('fs'); const tokensPath = process.env.HOME + '/.config/clawdbot/linear-agent-tokens.json'; try { const data = fs.readFileSync(tokensPath, 'utf8'); return JSON.parse(data); } catch (e) { console.error('[Linear Webhook] Failed to load agent tokens:', e.message); return {}; } } ``` ```javascript const agentKey = agentName.toLowerCase().split(' ')[0].replace(/[^a-z]/g, ''); // Load OAuth tokens const tokens = loadAgentTokens(); const agentToken = tokens[agentKey]?.accessToken; // Use personal API key directly (OAuth tokens have auth issues) // Read from file as most reliable source const fs = require('fs'); let personalKey = process.env.LINEAR_API_KEY || process.env.CLAWDBOT_LINEAR_API_KEY; try { personalKey = fs.readFileSync(process.env.HOME + '/.linear_api_key', 'utf8').trim(); } catch (e) { /* use env var */ } const apiKey = personalKey; if (!apiKey) { console.error('[Linear Webhook] No API key found for agent:', agentKey); return; } console.log('[Linear Webhook] Posting as:', agentToken ? `OAuth app (${agentKey})` : 'Personal API key'); ``` ### Technical Analysis The response-posting function reads an entire file containing multiple agents’ OAuth tokens, even though the selected OAuth token is not used as the authorization credential for the request. It then reads a personal API key from the user’s home directory and gives that file precedence over explicitly supplied environment credentials. This behavior unnecessarily expands the number and privilege of secrets exposed to the process. It also conflicts with the code’s apparent agent-specific authorization design because ...[truncated 1111 chars]
Remediation
## Remediation Suggestions - Remove `loadAgentTokens` unless the OAuth token is genuinely required and used. - Load only the single credential needed for the selected agent and operation. - Do not silently override an explicitly supplied environment credential with a home-directory file. - Use a dedicated Linear integration token with the minimum required scope rather than a broad personal API key. - Store credentials in an operating-system keychain or secret manager and limit file permissions where file-backed secrets are unavoidable. - Keep credentials out of subprocesses and any execution context influenced by webhook content. - Document the exact credential-selection behavior and fail closed when the expected credential is unavailable.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (27)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
Reading a local API key from ~/.linear_api_key and behaving as a CLI helper rather than a webhook receiver introduces undeclared local file access and a different execution model than advertised. This is dangerous because users may install the skill expecting only inbound webhook handling, while it can instead access local credentials and perform authenticated API operations.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
Reading a local API key from ~/.linear_api_key and behaving as a CLI helper rather than a webhook receiver introduces undeclared local file access and a different execution model than advertised. This is dangerous because users may install the skill expecting only inbound webhook handling, while it can instead access local credentials and perform authenticated API operations.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
Reading a local API key from ~/.linear_api_key and behaving as a CLI helper rather than a webhook receiver introduces undeclared local file access and a different execution model than advertised. This is dangerous because users may install the skill expecting only inbound webhook handling, while it can instead access local credentials and perform authenticated API operations.

Ae1

High
Category
analysis-evasion
Content
module: "./linear-transform.js",
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
module: "./linear-transform.js",
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
module: "./linear-transform.js",
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
module: "./linear-transform.js",
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `SKILL.md` - This documentation
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ssd 3

High
Confidence
98% confidence
Finding
User-controlled issue descriptions and comment text are copied verbatim into the agent task, and the prompt then explicitly instructs the agent to post its response back to Linear. This creates a natural-language exfiltration channel: an attacker can place sensitive instructions or prompt-injection content in the comment/issue and cause the agent to disclose or relay unsafe content externally.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The README explicitly states that agents receive full issue context, including descriptions, labels, assignee data, comment text, and issue URLs, but it does not present a clear user-facing notice, consent mechanism, or data-minimization guidance. In a webhook-driven automation flow, this can cause users to disclose internal or sensitive project information to downstream agents without realizing the full scope of what is being transmitted.

External Transmission

Medium
Category
Data Exfiltration
Content
### Test Webhook Endpoint
```bash
curl -X POST http://localhost:18789/hooks/linear \
  -H "x-clawdbot-token: your-token" \
  -H "Content-Type: application/json" \
  -d @example-payload.json
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Lp3

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding
The skill documentation declares no explicit tool scope or permissions even though the described behavior requires environment access, network connectivity, and likely shell/CLI usage. This creates a trust and review gap: operators may enable a skill without understanding it can access secrets, call external APIs, or invoke local tooling.

Intent-Code Divergence

Medium
Confidence
89% confidence
Finding
The manifest description says the webhook receives Linear comments and routes them correctly, and the setup/test flow implies a full request-response loop ending with a posted Linear comment. Later lines state that posting back to Linear requires implementing API comment posting and adding a token, contradicting the implied out-of-the-box behavior.

Intent-Code Divergence

Medium
Confidence
94% confidence
Finding
The main workflow states that agent results are posted back to the Linear issue, presenting that behavior as already implemented. However, the troubleshooting section explicitly says to implement Linear API comment posting in the transform, which contradicts the earlier documentation about current behavior.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The skill forwards issue content, commenter identity, and potentially other context to agents and optionally to Telegram without a clear privacy warning or data-handling guidance. In many environments, Linear issues contain proprietary or personal data, so silent forwarding to external systems can create confidentiality and compliance exposure.

Intent-Code Divergence

Medium
Confidence
98% confidence
Finding
The module comment explicitly states 'Supports: @mason (code), @eureka (planning)'. That active documentation contradicts the actual code, which maps and accepts @forge too. This is a direct intent/documentation divergence rather than merely incomplete docs.

Description-Behavior Mismatch

Medium
Confidence
96% confidence
Finding
The manifest says the skill dispatches tasks to agents based on comments mentioning @mason or @eureka, and the file header repeats that supported set. However, the actual mention map and regex also recognize @forge, expanding behavior beyond the claimed routing scope. This is a semantic mismatch in declared supported behavior, not just an implementation detail.

Ssd 1

Medium
Confidence
93% confidence
Finding
The prompt tells the agent it must perform a specific follow-up action after responding, which gives attacker-controlled webhook content a stronger opportunity to steer behavior. In LLM-integrated systems, combining untrusted task text with authoritative-looking mandatory instructions materially increases the chance of unsafe execution or disclosure.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The generated task message instructs the agent to run a shell command that reads a local API key file and sends data to an external service. Embedding credential-using commands in model-consumable instructions creates a prompt-injection path where untrusted webhook content can induce secret-dependent actions and unintended exfiltration.

Description-Behavior Mismatch

Medium
Confidence
87% confidence
Finding
The skill is presented as a webhook transform/router, but it also contains outbound comment-posting logic that reads local credentials and transmits data to Linear. Hidden side effects and credential use increase risk because users or downstream agents may invoke functionality they would not expect from a transform module.

External Transmission

Medium
Category
Data Exfiltration
Content
`;

  try {
    const response = await fetch('https://api.linear.app/graphql', {
      method: 'POST',
      headers: {
        'Authorization': apiKey,
Confidence
70% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
`;

  try {
    const response = await fetch('https://api.linear.app/graphql', {
      method: 'POST',
      headers: {
        'Authorization': apiKey,
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Context-Inappropriate Capability

Medium
Confidence
94% confidence
Finding
The script builds a shell command with an untrusted sessionKey and executes it via child_process.exec, which invokes a shell. If an attacker can influence the sessionKey argument, they may inject shell metacharacters and execute arbitrary commands on the host. In a webhook-adjacent automation context, that turns a convenience integration into potential remote code execution.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The code extracts the last assistant message from session history and posts it verbatim to a Linear issue without checking for sensitive data, access scope, or whether the content was intended for external disclosure. If agent responses contain secrets, internal reasoning, user data, or other confidential material, this creates an unintended data exfiltration path into Linear comments.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The script accesses a credential file (`~/.linear_api_key`), which is a safety-relevant operation under the code-file warning criteria. Aside from a generic usage message, there is no comment, prompt, or user-facing notice explaining that the script will read sensitive credentials.

Static analysis

Detected: suspicious.env_credential_access, suspicious.potential_exfiltration

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
linear-transform.js:176

Sensitive-looking file read is paired with a network send.

Warn
Code
suspicious.potential_exfiltration
Location
linear-transform.js:178