Contributing to secure-code-agent
Thanks for considering a contribution. Read this first.
Scope
This project is an orchestrator + scoring + remediation-prompt layer. It is NOT a SAST engine. PRs that propose:
- writing a new AST analyzer in Python
- shipping a parallel ruleset to Semgrep / Bandit / CodeQL — meaning rules
that duplicate detection a floor scanner already does. Authoring rules for
a gap no floor scanner covers is not this, and is how the offline profile
exists at all; see D11 and D12. The test is measured,
not argued:
test_no_python_rule_duplicates_bandit,test_no_javascript_rule_duplicates_njsscanandtest_no_ruby_rule_duplicates_rubocopeach run the tool and fail the build on a rule flagging a line it already flags.
Before writing a rule, run the check. Name the language, install the candidate FOSS tools, run them against the fixtures, and record the result. A gap has to be proven, not asserted — D11 was written after nineteen Python rules turned out to duplicate Bandit seventeen times, and D12 after the same mistake was repeated by asserting that four other languages had no offline tool. Two of them did. - adding a SaaS dashboard - adding telemetry
…will be closed. See docs/design.md for the non-goals.
Development setup
pip install -e ".[dev]" # includes Bandit + pip-audit, so the repo can audit itself
secure-code-agent --preflight # confirm the toolchain resolves before running an audit
pytest -q
ruff check . && ruff format --check .
[dev] pulls in required-scanners deliberately: a checkout that cannot run
its own gate is a checkout that will surprise you in CI. Optional Python
scanners come from [python-scanners]; standalone binaries are listed with
their install commands in docs/scanners.md.
Adding a new scanner
The bar is high. Each new scanner must:
- Have a documented JSON or SARIF output mode.
- Satisfy the licence criteria below.
- Cover a category not already well-covered, OR materially raise precision
in an existing category. Where two tools answer the same question, the
better one joins the floor and the other becomes opt-in with the reason it
survived at all — see
src/secure_code_audit/scanners/floor.py.
Licence criteria
This rule used to read "MIT/Apache-2.0/BSD licensed (no GPL — license-surface contamination)". That was inherited from library-consuming projects and does not describe this one, which links to no scanner, vendors no scanner source, and distributes no scanner binary. Two shipped tools violated it while causing no actual problem, which is how a rule teaches people to ignore rules. It is replaced by three criteria keyed to mechanism rather than licence name.
- Any OSI-approved licence is acceptable for a tool we invoke as a
subprocess and never distribute. Copyleft reaches a combined work through
linking, vendoring or bundling. Separate processes exchanging arguments and
JSON are separate programs, and running
hadolintno more makes this tool GPL than runninggitdoes. Linking, vendoring or bundling still requires a permissive licence — and if this project ever ships an image with scanners baked in, that image carries their obligations. - AGPL tools are optional and off by default. Not a legal judgement: AGPL §13 can create a source-offer obligation for anyone running this as a hosted service, and that is their decision to make rather than one we make silently on their behalf. Plenty of adopters also exclude AGPL by policy.
- Rule content and data are licensed separately from engines, and must be checked separately. Semgrep is the live example — the engine is LGPL-2.1, but Semgrep-maintained registry rules have been under the Semgrep Rules Licence since December 2024, restricted to internal, non-competing, non-SaaS use. An engine's licence tells you nothing about the rules it fetches, and criterion 1 would have waved this through.
Record the licence of any new tool in floor.py alongside its rationale. See
D6.
Steps:
- Subclass
secure_code_audit.scanners.base.Scannerin a new file undersrc/secure_code_audit/scanners/. - Add rule-id → standards mappings to
src/secure_code_audit/standards.py. - Register in
src/secure_code_audit/scanners/__init__.py::SCANNERS. - Add fixtures in
tests/fixtures/<scanner>/with at least one HIGH true- positive, one false-positive that should be suppressible, and one parse- failure case. - Document the scanner in
docs/scanners.md.
Adding a built-in rule
Built-in rules are deliberately small + high-precision. Each new rule:
- Targets a single CWE id.
- Has ≥90% precision on a real codebase you can name.
- Ships with a true-positive fixture and a false-positive fixture.
- Has the CWE / OWASP / ASVS / SSDF mapping populated in
standards.py.
Code style
- Python 3.10+.
rufffor lint + formatting (seepyproject.toml).- Type hints required on public functions.
- Dataclasses for value objects; no plain dicts in the public API.
Tests
pytest tests/ --cov=secure_code_audit --cov-fail-under=85- Unit tests for everything except subprocess-shelling-out (mock those).
- Integration tests for the SARIF emit/ingest roundtrip.
- Regression tests for scoring drift (
tests/integration/test_scoring_drift.py). - A new adapter needs a real captured output fixture, not only a mock.
Run the tool against a deliberately-vulnerable tree, commit the output under
tests/fixtures/scanner-output/, rewrite local paths, and edit nothing else. Mock output tests the parser against what you believe the format is; that belief has already been wrong twice (D14). If the tool cannot be installed, add it toSCANNERS_WITHOUT_A_REAL_CAPTUREwith the reason — a declared gap is acceptable, an undeclared one is not, and a test enforces the difference.
Commit messages
Conventional Commits:
- feat(scanner): add trivy adapter
- fix(scoring): clamp normalized subtotal at 0
- docs(standards): refresh OWASP Top 10 anchor to 2024 cycle
Pull requests
- Keep PRs bounded — one concern per PR.
- Cite the standard / source for any new rule mapping.
- Update
CHANGELOG.mdunder## Unreleased.
Releasing
In the release commit, alongside the version bump:
- Regenerate the published example report. Audit this repository at the new
version, then render the Markdown report with
python tools/render_example_report.py secure-code-report.md, which writesdocs/example-report.html. A test fails when the page does not state the current version. - Re-render the docs site with
python tools/render_docs.py.
Security disclosures
See SECURITY.md. Do not open public issues for
vulnerabilities in this tool.
License
Contributions are accepted under the MIT license. By submitting a PR you agree your contribution may be distributed under MIT.