Back to skill

Security audit

Openclaw Skills

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly coherent for making Instagram content, but it handles sensitive media and logged-in Instagram automation with unsafe helper scripts that need review before use.

Review before installing. Use only with trusted local media, trusted content-plan JSON, and a non-sensitive working directory. Do not run posting commands with --post unless you have inspected the preview and caption, and avoid exposing the OpenClaw CDP browser port to untrusted users. Be aware that transcription uploads audio to OpenAI and preview screenshots are written under /tmp.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/extract-frames.sh:19
Finding
Python Code Injection Through Shell-Interpolated Arguments<![CDATA[ ## Vulnerability Details **File Location**: `scripts/extract-frames.sh:19-61` **Vulnerability Type**: Python code injection **Risk Level**: High ### Vulnerable Code ```bash python3 -c " import json, subprocess, os timestamps = json.loads('''$TIMESTAMPS_JSON''') video = '$VIDEO_FILE' frames_dir = '$FRAMES_DIR' extracted = [] for item in timestamps: ts = item['timestamp'] frame_id = item['id'] primary = os.path.join(frames_dir, f'{frame_id}.png') subprocess.run([ 'ffmpeg', '-y', '-ss', str(ts), '-i', video, '-vframes', '1', '-q:v', '2', primary ], capture_output=True) # Save manifest manifest_path = os.path.join('$OUTPUT_DIR', 'frames-manifest.json') with open(manifest_path, 'w') as f: json.dump(extracted, f, indent=2) " ``` ### Technical Analysis The timestamp JSON, video path, frames directory, and output directory are inserted directly into Python source passed to `python3 -c`. Shell quoting does not make these values safe for use as Python source. A value containing Python string delimiters and additional Python statements can terminate one of the generated string literals. The resulting statements are then executed by the Python interpreter with the same permissions as the user or Agent running the Skill. This is not merely malformed-input handling: the vulnerable values originate from command-line arguments and are treated as executable source rather than data. ### Attack Path 1. An attacker influences the timestamp JSON, source-video path, or output path supplied to `extract-frames.sh`. 2. The crafted value includes characters that terminate the corresponding Python string literal. 3. Shell interpolation places the crafted content inside the `python3 -c` program. 4. Python parses the injected content as code. 5. The injected code executes with the Skill process's filesystem, environment, and process privileges. ### Impact Assessment Successful exploitation permits arbitrary local code ex ...[truncated 247 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not interpolate shell variables into Python source. - Store the Python implementation in a dedicated `.py` file and pass values through `sys.argv`. - Pass timestamp JSON through standard input and decode it with `json.load(sys.stdin)`. - Use an argument-safe invocation such as: ```bash printf '%s' "$TIMESTAMPS_JSON" | python3 scripts/extract_frames.py "$VIDEO_FILE" "$OUTPUT_DIR" ``` - In Python, read only from `sys.argv` and standard input: ```python video = sys.argv[1] output_dir = sys.argv[2] timestamps = json.load(sys.stdin) ``` - Validate the timestamp document's structure and reject unknown fields, invalid types, non-finite timestamps, and oversized input. - Run media-processing helpers with a restricted environment and only the filesystem access needed for the episode workspace. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/transcribe.sh:68
Finding
Python Code Injection Through Transcription Paths and API Data<![CDATA[ ## Vulnerability Details **File Location**: `scripts/transcribe.sh:68-93` and `scripts/transcribe.sh:105-119` **Vulnerability Type**: Python code injection and unsafe data-to-code conversion **Risk Level**: High ### Vulnerable Code ```bash ALL_SEGMENTS=$(echo "$ALL_SEGMENTS" | python3 -c " import json, sys existing = json.load(sys.stdin) new = json.loads('$ADJUSTED') existing.extend(new) json.dump(existing, sys.stdout) ") ``` ```bash python3 -c " import json segments = json.loads('$(echo "$ALL_SEGMENTS" | sed "s/'/\\\\'/g")') full_text = ' '.join(seg.get('text', '').strip() for seg in segments) result = { 'text': full_text, 'segments': segments, 'source': '$VIDEO_FILE', 'chunk_count': $NUM_CHUNKS } with open('$TRANSCRIPT_JSON', 'w') as f: json.dump(result, f, indent=2) with open('$TRANSCRIPT_TXT', 'w') as f: f.write(full_text) " ``` ```bash python3 -c " import json response = json.loads('''$(echo "$RESPONSE" | python3 -c "import sys; print(sys.stdin.read().replace(\"'''\", \"\\\\'''\"))")''') result = { 'text': response.get('text', ''), 'segments': response.get('segments', []), 'source': '$VIDEO_FILE', 'chunk_count': 1 } with open('$TRANSCRIPT_JSON', 'w') as f: json.dump(result, f, indent=2) with open('$TRANSCRIPT_TXT', 'w') as f: f.write(response.get('text', '')) " ``` ### Technical Analysis The script repeatedly converts data into executable Python source: - `ADJUSTED` JSON is inserted into a single-quoted Python string. - Aggregated transcript data is inserted into another Python program. - `VIDEO_FILE`, `TRANSCRIPT_JSON`, and `TRANSCRIPT_TXT` are embedded directly as Python literals. - OpenAI response content is inserted into a triple-quoted literal using incomplete delimiter replacement. Escaping one quote pattern is not a reliable way to serialize arbitrary data into source code. Crafted paths can terminate literals directly. Transcript text or API error content containing unexpected combi ...[truncated 1036 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Move all Python logic into a standalone script. - Pass paths through `sys.argv`, not generated source. - Pipe API responses and segment arrays through standard input. - Use temporary JSON files where multiple structured inputs are required. - Replace nested `echo`, `sed`, and `python3 -c` serialization with normal JSON parsing. - Use `curl --fail-with-body --show-error` and verify the HTTP status before parsing. - Validate that the API response is an object containing expected field types before processing it. - Ensure transcript files are created with restrictive permissions when podcast content may be sensitive. - Add regression tests using paths and transcript text containing quotes, backslashes, newlines, and Unicode delimiters. The flagged `curl | bash` behavior is not present in this file. Its `curl` requests upload audio to the declared OpenAI transcription endpoint; remediation should preserve that required network operation while eliminating source interpolation. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/extract-frames.sh:27
Finding
Arbitrary File Writes Through Unvalidated Frame Identifiers<![CDATA[ ## Vulnerability Details **File Location**: `scripts/extract-frames.sh:27-53` **Vulnerability Type**: Path traversal and arbitrary file write **Risk Level**: High ### Vulnerable Code ```python for item in timestamps: ts = item['timestamp'] frame_id = item['id'] # Extract primary frame at exact timestamp primary = os.path.join(frames_dir, f'{frame_id}.png') subprocess.run([ 'ffmpeg', '-y', '-ss', str(ts), '-i', video, '-vframes', '1', '-q:v', '2', primary ], capture_output=True) # Extract 2 additional frames nearby (±1.5s) for selection for offset_idx, offset in enumerate([-1.5, 1.5]): alt_ts = max(0, ts + offset) alt_path = os.path.join(frames_dir, f'{frame_id}_alt{offset_idx}.png') subprocess.run([ 'ffmpeg', '-y', '-ss', str(alt_ts), '-i', video, '-vframes', '1', '-q:v', '2', alt_path ], capture_output=True) extracted.append({ 'id': frame_id, 'timestamp': ts, 'primary': primary, 'alternates': [ os.path.join(frames_dir, f'{frame_id}_alt0.png'), os.path.join(frames_dir, f'{frame_id}_alt1.png') ] }) ``` ### Technical Analysis `frame_id` is used as part of an output path without validation. Python's `os.path.join` does not enforce containment: - A value containing `../` can traverse outside `frames_dir`. - An absolute identifier causes the preceding directory component to be discarded. - ffmpeg is invoked with `-y`, so an existing writable destination can be overwritten without confirmation. Although the resulting content is an encoded PNG rather than arbitrary attacker-selected bytes, writing or replacing files outside the episode workspace is still a material filesystem-boundary violation. ### Attack Path 1. An attacker supplies or influences the timestamps JSON. 2. An item uses an ID containing parent-directory components or an absolute path. 3. `os.path. ...[truncated 502 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Restrict frame IDs to a simple identifier format, such as: ```python if not re.fullmatch(r"[A-Za-z0-9_-]{1,64}", frame_id): raise ValueError("Invalid frame ID") ``` - Reject path separators, `.` and `..` components, absolute paths, control characters, and excessive lengths. - Resolve every output path canonically and enforce containment: ```python base = os.path.realpath(frames_dir) candidate = os.path.realpath(os.path.join(base, frame_id + ".png")) if os.path.commonpath([base, candidate]) != base: raise ValueError("Output path escapes frames directory") ``` - Check ffmpeg return codes using `subprocess.run(..., check=True)`. - Consider refusing to overwrite existing files unless the current run created them. - Create the output workspace with restrictive permissions. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/extract-reel.sh:9
Finding
Shell Arithmetic Injection Through Reel Timestamp Arguments<![CDATA[ ## Vulnerability Details **File Location**: `scripts/extract-reel.sh:9-13` **Vulnerability Type**: Shell arithmetic expression injection **Risk Level**: High ### Vulnerable Code ```bash VIDEO_FILE="${1:?Usage: extract-reel.sh <video_file> <output_path> <start> <duration>}" OUTPUT_PATH="${2:?Usage: extract-reel.sh <video_file> <output_path> <start> <duration>}" START="${3:?Provide start time in seconds}" DURATION="${4:-90}" echo "==> Extracting reel: ${START}s to $((START + DURATION))s..." ``` ### Technical Analysis `START` and `DURATION` are accepted as arbitrary strings and then evaluated inside Bash arithmetic expansion. Bash arithmetic operands are expressions rather than strictly parsed numbers. Untrusted expressions can trigger recursive variable and array-index evaluation and can reach shell-expansion behavior in unsafe constructions. Quoting the later ffmpeg arguments does not mitigate the earlier arithmetic evaluation performed by `$((START + DURATION))`. ### Attack Path 1. An attacker controls a Reel start or duration value in the content plan or invocation. 2. The value is assigned to `START` or `DURATION` without numeric validation. 3. Bash evaluates the value as part of the arithmetic expression used by the status message. 4. A malicious arithmetic expression triggers unintended shell evaluation. 5. Commands execute with the privileges of the Skill runner before ffmpeg processes the media. ### Impact Assessment Successful exploitation permits command execution as the account running the Skill. This can expose environment variables, API credentials, local files, generated media, and other resources available to the Agent. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions - Validate both arguments before using arithmetic expansion. - Accept only the intended numeric syntax, for example non-negative integers or bounded decimal numbers. - Reject signs, whitespace, brackets, variable names, command-substitution characters, and shell metacharacters. - Perform calculations in a safer parser rather than Bash arithmetic when decimal timestamps are allowed. - Enforce sensible bounds, such as a non-negative start and a duration within Instagram's supported limit. - Example integer-only validation: ```bash [[ "$START" =~ ^[0-9]+$ ]] || { echo "Invalid start time" >&2 exit 1 } [[ "$DURATION" =~ ^[0-9]+$ ]] || { echo "Invalid duration" >&2 exit 1 } ``` - Pass validated values to ffmpeg as separate quoted arguments. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/generate-carousel.js:68
Finding
Carousel Identifier Path Traversal<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate-carousel.js:68-70` and `scripts/generate-carousel.js:172-179` **Vulnerability Type**: Path traversal and out-of-scope file write **Risk Level**: Medium ### Vulnerable Code ```javascript const carousels = contentPlan.carousels || []; for (const carousel of carousels) { const carouselDir = path.join(slidesDir, carousel.id); fs.mkdirSync(carouselDir, { recursive: true }); for (let i = 0; i < carousel.slides.length; i++) { // ... const outPath = path.join( carouselDir, `slide_${String(i + 1).padStart(2, '0')}.png` ); const buffer = canvas.toBuffer('image/png'); fs.writeFileSync(outPath, buffer); } } ``` ```javascript const manifest = carousels.map(c => ({ id: c.id, slideCount: c.slides.length, dir: path.join(slidesDir, c.id), slides: c.slides.map((s, i) => path.join(slidesDir, c.id, `slide_${String(i + 1).padStart(2, '0')}.png`) ) })); ``` ### Technical Analysis The content plan's `carousel.id` is used as a directory component without validation or a containment check. An ID containing sufficient `../` components can cause `path.join` to resolve the carousel directory outside `outputDir/slides`. The generator then creates that directory recursively and writes PNG files into it. The generated manifest also records the escaped paths as if they were legitimate outputs. ### Attack Path 1. An attacker supplies or influences `content-plan.json`. 2. A carousel ID contains parent-directory traversal components. 3. The generator joins that ID to `slidesDir`. 4. The resulting path escapes the intended output directory. 5. Directories and generated slide files are written to the attacker-selected writable location. ### Impact Assessment The attacker can create directories and overwrite predictably named PNG files outside the intended episode workspace. This can corrupt other project outputs, poison files consumed by later automation, or wr ...[truncated 47 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Validate carousel IDs using a strict allowlist such as `^[A-Za-z0-9_-]{1,64}$`. - Resolve and enforce directory containment: ```javascript const base = path.resolve(slidesDir); const candidate = path.resolve(base, carousel.id); if (candidate !== base && !candidate.startsWith(base + path.sep)) { throw new Error('Carousel path escapes slides directory'); } ``` - Reject IDs containing separators, `.` or `..` components, control characters, or excessive lengths. - Apply the same canonical-path check when generating manifest entries. - Refuse to overwrite unrelated existing files. - Validate the full content-plan schema before rendering. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/capture-instagram-preview.js:38
Finding
Instagram-Origin Validation Bypass for CDP Target Selection<![CDATA[ ## Vulnerability Details **File Location**: `scripts/capture-instagram-preview.js:6-10,38-44,67-80`; related behavior in `scripts/prepare-instagram-video-draft.js:11-13,68-72` **Vulnerability Type**: Unauthorized browser-tab access through trusted CDP session **Risk Level**: Medium ### Vulnerable Code ```javascript const targetIndex = process.argv.indexOf('--target-id'); const targetId = targetIndex >= 0 ? process.argv[targetIndex + 1] : ''; const cdpUrlIndex = process.argv.indexOf('--cdp-url'); const cdpUrl = cdpUrlIndex >= 0 ? process.argv[cdpUrlIndex + 1] : 'http://127.0.0.1:18800'; ``` ```javascript async function resolvePreviewPage(browser) { if (targetId) { const targeted = await findPageByTargetId(browser, targetId); if (targeted) { return targeted; } } for (const context of browser.contexts()) { for (const page of context.pages()) { try { const state = await readState(page); if (!state.url.startsWith('https://www.instagram.com/')) { continue; } if ( state.textareaCount || state.editableCount || state.hasShare || state.hasNext ) { return page; } } catch {} } } return null; } ``` ```javascript const browser = await chromium.connectOverCDP(cdpUrl); try { const page = await resolvePreviewPage(browser); if (!page) { throw new Error('Could not find an Instagram composer page to capture.'); } await page.bringToFront(); await page.waitForTimeout(500); const screenshotPath = path.join( '/tmp', `ig-preview-${Date.now()}.png` ); await page.screenshot({ path: screenshotPath }); console.log(JSON.stringify({ ok: true, screenshotPath, state: await readState(page) }, null, 2)); } ``` The video-draft helper repeats the same trust pattern: ```javascript async function resolveDraftPage(browser) { if (targetId) { const targeted = await findPageB ...[truncated 1653 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Validate the selected page after every target lookup: ```javascript const selected = await findPageByTargetId(browser, targetId); if (selected) { const url = new URL(selected.url()); if (url.protocol !== 'https:' || url.hostname !== 'www.instagram.com') { throw new Error('Selected target is not an Instagram page'); } return selected; } ``` - Apply the same validation to `prepare-instagram-video-draft.js`. - Prefer an exact origin comparison instead of a raw string-prefix test. - Restrict CDP URLs to approved loopback addresses unless remote CDP access is explicitly required. - Do not expose the CDP port to untrusted local users or networks. - Require authentication or transport protection for any remotely reachable CDP service. - Create screenshots with restrictive permissions and delete temporary previews after use. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/post-to-instagram.js:789
Finding
Instagram Publishing Proceeds When Preview Capture Fails<![CDATA[ ## Vulnerability Details **File Location**: `scripts/post-to-instagram.js:789-829` **Vulnerability Type**: Fail-open approval and publication control **Risk Level**: Medium ### Vulnerable Code ```javascript console.log('==> Capturing preview screenshot...'); const preview = capturePreview(targetId); const screenshot = preview.ok ? preview.screenshot : null; if (screenshot) { console.log(` Preview: ${screenshot}`); } else { console.log(` Preview capture skipped: ${preview.error}`); } if (!doPost) { console.log('\n========================================'); console.log('==> STOPPED BEFORE POSTING'); console.log('==> Review the preview screenshot path above'); console.log('==> Run with --post flag to actually publish'); console.log('========================================'); return; } console.log('==> Waiting for Share/Post button...'); browser( ['wait', '--text', 'Share', '--timeout-ms', '60000'], { allowFailure: true } ); console.log('==> Publishing...'); const shareResult = clickShare(); console.log(` Share result: ${shareResult}`); if (shareResult === 'not-found') { throw new Error( 'Could not find Share/Post/Publish button in the Instagram composer.' ); } ``` ### Technical Analysis Preview capture failure is logged but is not treated as a blocking condition. If the caller supplies `--post`, the script continues to the authenticated Instagram Share action even when no preview screenshot was produced. This conflicts with the Skill's strict workflow requiring a preview and explicit approval before posting. The script treats possession of a command-line flag as sufficient authorization and does not bind approval to the exact caption, media files, account, or preview. ### Attack Path 1. A caller invokes the script with `--post`. 2. Preview capture fails because of a helper error, target mismatch, filesystem error, or browser-state issue. 3. The script logs that the preview was skipped. 4. Execution continues bec ...[truncated 449 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Fail closed if preview capture is unsuccessful: ```javascript if (!preview.ok || !preview.screenshot) { throw new Error('Publishing blocked because preview capture failed'); } ``` - Separate preview preparation and publication into distinct commands or processes. - Require an approval artifact tied to: - Cryptographic hashes of all staged media files - The complete caption - The Instagram account identity - The generated preview - An expiration time - Revalidate all hashes and the active account immediately before clicking Share. - Require a second explicit confirmation if the browser account or composer contents differ from the approved state. - Ensure both image and video/Reel publication paths enforce the same fail-closed approval checks. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • 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 (20)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The skill advertises handling YouTube/webpage URLs and generating multiple Instagram asset types, but the detected behavior for this portion appears limited and does not support those claims. Security-wise, this is risky because users may authorize broader access or supply sensitive sources expecting deterministic processing, while the actual code path may behave differently or fail into ad hoc manual/browser-assisted flows that are less controlled.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The skill advertises handling YouTube/webpage URLs and generating multiple Instagram asset types, but the detected behavior for this portion appears limited and does not support those claims. Security-wise, this is risky because users may authorize broader access or supply sensitive sources expecting deterministic processing, while the actual code path may behave differently or fail into ad hoc manual/browser-assisted flows that are less controlled.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The skill advertises handling YouTube/webpage URLs and generating multiple Instagram asset types, but the detected behavior for this portion appears limited and does not support those claims. Security-wise, this is risky because users may authorize broader access or supply sensitive sources expecting deterministic processing, while the actual code path may behave differently or fail into ad hoc manual/browser-assisted flows that are less controlled.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The skill advertises handling YouTube/webpage URLs and generating multiple Instagram asset types, but the detected behavior for this portion appears limited and does not support those claims. Security-wise, this is risky because users may authorize broader access or supply sensitive sources expecting deterministic processing, while the actual code path may behave differently or fail into ad hoc manual/browser-assisted flows that are less controlled.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The skill advertises handling YouTube/webpage URLs and generating multiple Instagram asset types, but the detected behavior for this portion appears limited and does not support those claims. Security-wise, this is risky because users may authorize broader access or supply sensitive sources expecting deterministic processing, while the actual code path may behave differently or fail into ad hoc manual/browser-assisted flows that are less controlled.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The skill advertises handling YouTube/webpage URLs and generating multiple Instagram asset types, but the detected behavior for this portion appears limited and does not support those claims. Security-wise, this is risky because users may authorize broader access or supply sensitive sources expecting deterministic processing, while the actual code path may behave differently or fail into ad hoc manual/browser-assisted flows that are less controlled.

Ae1

High
Category
analysis-evasion
Content
Script: `scripts/generate-carousel.js`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
Script: `scripts/post-to-instagram.js`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

External Script Fetching

High
Category
Supply Chain
Content
ffmpeg -y -i "$AUDIO_FILE" -ss "$START" -t "$CHUNK_DURATION" -acodec libmp3lame -ar 16000 -ac 1 -b:a 64k "$CHUNK_FILE" 2>/dev/null
        
        # Transcribe chunk
        RESPONSE=$(curl -s https://api.openai.com/v1/audio/transcriptions \
            -H "Authorization: Bearer $OPENAI_API_KEY" \
            -F file="@$CHUNK_FILE" \
            -F model="whisper-1" \
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

External Script Fetching

High
Category
Supply Chain
Content
else
    echo "==> Audio file is ${FILESIZE} bytes, transcribing in one shot..."
    
    RESPONSE=$(curl -s https://api.openai.com/v1/audio/transcriptions \
        -H "Authorization: Bearer $OPENAI_API_KEY" \
        -F file="@$AUDIO_FILE" \
        -F model="whisper-1" \
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The README explicitly describes Instagram draft automation using a logged-in browser session, which means the skill can perform authenticated actions on the user's account. Failing to clearly warn that the automation may post, upload, or otherwise affect the account reduces informed consent and can lead users to run account-impacting actions they did not fully expect.

Lp3

Medium
Category
MCP Least Privilege
Confidence
83% confidence
Finding
The skill describes shell execution and file-writing behavior but does not declare any explicit tool scope or allowed-tools boundary. That makes the effective execution surface broader and less auditable, increasing the risk of unintended command execution, filesystem modification, and abuse if the skill is invoked with untrusted inputs or implemented differently than documented.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The script captures screenshots of the Instagram composer and stores them in /tmp, which may include captions, media previews, account details, and other sensitive user content. In a multi-process or shared environment, these artifacts can persist longer than expected and be accessible to other users, logs, backup systems, or later forensic collection, creating unnecessary data exposure.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
When the script is run with --post, it performs the final Instagram share action immediately through browser automation, with no in-script confirmation, dry-run guard, or explicit acknowledgement step. In this skill's context, that is more dangerous because the tool is specifically designed to transform and publish social media content, so a mistaken flag, mis-targeted browser tab, or prompt injection in upstream workflow could cause unintended public posting from a real account.

External Transmission

Medium
Category
Data Exfiltration
Content
ffmpeg -y -i "$AUDIO_FILE" -ss "$START" -t "$CHUNK_DURATION" -acodec libmp3lame -ar 16000 -ac 1 -b:a 64k "$CHUNK_FILE" 2>/dev/null
        
        # Transcribe chunk
        RESPONSE=$(curl -s https://api.openai.com/v1/audio/transcriptions \
            -H "Authorization: Bearer $OPENAI_API_KEY" \
            -F file="@$CHUNK_FILE" \
            -F model="whisper-1" \
Confidence
90% confidence
Finding
This is a true external-transmission finding because the code sends audio chunks to api.openai.com. In the context of a media-processing skill, this behavior is expected functionally, but it still has security significance because it exports potentially sensitive content off-host to a third party.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The script uploads user-provided audio content to OpenAI's external transcription API without any explicit notice, consent check, or privacy gating in the script itself. Because podcast/video episodes may contain sensitive, copyrighted, or private speech, silent remote transmission creates a real data-exposure risk even if the destination service is legitimate.

External Transmission

Medium
Category
Data Exfiltration
Content
else
    echo "==> Audio file is ${FILESIZE} bytes, transcribing in one shot..."
    
    RESPONSE=$(curl -s https://api.openai.com/v1/audio/transcriptions \
        -H "Authorization: Bearer $OPENAI_API_KEY" \
        -F file="@$AUDIO_FILE" \
        -F model="whisper-1" \
Confidence
90% confidence
Finding
The one-shot transcription branch also performs external transmission of user audio to OpenAI. While not inherently malicious, this is a real privacy and compliance concern because the transfer is automatic and may surprise users who expect local processing.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
In the single-shot path, the script sends the entire extracted audio file to a remote API without any explicit warning or consent mechanism. This is materially the same privacy/security issue as the chunked path: user content is externally transmitted, which is risky in a skill intended to process full podcast/video episodes that may contain sensitive material.

Unpinned Dependencies

Low
Category
Supply Chain
Content
],
  "license": "MIT",
  "dependencies": {
    "canvas": "^3.2.3"
  }
}
Confidence
40% confidence
Finding
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Missing User Warnings

Low
Confidence
84% confidence
Finding
The script reads and uses the OPENAI_API_KEY environment variable to authenticate outbound API requests. While the header comment lists the variable as required, it does not provide a clear user-facing warning that a credential will be used for external transmission to a third-party service.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/post-to-instagram.js:56