Back to skill

Security audit

faceless-explainer

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its video-generation purpose, but it asks agents to run mutable remote tooling and contains file-path handling that can write outside the intended project.

Review before installing. Use only in a sandbox or disposable project, avoid approving automatic updates unless you trust the exact HyperFrames source being fetched, and prefer pinned tooling. Do not run it on projects containing untrusted `STORYBOARD.md` or `audio_meta.json` until path containment is fixed. Expect it to use HeyGen/local audio tooling and to save project briefs and preferences for later reuse.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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)

T08 · Insecure Dependencies

Error
Location
SKILL.md:6
Finding
Unpinned Remote Package Execution and Automatic Skill Updates<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:6`, `SKILL.md:30`, `SKILL.md:126`, `SKILL.md:182-200` **Vulnerability Type**: Unpinned third-party package execution and automatic remote updates **Risk Level**: High ### Vulnerable Code ```text > **First, keep this skill fresh — confirm with the user before running:** `npx hyperframes skills update faceless-explainer`. A fast no-op when everything is current; otherwise it refreshes this skill plus the core domain skills it depends on before you rely on them. ``` ```text `npx hyperframes init "videos/<project>" --non-interactive --example=blank --skill=faceless-explainer` — `init` checks the installed skills against the latest on GitHub and updates the global set if any are out of date. ``` ```text run `npx hyperframes catalog --query "<the look, in plain English>" --json` ``` ```text npx hyperframes lint npx hyperframes check npx hyperframes snapshot --at <frame-midpoints> npx hyperframes preview --background npx hyperframes render --skill=faceless-explainer --quality high --output renders/video.mp4 ``` ### Technical Analysis The workflow repeatedly invokes `npx hyperframes` without an exact package version, lockfile, or verified artifact hash. Depending on the local npm state, `npx` may download and execute the current package release from the configured npm registry. The initialization command also explicitly checks GitHub for newer Skill content and may update the global Skill set. Consequently, the code and instructions that execute can differ from the version reviewed in this audit. User confirmation before the explicit update reduces surprise but does not authenticate the downloaded artifact or protect subsequent unpinned `npx` invocations. It also does not constrain what a compromised package lifecycle or CLI entry point can execute. ### Attack Path 1. An attacker compromises the `hyperframes` npm package, its publisher account, registry resolution, GitHub update source, or a futu ...[truncated 1119 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin `hyperframes` to an audited exact version, for example: ```text npx --no-install hyperframes ``` after installing an exact version through a lockfile, or: ```text npx hyperframes@<audited-exact-version> ``` 2. Commit and enforce a package lockfile containing registry integrity hashes. 3. Disable automatic global Skill updates during `init`. 4. Separate updates into an explicit administrative workflow that: - displays the source and exact target version; - verifies a signature or cryptographic digest; - downloads without executing; - presents the diff for review; - requires separate approval before installation. 5. Run package commands in a sandbox with access limited to the active project and required media directories. 6. Restrict network destinations to approved npm, GitHub, HeyGen, and catalog endpoints. 7. Avoid inheriting unnecessary credentials or environment variables when executing third-party CLIs. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/assemble-index.mjs:255
Finding
Storyboard-Controlled Frame Paths Can Modify Files Outside the Project<![CDATA[ ## Vulnerability Details **File Location**: `scripts/assemble-index.mjs:255-296`, `scripts/transitions.mjs:100-143`, `scripts/lib/pad-frame-duration.mjs:15-33` **Vulnerability Type**: Path traversal leading to arbitrary file modification **Risk Level**: High ### Vulnerable Code From `scripts/assemble-index.mjs`: ```js for (const f of manifest.frames) { const label = `frame ${f.number ?? f.index}${f.title ? ` (${f.title})` : ""}`; const built = f.status === "built" || f.status === "animated"; if (!f.src) { if (built) die(`${label} is ${f.status} but has no \`src\` — the orchestrator must write it`); anomalies.push(`${label}: status ${f.status}, no src — skipped`); continue; } const compAbs = join(hyperframesDir, f.src); let html; try { html = readFileSync(compAbs, "utf8"); } catch { if (built) die(`${label} is ${f.status} but its src ${f.src} is not on disk — re-dispatch the worker`); anomalies.push(`${label}: src ${f.src} not on disk (status ${f.status}) — skipped`); continue; } // ... const guard = guardFrame(html, label); if (guard.repairedHtml) { writeFileSync(compAbs, guard.repairedHtml); html = guard.repairedHtml; repairs.push(guard.repairNote); } } ``` From `scripts/transitions.mjs`: ```js function extendFrameTail(hyperframesDir, frame, baseDuration, targetDuration, die) { if (!frame?.src || targetDuration <= baseDuration) return; const framePath = join(hyperframesDir, frame.src); let html; try { html = readFileSync(framePath, "utf8"); } catch { die(`outgoing frame file not found at ${framePath}`); } // ... if (!foundRoot) die(`${frame.src} has no data-composition-id="${compId}" root`); writeFileSync(framePath, rewritten); } ``` From `scripts/lib/pad-frame-duration.mjs`: ```js export function padFrameInternalDuration(hyperframesDir, frameSrc, frameId, newDuration) { const framePath = resolve(hyperframesDir, frameSrc); let html; try { ...[truncated 2317 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create a centralized containment helper: ```js import { isAbsolute, relative, resolve, sep } from "node:path"; function resolveInside(base, candidate) { if (typeof candidate !== "string" || !candidate || isAbsolute(candidate)) { throw new Error("absolute or empty paths are not allowed"); } const root = resolve(base); const target = resolve(root, candidate); const rel = relative(root, target); if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) { throw new Error(`path escapes project root: ${candidate}`); } return target; } ``` 2. Apply the helper before every read or write based on `frame.src`. 3. Enforce a narrower allowlist: - path must begin with `compositions/frames/`; - extension must be `.html`; - reject null bytes, absolute paths, and all `..` segments. 4. Resolve symlinks with `realpath()` and verify the real target remains beneath the real project root before writing. 5. Open files defensively where supported to avoid following attacker-controlled symlinks. 6. Validate `STORYBOARD.md` against a schema before assembly. 7. Add regression tests for: - `../target.html`; - nested traversal; - absolute paths; - symlinks escaping the project; - valid files under `compositions/frames/`. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/assemble-index.mjs:79
Finding
Audio Metadata Path Traversal Can Cause FFmpeg to Overwrite External Files<![CDATA[ ## Vulnerability Details **File Location**: `scripts/assemble-index.mjs:79-116`, `scripts/assemble-index.mjs:419-430` **Vulnerability Type**: Path traversal through BGM metadata leading to external file reads and writes **Risk Level**: High ### Vulnerable Code ```js function ensureBgmCovers(relPath, hyperframesDir, total) { const abs = join(hyperframesDir, relPath); const probe = spawnSync( "ffprobe", ["-v", "error", "-show_entries", "format=duration", "-of", "csv=p=0", "--", abs], { encoding: "utf8" }, ); if (probe.status !== 0) return { looped: false, short: false, reason: "ffprobe unavailable" }; const dur = parseFloat(String(probe.stdout || "").trim()); if (!Number.isFinite(dur) || dur <= 0) return { looped: false, short: false, reason: "unreadable duration" }; if (dur >= total - 0.1) return { looped: false, short: false, dur }; const relOut = relPath.replace(/\.([^./]+)$/, ".loop.mp3"); const absOut = join(hyperframesDir, relOut); const fadeOut = Math.max(0, total - 1.5); const ff = spawnSync( "ffmpeg", [ "-y", "-stream_loop", "-1", "-i", abs, "-t", String(total), "-af", `afade=t=in:st=0:d=0.4,afade=t=out:st=${fadeOut}:d=1.5`, "-c:a", "libmp3lame", "-q:a", "2", absOut, ], { encoding: "utf8" }, ); if (ff.status !== 0 || !existsSync(absOut)) return { looped: false, short: true, dur, reason: "ffmpeg unavailable" }; return { looped: true, rel: relOut, from: dur }; } ``` ```js if (audio.bgm?.path) { if (existsSync(join(hyperframesDir, audio.bgm.path))) { let bgmSrc = audio.bgm.path; const cov = ensureBgmCovers(audio.bgm.path, hyperframesDir, TOTAL); if (cov.looped) { bgmSrc = cov.rel; bgmNote = ` (looped ${cov.from.toFixed(1)}s→${TOTAL}s)`; } // ... } } ``` ### Technical Analysis The BGM path is loaded from `audio_meta.json`, which may be generated by the shared aud ...[truncated 1761 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate all media paths before using them: - reject absolute paths; - reject `..` segments; - require BGM paths to reside under `assets/bgm/`; - require voice paths under `assets/voice/`; - require SFX paths under `assets/sfx/`. 2. Resolve and compare canonical paths, including symlink resolution: ```js const bgmRoot = realpathSync(join(hyperframesDir, "assets/bgm")); const input = realpathSync(resolve(hyperframesDir, relPath)); const rel = relative(bgmRoot, input); if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) { throw new Error("BGM path escapes assets/bgm"); } ``` 3. Do not derive an output path directly from untrusted metadata. Generate the looped filename inside a fixed directory using a validated basename or random identifier. 4. Refuse to overwrite an existing output file unless it was previously generated by this workflow and positively identified as such. 5. Validate `audio_meta.json` against a strict schema before processing. 6. Treat metadata produced by shared or remote engines as untrusted input. 7. Add tests covering traversal, absolute paths, symlink escapes, malformed extensions, and attempts to overwrite existing external files. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (38)

Ae1

High
Category
analysis-evasion
Content
**Gate:** `build-frame.mjs` exited 0 — `frame.md` exists from a named preset, and (when the preset ships one) `.hyperframes/caption-skin.html` exists as the cap
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
md`. That combination marks the project silent — no narration, no BGM, no SFX. `audio.mjs` recognizes it and generates nothing (it removes any stale `audio_meta
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Hidden Instructions

High
Category
Prompt Injection
Content
function guardFrame(html, label) {
  const errors = [];
  // Scan a copy with comments + <script>/<style> bodies blanked, so a tag-like string
  // in a comment (e.g. "<!-- match the host <video> coords -->") or in GSAP code can't
  // trip ②. ① still splices into the ORIGINAL html, so its offsets stay correct.
  const scan = html
    .replace(/<!--[\s\S]*?-->/g, " ")
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Hidden Instructions

High
Category
Prompt Injection
Content
function guardFrame(html, label) {
  const errors = [];
  // Scan a copy with comments + <script>/<style> bodies blanked, so a tag-like string
  // in a comment (e.g. "<!-- match the host <video> coords -->") or in GSAP code can't
  // trip 2. 1 still splices into the ORIGINAL html, so its offsets stay correct.
  const scan = html
    .replace(/<!--[\s\S]*?-->/g, " ")
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Hidden Instructions

High
Category
Prompt Injection
Content
let captionsEmitted = false;
if (existsSync(join(hyperframesDir, "compositions/captions.html"))) {
  body.push(
    `      <!-- captions -->`,
    `      <div`,
    `        id="el-captions"`,
    `        class="scene"`,
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Anti-Refusal Statement

High
Category
Anti-Refusal
Content
assert.match(result.stderr + result.stdout, /pending/i);
});

test("fetch-sfx: a resolved bed reports bgm_pending false and no warning", () => {
  const { dir, result } = runFetchSfx({
    storyboard: FRAME_WITH_SFX("whoosh"),
    neutralOut: { voices: [], bgm: { path: "assets/bgm/track.mp3", volume: 0.12 }, sfx: [] },
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.

Lp3

Medium
Category
MCP Least Privilege
Confidence
80% confidence
Finding
The skill declares no explicit tool scope while instructing the agent to run many shell commands and consult environment-dependent auth state. Without a restrictive allowlist, an agent may exercise broader local capabilities than the workflow actually needs, increasing the blast radius if the skill or its dependencies are compromised.

Rp1

Medium
Category
MCP Rug Pull
Confidence
97% confidence
Finding
The skill tells the agent to run `npx hyperframes skills update faceless-explainer`, which pulls and executes whatever version is current at runtime. That creates a supply-chain risk: a compromised upstream package or repo can change behavior after review and cause arbitrary code execution on the user's machine.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The skill explicitly instructs the agent to update skills before running, despite the workflow being a text-to-video authoring task that does not require fetching fresh code on each execution. This unnecessarily expands trust to remote GitHub-hosted content and turns a routine content task into a software installation event.

Vague Triggers

Medium
Confidence
90% confidence
Finding
This markdown file defines routing/activation behavior, so SQP-1 applies. The phrases 'any other intent, a bare "make a video", or any uncertainty' are broad and ambiguous, making it unclear when this skill should activate versus defer, which could cause unintended routing collisions with common requests.

Session Persistence

Medium
Category
Rogue Agent
Content
## Step 0: Setup

Goal: Enter with a confirmed brief, create the HyperFrames project, and make the brief durable.

**The brief is confirmed by the intent layer, not by questions asked here.** Opening rule, in order: **(1)** `BRIEF.md` exists → read it and ask nothing — the brief is settled, and its `flow`/`storyboard` derive the mode (brief contract § 1). **(2)** No `BRIEF.md` but the project exists (`hyperframes.json` / `STORYBOARD.md` on disk) → resume from the storyboard's frontmatter and the recorded preferences; never re-interrogate a half-built project. **(3)** Neither — a fresh creation request that arrived here directly → read `/hyperframes` and run its intent layer (`references/intent-interview.md`): it checks recipes and remembered defaults, conducts this route's questions (`../hyperframes/references/routes/faceless-explainer.md`), and hands back the locked brief. Edit requests skip all of this — go do the edit.
Confidence
84% confidence
Finding
The skill directs the agent to persist and reuse prior state via `BRIEF.md`, storyboard files, and recorded preferences, including instructions to 'never re-interrogate a half-built project.' That can cause stale or sensitive user context to be silently reused across sessions or edits, leading to privacy leakage, incorrect actions, or execution based on outdated approvals.

Rp1

Medium
Category
MCP Rug Pull
Confidence
98% confidence
Finding
The `npx hyperframes init ...` command is paired with text stating that init checks installed skills against the latest on GitHub and updates the global set if any are out of date. This combines project setup with implicit remote updates, creating an unpinned code-execution path that can introduce malicious or unexpected behavior during routine use.

Context-Inappropriate Capability

Medium
Confidence
96% confidence
Finding
The setup step says project initialization may trigger global skill updates from GitHub, which exceeds what is justified for creating an explainer video from text. This widens the attack surface from local content processing to remote code retrieval and execution with global side effects.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Whitespace Padding

Medium
Category
Prompt Injection
Content
The reusable, domain-agnostic shot shapes live in `../hyperframes-animation/blueprints/` (indexed by `../hyperframes-animation/blueprints-index.md`).

| Read                                                                                                                                                        | When                                                                                                     |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `[../hyperframes-core/references/brief-contract.md](../hyperframes-core/references/brief-contract.md)`                                                      | Gate types, mode derivation from `BRIEF.md`, field semantics.                                            |
| `[../hyperframes-creative/references/story-spine.md](../hyperframes-creative/references/story-spine.md)`                                                    | Step 3: story doctrine — hook language, value-before-evidence, proposal shape, source-traceable visuals. |
Confidence
70% confidence
Finding
Large whitespace padding was detected (a block of blank lines or a long run of spaces). This can push injected instructions below or to the right of the visible area so a human reviewer never sees them while the agent still reads them. Manual review of the hidden content is recommended.

Whitespace Padding

Medium
Category
Prompt Injection
Content
The reusable, domain-agnostic shot shapes live in `../hyperframes-animation/blueprints/` (indexed by `../hyperframes-animation/blueprints-index.md`).

| Read                                                                                                                                                        | When                                                                                                     |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `[../hyperframes-core/references/brief-contract.md](../hyperframes-core/references/brief-contract.md)`                                                      | Gate types, mode derivation from `BRIEF.md`, field semantics.                                            |
| `[../hyperframes-creative/references/story-spine.md](../hyperframes-creative/references/story-spine.md)`                                                    | Step 3: story doctrine — hook language, value-before-evidence, proposal shape, source-traceable visuals. |
Confidence
70% confidence
Finding
Large whitespace padding was detected (a block of blank lines or a long run of spaces). This can push injected instructions below or to the right of the visible area so a human reviewer never sees them while the agent still reads them. Manual review of the hidden content is recommended.

Whitespace Padding

Medium
Category
Prompt Injection
Content
| `[../hyperframes-creative/references/story-spine.md](../hyperframes-creative/references/story-spine.md)`                                                    | Step 3: story doctrine — hook language, value-before-evidence, proposal shape, source-traceable visuals. |
| `[../hyperframes-creative/frame-presets/](../hyperframes-creative/frame-presets/)`                                                                          | Step 2: choose and adopt a frame preset.                                                                 |
| `[../hyperframes-creative/references/design-spec.md](../hyperframes-creative/references/design-spec.md)`                                                    | Step 2: apply brand tokens correctly.                                                                    |
| `[references/story-design.md](references/story-design.md)`                                                                                                  | Step 3: plan the explainer story.                                                                        |
| `[../hyperframes-animation/blueprints-index.md](../hyperframes-animation/blueprints-index.md)`                                                              | Step 3: role→blueprint menu. Step 4: pick the shot shape.                                                |
| `[../hyperframes-core/references/storyboard-format.md](../hyperframes-core/references/storyboard-format.md)`                                                | Step 3: write `STORYBOARD.md`.                                                                           |
| `[../hyperframes-core/references/script-format.md](../hyperframes-core/references/script-format.md)`                                                        | Step 3: write `SCRIPT.md`.                                                                               |
Confidence
70% confidence
Finding
Large whitespace padding was detected (a block of blank lines or a long run of spaces). This can push injected instructions below or to the right of the visible area so a human reviewer never sees them while the agent still reads them. Manual review of the hidden content is recommended.

Whitespace Padding

Medium
Category
Prompt Injection
Content
| `[../hyperframes-creative/references/story-spine.md](../hyperframes-creative/references/story-spine.md)`                                                    | Step 3: story doctrine — hook language, value-before-evidence, proposal shape, source-traceable visuals. |
| `[../hyperframes-creative/frame-presets/](../hyperframes-creative/frame-presets/)`                                                                          | Step 2: choose and adopt a frame preset.                                                                 |
| `[../hyperframes-creative/references/design-spec.md](../hyperframes-creative/references/design-spec.md)`                                                    | Step 2: apply brand tokens correctly.                                                                    |
| `[references/story-design.md](references/story-design.md)`                                                                                                  | Step 3: plan the explainer story.                                                                        |
| `[../hyperframes-animation/blueprints-index.md](../hyperframes-animation/blueprints-index.md)`                                                              | Step 3: role→blueprint menu. Step 4: pick the shot shape.                                                |
| `[../hyperframes-core/references/storyboard-format.md](../hyperframes-core/references/storyboard-format.md)`                                                | Step 3: write `STORYBOARD.md`.                                                                           |
| `[../hyperframes-core/references/script-format.md](../hyperframes-core/references/script-format.md)`                                                        | Step 3: write `SCRIPT.md`.                                                                               |
Confidence
70% confidence
Finding
Large whitespace padding was detected (a block of blank lines or a long run of spaces). This can push injected instructions below or to the right of the visible area so a human reviewer never sees them while the agent still reads them. Manual review of the hidden content is recommended.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/assemble-index.test.mjs:32

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/audio.mjs:87

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/audio.test.mjs:43

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/transitions.test.mjs:58