secure-code-agent 0.2.0__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. {secure_code_agent-0.2.0 → secure_code_agent-0.4.0}/PKG-INFO +118 -19
  2. secure_code_agent-0.2.0/src/secure_code_agent.egg-info/PKG-INFO → secure_code_agent-0.4.0/README.md +104 -51
  3. {secure_code_agent-0.2.0 → secure_code_agent-0.4.0}/pyproject.toml +43 -4
  4. secure_code_agent-0.2.0/README.md → secure_code_agent-0.4.0/src/secure_code_agent.egg-info/PKG-INFO +150 -17
  5. {secure_code_agent-0.2.0 → secure_code_agent-0.4.0}/src/secure_code_agent.egg-info/SOURCES.txt +7 -0
  6. secure_code_agent-0.4.0/src/secure_code_agent.egg-info/requires.txt +17 -0
  7. {secure_code_agent-0.2.0 → secure_code_agent-0.4.0}/src/secure_code_audit/__init__.py +1 -1
  8. {secure_code_agent-0.2.0 → secure_code_agent-0.4.0}/src/secure_code_audit/baseline.py +37 -28
  9. secure_code_agent-0.4.0/src/secure_code_audit/cli.py +632 -0
  10. secure_code_agent-0.4.0/src/secure_code_audit/config.py +348 -0
  11. secure_code_agent-0.4.0/src/secure_code_audit/data/semgrep-offline.yaml +396 -0
  12. {secure_code_agent-0.2.0 → secure_code_agent-0.4.0}/src/secure_code_audit/findings.py +66 -66
  13. {secure_code_agent-0.2.0 → secure_code_agent-0.4.0}/src/secure_code_audit/git_tools.py +3 -20
  14. {secure_code_agent-0.2.0 → secure_code_agent-0.4.0}/src/secure_code_audit/instructions.py +17 -18
  15. {secure_code_agent-0.2.0 → secure_code_agent-0.4.0}/src/secure_code_audit/remediation.py +2 -2
  16. secure_code_agent-0.4.0/src/secure_code_audit/renderers.py +403 -0
  17. secure_code_agent-0.4.0/src/secure_code_audit/ruleset.py +97 -0
  18. secure_code_agent-0.4.0/src/secure_code_audit/sarif.py +408 -0
  19. secure_code_agent-0.4.0/src/secure_code_audit/scanner_status.py +197 -0
  20. secure_code_agent-0.4.0/src/secure_code_audit/scanners/__init__.py +53 -0
  21. secure_code_agent-0.4.0/src/secure_code_audit/scanners/bandit_scanner.py +98 -0
  22. secure_code_agent-0.4.0/src/secure_code_audit/scanners/base.py +376 -0
  23. {secure_code_agent-0.2.0 → secure_code_agent-0.4.0}/src/secure_code_audit/scanners/builtin_rules.py +25 -20
  24. {secure_code_agent-0.2.0 → secure_code_agent-0.4.0}/src/secure_code_audit/scanners/checkov_scanner.py +29 -23
  25. secure_code_agent-0.4.0/src/secure_code_audit/scanners/floor.py +282 -0
  26. secure_code_agent-0.4.0/src/secure_code_audit/scanners/gitleaks_scanner.py +103 -0
  27. secure_code_agent-0.4.0/src/secure_code_audit/scanners/gosec_scanner.py +132 -0
  28. {secure_code_agent-0.2.0 → secure_code_agent-0.4.0}/src/secure_code_audit/scanners/hadolint_scanner.py +29 -32
  29. secure_code_agent-0.4.0/src/secure_code_audit/scanners/njsscan_scanner.py +108 -0
  30. secure_code_agent-0.4.0/src/secure_code_audit/scanners/npm_audit_scanner.py +130 -0
  31. {secure_code_agent-0.2.0 → secure_code_agent-0.4.0}/src/secure_code_audit/scanners/osv_scanner.py +44 -36
  32. secure_code_agent-0.4.0/src/secure_code_audit/scanners/pip_audit_scanner.py +216 -0
  33. secure_code_agent-0.4.0/src/secure_code_audit/scanners/rubocop_scanner.py +118 -0
  34. {secure_code_agent-0.2.0 → secure_code_agent-0.4.0}/src/secure_code_audit/scanners/scorecard_scanner.py +58 -46
  35. secure_code_agent-0.4.0/src/secure_code_audit/scanners/semgrep_scanner.py +192 -0
  36. {secure_code_agent-0.2.0 → secure_code_agent-0.4.0}/src/secure_code_audit/scanners/trivy_scanner.py +28 -21
  37. {secure_code_agent-0.2.0 → secure_code_agent-0.4.0}/src/secure_code_audit/scanners/trufflehog_scanner.py +43 -18
  38. secure_code_agent-0.4.0/src/secure_code_audit/scoring.py +421 -0
  39. {secure_code_agent-0.2.0 → secure_code_agent-0.4.0}/src/secure_code_audit/standards.py +279 -147
  40. secure_code_agent-0.4.0/src/secure_code_audit/suppressions.py +246 -0
  41. secure_code_agent-0.2.0/src/secure_code_agent.egg-info/requires.txt +0 -5
  42. secure_code_agent-0.2.0/src/secure_code_audit/cli.py +0 -258
  43. secure_code_agent-0.2.0/src/secure_code_audit/config.py +0 -115
  44. secure_code_agent-0.2.0/src/secure_code_audit/renderers.py +0 -253
  45. secure_code_agent-0.2.0/src/secure_code_audit/sarif.py +0 -221
  46. secure_code_agent-0.2.0/src/secure_code_audit/scanners/__init__.py +0 -50
  47. secure_code_agent-0.2.0/src/secure_code_audit/scanners/bandit_scanner.py +0 -83
  48. secure_code_agent-0.2.0/src/secure_code_audit/scanners/base.py +0 -194
  49. secure_code_agent-0.2.0/src/secure_code_audit/scanners/gitleaks_scanner.py +0 -86
  50. secure_code_agent-0.2.0/src/secure_code_audit/scanners/npm_audit_scanner.py +0 -101
  51. secure_code_agent-0.2.0/src/secure_code_audit/scanners/pip_audit_scanner.py +0 -83
  52. secure_code_agent-0.2.0/src/secure_code_audit/scanners/semgrep_scanner.py +0 -119
  53. secure_code_agent-0.2.0/src/secure_code_audit/scoring.py +0 -280
  54. secure_code_agent-0.2.0/src/secure_code_audit/suppressions.py +0 -175
  55. {secure_code_agent-0.2.0 → secure_code_agent-0.4.0}/LICENSE +0 -0
  56. {secure_code_agent-0.2.0 → secure_code_agent-0.4.0}/setup.cfg +0 -0
  57. {secure_code_agent-0.2.0 → secure_code_agent-0.4.0}/src/secure_code_agent.egg-info/dependency_links.txt +0 -0
  58. {secure_code_agent-0.2.0 → secure_code_agent-0.4.0}/src/secure_code_agent.egg-info/entry_points.txt +0 -0
  59. {secure_code_agent-0.2.0 → secure_code_agent-0.4.0}/src/secure_code_agent.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: secure-code-agent
