Skip to content

Add regexpdynamicpattern linter: flag non-constant regexp compile patterns - #50674

Merged
pelikhan merged 3 commits into
mainfrom
copilot/linter-miner-add-regexpdynamicpattern-linter
Aug 5, 2026
Merged

Add regexpdynamicpattern linter: flag non-constant regexp compile patterns#50674
pelikhan merged 3 commits into
mainfrom
copilot/linter-miner-add-regexpdynamicpattern-linter

Conversation

Copilot AI commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

regexp.Compile/regexp.MustCompile calls built from dynamic input (string concatenation, fmt.Sprintf, function parameters) can panic at runtime on malformed patterns or enable ReDoS if the dynamic portion is influenced by untrusted input. The existing regexpcompileinfunction linter only checks where compilation happens (in-function vs. package-level), not whether the pattern itself is constant.

New linter

  • pkg/linters/regexpdynamicpattern/regexpdynamicpattern.go — flags regexp.Compile/MustCompile calls whose pattern argument is not a compile-time constant string (literal, const identifier, or constant-only expression). Resolves the regexp package identity via the type checker to handle aliased imports and avoid false positives from shadowed identifiers. Applies at both package level and inside functions, and respects //nolint:regexpdynamicpattern suppressions.
// not flagged — constant pattern
re := regexp.MustCompile(`^[a-z]+$`)

// flagged — pattern built from dynamic input
re, err := regexp.Compile(fmt.Sprintf("^%s$", prefix))
re := regexp.MustCompile(pattern) // pattern is a function parameter

Wiring and docs

  • Registered in pkg/linters/registry.go (All())
  • Documented in pkg/linters/doc.go and pkg/linters/README.md (bullet list, analyzer table, import example, subpackages list), with active-analyzer count updated
  • Added to pkg/linters/spec_test.go's documentedAnalyzers() list to keep doc-sync tests aligned

Tests

  • regexpdynamicpattern_test.go using analysistest, with fixtures under testdata/src/regexpdynamicpattern/ covering literal/const patterns (not flagged), fmt.Sprintf-built and concatenation-with-variable patterns (flagged), pattern passed through from a parameter (flagged), constant-only concatenation (not flagged), and //nolint:regexpdynamicpattern suppression on both the same line and the previous line.

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Add regexpdynamicpattern linter to flag non-constant regexp patterns Add regexpdynamicpattern linter: flag non-constant regexp compile patterns Aug 5, 2026
Copilot AI requested a review from pelikhan August 5, 2026 19:21
@pelikhan
pelikhan marked this pull request as ready for review August 5, 2026 19:26
Copilot AI balanced review requested due to automatic review settings August 5, 2026 19:26
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ failed during design decision gate check.

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a custom Go analyzer that detects dynamic patterns passed to regexp.Compile and regexp.MustCompile.

Changes:

  • Implements and registers regexpdynamicpattern.
  • Adds analysistest fixtures and suppression coverage.
  • Updates analyzer documentation and synchronization tests.
Show a summary per file
File Description
pkg/linters/regexpdynamicpattern/regexpdynamicpattern.go Implements pattern analysis and diagnostics.
pkg/linters/regexpdynamicpattern/regexpdynamicpattern_test.go Runs analyzer fixtures.
pkg/linters/regexpdynamicpattern/testdata/src/regexpdynamicpattern/regexpdynamicpattern.go Covers constant, dynamic, and suppressed patterns.
pkg/linters/registry.go Registers the analyzer.
pkg/linters/spec_test.go Adds documentation synchronization coverage.
pkg/linters/README.md Documents the new analyzer.
pkg/linters/doc.go Updates package documentation and analyzer count.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Suppressed comments (1)

pkg/linters/regexpdynamicpattern/regexpdynamicpattern.go:69

  • This diagnostic makes two incorrect claims for calls the analyzer flags: regexp.Compile returns malformed-pattern errors rather than panicking, and Go's regexp implementation uses linear-time matching rather than catastrophic backtracking. Report the policy violation without those claims, or emit separate MustCompile-specific wording.
			Message: "regexp pattern is not a compile-time constant; dynamic patterns can panic at runtime or enable ReDoS if influenced by untrusted input",
  • Files reviewed: 7/8 changed files
  • Comments generated: 3
  • Review effort level: Balanced

