Skip to content

The Git Glob Blind Spot in a Token Build Gate

Adityo Guni Waluyo

git's ** pathspec skips files directly under src/; a four-command throwaway repo exposed the blind spot in a token build gate before anyone exploited it.

TL;DR

While testing a build gate that blocks hardcoded hex colors, I assumed src/**/*.tsx would catch files directly in src. A quick git ls-files experiment proved the pattern misses direct children, leaving a silent loophole. I fixed it by combining shallow and recursive globs and added tests to ensure coverage at any depth.

I was writing tests for a new token build gate in a Next.js company-profile frontend. The gate’s job is simple: block hardcoded hex colors like #fff123 outside of designated token sheets. Everything was going smoothly until a stray thought hit me. What if a component file sits directly under src/, instead of inside a subfolder like every current component in the project? My intuition immediately said it was safe. After all, ** in a glob pattern means **any depth, including zero**, right?

I was wrong. The gate could let a violating file pass completely silently.

Probing With a Throwaway Repo

I didn't want to trust my gut, so I spun up a scratch repository to test the assumption. I created one file directly in src/ and another nested inside src/components/, then asked git ls-files to list them based on the glob pattern the build gate was using.

mkdir -p frontend/src/components
echo "const x = '#fff123'" > frontend/src/Foo.tsx
echo "const y = '#000000'" > frontend/src/components/Bar.tsx
git init && git add .

git ls-files "frontend/src/**/*.tsx"

git ls-files "frontend/src/*.tsx"

The result was immediate and unsettling. The ** pattern completely missed Foo.tsx, which is the exact class of violation the gate was built to catch. Since git ls-files shows files tracked in the index by default [5], this wasn't some weird repository quirk. It was **pure, unforgiving pattern matching**. Four commands, twenty seconds, and one long-held assumption was dead. It is always cheaper to verify than to trust.

Why This Happens

To understand why, we have to look at how Git handles pathspecs [3]. A pathspec is simply a pattern that limits paths in Git commands. After the directory prefix matches literally, the remainder of the pattern goes through fnmatch(3), where * and ? can match directory separators [3]. This is a subtle but critical distinction.

Here is the mental model that finally made it click. In a pattern like dir/**/*.ext, the dir/ part must match exactly. The ** only widens the remainder; it does not make the prefix optional or magically collapse the directory structure. A file sitting exactly at frontend/src/ is missed because the pattern is actively looking for something inside src/, not src/ itself. No extra depth means no match.

In practice, the gate was still running fine. Every current component in the project was nested, so every build successfully scanned all relevant files. The hole only surfaced because I was writing tests. A QA log entry had flagged the gate for a check, and the answer only arrived during this deep dive. The hole was real, it just had never been exploited.

The Union and Its Regression Tests

The fix was straightforward: a union of patterns. I joined the direct-children patterns with the recursive ones, ensuring the file list no longer depended on arbitrary nesting depth. The exit contract remained unchanged: zero for a clean run, one for a violation. Only the coverage changed.

I added two new regression tests to lock this in. First, a Bad.tsx file with a forbidden hex code directly in src/ must now fail with exit 1 (a scenario that previously passed with exit 0). Second, a clean file directly in src/ must still pass, guaranteeing the fix did not introduce a new false alarm. The test suite grew from 12 to 14 tests, and npm run build still passed cleanly with the updated gate.

The scary part about luck is how comfortable it feels: no alarms, no symptoms, until someone deliberately drops a file in src/ to dodge review, and the gate stays stubbornly green. I hold a firm opinion on this: a gate that passes today purely because of folder-structure luck is just a **bug on an installment plan**. If a gate’s job is selecting files, it must be explicitly tested for directory-shape assumptions, covering both direct children and deeply nested files, not just good or bad file contents.

## Sources [3] Official Git glossary (pathspec): https://git-scm.com/docs/gitglossary [5] Git ls-files documentation: https://git-scm.com/docs/git-ls-files

Sources

3. the official Git glossary (pathspec)
5. the git ls-files docs

Related articles