3
- Version: 0.2.0
3
+ Version: 0.4.0
4
4
  Summary: Deterministic security gate + bounded AI remediation prompt generator. NIST SSDF / OWASP ASVS / CWE Top 25 anchored.
5
5
  Author: Marshall Guillory
6
6
  License: MIT
@@ -21,15 +21,27 @@ Classifier: Programming Language :: Python :: 3
21
21
  Classifier: Programming Language :: Python :: 3.10
22
22
  Classifier: Programming Language :: Python :: 3.11
23
23
  Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Programming Language :: Python :: 3.13
25
+ Classifier: Programming Language :: Python :: 3.14
24
26
  Classifier: Topic :: Security
25
27
  Classifier: Topic :: Software Development :: Quality Assurance
26
28
  Requires-Python: >=3.10
27
29
  Description-Content-Type: text/markdown
28
30
  License-File: LICENSE
31
+ Requires-Dist: PyYAML<7,>=6.0.2
32
+ Provides-Extra: required-scanners
33
+ Requires-Dist: bandit==1.9.4; extra == "required-scanners"
34
+ Requires-Dist: pip-audit==2.10.1; extra == "required-scanners"
35
+ Requires-Dist: njsscan==1.0.0; extra == "required-scanners"
36
+ Provides-Extra: python-scanners
37
+ Requires-Dist: secure-code-agent[required-scanners]; extra == "python-scanners"
38
+ Requires-Dist: semgrep<2,>=1.172; extra == "python-scanners"
39
+ Requires-Dist: checkov<4,>=3.3; extra == "python-scanners"
29
40
  Provides-Extra: dev
41
+ Requires-Dist: secure-code-agent[required-scanners]; extra == "dev"
30
42
  Requires-Dist: pytest>=8.0; extra == "dev"
31
43
  Requires-Dist: pytest-cov>=4.0; extra == "dev"
32
- Requires-Dist: ruff>=0.5; extra == "dev"
44
+ Requires-Dist: ruff==0.16.2; extra == "dev"
33
45
  Dynamic: license-file
34
46
 
35
47
  # secure-code-agent
@@ -38,7 +50,7 @@ Dynamic: license-file
38
50
  > Anchored to NIST SSDF · OWASP ASVS · OWASP Top 10 · MITRE CWE Top 25 · OpenSSF Scorecard · SARIF 2.1.0.
39
51
 
