{"record":{"id":"6d533efdfd4dfcd0","repo":"bmad-code-org/BMAD-METHOD","slug":"unsafe-ref-name-json-stringify-ref","errorCode":null,"errorMessage":"Unsafe ref name: ${JSON.stringify(ref)}","messagePattern":"Unsafe ref name: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"tools/installer/modules/custom-module-manager.js","lineNumber":10,"sourceCode":"const fs = require('../fs-native');\nconst os = require('node:os');\nconst path = require('node:path');\nconst { execSync } = require('node:child_process');\nconst prompts = require('../prompts');\nconst { gitEnv } = require('./git-env');\n\nfunction quoteCustomRef(ref) {\n  if (typeof ref !== 'string' || !/^[\\w.\\-+/]+$/.test(ref)) {\n    throw new Error(`Unsafe ref name: ${JSON.stringify(ref)}`);\n  }\n  return `\"${ref}\"`;\n}\n\nfunction isLocalSourcePath(input) {\n  return (\n    input.startsWith('/') ||\n    input.startsWith('./') ||\n    input.startsWith('../') ||\n    input.startsWith('.\\\\') ||\n    input.startsWith('..\\\\') ||\n    input.startsWith('~') ||\n    path.win32.isAbsolute(input)\n  );\n}\n\n/**\n * Manages custom modules installed from user-provided sources.","sourceCodeStart":1,"sourceCodeEnd":28,"githubUrl":"https://github.com/bmad-code-org/BMAD-METHOD/blob/b70486b9bdcb0a404d329e2a763b57964e7f1360/tools/installer/modules/custom-module-manager.js#L1-L28","documentation":"Thrown by quoteCustomRef() when a Git ref (branch/tag name) fails the whitelist regex ^[\\w.\\-+/]+$. The function exists to safely embed user-supplied or URL-parsed refs into git --branch and git fetch commands, preventing shell injection. Any ref containing spaces, semicolons, pipes, or other shell metacharacters is rejected before reaching execSync.","triggerScenarios":"Calling CustomModuleManager.cloneRepo() with a source URL whose @version suffix or /tree/<ref> path segment contains characters outside [A-Za-z0-9_.\\-+/]. Also triggered when a parsed default branch name from git symbolic-ref contains unexpected characters, or when options.pinOverride is set to a malformed value.","commonSituations":"A user supplies a URL like https://github.com/org/repo.git@feature/my branch (space in branch name); a ref with a colon like 'HEAD~1'; a non-string pinOverride passed programmatically; a ref derived from a malformed deep-path URL that the parser partially consumed.","solutions":["Check the ref string for whitespace or shell metacharacters and remove/escape them before passing as a version suffix.","If the ref is a raw commit SHA, note that git clone --branch cannot use SHAs — use --pin at the module level or a branch/tag name instead.","Ensure options.pinOverride, when provided, is a simple alphanumeric/semver tag string.","URL-encode branch names containing slashes inside the path, or quote the @version suffix properly."],"exampleFix":"// before\nconst url = 'https://github.com/org/repo.git@feature/fix bug';\nawait mgr.cloneRepo(url);\n\n// after\nconst url = 'https://github.com/org/repo.git@feature/fix-bug';\nawait mgr.cloneRepo(url);","handlingStrategy":"validation","validationCode":"function isValidRef(ref) {\n  return typeof ref === 'string' && /^[\\w.\\-+/]+$/.test(ref);\n}\n\n// Before calling cloneRepo:\nconst version = extractVersion(url);\nif (version && !isValidRef(version)) {\n  throw new Error(`Invalid ref name: ${version}`);\n}","typeGuard":"function isSafeRef(ref) {\n  return typeof ref === 'string' && ref.length > 0 && /^[\\w.\\-+/]+$/.test(ref) && !ref.includes('//');\n}","tryCatchPattern":"try {\n  await mgr.cloneRepo(url, { pinOverride });\n} catch (e) {\n  if (e.message.startsWith('Unsafe ref name')) {\n    console.error('The version/tag contains invalid characters. Use only letters, digits, dots, hyphens, underscores, plus, and slashes.');\n  }\n  throw e;\n}","preventionTips":["Validate all user-supplied refs against ^[\\w.\\-+/]+$ before passing to the installer.","Never pass raw commit SHAs as version suffixes — git clone --branch does not support them.","Sanitize --pin values at the CLI boundary before they reach internal APIs.","Test branch/tag names with 'git check-ref-format' if unsure."],"tags":["security","shell-injection","git","input-validation"],"backgroundTag":null,"analyzedSha":"b70486b9bdcb0a404d329e2a763b57964e7f1360","analyzedAt":"2026-08-13T01:21:12.247Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}