{"record":{"id":"003707f992a688f8","repo":"Hmbown/CodeWhale","slug":"refusing-to-run-with-a-secret-under-ci-marker-is-set-publish","errorCode":null,"errorMessage":"refusing to run with a secret under CI (${marker} is set); publish from the founder's machine","messagePattern":"refusing to run with a secret under CI \\((.+?) is set\\); publish from the founder's machine","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/scripts/facts-publish.mjs","lineNumber":416,"sourceCode":"function loadTrustedKeysFromRepo() {\n  const keys = parseTsKeys(readBoundedFile(resolve(WEB_ROOT, \"lib/cloud-facts/keys.ts\"), 64 * 1024).toString(\"utf8\"));\n  return new Map(keys.map((key) => [key.keyId, key]));\n}\n\nexport function activePublishingKey(envelope, keys, now = Date.now()) {\n  const key = validateTrustedKeys(keys).find((key) => key.keyId === envelope.key_id && key.status === \"active\");\n  if (!key) throw new Error(\"primary signing key is not pinned and active; refusing publication\");\n  const check = verifyEnvelope(envelope, key.publicKey);\n  if (!check.ok) throw new Error(`envelope does not verify: ${check.errors.join(\"; \")}`);\n  if (!Number.isFinite(now) || utcTime(check.payload.published_at) > now + 300_000 ||\n      (check.payload.not_after != null && utcTime(check.payload.not_after) <= now)) throw new Error(\"publication timestamp is future or expired\");\n  return { key, check };\n}\n\nfunction refuseUnderCi() {\n  for (const marker of CI_MARKERS) {\n    if (process.env[marker] && !/^(0|false|no|off)$/i.test(process.env[marker])) {\n      throw new Error(`refusing to run with a secret under CI (${marker} is set); publish from the founder's machine`);\n    }\n  }\n}\n\nfunction sqlLiteral(value) {\n  if (value === null || value === undefined) return \"null\";\n  return `'${String(value).replace(/'/g, \"''\")}'`;\n}\n\nexport function emitSql(envelope, { publishedBy = \"\", publicKeyB64, notes = \"\" }) {\n  if (!publicKeyB64) throw new Error(\"public key required to emit the facts_key row\");\n  const check = verifyEnvelope(envelope, publicKeyB64);\n  if (!check.ok) throw new Error(`envelope does not verify: ${check.errors.join(\"; \")}`);\n  const payloadJson = Buffer.from(envelope.payload_b64, \"base64\").toString(\"utf8\");\n  return [\n    \"begin;\",\n    `insert into public.facts_key (key_id, scope, algorithm, public_key, status)`,\n    `  values (${sqlLiteral(envelope.key_id)}, 'global', 'ed25519', ${sqlLiteral(publicKeyB64)}, 'active')`,","sourceCodeStart":398,"sourceCodeEnd":434,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/web/scripts/facts-publish.mjs#L398-L434","documentation":"refuseUnderCi scans CI_MARKERS environment variables and throws if any is set to a truthy value (0/false/no/off are allowed as opt-outs). The publish script must only run from the founder's machine where the signing secret lives, never inside CI where secrets and signing keys should not exist.","triggerScenarios":"Running facts-publish.mjs (or any code path that calls postgrest, which calls refuseUnderCi) while CI env vars like CI, GITHUB_ACTIONS, or TEAMCITY_VERSION are set to non-falsy values, e.g. in a GitHub Actions workflow, container with CI=true inherited, or a local shell that exported CI=1.","commonSituations":"A developer tried to automate publishing in CI; a local environment had CI=true exported globally from previous tooling; running inside a devcontainer or test runner that sets CI.","solutions":["Run the publish script on the founder's machine with the CI markers unset (check `env | grep -i ci`)","Explicitly unset the marker: `env -u CI -u GITHUB_ACTIONS node web/scripts/facts-publish.mjs ...`, or set it to an allowed falsy value like CI=0","Do not bypass via CI=0 unless you truly are on the trusted local machine — the guard exists to keep the signing secret out of CI"],"exampleFix":"// before (CI)\nnode web/scripts/facts-publish.mjs\n// after (local, markers cleared)\nenv -u CI -u GITHUB_ACTIONS node web/scripts/facts-publish.mjs","handlingStrategy":"try-catch","validationCode":"const ciMarkers = [\"CI\", \"GITHUB_ACTIONS\", \"TEAMCITY_VERSION\", \"BUILD_NUMBER\"];\nconst active = ciMarkers.filter((m) => process.env[m] && !/^(0|false|no|off)$/i.test(process.env[m]));\nif (active.length) throw new Error(`publishing blocked under CI markers: ${active.join(\", \")} — run from the founder's machine`);","typeGuard":null,"tryCatchPattern":"try {\n  await publishFacts(envelope);\n} catch (err) {\n  if (err.message.includes(\"refusing to run with a secret under CI\")) {\n    console.error(\"Unset the CI marker named in the message and run the publish from the trusted local machine\");\n    process.exit(1);\n  }\n  throw err;\n}","preventionTips":["Run publication only from the trusted local machine; never automate it in CI","In shared shells/devcontainers, check `env | grep -i '^CI=' ` and unset inherited markers before publishing","Keep the signing secret off CI runners entirely so CI publishing is impossible by construction"],"tags":["security","ci","environment"],"backgroundTag":"missing-env-var","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"}