{"record":{"id":"a9ff901c17f5cd63","repo":"affaan-m/ECC","slug":"spec-file-must-be-a-portable-filename-without-path","errorCode":null,"errorMessage":"spec.file must be a portable filename without path components or control characters","messagePattern":"spec\\.file must be a portable filename without path components or control characters","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"skills/master-agreement-generator/scripts/build-agreement.js","lineNumber":92,"sourceCode":"    });\n    return `| ${cells.join(' | ')} |`;\n  }).join('\\n');\n}\n\nfunction buildValues(spec, now) {\n  if (!spec || typeof spec !== 'object') {\n    throw new Error('spec must be an object');\n  }\n  for (const key of ['file', 'short', 'role']) {\n    if (typeof spec[key] !== 'string' || spec[key].trim() === '') {\n      throw new Error(`spec.${key} is required`);\n    }\n  }\n  // Reject path syntax on every host, including Windows paths supplied on POSIX.\n  if (/[<>:\"/\\\\|?*\\p{Cc}]/u.test(spec.file) ||\n      /[. ]$/.test(spec.file) ||\n      /^(con|prn|aux|nul|com[1-9¹²³]|lpt[1-9¹²³])(?:\\.|$)/i.test(spec.file)) {\n    throw new Error('spec.file must be a portable filename without path components or control characters');\n  }\n  const clauses = ROLE_CLAUSES[spec.role];\n  if (!clauses) {\n    throw new Error(`unknown role \"${spec.role}\"; expected one of ${Object.keys(ROLE_CLAUSES).join(', ')}`);\n  }\n  const cp = spec.short;\n  const fill = text => text.split('{cp}').join(cp);\n  const supplement = typeof spec.supplement === 'string' && spec.supplement.trim() ? `${spec.supplement.trim()}; ` : '';\n\n  return {\n    FEE_TITLE: clauses.title,\n    CP_SHORT: cp,\n    DATE: spec.date || defaultDate(now),\n    CP_LEGAL: spec.legal || BLANK,\n    CP_JURIS: spec.juris || BLANK,\n    CP_ADDR: spec.addr || BLANK,\n    ROLE_CLAUSE: fill(clauses.role),\n    FEE_CLAUSE: fill(clauses.fee),","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/master-agreement-generator/scripts/build-agreement.js#L74-L110","documentation":"The library enforces that spec.file is a portable, safe filename: no path separators (/ or \\), no Windows-reserved characters (<>:\"|?*), no control characters, no trailing dot/space, and not a Windows reserved device name (con, prn, aux, nul, com1-9, lpt1-9). It throws when the value would be unsafe or non-portable as a generated filename across hosts.","triggerScenarios":"spec.file = 'contracts/NDA' (path component), spec.file = 'NDA?' or 'NDA: draft' (reserved chars), spec.file = 'NDA.' (trailing dot), spec.file = 'COM1' (reserved device name), or a file name containing a newline/tab.","commonSituations":"Users pasting a full path or Windows path into a spec field intended only for a bare filename, filenames with characters legal on macOS but not Windows, or programmatically built names with trailing whitespace/period.","solutions":["Set spec.file to a bare filename with no directory components or reserved characters.","Strip any directory portion yourself: path.basename(file) — but only if the base name is itself safe.","Sanitize: replace reserved characters and control chars, trim trailing dots/spaces, then re-validate.","Avoid Windows reserved device names entirely, even with extensions like 'con.md'."],"exampleFix":"// before\nconst spec = { file: 'contracts/NDA MASTER', short: 'NDA', role: 'consultant' };\n// after\nconst spec = { file: 'NDA MASTER', short: 'NDA', role: 'consultant' };","handlingStrategy":"validation","validationCode":"const FILENAME_RE = /^[^<>:\"/\\\\|?*\\x00-\\x1f]+$/;\nfunction isPortableFilename(file) {\n  return typeof file === 'string' && file.trim() !== '' &&\n    FILENAME_RE.test(file) && !/[. ]$/.test(file) &&\n    !/^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\\.|$)/i.test(file);\n}\nif (!isPortableFilename(spec.file)) throw new Error(`unsafe filename: ${spec.file}`);","typeGuard":"function isSafeFilename(v) {\n  return typeof v === 'string' && v.length > 0 &&\n    !/[<>:\"/\\\\|?*\\p{Cc}]/u.test(v) && !/[. ]$/.test(v);\n}","tryCatchPattern":"try {\n  outputPaths(outDir, spec.file);\n} catch (e) {\n  if (e.message.startsWith('spec.file must be a portable filename')) {\n    console.error(`Fix spec.file: ${spec.file} is not a portable filename`);\n  } else throw e;\n}","preventionTips":["Treat spec.file as a bare filename only; never let users pass paths there.","Sanitize user-supplied names: strip path components, reserved chars, control chars, trailing dots/spaces.","Test specs against both POSIX and Windows rules since the library enforces the stricter cross-platform set."],"tags":["validation","filename","cross-platform"],"backgroundTag":"invalid-argument-format","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}