Visitar URL original
Scan extension-less entry points using their shebang by VictorMartins3 · Pull Request #885 · DataDog/guarddog · GitHub
Skip to content

Scan extension-less entry points using their shebang - #885

Open
VictorMartins3 wants to merge 1 commit into
DataDog:v3from
VictorMartins3:VictorMartins3/bin-entrypoints
Open

VictorMartins3 wants to merge 1 commit into
DataDog:v3from
VictorMartins3:VictorMartins3/bin-entrypoints

Conversation

@VictorMartins3

@VictorMartins3 VictorMartins3 commented Sep 17, 2026 •

Copy link
Copy Markdown

What's up

Every source code rule filters files through path_include, and all 23
distinct globs across the 54 rules select by extension:

*.py *.pyx *.pyi *.pth *.js *.jsx *.mjs *.cjs *.ts *.tsx
*.go *.rb *.gemspec *.rs *.sh Rakefile */Rakefile
extconf.rb */extconf.rb package.json */package.json setup.py */setup.py

An npm package's bin target normally has no extension, because the shell
runs it through the shebang. "bin": {"tool": "./bin/tool"} is the usual
shape. Nothing in that list matches bin/tool, so the file never reaches
a rule, and it's the one a consumer executes directly.

Same payload, two locations, full npm rule set:

index.js   8 issues   capability-filesystem-read, capability-network-download,
                      capability-network-outbound, threat-filesystem-read,
                      threat-runtime-environment-read
bin/tool   0 issues   (nothing)

Worth saying: this isn't only a capability-tagging gap. Two threat-*
rules fire on the first and neither fires on the second, so the whole
source code analyzer is blind to the file.

Sampling published npm packages that declare a bin, roughly one in nine
points it at a path with no source extension.

What I changed

When a file has no extension and doesn't match path_include, its shebang
stands in for one. #!/usr/bin/env node makes it eligible for the globs
that select .js, python3 for .py, bash for .sh, and so on, so a
shell script doesn't suddenly get handed to the Python rules.

A file with no shebang is still skipped — the fallback is the interpreter
line, not the absence of an extension, so a LICENSE or a data blob doesn't
start reaching every rule.

Each rule walks the tree on its own, so the probe is cached per scan and
each extension-less file is read once instead of 54 times. It's a 128-byte
read.

An alternative I considered

Resolving package.json's bin entries and always scanning those. More
precise for npm, but ecosystem-specific and it wouldn't cover a shebang
script that isn't declared in bin. The shebang approach felt like the
better fit for a multi-ecosystem scanner, but I'm happy to redo it the
other way if you'd rather keep the analyzer ecosystem-agnostic.

Tests

Three in tests/core/test_sourcecode_analyzer.py: the entry point is
scanned, an extension-less file without a shebang still isn't, and a
shebang maps to its own language only. First two fail on v3.

black --check guarddog     clean
flake8 guarddog            0
mypy guarddog              no issues in 74 files
pytest tests/core          all pass

tests/analyzer/metadata has 3 failures and 11 errors on a clean v3
checkout here, unrelated to this.

@VictorMartins3
VictorMartins3 requested a review from a team as a code owner September 17, 2026 03:35
Every source code rule selects files through path_include globs, and all of
them select by extension. A package's `bin` target normally has none,
because the shell runs it through the shebang, so that file is never handed
to any rule even though it is what a consumer executes directly.

Same payload in two places, scanned with the full npm rule set:

    index.js   8 issues, including threat-filesystem-read and
               threat-runtime-environment-read
    bin/tool   0 issues

When a file has no extension and does not match path_include, its shebang
now stands in for one: `#!/usr/bin/env node` makes it eligible for the
globs that select .js, python3 for .py, bash for .sh. A file with no
shebang is still skipped, so a LICENSE or a data blob does not start
reaching every rule.

Each rule walks the tree separately, so the probe result is cached per
scan and each extension-less file is read once rather than once per rule.
@VictorMartins3
VictorMartins3 force-pushed the VictorMartins3/bin-entrypoints branch from cb85026 to eb38cb9 Compare September 17, 2026 04:02

This branch has not been deployed

No deployments
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.

1 participant