git clone https://github.com/ericrihm/depfence && cd depfence
pip install -e ".[dev]"
pytest tests/ -v
ruff check depfence/ tests/depfence loads scanners via pip entry points. To add one:
- Create
depfence/scanners/your_scanner.py:
from depfence.core.models import Finding, FindingType, PackageMeta, Severity
class YourScanner:
name = "your_scanner"
ecosystems = ["pypi", "npm"] # which ecosystems this applies to
async def scan(self, packages: list[PackageMeta]) -> list[Finding]:
"""Scan package metadata. Return findings."""
findings = []
for meta in packages:
# your detection logic here
pass
return findings
async def scan_project(self, project_dir: Path) -> list[Finding]:
"""Optional: scan files in the project directory."""
return []- Register it in
pyproject.tomlunder[project.entry-points."depfence.scanners"]:
your_scanner = "depfence.scanners.your_scanner:YourScanner"-
Add tests in
tests/test_your_scanner.py. Every scanner needs tests. -
Reinstall:
pip install -e ".[dev]"to pick up the new entry point.
Two scanner types:
- Entry-point scanners implement
scan(packages)— they operate on fetched package metadata (name, version, maintainers, install scripts). - Project scanners implement
scan_project(project_dir)— they walk files (workflows, Dockerfiles, model files, configs).
A scanner can implement both.
lockfile detection → metadata fetch → scanner execution → enrichment → output
│
┌───────────┴───────────┐
entry-point scanners project scanners
(42 via pip registry) (21 filesystem-based)
│ │
operate on PackageMeta operate on project dir
Enrichment adds EPSS scores, CISA KEV status, OpenSSF Scorecard, and reachability data. Output formats: table, JSON, SARIF, HTML, CycloneDX, SPDX.
- Linter: ruff (config in
pyproject.toml) - Line length: 100
- Target: Python 3.10+
- Tests: pytest with pytest-asyncio
- Type hints: use them, but full mypy strictness is not required
Run before submitting:
ruff check depfence/ tests/
pytest tests/ -v- Every scanner needs a corresponding
tests/test_<scanner_name>.py - Target >95% coverage per scanner file
- Test both detection (malicious input triggers finding) and false-positive avoidance (benign input produces no findings)
- Use
pytest-asynciofor async scanner methods - CI runs tests on Python 3.10, 3.11, 3.12, and 3.13
- Branch from
main. Name branchesfeat/<thing>,fix/<thing>, ordocs/<thing>. - Keep PRs focused — one scanner or one feature per PR.
- Include tests. PRs without tests for new detection logic will be asked to add them.
- CI must pass (lint + tests + self-scan + action pin verification).
- Write a clear description: what the scanner detects, a real-world example of the attack, and how you tested it.
Open an issue with:
- The
finding_typeandmatched_patternfrom the finding metadata - The package name and version (or file path) that triggered it
- Why you believe it's a false positive
We take false positives seriously — every one erodes trust in the tool.
See SECURITY.md for reporting security issues. Do not open public issues for vulnerabilities.
By contributing, you agree that your contributions will be licensed under the Apache 2.0 License.