Comment on lines +3 to +6
// is not a compile-time constant string. Dynamically constructed patterns can
// panic at runtime on malformed input and, when influenced by untrusted
// input, can enable catastrophic-backtracking (ReDoS) denial-of-service
// attacks.
Comment on lines +76 to +79
// isRegexpCompileCall checks if the call is to regexp.MustCompile or regexp.Compile,
// resolving the package identity via the type checker to handle aliased imports
// and avoid false positives from local identifiers named "regexp".
func isRegexpCompileCall(pass *analysis.Pass, call *ast.CallExpr) bool {
Comment thread pkg/linters/registry.go
panicinlibrarycode.Analyzer,
rawloginlib.Analyzer,
regexpcompileinfunction.Analyzer,
regexpdynamicpattern.Analyzer,
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 85/100 — Excellent

Analyzed 2 test(s): 2 design, 0 implementation, 0 violation(s).

📊 Metrics (2 tests)
Metric Value
Analyzed 2 (Go: 2, JS: 0)
✅ Design 2 (100%)
⚠️ Implementation 0 (0%)
Edge/error coverage 1 (50%)
Duplicate clusters 0
Inflation No (test: 16 lines, prod: 123 lines, ratio 0.13:1)
🚨 Violations 0
Test File Classification Issues
TestAnalyzer regexpdynamicpattern_test.go:13 design_test, high_value None
regexpdynamicpattern entry in documentedAnalyzers() spec_test.go:144 design_test, high_value None

Build tag compliance: Both files carry //go:build !integration on line 1 ✅

Coverage quality note: TestAnalyzer exercises the full behavioral matrix via analysistest.Run — 3 true-negative cases (literal, const, const-concat), 3 true-positive cases (fmt.Sprintf, variable concat, direct param), and 2 nolint suppression variants (previous-line + same-line). This is excellent coverage of the linter contract.

Verdict

Passed. 0% implementation tests (threshold: 30%). No violations detected.

🧪 Test quality analysis by Test Quality Sentinel · sonnet46 · 40.2 AIC · ⌖ 11 AIC · ⊞ 7.7K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Test Quality Sentinel: 85/100. 0% implementation tests (threshold: 30%). No violations.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation is correct and well-structured. The type-checker-based package resolution properly handles aliased imports. The hasConstantStringPattern function correctly handles string literals, const identifiers, and constant-only concatenation. nolint suppression on both same-line and previous-line is tested. Wiring into registry, docs, and spec tests is complete.> 🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 · 20 AIC · ⌖ 9.21 AIC · ⊞ 5.4K

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: no blocking issues

Solid, focused addition — the new linter correctly detects non-constant regexp patterns using type-checker-based package resolution and constant-value inspection.

Review notes
  • isRegexpCompileCall correctly resolves the regexp package via types.PkgName, avoiding false positives from local shadowing.
  • hasConstantStringPattern correctly uses types.TypeAndValue.Value to detect compile-time constants (literals, const idents, const-only concatenation).
  • Nolint suppression (same-line and previous-line) is tested and works as expected.
  • Minor gap: doc comments claim handling of aliased imports and shadowed identifiers, but no test fixtures cover those cases (see inline comment) — non-blocking, worth adding for regression safety.
  • Wiring (registry, doc.go, README, spec_test) is consistent and complete.
> 🔎 *Code quality review by [PR Code Quality Reviewer](https://github.com/github/gh-aw/actions/runs/31039435292)* · auto · 39.2 AIC · ⌖ 3.49 AIC · ⊞ 7.9K > Comment /review to run again

Comment on lines +13 to +16
func TestAnalyzer(t *testing.T) {
testdata := analysistest.TestData()
analysistest.Run(t, testdata, regexpdynamicpattern.Analyzer, "regexpdynamicpattern")
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test coverage doesn't exercise the two false-positive scenarios the package doc explicitly claims to handle (aliased regexp import, shadowed local identifier named regexp).

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skills-Based Review 🧠

Applied /tdd — requesting changes on test-coverage gaps before merging.

📋 Key Themes & Highlights

Issues Found

  • Missing POSIX variants: regexp.CompilePOSIX / regexp.MustCompilePOSIX carry identical risks but are not checked (line 80)
  • Aliased import not tested: the PR claims type-checker resolution handles aliased imports, but no testdata fixture validates this path (testdata line 9)
  • Minor: zero-arg guard (line 103) is good, but has no test to document the invariant

Positive Highlights

  • ✅ Excellent use of types.PkgName.Imported().Path() to avoid false positives from shadowed identifiers
  • hasConstantStringPattern correctly uses tv.Value from the type checker — handles consts and const-only expressions cleanly
  • ✅ Both same-line and previous-line //nolint suppression are tested
  • ✅ Documentation fully wired across README, doc.go, registry, and spec_test
  • ✅ Clear, accurate PR description linking to the security motivation
> 🧠 *Reviewed using Matt Pocock's skills by [Matt Pocock Skills Reviewer](https://github.com/github/gh-aw/actions/runs/31039435054)* · sonnet46 · 45.2 AIC · ⌖ 10.4 AIC · ⊞ 7.1K > Comment /matt to run again

// resolving the package identity via the type checker to handle aliased imports
// and avoid false positives from local identifiers named "regexp".
func isRegexpCompileCall(pass *analysis.Pass, call *ast.CallExpr) bool {
sel, ok := call.Fun.(*ast.SelectorExpr)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] regexp.CompilePOSIX and regexp.MustCompilePOSIX are not checked here, but they carry the same dynamic-pattern risk as their non-POSIX counterparts.

💡 Suggested fix

Extend the name check:

if sel.Sel.Name != "MustCompile" && sel.Sel.Name != "Compile" &&
	sel.Sel.Name != "MustCompilePOSIX" && sel.Sel.Name != "CompilePOSIX" {
	return false
}

Add testdata fixtures for the POSIX variants to keep the test specification complete.

@copilot please address this.

}

// hasConstantStringPattern checks whether the regexp pattern is a compile-time constant string,
// such as a string literal, const identifier, or an expression built entirely from constants

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] The len(call.Args) == 0 guard is a good defensive check, but there is no test fixture verifying a zero-argument call to regexp.Compile() is silently skipped rather than crashing. Zero-arg calls are technically invalid Go code, but the analyzer should not panic on malformed ASTs under analysis.

💡 Suggested test case

Since analysistest runs against valid Go, add a comment in the testdata noting this guard and consider a brief unit-test that exercises hasConstantStringPattern directly with a synthetic ast.CallExpr{Args: nil} to document the invariant.

@copilot please address this.

)

