heygen-com/hyperframes · critical · Error
Unsafe target "${target}": resolves outside destDir ${destDi
Error message
Unsafe target "${target}": resolves outside destDir ${destDir}. What it means
Thrown by assertSafeTarget as the final backstop when a target passes the absolute/.. /drive-letter checks but still resolves outside destDir after resolve(). The relative() of the resolved path against destDir is computed; if it starts with '..' or is absolute the target escapes and is rejected.
Source
Thrown at packages/cli/src/registry/installer.ts:107
* Reject target paths that would escape `destDir`. Mirrors the pattern check
* in `packages/core/schemas/registry-item.json#files.items.target`, but runs at
* install time so a registry that bypasses schema validation still can't write
* outside the project.
*/
export function assertSafeTarget(destDir: string, target: string): void {
if (isAbsolute(target)) {
throw new Error(`Unsafe target "${target}": absolute paths are not allowed.`);
}
if (/(^|[/\\])\.\.([/\\]|$)/.test(target)) {
throw new Error(`Unsafe target "${target}": path segments may not contain "..".`);
}
if (/^[A-Za-z]:[/\\]/.test(target)) {
throw new Error(`Unsafe target "${target}": Windows drive letters are not allowed.`);
}
const resolved = resolve(destDir, target);
const rel = relative(resolve(destDir), resolved);
if (rel.startsWith("..") || isAbsolute(rel)) {
throw new Error(`Unsafe target "${target}": resolves outside destDir ${destDir}.`);
}
}
function isInstalledRegistryBlockComposition(item: RegistryItem, file: FileTarget): boolean {
return (
item.type === "hyperframes:block" &&
file.type === "hyperframes:composition" &&
file.target.toLowerCase().endsWith(".html")
);
}
function addRegistryItemMarker(source: string, item: RegistryItem): string {
if (/^\s*<!--\s*hyperframes-registry-item:[^>]*-->/i.test(source.slice(0, 512))) {
return source;
}
return `<!-- hyperframes-registry-item: ${item.name} -->\n${source}`;
}View on GitHub (pinned to c2996c8626)
Solutions
- Make the target a simple relative path contained within destDir
- Resolve destDir and ensure it isn't a symlink that escapes its declared location
- Avoid unicode/encoded characters in target paths
- Re-run with a target you can confirm stays under the project root
Example fix
// before (destDir is a symlink escaping its dir)
{ "target": "x.html" } // resolves outside real destDir
// after
// point destDir at the real directory, or use a target under the resolved root Defensive patterns
Strategy: validation
Validate before calling
import { resolve, relative, isAbsolute } from "node:path";
function targetWithin(destDir: string, target: string): boolean {
const rel = relative(resolve(destDir), resolve(destDir, target));
return !rel.startsWith("..") && !isAbsolute(rel);
} Type guard
import { resolve, relative, isAbsolute } from "node:path";
function isTargetContained(destDir: string, target: string): boolean {
const rel = relative(resolve(destDir), resolve(destDir, target));
return !rel.startsWith("..") && !isAbsolute(rel);
} Prevention
- Point destDir at a real (non-symlink-escaping) directory
- Use simple relative targets with no encoded/unicode separators
- Re-check containment after resolving symlinks in destDir
When it happens
Trigger: assertSafeTarget computes resolved=resolve(destDir,target) and rel=relative(resolve(destDir),resolved); if rel starts with '..' or isAbsolute(rel) → throw at installer.ts:107. Catches symlink/encoding edge cases the earlier regexes miss.
Common situations: A destDir that is itself a symlink so resolve() lands elsewhere; a target with encoded/unicode separators that defeated the '..' regex but resolves outside; an unusual platform path normalization; a target that becomes absolute after resolution.
Related errors
- Unsafe target "${target}": absolute paths are not allowed.
- Unsafe target "${target}": path segments may not contain "..
- Unsafe target "${target}": Windows drive letters are not all
- --file must be an HTML file inside the project
- Unknown effect type: ${type}
AI-assisted analysis of heygen-com/hyperframes@c2996c8626 (2026-08-12).
Data as JSON: /api/errors/5416d7db410a0baf.
Report an issue: GitHub.