Add x-posting-license community skill - #5
Conversation
|
Addressed the review findings (new head commit):
Also: invalid counts ( 🤖 Generated with Claude Code |
Hardened per review: untrusted profile data escaped and validated, symlink-safe writes, installed invocation path, accurate network/dependency boundary, Originkit provenance. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
87725b6 to
49a0aa0
Compare
jrusso1020
left a comment
There was a problem hiding this comment.
Post-merge advisory review (this PR is already merged into master). Findings flagged for follow-up, not gating.
What improved: build.mjs:46-101 now escapes untrusted profile strings and rejects NaN, negative, and fractional counters; hostile-HTML probes no longer emit executable profile markup. SKILL.md:46-62 also works from an isolated npx skills@1.5.23 install.
Follow-up findings: Inline comments cover the remaining output-safety, dependency, telemetry/network, runtime, input-validation, shell-path, and third-party-license gaps. The highest-risk items are silent/partial overwrites, the renders/ symlink escape, the mutable canonical renderer, and the false No telemetry claim.
Validation: exact merged head 49a0aa06afcaeb90570ce944259401678ad35d4d; green repository checks; validator and Node syntax check; isolated installer/build; hyperframes check; hostile profile, malformed-count, regular-target overwrite, target-symlink, nested-directory-symlink, and whitespace-path probes; independent adversarial pass.
Verdict: COMMENT (post-merge: With fixes)
Reasoning: The normal path works and the earlier injection/install issues are fixed, but a follow-up PR is needed before users can rely on the documented safety and side-effect boundaries.
— Codex
| const outArg = resolve(args.out); | ||
| refuseSymlink(outArg); | ||
| mkdirSync(join(outArg, "assets"), { recursive: true }); | ||
| mkdirSync(join(outArg, "renders"), { recursive: true }); |
There was a problem hiding this comment.
important follow-up: mkdirSync(..., {recursive:true}) accepts an existing renders symlink, and that directory is never checked through safePath. A probe with out/renders -> external-dir completed successfully; the documented render path then resolves outside --out. Reject symlinked output subdirectories and verify their real paths before advertising or using the render destination. The repository validator only rejects symlinks committed to this repository, so it cannot catch this generated-project case.
| writeFileSync(safePath("index.html"), html); | ||
| copyFileSync(args.avatar, safePath("assets", "avatar.jpg")); | ||
| writeFileSync(safePath("hyperframes.json"), JSON.stringify({ | ||
| $schema: "https://hyperframes.heygen.com/schema/hyperframes.json", | ||
| paths: { blocks: "compositions", components: "compositions/components", assets: "assets" }, | ||
| }, null, 2)); | ||
| writeFileSync(safePath("meta.json"), JSON.stringify({ | ||
| id: "x-posting-license", name: "x-posting-license-" + handle.toLowerCase(), | ||
| }, null, 2)); |
There was a problem hiding this comment.
important follow-up: These writes overwrite existing regular files without confirmation, and validation happens one target at a time. An existing project was silently replaced in testing; if a later target (for example meta.json) is a symlink, earlier files have already been overwritten before the script exits. Preflight every destination before modifying anything and require an explicit --force for replacement, ideally using atomic temp-file-and-rename writes.
| 3. **Render** (ask the user before rendering if your harness gates renders): | ||
|
|
||
| ```bash | ||
| npx hyperframes@latest render ./license-jake -q high -o ./license-jake/renders/license.mp4 |
There was a problem hiding this comment.
important follow-up: The canonical workflow still executes mutable hyperframes@latest, even though CONTRIBUTING.md:69 rejects behavior-changing unpinned dependencies. An optional pinning suggestion does not make the default path deterministic. Pin the exact tested CLI version consistently in render, check, script comments, and the generated render hint.
| ## Network and side effects (complete list) | ||
|
|
||
| - `x.com/<handle>` — profile page the agent reads (data gathering). | ||
| - `pbs.twimg.com` — the avatar image download (data gathering). | ||
| - `api.fxtwitter.com/<handle>` — post count; free, no credentials (data gathering). | ||
| - `registry.npmjs.org` — the HyperFrames CLI itself, via `npx`. | ||
| - `cdn.jsdelivr.net` — the composition loads GSAP (pinned `3.14.2`) from | ||
| jsDelivr at preview/render time. Rendering is NOT fully offline. | ||
|
|
||
| No credentials, no paid operations, no telemetry. `build.mjs` writes only | ||
| inside the `--out` directory: it validates counters as plain non-negative | ||
| integers, strips control characters, caps lengths, HTML-escapes every profile | ||
| string before substitution, and refuses to write through symlinks. |
There was a problem hiding this comment.
important follow-up: No telemetry and complete list do not match the documented workflow. HyperFrames 0.8.16 enables anonymous PostHog telemetry by default (us.i.posthog.com), while the verified check also fetched from Google Fonts and populated ~/.cache/hyperframes/fonts. Run the documented CLI commands with HYPERFRAMES_NO_TELEMETRY=1, and disclose fonts.googleapis.com, fonts.gstatic.com, and the external cache write.
| var fs = "#version 300 es\n" + | ||
| "precision highp float; uniform float uTime; out vec4 fragColor;\n" + | ||
| "const vec2 uRes = vec2(1920.0, 1080.0);\n" + | ||
| "vec3 mod289(vec3 x){ return x - floor(x * (1.0/289.0)) * 289.0; }\n" + |
There was a problem hiding this comment.
important follow-up: This embeds a substantial portion of the acknowledged Ashima/Stefan Gustavson simplex-noise implementation, but its copyright and MIT permission notice are absent. The upstream license requires that notice in copies or substantial portions: https://github.com/ashima/webgl-noise/blob/master/LICENSE. Include the required notice in a form that accompanies both the skill and generated copies.
|
|
||
| ## Requirements | ||
|
|
||
| - Node 18+ and the HyperFrames CLI via `npx` (fetched from npm; `@latest` is |
There was a problem hiding this comment.
important follow-up: The CLI invoked here currently resolves to HyperFrames 0.8.16, whose published package metadata requires Node >=22. A Node 18 user satisfies this stated requirement but runs an unsupported configuration; raise the requirement to Node 22+ when pinning the CLI.
| function cleanCount(v, label) { | ||
| const s = v.trim().replace(/,/g, ""); | ||
| if (!/^\d{1,10}$/.test(s)) die("--" + label + ' must be a non-negative integer (got "' + v + '")'); | ||
| return String(Number(s)); |
There was a problem hiding this comment.
important follow-up: Removing every comma before validation silently changes malformed values: 1,,2 becomes 12, and 12,34 becomes 1234. Validate either plain digits or correctly grouped thousands before stripping separators so scraped/entered profile counts cannot be corrupted.
| console.log("render: npx hyperframes@latest render " + outReal + | ||
| " -q high -o " + join(outReal, "renders", "license.mp4")); |
There was a problem hiding this comment.
important follow-up: The copyable render command concatenates paths without shell quoting. Spaces make it unusable, while shell metacharacters can execute unintended commands if an agent copies the hint. Emit safely shell-escaped arguments or avoid presenting a directly executable command string.
## What Adds `skills/duo/`, a community skill authored by @jmoran-heygen and submitted here on his behalf. It composites any two HTML screens into a locked 1448×1086 @ 30fps photo plate of hands holding an open foldable phone, for side-by-side comparison and meme videos (TikTok vs Reels, two chats answering the same prompt, before/after, two apps racing). The plate, measured screen geometry, hinge divider and single camera push are fixed; the agent only authors what plays inside the two `.content` slots (left 495×849, right 498×849) and adds tweens to the one shared GSAP timeline. Contents: - `SKILL.md` — when/when-not to use, requirements, complete network/side-effect list, flow, verification, rules, provenance. - `scripts/build.mjs` — dependency-free Node 18+ scaffolder (writes only inside `--out`, refuses to write through symlinks, validates `--duration` and `--icons`). - `template/index.template.html` — the locked rig with two empty slots and the timeline hook. - `assets/plate.png` (398 KB), `assets/plate-preview.jpg`, `assets/screen-spec.json`, `assets/icons-tiktok.svg`, `assets/icons-instagram.svg` (plain-text `<symbol>` sheets). - `references/screen-geometry.md`, `references/feed-recipes.md`, `references/sourcing-real-ui.md`. - README "Available skills" row (alphabetical, first). Files excluded from the author's zip and why: - `__MACOSX/`, `._*`, `.DS_Store` — macOS archive metadata, not skill content. - `TEST-scaffold-snapshot.png` — a generated test artifact; CONTRIBUTING forbids generated output. Edits to the author's content: none. The `>` folded-block `description` in the frontmatter is the same form used by `x-posting-license` (#5) and passes the validator as-is. ## Why it belongs here It is a single fixed-plate template for one specific meme format. Same shape as `x-posting-license` (#5): a locked composition where the agent fills content and renders. Too narrow for the curated set, useful to share. ## Trust boundary - Commands: `node scripts/build.mjs --out <dir> [--id] [--duration] [--icons tiktok,instagram]`, then `npx hyperframes@latest check|snapshot|render` on the output dir. - File access: `build.mjs` reads only the skill's own `template/` and `assets/`, writes only inside `--out` (creates `assets/` and `renders/` subdirs), refuses symlinked targets. - Network: `registry.npmjs.org` (HyperFrames CLI via `npx`); `cdn.jsdelivr.net` (GSAP pinned `3.14.2` loaded by the template at preview/render time). `build.mjs` itself makes no network calls. `references/sourcing-real-ui.md` documents how an agent may pull real app UI/posts from TikTok/Instagram if the user's request calls for real content, with a rights caveat; that is user-directed, not something the scaffolder does. - Credentials: None. Paid operations: None. Telemetry: None. - Externally visible side effects: None (renders are local; publishing is up to the user). - Dependency pinning: GSAP is pinned. `npx hyperframes@latest` is mutable; the SKILL.md advises pinning a version for byte-identical re-renders. Reviewers may prefer a pinned example in the docs. ## Verification - `bash .github/scripts/validate-skills.sh` → `Validated 4 skill(s).` (exit 0). - Scaffolder normal path: `node skills/duo/scripts/build.mjs --out /tmp/duo-test --duration 10 --icons tiktok,instagram` → exit 0; produced `index.html`, `hyperframes.json` (`1448x1086 @ 30fps`), `assets/plate.png`, `assets/screen-spec.json`, `assets/icons-tiktok.svg`, `assets/icons-instagram.svg`. - Scaffolder failure paths: `--icons snapchat` → `unknown icon sheet "snapchat"` exit 1, nothing written; no `--out` → usage, exit 1. - `npx hyperframes@latest check /tmp/duo-test` (Node 22, Linux, software GPU) → Lint 0/0, Runtime 0/0, Layout 0 issues across 9 samples, Motion 0/0, "Check passed". No render performed. - Scanned the skill for symlinks (none), secrets/tokens/keys (none), and URLs: only `w3.org` SVG namespace, the jsDelivr GSAP URL, the X status / `pbs.twimg.com` provenance links in `screen-spec.json`, and the `tiktok.com` example in the sourcing reference. - TruffleHog runs in CI; not available locally. ## Licensing note for reviewers Two items need code-owner judgment under CONTRIBUTING's "right to redistribute every dependency and asset" requirement: 1. `assets/plate.png` is derived from a photo posted publicly on X by @A_Kapustin (status `2097769962936868872`), with both phone screens blanked to neutral. The SKILL.md and `screen-spec.json` document this provenance and tell users to credit the source where appropriate. No explicit license from the photographer is on file. 2. `assets/icons-tiktok.svg` and `assets/icons-instagram.svg` reproduce TikTok's and Instagram's own UI glyphs, extracted from their public mobile web DOM, so mock screens look like the real apps. They are trademarks of their owners and are included for parody/commentary use only, as the SKILL.md states. If either is not acceptable for redistribution in this repo, the skill can be adapted (for example, ship without the icon sheets, or require the user to supply their own plate). ## Checklist - [x] The skill is self-contained and its directory name matches `SKILL.md`. - [x] New or renamed skills are linked in the README's **Available skills** table. - [x] I read every instruction and script in the submitted skill. - [x] I disclosed all dependencies, network access, credentials, costs, and side effects. - [x] No secrets, private data, private endpoints, generated output, or opaque executables are included. - [x] Destructive, paid, or externally visible actions require explicit user confirmation (render is user-triggered; no destructive or paid actions). - [x] Dependencies are minimal, pinned where practical, and license-compatible (GSAP pinned; `hyperframes@latest` mutable, pinning advised; asset licensing flagged above). - [x] I tested the normal path and a failure or cancellation path in a fresh agent session (scaffolder normal + two failure paths, plus `hyperframes check`; no end-to-end render). - [x] `bash .github/scripts/validate-skills.sh` passes locally. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Use case
Turns any X profile into a 10.3s animated "POSTING LICENSE" ID-card video — a parody driver's license in X's visual language. The composition is a finished, motion-locked template (overlapped keyframes end to end, verified zero dead frames), so the agent's job is minimal: gather five profile fields + the avatar, run
scripts/build.mjs, render.What the agent runs
scripts/build.mjs— token-fillstemplate/index.template.htmlinto a render-ready HyperFrames project (plain Node, no dependencies)npx hyperframes render— standard HF renderingNetwork destinations
x.com/<handle>— the profile page the agent reads (name, handle, joined date, follower/following counts, bio)pbs.twimg.com— the profile avatar imageapi.fxtwitter.com/<handle>— post count (not exposed on logged-out profile pages); free, no credentialsNo credentials, no paid operations, no telemetry. Documented in
references/profile-data.md.Testing
Built and rendered six licenses end to end (five HeyGen team profiles + one 0-post/no-bio edge case) — counters, diacritics in names (
Miguel Ángel), thousands separators (12,486), PNG avatars, and missing-bio fallback all exercised. Repo validator passes (Validated 3 skill(s).).Residual risk
The card reproduces X's logo and brand look as parody/fan content; contributors using the skill publish at their own judgment. Profile data used is public.
🤖 Generated with Claude Code