secure-code-agent 0.12.3__tar.gz → 0.12.4__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 (52) hide show
  1. {secure_code_agent-0.12.3/src/secure_code_agent.egg-info → secure_code_agent-0.12.4}/PKG-INFO +11 -3
  2. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/README.md +10 -2
  3. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4/src/secure_code_agent.egg-info}/PKG-INFO +11 -3
  4. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/__init__.py +1 -1
  5. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/cli.py +22 -0
  6. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/config.py +4 -0
  7. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/sarif.py +36 -5
  8. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/LICENSE +0 -0
  9. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/pyproject.toml +0 -0
  10. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/setup.cfg +0 -0
  11. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_agent.egg-info/SOURCES.txt +0 -0
  12. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_agent.egg-info/dependency_links.txt +0 -0
  13. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_agent.egg-info/entry_points.txt +0 -0
  14. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_agent.egg-info/requires.txt +0 -0
  15. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_agent.egg-info/top_level.txt +0 -0
  16. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/baseline.py +0 -0
  17. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/data/semgrep-offline.yaml +0 -0
  18. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/demo.py +0 -0
  19. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/findings.py +0 -0
  20. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/git_tools.py +0 -0
  21. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/history.py +0 -0
  22. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/instructions.py +0 -0
  23. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/mcp_server.py +0 -0
  24. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/pillar.py +0 -0
  25. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/practice.py +0 -0
  26. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/remediation.py +0 -0
  27. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/renderers.py +0 -0
  28. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/ruleset.py +0 -0
  29. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/scanner_status.py +0 -0
  30. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/scanners/__init__.py +0 -0
  31. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/scanners/bandit_scanner.py +0 -0
  32. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/scanners/base.py +0 -0
  33. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/scanners/builtin_rules.py +0 -0
  34. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/scanners/checkov_scanner.py +0 -0
  35. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/scanners/floor.py +0 -0
  36. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/scanners/gitleaks_scanner.py +0 -0
  37. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/scanners/gosec_scanner.py +0 -0
  38. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/scanners/hadolint_scanner.py +0 -0
  39. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/scanners/njsscan_scanner.py +0 -0
  40. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/scanners/npm_audit_scanner.py +0 -0
  41. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/scanners/osv_scanner.py +0 -0
  42. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/scanners/pip_audit_scanner.py +0 -0
  43. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/scanners/rubocop_scanner.py +0 -0
  44. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/scanners/scorecard_scanner.py +0 -0
  45. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/scanners/semgrep_scanner.py +0 -0
  46. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/scanners/trivy_scanner.py +0 -0
  47. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/scanners/trufflehog_scanner.py +0 -0
  48. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/scoring.py +0 -0
  49. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/standards.py +0 -0
  50. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/suppressions.py +0 -0
  51. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/triage.py +0 -0
  52. {secure_code_agent-0.12.3 → secure_code_agent-0.12.4}/src/secure_code_audit/verify.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: secure-code-agent
3
- Version: 0.12.3
3
+ Version: 0.12.4
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
@@ -513,7 +513,7 @@ For agents that support invokable skills, this repo ships a portable skill under
513
513
  ## GitHub Action
514
514
 
515
515
  ```yaml
516
- - uses: marshallguillory86/secure-code-agent@v0.12.3
516
+ - uses: marshallguillory86/secure-code-agent@v0.12.4
517
517
  with:
518
518
  config: secure-code-agent.json
519
519
  fail-on-gate: true
@@ -523,7 +523,15 @@ The action installs the exact source bundled with the referenced action plus
523
523
  the pinned `required-scanners` extra (Bandit and pip-audit),
524
524
  emits Markdown, JSON, SARIF, PR-comment, and remediation artifacts, and uploads
525
525
  SARIF by default. The calling workflow must grant `security-events: write` for
526
- SARIF upload. Pin production usage to a full commit SHA; the version tag above
526
+ SARIF upload.
527
+
528
+ What it uploads is `secure-code.code-scanning.sarif`, not the full SARIF. Code
529
+ scanning opens an alert for every uploaded result and does not act on SARIF
530
+ `suppressions`, so uploading the full file raises an alert, and a review thread
531
+ on pull requests, for every test-tree and documentation finding and every
532
+ reviewed `.scignore.yaml` entry. The upload file leaves those out; the full SARIF,
533
+ with each suppression and its reason, is kept as an artifact. Outside the action,
534
+ `--code-scanning-sarif-output PATH` writes the same file. Pin production usage to a full commit SHA; the version tag above
527
535
  is shown for readability. See [`action.yml`](action.yml) and
528
536
  [`examples/github-actions/`](examples/github-actions/) for full workflows.
529
537
 
@@ -465,7 +465,7 @@ For agents that support invokable skills, this repo ships a portable skill under
465
465
  ## GitHub Action
466
466
 
467
467
  ```yaml
