From 2b9acbd7a522780a930b7d0cc2c0c117558af446 Mon Sep 17 00:00:00 2001 From: Joseph Mikhail Date: Sun, 5 Apr 2026 17:17:39 -0700 Subject: [PATCH 1/7] fix(security): replace polynomial regex in stripSqlComments (CodeQL #5) Replaces backtracking-prone /\/\*[\s\S]*?\*\// with Friedl's unrolled loop pattern to eliminate ReDoS risk on unterminated block comments. Co-Authored-By: Claude Sonnet 4.6 --- core-ingestion/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core-ingestion/src/index.ts b/core-ingestion/src/index.ts index 51cce19..61554ad 100644 --- a/core-ingestion/src/index.ts +++ b/core-ingestion/src/index.ts @@ -553,7 +553,7 @@ function parseDockerfileFile(filePath: string, source: string): FileParseResult function stripSqlComments(source: string): string { // Block comments first, then line comments return source - .replace(/\/\*[\s\S]*?\*\//g, match => match.replace(/[^\n]/g, ' ')) + .replace(/\/\*[^*]*\*+(?:[^/*][^*]*\*+)*\//g, match => match.replace(/[^\n]/g, ' ')) .replace(/--[^\n]*/g, match => ' '.repeat(match.length)); } From ab09ae5ee07985cb7e87097a7f71a00da9ea7e33 Mon Sep 17 00:00:00 2001 From: Joseph Mikhail Date: Sun, 5 Apr 2026 17:19:06 -0700 Subject: [PATCH 2/7] fix(security): replace trailing-whitespace regex with trimEnd (CodeQL #4) /\s+$/ can backtrack catastrophically on lines with many spaces. Replaced with String.trimEnd() which is built-in and regex-free. Co-Authored-By: Claude Sonnet 4.6 --- core-ingestion/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core-ingestion/src/index.ts b/core-ingestion/src/index.ts index 61554ad..288d569 100644 --- a/core-ingestion/src/index.ts +++ b/core-ingestion/src/index.ts @@ -514,7 +514,7 @@ function parseDockerfileFile(filePath: string, source: string): FileParseResult for (let index = 0; index < lines.length; index++) { const line = lines[index]; - const trimmedEnd = line.replace(/\s+$/, ''); + const trimmedEnd = line.trimEnd(); const lineContent = pending ? `${pending}\n${line}` : line; if (!pending) logicalStartLine = index + 1; From c891a4730be91dbc136865def70041b9bb8aa225 Mon Sep 17 00:00:00 2001 From: Joseph Mikhail Date: Sun, 5 Apr 2026 17:21:57 -0700 Subject: [PATCH 3/7] fix(security): remove overlapping quantifiers in keyLinePattern (CodeQL #3) (?:\s*.*)?$ causes backtracking ambiguity since both \s* and .* match spaces. Simplified to .*$ which is equivalent and linear-time. Co-Authored-By: Claude Sonnet 4.6 --- core-ingestion/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core-ingestion/src/index.ts b/core-ingestion/src/index.ts index 288d569..11fe025 100644 --- a/core-ingestion/src/index.ts +++ b/core-ingestion/src/index.ts @@ -312,7 +312,7 @@ function parseYamlFile(filePath: string, source: string): FileParseResult { const lineStarts = computeLineStarts(source); const lines = source.split(/\r?\n/); const stack: Array<{ indent: number; key: string }> = []; - const keyLinePattern = /^(\s*)(?:-\s+)?([A-Za-z0-9_.-]+)\s*:(?:\s*.*)?$/; + const keyLinePattern = /^(\s*)(?:-\s+)?([A-Za-z0-9_.-]+)\s*:.*$/; for (let index = 0; index < lines.length; index++) { const line = lines[index]; From 6864467c36ce3ae15d765f1e97a5bae20070f3f3 Mon Sep 17 00:00:00 2001 From: Joseph Mikhail Date: Sun, 5 Apr 2026 17:26:15 -0700 Subject: [PATCH 4/7] fix(security): restrict GITHUB_TOKEN permissions in notify-sync (CodeQL #2) Workflow uses a GitHub App token exclusively; GITHUB_TOKEN is never referenced. Adding permissions: {} enforces least privilege. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/notify-sync.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/notify-sync.yml b/.github/workflows/notify-sync.yml index 8a5f368..a212a8b 100644 --- a/.github/workflows/notify-sync.yml +++ b/.github/workflows/notify-sync.yml @@ -11,6 +11,8 @@ on: release: types: [published] +permissions: {} + jobs: notify: runs-on: ubuntu-latest From 9d723d662c5994e656fd2ef2c48e8e5c4f97c6da Mon Sep 17 00:00:00 2001 From: Joseph Mikhail Date: Sun, 5 Apr 2026 17:28:08 -0700 Subject: [PATCH 5/7] fix(security): restrict GITHUB_TOKEN permissions in ci workflow (CodeQL #1) Only contents: read is needed for actions/checkout. Explicit permissions block enforces least privilege on the GITHUB_TOKEN. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index df5ff77..3ac6e7f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,9 @@ on: pull_request: branches: [main] +permissions: + contents: read + jobs: cli: name: CLI (TypeScript) From 86bce65b313a53a921992cd0cdea15eeb75687c5 Mon Sep 17 00:00:00 2001 From: Joseph Mikhail Date: Sun, 5 Apr 2026 17:45:18 -0700 Subject: [PATCH 6/7] fix(security): use unambiguous alternation for block comment regex (CodeQL #5) Previous unrolled loop pattern still had ambiguous quantifiers. Replaced with (?:[^*]|\*[^/])* where alternatives are mutually exclusive, making backtracking impossible. Co-Authored-By: Claude Sonnet 4.6 --- core-ingestion/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core-ingestion/src/index.ts b/core-ingestion/src/index.ts index 11fe025..24186e2 100644 --- a/core-ingestion/src/index.ts +++ b/core-ingestion/src/index.ts @@ -553,7 +553,7 @@ function parseDockerfileFile(filePath: string, source: string): FileParseResult function stripSqlComments(source: string): string { // Block comments first, then line comments return source - .replace(/\/\*[^*]*\*+(?:[^/*][^*]*\*+)*\//g, match => match.replace(/[^\n]/g, ' ')) + .replace(/\/\*(?:[^*]|\*[^/])*\*\//g, match => match.replace(/[^\n]/g, ' ')) .replace(/--[^\n]*/g, match => ' '.repeat(match.length)); } From b764a68530b593e69f67cb25a8df6836c67b7fc9 Mon Sep 17 00:00:00 2001 From: Joseph Mikhail Date: Sun, 5 Apr 2026 17:49:19 -0700 Subject: [PATCH 7/7] fix(security): replace block comment regex with indexOf scan (CodeQL #5) Regex-based approaches are flagged by CodeQL due to O(n^2) backtracking when the pattern fails to match. indexOf is a plain linear scan with no NFA and no backtracking possible. Co-Authored-By: Claude Sonnet 4.6 --- core-ingestion/src/index.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/core-ingestion/src/index.ts b/core-ingestion/src/index.ts index 24186e2..7171e50 100644 --- a/core-ingestion/src/index.ts +++ b/core-ingestion/src/index.ts @@ -551,10 +551,20 @@ function parseDockerfileFile(filePath: string, source: string): FileParseResult /** Strip -- line comments and /* block comments *\/ from SQL source. */ function stripSqlComments(source: string): string { - // Block comments first, then line comments - return source - .replace(/\/\*(?:[^*]|\*[^/])*\*\//g, match => match.replace(/[^\n]/g, ' ')) - .replace(/--[^\n]*/g, match => ' '.repeat(match.length)); + // Block comments: use indexOf to avoid ReDoS from regex backtracking + let result = ''; + let i = 0; + while (i < source.length) { + const start = source.indexOf('/*', i); + if (start === -1) { result += source.slice(i); break; } + result += source.slice(i, start); + const end = source.indexOf('*/', start + 2); + if (end === -1) { result += source.slice(start).replace(/[^\n]/g, ' '); break; } + result += source.slice(start, end + 2).replace(/[^\n]/g, ' '); + i = end + 2; + } + // Line comments: [^\n]* is always linear (no backtracking possible) + return result.replace(/--[^\n]*/g, match => ' '.repeat(match.length)); } /** Extract unquoted table/view names following FROM, JOIN variants, INTO, UPDATE, REFERENCES. */