From e2001ddd83a219dea603269d55d9ee10d00329c0 Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Thu, 17 Sep 2026 02:38:14 +0800 Subject: [PATCH] Allow the exact Global ETF job workflow identity Co-Authored-By: Codex --- .github/workflows/vps_codex_service_ops.yml | 2 +- scripts/deploy_codex_audit_service.sh | 2 +- tests/test_oidc_reusable_workflow_auth.py | 49 +++++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/.github/workflows/vps_codex_service_ops.yml b/.github/workflows/vps_codex_service_ops.yml index 27daa7c..657de79 100644 --- a/.github/workflows/vps_codex_service_ops.yml +++ b/.github/workflows/vps_codex_service_ops.yml @@ -67,7 +67,7 @@ jobs: CODEX_AUDIT_SERVICE_ALLOWED_REFS: refs/heads/main # Exact canonical audit job plus immutable QPK `uses:` refs pinned by strategy drift callers. # Rotation tracked in #64; remove the old QPK SHA after final strategy-run verification. - CODEX_AUDIT_SERVICE_ALLOWED_JOB_WORKFLOW_REFS: QuantStrategyLab/AIAuditBridge/.github/workflows/codex_audit.yml@refs/heads/main,QuantStrategyLab/AIAuditBridge/.github/workflows/research_input_readback.yml@refs/heads/main,QuantStrategyLab/AIAuditBridge/.github/workflows/dependency_audit.yml@refs/heads/main,QuantStrategyLab/AIAuditBridge/.github/workflows/strategy_optimization_watcher.yml@refs/heads/main,QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@644cd9002ae92f2aaca6f7efb4afa4986fae05ea,QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@d0a081ca5868faaf1a6dd870cf4b93643978cd11,QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@fcddef20eea5deb876e739263042acdcb3e9cd1b,QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@4f8465b28a6787d39d21e50f9d95a77841d6ad56,QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@651c9ac4f37ce6e7fe1bac84dc7646cd5abc9e6e,QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@bb91e4cad1a32d58dc0e4023f1cbe0c7ec150d81,QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@6b887d9954eb656141597eac077ca22053a525ef,QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@b939522701b527096e5d028346e777714466bcc5,QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@8b8ce3543c04bf12920e38b0f45d2b32a8896580 + CODEX_AUDIT_SERVICE_ALLOWED_JOB_WORKFLOW_REFS: QuantStrategyLab/AIAuditBridge/.github/workflows/codex_audit.yml@refs/heads/main,QuantStrategyLab/AIAuditBridge/.github/workflows/research_input_readback.yml@refs/heads/main,QuantStrategyLab/AIAuditBridge/.github/workflows/dependency_audit.yml@refs/heads/main,QuantStrategyLab/AIAuditBridge/.github/workflows/strategy_optimization_watcher.yml@refs/heads/main,QuantStrategyLab/AIAuditBridge/.github/workflows/global_etf_research_codegen.yml@refs/heads/main,QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@644cd9002ae92f2aaca6f7efb4afa4986fae05ea,QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@d0a081ca5868faaf1a6dd870cf4b93643978cd11,QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@fcddef20eea5deb876e739263042acdcb3e9cd1b,QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@4f8465b28a6787d39d21e50f9d95a77841d6ad56,QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@651c9ac4f37ce6e7fe1bac84dc7646cd5abc9e6e,QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@bb91e4cad1a32d58dc0e4023f1cbe0c7ec150d81,QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@6b887d9954eb656141597eac077ca22053a525ef,QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@b939522701b527096e5d028346e777714466bcc5,QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@8b8ce3543c04bf12920e38b0f45d2b32a8896580 CODEX_AUDIT_SERVICE_ALLOWED_DIRECT_REPOSITORIES: QuantStrategyLab/AIAuditBridge CODEX_AUDIT_SERVICE_ALLOWED_SOURCE_REPOSITORIES: QuantStrategyLab/AIAuditBridge,QuantStrategyLab/BinancePlatform,QuantStrategyLab/CharlesSchwabPlatform,QuantStrategyLab/CnEquitySnapshotPipelines,QuantStrategyLab/CnEquityStrategies,QuantStrategyLab/CryptoLivePoolPipelines,QuantStrategyLab/CryptoStrategies,QuantStrategyLab/FirstradePlatform,QuantStrategyLab/HkEquitySnapshotPipelines,QuantStrategyLab/HkEquityStrategies,QuantStrategyLab/IBKRGatewayManager,QuantStrategyLab/InteractiveBrokersPlatform,QuantStrategyLab/LongBridgePlatform,QuantStrategyLab/MarketSignalSources,QuantStrategyLab/PoliticalEventTrackingResearch,QuantStrategyLab/QmtPlatform,QuantStrategyLab/QuantAdvisorResearch,QuantStrategyLab/QuantPlatformKit,QuantStrategyLab/QuantRuntimeSettings,QuantStrategyLab/QuantStrategyPlugins,QuantStrategyLab/ResearchSignalContextPipelines,QuantStrategyLab/SchwabTokenAutoRefresher,QuantStrategyLab/UsEquitySnapshotPipelines,QuantStrategyLab/UsEquityStrategies CODEX_AUDIT_SERVICE_MODEL: ${{ vars.CODEX_AUDIT_SERVICE_MODEL }} diff --git a/scripts/deploy_codex_audit_service.sh b/scripts/deploy_codex_audit_service.sh index 431acd3..bee1dd5 100644 --- a/scripts/deploy_codex_audit_service.sh +++ b/scripts/deploy_codex_audit_service.sh @@ -17,7 +17,7 @@ ALLOWED_REPOSITORY_VISIBILITIES="${CODEX_AUDIT_SERVICE_ALLOWED_REPOSITORY_VISIBI # Exact canonical audit job identity. Single source of truth for delegated drift code follows. # Rotation #64 removes the old SHA by 2026-07-18. # Rotate with the two-SHA procedure in docs/drift_oidc_rotation.md. -ALLOWED_JOB_WORKFLOW_REFS="${CODEX_AUDIT_SERVICE_ALLOWED_JOB_WORKFLOW_REFS:-QuantStrategyLab/AIAuditBridge/.github/workflows/codex_audit.yml@refs/heads/main,QuantStrategyLab/AIAuditBridge/.github/workflows/research_input_readback.yml@refs/heads/main,QuantStrategyLab/AIAuditBridge/.github/workflows/dependency_audit.yml@refs/heads/main,QuantStrategyLab/AIAuditBridge/.github/workflows/strategy_optimization_watcher.yml@refs/heads/main,QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@644cd9002ae92f2aaca6f7efb4afa4986fae05ea,QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@d0a081ca5868faaf1a6dd870cf4b93643978cd11,QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@fcddef20eea5deb876e739263042acdcb3e9cd1b,QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@4f8465b28a6787d39d21e50f9d95a77841d6ad56,QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@651c9ac4f37ce6e7fe1bac84dc7646cd5abc9e6e,QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@bb91e4cad1a32d58dc0e4023f1cbe0c7ec150d81,QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@6b887d9954eb656141597eac077ca22053a525ef,QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@b939522701b527096e5d028346e777714466bcc5,QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@8b8ce3543c04bf12920e38b0f45d2b32a8896580}" +ALLOWED_JOB_WORKFLOW_REFS="${CODEX_AUDIT_SERVICE_ALLOWED_JOB_WORKFLOW_REFS:-QuantStrategyLab/AIAuditBridge/.github/workflows/codex_audit.yml@refs/heads/main,QuantStrategyLab/AIAuditBridge/.github/workflows/research_input_readback.yml@refs/heads/main,QuantStrategyLab/AIAuditBridge/.github/workflows/dependency_audit.yml@refs/heads/main,QuantStrategyLab/AIAuditBridge/.github/workflows/strategy_optimization_watcher.yml@refs/heads/main,QuantStrategyLab/AIAuditBridge/.github/workflows/global_etf_research_codegen.yml@refs/heads/main,QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@644cd9002ae92f2aaca6f7efb4afa4986fae05ea,QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@d0a081ca5868faaf1a6dd870cf4b93643978cd11,QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@fcddef20eea5deb876e739263042acdcb3e9cd1b,QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@4f8465b28a6787d39d21e50f9d95a77841d6ad56,QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@651c9ac4f37ce6e7fe1bac84dc7646cd5abc9e6e,QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@bb91e4cad1a32d58dc0e4023f1cbe0c7ec150d81,QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@6b887d9954eb656141597eac077ca22053a525ef,QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@b939522701b527096e5d028346e777714466bcc5,QuantStrategyLab/QuantPlatformKit/.github/workflows/reusable-drift-check.yml@8b8ce3543c04bf12920e38b0f45d2b32a8896580}" ALLOWED_DIRECT_REPOSITORIES="${CODEX_AUDIT_SERVICE_ALLOWED_DIRECT_REPOSITORIES:-QuantStrategyLab/AIAuditBridge}" ALLOWED_SOURCE_REPOSITORIES="${CODEX_AUDIT_SERVICE_ALLOWED_SOURCE_REPOSITORIES:-QuantStrategyLab/AIAuditBridge,QuantStrategyLab/BinancePlatform,QuantStrategyLab/CharlesSchwabPlatform,QuantStrategyLab/CnEquitySnapshotPipelines,QuantStrategyLab/CnEquityStrategies,QuantStrategyLab/CryptoLivePoolPipelines,QuantStrategyLab/CryptoStrategies,QuantStrategyLab/FirstradePlatform,QuantStrategyLab/HkEquitySnapshotPipelines,QuantStrategyLab/HkEquityStrategies,QuantStrategyLab/IBKRGatewayManager,QuantStrategyLab/InteractiveBrokersPlatform,QuantStrategyLab/LongBridgePlatform,QuantStrategyLab/MarketSignalSources,QuantStrategyLab/PoliticalEventTrackingResearch,QuantStrategyLab/QmtPlatform,QuantStrategyLab/QuantAdvisorResearch,QuantStrategyLab/QuantPlatformKit,QuantStrategyLab/QuantRuntimeSettings,QuantStrategyLab/QuantStrategyPlugins,QuantStrategyLab/ResearchSignalContextPipelines,QuantStrategyLab/SchwabTokenAutoRefresher,QuantStrategyLab/UsEquitySnapshotPipelines,QuantStrategyLab/UsEquityStrategies}" JOB_DIR="${CODEX_AUDIT_SERVICE_JOB_DIR:-/var/lib/codex-audit-bridge/jobs}" diff --git a/tests/test_oidc_reusable_workflow_auth.py b/tests/test_oidc_reusable_workflow_auth.py index 5ce869f..953b2d9 100644 --- a/tests/test_oidc_reusable_workflow_auth.py +++ b/tests/test_oidc_reusable_workflow_auth.py @@ -5,6 +5,7 @@ import time import unittest from pathlib import Path +import re from unittest.mock import patch from service import auth @@ -159,6 +160,54 @@ def test_global_etf_codegen_workflow_is_in_both_deployment_allowlists(self) -> N self.assertIn(workflow_ref, deploy_script) self.assertIn(workflow_ref, ops_workflow) + def test_global_etf_codegen_job_workflow_ref_is_exactly_allowlisted(self) -> None: + workflow_ref = ( + "QuantStrategyLab/AIAuditBridge/.github/workflows/" + "global_etf_research_codegen.yml@refs/heads/main" + ) + payload: dict[str, object] = { + "aud": "quant-codex-audit", + "iss": auth.GITHUB_OIDC_ISSUER, + "exp": int(time.time()) + 300, + "repository": "QuantStrategyLab/AIAuditBridge", + "workflow_ref": workflow_ref, + "job_workflow_ref": workflow_ref, + "ref": "refs/heads/main", + } + env = { + "CODEX_AUDIT_SERVICE_ALLOWED_REPOSITORIES": "QuantStrategyLab/AIAuditBridge", + "CODEX_AUDIT_SERVICE_ALLOWED_WORKFLOW_REFS": workflow_ref, + "CODEX_AUDIT_SERVICE_ALLOWED_REFS": "refs/heads/main", + "CODEX_AUDIT_SERVICE_ALLOWED_DIRECT_REPOSITORIES": "QuantStrategyLab/AIAuditBridge", + "CODEX_AUDIT_SERVICE_ALLOWED_JOB_WORKFLOW_REFS": workflow_ref, + } + + self.assertEqual(self._verify(payload, env)["job_workflow_ref"], workflow_ref) + for rejected_job_workflow_ref in ( + workflow_ref.replace("refs/heads/main", "refs/heads/feature"), + workflow_ref.replace("global_etf_research_codegen.yml", "other.yml"), + ): + with self.subTest(job_workflow_ref=rejected_job_workflow_ref): + payload["job_workflow_ref"] = rejected_job_workflow_ref + with self.assertRaisesRegex(PermissionError, "job workflow ref is not allowed"): + self._verify(payload, env) + + def test_global_etf_codegen_job_workflow_ref_is_in_both_deployment_job_allowlists(self) -> None: + workflow_ref = ( + "QuantStrategyLab/AIAuditBridge/.github/workflows/" + "global_etf_research_codegen.yml@refs/heads/main" + ) + root = Path(__file__).parents[1] + deploy_script = (root / "scripts/deploy_codex_audit_service.sh").read_text() + ops_workflow = (root / ".github/workflows/vps_codex_service_ops.yml").read_text() + deploy_job_refs = re.search(r'^ALLOWED_JOB_WORKFLOW_REFS="\$\{[^:]+:-([^}]*)\}"$', deploy_script, re.MULTILINE) + ops_job_refs = re.search(r'^\s+CODEX_AUDIT_SERVICE_ALLOWED_JOB_WORKFLOW_REFS: (.+)$', ops_workflow, re.MULTILINE) + + self.assertIsNotNone(deploy_job_refs) + self.assertIsNotNone(ops_job_refs) + self.assertIn(workflow_ref, deploy_job_refs.group(1)) + self.assertIn(workflow_ref, ops_job_refs.group(1)) + def test_strategy_drift_requires_trusted_qpk_reusable_workflow(self) -> None: qpk_job_ref = ( "QuantStrategyLab/QuantPlatformKit/.github/workflows/"