Skip to content

Exclude regex patterns are unanchored #23

Description

@adi7312

Summary

The default cpg.exclusion_patterns in config.example.yaml / src/defaults.py are written assuming the regex is full-matched against a path. That assumption holds for the JVM-native frontends (c2cpg, javasrc2cpg, pysrc2cpg), but not for jssrc2cpg (and likely other astgen-based frontends), which apply the same --exclude-regex string with unanchored substring search against the file's absolute path. As a result, patterns meant to skip hidden files/test dirs instead exclude nearly every real source file — including entry points like index.js. In the default config this produces a completely empty CPG for ordinary JS/TS projects.

Environment

  • codebadger-toolkit, config.example.yaml defaults (unmodified)
  • JOERN_VERSION=4.0.555 (per Dockerfile)
  • Frontend: jssrc2cpg (JavaScript)

Steps to reproduce

  1. Use the example default config, in particular:
    exclusion_patterns:
      - ".*/\\..*"
      - "\\..*"
      # ... (rest of the default list)
  2. Call generate_cpg (or run jssrc2cpg.sh directly) against any JS/TS repo that has a top-level index.js.
  3. Inspect the jssrc2cpg invocation's --exclude-regex value and the resulting CPG.

Expected behavior

Only genuine junk (hidden dirs, node_modules, test/fixture directories, build artifacts, etc.) should be excluded. index.js and other normal source files should be parsed and present in the CPG.

Actual behavior

index.js (and effectively any file with a . anywhere in its path) is silently excluded from the CPG before parsing even starts. With the default pattern list this empties out the whole source tree, and codebadger's own load-verification step correctly flags the resulting CPG as broken:

CPG loaded but is empty (0 user-defined methods) — treating as a failed/empty build
CPG for <hash> is empty/broken (not a transient failure) — not retrying reload

Root cause

--exclude-regex matching semantics differ by frontend:

  • JVM frontends (c2cpg, javasrc2cpg, pysrc2cpg) filter files via SourceFiles.scala, using Scala/Java Regex.matches() / String.matches()implicitly full-match (^...$) — against the path relative to the input dir. A bare pattern like "\\..*" only matches paths that literally start with ..

  • jssrc2cpg delegates file discovery to the external javascript-astgen binary (joernio/astgen-monorepo, written in TypeScript). Its FileUtils.ts applies the same --exclude-regex string via:

    rules.regex?.test(fullPath)   // plain RegExp.test() — unanchored substring search

    against the file's absolute path (e.g. /playground/codebases/<hash>/src/index.js), inside both ignoreDirectory() and ignoreFileByName().

Under unanchored RegExp.test(), a pattern like "\\..*" (literal . followed by .*, which can match zero characters) is satisfied by any single . occurring anywhere in the string — i.e. any file with an extension. index.js contains .js, so \..* matches it and it gets excluded. The same collapse happens to every other bare-token pattern in the default list that lacks a leading .*/ boundary (test.*, spec.*, fuzz.*, e2e.*, demo.*, unit.*, guide.*, …) — under substring search these degenerate into "contains TOKEN anywhere" (e.g. test.* would also incidentally match latest.js, attestation.go).

Reproduction

Built a minimal demo repo (index.js requiring src/math.js, 3 functions total: main, add, greet) and ran generate_cpg against a locally running codebadger-mcp instance, first with the stock default config, then again after applying the fix below — same server, same source, only the exclusion patterns changed.

Before (default patterns):

Image
Executing CPG generation ...: jssrc2cpg.sh <src> -o <out> --exclude-regex (.*/\..*)|(\..*)|(.*/test.*)|(test.*)|...
CPG generated successfully: .../cpg.bin
CPG loaded but is empty (0 user-defined methods) — treating as a failed/empty build
CPG for <hash> is empty/broken (not a transient failure) — not retrying reload

get_cpg_status"status": "failed", "error": "CPG generated but failed to load into Joern server".

Image

After (anchored patterns, (?:^|.*/)TOKEN form):

get_cpg_status"status": "ready", "user_method_count": 5.
list_methods returns exactly the expected set — both files fully recovered, nothing over- or under-included:

Image Image Image
Original config Fixed config
--exclude-regex bare \..*, test.*, demo.*, ... (unanchored) (?:^|.*/)\..*, (?:^|.*/)test.*, ... (boundary-anchored)
jssrc2cpg result 0 user-defined methods 5 user-defined methods
get_cpg_status "failed" "ready"
Methods found none main, add, greet (+ 2 :program nodes)

Proposed fix

Require an explicit boundary — start-of-string or a literal / — immediately before each token, via (?:^|.*/), e.g.:

# before
- ".*/\\..*"
- "\\..*"
- ".*/test.*"
- "test.*"

# after
- "(?:^|.*/)\\..*"
- "(?:^|.*/)test.*"

This is a no-op for the JVM frontends (verified identical fullmatch results against relative paths, and it additionally fixes a pre-existing gap where root-level dirs like vendor/ weren't matched because they lacked a preceding /), and fixes the astgen frontends' unanchored matching (index.js, latest.js, attestation.go are kept; .git/, test/, node_modules/, vendor/ are still excluded). Confirmed live above.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions