Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 45 additions & 5 deletions .github/workflows/audit-central-ruleset.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,56 @@ jobs:
with:
persist-credentials: false

- name: Read live organization ruleset
- name: Read live inherited organization ruleset and public scope
env:
RULESET_ENDPOINT: orgs/ContextualWisdomLab/rulesets/18156473
ORG_LOGIN: ContextualWisdomLab
RULESET_ID: "18156473"
RULESET_SENTINEL_REPOSITORY: naruon
run: |
set -euo pipefail
ruleset_json="$RUNNER_TEMP/central-required-workflow-ruleset.json"
ruleset_with_scope_json="$RUNNER_TEMP/central-required-workflow-ruleset-with-scope.json"
ruleset_error="$RUNNER_TEMP/central-required-workflow-ruleset.error"
if ! gh api "$RULESET_ENDPOINT" >"$ruleset_json" 2>"$ruleset_error"; then
echo "::error::Ruleset audit could not read organization ruleset 18156473."
repositories_json="$RUNNER_TEMP/central-required-workflow-public-repositories.json"
scope_json="$RUNNER_TEMP/central-required-workflow-scope.json"
ruleset_endpoint="repos/${ORG_LOGIN}/${RULESET_SENTINEL_REPOSITORY}/rulesets/${RULESET_ID}?includes_parents=true"

if ! gh api "$ruleset_endpoint" >"$ruleset_json" 2>"$ruleset_error"; then
echo "::error::Ruleset audit could not read inherited organization ruleset ${RULESET_ID} through ${ORG_LOGIN}/${RULESET_SENTINEL_REPOSITORY}."
sed 's/^/ /' "$ruleset_error"
exit 1
fi
python3 scripts/ci/audit_central_required_workflows.py "$ruleset_json"
if ! gh api --paginate "orgs/${ORG_LOGIN}/repos?type=public&per_page=100" \
| jq -s 'add | map(.name) | unique | sort' >"$repositories_json"; then
echo "::error::Ruleset audit could not enumerate public repositories for ${ORG_LOGIN}."
exit 1
fi

printf '{}\n' >"$scope_json"
while IFS= read -r repository; do
probe_json="$RUNNER_TEMP/ruleset-probe-${repository//[^A-Za-z0-9_.-]/_}.json"
probe_error="$RUNNER_TEMP/ruleset-probe-${repository//[^A-Za-z0-9_.-]/_}.error"
probe_endpoint="repos/${ORG_LOGIN}/${repository}/rulesets/${RULESET_ID}?includes_parents=true"
inherited=false
if gh api "$probe_endpoint" >"$probe_json" 2>"$probe_error"; then
if ! jq -e --argjson ruleset_id "$RULESET_ID" '.id == $ruleset_id' "$probe_json" >/dev/null; then
echo "::error::Ruleset scope probe for ${ORG_LOGIN}/${repository} returned the wrong ruleset object."
jq '{id,name,source_type,source,enforcement,target}' "$probe_json"
exit 1
fi
inherited=true
elif ! grep -q 'HTTP 404' "$probe_error"; then
echo "::error::Ruleset scope probe for ${ORG_LOGIN}/${repository} failed for a reason other than non-inheritance."
sed 's/^/ /' "$probe_error"
exit 1
fi
echo "RULESET_SCOPE repository=${repository} inherited=${inherited}"
jq --arg repository "$repository" --argjson inherited "$inherited" \
'. + {($repository): $inherited}' "$scope_json" >"${scope_json}.next"
mv "${scope_json}.next" "$scope_json"
done < <(jq -r '.[]' "$repositories_json")

jq --slurpfile scope "$scope_json" \
'. + {"_audit_repository_scope": $scope[0]}' \
"$ruleset_json" >"$ruleset_with_scope_json"
python3 scripts/ci/audit_central_required_workflows.py "$ruleset_with_scope_json"
19 changes: 12 additions & 7 deletions .github/workflows/osv-scanner-pr.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Uploads an osv-scanner code scanning analysis on every PR so the org
# ruleset "CWL Central required workflows" -> code_scanning(osv-scanner)
# requirement can actually be satisfied. Without this, the required tool
# never reports on PR refs and every PR stays mergeStateStatus=BLOCKED.
# Keeps the upstream OSV base/head diff check available on every PR. The
# central Security Scan workflow owns the blocking OSV result, finding logs,
# and SARIF upload so this supplemental check does not duplicate installation
# API calls or fail an otherwise clean PR when GitHub's upload quota is spent.
name: OSV-Scanner PR