40
52
  ```bash
41
- pip install secure-code-agent
53
+ pip install 'secure-code-agent[required-scanners]'
42
54
 
43
55
  secure-code-agent --fail-on-gate \
44
56
  --output secure-code-report.md \
@@ -115,18 +127,21 @@ Hand the prompt to Claude Code, Codex, Cursor, Copilot, or any agent. The agent
115
127
 
116
128
  ## Standards anchored, not invented
117
129
 
118
- Every finding maps to five public standards. Operators see *which standard is failing*, not just *which scanner shouted*.
130
+ Known rules map to fields from five public standards. Unmapped and scanner-control
131
+ findings retain null standards fields rather than receiving invented mappings.
119
132
 
120
133
  | Source | What we use it for |
121
134
  |----------------------------------------------|----------------------------------------------------------|
122
135
  | [NIST SSDF SP 800-218](https://csrc.nist.gov/pubs/sp/800/218/final) | Process practice id (e.g. `PW.5.1`) |
123
136
  | [OWASP Top 10 (2021)](https://owasp.org/Top10/2021/) | Risk bucket (e.g. `A03:2021-Injection`) |
124
137
  | [OWASP ASVS 5.0](https://github.com/OWASP/ASVS) | Verification requirement (e.g. `V5.3`) |
125
- | [MITRE CWE Top 25 (2025)](https://cwe.mitre.org/top25/) | Canonical weakness id the dedupe key |
138
+ | [MITRE CWE Top 25 (2025)](https://cwe.mitre.org/top25/) | Canonical weakness id used in stable fingerprints |
126
139
  | [OpenSSF Scorecard](https://openssf.org/projects/scorecard/) | Repo + supply-chain hygiene |
127
140
  | [SARIF 2.1.0](https://www.oasis-open.org/standard/sarif-v2-1-0/) | Output format (and external scanner ingest) |
128
141
 
129
- When Semgrep, CodeQL, and Bandit fire on the same SQL-injection sink with three different rule ids, they all map to `CWE-89` and the scorer counts **one** underlying weakness. Not three.
142
+ When scanners map a finding to `CWE-89`, the canonical CWE participates in its
143
+ stable fingerprint and baseline identity. Cross-scanner findings are not yet
144
+ collapsed before scoring; reports preserve the original scanner evidence.
130
145
 
131
146
  ## Architecture (orchestrator, not engine)
132
147
 
@@ -159,7 +174,9 @@ When Semgrep, CodeQL, and Bandit fire on the same SQL-injection sink with three
159
174
  ├── Hadolint (Dockerfile lint)
160
175
  ├── OSV-Scanner (multi-ecosystem SCA via osv.dev)
161
176
  ├── OpenSSF Scorecard (repo hygiene + supply chain)
162
- ├── eslint-plugin-security (JS/TS SAST) [v0.3]
177
+ ├── njsscan (JS/TS SAST, offline, no Node needed)
178
+ ├── RuboCop (Ruby security cops, --only Security)
179
+ ├── gosec (Go SAST — opt-in; needs the Go toolchain)
163
180
  ├── CodeQL SARIF (ingest GitHub-hosted analysis)
164
181
  └── Built-in regex rules (high-confidence, low-FP)
165
182
  ```
@@ -203,7 +220,67 @@ Scoring math + worked examples in [`docs/scoring.md`](docs/scoring.md).
203
220
 
204
221
  Any tripped gate is a nonzero exit. Compose freely.
205
222
 
206
- ## Suppressions you can't game
223
+ `require_scanners` is a coverage gate, not a vulnerability gate. A required
224
+ scanner must resolve and complete successfully. Missing executables, timeouts,
225
+ invalid output, unsupported inputs, or excluding the scanner with CLI filters
226
+ fail coverage. Optional scanner failures produce `PARTIAL` coverage without
227
+ turning a clean finding set into a false comprehensive result. Markdown and
228
+ JSON reports record each scanner's outcome, resolved command, and version.
229
+ When `require_scanners` is present it must name at least one scanner; an empty
230
+ list is rejected instead of silently removing the structural coverage gate.
231
+
232
+ External scanners are not bundled, and the agent never installs one for you —
233
+ a gate that fetches and runs binaries to satisfy its own coverage requirement
234
+ is the supply-chain risk it is supposed to catch. Resolution order is an
235
+ explicit `scanners.<name>.command`, the active `PATH`, then `python -m <module>`
236
+ for supported Python scanners. Relative executable paths resolve from the scan
237
+ target and are executed with `shell=False`.
238
+
239
+ `secure-code-agent --preflight` reports which enabled scanners resolve on this
240
+ host, with versions and the install command for anything missing, and exits
241
+ nonzero when a required scanner is unavailable — so a missing toolchain costs a
242
+ second instead of a full audit. Bandit and pip-audit install as
243
+ `secure-code-agent[required-scanners]`; Semgrep and Checkov add
244
+ `[python-scanners]`. The remaining scanners are standalone binaries that cannot
245
+ come from PyPI: install them with your package manager, or run their pinned
246
+ upstream CI action and feed us the SARIF, which counts as coverage:
247
+
248
+ ```bash
249
+ secure-code-agent --fail-on-gate --sarif-import trivy.sarif
250
+ ```
251
+
252
+ An import satisfies `require_scanners` for the tool that produced it. An
253
+ unreadable, malformed, or run-less import fails the gate rather than ingesting
254
+ nothing quietly, an import reporting its own `executionSuccessful: false` is
255
+ recorded as failed, and when a scanner reports both locally and by import the
256
+ worse outcome wins. See [`docs/scanners.md`](docs/scanners.md) for the full
257
+ install matrix.
258
+
259
+ ```json
260
+ {
261
+ "scanners": {
262
+ "bandit": {
263
+ "enabled": true,
264
+ "command": [".audit-tools/bin/python", "-m", "bandit"]
265
+ },
266
+ "pip_audit": {
267
+ "enabled": true,
268
+ "command": [".audit-tools/bin/python", "-m", "pip_audit"],
269
+ "mode": "project",
270
+ "inputs": ["engine/pyproject.toml"]
271
+ }
272
+ }
273
+ }
274
+ ```
275
+
276
+ The tool never downloads a scanner during an audit. Install and pin scanner
277
+ versions in the audit environment or CI image.
278
+
279
+ This repository's own CI audits `requirements-audit.txt`, which pins the
280
+ minimum supported runtime dependency version. Project mode remains available
281
+ for repositories whose `pyproject.toml` is their authoritative audit input.
282
+
283
+ ## Time-bounded suppressions
207
284
 
208
285
  `.scignore.yaml` — every suppression requires a `reason` AND an `expires` date (max 365 days). Past-expiry suppressions become CRITICAL findings on their own. You can't ship `reason: "we'll fix it later"` forever.
209
286
 
@@ -213,6 +290,10 @@ Any tripped gate is a nonzero exit. Compose freely.
213
290
  reason: "Slated for rewrite Q3 2026 — gated by initiative INV-44."