468
- - uses: marshallguillory86/secure-code-agent@v0.12.3
468
+ - uses: marshallguillory86/secure-code-agent@v0.12.4
469
469
  with:
470
470
  config: secure-code-agent.json
471
471
  fail-on-gate: true
@@ -475,7 +475,15 @@ The action installs the exact source bundled with the referenced action plus
475
475
  the pinned `required-scanners` extra (Bandit and pip-audit),
476
476
  emits Markdown, JSON, SARIF, PR-comment, and remediation artifacts, and uploads
477
477
  SARIF by default. The calling workflow must grant `security-events: write` for
478
- SARIF upload. Pin production usage to a full commit SHA; the version tag above
478
+ SARIF upload.
479
+
480
+ What it uploads is `secure-code.code-scanning.sarif`, not the full SARIF. Code
481
+ scanning opens an alert for every uploaded result and does not act on SARIF
482
+ `suppressions`, so uploading the full file raises an alert, and a review thread
483
+ on pull requests, for every test-tree and documentation finding and every
484
+ reviewed `.scignore.yaml` entry. The upload file leaves those out; the full SARIF,
485
+ with each suppression and its reason, is kept as an artifact. Outside the action,
486
+ `--code-scanning-sarif-output PATH` writes the same file. Pin production usage to a full commit SHA; the version tag above
479
487
  is shown for readability. See [`action.yml`](action.yml) and
480
488
  [`examples/github-actions/`](examples/github-actions/) for full workflows.
481
489
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: secure-code-agent
3
- Version: 0.12.3
3
+ Version: 0.12.4
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
@@ -513,7 +513,7 @@ For agents that support invokable skills, this repo ships a portable skill under
513
513
  ## GitHub Action
514
514
 
