{"record":{"id":"b2e72fe23d88277a","repo":"Hmbown/CodeWhale","slug":"out-path-is-required-write-the-private-key-outside-any","errorCode":null,"errorMessage":"--out <path> is required (write the private key OUTSIDE any repository)","messagePattern":"--out <path> is required \\(write the private key OUTSIDE any repository\\)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/scripts/facts-publish.mjs","lineNumber":513,"sourceCode":"  return JSON.parse(new TextDecoder(\"utf-8\", { fatal: true }).decode(readBoundedFile(path)));\n}\n\nfunction nowIso() {\n  return new Date().toISOString().replace(/\\.\\d{3}Z$/, \"Z\");\n}\n\nasync function main(argv) {\n  const { positional, flags } = parseArgs(argv);\n  const cmd = positional[0];\n  if (!cmd || flags.help) {\n    console.log(readFileSync(fileURLToPath(import.meta.url), \"utf8\").split(\"\\n\").slice(1, 26).join(\"\\n\"));\n    return 0;\n  }\n  if (cmd === \"keygen\") {\n    const keyId = String(flags[\"key-id\"] ?? \"\");\n    if (!KEY_ID_RE.test(keyId)) throw new Error(\"--key-id must match cwf-[a-z0-9-]{1,32}\");\n    const out = flags.out ? resolve(String(flags.out)) : null;\n    if (!out) throw new Error(\"--out <path> is required (write the private key OUTSIDE any repository)\");\n    refuseUnderCi();\n    const { privateKey, publicKey } = generateKeyPairSync(\"ed25519\");\n    mkdirSync(dirname(out), { recursive: true, mode: 0o700 });\n    const fd = openSync(out, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0), 0o600);\n    try { writeFileSync(fd, privateKey.export({ type: \"pkcs8\", format: \"pem\" })); }\n    finally { closeSync(fd); }\n    const raw = rawPublicKeyFromKeyObject(publicKey);\n    console.log(JSON.stringify({\n      key_id: keyId,\n      algorithm: \"ed25519\",\n      public_key_b64: raw.toString(\"base64\"),\n      public_key_bytes: [...raw],\n      private_key_file: out,\n      note: \"Private key written with mode 0600. Move it into custody (password manager); never commit it.\",\n    }, null, 2));\n    return 0;\n  }\n  if (cmd === \"sign\") {","sourceCodeStart":495,"sourceCodeEnd":531,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/web/scripts/facts-publish.mjs#L495-L531","documentation":"The keygen subcommand requires an explicit --out path where the generated ed25519 private key (PKCS#8 PEM) will be written with 0600 permissions. If the flag is absent, out resolves to null and this error is thrown, deliberately instructing you to write the key outside any repository.","triggerScenarios":"Running `facts-publish.mjs keygen --key-id cwf-x` without --out, or passing --out with an empty string value so flags.out is falsy.","commonSituations":"Following docs that show keygen without the flag; shell quoting problems making --out empty; forgetting that keys must live outside the repo to avoid committing secrets.","solutions":["Add --out /secure/path/outside/repo/cwf-x.pem (a directory outside any git repository)","Ensure the parent directory exists or is creatable (the script mkdirs it with mode 0700)","Note the file is opened O_EXCL|O_NOFOLLOW: choose a path where no file already exists"],"exampleFix":"// before\nnode facts-publish.mjs keygen --key-id cwf-prod\n// after\nnode facts-publish.mjs keygen --key-id cwf-prod --out ~/.secrets/cwf-prod.pem","handlingStrategy":"validation","validationCode":"if (!outPath) throw new Error('keygen requires --out pointing outside any repository');\nif (existsSync(outPath)) throw new Error('output file already exists (O_EXCL will fail)');","typeGuard":null,"tryCatchPattern":"try {\n  await run(['keygen', '--key-id', keyId, '--out', outPath]);\n} catch (e) {\n  if (e.message.includes('--out <path> is required')) console.error('Pass a path outside the repo, e.g. ~/.secrets/<key-id>.pem');\n  throw e;\n}","preventionTips":["Store private keys in ~/.secrets or a secrets manager, never in the repo","Ensure the target file does not already exist (script opens with O_EXCL)","Run keygen locally, not under CI (refuseUnderCi blocks it anyway)"],"tags":["cli","missing-argument","secrets"],"backgroundTag":"missing-required-flag","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}