nexu-io/open-design · error · Error

invalid brand id: ${input.brandId}

Error message

invalid brand id: ${input.brandId}

What it means

Thrown by syncBrandFilesToProject when resolveBrandFile(brandsRoot, input.brandId, []) returns null. This is a path-safety/slug guard, NOT a not-found check: the brand id failed validation (contains '..', slashes, or characters outside the allowed set) and was refused before directory lookup. Security-relevant: it blocks path traversal.

Source

Thrown at apps/daemon/src/brands/index.ts:2107

    return;
  }
  const source = path.join(projectDir, dirName);
  if (!isDirectory(source)) return;
  const target = resolveBrandFile(brandsRoot, brandId, [dirName]);
  if (!target) return;
  copyDirectorySync(source, target);
}

async function syncBrandFilesToProject(input: {
  brandsRoot: string;
  projectsRoot: string;
  brandId: string;
  projectId: string;
  brand: Brand;
  metadata: ProjectMetadata;
}): Promise<void> {
  const brandRoot = resolveBrandFile(input.brandsRoot, input.brandId, []);
  if (!brandRoot) throw new Error(`invalid brand id: ${input.brandId}`);
  const write = async (name: string, body: string | Buffer) => {
    await writeProjectFile(input.projectsRoot, input.projectId, name, body, { overwrite: true }, input.metadata);
  };
  await write('brand.json', JSON.stringify(input.brand, null, 2));
  await write('DESIGN.md', brandToDesignMd(input.brand));
  await writeOptionalFileToProject(input.projectsRoot, input.projectId, input.metadata, brandRoot, 'guide.md');
  await copyDirectoryToProject(input.projectsRoot, input.projectId, input.metadata, brandSystemDir(input.brandsRoot, input.brandId), 'system');
  await copyOptionalDirectoryToProject(input.projectsRoot, input.projectId, input.metadata, path.join(brandRoot, 'logos'), 'logos');
  await copyOptionalDirectoryToProject(input.projectsRoot, input.projectId, input.metadata, path.join(brandRoot, 'fonts'), 'fonts');
  await copyOptionalDirectoryToProject(input.projectsRoot, input.projectId, input.metadata, path.join(brandRoot, 'imagery'), 'imagery');
  await copyOptionalDirectoryToProject(input.projectsRoot, input.projectId, input.metadata, path.join(brandRoot, 'prefetch'), 'prefetch');
  await copyOptionalDirectoryToProject(input.projectsRoot, input.projectId, input.metadata, path.join(brandRoot, 'context'), 'context');
}

async function writeOptionalFileToProject(
  projectsRoot: string,
  projectId: string,
  metadata: ProjectMetadata,

View on GitHub (pinned to 5be4028344)

Solutions

  1. Use only safe slug brand ids produced by the brand-creation flow.
  2. Reject any id containing '/', '\', or '..' before calling syncBrandFilesToProject.
  3. If accepting user input, slugify and validate against a strict character set upstream.
Defensive patterns

Strategy: validation

Validate before calling

function isSafeBrandId(id: string): boolean {
  // Mirror resolveBrandFile's contract: no path separators, no parent traversal.
  return typeof id === 'string' && id.length > 0 && !/[\\/]/.test(id) && !/(^|\/|\\)\.\.?(\/|\\|$)/.test(id);
}
if (!isSafeBrandId(input.brandId)) {
  throw new Error(`Refusing to sync: brand id '${input.brandId}' is not a safe slug.`);
}

Type guard

function isSafeBrandId(id: unknown): id is string {
  return typeof id === 'string'
    && id.length > 0
    && !/[\\/]/.test(id)
    && id !== '..'
    && !id.includes('../');
}

Try / catch

try {
  await syncBrandFilesToProject(input);
} catch (err) {
  if (err instanceof Error && err.message.startsWith('invalid brand id:')) {
    return badRequest('Brand id must be a safe slug with no path characters.');
  }
  throw err;
}

Prevention

When it happens

Trigger: Passing a brandId containing path separators ('../x', 'a/b'), parent traversal ('..'), or characters the resolver rejects; constructing an id from untrusted URL path input without sanitization.

Common situations: User-supplied or URL-derived brand id reaching the sync path unsanitized; programmatic callers concatenating path segments into the id; testing with literal traversal strings.

Related errors


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/fc1a80e42226e387. Report an issue: GitHub.