heygen-com/hyperframes · error · Error

--file must be an HTML file inside the project

Error message

--file must be an HTML file inside the project

What it means

Thrown by resolveMutationFile when the resolved --file path either falls outside the project directory (path traversal) or does not end with .html. This is a security boundary (isPathInside) combined with an extension guard ensuring only HTML composition files inside the project are mutated.

Source

Thrown at packages/cli/src/commands/media-treatment.ts:577

    args.apply,
    args.analyze,
  ].some(Boolean);
  if (hasMutationOption) throw new Error("--capabilities cannot be combined with mutation options");
  if (args.all === true && capability)
    throw new Error("Use either --all or --capability, not both");

  let capabilities: unknown = getMediaTreatmentCapabilityOverview();
  if (args.all === true) capabilities = getHfColorGradingCapabilities();
  else if (capability) capabilities = getMediaTreatmentCapabilityDetail(capability);
  console.log(JSON.stringify(withMeta({ ok: true, capabilities }), null, 2));
}

function resolveMutationFile(args: MediaTreatmentCommandArgs) {
  const project = resolveProject(readOptionalString(args.project));
  const fileArg = readOptionalString(args.file) ?? "index.html";
  const filePath = resolve(project.dir, fileArg);
  if (!isPathInside(filePath, project.dir) || !filePath.toLowerCase().endsWith(".html")) {
    throw new Error("--file must be an HTML file inside the project");
  }
  if (!existsSync(filePath)) throw new Error(`Composition file not found: ${fileArg}`);
  return { project, filePath };
}

function analyzeTarget(args: MediaTreatmentCommandArgs) {
  const { project, filePath } = resolveMutationFile(args);
  const selector = readOptionalString(args.selector);
  if (!selector) throw new Error("--selector is required");
  if (
    readOptionalString(args.grading) ||
    args.clear === true ||
    args.apply === true ||
    args["dry-run"] === true
  ) {
    throw new Error("--analyze cannot be combined with mutation options");
  }
  const selectorIndex = parseSelectorIndex(readOptionalString(args["selector-index"]));

View on GitHub (pinned to c2996c8626)

Solutions

  1. Ensure the file is inside the project directory and ends with .html
  2. Pass --project <dir> to set the correct project root if the file is legitimately in a sibling project
  3. Move or copy the composition into the project directory
  4. Check the extension is exactly .html (not .htm or .xhtml)

Example fix

// before
hyperframes media-treatment --file ../shared/scene.html --selector '#hero' --apply
// after — file inside the project
hyperframes media-treatment --file compositions/scene.html --selector '#hero' --apply
Defensive patterns

Strategy: validation

Validate before calling

import { resolve } from 'node:path';
import { isPathInside } from '@hyperframes/core';

function assertFileInsideProject(projectDir: string, fileArg: string): void {
  const filePath = resolve(projectDir, fileArg);
  if (!isPathInside(filePath, projectDir) || !filePath.toLowerCase().endsWith('.html')) {
    throw new Error('--file must be an HTML file inside the project');
  }
}

Prevention

When it happens

Trigger: Passing `--file ../outside.html` (escapes project dir), `--file /etc/passwd` (absolute outside project), or `--file scene.txt` (wrong extension). Also triggered by symlinks resolving outside the project root.

Common situations: Monorepo users pointing at a shared composition in a parent directory. Typos like --file index.htm. Agents guessing paths outside the resolved project root.

Related errors


AI-assisted analysis of heygen-com/hyperframes@c2996c8626 (2026-08-12). Data as JSON: /api/errors/bd13eea7f7e4c738. Report an issue: GitHub.