From 6c9af304f7d78f59b2ab49c85fda05b87a712b68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kim=20Harjam=C3=A4ki?= Date: Sat, 4 Jul 2026 22:05:23 +0300 Subject: [PATCH 1/5] ci: harden workflow permissions and timeouts --- .github/workflows/ci.yml | 6 +++++- .github/workflows/codeql.yml | 1 + .github/workflows/pages.yml | 7 +++++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 60a9213..82a6204 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,11 +7,11 @@ on: permissions: contents: read - packages: write jobs: validate: runs-on: ubuntu-latest + timeout-minutes: 15 steps: - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 @@ -30,6 +30,10 @@ jobs: docker: runs-on: ubuntu-latest + timeout-minutes: 20 + permissions: + contents: read + packages: write needs: validate steps: - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 8beec42..aae2136 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -8,6 +8,7 @@ jobs: analyze: name: Analyze runs-on: ubuntu-latest + timeout-minutes: 30 permissions: actions: read contents: read diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 6c1d78d..f37e918 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -6,14 +6,13 @@ on: - master permissions: contents: read - pages: write - id-token: write concurrency: group: "pages" cancel-in-progress: false jobs: build: runs-on: ubuntu-latest + timeout-minutes: 15 steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 @@ -25,10 +24,14 @@ jobs: with: path: ./site deploy: + permissions: + pages: write + id-token: write environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest + timeout-minutes: 10 needs: build steps: - name: Deploy to GitHub Pages From 176281ace2ef5b8c798ba7f2d6d64efd5087e8e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kim=20Harjam=C3=A4ki?= Date: Sat, 4 Jul 2026 22:09:22 +0300 Subject: [PATCH 2/5] test(contracts): sync immutable registry release --- .../v0.1.0/artifact-manifest.schema.json | 2 +- .../cas-contracts/v0.1.0/common.schema.json | 2 +- .../v0.1.0/evaluation-result.schema.json | 2 +- .../cas-contracts/v0.1.0/manifest.json | 32 +++++++++---------- .../v0.1.0/policy-decision.schema.json | 2 +- .../v0.1.0/prompt-envelope.schema.json | 2 +- .../v0.1.0/run-event.schema.json | 2 +- .../v0.1.0/verification-result.schema.json | 2 +- .../v0.1.0/work-request.schema.json | 2 +- 9 files changed, 24 insertions(+), 24 deletions(-) diff --git a/tests/contracts/cas-contracts/v0.1.0/artifact-manifest.schema.json b/tests/contracts/cas-contracts/v0.1.0/artifact-manifest.schema.json index c399640..48c26ec 100644 --- a/tests/contracts/cas-contracts/v0.1.0/artifact-manifest.schema.json +++ b/tests/contracts/cas-contracts/v0.1.0/artifact-manifest.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://schemas.coding-autopilot.dev/v0.1/artifact-manifest.schema.json", + "$id": "https://coding-autopilot-system.github.io/cas-contracts/registry/v0.1/artifact-manifest.schema.json", "title": "ArtifactManifest", "type": "object", "allOf": [ diff --git a/tests/contracts/cas-contracts/v0.1.0/common.schema.json b/tests/contracts/cas-contracts/v0.1.0/common.schema.json index 0eec265..410c751 100644 --- a/tests/contracts/cas-contracts/v0.1.0/common.schema.json +++ b/tests/contracts/cas-contracts/v0.1.0/common.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://schemas.coding-autopilot.dev/v0.1/common.schema.json", + "$id": "https://coding-autopilot-system.github.io/cas-contracts/registry/v0.1/common.schema.json", "title": "CAS Common Definitions", "$defs": { "actor": { diff --git a/tests/contracts/cas-contracts/v0.1.0/evaluation-result.schema.json b/tests/contracts/cas-contracts/v0.1.0/evaluation-result.schema.json index 5d49d77..d5fd1a1 100644 --- a/tests/contracts/cas-contracts/v0.1.0/evaluation-result.schema.json +++ b/tests/contracts/cas-contracts/v0.1.0/evaluation-result.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://schemas.coding-autopilot.dev/v0.1/evaluation-result.schema.json", + "$id": "https://coding-autopilot-system.github.io/cas-contracts/registry/v0.1/evaluation-result.schema.json", "title": "EvaluationResult", "type": "object", "allOf": [ diff --git a/tests/contracts/cas-contracts/v0.1.0/manifest.json b/tests/contracts/cas-contracts/v0.1.0/manifest.json index 58b90c2..83fb4f9 100644 --- a/tests/contracts/cas-contracts/v0.1.0/manifest.json +++ b/tests/contracts/cas-contracts/v0.1.0/manifest.json @@ -2,44 +2,44 @@ "version": "0.1.0", "schemas": [ { - "id": "https://schemas.coding-autopilot.dev/v0.1/artifact-manifest.schema.json", + "id": "https://coding-autopilot-system.github.io/cas-contracts/registry/v0.1/artifact-manifest.schema.json", "path": "artifact-manifest.schema.json", - "sha256": "6c2192fb6ca79843361695f3dccd5ebf1dda8ce320c974db2630b0a2b78705bd" + "sha256": "6a18c8f2f279b3ef10f7705ba4e9f39b46ba5a2b2ef0d7072ce7225f06f12ea7" }, { - "id": "https://schemas.coding-autopilot.dev/v0.1/common.schema.json", + "id": "https://coding-autopilot-system.github.io/cas-contracts/registry/v0.1/common.schema.json", "path": "common.schema.json", - "sha256": "c7ce72a6f5da8394e48f2421820588a8142546962e05152997bd1e6ced994928" + "sha256": "6c86075df043ec924f6b7aa004c6a694916a6c3d7a822dda4235f1ee8368f92a" }, { - "id": "https://schemas.coding-autopilot.dev/v0.1/evaluation-result.schema.json", + "id": "https://coding-autopilot-system.github.io/cas-contracts/registry/v0.1/evaluation-result.schema.json", "path": "evaluation-result.schema.json", - "sha256": "12e8019f858dc0cda80ac8994ca1251a9a229a8642b05706227bd9c995c5799a" + "sha256": "34ff39414099d16d9aed32e416a0c6538e7738c93df01e5fb0940e042b5904d7" }, { - "id": "https://schemas.coding-autopilot.dev/v0.1/policy-decision.schema.json", + "id": "https://coding-autopilot-system.github.io/cas-contracts/registry/v0.1/policy-decision.schema.json", "path": "policy-decision.schema.json", - "sha256": "9cf259d405664560b30b7afd25d08d888337fd9a81a6a388ceeb36cbf7edcc33" + "sha256": "32d29407ae471c153705b238a550fa97e1e6c9ac064a5291d5164cc3de369e5a" }, { - "id": "https://schemas.coding-autopilot.dev/v0.1/prompt-envelope.schema.json", + "id": "https://coding-autopilot-system.github.io/cas-contracts/registry/v0.1/prompt-envelope.schema.json", "path": "prompt-envelope.schema.json", - "sha256": "58a991862031f40c2ffcc073743776d05206684534fac2c20c67c274fbf05c84" + "sha256": "3d41fcde3223f208e9c268d88474979de6f315ca7fc82c98207dfff3e6d83471" }, { - "id": "https://schemas.coding-autopilot.dev/v0.1/run-event.schema.json", + "id": "https://coding-autopilot-system.github.io/cas-contracts/registry/v0.1/run-event.schema.json", "path": "run-event.schema.json", - "sha256": "1bcaee2ff546439ea81d64d57dcb51cd9f57dd790fe300fd90e251823e27a36f" + "sha256": "d03702b78d431cdb05b81ca5f44c5dc0301619660dcf149ae3024bd1ad8b1e3a" }, { - "id": "https://schemas.coding-autopilot.dev/v0.1/verification-result.schema.json", + "id": "https://coding-autopilot-system.github.io/cas-contracts/registry/v0.1/verification-result.schema.json", "path": "verification-result.schema.json", - "sha256": "aeff3d41eeb99b3017b460c2a2a8d07b4e1c828830b72853d5f41d93d6abe012" + "sha256": "a66c5b02b7791f4b163c6b2b0dfd476ff60dde67246f3bfcf372f416dc1de253" }, { - "id": "https://schemas.coding-autopilot.dev/v0.1/work-request.schema.json", + "id": "https://coding-autopilot-system.github.io/cas-contracts/registry/v0.1/work-request.schema.json", "path": "work-request.schema.json", - "sha256": "543e1dd2313cff2a1ebeb1470db3085b89c3933694231eb5b4798096f147e4a7" + "sha256": "e4648cfc04494030b26cc8a1cf79fbe92bb9ad841316e0b920f3a15c997967f3" } ] } diff --git a/tests/contracts/cas-contracts/v0.1.0/policy-decision.schema.json b/tests/contracts/cas-contracts/v0.1.0/policy-decision.schema.json index 38deb32..2fd894c 100644 --- a/tests/contracts/cas-contracts/v0.1.0/policy-decision.schema.json +++ b/tests/contracts/cas-contracts/v0.1.0/policy-decision.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://schemas.coding-autopilot.dev/v0.1/policy-decision.schema.json", + "$id": "https://coding-autopilot-system.github.io/cas-contracts/registry/v0.1/policy-decision.schema.json", "title": "PolicyDecision", "type": "object", "allOf": [ diff --git a/tests/contracts/cas-contracts/v0.1.0/prompt-envelope.schema.json b/tests/contracts/cas-contracts/v0.1.0/prompt-envelope.schema.json index 4f3f395..87db4a6 100644 --- a/tests/contracts/cas-contracts/v0.1.0/prompt-envelope.schema.json +++ b/tests/contracts/cas-contracts/v0.1.0/prompt-envelope.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://schemas.coding-autopilot.dev/v0.1/prompt-envelope.schema.json", + "$id": "https://coding-autopilot-system.github.io/cas-contracts/registry/v0.1/prompt-envelope.schema.json", "title": "PromptEnvelope", "type": "object", "allOf": [ diff --git a/tests/contracts/cas-contracts/v0.1.0/run-event.schema.json b/tests/contracts/cas-contracts/v0.1.0/run-event.schema.json index 204c518..85d190f 100644 --- a/tests/contracts/cas-contracts/v0.1.0/run-event.schema.json +++ b/tests/contracts/cas-contracts/v0.1.0/run-event.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://schemas.coding-autopilot.dev/v0.1/run-event.schema.json", + "$id": "https://coding-autopilot-system.github.io/cas-contracts/registry/v0.1/run-event.schema.json", "title": "RunEvent", "type": "object", "allOf": [ diff --git a/tests/contracts/cas-contracts/v0.1.0/verification-result.schema.json b/tests/contracts/cas-contracts/v0.1.0/verification-result.schema.json index 178df29..f69ad66 100644 --- a/tests/contracts/cas-contracts/v0.1.0/verification-result.schema.json +++ b/tests/contracts/cas-contracts/v0.1.0/verification-result.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://schemas.coding-autopilot.dev/v0.1/verification-result.schema.json", + "$id": "https://coding-autopilot-system.github.io/cas-contracts/registry/v0.1/verification-result.schema.json", "title": "VerificationResult", "type": "object", "allOf": [ diff --git a/tests/contracts/cas-contracts/v0.1.0/work-request.schema.json b/tests/contracts/cas-contracts/v0.1.0/work-request.schema.json index 16d9da3..bf30df4 100644 --- a/tests/contracts/cas-contracts/v0.1.0/work-request.schema.json +++ b/tests/contracts/cas-contracts/v0.1.0/work-request.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://schemas.coding-autopilot.dev/v0.1/work-request.schema.json", + "$id": "https://coding-autopilot-system.github.io/cas-contracts/registry/v0.1/work-request.schema.json", "title": "WorkRequest", "type": "object", "allOf": [ From feb4153f2f07d3363dbd7009b624c6f7b31bf173 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kim=20Harjam=C3=A4ki?= Date: Sat, 4 Jul 2026 22:19:37 +0300 Subject: [PATCH 3/5] fix(foundry): preserve workflow failure causes --- src/cas_reference_product/workflow.py | 4 ++-- tests/test_service_factory.py | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/cas_reference_product/workflow.py b/src/cas_reference_product/workflow.py index 7354210..f9c288b 100644 --- a/src/cas_reference_product/workflow.py +++ b/src/cas_reference_product/workflow.py @@ -56,8 +56,8 @@ def run(self, envelope: PromptEnvelope) -> str: } }, ) - except Exception: - raise WorkflowAgentServiceError("Foundry workflow invocation failed") from None + except Exception as error: + raise WorkflowAgentServiceError("Foundry workflow invocation failed") from error return response.output_text diff --git a/tests/test_service_factory.py b/tests/test_service_factory.py index 84cbe06..a0d24f2 100644 --- a/tests/test_service_factory.py +++ b/tests/test_service_factory.py @@ -88,5 +88,10 @@ def test_foundry_service_sanitizes_sdk_failure(envelope: "Any") -> None: ) service = FoundryWorkflowAgentService(settings) - with pytest.raises(WorkflowAgentServiceError, match="Foundry workflow invocation failed"): + with pytest.raises( + WorkflowAgentServiceError, match="Foundry workflow invocation failed" + ) as caught: service.run(envelope) + + assert isinstance(caught.value.__cause__, RuntimeError) + assert str(caught.value.__cause__) == "sensitive provider detail" From abc20e3acb812c96554ef1c82aa28655038c5216 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kim=20Harjam=C3=A4ki?= Date: Sat, 4 Jul 2026 23:05:31 +0300 Subject: [PATCH 4/5] fix(api): preserve workflow failure chaining --- src/cas_reference_product/app.py | 10 ++++++---- tests/test_api.py | 24 +++++++++++++++++++++++- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/cas_reference_product/app.py b/src/cas_reference_product/app.py index 7eaac9c..3d6b22a 100644 --- a/src/cas_reference_product/app.py +++ b/src/cas_reference_product/app.py @@ -13,6 +13,10 @@ _tracer = trace.get_tracer(__name__) +def _raise_workflow_backend_failure(error: WorkflowAgentServiceError) -> None: + raise HTTPException(status_code=502, detail="Workflow backend request failed") from error + + def create_app(settings: Settings | None = None) -> FastAPI: app_settings = settings or get_settings() @@ -55,7 +59,7 @@ def execute(envelope: PromptEnvelope, request: Request) -> WorkflowResult: orchestrator = WorkflowOrchestrator(service, app_settings.repository) try: result = orchestrator.execute(envelope) - except WorkflowAgentServiceError: + except WorkflowAgentServiceError as error: span.add_event( "workflow.failed", attributes={ @@ -64,9 +68,7 @@ def execute(envelope: PromptEnvelope, request: Request) -> WorkflowResult: "error": True, }, ) - raise HTTPException( - status_code=502, detail="Workflow backend request failed" - ) from None + _raise_workflow_backend_failure(error) span.add_event( "workflow.completed", attributes={ diff --git a/tests/test_api.py b/tests/test_api.py index 6aa26c2..6613f47 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,9 +1,11 @@ from typing import Any from unittest.mock import patch +import pytest +from fastapi import HTTPException from fastapi.testclient import TestClient -from cas_reference_product.app import create_app +from cas_reference_product.app import _raise_workflow_backend_failure, create_app from cas_reference_product.config import Settings from cas_reference_product.workflow import WorkflowAgentServiceError @@ -69,3 +71,23 @@ def test_workflow_api_sanitizes_external_service_failures(envelope: "Any") -> No assert response.status_code == 502 assert response.json() == {"detail": "Workflow backend request failed"} assert "sensitive" not in response.text + + +def test_workflow_backend_failure_preserves_exception_chain() -> None: + with pytest.raises(HTTPException) as caught: + try: + raise RuntimeError("sensitive provider detail") + except RuntimeError as provider_error: + try: + raise WorkflowAgentServiceError("Foundry workflow invocation failed") from ( + provider_error + ) + except WorkflowAgentServiceError as workflow_error: + _raise_workflow_backend_failure(workflow_error) + + assert caught.value.status_code == 502 + assert caught.value.detail == "Workflow backend request failed" + assert isinstance(caught.value.__cause__, WorkflowAgentServiceError) + assert str(caught.value.__cause__) == "Foundry workflow invocation failed" + assert isinstance(caught.value.__cause__.__cause__, RuntimeError) + assert str(caught.value.__cause__.__cause__) == "sensitive provider detail" From 2849345ba70adb19b1764d5014f33436409f4dd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kim=20Harjam=C3=A4ki?= Date: Sun, 5 Jul 2026 10:31:32 +0300 Subject: [PATCH 5/5] fix(security): sanitize provider span failures --- src/cas_reference_product/app.py | 10 ++++------ src/cas_reference_product/workflow.py | 4 ++-- tests/test_api.py | 24 +----------------------- tests/test_service_factory.py | 3 +-- 4 files changed, 8 insertions(+), 33 deletions(-) diff --git a/src/cas_reference_product/app.py b/src/cas_reference_product/app.py index 3d6b22a..7eaac9c 100644 --- a/src/cas_reference_product/app.py +++ b/src/cas_reference_product/app.py @@ -13,10 +13,6 @@ _tracer = trace.get_tracer(__name__) -def _raise_workflow_backend_failure(error: WorkflowAgentServiceError) -> None: - raise HTTPException(status_code=502, detail="Workflow backend request failed") from error - - def create_app(settings: Settings | None = None) -> FastAPI: app_settings = settings or get_settings() @@ -59,7 +55,7 @@ def execute(envelope: PromptEnvelope, request: Request) -> WorkflowResult: orchestrator = WorkflowOrchestrator(service, app_settings.repository) try: result = orchestrator.execute(envelope) - except WorkflowAgentServiceError as error: + except WorkflowAgentServiceError: span.add_event( "workflow.failed", attributes={ @@ -68,7 +64,9 @@ def execute(envelope: PromptEnvelope, request: Request) -> WorkflowResult: "error": True, }, ) - _raise_workflow_backend_failure(error) + raise HTTPException( + status_code=502, detail="Workflow backend request failed" + ) from None span.add_event( "workflow.completed", attributes={ diff --git a/src/cas_reference_product/workflow.py b/src/cas_reference_product/workflow.py index f9c288b..7354210 100644 --- a/src/cas_reference_product/workflow.py +++ b/src/cas_reference_product/workflow.py @@ -56,8 +56,8 @@ def run(self, envelope: PromptEnvelope) -> str: } }, ) - except Exception as error: - raise WorkflowAgentServiceError("Foundry workflow invocation failed") from error + except Exception: + raise WorkflowAgentServiceError("Foundry workflow invocation failed") from None return response.output_text diff --git a/tests/test_api.py b/tests/test_api.py index 6613f47..6aa26c2 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,11 +1,9 @@ from typing import Any from unittest.mock import patch -import pytest -from fastapi import HTTPException from fastapi.testclient import TestClient -from cas_reference_product.app import _raise_workflow_backend_failure, create_app +from cas_reference_product.app import create_app from cas_reference_product.config import Settings from cas_reference_product.workflow import WorkflowAgentServiceError @@ -71,23 +69,3 @@ def test_workflow_api_sanitizes_external_service_failures(envelope: "Any") -> No assert response.status_code == 502 assert response.json() == {"detail": "Workflow backend request failed"} assert "sensitive" not in response.text - - -def test_workflow_backend_failure_preserves_exception_chain() -> None: - with pytest.raises(HTTPException) as caught: - try: - raise RuntimeError("sensitive provider detail") - except RuntimeError as provider_error: - try: - raise WorkflowAgentServiceError("Foundry workflow invocation failed") from ( - provider_error - ) - except WorkflowAgentServiceError as workflow_error: - _raise_workflow_backend_failure(workflow_error) - - assert caught.value.status_code == 502 - assert caught.value.detail == "Workflow backend request failed" - assert isinstance(caught.value.__cause__, WorkflowAgentServiceError) - assert str(caught.value.__cause__) == "Foundry workflow invocation failed" - assert isinstance(caught.value.__cause__.__cause__, RuntimeError) - assert str(caught.value.__cause__.__cause__) == "sensitive provider detail" diff --git a/tests/test_service_factory.py b/tests/test_service_factory.py index a0d24f2..f498f19 100644 --- a/tests/test_service_factory.py +++ b/tests/test_service_factory.py @@ -93,5 +93,4 @@ def test_foundry_service_sanitizes_sdk_failure(envelope: "Any") -> None: ) as caught: service.run(envelope) - assert isinstance(caught.value.__cause__, RuntimeError) - assert str(caught.value.__cause__) == "sensitive provider detail" + assert caught.value.__cause__ is None