Opened by detail-app[bot] · 6 days ago
Sign in to comment.
app.detail.dev/org_0f605249-55f3-46eb-880c-20ae3e45ae99/…/bug_b096d1ba-a021-4eb2-b3c9-d963b36d2fba
Introduced in #10 by @ripgrim on Jul 22, 2026
packages/sdk/src/glob.ts implements the glob-lite matcher used by the honeypot rule to decide whether a contributor's changed paths touch bait/protected files — both the rule's pass/fail verdict and its "touched" evidence come from this one function.globMatch uses a literal space character as the temporary placeholder for **, so any literal space in a user-configured pattern is silently converted into the wildcard .* (which also spans /, since JS . matches /).docs/My Folder/** matches docs/MyXFolder/secret.yml (it compiles to ^docs/My.*Folder/.*$); it should only match paths under the literal directory docs/My Folder/..* is a superset of the literal space, a genuine honeypot touch is never missed — the bug causes false positives on unrelated paths/PRs, never a security gap.export function globMatch(pattern: string, path: string): boolean {
const regex = new RegExp(
`^${pattern
.replaceAll(/[.+^${}()|[\]\\]/g, "\\$&")
.replaceAll("**", " ") // <-- BUG 🔴 '**' replaced with a literal SPACE marker
.replaceAll("*", "[^/]*")
.replaceAll(" ", ".*")}$`, // <-- BUG 🔴 literal spaces become '.*' wildcard (also spans '/')
);
return regex.test(path);
}
globMatch constructs a regex by first escaping special regex characters, then translating glob tokens. To handle **, it temporarily replaces ** with a marker, later expanding the marker into .*.
The implementation uses a literal space (" ") as that marker. Because spaces are not escaped and can appear in user-provided patterns (e.g., real folder names like My Folder), the final .replaceAll(" ", ".*") converts every literal space in the pattern into .*. This causes broad over-matching, including across path separators.
Earlier honeypot matcher code used a raw NUL byte ("\0") as the ** placeholder, which cannot collide with user input. The regression was introduced when the matcher was moved into the SDK and the placeholder was changed from "\0" to a space while comments claimed it was “moved verbatim”.
Use a placeholder that cannot appear in user input (e.g. restore the NUL byte marker or avoid a sentinel entirely):
.replaceAll("**", "\0")
.replaceAll("*", "[^/]*")
.replaceAll("\0", ".*")
Add coverage for patterns containing spaces, e.g.:
globMatch("docs/My Folder/**", "docs/MyXFolder/secret.yml") === falseglobMatch("docs/My Folder/**", "docs/My Folder/file.yml") === trueThis bug was introduced in commit 2459c9d0 ("phase 3"). That commit extracted the honeypot's glob matcher into a new packages/sdk/src/glob.ts and swapped the ** placeholder from a raw NUL byte ("\0") to a literal space (" ").