Back to skill

Security audit

Video App

Security checks for vulnerabilities and agentic risk

Overview

This is a coherent local video-generation skill, but its web server exposes risky unauthenticated media processing and avoidable security flaws that users should review before installing.

Install only in a trusted local environment, not on an exposed VPS, until the app adds authentication, upload limits, rate limiting, job timeouts, safe output cleanup, UUID validation on downloads, DOM-safe rendering, and patched/pinned dependencies. Treat submitted audio, images, and prompts as sensitive content handled by the local Node service and retained as generated MP4 outputs.

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

T05 · Unauthorized Access and Privilege Escalation

Error
Location
index.js:97
Finding
Path Traversal in the Video Download Endpoint<![CDATA[ ## Vulnerability Details **File Location**: `index.js`, lines 97-104 **Vulnerability Type**: Unrestricted filesystem path construction **Risk Level**: High ### Vulnerable Code ```js app.get('/download/:id.mp4', (req, res) => { const filePath = path.join(__dirname, 'outputs', `${req.params.id}.mp4`); if (fs.existsSync(filePath)) { res.download(filePath); } else { res.status(404).send('Video not found'); } }); ``` ### Technical Analysis The `id` route parameter is inserted directly into a local filesystem path. The application does not verify that the value is a UUID, reject path separators, or confirm that the resolved path remains inside the intended `outputs` directory. Because route parameters are URL-decoded, a value containing encoded traversal sequences and path separators may cause `path.join()` to resolve a path outside `outputs`. The application then checks the attacker-selected path with `fs.existsSync()` and returns it with `res.download()`. The route always appends `.mp4`, so exploitation is limited to accessible files whose resulting path ends with that extension. This restriction reduces scope but does not prevent unauthorized access to media or other files using that extension. ### Attack Path 1. The attacker identifies the unauthenticated `/download/:id.mp4` endpoint. 2. The attacker supplies encoded `..` segments and path separators as the `id` parameter. 3. Express decodes the route parameter. 4. `path.join(__dirname, 'outputs', ...)` normalizes the traversal sequences and may produce a path outside the `outputs` directory. 5. If the resulting `.mp4` file exists and is readable by the Node.js process, `res.download()` returns it to the attacker. ### Impact Assessment A remote unauthenticated attacker may download files outside the intended output directory when: - The target path resolves to an existing file ending in `.mp4`. - The Node.js process has permission to read the target file. The vulnerability vi ...[truncated 225 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate `id` against the exact format generated by the application. For example, require a canonical UUID: ```js const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; app.get('/download/:id.mp4', (req, res) => { if (!UUID_RE.test(req.params.id)) { return res.status(400).send('Invalid video identifier'); } const outputRoot = path.resolve(__dirname, 'outputs'); const filePath = path.resolve(outputRoot, `${req.params.id}.mp4`); if (!filePath.startsWith(`${outputRoot}${path.sep}`)) { return res.status(400).send('Invalid video identifier'); } res.download(filePath, (error) => { if (error && !res.headersSent) { res.status(error.statusCode === 404 ? 404 : 500).send('Unable to download video'); } }); }); ``` 2. Store generated IDs and corresponding server-controlled paths in a database or lookup table rather than deriving arbitrary paths from request parameters. 3. Apply authorization checks so that only the owner of a generated video can download it. 4. Run the service under a dedicated account with read access limited to application-owned directories. 5. Add automated tests using encoded traversal sequences, mixed separators, malformed UUIDs, and nonexistent identifiers. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
public/index.html:49
Finding
DOM-Based Cross-Site Scripting Through Untrusted Model and Error Output<![CDATA[ ## Vulnerability Details **File Location**: `public/index.html`, lines 49-59 **Vulnerability Type**: DOM-based cross-site scripting **Risk Level**: High ### Vulnerable Code ```js if (data.success) { resultDiv.innerHTML = ` <h3>Scenes Description:</h3> <pre>${data.scenes}</pre> <h3>Video:</h3> <video controls src="${data.videoUrl}"></video> <a href="${data.downloadUrl}" download>Download MP4</a> `; } else { resultDiv.innerHTML = `<p>Error: ${data.error}</p>`; } ``` ### Technical Analysis The application inserts several response fields directly into `innerHTML`: - `data.scenes` - `data.videoUrl` - `data.downloadUrl` - `data.error` The most important source is `data.scenes`, which originates from an Ollama response generated using an attacker-controlled prompt. Although the server asks the model to return only a Markdown list, this is a behavioral instruction rather than an output-security boundary. Prompt injection or unpredictable model output can cause HTML to be returned. Placing model-generated HTML into `innerHTML` allows the browser to parse it as markup. Active attributes such as event handlers can execute JavaScript. The error branch has the same unsafe rendering pattern, and server-side exception messages are returned by `index.js`. The video and download URLs are currently generated by the server rather than directly supplied by users, but they should still be assigned through validated DOM properties instead of HTML-string interpolation. ### Attack Path 1. An attacker supplies a prompt crafted to make the local language model return HTML rather than the requested Markdown list. 2. The server includes the model output in the `scenes` property of the JSON response. 3. The browser interpolates `data.scenes` into an HTML template. 4. The template is assigned to `resultDiv.innerHTML`. 5. The browser parses the model response as HTML. 6. If the response contains executable markup, such as an element with an activ ...[truncated 1107 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not use `innerHTML` for model-generated text or server error messages. 2. Construct fixed elements through DOM APIs and assign untrusted strings through `textContent`: ```js resultDiv.replaceChildren(); if (data.success) { const scenesHeading = document.createElement('h3'); scenesHeading.textContent = 'Scenes Description:'; const scenes = document.createElement('pre'); scenes.textContent = String(data.scenes ?? ''); const videoHeading = document.createElement('h3'); videoHeading.textContent = 'Video:'; const video = document.createElement('video'); video.controls = true; const download = document.createElement('a'); download.textContent = 'Download MP4'; download.download = ''; const videoUrl = new URL(data.videoUrl, window.location.origin); const downloadUrl = new URL(data.downloadUrl, window.location.origin); if ( videoUrl.origin !== window.location.origin || downloadUrl.origin !== window.location.origin ) { throw new Error('Invalid response URL'); } video.src = videoUrl.href; download.href = downloadUrl.href; resultDiv.append( scenesHeading, scenes, videoHeading, video, download ); } else { const errorMessage = document.createElement('p'); errorMessage.textContent = `Error: ${String(data.error ?? 'Unknown error')}`; resultDiv.append(errorMessage); } ``` 3. Return generic server-side error messages instead of exposing `error.message`. 4. Add a restrictive Content Security Policy that disallows inline scripts and event handlers as defense in depth. 5. Treat all language-model output as untrusted data, regardless of system or prompt instructions. 6. Add browser security tests using model responses containing tags, event handlers, malformed attributes, and encoded markup. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
index.js:14
Finding
Unauthenticated Media Processing Enables Resource Exhaustion and Persistent Disk Consumption<![CDATA[ ## Vulnerability Details **File Location**: `index.js`, lines 14-89 and 107-109 **Vulnerability Type**: Unbounded upload and computational resource consumption **Risk Level**: High ### Vulnerable Code ```js const upload = multer({ dest: 'uploads/' }); ``` ```js app.post('/generate', upload.fields([ { name: 'audio', maxCount: 1 }, { name: 'photo', maxCount: 1 } ]), async (req, res) => { try { const { prompt } = req.body; if (!prompt || !req.files.audio || !req.files.photo) { return res.status(400).json({ error: 'Missing audio, photo, or prompt' }); } const audioPath = req.files.audio[0].path; const photoPath = req.files.photo[0].path; const id = uuidv4(); const outputPath = path.join('outputs', `${id}.mp4`); // Generate scenes description with Ollama const scenesPrompt = `Create a detailed scene-by-scene description for a short music video synced to a melody. The video shows morphing/zoom/pan effects on one photo with audio waveform visualization at the bottom. User description/theme: "${prompt}". Output ONLY a markdown numbered list of 5 scenes with approximate timings (e.g. 0-10s: ...). Keep it vivid and matching the theme.`; const response = await ollama.chat({ model: 'llama3.2:1b', messages: [ { role: 'system', content: 'You are a creative music video director. Generate engaging scene descriptions.' }, { role: 'user', content: scenesPrompt } ], }); const scenesDesc = response.message.content; // Generate video with FFmpeg const args = [ '-loop', '1', '-tune', 'stillimage', '-i', photoPath, '-i', audioPath, '-filter_complex', `[0:v]scale=1280:720:force_original_aspect_ratio=decrease,pad=1280:720:(ow-iw)/2:(oh-ih)/2,zoompan=z='min(zoom+0.001,1.5)':d=250:x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':fps=30[photo]; [1:a]showwaves=s=1280x120:mode=line:colors=00ff00:scale=log:draw=full[wave]; [photo][wave]overl ...[truncated 3713 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require authentication and authorization for `/generate`. 2. Add per-user and per-IP rate limiting, generation quotas, and billing or credit checks where appropriate. 3. Configure strict Multer limits: ```js const upload = multer({ dest: 'uploads/', limits: { fileSize: 25 * 1024 * 1024, files: 2, fields: 5, fieldSize: 16 * 1024 }, fileFilter(req, file, callback) { const allowed = new Set([ 'audio/mpeg', 'audio/wav', 'image/jpeg', 'image/png' ]); callback(allowed.has(file.mimetype) ? null : new Error('Unsupported media type'), allowed.has(file.mimetype)); } }); ``` 4. Do not trust MIME metadata alone. Inspect magic bytes and probe media with a restricted validation process before full transcoding. 5. Place generation jobs in a bounded queue with strict global and per-user concurrency limits. 6. Apply hard deadlines to Ollama and FFmpeg. Terminate child processes when deadlines expire or clients disconnect. 7. Register an FFmpeg `error` handler in addition to the `close` handler. 8. Move cleanup into a reliable `finally` path and use asynchronous deletion methods so cleanup errors do not crash the process. 9. Delete partial output files after failures and implement automatic expiration for completed outputs. 10. Run FFmpeg in an isolated container or sandbox with CPU, memory, process, filesystem, and execution-time limits. 11. Apply reverse-proxy request-size and connection limits before requests reach Node.js. 12. Monitor upload volume, active jobs, subprocess counts, disk usage, execution duration, and cleanup failures. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (17)

Context-Inappropriate Capability

High
Confidence
91% confidence
Finding
The code spawns FFmpeg as a child process to transform uploaded media into a video. Without a manifest describing the skill as a media-generation or transcoding tool, subprocess execution is an unjustified high-impact capability because it materially expands what the skill can do on the host.

Known Vulnerable Dependency: multer==2.0.2 — 8 advisory(ies): CVE-2026-5038 (Multer vulnerable to Denial of Service via incomplete cleanup of aborted uploads); CVE-2026-82333 (multer vulnerable to Denial of Service via oversized array index in field names); CVE-2026-3520 (Multer Vulnerable to Denial of Service via Uncontrolled Recursion) +5 more

High
Category
Supply Chain
Confidence
96% confidence
Finding
The application directly depends on multer 2.0.2, and the listed advisories are multiple denial-of-service issues in multipart upload parsing and cleanup. Because this is a video app and multer is commonly used for user-uploaded files, the skill context makes these findings more dangerous: an unauthenticated or low-privilege attacker may be able to exhaust server resources by sending malformed or abusive upload requests.

Known Vulnerable Dependency: path-to-regexp==8.3.0 — 2 advisory(ies): CVE-2026-4923 (path-to-regexp vulnerable to Regular Expression Denial of Service via multiple w); CVE-2026-4926 (path-to-regexp vulnerable to Denial of Service via sequential optional groups)

High
Category
Supply Chain
Confidence
90% confidence
Finding
path-to-regexp 8.3.0 is reported with ReDoS/DoS issues, and it is used by the router/express stack to process route patterns. While exploitation depends on specific route definitions and attacker-controlled paths, routing sits on the request path for all traffic, so availability risk can be significant if a vulnerable pattern is present.

Known Vulnerable Dependency: multer==2.0.2 — 8 advisory(ies): CVE-2026-5038 (Multer vulnerable to Denial of Service via incomplete cleanup of aborted uploads); CVE-2026-82333 (multer vulnerable to Denial of Service via oversized array index in field names); CVE-2026-3520 (Multer Vulnerable to Denial of Service via Uncontrolled Recursion) +5 more

High
Category
Supply Chain
Confidence
98% confidence
Finding
The project depends on multer 2.0.2, which is flagged with multiple denial-of-service vulnerabilities. In a video application, multer is likely used to process uploads, making the context more dangerous because malformed or malicious upload requests could exhaust resources or crash the service remotely.

Vague Triggers

Medium
Confidence
87% confidence
Finding
The skill description is broad and does not define clear activation boundaries, inputs, or safety constraints, which can cause the agent to invoke it in unintended contexts. Because this skill launches a local webapp workflow that processes user media and prompts and depends on external binaries, ambiguous scope increases the chance of unnecessary exposure of local resources or accidental handling of sensitive content.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The skill accepts user audio, photos, and prompts and sends them to a local web service for processing, but it does not warn users that their media and text will be transmitted to and handled by that service. This lack of transparency can lead to inadvertent exposure of sensitive personal content and prevents informed consent about how data is processed, stored, or logged.

Context-Inappropriate Capability

Medium
Confidence
81% confidence
Finding
No manifest is available, so the skill has no declared purpose or scope to justify auxiliary AI-driven scene generation. The code does more than simple media processing by sending user prompt content to an Ollama model to create creative video scenes, which is a distinct capability from file handling or transcoding.

Missing User Warnings

Medium
Confidence
84% confidence
Finding
User-provided prompt text is sent to a local LLM process and uploaded media is passed into FFmpeg without any visible disclosure, consent, or retention policy. In this skill context, the data flow is functionally expected, but the absence of transparent user-facing notice and handling controls creates a real privacy and trust issue, especially if users assume uploads are not further processed or stored.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
This HTML/JS file collects user-provided media and text, then submits them via a POST request to /generate. While the form implies upload, there is no explicit warning or disclosure about transmitting potentially sensitive user content to a server for processing.

Known Vulnerable Dependency: body-parser==2.2.2 — 1 advisory(ies): CVE-2026-12590 (body-parser vulnerable to denial of service when invalid limit value silently di)

Low
Category
Supply Chain
Confidence
81% confidence
Finding
The lockfile pins body-parser 2.2.2, and the static finding cites a denial-of-service issue related to invalid limit handling. In a dependency lockfile, this is a real supply-chain exposure if the affected code path is reachable, though the impact is limited to availability and there is no evidence in this file alone of direct exploitation.

Known Vulnerable Dependency: qs==6.15.0 — 3 advisory(ies): CVE-2026-82417 (qs: Denial of Service via Attacker Controlled isBuffer); CVE-2026-8723 (qs has a remotely triggerable DoS: qs.stringify crashes with TypeError on null/u); CVE-2026-82562 (qs array-limit bypass via bracket-key comma parsing)

Low
Category
Supply Chain
Confidence
78% confidence
Finding
qs 6.15.0 has reported denial-of-service and parser edge-case issues, and it is commonly used to parse attacker-controlled query strings or form bodies. This is a real dependency risk, but in this file alone there is no evidence of especially dangerous usage, so the likely impact is limited to availability degradation rather than code execution or data compromise.

Known Vulnerable Dependency: uuid==13.0.0 — 1 advisory(ies): CVE-2026-41907 (uuid: Missing buffer bounds check in v3/v5/v6 when buf is provided)

Low
Category
Supply Chain
Confidence
66% confidence
Finding
uuid 13.0.0 is flagged for a missing bounds check in certain version generators when a caller supplies a buffer. This is likely a real library flaw, but exploitability depends on the application invoking the affected v3/v5/v6 APIs with attacker-influenced buffer arguments; the lockfile alone does not show such usage, so practical risk appears low.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^5.2.1",
    "multer": "^2.0.2",
    "ollama": "^0.6.3",
    "uuid": "^13.0.0"
Confidence
93% confidence
Finding
The dependency uses a caret version range, which allows future semver-compatible releases to be installed automatically. This increases supply-chain risk because a newly published compromised or breaking package version could be pulled in without explicit review.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"license": "ISC",
  "dependencies": {
    "express": "^5.2.1",
    "multer": "^2.0.2",
    "ollama": "^0.6.3",
    "uuid": "^13.0.0"
  }
Confidence
95% confidence
Finding
The multer dependency is specified with a caret range, allowing automatic installation of later releases without explicit approval. In this case the package is also flagged as having known advisories, so leaving it unpinned worsens supply-chain and operational risk by making dependency state less predictable while already relying on a problematic package line.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"dependencies": {
    "express": "^5.2.1",
    "multer": "^2.0.2",
    "ollama": "^0.6.3",
    "uuid": "^13.0.0"
  }
}
Confidence
92% confidence
Finding
Using a caret for ollama permits silent adoption of newer compatible releases during install. That weakens build reproducibility and can expose the application to accidental or malicious upstream changes in the dependency supply chain.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"express": "^5.2.1",
    "multer": "^2.0.2",
    "ollama": "^0.6.3",
    "uuid": "^13.0.0"
  }
}
Confidence
90% confidence
Finding
The uuid package is not pinned to an exact version, so installs may resolve to different patch/minor releases over time. That creates reproducibility and supply-chain integrity issues, and is more concerning here because this package line is also associated with a known advisory.

Known Vulnerable Dependency: uuid==13.0.0 — 1 advisory(ies): CVE-2026-41907 (uuid: Missing buffer bounds check in v3/v5/v6 when buf is provided)

Low
Category
Supply Chain
Confidence
81% confidence
Finding
The dependency uuid 13.0.0 is associated with an advisory involving missing buffer bounds checks when specific API variants are used with a provided buffer. This is a real issue, but its practical impact depends on whether the application uses the affected v3/v5/v6 code paths with attacker-influenced buffer arguments; based on package.json alone, exposure is limited but still present.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
index.js:69