214
291
  expires: "2026-09-30"
215
292
 
293
+ fingerprint: 0aaa689f8a967d8c # optional: pin to ONE finding (16 hex, from the report)
294
+
295
+ line: 18 # optional: with fingerprint, pins the exact location
296
+
216
297
  - rule_id: "B101"
217
298
  paths: ["tests/"]
218
299
  reason: "assert statements are legitimate in test code."
@@ -228,15 +309,19 @@ Wildcard rule (`rule_id: "*"`) requires a `file` or `paths` scope — you cannot
228
309
  - Findings present in baseline → **acknowledged**; don't trip `fail_on_new`.
229
310
  - Findings missing from baseline → **new**; trip the gate.
230
311
 
231
- Bumping a CRITICAL or HIGH finding into the baseline requires `--bump-baseline --i-acknowledge-risk`. The bump records the operator's git `user.email` per fingerprint so PR review can see who acknowledged what.
312
+ `--bump-baseline` rewrites the baseline from the current findings. The file is
313
+ plain JSON and must be reviewed like any other security-policy change. This
314
+ release does not implement an interactive acknowledgment. Baseline entries
315
+ record the best-effort local Git email, while repository review policy remains
316
+ the approval boundary.
232
317
 
233
318
  This lets legacy repos adopt the gate without a 200-finding day-one cleanup.
234
319
 
235
320
  ## Quickstart
236
321
 
237
322
  ```bash
238
- # Install
239
- pip install secure-code-agent
323
+ # Install the orchestrator with its pinned default Bandit + pip-audit toolchain
324
+ pip install 'secure-code-agent[required-scanners]'
240
325
 
241
326
  # Initialize agent standards files for your AI coding tools
242
327
  secure-code-agent --init-agent-standards \
@@ -251,14 +336,18 @@ secure-code-agent --config secure-code-agent.json \
251
336
  --comment-output secure-code-pr-comment.md \
252
337
  --prompt-output secure-code-remediation-prompt.md
253
338
 
254
- # Audit only changed files since main
255
- secure-code-agent --changed-only main...HEAD --fail-on-new
256
-
257
339
  # Ingest external scanner SARIF (CodeQL, Snyk, Trivy, etc.)
258
340
  secure-code-agent --sarif-import codeql-results.sarif \
259
341
  --sarif-import snyk-results.sarif
260
342
  ```
261
343
 
344
+ `--changed-only` is reserved but not yet safely implemented. Passing it fails
345
+ with exit code 2 so a caller cannot accidentally treat an unscoped audit as a
346
+ changed-file audit.
347
+
348
+ The current orchestrator accepts one repository root per invocation. Multiple
349
+ positional roots fail with exit code 2 instead of silently ignoring coverage.
350
+
262
351
  ## Invokable skill / slash command
263
352
 
264
353
  For agents that support invokable skills, this repo ships a portable skill under [`skills/secure-code-agent/`](skills/secure-code-agent/). The `SKILL.md` body is the source of truth; per-host adapters live under `agents/` and `copilot/`.
@@ -272,14 +361,19 @@ For agents that support invokable skills, this repo ships a portable skill under
272
361
  ## GitHub Action
273
362
 
274
363
  ```yaml
275
- - uses: marshallguillory86/secure-code-agent@v0.1.0
364
+ - uses: marshallguillory86/secure-code-agent@v0.3.0
276
365
  with:
277
366
  config: secure-code-agent.json
278
- changed-only: main...HEAD
279
367
  fail-on-gate: true
280
368
  ```
281
369
 
282
- The action uploads SARIF to GitHub Code Scanning by default. See [`action.yml`](action.yml) and [`examples/github-actions/`](examples/github-actions/) for full workflows.
370
+ The action installs the exact source bundled with the referenced action plus
371
+ the pinned `required-scanners` extra (Bandit and pip-audit),
372
+ emits Markdown, JSON, SARIF, PR-comment, and remediation artifacts, and uploads
373
+ SARIF by default. The calling workflow must grant `security-events: write` for
374
+ SARIF upload. Pin production usage to a full commit SHA; the version tag above
375
+ is shown for readability. See [`action.yml`](action.yml) and
376
+ [`examples/github-actions/`](examples/github-actions/) for full workflows.
283
377
 
284
378
  ## What this is NOT
285
379
 