515
515
  ```yaml
516
- - uses: marshallguillory86/secure-code-agent@v0.12.3
516
+ - uses: marshallguillory86/secure-code-agent@v0.12.4
517
517
  with:
518
518
  config: secure-code-agent.json
519
519
  fail-on-gate: true
@@ -523,7 +523,15 @@ The action installs the exact source bundled with the referenced action plus
523
523
  the pinned `required-scanners` extra (Bandit and pip-audit),
524
524
  emits Markdown, JSON, SARIF, PR-comment, and remediation artifacts, and uploads
525
525
  SARIF by default. The calling workflow must grant `security-events: write` for
526
- SARIF upload. Pin production usage to a full commit SHA; the version tag above
526
+ SARIF upload.
527
+
528
+ What it uploads is `secure-code.code-scanning.sarif`, not the full SARIF. Code
529
+ scanning opens an alert for every uploaded result and does not act on SARIF
530
+ `suppressions`, so uploading the full file raises an alert, and a review thread
531
+ on pull requests, for every test-tree and documentation finding and every
532
+ reviewed `.scignore.yaml` entry. The upload file leaves those out; the full SARIF,
533
+ with each suppression and its reason, is kept as an artifact. Outside the action,
534
+ `--code-scanning-sarif-output PATH` writes the same file. Pin production usage to a full commit SHA; the version tag above
527
535
  is shown for readability. See [`action.yml`](action.yml) and
528
536
  [`examples/github-actions/`](examples/github-actions/) for full workflows.
529
537
 
@@ -12,4 +12,4 @@
12
12
  #:
13
13
  #: PyPI is immutable, so 0.4.0 stays wrong. 0.5.0 is the first build whose
14
14
  #: artifacts name their own producer correctly.
15
- __version__ = "0.12.3"
15
+ __version__ = "0.12.4"
@@ -82,6 +82,15 @@ def _parser() -> argparse.ArgumentParser:
82
82
  p.add_argument("--output", help="Markdown report output path.")
83
83
  p.add_argument("--json-output", help="Canonical JSON output path.")
84
84
  p.add_argument("--sarif-output", help="SARIF 2.1.0 output path.")
85
+ p.add_argument(
86
+ "--code-scanning-sarif-output",
87
+ help=(
88
+ "SARIF for upload to GitHub code scanning: the --sarif-output results "
89
+ "without the suppressed ones (test tree, documentation, .scignore.yaml). "
90
+ "Code scanning opens an alert for every uploaded result and ignores "
91
+ "SARIF suppressions, so upload this file, not the record."
92
+ ),
93
+ )
85
94
  p.add_argument("--comment-output", help="PR-comment markdown output path.")
86
95
  p.add_argument("--prompt-output", help="Remediation prompt output path.")
87
96
  p.add_argument(
@@ -795,6 +804,7 @@ def _own_artifacts(paths: _OutputPaths, cfg: config_mod.Config, root: Path) -> f
795
804
  paths.markdown,
796
805
  paths.json_out,
797
806
  paths.sarif,
807
+ paths.code_scanning_sarif,
798
808
  paths.comment,
799
809
  paths.prompt,
800
810
  paths.security_pillar,
@@ -933,6 +943,14 @@ def _write_outputs(
933
943
  renderers.write_json(findings, score, gate, paths.json_out, coverage, verdict, axes, root)
934
944
  if paths.sarif is not None:
935
945
  sarif.write(findings, paths.sarif, coverage, lambda f: renderers.axis_of(f, axes))
946
+ if paths.code_scanning_sarif is not None:
947
+ sarif.write(
948
+ findings,
949
+ paths.code_scanning_sarif,
950
+ coverage,
951
+ lambda f: renderers.axis_of(f, axes),
952
+ alerts_only=True,
953
+ )
936
954
  if paths.comment is not None:
937
955
  renderers.write_pr_comment(findings, score, gate, paths.comment, coverage, verdict)
938
956
  if paths.prompt is not None:
@@ -970,6 +988,7 @@ class _OutputPaths:
970
988
  comment: Path | None
971
989
  prompt: Path | None
972
990
  security_pillar: Path | None
991
+ code_scanning_sarif: Path | None = None
973
992
 
974
993
 
975
994
  def _resolve_outputs(args: argparse.Namespace, cfg: config_mod.Config, root: Path) -> _OutputPaths:
@@ -1014,6 +1033,7 @@ def _resolve_outputs(args: argparse.Namespace, cfg: config_mod.Config, root: Pat
1014
1033
  comment=_p(args.comment_output, "comment_path"),
1015
1034
  prompt=_p(args.prompt_output, "prompt_path"),
1016
1035
  security_pillar=_p(args.security_pillar, "security_pillar_path"),
1036
+ code_scanning_sarif=_p(args.code_scanning_sarif_output, "code_scanning_sarif_path"),
1017
1037
  )
1018
1038
 
1019
1039
 
@@ -1027,6 +1047,7 @@ _WRITE_KEYS = (
1027
1047
  "markdown_path",
1028
1048
  "json_path",
1029
1049
  "sarif_path",
1050
+ "code_scanning_sarif_path",
1030
1051
  "comment_path",
1031
1052
  "prompt_path",
1032
1053
  "security_pillar_path",
@@ -1169,6 +1190,7 @@ def _print_summary(
1169
1190
  paths.markdown,
1170
1191
  paths.json_out,
1171
1192
  paths.sarif,
1193
+ paths.code_scanning_sarif,
1172
1194
  paths.comment,
1173
1195
  paths.prompt,
1174
1196
  paths.security_pillar,
@@ -136,6 +136,10 @@ DEFAULT_OUTPUTS: dict[str, str] = {
136
136
  "markdown_path": "secure-code-report.md",
137
137
  "json_path": "secure-code-report.json",
138
138
  "sarif_path": "secure-code.sarif",
139
+ # For upload to code scanning: the SARIF above without results that carry
140
+ # `suppressions`, which GitHub turns into alerts regardless (D22). Off
141
+ # unless declared, like sarif_path.
142
+ "code_scanning_sarif_path": "secure-code.code-scanning.sarif",
139
143
  "comment_path": "secure-code-pr-comment.md",
140
144
  "prompt_path": "secure-code-remediation-prompt.md",
141
145
  "baseline_path": "secure-code-baseline.json",
@@ -18,6 +18,7 @@ from secure_code_audit.scanner_status import (
18
18
  ScannerExecution,
19
19
  ScannerOutcome,
20
20
  )
21
+ from secure_code_audit.scoring import GATED_FROM_ANY_AXIS
21
22
  from secure_code_audit.standards import cwe_url
22
23
 
23
24
  _SARIF_LEVEL = {
@@ -33,8 +34,25 @@ def emit(
33
34
  findings: Iterable[Finding],
34
35
  coverage: CoverageReport | None = None,
35
36
  axis_of=lambda _f: "primary",
37
+ *,
38
+ alerts_only: bool = False,
36
39
  ) -> dict:
37
- """Build a SARIF 2.1.0 document from canonical findings."""
40
+ """Build a SARIF 2.1.0 document from canonical findings.
41
+
42
+ `alerts_only` builds the document for upload to a code-scanning platform:
43
+ results carrying `suppressions` are left out, rather than kept with the
44
+ field. GitHub code scanning opens an alert for every uploaded result and
45
+ does not act on `suppressions`, so the standard form turned each
46
+ test-tree `assert` into a pull-request review thread once D20 made paths
47
+ placeable (D22). The default keeps every result, as the record.
48
+
49
+ **Except what the build fails on.** A side-axis finding in a category the
50
+ gate escalates from any axis — a secret in a test fixture — is marked
51
+ suppressed in the record and still uploaded, without the marker, so the
52
+ Security tab shows the finding that failed the build. An operator's
53
+ reviewed suppression is left out whatever its category; it returns as a
54
+ live critical finding when it expires.
55
+ """
38
56
  findings = list(findings)
39
57
 
40
58
  # Build the rules array per-canonical-rule (rule_id is local; dedupe).
@@ -42,10 +60,15 @@ def emit(
42
60
  results: list[dict] = []
43
61
 
44
62
  for f in findings:
63
+ result = _result(f, axis_of(f))
64
+ if alerts_only and result.get("suppressions"):
65
+ if f.suppressed or f.category not in GATED_FROM_ANY_AXIS:
66
+ continue
67
+ del result["suppressions"]
45
68
  rid = f.rule_id
46
69
  if rid not in rule_meta:
47
70
  rule_meta[rid] = _rule(f)
48
- results.append(_result(f, axis_of(f)))
71
+ results.append(result)
49
72
 
50
73
  run = {
51
74
  "tool": {
@@ -144,8 +167,13 @@ def _suppressions(f: Finding, axis: str) -> list[dict] | None:
144
167
  the shipped source.
145
168
 
146
169
  Suppressed, not omitted. The finding stays in the file with its
147
- location and justification, so nothing is hidden from a reader — it
148
- simply does not become someone's ticket.
170
+ location and justification, so nothing is hidden from a reader.
171
+
172
+ **GitHub code scanning does not honour it (D22).** It opens an alert for
173
+ every uploaded result regardless, which this function's premise assumed
174
+ it would not. So this is right for the record, and not enough for the
175
+ upload: `emit(..., alerts_only=True)` leaves these results out of the
176
+ file that goes to code scanning.
149
177
  """
150
178
  if f.suppressed:
151
179
  return [
@@ -206,8 +234,11 @@ def write(
206
234
  output: Path,
207
235
  coverage: CoverageReport | None = None,
208
236
  axis_of=lambda _f: "primary",
237
+ *,
238
+ alerts_only: bool = False,
209
239
  ) -> None:
210
- output.write_text(json.dumps(emit(findings, coverage, axis_of), indent=2), encoding="utf-8")
240
+ document = emit(findings, coverage, axis_of, alerts_only=alerts_only)
241
+ output.write_text(json.dumps(document, indent=2), encoding="utf-8")
211
242
 
212
243
 
213
244
  # ----- ingest -------------------------------------------------------------