nexu-io/open-design · error · Error
invalid live artifact id
Error message
invalid live artifact id
What it means
Thrown by validateLiveArtifactStorageId() when an artifactId fails the safe-id regex `/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/` or equals '.'/'..'. Every storage path derives from this id (it becomes a directory name under .live-artifacts/), so an unsafe value could escape the project dir or collide with filesystem metadata. The function is the single gate called by liveArtifactStorePaths(), getLiveArtifact(), and friends.
Source
Thrown at apps/daemon/src/live-artifacts/store.ts:294
export function generateLiveArtifactId(options: GenerateLiveArtifactIdOptions): string {
const randomSuffix = options.randomSuffix ?? randomBytes(LIVE_ARTIFACT_ID_RANDOM_BYTES).toString('hex');
if (!/^[a-f0-9]+$/i.test(randomSuffix) || randomSuffix.length === 0) {
throw new Error('invalid live artifact id random suffix');
}
const suffix = randomSuffix.toLowerCase();
const maxSlugLength = MAX_LIVE_ARTIFACT_STORAGE_ID_LENGTH - LIVE_ARTIFACT_ID_PREFIX.length - suffix.length - 2;
if (maxSlugLength < 1) {
throw new Error('invalid live artifact id random suffix');
}
const slug = truncateSlugAtSegmentBoundary(generateLiveArtifactSlug(options.slug ?? options.title), maxSlugLength);
return validateLiveArtifactStorageId(`${LIVE_ARTIFACT_ID_PREFIX}-${slug}-${suffix}`);
}
export function validateLiveArtifactStorageId(artifactId: string): string {
if (!SAFE_LIVE_ARTIFACT_ID.test(artifactId) || artifactId === '.' || artifactId === '..') {
throw new Error('invalid live artifact id');
}
return artifactId;
}
export function liveArtifactsRootDir(projectsRoot: string, projectId: string): string {
const projectDirPath = path.resolve(projectDir(projectsRoot, projectId));
return resolveInside(projectDirPath, LIVE_ARTIFACTS_DIR_NAME, 'live artifact path escapes project dir');
}
export function liveArtifactStorePaths(
projectsRoot: string,
projectId: string,
artifactId: string,
): LiveArtifactStorePaths {
const safeArtifactId = validateLiveArtifactStorageId(artifactId);
const projectDirPath = path.resolve(projectDir(projectsRoot, projectId));
const rootDir = liveArtifactsRootDir(projectsRoot, projectId);
const artifactDir = resolveInside(rootDir, safeArtifactId, 'live artifact path escapes storage root');View on GitHub (pinned to 5be4028344)
Solutions
- Use ids returned by generateLiveArtifactId() — they always match the safe pattern (la-<slug>-<hexsuffix>).
- If you must accept external ids, validate with the same regex before calling store functions: `/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(id) && id !== '.' && id !== '..'`.
- Rename any externally-corrupted storage directory back to a valid id, or delete it.
- Do not pass paths, URLs, or slugs-with-spaces as artifactId; pass only the bare id.
Example fix
// before
getLiveArtifact({ projectsRoot, projectId, artifactId: 'my artifact/2' });
// after
getLiveArtifact({ projectsRoot, projectId, artifactId: 'la-my-artifact-9f2a1c8e0b7d' }); Defensive patterns
Strategy: validation
Validate before calling
import { validateLiveArtifactStorageId } from './store';
function safeArtifactId(id: string): string {
try {
return validateLiveArtifactStorageId(id);
} catch {
throw new Error(`Refusing unsafe artifact id: ${JSON.stringify(id)}`);
}
}
// or inline at the trust boundary
const SAFE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
function isSafeArtifactId(id: string): boolean {
return SAFE.test(id) && id !== '.' && id !== '..';
} Type guard
function isLiveArtifactStorageId(value: unknown): value is string {
return typeof value === 'string'
&& /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(value)
&& value !== '.'
&& value !== '..';
} Prevention
- Only use ids minted by generateLiveArtifactId().
- Validate externally-supplied ids at the API boundary before they reach the store.
- Never pass paths, URLs, or user slugs as the artifactId.
When it happens
Trigger: Calling getLiveArtifact/update with a hand-crafted id containing '/', spaces, unicode, or exceeding 128 chars; an id of literally '.' or '..'; an id starting with a non-alphanumeric character like '-foo' or '_bar'; passing a URL or full path instead of the bare id.
Common situations: External client constructs an artifactId from user input or a URL slug without sanitizing; filesystem directory was renamed externally to an invalid name and listLiveArtifacts then re-validates each entry; migration tooling invents its own id scheme.
Related errors
- connector refresh source requires connector metadata
- ${field} must be a dot-separated JSON path
- public_github_repository_metric input.url must be a valid UR
- public_github_repository_metric only supports https://api.gi
- public_github_repository_metric only supports /repos/{owner}
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/b96bea8728dc74b4.
Report an issue: GitHub.