on:
Expand Down Expand Up @@ -43,6 +43,8 @@ jobs:
permissions:
actions: read
contents: read
# The pinned upstream reusable workflow declares this permission at its
# top level, so GitHub validates it even when upload-sarif is false.
security-events: write
with:
# Keep the PR code-scanning upload deterministic: direct manifest
Expand All @@ -56,7 +58,10 @@ jobs:
--no-resolve
-r
./
# Merge gating is done by the org code_scanning ruleset rule
# (medium_or_higher), not by failing this check. Keep the check green
# so it only supplies the analysis; the ruleset decides blocking.
# The required central security-scan.yml job uploads the comprehensive
# current-head OSV SARIF. Avoid a second upload through the reusable
# workflow because installation rate-limit failures are not findings.
upload-sarif: false
# Merge gating is done by central security-scan.yml with
# --fail-on-vuln=true after printing package, version, OSV ID and aliases.
fail-on-vuln: false
8 changes: 8 additions & 0 deletions .github/workflows/python-security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -186,11 +186,19 @@ jobs:
Path("bandit-results.sarif").write_text(json.dumps(sarif, indent=2), encoding="utf-8")
PY
- name: Upload Bandit SARIF to code scanning
id: upload_bandit_sarif
if: always() && hashFiles('bandit-results.sarif') != ''
# The explicit gate below still fails on every Medium+ Bandit result.
continue-on-error: true
uses: github/codeql-action/upload-sarif@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
with:
sarif_file: bandit-results.sarif
category: bandit
wait-for-processing: false
- name: Report Bandit SARIF upload failure
if: steps.upload_bandit_sarif.outcome == 'failure'
run: |
echo "::warning::Bandit SARIF upload to code scanning failed after the local Bandit scan. The Bandit hard gate still follows the scan rc, so upload rate limits cannot hide MEDIUM+ findings."
- name: Enforce bandit gate (fail on MEDIUM+ findings)
if: steps.bandit.outputs.rc != '0'
run: |
Expand Down
22 changes: 18 additions & 4 deletions .github/workflows/sast-semgrep.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,22 @@ jobs:
--exclude='docs/research/**/standards' \
--error \
--sarif \
--output=semgrep-results.sarif \
--output=semgrep-results.raw.sarif \
--metrics=off
echo "rc=$?" >> "$GITHUB_OUTPUT"
set -e
- name: Remove explicitly suppressed findings from Semgrep SARIF
id: semgrep_sarif
if: always() && hashFiles('semgrep-results.raw.sarif') != ''
run: |
set -euo pipefail
suppressed_count=$(jq '[.runs[]?.results[]? | select(((.suppressions // []) | length) > 0)] | length' semgrep-results.raw.sarif)
jq '(.runs[]? | .results) |= ((. // []) | map(select(((.suppressions // []) | length) == 0)))' \
semgrep-results.raw.sarif > semgrep-results.sarif
finding_count=$(jq '[.runs[]?.results[]?] | length' semgrep-results.sarif)
echo "suppressed_count=$suppressed_count" >> "$GITHUB_OUTPUT"
echo "finding_count=$finding_count" >> "$GITHUB_OUTPUT"
echo "SEMGREP_SUPPRESSED_COUNT=$suppressed_count SEMGREP_FINDING_COUNT=$finding_count"
- name: Upload Semgrep SARIF to code scanning
if: always() && hashFiles('semgrep-results.sarif') != ''
continue-on-error: true
Expand All @@ -98,10 +110,11 @@ jobs:
if: always() && hashFiles('semgrep-results.sarif') != ''
env:
SEMGREP_RC: ${{ steps.semgrep.outputs.rc }}
SEMGREP_SUPPRESSED_COUNT: ${{ steps.semgrep_sarif.outputs.suppressed_count }}
run: |
set -euo pipefail
finding_count=$(jq '[.runs[]?.results[]?] | length' semgrep-results.sarif)
echo "SEMGREP_FINDING_COUNT=${finding_count} SEMGREP_RC=${SEMGREP_RC:-missing}"
echo "SEMGREP_FINDING_COUNT=${finding_count} SEMGREP_SUPPRESSED_COUNT=${SEMGREP_SUPPRESSED_COUNT:-missing} SEMGREP_RC=${SEMGREP_RC:-missing}"
jq -r '
.runs[]? as $run
| ($run.tool.driver.rules // []
Expand All @@ -119,11 +132,12 @@ jobs:
echo "SEMGREP_ENGINE_FAILURE rc=${SEMGREP_RC:-missing}: Semgrep failed without a WARNING/ERROR SARIF result; inspect the scan command output above."
fi
- name: Enforce Semgrep gate (fail on Medium+ findings)
if: steps.semgrep.outputs.rc != '0'
if: always() && (steps.semgrep_sarif.outputs.finding_count != '0' || steps.semgrep.outputs.rc != '0')
env:
SEMGREP_RC: ${{ steps.semgrep.outputs.rc }}
SEMGREP_FINDING_COUNT: ${{ steps.semgrep_sarif.outputs.finding_count }}
run: |
if [ "${SEMGREP_RC}" = "1" ]; then
if [ "${SEMGREP_FINDING_COUNT:-missing}" != "0" ]; then
echo "::error::Semgrep found WARNING/ERROR (Medium+) findings. Every rule, path, line, and message is listed in the preceding report step and the 'semgrep' code scanning category."
else
echo "::error::Semgrep engine/configuration failed with rc=${SEMGREP_RC}. The concrete scan output and SARIF report are logged above."
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/scorecard-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ jobs:
PY

- name: Upload to code scanning
# Scorecard posture is preserved in its SARIF-generation log; an
# installation upload quota outage must not fail the default branch.
continue-on-error: true
uses: github/codeql-action/upload-sarif@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
with:
sarif_file: results.sarif
26 changes: 13 additions & 13 deletions .github/workflows/scorecard-pr.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
# Uploads an OpenSSF Scorecard code scanning analysis on every PR so the org
# ruleset "CWL Central required workflows" -> code_scanning(Scorecard)
# requirement can be satisfied on PR refs. scorecard-analysis.yml only runs on
# push/schedule (default branch), so PRs never had a Scorecard analysis and
# stayed mergeStateStatus=BLOCKED.
# Runs a supplemental OpenSSF Scorecard analysis on every PR and preserves its
# filtered SARIF as an artifact. The central Security Scan workflow owns the
# PR code-scanning upload so this workflow does not duplicate installation API
# calls or fail a clean PR when GitHub's upload quota is spent.
#
# NOTE: Scorecard reports repository-posture findings (branch protection, token
# permissions, dependency pinning, ...) that are unrelated to the PR diff. The
# org code_scanning ruleset rule therefore gates Scorecard at a raised
# threshold (see the ruleset) and delegates PR-only SAST/vulnerability posture
# findings to the dedicated CodeQL, OSV, Trivy, and dependency-review hard gates.
# central Security Scan job therefore treats Scorecard as soft visibility and
# delegates PR-only SAST/vulnerability posture findings to the dedicated
# CodeQL, OSV, Trivy, and dependency-review hard gates.
name: Scorecard PR

on:
Expand Down Expand Up @@ -38,7 +37,6 @@ jobs:
if: github.event.action != 'closed'
runs-on: ubuntu-latest
permissions:
security-events: write
contents: read
actions: read
steps:
Expand Down Expand Up @@ -97,8 +95,10 @@ jobs:
)
PY

