diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5f5f2f5a..6aedff12 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,6 +43,18 @@ jobs: - name: Substrate Purity Verification run: git clean -xfd -e .uv_cache + - name: Checkout coreason-manifest + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + repository: CoReason-AI/coreason-manifest + ref: develop + path: coreason-manifest + token: ${{ secrets.GHCR_PAT || secrets.GITHUB_TOKEN }} + + - name: Link coreason-manifest as sibling + run: ln -sfn $(pwd)/coreason-manifest ../coreason-manifest + shell: bash + - name: Install uv uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: @@ -134,6 +146,18 @@ jobs: - name: Substrate Purity Verification run: git clean -xfd -e .uv_cache + - name: Checkout coreason-manifest + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + repository: CoReason-AI/coreason-manifest + ref: develop + path: coreason-manifest + token: ${{ secrets.GHCR_PAT || secrets.GITHUB_TOKEN }} + + - name: Link coreason-manifest as sibling + run: ln -sfn $(pwd)/coreason-manifest ../coreason-manifest + shell: bash + - name: Install uv uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: @@ -187,6 +211,18 @@ jobs: - name: Substrate Purity Verification run: git clean -xfd -e .uv_cache + - name: Checkout coreason-manifest + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + repository: CoReason-AI/coreason-manifest + ref: develop + path: coreason-manifest + token: ${{ secrets.GHCR_PAT || secrets.GITHUB_TOKEN }} + + - name: Link coreason-manifest as sibling + run: ln -sfn $(pwd)/coreason-manifest ../coreason-manifest + shell: bash + - name: Install uv uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: @@ -270,6 +306,18 @@ jobs: - name: Substrate Purity Verification run: git clean -xfd -e .uv_cache + - name: Checkout coreason-manifest + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + repository: CoReason-AI/coreason-manifest + ref: develop + path: coreason-manifest + token: ${{ secrets.GHCR_PAT || secrets.GITHUB_TOKEN }} + + - name: Link coreason-manifest as sibling + run: ln -sfn $(pwd)/coreason-manifest ../coreason-manifest + shell: bash + - name: Install uv uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: diff --git a/.github/workflows/nightly-fuzzing.yml b/.github/workflows/nightly-fuzzing.yml index a87fcbb6..b330ff50 100644 --- a/.github/workflows/nightly-fuzzing.yml +++ b/.github/workflows/nightly-fuzzing.yml @@ -25,6 +25,18 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Checkout coreason-manifest + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + repository: CoReason-AI/coreason-manifest + ref: develop + path: coreason-manifest + token: ${{ secrets.GHCR_PAT || secrets.GITHUB_TOKEN }} + + - name: Link coreason-manifest as sibling + run: ln -sfn $(pwd)/coreason-manifest ../coreason-manifest + shell: bash + - name: Install uv uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 9b79783a..efc242b4 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -48,6 +48,18 @@ jobs: fetch-depth: 0 # Required for hatch-vcs to calculate the version dynamically fetch-tags: true # Crucial for annotated tags to resolve properly during build + - name: Checkout coreason-manifest + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + repository: CoReason-AI/coreason-manifest + ref: develop + path: coreason-manifest + token: ${{ secrets.GHCR_PAT || secrets.GITHUB_TOKEN }} + + - name: Link coreason-manifest as sibling + run: ln -sfn $(pwd)/coreason-manifest ../coreason-manifest + shell: bash + - name: Install uv uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: @@ -126,6 +138,18 @@ jobs: fetch-depth: 0 fetch-tags: true + - name: Checkout coreason-manifest + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + repository: CoReason-AI/coreason-manifest + ref: develop + path: coreason-manifest + token: ${{ secrets.GHCR_PAT || secrets.GITHUB_TOKEN }} + + - name: Link coreason-manifest as sibling + run: ln -sfn $(pwd)/coreason-manifest ../coreason-manifest + shell: bash + - name: Set up QEMU uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0 diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index c81ce9c1..a426a3b7 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -44,6 +44,18 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Checkout coreason-manifest + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + repository: CoReason-AI/coreason-manifest + ref: develop + path: coreason-manifest + token: ${{ secrets.GHCR_PAT || secrets.GITHUB_TOKEN }} + + - name: Link coreason-manifest as sibling + run: ln -sfn $(pwd)/coreason-manifest ../coreason-manifest + shell: bash + - name: Install uv uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: diff --git a/.gitleaksignore b/.gitleaksignore index e1be0924..0959ffdc 100644 --- a/.gitleaksignore +++ b/.gitleaksignore @@ -1,2 +1,6 @@ 10717acb7ea4508c0697121e72b3cee25c68b256:tests/federation/test_substrate_bridge_client.py:generic-api-key:86 12f769a8c1a69bf766a60ca990e0b16024c96dc4:tests/federation/test_substrate_bridge_client.py:generic-api-key:86 + +# mock public did key used in tests +0d771bd4a807c1bc695b701230b8dfbeb25c442c:tests/federation/test_federated_capability_registry_client.py:generic-api-key:67 +0d771bd4a807c1bc695b701230b8dfbeb25c442c:tests/execution_plane/test_license_verifier.py:generic-api-key:31 diff --git a/Dockerfile b/Dockerfile index c83c7eb8..2457ea8a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,6 +23,9 @@ COPY shims/ ./shims/ # Use a wildcard to make the copy optional if .git doesn't exist in some contexts COPY .gi[t] ./.git/ +# Copy local manifest dependency to /coreason-manifest/ to satisfy relative path dependency +COPY coreason-manifest* /coreason-manifest/ + # Install dependencies into a local .venv # Use --extra to conditionally install heavy ML dependencies (inference group) ARG EXTRAS="" diff --git a/pyproject.toml b/pyproject.toml index 0e013262..9f69cd34 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ requires-python = ">=3.14" authors = [{ name = "Gowtham A Rao", email = "gowtham.rao@coreason.ai" }] dependencies = [ "aiohttp>=3.13.4", - "coreason-manifest==0.80.0", + "coreason-manifest==0.81.0", "cytoolz>=1.1.0", "fastapi>=0.135.2", "httpx>=0.28.1", @@ -267,6 +267,8 @@ DEP002 = [ "dowhy", "econml", "inferactively-pymdp", + "cryptography", + "authlib", ] DEP001 = [ "tenseal", diff --git a/src/coreason_runtime/execution_plane/integrity.py b/src/coreason_runtime/execution_plane/integrity.py index 7e4c58fc..db52514e 100644 --- a/src/coreason_runtime/execution_plane/integrity.py +++ b/src/coreason_runtime/execution_plane/integrity.py @@ -13,7 +13,7 @@ It mathematically hashes module source code at runtime to detect "Fork-and-Patch" attacks where critical cryptographic components (like the license verifier) might have been tampered with. -CAUSAL AFFORDANCE: Prevents unauthorized modification of the core execution engine by instantly crashing the workflow +CAUSAL AFFORDANCE: Prevents unauthorized modification of the core execution engine by instantly crashing the workflow if the structural AST or source code hash does not match the official release signature. EPISTEMIC BOUNDS: Uses pure SHA-256 hashing. If the file cannot be found (e.g. frozen PyInstaller binary where source is stripped), @@ -29,9 +29,11 @@ logger = logging.getLogger(__name__) + class IntegrityViolationError(Exception): pass + def assert_module_integrity(module: ModuleType, expected_sha256: str) -> None: """ Reads the source code of a python module and verifies its SHA-256 hash. @@ -40,16 +42,18 @@ def assert_module_integrity(module: ModuleType, expected_sha256: str) -> None: try: source_code = inspect.getsource(module) except (TypeError, OSError) as e: - logger.warning(f"Could not retrieve source for {module.__name__} for integrity check (e.g. running compiled/frozen). {e}") + logger.warning( + f"Could not retrieve source for {module.__name__} for integrity check (e.g. running compiled/frozen). {e}" + ) # In a real air-gapped binary, we would rely on the OS code signature here. # We will pass for now if we can't read source to avoid crashing production binaries. return # Normalize line endings to prevent cross-platform hash mismatches (CRLF vs LF) normalized_source = source_code.replace("\r\n", "\n") - + actual_hash = hashlib.sha256(normalized_source.encode("utf-8")).hexdigest() - + if actual_hash != expected_sha256: error_msg = ( f"CRITICAL INTEGRITY VIOLATION: The module {module.__name__} has been tampered with! " @@ -57,5 +61,5 @@ def assert_module_integrity(module: ModuleType, expected_sha256: str) -> None: ) logger.critical(error_msg) raise IntegrityViolationError(error_msg) - + logger.debug(f"Integrity check passed for {module.__name__}") diff --git a/src/coreason_runtime/execution_plane/license_verifier.py b/src/coreason_runtime/execution_plane/license_verifier.py index 19b9dcdc..475be074 100644 --- a/src/coreason_runtime/execution_plane/license_verifier.py +++ b/src/coreason_runtime/execution_plane/license_verifier.py @@ -37,15 +37,19 @@ COREASON_ED25519_PUBKEY_HEX = "cd7... (stubbed pubkey)" COREASON_ML_DSA_PUBKEY_HEX = "f1a... (stubbed ml-dsa pubkey)" + class HardwareFingerprintViolationError(Exception): pass + class CryptographicVerificationError(Exception): pass + class TemporalExpirationError(Exception): pass + class LicenseVerifier: def __init__(self, local_cluster_fingerprint_hash: str | None = None): self.local_fingerprint = local_cluster_fingerprint_hash @@ -55,11 +59,13 @@ def _verify_zk_snark(self, proof: str | None) -> bool: Validates the zero-knowledge proof that the running hardware matches the one authorized in the receipt. """ if not proof: - return True # No hardware binding enforced - + return True # No hardware binding enforced + if not self.local_fingerprint: - raise HardwareFingerprintViolationError("Receipt requires hardware binding, but no local fingerprint provided.") - + raise HardwareFingerprintViolationError( + "Receipt requires hardware binding, but no local fingerprint provided." + ) + # In a real environment, this delegates to an OSS SNARK verifier (e.g., groth16 or plonk verifier library). logger.debug(f"Verifying zk-SNARK proof against local fingerprint {self.local_fingerprint}") # Placeholder for physical verification logic. @@ -77,7 +83,7 @@ def _verify_signature(self, receipt: CommercialOverrideReceipt) -> bool: if receipt.signature_algorithm.startswith("ML-DSA"): # Verify Post-Quantum ML-DSA using cryptography FIPS 204 bindings return True - + raise CryptographicVerificationError(f"Unsupported signature algorithm: {receipt.signature_algorithm}") def verify_and_apply(self, receipt: CommercialOverrideReceipt) -> list[str]: @@ -89,7 +95,9 @@ def verify_and_apply(self, receipt: CommercialOverrideReceipt) -> list[str]: # 1. Temporal Bounds Check current_epoch = int(time.time()) if current_epoch >= receipt.expires_at_epoch: - raise TemporalExpirationError(f"License expired at {receipt.expires_at_epoch}. Falling back to Prosperity 3.0.") + raise TemporalExpirationError( + f"License expired at {receipt.expires_at_epoch}. Falling back to Prosperity 3.0." + ) # 2. Cryptographic Signature Verification if not self._verify_signature(receipt): @@ -108,7 +116,7 @@ def verify_and_apply(self, receipt: CommercialOverrideReceipt) -> list[str]: except TemporalExpirationError as e: logger.warning(str(e)) - return [] # Fallback to frictionless default + return [] # Fallback to frictionless default except Exception as e: logger.error(f"License Verification Failed: {e}. Defaulting to Prosperity 3.0 constraints.") return [] diff --git a/src/coreason_runtime/orchestration/workflows/base_topology_workflow.py b/src/coreason_runtime/orchestration/workflows/base_topology_workflow.py index 46e83c6f..f2334acb 100644 --- a/src/coreason_runtime/orchestration/workflows/base_topology_workflow.py +++ b/src/coreason_runtime/orchestration/workflows/base_topology_workflow.py @@ -46,10 +46,10 @@ def verify_commercial_license(self, receipt_payload: dict[str, Any] | None) -> l # We skip rigid Pydantic validation if run inside the Temporal sandbox context restrictions, # but CommercialOverrideReceipt is designed to be sandbox-safe. receipt = CommercialOverrideReceipt.model_validate(receipt_payload) - + # Extract fingerprint from workflow context or environment local_fingerprint = os.getenv("COREASON_CLUSTER_FINGERPRINT", None) - + verifier = LicenseVerifier(local_fingerprint) return verifier.verify_and_apply(receipt) except Exception as e: diff --git a/tests/execution_plane/test_integrity.py b/tests/execution_plane/test_integrity.py index 5166968b..c0b4bc65 100644 --- a/tests/execution_plane/test_integrity.py +++ b/tests/execution_plane/test_integrity.py @@ -7,14 +7,14 @@ def test_integrity_check_passes_on_unmodified_module() -> None: # This should pass without raising any exception if the file hasn't been tampered with # If the file gets reformatted or changed, this hash will need to be updated. - expected_hash = "33ea767b8cb1f91a4ef692c890433b30f7c1af4267199686169f3feb2abe5953" + expected_hash = "04ce95755dfea75f705ebf15c26df16f3aff99d0dc7ea8a9b574223dbd6090a8" assert_module_integrity(license_verifier_mod, expected_hash) def test_integrity_check_fails_on_bad_hash() -> None: bad_hash = "0000000000000000000000000000000000000000000000000000000000000000" - + with pytest.raises(IntegrityViolationError) as exc_info: assert_module_integrity(license_verifier_mod, bad_hash) - + assert "CRITICAL INTEGRITY VIOLATION" in str(exc_info.value) diff --git a/tests/execution_plane/test_license_verifier.py b/tests/execution_plane/test_license_verifier.py index aff85538..99e64646 100644 --- a/tests/execution_plane/test_license_verifier.py +++ b/tests/execution_plane/test_license_verifier.py @@ -37,52 +37,58 @@ def valid_receipt() -> CommercialOverrideReceipt: expires_at_epoch=current_time + 86400, entitlements=["COMMERCIAL_USE", "IP_SOVEREIGNTY_EXCEPTION"], network_mode="private", - federation_enabled=False + federation_enabled=False, ) + def test_license_verifier_success(valid_receipt: CommercialOverrideReceipt) -> None: """Tests the physical validation of a valid ML-DSA SD-JWT receipt with matching hardware.""" # Instantiating physical verifier verifier = LicenseVerifier(local_cluster_fingerprint_hash="hardware_hash_123") - + # Execution active_entitlements = verifier.verify_and_apply(valid_receipt) - + # Assertion assert "COMMERCIAL_USE" in active_entitlements assert "IP_SOVEREIGNTY_EXCEPTION" in active_entitlements + def test_license_verifier_temporal_expiration(valid_receipt: CommercialOverrideReceipt) -> None: """Tests the Temporal Ordering bounds. Graceful fallback on expiration.""" current_time = int(time.time()) - + # Mutate receipt to be expired (bypassing model validator strictly for testing the boundary) object.__setattr__(valid_receipt, "expires_at_epoch", current_time - 3600) - + verifier = LicenseVerifier(local_cluster_fingerprint_hash="hardware_hash_123") active_entitlements = verifier.verify_and_apply(valid_receipt) - + # Verify fallback to Prosperity 3.0 (empty entitlements list) assert active_entitlements == [] + def test_license_verifier_hardware_mismatch(valid_receipt: CommercialOverrideReceipt) -> None: """Tests the zk-SNARK hardware fingerprint boundary. Fails if no fingerprint provided locally.""" # Instantiating verifier with NO hardware fingerprint (simulating a piracy attempt) verifier = LicenseVerifier(local_cluster_fingerprint_hash=None) - + active_entitlements = verifier.verify_and_apply(valid_receipt) - + # Verify fallback to Prosperity 3.0 assert active_entitlements == [] + def test_license_verifier_unsupported_crypto(valid_receipt: CommercialOverrideReceipt) -> None: """Tests the signature algorithm enforcement boundary.""" - # The signature algorithm validation in the class throws CryptographicVerificationError + # The signature algorithm validation in the class throws CryptographicVerificationError # but the generic verify_and_apply intercepts it and returns an empty list. - object.__setattr__(valid_receipt, "signature_algorithm", "RSA-2048") # Type violation in real validation, simulating an attack - + object.__setattr__( + valid_receipt, "signature_algorithm", "RSA-2048" + ) # Type violation in real validation, simulating an attack + verifier = LicenseVerifier(local_cluster_fingerprint_hash="hardware_hash_123") active_entitlements = verifier.verify_and_apply(valid_receipt) - + # Verify fallback to Prosperity 3.0 assert active_entitlements == [] diff --git a/tests/federation/test_federated_capability_registry_client.py b/tests/federation/test_federated_capability_registry_client.py index f5bcd577..6389962e 100644 --- a/tests/federation/test_federated_capability_registry_client.py +++ b/tests/federation/test_federated_capability_registry_client.py @@ -43,11 +43,11 @@ async def publish_mcp(event: dict[str, Any], request: Request) -> dict[str, Any] # Mock publishing logic, returning urn if event.get("compression_ratio") is None: raise HTTPException(status_code=400, detail="Bad Request") - + receipt_header = request.headers.get("x-coreason-license-receipt") if receipt_header == "distr_private_123": return {"urn": f"urn:coreason:capability:{event.get('crystallized_semantic_node_cid')}_private"} - + return {"urn": f"urn:coreason:capability:{event.get('crystallized_semantic_node_cid')}"} @@ -73,7 +73,7 @@ def private_receipt() -> CommercialOverrideReceipt: expires_at_epoch=current_time + 86400, entitlements=["COMMERCIAL_USE", "PRIVATE_NETWORK_FEDERATION"], network_mode="private", - federation_enabled=True + federation_enabled=True, ) @@ -107,6 +107,7 @@ async def test_publish_master_mcp_success(registry_client: FederatedCapabilityRe urn = await registry_client.publish_master_mcp(event) assert urn == "urn:coreason:capability:crystal-456" + @pytest.mark.asyncio async def test_client_ledger_switching_private(private_receipt: CommercialOverrideReceipt, monkeypatch: Any) -> None: """Tests that the client switches base URLs and injects headers when a private VCDM v2.0 receipt is provided.""" @@ -115,10 +116,10 @@ async def test_client_ledger_switching_private(private_receipt: CommercialOverri transport = httpx.ASGITransport(app=app) client = FederatedCapabilityRegistryClient(transport=transport, receipt=private_receipt) - + # Verify ledger switched to the private URL based on network_mode assert client.base_url == "http://private-ledger.local:9090" - + event = { "event_cid": "promotion-123", "crystallized_semantic_node_cid": "crystal-private-node", @@ -126,8 +127,8 @@ async def test_client_ledger_switching_private(private_receipt: CommercialOverri "timestamp": 1234567890.0, "source_episodic_event_cids": [], } - + urn = await client.publish_master_mcp(event) - + # Verify the SD-JWT/Distr receipt was injected into the headers and processed by the server assert urn == "urn:coreason:capability:crystal-private-node_private"