Thanks for your interest in scanipy — the local, private, zero-config taint-tracking SAST CLI for Python. scanipy follows untrusted data from sources to sinks (through sanitizers) and reports the witness trace, not just pattern matches. This is an early scaffold, so a lot is still stubbed out and the taint DSL is a draft that co-evolves with the engine. Contributions of all sizes are welcome: bug reports, new detectors, docs, tests, and engine work. Please keep changes lean, honest about what works, and aligned with the project principles (local & private, witness-backed findings, determinism, declarative detectors).
scanipy uses a src/ layout and the hatchling build backend. Requires Python >= 3.10.
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e ".[dev]"
pre-commit installThe editable install (-e ".[dev]") pulls in the runtime and dev dependencies and puts the scanipy command on your PATH (you can also run it as python -m scanipy). pre-commit install wires up the lint/format/type hooks so they run automatically on commit.
Run these locally before opening a pull request — CI runs the same checks across Python 3.10, 3.11, 3.12, and 3.13:
ruff check . # lint
ruff format . # format
mypy src # type-check (strict on src/)
pytest # testsmain is a protected trunk: all changes land via pull request — never push to main directly. Work on a feature branch, open a PR, and merge it once CI is green.
Three layers enforce this:
- Server-side (authoritative): a GitHub branch ruleset on
mainrequires a pull request and blocks direct pushes, branch deletion, and force-pushes. This is the real lock. - CI backstop:
.github/workflows/enforce-pr-only-merges.ymlfails red if a commit reachesmainoutside the GitHub merge UI. - Local (run once):
pre-commit installarms both a commit guard (no-commit-to-branch) and a push guard (no-push-to-main), so your clone refuses commits and pushes that targetmain. Emergency bypass (avoid):git commit/push --no-verify.
Maintainers can (re)apply the server-side ruleset with:
gh api -X POST repos/scanipy/scanipy-oss/rulesets --input - <<'JSON'
{
"name": "protect-main",
"target": "branch",
"enforcement": "active",
"conditions": { "ref_name": { "include": ["refs/heads/main"], "exclude": [] } },
"rules": [
{ "type": "pull_request" },
{ "type": "deletion" },
{ "type": "non_fast_forward" }
]
}
JSONDetectors are declarative YAML specs — detection logic lives in the DSL, not in engine code (principle P4). To add one:
- Read the guide: docs/writing-detectors.md. For the canonical DSL schema, see docs/dsl-reference.md.
- Scaffold it with the
/new-detectorhelper command. - Ship both fixtures. Every detector MUST come with a true-positive fixture (code that should be flagged) AND a true-negative fixture (code that should not be flagged) — this is principle P5, and it is required. Sanitizer soundness is one-sided: a missed sanitizer is a false positive, never a silently-suppressed real vulnerability. When in doubt, prefer reporting over suppressing.
-
Formatting & lint: ruff, configured in
pyproject.toml— line length 100, double quotes. Runruff format .andruff check .. -
SPDX header: every Python source file starts with:
# SPDX-License-Identifier: Apache-2.0 -
Type hints: full type hints on all code;
mypyruns in strict mode onsrc/. Keepmypy srcclean. -
Commit messages: use Conventional Commits — e.g.
feat(detectors): add subprocess shell-injection detector,fix(cli): correct exit code for empty scan,docs: clarify DSL parameter pattern.
By contributing, you agree that your contributions are licensed under the project's Apache-2.0 license.
Participation in this project is governed by our Code of Conduct. Please read it before contributing.
scanipy is developed with a small team of role-scoped agents, each owning a slice of the codebase. Their definitions live under .claude/.
- taint-engine — the source→sink taint analysis core.
- detector-author — authors and maintains the declarative YAML detectors.
- cli-ux — the
scanipycommand-line surface and output formats. - qa-test — fixtures, test coverage, and the true-positive / true-negative discipline.
- docs-writer — user and contributor documentation.
- release-eng — packaging, versioning, and releases.
- code-reviewer — review for correctness, soundness, and adherence to the principles above.
Helper commands /new-detector, /scan-self, and /release support these roles. You don't need to use the agents to contribute — they're how the maintainers organize the work, and the definitions in .claude/ are a useful map of who owns what.