- name: Upload to code scanning
uses: github/codeql-action/upload-sarif@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
- name: Preserve Scorecard PR SARIF evidence
if: always() && hashFiles('results.sarif') != ''
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
sarif_file: results.sarif
category: scorecard-pr
name: scorecard-pr-sarif-${{ github.run_id }}-${{ github.run_attempt }}
path: results.sarif
retention-days: 7
25 changes: 25 additions & 0 deletions .github/workflows/security-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,11 @@ jobs:
"base/head comparison."
)
- name: Upload OSV SARIF to code scanning
id: upload_osv_sarif
if: always() && hashFiles('results.sarif') != ''
# The reporter above is the vulnerability gate. Preserve an upload
# quota failure in this step's log without reclassifying it as a CVE.
continue-on-error: true
uses: github/codeql-action/upload-sarif@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
with:
sarif_file: results.sarif
Expand All @@ -229,6 +233,11 @@ jobs:
# merge ref and fail with "commit_oid is not a merge commit".
ref: refs/pull/${{ github.event.pull_request.number }}/head
sha: ${{ github.event.pull_request.head.sha }}
wait-for-processing: false
- name: Report OSV SARIF upload failure
if: steps.upload_osv_sarif.outcome == 'failure'
run: |
echo "::warning::OSV SARIF upload to code scanning failed after the base/head comparison. The PR-introduced vulnerability reporter above remains the hard gate, so upload rate limits cannot hide OSV findings."
- name: Upload OSV debug artifacts
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v6.0.0
Expand Down Expand Up @@ -370,11 +379,19 @@ jobs:
print("Remediate each finding at the shared base branch so open PRs inherit the fix.")
raise SystemExit(1)
- name: Upload Trivy SARIF to code scanning
id: upload_trivy_sarif
if: always() && hashFiles('trivy-results.sarif') != ''
# The parser above fails on every fixable Medium+ finding independently.
continue-on-error: true
uses: github/codeql-action/upload-sarif@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
with:
sarif_file: trivy-results.sarif
category: trivy-fs
wait-for-processing: false
- name: Report Trivy SARIF upload failure
if: steps.upload_trivy_sarif.outcome == 'failure'
run: |
echo "::warning::Trivy SARIF upload to code scanning failed after the filesystem scan. The Trivy finding log above remains the hard gate, so upload rate limits cannot hide CRITICAL/HIGH/MEDIUM findings."

