{"record":{"id":"cd0691bc05c58d34","repo":"nexu-io/open-design","slug":"invalid-brand-id-id","errorCode":null,"errorMessage":"invalid brand id: ${id}","messagePattern":"invalid brand id: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/daemon/src/brands/store.ts","lineNumber":83,"sourceCode":" * Resolve `relParts` under a brand dir with a traversal guard. Returns the\n * absolute path, or null when the id is invalid or the resolved path escapes\n * the brand dir.\n */\nexport function resolveBrandFile(\n  brandsRoot: string,\n  id: string,\n  relParts: string[],\n): string | null {\n  if (!isValidBrandId(id)) return null;\n  const base = path.resolve(brandDir(brandsRoot, id));\n  const target = path.resolve(base, ...relParts);\n  if (target !== base && !target.startsWith(`${base}${path.sep}`)) return null;\n  return target;\n}\n\n/** Create the brand dir and write its initial meta.json. */\nexport function createBrandDir(brandsRoot: string, id: string, meta: BrandMeta): void {\n  if (!isValidBrandId(id)) throw new Error(`invalid brand id: ${id}`);\n  fs.mkdirSync(brandDir(brandsRoot, id), { recursive: true });\n  writeMeta(brandsRoot, id, meta);\n}\n\nfunction readJson<T>(file: string): T | null {\n  try {\n    return JSON.parse(fs.readFileSync(file, 'utf8')) as T;\n  } catch {\n    return null;\n  }\n}\n\nfunction writeJson(file: string, value: unknown): void {\n  fs.mkdirSync(path.dirname(file), { recursive: true });\n  fs.writeFileSync(file, JSON.stringify(value, null, 2), 'utf8');\n}\n\nexport function readMeta(brandsRoot: string, id: string): BrandMeta | null {","sourceCodeStart":65,"sourceCodeEnd":101,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/brands/store.ts#L65-L101","documentation":"Thrown by createBrandDir when the id fails isValidBrandId. This is the very first write of a brand's lifecycle (mkdir + initial meta.json), so an invalid id here means the brand dir was never created. The id must match /^[a-z0-9][a-z0-9-]*$/ and contain no '..'.","triggerScenarios":"createBrandDir is invoked with a caller-supplied id that contains uppercase, underscores, slashes, dots, spaces, is empty, or contains '..'. Typically the caller should be using newBrandId(sourceUrl) to mint the id instead.","commonSituations":"Code path that takes a brand id from request body or CLI arg and passes it straight to createBrandDir; tests that hard-code ids like 'Test_Brand'; a refactor that replaced newBrandId with a hand-built slug; race where the id is computed from untrusted input.","solutions":["Always mint ids via newBrandId(sourceUrl) (host slug + 6-char random suffix); never accept caller-supplied ids for creation.","At the API boundary, run isValidBrandId(id) and 400 the request on failure.","If you need a deterministic id for tests, use a lowercase-hyphen slug that matches the regex (e.g. 'test-brand-1').","Add a unit test over createBrandDir with a table of invalid ids to lock the contract."],"exampleFix":"// before\ncreateBrandDir(brandsRoot, req.body.id, meta);\n// after\nimport { isValidBrandId, newBrandId } from './store.js';\nconst id = req.body.id ? validateAndNormalize(req.body.id) : newBrandId(meta.sourceUrl);\nif (!isValidBrandId(id)) return res.status(400).json({ error: 'invalid brand id' });\ncreateBrandDir(brandsRoot, id, meta);","handlingStrategy":"validation","validationCode":"import { isValidBrandId, newBrandId } from './store.js';\nconst id = newBrandId(sourceUrl);\nif (!isValidBrandId(id)) throw new Error('internal: generated invalid id');","typeGuard":"import { isValidBrandId } from './store.js';\nconst isBrandId = (id: unknown): id is string =>\n  typeof id === 'string' && isValidBrandId(id);","tryCatchPattern":null,"preventionTips":["Always mint ids with newBrandId; never accept caller-supplied ids for creation.","Validate ids at the API boundary.","Lock the contract with a unit test over a table of invalid ids."],"tags":["brand","id-validation","filesystem","validation"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}