@@ -294,7 +388,7 @@ The action uploads SARIF to GitHub Code Scanning by default. See [`action.yml`](
294
388
  1. **Deterministic first, AI optional.** The audit never calls an LLM by default. The remediation prompt is a generated artifact you choose to hand to an agent.
295
389
  2. **Bounded scope.** The remediation prompt explicitly forbids touching crypto, auth, validation, logging, and tests.
296
390
  3. **Standards-anchored.** Five public standards (NIST / OWASP-x3 / CWE) — no invented taxonomy.
297
- 4. **CWE-deduped scoring.** One underlying weakness = one finding, regardless of how many scanners found it.
391
+ 4. **Stable finding identity.** CWE, normalized path, and normalized evidence form the baseline fingerprint. Cross-scanner score deduplication remains future work.
298
392
  5. **No vendor lock-in.** Markdown, JSON, SARIF, plain files. Pipe anywhere.
299
393
  6. **CI-first, local-first.** Same binary in pre-commit, local CI, GitHub Actions, GitLab, Buildkite.
300
394
 
@@ -302,7 +396,11 @@ Full design philosophy in [`docs/design.md`](docs/design.md).
302
396
 
303
397
  ## Documentation
304
398
 
399
+ - [`docs/product-intent.md`](docs/product-intent.md) — Why this exists, who it serves, what it refuses to become
400
+ - [`docs/decisions.md`](docs/decisions.md) — Decision register: rulings the code alone cannot answer
305
401
  - [`docs/design.md`](docs/design.md) — Architecture + non-goals + scanner protocol
402
+ - [`docs/architecture.md`](docs/architecture.md) — Audit of the system as built + remediation sequence
403
+ - [`docs/release-blockers.md`](docs/release-blockers.md) — Open v0.3.0 release blockers (do not tag until closed)
306
404
  - [`docs/standards.md`](docs/standards.md) — NIST SSDF / OWASP / CWE / Scorecard / SARIF citations
307
405
  - [`docs/scoring.md`](docs/scoring.md) — Weighting model + worked examples
308
406
  - [`docs/scanners.md`](docs/scanners.md) — Per-scanner integrations + caveats
@@ -312,7 +410,8 @@ Full design philosophy in [`docs/design.md`](docs/design.md).
312
410
  ## Versioning
313
411
 
314
412
  - **Semver.** v0.x is pre-1.0 — the config schema may evolve. v1.0 locks it.
315
- - **SARIF 2.1.0** output is pinned and validated against the OASIS schema in CI.
413
+ - **SARIF 2.1.0-shaped** output is structurally unit-tested and round-tripped;
414
+ full OASIS schema validation is not yet part of CI.
316
415
 
317
416
  ## Get in touch
318
417
 
@@ -1,44 +1,10 @@
1
- Metadata-Version: 2.4
2
- Name: secure-code-agent
3
- Version: 0.2.0
4
- Summary: Deterministic security gate + bounded AI remediation prompt generator. NIST SSDF / OWASP ASVS / CWE Top 25 anchored.
5
- Author: Marshall Guillory
6
- License: MIT
7
- Project-URL: Homepage, https://github.com/marshallguillory86/secure-code-agent
8
- Project-URL: Repository, https://github.com/marshallguillory86/secure-code-agent
9
- Project-URL: Documentation, https://github.com/marshallguillory86/secure-code-agent/tree/main/docs
10
- Project-URL: Issues, https://github.com/marshallguillory86/secure-code-agent/issues
11
- Project-URL: Changelog, https://github.com/marshallguillory86/secure-code-agent/blob/main/CHANGELOG.md
12
- Keywords: security,appsec,sast,sca,secret-scanning,owasp,asvs,cwe,nist-ssdf,sarif,ai-code-review,ai-guardrail,remediation-prompt,ci,audit
13
- Classifier: Development Status :: 4 - Beta
14
- Classifier: Intended Audience :: Developers
15
- Classifier: Intended Audience :: Information Technology
16
- Classifier: License :: OSI Approved :: MIT License
17
- Classifier: Operating System :: POSIX
18
- Classifier: Operating System :: MacOS
19
- Classifier: Operating System :: POSIX :: Linux
20
- Classifier: Programming Language :: Python :: 3
21
- Classifier: Programming Language :: Python :: 3.10
22
- Classifier: Programming Language :: Python :: 3.11
23
- Classifier: Programming Language :: Python :: 3.12
24
- Classifier: Topic :: Security
25
- Classifier: Topic :: Software Development :: Quality Assurance
26
- Requires-Python: >=3.10
27
- Description-Content-Type: text/markdown
28
- License-File: LICENSE
29
- Provides-Extra: dev
30
- Requires-Dist: pytest>=8.0; extra == "dev"
31
- Requires-Dist: pytest-cov>=4.0; extra == "dev"
32
- Requires-Dist: ruff>=0.5; extra == "dev"
33
- Dynamic: license-file
34
-
35
1
  # secure-code-agent
36
2
 
37
3
  > **Deterministic security gate + bounded AI remediation prompts for repos with AI coding agents in the loop.**
38
4
  > Anchored to NIST SSDF · OWASP ASVS · OWASP Top 10 · MITRE CWE Top 25 · OpenSSF Scorecard · SARIF 2.1.0.
39
5
 
40
6
  ```bash
41
- pip install secure-code-agent
7
+ pip install 'secure-code-agent[required-scanners]'
42
8
 
43
9
  secure-code-agent --fail-on-gate \
44
10
  --output secure-code-report.md \
@@ -115,18 +81,21 @@ Hand the prompt to Claude Code, Codex, Cursor, Copilot, or any agent. The agent
115
81
 
116
82
  ## Standards anchored, not invented
117
83
 
118
- Every finding maps to five public standards. Operators see *which standard is failing*, not just *which scanner shouted*.
84
+ Known rules map to fields from five public standards. Unmapped and scanner-control
85
+ findings retain null standards fields rather than receiving invented mappings.
119
86
 
120
87
  | Source | What we use it for |
121
88
  |----------------------------------------------|----------------------------------------------------------|
122
89
  | [NIST SSDF SP 800-218](https://csrc.nist.gov/pubs/sp/800/218/final) | Process practice id (e.g. `PW.5.1`) |
123
90
  | [OWASP Top 10 (2021)](https://owasp.org/Top10/2021/) | Risk bucket (e.g. `A03:2021-Injection`) |
124
91
  | [OWASP ASVS 5.0](https://github.com/OWASP/ASVS) | Verification requirement (e.g. `V5.3`) |
125
- | [MITRE CWE Top 25 (2025)](https://cwe.mitre.org/top25/) | Canonical weakness id the dedupe key |
92
+ | [MITRE CWE Top 25 (2025)](https://cwe.mitre.org/top25/) | Canonical weakness id used in stable fingerprints |
126
93
  | [OpenSSF Scorecard](https://openssf.org/projects/scorecard/) | Repo + supply-chain hygiene |
127
94
  | [SARIF 2.1.0](https://www.oasis-open.org/standard/sarif-v2-1-0/) | Output format (and external scanner ingest) |
128
95
 
129
- When Semgrep, CodeQL, and Bandit fire on the same SQL-injection sink with three different rule ids, they all map to `CWE-89` and the scorer counts **one** underlying weakness. Not three.
96
+ When scanners map a finding to `CWE-89`, the canonical CWE participates in its
97
+ stable fingerprint and baseline identity. Cross-scanner findings are not yet
98
+ collapsed before scoring; reports preserve the original scanner evidence.
130
99
 
131
100
  ## Architecture (orchestrator, not engine)
132
101
 
@@ -159,7 +128,9 @@ When Semgrep, CodeQL, and Bandit fire on the same SQL-injection sink with three
159
128
  ├── Hadolint (Dockerfile lint)
160
129
  ├── OSV-Scanner (multi-ecosystem SCA via osv.dev)
161
130
  ├── OpenSSF Scorecard (repo hygiene + supply chain)
162
- ├── eslint-plugin-security (JS/TS SAST) [v0.3]
131
+ ├── njsscan (JS/TS SAST, offline, no Node needed)
132
+ ├── RuboCop (Ruby security cops, --only Security)
133
+ ├── gosec (Go SAST — opt-in; needs the Go toolchain)
163
134
  ├── CodeQL SARIF (ingest GitHub-hosted analysis)
164
135
  └── Built-in regex rules (high-confidence, low-FP)
165
136
  ```
@@ -203,7 +174,67 @@ Scoring math + worked examples in [`docs/scoring.md`](docs/scoring.md).
203
174
 
204
175
  Any tripped gate is a nonzero exit. Compose freely.
205
176
 
206
- ## Suppressions you can't game
177
+ `require_scanners` is a coverage gate, not a vulnerability gate. A required
178
+ scanner must resolve and complete successfully. Missing executables, timeouts,
179
+ invalid output, unsupported inputs, or excluding the scanner with CLI filters
180
+ fail coverage. Optional scanner failures produce `PARTIAL` coverage without
181
+ turning a clean finding set into a false comprehensive result. Markdown and
182
+ JSON reports record each scanner's outcome, resolved command, and version.
183
+ When `require_scanners` is present it must name at least one scanner; an empty
184
+ list is rejected instead of silently removing the structural coverage gate.
185
+
186
+ External scanners are not bundled, and the agent never installs one for you —
187
+ a gate that fetches and runs binaries to satisfy its own coverage requirement
188
+ is the supply-chain risk it is supposed to catch. Resolution order is an
189
+ explicit `scanners.<name>.command`, the active `PATH`, then `python -m <module>`
190
+ for supported Python scanners. Relative executable paths resolve from the scan
191
+ target and are executed with `shell=False`.
192
+
193
+ `secure-code-agent --preflight` reports which enabled scanners resolve on this
194
+ host, with versions and the install command for anything missing, and exits
195
+ nonzero when a required scanner is unavailable — so a missing toolchain costs a
196
+ second instead of a full audit. Bandit and pip-audit install as
197
+ `secure-code-agent[required-scanners]`; Semgrep and Checkov add
198
+ `[python-scanners]`. The remaining scanners are standalone binaries that cannot
199
+ come from PyPI: install them with your package manager, or run their pinned
200
+ upstream CI action and feed us the SARIF, which counts as coverage:
201
+
202
+ ```bash
203
+ secure-code-agent --fail-on-gate --sarif-import trivy.sarif
204
+ ```
205
+
206
+ An import satisfies `require_scanners` for the tool that produced it. An
207
+ unreadable, malformed, or run-less import fails the gate rather than ingesting
208
+ nothing quietly, an import reporting its own `executionSuccessful: false` is
209
+ recorded as failed, and when a scanner reports both locally and by import the
210
+ worse outcome wins. See [`docs/scanners.md`](docs/scanners.md) for the full
211
+ install matrix.
212
+
213
+ ```json
214
+ {
215
+ "scanners": {
216
+ "bandit": {
217
+ "enabled": true,
218
+ "command": [".audit-tools/bin/python", "-m", "bandit"]
219
+ },
220
+ "pip_audit": {
221
+ "enabled": true,
222
+ "command": [".audit-tools/bin/python", "-m", "pip_audit"],
223
+ "mode": "project",
224
+ "inputs": ["engine/pyproject.toml"]
225
+ }
226
+ }
227
+ }
228
+ ```
229
+
230
+ The tool never downloads a scanner during an audit. Install and pin scanner
231
+ versions in the audit environment or CI image.
232
+
233
+ This repository's own CI audits `requirements-audit.txt`, which pins the
234
+ minimum supported runtime dependency version. Project mode remains available
235
+ for repositories whose `pyproject.toml` is their authoritative audit input.
236
+
237
+ ## Time-bounded suppressions
207
238
 
208
239
  `.scignore.yaml` — every suppression requires a `reason` AND an `expires` date (max 365 days). Past-expiry suppressions become CRITICAL findings on their own. You can't ship `reason: "we'll fix it later"` forever.
209
240
 
@@ -213,6 +244,10 @@ Any tripped gate is a nonzero exit. Compose freely.
213
244
  reason: "Slated for rewrite Q3 2026 — gated by initiative INV-44."
214
245
  expires: "2026-09-30"
215
246
 
247
+ fingerprint: 0aaa689f8a967d8c # optional: pin to ONE finding (16 hex, from the report)
248
+
249
+ line: 18 # optional: with fingerprint, pins the exact location
250
+
216
251
  - rule_id: "B101"
217
252
  paths: ["tests/"]
218
253
  reason: "assert statements are legitimate in test code."
@@ -228,15 +263,19 @@ Wildcard rule (`rule_id: "*"`) requires a `file` or `paths` scope — you cannot
228
263
  - Findings present in baseline → **acknowledged**; don't trip `fail_on_new`.
229
264
  - Findings missing from baseline → **new**; trip the gate.
230
265
 
231
- Bumping a CRITICAL or HIGH finding into the baseline requires `--bump-baseline --i-acknowledge-risk`. The bump records the operator's git `user.email` per fingerprint so PR review can see who acknowledged what.
266
+ `--bump-baseline` rewrites the baseline from the current findings. The file is
267
+ plain JSON and must be reviewed like any other security-policy change. This
268
+ release does not implement an interactive acknowledgment. Baseline entries
269
+ record the best-effort local Git email, while repository review policy remains
270
+ the approval boundary.
232
271
 
233
272
  This lets legacy repos adopt the gate without a 200-finding day-one cleanup.
234
273
 
235
274
  ## Quickstart
236
275
 
237
276
  ```bash
238
- # Install
239
- pip install secure-code-agent
277
+ # Install the orchestrator with its pinned default Bandit + pip-audit toolchain
278
+ pip install 'secure-code-agent[required-scanners]'
240
279
 
241
280
  # Initialize agent standards files for your AI coding tools
242
281
  secure-code-agent --init-agent-standards \
@@ -251,14 +290,18 @@ secure-code-agent --config secure-code-agent.json \
251
290
  --comment-output secure-code-pr-comment.md \
252
291
  --prompt-output secure-code-remediation-prompt.md
253
292
 
254
- # Audit only changed files since main
255
- secure-code-agent --changed-only main...HEAD --fail-on-new
256
-
257
293
  # Ingest external scanner SARIF (CodeQL, Snyk, Trivy, etc.)
258
294
  secure-code-agent --sarif-import codeql-results.sarif \
259
295
  --sarif-import snyk-results.sarif
260
296
  ```
261
297
 
298
+ `--changed-only` is reserved but not yet safely implemented. Passing it fails
299
+ with exit code 2 so a caller cannot accidentally treat an unscoped audit as a
300
+ changed-file audit.
301
+
302
+ The current orchestrator accepts one repository root per invocation. Multiple
303
+ positional roots fail with exit code 2 instead of silently ignoring coverage.
304
+
262
305
  ## Invokable skill / slash command
263
306
 
264
307
  For agents that support invokable skills, this repo ships a portable skill under [`skills/secure-code-agent/`](skills/secure-code-agent/). The `SKILL.md` body is the source of truth; per-host adapters live under `agents/` and `copilot/`.
@@ -272,14 +315,19 @@ For agents that support invokable skills, this repo ships a portable skill under
272
315
  ## GitHub Action
273
316
 
274
317
  ```yaml
275
- - uses: marshallguillory86/secure-code-agent@v0.1.0
318
+ - uses: marshallguillory86/secure-code-agent@v0.3.0
276
319
  with:
277
320
  config: secure-code-agent.json
278
- changed-only: main...HEAD
279
321
  fail-on-gate: true
280
322
  ```
281
323
 
282
- The action uploads SARIF to GitHub Code Scanning by default. See [`action.yml`](action.yml) and [`examples/github-actions/`](examples/github-actions/) for full workflows.
324
+ The action installs the exact source bundled with the referenced action plus
325
+ the pinned `required-scanners` extra (Bandit and pip-audit),
326
+ emits Markdown, JSON, SARIF, PR-comment, and remediation artifacts, and uploads
327
+ SARIF by default. The calling workflow must grant `security-events: write` for
328
+ SARIF upload. Pin production usage to a full commit SHA; the version tag above
329
+ is shown for readability. See [`action.yml`](action.yml) and
330
+ [`examples/github-actions/`](examples/github-actions/) for full workflows.
283
331
 
284
332
  ## What this is NOT
285
333
 
@@ -294,7 +342,7 @@ The action uploads SARIF to GitHub Code Scanning by default. See [`action.yml`](
294
342
  1. **Deterministic first, AI optional.** The audit never calls an LLM by default. The remediation prompt is a generated artifact you choose to hand to an agent.
295
343
  2. **Bounded scope.** The remediation prompt explicitly forbids touching crypto, auth, validation, logging, and tests.
296
344
  3. **Standards-anchored.** Five public standards (NIST / OWASP-x3 / CWE) — no invented taxonomy.
297
- 4. **CWE-deduped scoring.** One underlying weakness = one finding, regardless of how many scanners found it.
345
+ 4. **Stable finding identity.** CWE, normalized path, and normalized evidence form the baseline fingerprint. Cross-scanner score deduplication remains future work.
298
346
  5. **No vendor lock-in.** Markdown, JSON, SARIF, plain files. Pipe anywhere.
299
347
  6. **CI-first, local-first.** Same binary in pre-commit, local CI, GitHub Actions, GitLab, Buildkite.
300
348
 
@@ -302,7 +350,11 @@ Full design philosophy in [`docs/design.md`](docs/design.md).
302
350
 
303
351
  ## Documentation
304
352
 
353
+ - [`docs/product-intent.md`](docs/product-intent.md) — Why this exists, who it serves, what it refuses to become
354
+ - [`docs/decisions.md`](docs/decisions.md) — Decision register: rulings the code alone cannot answer
305
355
  - [`docs/design.md`](docs/design.md) — Architecture + non-goals + scanner protocol
356
+ - [`docs/architecture.md`](docs/architecture.md) — Audit of the system as built + remediation sequence
357
+ - [`docs/release-blockers.md`](docs/release-blockers.md) — Open v0.3.0 release blockers (do not tag until closed)
306
358
  - [`docs/standards.md`](docs/standards.md) — NIST SSDF / OWASP / CWE / Scorecard / SARIF citations
307
359
  - [`docs/scoring.md`](docs/scoring.md) — Weighting model + worked examples
308
360
  - [`docs/scanners.md`](docs/scanners.md) — Per-scanner integrations + caveats
@@ -312,7 +364,8 @@ Full design philosophy in [`docs/design.md`](docs/design.md).
312
364
  ## Versioning
313
365
 
314
366
  - **Semver.** v0.x is pre-1.0 — the config schema may evolve. v1.0 locks it.
315
- - **SARIF 2.1.0** output is pinned and validated against the OASIS schema in CI.
367
+ - **SARIF 2.1.0-shaped** output is structurally unit-tested and round-tripped;
368
+ full OASIS schema validation is not yet part of CI.
316
369
 
317
370
  ## Get in touch
318
371
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "secure-code-agent"
7
- version = "0.2.0"
7
+ version = "0.4.0"
8
8
  description = "Deterministic security gate + bounded AI remediation prompt generator. NIST SSDF / OWASP ASVS / CWE Top 25 anchored."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -30,19 +30,49 @@ classifiers = [
30
30
  "Programming Language :: Python :: 3.10",
31
31
  "Programming Language :: Python :: 3.11",
32
32
  "Programming Language :: Python :: 3.12",
33
+ "Programming Language :: Python :: 3.13",
34
+ "Programming Language :: Python :: 3.14",
33
35
  "Topic :: Security",
34
36
  "Topic :: Software Development :: Quality Assurance",
35
37
  ]
36
38
  dependencies = [
37
- # Stdlib-only at runtime. Scanners are invoked as subprocesses so
38
- # the tool has no hard dependency on their Python packages.
39
+ "PyYAML>=6.0.2,<7",
40
+ # Scanner packages remain external subprocess dependencies.
39
41
  ]
40
42
 
41
43
  [project.optional-dependencies]
44
+ # Scanners the default configuration lists in gates.require_scanners. Pinned
45
+ # exactly: a gate asserting "bandit completed" should mean a known Bandit
46
+ # completed, not whatever Bandit resolved that day.
47
+ required-scanners = [
48
+ "bandit==1.9.4",
49
+ "pip-audit==2.10.1",
50
+ # njsscan is in the floor (D12) and is a Python package carrying its own
51
+ # rules, so a JavaScript repository can be scanned on a host with no Node.
52
+ # Pinned for the same reason as the rest: the floor is a claim about what
53
+ # ran, and an unpinned ruleset makes that claim unreproducible.
54
+ "njsscan==1.0.0",
55
+ ]
56
+ # The remaining Python-packaged scanners. Bounded rather than pinned because
57
+ # they are optional; the coverage gate does not require them by default.
58
+ # Scanners distributed as standalone binaries or through another language's
59
+ # package manager (gitleaks, gosec, hadolint, npm, osv-scanner, rubocop,
60
+ # scorecard, trivy, trufflehog) cannot be installed from PyPI — see
61
+ # docs/scanners.md for their install commands and the SARIF-import route.
62
+ python-scanners = [
63
+ "secure-code-agent[required-scanners]",
64
+ "semgrep>=1.172,<2",
65
+ "checkov>=3.3,<4",
66
+ ]
67
+ # Includes required-scanners so a fresh checkout can run its own audit.
68
+ # Ruff is pinned exactly: `ruff format --check` is a CI gate, and an
69
+ # unpinned formatter turns every upstream release into a possible red build
70
+ # with no change in this repository. Same reasoning as required-scanners.
42
71
  dev = [
72
+ "secure-code-agent[required-scanners]",
43
73
  "pytest>=8.0",
44
74
  "pytest-cov>=4.0",
45
- "ruff>=0.5",
75
+ "ruff==0.16.2",
46
76
  ]
47
77
 
48
78
  [project.scripts]
@@ -65,6 +95,15 @@ secure_code_audit = ["data/*.json", "data/*.yaml"]
65
95
  [tool.ruff]
66
96
  line-length = 100
67
97
  target-version = "py310"
98
+ # Ruff 0.16 formats Python blocks embedded in Markdown. Our docs use those
99
+ # blocks illustratively — excerpted statements, aligned-comment schemas — and
100
+ # reformatting them damages the documentation without improving any code.
101
+ extend-exclude = [
102
+ "**/*.md",
103
+ # Scanner fixtures are deliberately vulnerable sample code. Linting them
104
+ # would demand we fix the very patterns the rules exist to detect.
105
+ "tests/fixtures/**",
106
+ ]
68
107
 
69
108
  [tool.ruff.lint]
70
109
  select = ["E", "F", "I", "B", "UP", "SIM", "C4"]