scorecard:
if: github.event.action != 'closed'
Expand Down Expand Up @@ -437,7 +454,15 @@ jobs:
)
PY
- name: Upload Scorecard SARIF to code scanning
id: upload_scorecard_sarif
# Scorecard is soft repository-posture evidence; upload quota is external.
continue-on-error: true
uses: github/codeql-action/upload-sarif@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
with:
sarif_file: results.sarif
category: scorecard
wait-for-processing: false
- name: Report Scorecard SARIF upload failure
if: steps.upload_scorecard_sarif.outcome == 'failure'
run: |
echo "::warning::Scorecard SARIF upload to code scanning failed after delegated PR-only findings were filtered. Scorecard is PR posture evidence only; CodeQL, OSV, Trivy, and dependency-review remain the hard gates."
2 changes: 2 additions & 0 deletions docs/org-required-workflow-rollout.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ non-fork inventory snapshot and rollout ledger, not the ruleset target list.
- On 2026-07-02 07:25 KST, organization ruleset `18156473` still reported `enforcement=active`, `repository_name.include=["~ALL"]`, `ref_name.include=["~DEFAULT_BRANCH"]`, and the same three required workflow paths from `ContextualWisdomLab/.github@refs/heads/main`.
- On 2026-07-11 11:30 KST, organization ruleset `18156473` was normalized to keep the five central required workflows, stale-review dismissal, last-pusher protection, and review-thread resolution while setting `required_approving_review_count=0` and `require_code_owner_review=false`. The merge gate remains current-head OpenCode approval plus required checks and scheduler evidence; the change removes self-authored/code-owner deadlocks that left approved PRs unable to merge.
- On 2026-07-13 21:10 KST, live inspection found that `sast-semgrep.yml` described itself as the central replacement for removed repository-local Semgrep jobs but was absent from ruleset `18156473`. The active ruleset was updated to require that workflow from `.github@refs/heads/main`, while preserving one approval, stale-review dismissal, last-push approval, and review-thread resolution. `scripts/ci/audit_central_required_workflows.py` and the scheduled ruleset audit now report each missing workflow, wrong source ref, or weakened review protection explicitly.
- On 2026-07-13 22:21 KST, the first main-branch ruleset audit proved that a repository `GITHUB_TOKEN` cannot read the organization-administration endpoint (`HTTP 403 Resource not accessible by integration`). The audit now uses the least-privilege inherited-ruleset endpoint, enumerates every public organization repository, logs `RULESET_SCOPE` for each one, requires inheritance everywhere except `.github`, `argos`, and `noema`, and still validates the complete workflow and pull-request rule payload through `naruon`.
- On 2026-07-13 22:37 KST, xtrmLLMBatchPython current-head evidence proved that Semgrep 1.169.0 reports zero blocking findings while retaining 23 source-suppressed results in raw SARIF. The central gate now logs the suppressed count, removes only SARIF results carrying explicit in-source suppressions before upload, and fails from the remaining SARIF finding count even when Semgrep's SARIF-mode exit code is zero.
- `.github` PR `#225` raised high reasoning effort for all reasoning-capable OpenCode review model definitions and merged at `50c6ef82f52af3eeb0e58c174902fc9855c36682`.
- `.github` PR `#226` stopped the merge scheduler from treating old deterministic fallback approval bodies as current-head approval evidence and merged at `57a1fa580731a0f76b31dcf29a597c5715dba2fd`.
- `.github` PR `#230` added changed-file candidates to merge-conflict guidance so `DIRTY` or `CONFLICTING` PRs name the first files to inspect instead of giving only generic conflict instructions. It merged at `0cab5c8d46e88c1a3f68ef3f71b5d44d971cd2ef`.
Expand Down
Loading
Loading