{"record":{"id":"1171e610766dbeb3","repo":"bmad-code-org/BMAD-METHOD","slug":"set-entry-proto-prototype-and-c","errorCode":null,"errorMessage":"--set \"${entry}\": '__proto__', 'prototype', and 'constructor' are reserved and cannot be used as a module or key name.","messagePattern":"--set \"(.+?)\": '__proto__', 'prototype', and 'constructor' are reserved and cannot be used as a module or key name\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"tools/installer/set-overrides.js","lineNumber":58,"sourceCode":"  if (eq === -1) {\n    throw new Error(`--set \"${entry}\": missing '='. Expected <module>.<key>=<value>`);\n  }\n  const lhs = entry.slice(0, eq);\n  // Note: only the LHS is trimmed. Values may legitimately contain leading\n  // or trailing whitespace (paths with spaces, quoted strings); module / key\n  // names cannot, so it's safe to be strict on the left.\n  const value = entry.slice(eq + 1);\n  const dot = lhs.indexOf('.');\n  if (dot === -1) {\n    throw new Error(`--set \"${entry}\": missing '.'. Expected <module>.<key>=<value>`);\n  }\n  const moduleCode = lhs.slice(0, dot).trim();\n  const key = lhs.slice(dot + 1).trim();\n  if (!moduleCode || !key) {\n    throw new Error(`--set \"${entry}\": empty module or key. Expected <module>.<key>=<value>`);\n  }\n  if (PROTOTYPE_POLLUTING_NAMES.has(moduleCode) || PROTOTYPE_POLLUTING_NAMES.has(key)) {\n    throw new Error(\n      `--set \"${entry}\": '__proto__', 'prototype', and 'constructor' are reserved and cannot be used as a module or key name.`,\n    );\n  }\n  return { module: moduleCode, key, value };\n}\n\n/**\n * Parse repeated `--set` entries into a `{ module: { key: value } }` map.\n * Later entries overwrite earlier ones for the same key. Both the outer\n * map and the per-module inner maps are `Object.create(null)` so callers\n * that bypass `parseSetEntry`'s name check still can't pollute prototypes.\n *\n * @param {string[]} entries\n * @returns {Object<string, Object<string, string>>}\n */\nfunction parseSetEntries(entries) {\n  const overrides = Object.create(null);\n  if (!Array.isArray(entries)) return overrides;","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/bmad-code-org/BMAD-METHOD/blob/b70486b9bdcb0a404d329e2a763b57964e7f1360/tools/installer/set-overrides.js#L40-L76","documentation":"Thrown by parseSetEntry when the module code or key is one of __proto__, prototype, or constructor (the PROTOTYPE_POLLUTING_NAMES set). parseSetEntries assigns overrides into plain-object maps keyed by user input; these names would mutate Object.prototype and cascade into every object lookup. Rejected at parse time as defense-in-depth — the maps are also Object.create(null).","triggerScenarios":"Passing --set __proto__.x=1, --set module.constructor=y, or --set prototype.key=val. Any override whose module or key segment equals one of the reserved names.","commonSituations":"Adversarial/fuzzed CLI input; a generated script feeding arbitrary strings as keys; misunderstanding the format and using a JS built-in as a name; attempting prototype pollution deliberately (this is the guard working).","solutions":["Choose a module or key name that isn't __proto__, prototype, or constructor.","If --set args are generated from external data, sanitize/reserved-filter upstream before passing them to the CLI."],"exampleFix":"# before\n#   --set module.constructor=y\n#\n# after\n#   --set module.ctor=y","handlingStrategy":"validation","validationCode":"const RESERVED = new Set(['__proto__', 'prototype', 'constructor']);\nfunction isSafeSetEntry(entry) {\n  const eq = entry.indexOf('='); if (eq === -1) return false;\n  const lhs = entry.slice(0, eq);\n  const dot = lhs.indexOf('.'); if (dot === -1) return false;\n  const mod = lhs.slice(0, dot).trim();\n  const key = lhs.slice(dot + 1).trim();\n  return !RESERVED.has(mod) && !RESERVED.has(key);\n}\nconst entries = raw.filter(isSafeSetEntry);","typeGuard":"function isSafeSetEntry(entry) {\n  const RESERVED = new Set(['__proto__', 'prototype', 'constructor']);\n  if (typeof entry !== 'string') return false;\n  const m = entry.match(/^([^.=]+)\\.([^.=]+)=/);\n  return !!m && !RESERVED.has(m[1]) && !RESERVED.has(m[2]);\n}","tryCatchPattern":"try {\n  overrides = parseSetEntries(entries);\n} catch (e) {\n  if (/reserved and cannot be used/.test(e.message)) {\n    // drop the offending entry; it's either hostile input or a naming mistake\n  } else { throw e; }\n}","preventionTips":["Sanitize --set args from external/untrusted sources upstream.","Treat this error as a security signal — investigate where the reserved name came from."],"tags":["cli","set-overrides","security","prototype-pollution"],"backgroundTag":null,"analyzedSha":"b70486b9bdcb0a404d329e2a763b57964e7f1360","analyzedAt":"2026-08-13T01:21:12.247Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}