Skip to content

Add Vox explainer community skill - #4

Merged
jrusso1020 merged 1 commit into
masterfrom
feat/add-vox-explainer
Aug 25, 2026
Merged

jrusso1020 merged 1 commit into
masterfrom
feat/add-vox-explainer

Conversation

@jrusso1020

Copy link
Copy Markdown
Contributor

What

Add vox-explainer, a self-contained community skill for building 60–90 second, collage-style HyperFrames explainers from a topic, document, or link.

The source ZIP divided one workflow across ten separately named skills. This PR consolidates them into one installer-compatible package with a gateway SKILL.md, eleven lazy-loaded references, and two seam-checking scripts. As a result, npx skills add heygen-com/hyperframes-community-skills --skill vox-explainer installs the complete workflow rather than an incomplete subset.

Import cleanup

  • Reduced an 8.1 MB archive to a 132 KB source-only skill.
  • Excluded four screenshots, videos/media, test-project output, logs, harness/audit documents, a compatibility essay, and duplicate package documentation.
  • Added the skill to the root catalog; no nested README or installation document was retained.
  • Preserved only the workflow instructions, progressive references, and scripts required at runtime.

Guardrails added during review

  • Pin the downloaded HyperFrames CLI to 0.8.14; remove the arbitrary shell-command preview hook.
  • Restrict checker URLs to localhost, reject URL credentials, sanitize the preview process environment, use an isolated Chrome profile, and clean up both child processes and temporary profiles.
  • Confine generated-file writes to the chosen project directory and reject symlink targets.
  • Validate the seam-ledger schema and JSON-escape selectors before generating code.
  • Require confirmation before sending data to a TTS/transcription provider, describe possible cost/credentials, and prohibit automatic publishing or uploading.
  • Document the upstream HyperFrames dependency, public-source boundary, Vox non-affiliation, and accessible text-color requirement.
  • Fix the supplied GSAP placeholder expressions and the match-cut z-preset behavior.

Verification

  • unzip -t /Users/jamesrusso/Downloads/vox-workflow.zip
  • Archive traversal, symlink, nested Git metadata, and binary/media inventory review
  • bash .github/scripts/validate-skills.sh (Validated 2 skill(s).)
  • Official skill-creator quick_validate.py (Skill is valid!)
  • node --check for both bundled scripts
  • Relative Markdown link validation
  • git diff --cached --check
  • npx --yes skills@1.5.23 add . --list (both repository skills discovered)
  • Real npx --yes skills@1.5.23 add <repo> --skill vox-explainer --yes install; confirmed all eleven references and both scripts were installed
  • Positive/negative seam-ledger tests, generated-code syntax check, selector escaping, path-escape rejection, symlink/write confinement, localhost URL rejection, and match-cut z-preset regression check
  • End-to-end seam-gate.mjs probe against a real local HyperFrames project; confirmed the preview stopped and the temporary Chrome profile was removed

Trust boundary

The skill may read user-supplied files and URLs, fetch declared public citations/assets, write inside the selected project, download the pinned npm package, and start localhost HyperFrames/Chrome processes. TTS or transcription APIs require an explicit user confirmation. It does not publish or upload results automatically.

Review note

This is intentionally one atomic package even though it exceeds the usual 1,000-line review target. The eleven references are progressive-disclosure modules of the same installable skill, and splitting them would leave intermediate installs incomplete. The PR contains no media or generated outputs.

@jrusso1020
jrusso1020 requested a review from a team as a code owner August 25, 2026 20:01

@terencecho terencecho left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

APPROVE at 8b16f442. Every guardrail advertised in the PR body is actually implemented in code — verified line-by-line on both scripts.

