From 6e7a8d8a947fec1ffdfff15f165b3a171ec2e03e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 21:37:19 +0900 Subject: [PATCH 1/5] ci(workflows): use central reusable dependency-review.yml Replace this repo's hand-written dependency-review.yml (harden-runner step, comment-summary-in-pr: never, moderate severity, multi-branch trigger) with a thin caller into ContextualWisdomLab/.github's reusable workflow, preserving this repo's exact original policy. The harden-runner step and comment_summary_in_pr input this repo needed were added to the central workflow specifically because of this repo's own original design (see ContextualWisdomLab/.github#1732) -- this repo is the reason those two features exist centrally, not just a consumer of an unrelated change. Drops the redundant job-level permissions: block (workflow-level already covers it) and the informational "Log dependency review policy" step (no policy value beyond what the underlying action itself reports). See ContextualWisdomLab/.github#1724, #1728, #1732 and their docs/adr/0024-.../docs/doctoring/dependency-review-reusable-workflow- consolidation.md for the full audit and design rationale. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/dependency-review.yml | 38 +++---------------------- 1 file changed, 4 insertions(+), 34 deletions(-) diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index c303d1e61..7d2e7a9e7 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -8,43 +8,13 @@ on: - "release/**" workflow_dispatch: -permissions: - contents: read - pull-requests: read - concurrency: group: dependency-review-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: dependency-review: - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: read - steps: - - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 - with: - egress-policy: audit - - - name: Checkout repository - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - - name: Log dependency review policy - env: - BASE_REF: ${{ github.base_ref || github.ref_name }} - HEAD_REF: ${{ github.head_ref || github.ref_name }} - run: | - printf 'Dependency Review fails on moderate, high, or critical dependency findings.\n' - printf 'Base ref: %s\n' "$BASE_REF" - printf 'Head ref: %s\n' "$HEAD_REF" - - - name: Review dependency changes - uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 - with: - fail-on-severity: moderate - comment-summary-in-pr: never - warn-only: false + uses: ContextualWisdomLab/.github/.github/workflows/dependency-review.yml@5f8e5b2a79e709c4ab1a4179a605d34c458b13a1 + with: + fail_on_severity: moderate + comment_summary_in_pr: never From f5d94842314f1bc6cb56bf110429d3f846a09a92 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 10:03:21 +0900 Subject: [PATCH 2/5] ci(dependency-review): add missing permissions block, fix stale local- workflow test assertions Two independently-found real gaps in the migration to the central reusable dependency-review.yml: 1. This caller had no permissions: block at all (CodeQL: workflow does not contain permissions). GitHub reusable workflows cannot elevate GITHUB_TOKEN permissions through the call chain, so without this the job ran with the repo's broader default token permissions instead of the minimal contents/pull-requests read the central workflow needs. Added an explicit read-only permissions block matching the other consumer repos. 2. test_stepsecurity_remediation_adds_pinned_audit_hardening still asserted the OLD standalone workflow's internals (a direct dependency-review-action pin, BASE_REF/HEAD_REF env vars, a specific step-name split) even though the comment directly above it already said dependency-review is centralized and "intentionally not duplicated locally" (Devin Review). The migrated file is a thin workflow_call caller with none of that -- the assertions were simply never updated to match. Replaced them with checks on the caller shape that actually matters: it calls the central reusable workflow, pinned to a full 40-character commit SHA (not a mutable ref), with the read-only permissions block from fix 1. Full backend suite: 1807 passed, 32 skipped. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/dependency-review.yml | 10 ++++++++ backend/tests/test_release_governance.py | 31 +++++++++--------------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 7d2e7a9e7..93896e62d 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -8,6 +8,16 @@ on: - "release/**" workflow_dispatch: +# GitHub reusable workflows cannot elevate GITHUB_TOKEN permissions through +# the call chain -- the called workflow's own permissions: block is only a +# ceiling. Without an explicit grant here, this job runs with the repo's +# broader default token permissions instead of the minimal read-only set +# the central workflow actually needs (CodeQL: workflow does not contain +# permissions). +permissions: + contents: read + pull-requests: read + concurrency: group: dependency-review-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true diff --git a/backend/tests/test_release_governance.py b/backend/tests/test_release_governance.py index a23c70746..cc64d2d66 100644 --- a/backend/tests/test_release_governance.py +++ b/backend/tests/test_release_governance.py @@ -401,31 +401,24 @@ def test_stepsecurity_remediation_adds_pinned_audit_hardening() -> None: assert "egress-policy: block" in mail_smoke_workflow assert "allowed-endpoints:" in mail_smoke_workflow + # dependency-review.yml is now a thin caller into the org-level central + # reusable workflow (see the comment above), not a standalone workflow + # with its own hardened action pin/env vars -- assert the caller shape + # instead of the old local implementation details this test previously + # checked, which no longer exist in this file (Devin Review). dependency_review_workflow = read_repo_text( ".github/workflows/dependency-review.yml" ) assert ( - "actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0" + "uses: ContextualWisdomLab/.github/.github/workflows/dependency-review.yml@" in dependency_review_workflow ) - assert "BASE_REF: ${{ github.base_ref || github.ref_name }}" in ( - dependency_review_workflow - ) - assert "HEAD_REF: ${{ github.head_ref || github.ref_name }}" in ( - dependency_review_workflow - ) - log_dependency_review_step = dependency_review_workflow.split( - "- name: Log dependency review policy", 1 - )[1].split("- name: Review dependency changes", 1)[0] - log_dependency_review_script = log_dependency_review_step.split("run: |", 1)[1] - assert "${{ github.base_ref || github.ref_name }}" not in ( - log_dependency_review_script - ) - assert "${{ github.head_ref || github.ref_name }}" not in ( - log_dependency_review_script - ) - assert "printf 'Base ref: %s\\n' \"$BASE_REF\"" in log_dependency_review_script - assert "printf 'Head ref: %s\\n' \"$HEAD_REF\"" in log_dependency_review_script + assert re.search( + r"dependency-review\.yml@[0-9a-f]{40}\b", dependency_review_workflow + ), "the reusable workflow must be pinned to a full commit SHA, not a mutable ref" + assert "permissions:" in dependency_review_workflow + assert "contents: read" in dependency_review_workflow + assert "pull-requests: read" in dependency_review_workflow pre_commit = read_repo_text(".pre-commit-config.yaml") assert "https://github.com/gitleaks/gitleaks" in pre_commit From 72354c688f3830a3dbbb169cf7ceeffc2273a959 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 23:47:31 +0900 Subject: [PATCH 3/5] fix(ci): label reusable workflow provenance --- .github/workflows/dependency-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 93896e62d..3bd0a3f8d 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -24,7 +24,7 @@ concurrency: jobs: dependency-review: - uses: ContextualWisdomLab/.github/.github/workflows/dependency-review.yml@5f8e5b2a79e709c4ab1a4179a605d34c458b13a1 + uses: ContextualWisdomLab/.github/.github/workflows/dependency-review.yml@5f8e5b2a79e709c4ab1a4179a605d34c458b13a1 # .github#1732 (2026-09-02) with: fail_on_severity: moderate comment_summary_in_pr: never From 7bd59da818c02ce68dd87bd316bf5fa733bedcbb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 3 Sep 2026 23:59:23 +0900 Subject: [PATCH 4/5] test(governance): assert dependency-review caller config structurally CodeRabbit on #1539: the substring/regex checks for dependency-review.yml's reusable-workflow caller shape pass even if a write permission is added alongside the read ones, and say nothing about fail_on_severity or comment_summary_in_pr regressing to a weaker value. Parse the workflow YAML and assert the exact permissions mapping and with: mapping instead of scanning for substrings. Verified the new assertions against the actual file content directly before committing, then ran the full backend suite with CI's exact invocation (hash-pinned install, PYTHONWARNINGS=error): 1806 passed, 33 skipped. Co-Authored-By: Claude Sonnet 5 --- backend/tests/test_release_governance.py | 27 ++++++++++++++++-------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/backend/tests/test_release_governance.py b/backend/tests/test_release_governance.py index cc64d2d66..13bef913a 100644 --- a/backend/tests/test_release_governance.py +++ b/backend/tests/test_release_governance.py @@ -409,16 +409,25 @@ def test_stepsecurity_remediation_adds_pinned_audit_hardening() -> None: dependency_review_workflow = read_repo_text( ".github/workflows/dependency-review.yml" ) - assert ( - "uses: ContextualWisdomLab/.github/.github/workflows/dependency-review.yml@" - in dependency_review_workflow - ) - assert re.search( - r"dependency-review\.yml@[0-9a-f]{40}\b", dependency_review_workflow + # Structured equality, not substring/regex checks: a substring check for + # "contents: read" still passes if a "contents: write" line is added + # alongside it, and neither substring nor the SHA-pin regex say anything + # about fail_on_severity or comment_summary_in_pr regressing to weaker + # values (CodeRabbit, 2026-09-03). + dependency_review_config = yaml.safe_load(dependency_review_workflow) + dependency_review_job = dependency_review_config["jobs"]["dependency-review"] + assert re.fullmatch( + r"ContextualWisdomLab/\.github/\.github/workflows/dependency-review\.yml@[0-9a-f]{40}", + dependency_review_job["uses"], ), "the reusable workflow must be pinned to a full commit SHA, not a mutable ref" - assert "permissions:" in dependency_review_workflow - assert "contents: read" in dependency_review_workflow - assert "pull-requests: read" in dependency_review_workflow + assert dependency_review_config["permissions"] == { + "contents": "read", + "pull-requests": "read", + } + assert dependency_review_job["with"] == { + "fail_on_severity": "moderate", + "comment_summary_in_pr": "never", + } pre_commit = read_repo_text(".pre-commit-config.yaml") assert "https://github.com/gitleaks/gitleaks" in pre_commit From acd8a8412475a38a86c2749958b59e589de6d1e6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 20:20:56 +0900 Subject: [PATCH 5/5] ci(security): remove duplicate dependency review workflow The organization-required Security Scan already owns the exact-base/head dependency-review hard gate and check context. Remove the redundant local caller and keep a repository contract that central ownership remains explicit. Signed-off-by: Seongho Bae --- .github/workflows/dependency-review.yml | 30 -------------------- backend/tests/test_release_governance.py | 36 ++++++------------------ 2 files changed, 8 insertions(+), 58 deletions(-) delete mode 100644 .github/workflows/dependency-review.yml diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml deleted file mode 100644 index 3bd0a3f8d..000000000 --- a/.github/workflows/dependency-review.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Dependency Review - -on: - pull_request: - branches: - - develop - - master - - "release/**" - workflow_dispatch: - -# GitHub reusable workflows cannot elevate GITHUB_TOKEN permissions through -# the call chain -- the called workflow's own permissions: block is only a -# ceiling. Without an explicit grant here, this job runs with the repo's -# broader default token permissions instead of the minimal read-only set -# the central workflow actually needs (CodeQL: workflow does not contain -# permissions). -permissions: - contents: read - pull-requests: read - -concurrency: - group: dependency-review-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -jobs: - dependency-review: - uses: ContextualWisdomLab/.github/.github/workflows/dependency-review.yml@5f8e5b2a79e709c4ab1a4179a605d34c458b13a1 # .github#1732 (2026-09-02) - with: - fail_on_severity: moderate - comment_summary_in_pr: never diff --git a/backend/tests/test_release_governance.py b/backend/tests/test_release_governance.py index 13bef913a..ea9d0e27d 100644 --- a/backend/tests/test_release_governance.py +++ b/backend/tests/test_release_governance.py @@ -401,34 +401,6 @@ def test_stepsecurity_remediation_adds_pinned_audit_hardening() -> None: assert "egress-policy: block" in mail_smoke_workflow assert "allowed-endpoints:" in mail_smoke_workflow - # dependency-review.yml is now a thin caller into the org-level central - # reusable workflow (see the comment above), not a standalone workflow - # with its own hardened action pin/env vars -- assert the caller shape - # instead of the old local implementation details this test previously - # checked, which no longer exist in this file (Devin Review). - dependency_review_workflow = read_repo_text( - ".github/workflows/dependency-review.yml" - ) - # Structured equality, not substring/regex checks: a substring check for - # "contents: read" still passes if a "contents: write" line is added - # alongside it, and neither substring nor the SHA-pin regex say anything - # about fail_on_severity or comment_summary_in_pr regressing to weaker - # values (CodeRabbit, 2026-09-03). - dependency_review_config = yaml.safe_load(dependency_review_workflow) - dependency_review_job = dependency_review_config["jobs"]["dependency-review"] - assert re.fullmatch( - r"ContextualWisdomLab/\.github/\.github/workflows/dependency-review\.yml@[0-9a-f]{40}", - dependency_review_job["uses"], - ), "the reusable workflow must be pinned to a full commit SHA, not a mutable ref" - assert dependency_review_config["permissions"] == { - "contents": "read", - "pull-requests": "read", - } - assert dependency_review_job["with"] == { - "fail_on_severity": "moderate", - "comment_summary_in_pr": "never", - } - pre_commit = read_repo_text(".pre-commit-config.yaml") assert "https://github.com/gitleaks/gitleaks" in pre_commit assert "rev: v8.16.3" in pre_commit @@ -442,6 +414,14 @@ def test_stepsecurity_remediation_adds_pinned_audit_hardening() -> None: assert "rev: v2.17.2" in pre_commit +def test_dependency_review_is_owned_by_the_central_required_workflow() -> None: + assert not (WORKFLOW_DIR / "dependency-review.yml").exists() + + agent_guidance = read_repo_text("AGENTS.md") + assert "central **Security Scan** required gate" in agent_guidance + assert "`dependency-review` (diff-scoped)" in agent_guidance + + def test_actionlint_recognizes_the_mail_egress_runner_label() -> None: actionlint_config = read_repo_text(".github/actionlint.yaml")