From 22fe139e7b0096d661384c6bb3f6038b36ea0df3 Mon Sep 17 00:00:00 2001 From: Tibsfox Date: Tue, 3 Mar 2026 01:43:39 -0800 Subject: [PATCH] fix(milestone): escape reqId in regex patterns to prevent injection cmdRequirementsMarkComplete interpolates user-supplied reqId strings directly into RegExp constructors. If a reqId contains regex metacharacters (e.g. parentheses, brackets, dots), the patterns break or match unintended content. Import escapeRegex from core.cjs (already used in state.cjs and phase.cjs) and apply it to reqId before interpolation into all four regex patterns in the function. Same class of fix as gsd-build/get-shit-done#741 (state.cjs). Co-Authored-By: Claude Opus 4.6 --- get-shit-done/bin/lib/milestone.cjs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/get-shit-done/bin/lib/milestone.cjs b/get-shit-done/bin/lib/milestone.cjs index 77625376b..9021d2bea 100644 --- a/get-shit-done/bin/lib/milestone.cjs +++ b/get-shit-done/bin/lib/milestone.cjs @@ -4,7 +4,7 @@ const fs = require('fs'); const path = require('path'); -const { output, error } = require('./core.cjs'); +const { escapeRegex, output, error } = require('./core.cjs'); const { extractFrontmatter } = require('./frontmatter.cjs'); const { writeStateMd } = require('./state.cjs'); @@ -37,20 +37,21 @@ function cmdRequirementsMarkComplete(cwd, reqIdsRaw, raw) { for (const reqId of reqIds) { let found = false; + const reqEscaped = escapeRegex(reqId); // Update checkbox: - [ ] **REQ-ID** → - [x] **REQ-ID** - const checkboxPattern = new RegExp(`(-\\s*\\[)[ ](\\]\\s*\\*\\*${reqId}\\*\\*)`, 'gi'); + const checkboxPattern = new RegExp(`(-\\s*\\[)[ ](\\]\\s*\\*\\*${reqEscaped}\\*\\*)`, 'gi'); if (checkboxPattern.test(reqContent)) { reqContent = reqContent.replace(checkboxPattern, '$1x$2'); found = true; } // Update traceability table: | REQ-ID | Phase N | Pending | → | REQ-ID | Phase N | Complete | - const tablePattern = new RegExp(`(\\|\\s*${reqId}\\s*\\|[^|]+\\|)\\s*Pending\\s*(\\|)`, 'gi'); + const tablePattern = new RegExp(`(\\|\\s*${reqEscaped}\\s*\\|[^|]+\\|)\\s*Pending\\s*(\\|)`, 'gi'); if (tablePattern.test(reqContent)) { // Re-read since test() advances lastIndex for global regex reqContent = reqContent.replace( - new RegExp(`(\\|\\s*${reqId}\\s*\\|[^|]+\\|)\\s*Pending\\s*(\\|)`, 'gi'), + new RegExp(`(\\|\\s*${reqEscaped}\\s*\\|[^|]+\\|)\\s*Pending\\s*(\\|)`, 'gi'), '$1 Complete $2' ); found = true;