Back to skill

Security audit

pr-to-video

Security checks for vulnerabilities and agentic risk

Overview

The skill’s PR-to-video workflow is coherent, but it relies on mutable external code and can update global HyperFrames skills during execution.

Install only if you are comfortable with HyperFrames, npm registry resolution, GitHub CLI access to the target PR, and optional HeyGen/media credentials being used during the run. Prefer a pinned or preinstalled HyperFrames CLI, review any global skill updates before accepting them, and run the workflow in a project-scoped sandbox without unnecessary home-directory secrets.

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

T08 · Insecure Dependencies

Warning
Location
scripts/preflight.mjs:12
Finding
Unpinned HyperFrames Package and Registry Component Execution<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:6, 45, 154, 178, 216-234`; `scripts/preflight.mjs:12-16` **Vulnerability Type**: Supply-chain exposure through unpinned package and component execution **Risk Level**: Medium ### Vulnerable Code `scripts/preflight.mjs:12-16`: ```js export function runCliPreflight({ command = "check", spawn = spawnSync } = {}) { const result = spawn("npx", ["hyperframes", "--help"], { encoding: "utf8", shell: process.platform === "win32", }); ``` Related commands in `SKILL.md`: ```bash npx hyperframes skills update pr-to-video npx hyperframes init "$PROJECT_DIR" --non-interactive --example=blank --skill=pr-to-video npx hyperframes catalog --query "<the look, in plain English>" --json for b in <each registry block named in the storyboard>; do npx hyperframes add "$b"; done npx hyperframes lint npx hyperframes check npx hyperframes snapshot --at <frame-midpoints> npx hyperframes preview "$PROJECT_DIR" --background npx hyperframes render --skill=pr-to-video --quality high --output renders/video.mp4 ``` ### Technical Analysis The workflow repeatedly invokes `npx hyperframes` without specifying an audited package version or integrity digest. If a trusted local package is unavailable, `npx` can resolve and download a package from the configured npm registry before executing it. The workflow also installs registry blocks selected dynamically from storyboard content. No component version, cryptographic digest, or immutable manifest is required before those blocks are added to the project. The initial Skill update requires user confirmation, but that confirmation does not provide integrity protection and does not cover every later package or registry-component retrieval. Consequently, the effective executable content can change after this Skill has been reviewed. This access is broader than the minimum privilege needed to invoke a known renderer: a fixed, previously verified CLI and immutable component ...[truncated 1476 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin the HyperFrames CLI to an explicitly audited version, such as an exact package version rather than an unconstrained package name. 2. Install the verified CLI during a controlled setup phase and use `npx --no-install hyperframes ...` or invoke the verified local binary directly. 3. Maintain a lockfile and require registry integrity metadata for all npm artifacts. 4. Pin each HyperFrames registry block to an immutable version or content digest. 5. Verify downloaded component hashes against a trusted manifest before installation or rendering. 6. Disable package lifecycle scripts where feasible and reject packages that require unexpected installation hooks. 7. Require explicit user approval before retrieving any component that is not already present in the verified manifest. 8. Run package/component installation and rendering in a sandbox with: - A project-scoped writable filesystem. - No unnecessary access to the home directory. - No inherited cloud or repository credentials. - Restricted outbound network access. 9. Log the exact package versions, component versions, sources, and hashes used for every build. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/audio.mjs:70
Finding
Environment-Controlled JavaScript Audio Engine Execution<![CDATA[ ## Vulnerability Details **File Location**: `scripts/audio.mjs:70-88` **Vulnerability Type**: Arbitrary code execution through an unvalidated environment-controlled script path **Risk Level**: Medium ### Vulnerable Code ```js function runEngine({ request, hyperframesDir, neutral, only, extra = [] }, die) { const reqPath = join(hyperframesDir, "audio_request.json"); writeFileSync(reqPath, JSON.stringify(request, null, 2)); const engine = process.env.HF_MEDIA_ENGINE || DEFAULT_ENGINE; if (!existsSync(engine)) die(`media audio engine not found at ${engine} (set $HF_MEDIA_ENGINE)`); const args = [ engine, "--request", reqPath, "--hyperframes", hyperframesDir, "--out", neutral, "--only", only, ...extra, ]; const r = spawnSync("node", args, { stdio: "inherit" }); if (r.status !== 0) die(`media audio engine exited ${r.status}`); } ``` ### Technical Analysis The `HF_MEDIA_ENGINE` environment variable is treated as the path of a JavaScript program and passed directly to Node. The only validation is `existsSync(engine)`, which establishes that the path exists but does not establish that it is an approved engine. The code does not: - Canonicalize and constrain the path to an approved directory. - Verify file ownership or permissions. - Verify a cryptographic digest. - Require explicit confirmation for an override. - Distinguish the trusted default engine from an externally supplied engine. As a result, anyone who can influence the Skill process environment can select an arbitrary existing JavaScript file for execution when audio generation or SFX retrieval runs. The process uses argument-array execution rather than shell interpolation, so this is not shell command injection. The risk arises because an untrusted path is intentionally executed as JavaScript. ### Attack Path 1. An attacker gains the ability to influence the environment used to launch the Agent or Skill, such as through a wrapper, CI ...[truncated 1352 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the `HF_MEDIA_ENGINE` override from production execution when it is not strictly necessary. 2. If an override is required, expose it as an explicit CLI option requiring informed user approval rather than silently inheriting it from the environment. 3. Resolve the engine path with `realpathSync()` and require it to remain under a fixed, trusted media-engine directory. 4. Compare the selected path against a strict allowlist of approved engine files. 5. Verify the engine file against a trusted cryptographic hash before execution. 6. Reject symlinks or revalidate the canonical target immediately before spawning to reduce path-substitution risks. 7. Check that the engine and its parent directories are not writable by untrusted users. 8. Launch the audio engine with a minimal environment rather than inheriting all process variables. 9. Run the engine in a sandbox with project-scoped filesystem access and restricted outbound network access. 10. Record the canonical engine path and verified digest in build logs for auditability. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (70)

Ae1

High
Category
analysis-evasion
Content
nto the project as the source of information. There is **no website capture**. `fetch-pr.mjs` runs `gh` deterministically — completing the files list via pagina
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
nto the project as the source of information. There is **no website capture**. `fetch-pr.mjs` runs `gh` deterministically — completing the files list via pagina
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
.json`, so the end card can cite a real version instead of inventing one. Then `ingest.mjs` folds that into the synthetic capture package offline.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
.json`, so the end card can cite a real version instead of inventing one. Then `ingest.mjs` folds that into the synthetic capture package offline.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
**Gate:** `build-frame.mjs` exited 0 — `frame.md` exists from the code-editorial preset, and `.hyperframes/caption-skin.html` exists as the caption skin source.
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

Ae1

High
Category
analysis-evasion
Content
jecting brand tokens from `frame.md`; `captions: skipped (<reason>)` is valid. `assemble-index.mjs` stages the credits avatars from `assets/` as an idempotent b
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.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill invokes networked and environment-dependent tooling (`npx hyperframes`, `gh`, Node scripts, background services) but does not declare any explicit tool scope such as allowed tools or permissions. That omission weakens least-privilege controls and makes it easier for an agent runtime to grant broader capabilities than the workflow actually needs.

Rp1

Medium
Category
MCP Rug Pull
Confidence
97% confidence
Finding
The skill tells the agent to run `npx hyperframes ...` without pinning a version, which means execution depends on whatever package version is current at run time. If the upstream package is compromised or a breaking release ships, the agent could execute attacker-controlled code or behave unpredictably.

Rp1

Medium
Category
MCP Rug Pull
Confidence
97% confidence
Finding
This workflow initializes projects via unpinned `npx hyperframes`, so package resolution is non-deterministic and can execute newly published code. In an agent setting, that becomes a supply-chain execution risk rather than just a reliability issue.

Rp1

Medium
Category
MCP Rug Pull
Confidence
97% confidence
Finding
The command explicitly states that `init` may update global skills from GitHub, and it is launched through unpinned `npx hyperframes`. That compounds supply-chain risk by allowing both package-version drift and remote skill updates during execution.

Session Persistence

Medium
Category
Rogue Agent
Content
Goal: Turn the PR into an approved frame-by-frame explanation plan.

Read `../hyperframes-creative/references/story-spine.md` (hook language, value-before-evidence, storyboard-as-proposal, source-traceable visuals), `references/story-design.md`, `../hyperframes-animation/blueprints-index.md`, `../hyperframes-core/references/storyboard-format.md`, and `../hyperframes-core/references/script-format.md`. Use them to write `STORYBOARD.md` and, when narration is needed, `SCRIPT.md`. Set the frontmatter `duration:` from the brief's `length` — a rough expectation; assembly reports where the cut lands against it.

Use `story-design.md` for the PR archetype (changelog / feature-reveal / fix-explainer / refactor-walkthrough), the PR-native frame types, hook, persuasion, beats, the per-frame word budget, and the credits close. The sequence comes from **narrative design, not the diff's file order** — explain the change, don't read the diff aloud. As a **soft guide**, consult the role→blueprint menu in `../hyperframes-animation/blueprints-index.md`: for each beat, write the voiceover in the shape its candidate blueprint implies and tag that candidate `blueprint:` id when one fits (story truth still decides which beats exist — never force a beat to fit a shape). Feature 2–4 real diff hunks (from `capture/diff.patch`), each a small legible snippet; name the `code-*` block each wants in the frame's `scene`. Frames carry no `asset_candidates` except the `credits` close (1–6 `assets/<login>.png` avatars). Use the exact required fields from the storyboard and script references.
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The skill hard-codes a female default voice (`Marcia`) unless the user specifies otherwise, which can override user expectations and create consent/accessibility concerns around voice identity. This is not a classic exploit path, but it is a policy and trust issue in an agent workflow that generates user-facing media automatically.

Rp1

Medium
Category
MCP Rug Pull
Confidence
96% confidence
Finding
Using `npx hyperframes catalog --query ...` without version pinning still causes code from the package registry to be fetched/executed dynamically. Even read-oriented commands can run arbitrary install-time or runtime package code if the package is compromised.

Rp1

Medium
Category
MCP Rug Pull
Confidence
96% confidence
Finding
`npx hyperframes add` installs registry blocks based on storyboard content, and because the CLI is unpinned, both the package manager path and the fetched artifacts may vary over time. This expands the attack surface for dependency or content poisoning during agent execution.

Rp1

Medium
Category
MCP Rug Pull
Confidence
96% confidence
Finding
Running unpinned `npx hyperframes lint` means a validation step itself may execute changed upstream code. Security checks should be deterministic; otherwise the verification path becomes another supply-chain trust point.

Rp1

Medium
Category
MCP Rug Pull
Confidence
96% confidence
Finding
The `check` command is also executed from an unpinned package source, so the agent's trust in the results depends on mutable upstream code. A compromised or buggy release could mask issues or introduce harmful behavior.

Rp1

Medium
Category
MCP Rug Pull
Confidence
96% confidence
Finding
Snapshot generation through unpinned `npx hyperframes` has the same remote-code-execution and non-determinism problem as the other invocations. In aggregate, repeated unpinned executions increase exposure across the whole workflow.

Rp1

Medium
Category
MCP Rug Pull
Confidence
96% confidence
Finding
Previewing the project with unpinned `npx hyperframes preview` starts a background server using mutable upstream code. That increases risk because long-lived background processes may expose additional local interfaces or behaviors.

Rp1

Medium
Category
MCP Rug Pull
Confidence
97% confidence
Finding
Final rendering is performed by unpinned `npx hyperframes render`, making the highest-value execution step dependent on latest upstream package state. A malicious or broken release could affect output integrity or execute arbitrary code during render.

Rp1

Medium
Category
MCP Rug Pull
Confidence
96% confidence
Finding
Stopping the preview server through unpinned `npx hyperframes` is another mutable execution point. While lower impact than render, it still extends the workflow's exposure to uncontrolled upstream code.

Rp1

Medium
Category
MCP Rug Pull
Confidence
95% confidence
Finding
The quick reference reinforces a general pattern of installing components via `npx hyperframes add <name>` with no version or provenance constraints. That invites unreviewed remote content into the build path and is dangerous in an automated agent context.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

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

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/captions.test.mjs:242

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/fetch-pr.mjs:50

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/frame-contract.test.mjs:111