// not flagged: literal pattern at package level.
var packageLevelRegexp = regexp.MustCompile(`^[a-z]+$`)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] The testdata does not cover an aliased import of regexp (e.g. import re "regexp"). The analyzer claims to handle aliased imports via type-checker resolution, but this case is not proven by a test fixture.

💡 Suggested fixture additions
import re "regexp"

// not flagged: constant pattern via aliased import
var aliasedLiteral = re.MustCompile(`^[a-z]+$`)

// flagged: dynamic pattern via aliased import
func validateAliased(pattern string) { (nolint/redacted):...
    re.MustCompile(pattern) // want `regexp pattern is not a compile-time constant...`
}

This would give confidence that isRegexpCompileCall really resolves pkgName.Imported().Path() correctly regardless of local alias.

@copilot please address this.

Records the architectural decision to introduce a static analysis pass
that flags non-constant regexp.Compile/MustCompile patterns, including
the rationale, alternatives considered, and trade-offs.
@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please follow up on the latest review feedback on this PR:

Generated by 👨🍳 PR Sous Chef · run: https://github.com/github/gh-aw/actions/runs/31043697225

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 12.2 AIC · ⌖ 6.03 AIC · ⊞ 8.3K ·
Comment /souschef to run again

Copilot AI requested a review from gh-aw-bot August 5, 2026 20:35
@pelikhan
pelikhan merged commit 1b4c40f into main Aug 5, 2026
1 check passed
@pelikhan
pelikhan deleted the copilot/linter-miner-add-regexpdynamicpattern-linter branch August 5, 2026 21:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[linter-miner] Add regexpdynamicpattern linter: flag non-constant regexp patterns

4 participants