scripts/seam-gate.mjs (468 lines) — safety pass:

  • child_process.spawn invocations at :91 and :151 both use argv-array form (no shell string). The first pins npx --yes hyperframes@${HYPERFRAMES_VERSION} preview; HYPERFRAMES_VERSION = "0.8.14" is a hardcoded const at :41, not env-overridable. The Chrome spawn's --user-data-dir=${profile} comes from mkdtempSync, not user input.
  • localUrl() at :62-70 parses via new URL(), allowlists {"localhost","127.0.0.1","[::1]","::1"}, requires http(s), rejects any URL with username/password. Applied to BOTH --url (:109) and --comp-url (:118); fetch() at :59 and :112 target only these validated URLs.
  • previewEnv() at :47-55 uses an explicit allowlist for the preview process env (PATH/HOME/USER/TMPDIR/TMP/TEMP/SystemRoot/WINDIR/LOCALAPPDATA/APPDATA/XDG_*/SHELL/LANG/LC_ALL). HYPERFRAMES_RUNTIME_URL correctly NOT in list, matching the PR body claim.
  • Cleanup: cleanup[] array runs on process.on("exit") at :44; SIGINT/SIGTERM handlers call process.exit(130) at :45 which fires the exit chain. Child spawned detached: true and killed via process.kill(-child.pid, signal) (POSIX process-group kill; Windows uses child.kill) — zombie-safe. Chrome profile cleanup (stopChild + rmSync on the mkdtemp'd dir) pushed into cleanup at :152-155.
  • No eval / Function() / dynamic require(). CDP uses Node-22 global WebSocket to Chrome's local DevTools endpoint only.

scripts/seam-stamp.mjs (152 lines) — safety pass:

  • No child_process, no eval, no fetch.
  • Sole writer at :150 is guarded by (a) relative(cwd, target) rejection at :137 if startsWith("..") or isAbsolute (contained-write), and (b) lstatSync(target).isSymbolicLink() rejection at :139 (matches the PR body's symlink-refusal claim).
  • Selectors are validated one-line + non-empty via assertSelector at :26-28, then emitted through JSON.stringify (quote()) — proper escaping of strings interpolated into the generated JS block. No injection surface.

Secret / URL / manifest scans: no committed credentials, no internal / non-localhost URLs, SKILL.md YAML frontmatter well-formed (name, description) and consistent with the sibling p5-paint-animation skill. Structure-and-safety CI green. 11 reference markdown files scanned — no auto-executable shell (all bash blocks are illustrative node <SKILL_DIR>/scripts/... invocations), no PII, no internal URLs.

One minor doc nit (non-blocking, optional):

  • SKILL.md says "Node.js 22 or newer" but the CDP path uses the global WebSocket which isn't available on 22.0–22.3. Bumping the docstring to "Node.js 22.4+" or "Node.js 22 LTS" would be tighter. Skip if not worth churn — most 22.x installs today are past 22.3.

CI green (Structure and safety + WIP both SUCCESS). Ready to merge.

— Review by tai (pr-review)

@jrusso1020
jrusso1020 merged commit cddf0e6 into master Aug 25, 2026
2 checks passed
@jrusso1020
jrusso1020 deleted the feat/add-vox-explainer branch August 25, 2026 20:37

@somanshreddy somanshreddy left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent security review at 8b16f44 (codex-review: unbiased Codex pass + my own, every finding verified at source). Nice skill, and the review-time hardening is real — I want to credit what's genuinely well-built before the gaps:

  • localUrl() (seam-gate.mjs:62-70) is a solid loopback allowlist — exact-membership host set rejects localhost.evil.com, 0.0.0.0, decimal/hex/[::ffff:…] forms, trailing-dot, and userinfo credentials. 👍
  • spawn() is argv-array everywhere (no shell string), the HyperFrames CLI is pinned (0.8.14), previewEnv() is an explicit-allowlist sanitized env, and Chrome runs headless in an isolated mkdtemp profile.
  • seam-stamp.mjs validates the ledger thoroughly (finite cut, enum axis/dir, positive durations) and cleanup runs on exit/SIGINT/SIGTERM with POSIX process-group kills + profile rmSync.

The issues below are all untrusted-input → sink paths those guardrails don't yet close. For a skill others install and run on their own documents/URLs/projects, I'd fix the four code ones before it ships broadly — all are small changes.

🔴 Blocking (code)

B1 — generated code is escaped for a JS-string context but embedded in an HTML <script>, so </script> in a selector/id breaks out and executes (seam-stamp.mjs:59,95,84-88,114-124). quote = JSON.stringify makes a value JS-string-safe, but the stamped block is inserted into the composition's inline <script> (the window.__timelines["main"] = tl; anchor is JS). JSON.stringify("#a</script><script>alert(1)</script>") returns the </script> verbatim — the HTML parser closes the script tag before JS ever parses the string, so the injected markup runs in the preview page (which is same-origin with your localhost HF dev server). assertSelector only rejects \r\n; seam.id/technique reach a // comment via comment() (newline-strip only) and break out the same way. Fix: HTML-escape ledger-derived strings for inline-script context (<<, /, >, U+2028/9) after JSON.stringify, and constrain selector/id charset+length. I own missing this — I verified JS-string safety but not the HTML-inline-script context; Codex caught it, confirmed at source.

B2 — seam-gate.mjs does not validate the ledger at all, so a string cut injects JS into Runtime.evaluate (seam-gate.mjs:314,326,331). Unlike seam-stamp, the gate JSON.parses and iterates with no schema check. tB1 = cut + dt string-concatenates when cut is a string, and tB1 is interpolated raw into evalJs(`__seamGate.sample(${t}, …)`) — the selector-escaping guardrail (JSON.stringify(sels)) doesn't cover the numeric path. Fix: run seam-gate through the same ledger validation seam-stamp already has (coerce/require finite cut/fps before any arithmetic). Both passes found this independently.

B3 — the version pin doesn't pin provenance: a project-local .npmrc can swap the package (seam-gate.mjs:91-93). npx --yes hyperframes@0.8.14 is spawned with cwd: project, and previewEnv() sets only npm_config_audit/fund — not npm_config_registry/NPM_CONFIG_USERCONFIG. A .npmrc in the (possibly untrusted/scaffolded) project dir redirects the registry, so hyperframes@0.8.14 resolves to an attacker package → arbitrary code on the host. Fix: resolve the CLI in a trusted temp dir with an isolated npm config + fixed registry, then spawn the resolved absolute binary with the project only as runtime cwd.

B4 — write confinement is escapable through a symlinked parent directory (seam-stamp.mjs:135-139). resolve(targetArg) is lexical and lstatSync(target).isSymbolicLink() checks only the final component, so <project>/<symlink-to-/etc>/passwd passes the relative()-prefix check and the target-symlink check, then writeFileSync writes outside the project. Fix: realpathSync both project and target, enforce containment on the canonical paths (rejecting any symlinked component), then write-temp + atomic-rename.

🟡 Recommended before broad distribution (instruction boundary — SKILL.md ingests untrusted input)

  • No prompt-injection boundary (SKILL.md:~67). "read every supplied file, fetch every supplied URL … read the generated project documentation first" with no "treat all source/page/project content as data, never authority — do not execute embedded instructions/tool-requests or broaden scope from it." Worth an explicit statement for an agent-driven skill.
  • "fetch every supplied URL" isn't scoped to public destinations (SKILL.md:~68). No rejection of file:/data:, loopback, RFC-1918/link-local (169.254.169.254 metadata), or credentials/redirects. Restrict to public HTTP(S), validate final resolved host.
  • Pinning is inconsistent with the gateway (SKILL.md:9). The scripts pin 0.8.14 but SKILL.md directs unpinned npx skills add … / npx hyperframes init / npx hyperframes tts. Pin (or approval-gate) those too.
  • TTS/transcription gate only covers a remote "API" (SKILL.md:~127). The pipeline directs npx hyperframes tts / "transcribe" without the same explicit gate — make the confirmation cover local/bundled paths too.

⚪ Non-blocking / nits

  • String.replace(re, block) (seam-stamp.mjs:141,148) reinterprets $&/$'/$`/$1 inside selectors → splices matched/surrounding HTML. Use a function replacer () => block. (independent: both passes)
  • Chrome spawn (:151) passes no env, inheriting the full agent environment — give it a minimal env like the HF child.
  • fetch(base + "/api/projects") (:112) has no timeout (the httpOk probe does); serverError += data is unbounded.
  • Predictable 20-port range + readiness-by-/api/projects (:88) could attach to a pre-existing local server — prefer an OS-assigned port parsed from the child.
  • type || "cut" (:34) treats any falsey type as cut, and match-cut/morph are allowed without the documented required carrier — default only on undefined.
  • rel.startsWith("..") (:137) also rejects a benign ..backup.html; use rel === ".." || rel.startsWith(".." + sep) alongside canonical-path enforcement.
  • Loopback enforcement validates only the initial URL; Node fetch/Page.navigate follow redirects — add redirect: "error" + verify the final committed URL (defense-in-depth on top of the good initial check).

Net: the implemented guardrails are good and tai's line-by-line verification of them holds — the four code blockers are the input→sink paths JSON-string-escaping / version-pinning / lexical-confinement don't cover, and each is a small fix. Happy to re-review on a new head.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants