Skip to content

checks/sast: detect Semgrep, Bandit, and gosec SAST workflows - #5103

Closed
DevamShah wants to merge 1 commit into
ossf:mainfrom
DevamShah:feat/sast-detect-semgrep-bandit-gosec
Closed

DevamShah wants to merge 1 commit into
ossf:mainfrom
DevamShah:feat/sast-detect-semgrep-bandit-gosec

Conversation

@DevamShah

Copy link
Copy Markdown

What kind of change does this PR introduce?

Feature — expands SAST tool detection in the SAST check.

What is the current behavior?

The SAST check detects CodeQL, Sonar, Snyk, Pysa, Qodana, and Hadolint. Three
widely deployed open-source SAST tools are not recognized:

  • Semgrep (semgrep/semgrep, legacy returntocorp/semgrep-action)
  • Bandit (PyCQA/bandit-action) — the de facto Python SAST
  • gosec (securego/gosec, securecodewarrior/github-action-gosec) — the de facto Go SAST

Repositories that run these tools in CI are currently scored as if they have no
SAST configured. This is a false negative: a project with real static analysis
coverage receives a lower SAST score than its security posture warrants.

What is the new behavior (if this is a feature change)?

The SAST check now also detects Semgrep, Bandit, and gosec when invoked as a
GitHub Action. Detection follows the exact existing getSastUsesWorkflows
pattern used for CodeQL/Snyk/Qodana/Hadolint — each tool gets an anchored
uses: regex and a matching SASTWorkflowType constant. The
sastToolConfigured probe consumes the new workflow types automatically (it
maps SASTWorkflow.Type straight to a finding value, so no probe change is
required).

Changes:

  • checker/raw_result.go: add SemgrepWorkflow, BanditWorkflow, GosecWorkflow constants after HadolintWorkflow.

  • checks/raw/sast.go: add three getSastUsesWorkflows calls with anchored regexes.

  • checks/raw/sast_test.go: add Has Semgrep / Has Bandit / Has gosec table cases.

  • checks/raw/testdata/.github/workflows/: add three workflow fixtures using the real action references.

  • docs/checks/sast/README.md: list the three newly supported tools.

  • Tests for the changes have been added (for bug fixes/features)

Security rationale

SAST is a primary control against the introduction of injection, deserialization,
path-traversal, and hardcoded-secret defects (CWE-79, CWE-89, CWE-22, CWE-502,
CWE-798). Scorecard's SAST signal feeds downstream supply-chain risk decisions; a
false negative here understates the real assurance level of a dependency and can
nudge consumers toward unwarranted manual review or, worse, away from a project
that is in fact well-instrumented.

Semgrep, Bandit, and gosec are among the most commonly adopted open-source SAST
tools across the JavaScript/Python/Go ecosystems, so closing this gap materially
improves the accuracy of the SAST score for a large slice of the OSS population.

The detection regexes are fully anchored (^...$) on the action repository
reference, so they match the canonical tool actions and their documented legacy
aliases while rejecting look-alike forks and typosquats
(semgrep/semgrep-app-action, securego/gosec-installer, PyCQA/bandit-action-fork
do not match). This keeps false-positive risk minimal, consistent with the
existing CodeQL/Qodana matchers.

Which issue(s) this PR fixes

NONE

Special notes for your reviewer

  • The matcher for Semgrep covers both the current semgrep/semgrep action and
    the legacy returntocorp/semgrep-action, since both are still in active use
    across repositories.
  • gosec covers the maintained securego/gosec action and the older
    securecodewarrior/github-action-gosec wrapper.
  • I deliberately scoped this to GitHub Action (uses:) detection to mirror the
    existing pattern exactly and keep false-positive risk low. Config-file
    detection (e.g. .semgrep.yml, .bandit) is a reasonable follow-up but is
    noisier (these files are frequently present without CI enforcement), so I left
    it out of this PR; happy to add it in a follow-up if maintainers want it.

Does this PR introduce a user-facing change?

Yes — repositories that run Semgrep, Bandit, or gosec via GitHub Actions will now
have those tools recognized by the SAST check.

checks/sast: detect Semgrep, Bandit, and gosec SAST tools when configured as GitHub Actions

Signed-off-by: Devam Shah <devamshah91@gmail.com>
@DevamShah
DevamShah requested a review from a team as a code owner June 23, 2026 01:36
@DevamShah
DevamShah requested review from AdamKorcz and spencerschrock and removed request for a team June 23, 2026 01:36
@dosubot dosubot Bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Jun 23, 2026
yves-vogl added a commit to yves-vogl/moneymoney-paypal-pos-extension that referenced this pull request Jun 23, 2026
- new .github/workflows/sast.yml runs Semgrep on push (main, phase-*) + PR (any)
- ruleset: p/security-audit + p/secrets (D-34); --error makes ERROR-severity blocking (D-33)
- SARIF upload via github/codeql-action/upload-sarif with if: always() so findings surface even on failure
- pip install semgrep==1.167.0 (NOT container, avoids Docker Hub rate-limit per RESEARCH Pitfall 7)
- SHA-pinned uses: actions/checkout@11bd719 (v4.2.2),
  actions/setup-python@a309ff8 (v6.2.0),
  github/codeql-action@8272c29 (v3.36.2)
- top-level permissions: read-all; job permissions: security-events:write + actions:read + contents:read
- checkout: persist-credentials: false + fetch-depth: 0
- job name 'Semgrep SAST' (load-bearing for Plan 06.1-04 branch-protection CHECKS array)
- --exclude spec avoids JWT-shaped test fixtures (RESEARCH §5)
- local dry-run (semgrep 1.167.0 against src/**) returned 0 findings, 0 suppressions needed

Closes SEC-08 (the security control). Scorecard SAST score reporting deferred
to upstream ossf/scorecard#5103 — ADR-0009 (Plan 06.1-08) documents the gap.
yves-vogl added a commit to yves-vogl/moneymoney-paypal-pos-extension that referenced this pull request Jun 23, 2026
Add SUMMARY.md documenting the .github/workflows/sast.yml landing:
- ruleset p/security-audit + p/secrets, ERROR-blocking, SARIF upload
- SHA-pinned actions (checkout v4.2.2, setup-python v6.2.0, upload-sarif v3.36.2)
- pip install semgrep==1.167.0 (not container)
- local dry-run on src/** returned 0 findings, 0 suppressions
- Scorecard SAST score gap documented (ossf/scorecard#5103 upstream pending)
- branch-protection CHECKS update deferred to Plan 06.1-04 (deadlock prevention)
yves-vogl added a commit to yves-vogl/moneymoney-paypal-pos-extension that referenced this pull request Jun 23, 2026
Documents the bewusst akzeptierten Gaps (Fuzzing 0/10, Code-Review 0/10,
Packaging -1, Contributors structural, Maintained heals-itself) with
compensating mitigations and Alternatives Considered (Option B 1-reviewer,
Option C bot-reviewer, Snyk Code, SBOM+cosign+SLSA deferred to v1.1.x,
G-09 awk shell-interpolation low-severity, Lua/CI Semgrep rulesets).

Revises the Phase-6.1 aggregate target: ≥ 7.5 short-term, ≥ 8.5 once
ossf/scorecard#5103 merges (SAST 0→10) AND repo age crosses 90 days
(Maintained 0→8+, ~2026-09-15).

Closes DOC-11 (in concert with this plan's SECURITY.md + REQUIREMENTS.md
+ ROADMAP.md edits).
yves-vogl added a commit to yves-vogl/moneymoney-paypal-pos-extension that referenced this pull request Jun 23, 2026
- SEC-05 modified in-place: expanded with SCORECARD_READ_TOKEN provisioning,
  branch-protection invariants (linear history, no force-push/delete, no admin
  bypass), 5 required status checks, Scorecard Branch-Protection.score >= 3
  (Tier 1; Option A locked per ADR-0009).
- SEC-06 new: SHA-pinned uses: refs in workflows + CI grep gate + Dependabot
  lockstep + Scorecard Pinned-Dependencies.score == 10.
- SEC-07 new: permissions: read-all top-level + job-local writes +
  persist-credentials: false + Scorecard Token-Permissions.score == 10.
- SEC-08 new: Semgrep SAST workflow ERROR-blocking + SARIF in code-scanning +
  required status check; backlog issue tracks ossf/scorecard#5103.
- BUILD-07 new: CII Best Practices passing badge in both READMEs + Scorecard
  CII-Best-Practices.score >= 5.
- DOC-11 new: ADR-0009 + SECURITY.md supply-chain section + REQUIREMENTS
  updates + backlog issues.
- Traceability table appended with 5 Phase-6.1 rows; total count 70 -> 75.
yves-vogl added a commit to yves-vogl/moneymoney-paypal-pos-extension that referenced this pull request Jun 23, 2026
* docs(06.1): land RESEARCH + PATTERNS + CONTEXT post-Phase-6 addendum

Phase 6.1 (Supply-chain & Scorecard hardening) planning input.

- 06.1-RESEARCH.md (71K): current-state vs CONTEXT D-30..D-36 reconciliation,
  pinning strategy, permissions hardening, branch protection deltas, Semgrep
  SAST design, CII Best Practices, SBOM/attestation scope recommendation,
  egress allowlist refactor, ADR numbering, sequencing recommendation,
  open items. Cites live gh API + ossf/scorecard checks.md + Dependabot
  changelog + actions/attest-build-provenance + bestpractices.dev.
- 06.1-PATTERNS.md (15K): 14 target files mapped to in-repo analogs.
  Calls out CHECKS-array drift in setup-branch-protection.sh and missing
  persist-credentials:false across 5 checkout invocations as the two
  most load-bearing anti-patterns to guard.
- 06.1-CONTEXT.md addendum: D-30..D-36 status table (D-31/D-32 partially
  delivered by Phase 6 CP-2 + ci.yml split; D-30 not delivered).
  New decisions D-37..D-41: MkDocs Material docs site (Yves request
  2026-06-23), SBOM/attestation deferred to v1.1.x, ADR number 0009
  (not 0004 — taken), CII badge in both READMEs, Phase-6 R1 findings
  disposition (G-04/05/06/09/10/14 in scope; G-13/15/16 out; S-R2-L-01
  in; S-R2-M-01 monitoring-only).

* ci(06.1-02): add explicit .gitleaks.toml inheriting defaults

Closes G-14. The default gitleaks ruleset already covers every secret
shape this repo currently emits; an explicit config file makes the
active ruleset auditable and provides a slot for future project-
specific [[rules]] blocks if a Zettle/PayPal API-key shape ever evades
the default ruleset.

- File is minimal (23 lines), declares [extend] useDefault = true
- No active [[rules]] blocks — only a commented example slot
- Pairs with .gitleaksignore (per-fingerprint allowlist, owned by Phase 6)
- gitleaks-action auto-discovers the config at repo root; no CI YAML change
- Local smoke test: zero findings (gitleaks 8.30.1)

* ci(06.1-01): SHA-pin all 17 action references across 4 workflows

Replace every floating tag (`@v4`, `@v13`, `@v2`, `@v3`, `@v2.4.0`, …)
with a 40-char commit SHA + `# vX.Y.Z` trailing comment per Dependabot
convention. Closes SEC-06 (Pinned-Dependencies) and G-05.

Per-action resolved targets (re-resolved 2026-06-23 via `gh api`):

- actions/checkout                v4.2.2  11bd7190
- leafo/gh-actions-lua            v13.0.0 6919171c
- leafo/gh-actions-luarocks       v6.1.0  35d062de
- gitleaks/gitleaks-action        v2.3.9  ff98106e
- actions/upload-artifact         v4.6.2  ea165f8d
- actions/download-artifact       v4.3.0  d3f86a10
- softprops/action-gh-release     v2.6.2  3bb12739
- ossf/scorecard-action           v2.4.3  99c09fe9
- github/codeql-action            v3.36.2 8272c299

All majors held at currently-pinned line per PLAN guidance
(minimise blast radius — defer v5+/v6+/v7+ bumps to Dependabot review).

Files: 4 workflow files; no src/, tools/, or spec/ changes.
Reproducible-build SHA invariant on dist/paypal-pos.lua preserved
(baseline 18bb7a6a…).

* ci(06.1-01): persist-credentials, top-level read-all, gates hardening

Task 2 of Plan 06.1-01 — surgical hardening across all four workflows:

- ci.yml: add top-level `permissions: read-all` (SEC-07 acceptance bar);
  add `persist-credentials: false` to both checkouts (test + secret-scan
  jobs) per G-06; extend the D-79 print-gate to also catch `io.write(`
  and obvious `local p = print` / `local w = io.write` aliasing (G-04);
  add a new permanent SEC-06 grep gate step that walks
  `.github/workflows/*.yml` and fails any future PR that introduces a
  floating-tag `uses:` reference; replace the brittle
  `awk '{print $NF}' | sed 's/%//' | cut -d. -f1` luacov-parser pipeline
  with a single awk invocation that emits both the full percentage and
  the integer floor (G-09 / PATTERNS §3 row 2).

- release.yml: add `persist-credentials: false` to both checkouts
  (verify-signed-tag + build-test-coverage-repro jobs) per G-06 —
  `git verify-tag` is local-object-only per RESEARCH §3 A8; replace
  the same `awk | sed | cut` anti-pattern at line 139 with the
  single-awk pattern (ci.yml and release.yml updated in the same
  commit so they cannot drift); tighten the publish job to depend
  on `[verify-signed-tag, build-test-coverage-repro]` explicitly
  (G-10 defense in depth — a future refactor that drops the chained
  needs: cannot silently let an unsigned tag publish).

- commit-lint.yml: add explicit per-job `permissions: { contents: read }`
  for SEC-07 textual posture; add `persist-credentials: false` to the
  checkout per G-06 (commit-subject lint reads local git objects only).

- scorecard.yml: untouched in this commit (its checkout already carried
  `persist-credentials: false` per Phase 6 hardening; Task 1 already
  pinned its action references).

Reproducible-build SHA on dist/paypal-pos.lua unchanged
(18bb7a6a…ed8e0c1) — zero src/, tools/, or spec/ modifications.

Closes SEC-07 (Token-Permissions → 10/10 expected), G-04, G-06, G-09,
G-10. SEC-06 closed by Task 1's SHA-pinning + this commit's grep gate
that prevents future regressions.

* ci(06.1-05): group Dependabot github-actions minor+patch updates

Add groups: clause to .github/dependabot.yml github-actions ecosystem
to coalesce minor + patch SHA bumps into one weekly PR. Major-version
bumps stay in separate PRs (breaking-change surface needs attention).

Post Plan 06.1-01, 17 action refs across 4 workflows are SHA-pinned;
without grouping Dependabot would emit one PR per outdated action per
week. The actions-minor-patch group reduces that to typically one
combined PR + zero or one major-version PR.

Also extends the top-of-file comment to document Dependabot's SHA +
trailing version-comment lockstep behaviour (GitHub changelog
2022-10-31; extended Oct 2025 to correct stale comments). Maintainers
must not manually edit the # vX.Y.Z comments — let Dependabot manage
them in lockstep with the SHA.

applies-to: version-updates is explicit so security updates remain
individual PRs (faster turnaround on vulnerabilities).

* ci(06.1-03): add semgrep SAST workflow

- new .github/workflows/sast.yml runs Semgrep on push (main, phase-*) + PR (any)
- ruleset: p/security-audit + p/secrets (D-34); --error makes ERROR-severity blocking (D-33)
- SARIF upload via github/codeql-action/upload-sarif with if: always() so findings surface even on failure
- pip install semgrep==1.167.0 (NOT container, avoids Docker Hub rate-limit per RESEARCH Pitfall 7)
- SHA-pinned uses: actions/checkout@11bd719 (v4.2.2),
  actions/setup-python@a309ff8 (v6.2.0),
  github/codeql-action@8272c29 (v3.36.2)
- top-level permissions: read-all; job permissions: security-events:write + actions:read + contents:read
- checkout: persist-credentials: false + fetch-depth: 0
- job name 'Semgrep SAST' (load-bearing for Plan 06.1-04 branch-protection CHECKS array)
- --exclude spec avoids JWT-shaped test fixtures (RESEARCH §5)
- local dry-run (semgrep 1.167.0 against src/**) returned 0 findings, 0 suppressions needed

Closes SEC-08 (the security control). Scorecard SAST score reporting deferred
to upstream ossf/scorecard#5103 — ADR-0009 (Plan 06.1-08) documents the gap.

* docs(06.1-03): complete semgrep SAST workflow plan

Add SUMMARY.md documenting the .github/workflows/sast.yml landing:
- ruleset p/security-audit + p/secrets, ERROR-blocking, SARIF upload
- SHA-pinned actions (checkout v4.2.2, setup-python v6.2.0, upload-sarif v3.36.2)
- pip install semgrep==1.167.0 (not container)
- local dry-run on src/** returned 0 findings, 0 suppressions
- Scorecard SAST score gap documented (ossf/scorecard#5103 upstream pending)
- branch-protection CHECKS update deferred to Plan 06.1-04 (deadlock prevention)

* fix(06.1-03): switch semgrep ci → semgrep scan (--error not supported by ci)

First push of sast.yml failed in CI (run 28026173380):
  'semgrep ci: unknown option --error'

The 'semgrep ci' subcommand is hard-wired to the Semgrep AppSec Platform
login flow and does not accept --error (per Semgrep CLI 1.167.0 `--help`).
'semgrep scan' is the offline-friendly equivalent that honors --error to
flip the exit code on ERROR-severity findings without requiring a token.

Same rules (p/security-audit + p/secrets), same SARIF output, same exclude
patterns. Adds --metrics=off for full offline determinism.

Local re-verification on src/** with the new invocation: 0 findings,
exit 0, SARIF file (514 KB) produced.

Rule 1 deviation — bug introduced by the plan text (Plan 06.1-03 Task 2
codified 'semgrep ci --error' which is not a valid invocation). Plan
intent (ERROR-severity blocking via SARIF upload) preserved byte-for-byte.

* docs(06.1-06): mkdocs material site config + bilingual landing pages

Closes D-37 (in part — workflow follows in the next commit).

Adds MkDocs Material site config and content pages that nav into existing
Markdown source-of-truth files (README.de.md, README.md, CONTRIBUTING.md,
SECURITY.md, CHANGELOG.md, ADRs 0001-0008) via mkdocs-include-markdown-plugin —
zero content duplication.

Files:
  - mkdocs.yml — site config, Material theme, mkdocs-static-i18n (de default,
    en fallback per D-70/D-71), include-markdown for nav-referenced root files,
    validation tolerance for cross-repo relative links inside included Markdown
  - docs/index.md — German landing
  - docs/en/index.md — English landing (i18n override)
  - docs/readme.md, docs/security.md, docs/contributing.md, docs/changelog.md —
    include-markdown wrappers for root Markdown
  - docs/en/readme.md — English README inlined (other docs fall back to default
    locale automatically)

Forward reference to ADR-0009 (Plan 06.1-08) is commented out in nav so
`mkdocs build --strict` passes; 06.1-08 will uncomment.

Local validation: `mkdocs build --strict` exits 0 against mkdocs 1.6.1,
mkdocs-material 9.5.41, mkdocs-static-i18n 1.2.3, mkdocs-include-markdown-plugin
7.0.0 (the versions pinned in the docs.yml workflow).

* ci(06.1-06): add docs build + GitHub Pages deploy workflow (SHA-pinned)

Closes D-37 (workflow track).

New workflow .github/workflows/docs.yml:
  - Triggers: push to main → build + deploy; PR → build-only validate;
    workflow_dispatch → manual rebuild
  - Top-level `permissions: read-all` (SEC-07); deploy job escalates to
    pages: write + id-token: write + contents: read (minimum required by
    actions/deploy-pages OIDC verification)
  - All actions SHA-pinned (SEC-06): checkout v4.2.2, setup-python v5.3.0,
    upload-pages-artifact v3.0.1, deploy-pages v4.0.5
  - persist-credentials: false on checkout (G-06)
  - concurrency: group=pages, cancel-in-progress=false (GitHub-recommended
    Pages pattern)
  - Pinned Python toolchain: mkdocs 1.6.1, mkdocs-material 9.5.41,
    mkdocs-static-i18n 1.2.3, mkdocs-include-markdown-plugin 7.0.0
    (deterministic builds; manual bump until Plan 06.1-05 follow-up extends
    Dependabot to the pip ecosystem — T-06.1-06-01/SC mitigation)
  - Build step: `mkdocs build --strict`
  - Deploy step gated on `github.event_name == 'push' && github.ref == 'refs/heads/main'`

Yves CP-6.1-C (one-time precondition before first deploy succeeds):
  Open https://github.com/yves-vogl/moneymoney-paypal-pos-extension/settings/pages
  Source: "GitHub Actions" (not "Deploy from a branch"), then Save.
  After this enablement, the workflow auto-deploys on push to main and the
  site renders at https://yves-vogl.github.io/moneymoney-paypal-pos-extension/.

* docs(06.1-06): add Documentation/Dokumentation badge to bilingual READMEs

Closes D-40 (bilingual badge cluster).

Single-line insertion in each README's badge cluster, between the OpenSSF
Scorecard badge and the GitHub Sponsors badge:
  - README.md     → "Documentation" (English label)
  - README.de.md  → "Dokumentation" (German label)

Both badges link to https://yves-vogl.github.io/moneymoney-paypal-pos-extension/
(the Pages site shipped by ci(06.1-06) workflow). No other content change in
either README — surgical edit only.

* docs(06.1-06): complete MkDocs Material site plan

* docs(06.1-03): update SUMMARY with Rule 1 fix + CI-green confirmation

- Document semgrep ci → semgrep scan Rule 1 fix (commit 27d7476)
- Record all three CI run IDs (28026173380 failed, 28026276896 failed,
  28026375102 GREEN)
- Add deferred-items.md entries for Node 20 + CodeQL Action v3 deprecations
  (both shared with Plan 06.1-01 SHAs; sync bump needed in Plan 06.1-05 or
  a dedicated follow-on)

* docs(06.1): land 8 plan files + PLAN-INDEX from planner

Plans 06.1-01 through 06.1-08 (Wave 1-5) + PLAN-INDEX (dependency graph
+ cumulative Scorecard delta). Authored by gsd-planner 2026-06-23 with
inputs from CONTEXT (D-30..D-41 incl. addendum) + RESEARCH (11 sections)
+ PATTERNS (14 file mappings).

Plans 01/02/03/05/06 already executed and on branch; this commit lands
the source-of-truth plan documents so the consolidator (06.1-08) and the
verifier have the canonical task lists available.

* ci(06.1-04): wire SCORECARD_READ_TOKEN into scorecard-action

Add `repo_token: ${{ secrets.SCORECARD_READ_TOKEN }}` as a `with:` input
to the ossf/scorecard-action step so the Branch-Protection Scorecard
check can introspect main's protection state via a fine-grained PAT
(Administration: read). Without it scorecard-action falls back to the
default GITHUB_TOKEN, which cannot read branch-protection, and the
Branch-Protection check returns -1 (unreadable).

Closes SEC-05 expansion (Branch-Protection introspection).

Yves checkpoints (must run by hand — script run NOT included here):

  CP-6.1-A (BEFORE merge): Yves creates a fine-grained PAT
    - Resource owner: yves-vogl
    - Repository access: moneymoney-paypal-pos-extension only
    - Repository permissions: Administration -> Read-only (and nothing else)
    - Expiration: 1 year (rotation cadence captured in SECURITY.md by 06.1-08)
    Then:
      gh secret set SCORECARD_READ_TOKEN \
        --repo yves-vogl/moneymoney-paypal-pos-extension
    Verify:
      gh secret list --repo yves-vogl/moneymoney-paypal-pos-extension \
        | grep SCORECARD_READ_TOKEN
    If the secret is unset when scorecard.yml's next run fires (cron
    or push to main), the Branch-Protection check returns -1 for the
    next ~24 h of cached score.

  CP-6.1-B (AFTER merge): companion commit chore(06.1-04) updates
    tools/setup-branch-protection.sh; Yves runs the script post-merge
    to push the new 5-check CHECKS array into live protection on main.

SHA pinning (06.1-01) and top-level `permissions: read-all` preserved.

* chore(06.1-04): extend setup-branch-protection.sh CHECKS + S-R2-L-01 assertions

Three surgical edits to tools/setup-branch-protection.sh:

1. CHECKS array grows from 3 to 5 entries. Adds:
     - "Scorecard analysis"   (matches scorecard.yml job name byte-exact)
     - "Semgrep SAST"          (matches sast.yml job name byte-exact)
   Both strings verified against the workflow `name:` declarations
   in this same branch — string-drift here would silently deadlock
   every PR waiting for a non-existent check-run context.

2. Manual-UI fallback block (used when the running PAT lacks
   `Administration: write`) gains the same two bullet entries so a
   maintainer falling back to the GitHub UI configures all 5 required
   checks in lockstep.

3. Post-condition GET adds S-R2-L-01 hardening: two new jq assertions
   on the read-back protection JSON for
     `.allow_force_pushes.enabled == false`
     `.allow_deletions.enabled == false`
   The PUT payload already declares both as false; the assertions
   catch silent partial-apply by the GitHub API.

Final OK line updated to mention both new invariants.

`bash -n tools/setup-branch-protection.sh` passes cleanly.

The script is NOT run in this commit. Yves runs it post-merge as the
existing CP-2 pattern. Companion commit ci(06.1-04) wires the
SCORECARD_READ_TOKEN secret into scorecard.yml.

Closes S-R2-L-01 (branch-protection post-condition assertions for
allow_force_pushes + allow_deletions).

* docs(06.1-04): land SUMMARY + STATE bump for scorecard token + CHECKS extension

Records the two-commit execution of Plan 06.1-04:
  184b2f6  ci(06.1-04): wire SCORECARD_READ_TOKEN into scorecard-action
  93684aa  chore(06.1-04): extend setup-branch-protection.sh CHECKS + S-R2-L-01 assertions

SUMMARY captures:
  - byte-exact job-name audit trail (CHECKS entries vs workflow `name:` declarations)
  - both Yves CPs (CP-6.1-A pre-merge secret provisioning, CP-6.1-B post-merge script run)
  - threat-model coverage notes
  - self-check confirmation (file presence + commit GPG signatures)

STATE last_updated + Session Continuity bumped to reflect Phase 6.1 wave-3 progress.

Closes SEC-05 expansion + S-R2-L-01 (executor side; live protection update
gated on CP-6.1-B by Yves post-merge).

* docs(06.1-08): adr-0009 openssf scorecard stance

Documents the bewusst akzeptierten Gaps (Fuzzing 0/10, Code-Review 0/10,
Packaging -1, Contributors structural, Maintained heals-itself) with
compensating mitigations and Alternatives Considered (Option B 1-reviewer,
Option C bot-reviewer, Snyk Code, SBOM+cosign+SLSA deferred to v1.1.x,
G-09 awk shell-interpolation low-severity, Lua/CI Semgrep rulesets).

Revises the Phase-6.1 aggregate target: ≥ 7.5 short-term, ≥ 8.5 once
ossf/scorecard#5103 merges (SAST 0→10) AND repo age crosses 90 days
(Maintained 0→8+, ~2026-09-15).

Closes DOC-11 (in concert with this plan's SECURITY.md + REQUIREMENTS.md
+ ROADMAP.md edits).

* docs(06.1-08): security.md supply-chain controls section

Adds bilingual 'Lieferketten-Kontrollen / Supply-chain controls' sections
listing all 10 active controls (SHA-pinned actions, least-privilege tokens,
Semgrep SAST, branch protection, Scorecard introspection, gitleaks,
signed releases, reproducible build, redact-before-log, egress allowlist)
plus the SCORECARD_READ_TOKEN rotation runbook (≤1y expiry).

Cross-references ADR-0009 for the trade-off rationale. Mirrors the
existing DE-primary / EN-follows structure; no content above the
insertion points changed.

* chore(06.1-08): requirements bumps for SEC-05..08 BUILD-07 DOC-11

- SEC-05 modified in-place: expanded with SCORECARD_READ_TOKEN provisioning,
  branch-protection invariants (linear history, no force-push/delete, no admin
  bypass), 5 required status checks, Scorecard Branch-Protection.score >= 3
  (Tier 1; Option A locked per ADR-0009).
- SEC-06 new: SHA-pinned uses: refs in workflows + CI grep gate + Dependabot
  lockstep + Scorecard Pinned-Dependencies.score == 10.
- SEC-07 new: permissions: read-all top-level + job-local writes +
  persist-credentials: false + Scorecard Token-Permissions.score == 10.
- SEC-08 new: Semgrep SAST workflow ERROR-blocking + SARIF in code-scanning +
  required status check; backlog issue tracks ossf/scorecard#5103.
- BUILD-07 new: CII Best Practices passing badge in both READMEs + Scorecard
  CII-Best-Practices.score >= 5.
- DOC-11 new: ADR-0009 + SECURITY.md supply-chain section + REQUIREMENTS
  updates + backlog issues.
- Traceability table appended with 5 Phase-6.1 rows; total count 70 -> 75.

* docs(06.1-08): roadmap + state reconciliation + mkdocs adr-0009 nav

- ROADMAP.md: Phase 6.1 success criteria amended (aggregate >=7.5 short-term;
  Branch-Protection >=3 per Option A; SAST deferred-acceptance per ADR-0009;
  ADR file number 0009 not 0004; both READMEs for CII badge per D-40; new
  criterion #9 for MkDocs site per D-37). Phase 6.1 marked [x]. Progress
  Table appended with 'Phase 6.1 | 8/8 | Complete | 2026-06-23'.
- STATE.md: re-authoritatively rewritten with post-Phase-6.1 reality.
  status: v1.0.0-shipped-pending-tag-and-6.1-shipped; completed_phases: 7;
  Phase 6.1 ship summary documented; CP-6.1-A..D + CP-Post-08 surfaced as
  Yves tasks; CP-4/5 still pending; metrics row for Phase 6.1 added.
- mkdocs.yml: ADR-0009 nav entry un-commented (Plan 06.1-06 forward-reference
  resolved); nav_translations entry for EN locale added.

* docs(06.1-08): SUMMARY for plan 06.1-08 (consolidating docs batch)

Records: 4 commits (852cc88 ADR / 7f79a5f SECURITY / 3a42da8 REQUIREMENTS
/ 78dd7b4 ROADMAP+STATE+mkdocs), all gates green, META-03 walker 3/0/0/0,
reproducible-build SHA unchanged (no src/ edits), Yves post-merge tasks
(CP-6.1-A/B/C/Post-08) surfaced.

* fix(06.1-04): repair ci.yml YAML block-scalar in D-79 alias gate

The Plan-06.1-01 extension of the D-79 raw-print() gate concatenated
BAD and BAD_ALIAS with a literal mid-string newline at column 0:

    if [ -n "${BAD}" ]; then BAD="${BAD}
${BAD_ALIAS}"; else BAD="${BAD_ALIAS}"; fi

The unindented "${BAD_ALIAS}" line terminated the YAML literal block
scalar prematurely (block scalars are bounded by indent restoration),
and GitHub Actions rejected the workflow with a YAML parse error on
every push since 89f4ee4. Refactor to printf concatenation that stays
inside the indented block:

    BAD="${BAD_CALLS}"
    if [ -n "${BAD_ALIAS}" ]; then
      BAD="$(printf '%s\n%s' "${BAD}" "${BAD_ALIAS}")"
    fi

Semantically identical (same final BAD content); structurally valid YAML.
Verified via python -c "yaml.safe_load(...)" → OK.

* fix(06.1-r): bump mkdocs-include-markdown-plugin past CVE-2025-59940 (S-01)

* fix(06.1-r): extend branch-protection post-condition assertions (S-03 + P6.1-R-04)

* fix(06.1-r): add EN locale include wrappers for --strict build (P6.1-R-01)

* docs(06.1-r): list ADR-0009 in bilingual landing pages (P6.1-R-02)

* docs(06.1-r): ROADMAP coverage counter + Scorecard target reconciliation (P6.1-R-03)
@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown

This pull request has been marked stale because it has been open for 10 days with no activity

@DevamShah

Copy link
Copy Markdown
Author

This PR was closed by the stale bot after 10 days; no maintainer ever looked at it. Before asking for any of your time I re-verified the premise on current main and found that my own patch was partly wrong, so here is the corrected state.

The gap is still real on main (d1fab88).

checks/raw/sast.go SAST() dispatches exactly six matchers:

  • sast.go:60 ^github/codeql-action/analyze$
  • sast.go:66 getSonarWorkflows
  • sast.go:72 ^snyk/actions/.*
  • sast.go:78 ^facebook/pysa-action$
  • sast.go:84 ^JetBrains/qodana-action$
  • sast.go:90 ^hadolint/hadolint-action$

checker/raw_result.go:280-290 declares the matching six SASTWorkflowType constants, ending at HadolintWorkflow. The check-run path is separate and narrower still: sast.go:40-46 sastTools only accepts the app slugs github-advanced-security, github-code-scanning, lgtm-com, sonarcloud, sonarqubecloud.

So a repo running Semgrep, Bandit, or gosec in CI scores as having no SAST configured. #2318 asked for gosec and Bandit in its body and Semgrep in a comment, and was closed as completed; Snyk, Pysa, Qodana and Hadolint are in the check today, but those three are not.

Three references in my original patch were wrong. I removed them.

I checked each action reference against the GitHub API instead of assuming:

  • semgrep/semgrep is not a GitHub Action. gh api /repos/semgrep/semgrep/contents/action.yml returns 404. That matcher could never have fired.
  • securecodewarrior/github-action-gosec no longer exists. gh api /repos/securecodewarrior/github-action-gosec returns 404. Matching it would credit a workflow step that cannot run.
  • returntocorp/semgrep-action is the one that actually matters. gh api /users/semgrep reports created_at: 2017-06-28 — that is the original account, carried through the rename. gh api /users/returntocorp reports created_at: 2023-10-13 and public_repos: 1, and returntocorp/semgrep-action under it is a fork with 1 star. In other words the freed namespace was re-registered by a different account after Semgrep vacated it. Scorecard should not grant SAST credit on a reference that is no longer controlled by the tool's project, so I dropped it.

The matchers are now only the three tool-owned actions that resolve today, all anchored:

semgrepWorkflows, err := getSastUsesWorkflows(c, "^semgrep/semgrep-action$", checker.SemgrepWorkflow)
banditWorkflows,  err := getSastUsesWorkflows(c, "^PyCQA/bandit-action$",    checker.BanditWorkflow)
gosecWorkflows,   err := getSastUsesWorkflows(c, "^securego/gosec$",         checker.GosecWorkflow)

I added a negative fixture (github-sast-lookalike-workflow.yaml) covering returntocorp/semgrep-action, securecodewarrior/github-action-gosec, semgrep/semgrep-app-action, securego/gosec-installer and PyCQA/bandit-action-fork, with a Does not match SAST action look-alikes case expecting Workflows: nil. I mutation-checked it: re-adding |^returntocorp/semgrep-action$ to the Semgrep regex makes that case fail with + Type: "Semgrep", and removing it makes it pass again.

Known limitation I did not try to solve. The common modern Semgrep setup is a job container: image: semgrep/semgrep with run: semgrep ci, which getSastUsesWorkflows cannot see because it only walks ExecAction.Uses. Detecting that needs a different matcher shape, so I left it out and documented it in docs/checks/sast/README.md rather than pretending the coverage is complete.

What I ran, on the rebased branch (a2601e3, rebased cleanly onto d1fab88):

$ go build ./...        # exit 0
$ go vet ./...          # exit 0
$ go test ./checks/... -count=1
ok  github.com/ossf/scorecard/v5/checks              0.655s
ok  github.com/ossf/scorecard/v5/checks/evaluation   1.148s
ok  github.com/ossf/scorecard/v5/checks/fileparser   1.678s
ok  github.com/ossf/scorecard/v5/checks/raw          3.556s
ok  github.com/ossf/scorecard/v5/checks/raw/gitlab   2.750s

TestSAST is now 14 subtests, all passing, including Has_Semgrep, Has_Bandit, Has_gosec and Does_not_match_SAST_action_look-alikes. ./probes/sastToolConfigured and ./probes/sastToolRunsOnAllCommits also pass — no probe change was needed, since sastToolConfigured/impl.go:57 reads r.Workflows[i].Type straight into the finding value.

I have not run the e2e suite (it needs credentials I do not have) and I have not run golangci-lint, which is not installed here. The longest line I added is 102 characters, so lll should be satisfied, but I have not confirmed that by running the linter.

The branch is pushed and ready either way. @spencerschrock, you were the last maintainer to touch checks/raw/sast.go — would you rather reopen this, or should I resubmit as a fresh PR against current main? If you would prefer to skip Semgrep because semgrep/semgrep-action is archived and only the container form is current, say so and I will cut it down to Bandit and gosec.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M This PR changes 30-99 lines, ignoring generated files. Stale

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant