This commit is contained in:
@@ -0,0 +1,35 @@
|
|||||||
|
name: Verify Protocol
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: protocol-${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
verify:
|
||||||
|
# The Phase 3A full verifier includes Swift type-checking. The owner must
|
||||||
|
# provide a macOS runner with the pinned toolchain from the handoff.
|
||||||
|
runs-on: macos-26
|
||||||
|
timeout-minutes: 30
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
- name: Assert pinned toolchain
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
python3 --version
|
||||||
|
go version | grep -F 'go1.26.5 '
|
||||||
|
protoc --version | grep -F 'libprotoc 35.1'
|
||||||
|
buf --version | grep -F '1.72.0'
|
||||||
|
rustc --version | grep -F '1.97.1'
|
||||||
|
swift --version | grep -F 'Swift version 6.3.3'
|
||||||
|
- name: Verify generation, breaking policy, scope, and conformance
|
||||||
|
run: make verify
|
||||||
|
- name: Verify clean regeneration
|
||||||
|
run: git diff --exit-code
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
.PHONY: verify generate proto-lint proto-breaking source-verify scope-verify conformance frame-verify go-test binding-compile clean-generated
|
||||||
|
|
||||||
|
PYTHON ?= python3
|
||||||
|
PROTOC ?= protoc
|
||||||
|
BUF ?= buf
|
||||||
|
|
||||||
|
generate:
|
||||||
|
$(PYTHON) tools/generate.py
|
||||||
|
$(PROTOC) --proto_path=proto --descriptor_set_out=gen/protobuf/control-v1.pb --include_imports --include_source_info proto/versevdi/control/v1/control.proto
|
||||||
|
$(PROTOC) --proto_path=proto --descriptor_set_out=gen/protobuf/tunnel-v1.pb --include_imports --include_source_info proto/versevdi/tunnel/v1/tunnel.proto
|
||||||
|
|
||||||
|
proto-lint:
|
||||||
|
$(BUF) lint
|
||||||
|
|
||||||
|
proto-breaking:
|
||||||
|
$(BUF) breaking proto --against $(abspath gen/protobuf/phase3a-baseline.binpb) --limit-to-input-files
|
||||||
|
|
||||||
|
source-verify:
|
||||||
|
$(PYTHON) -B tools/validate.py
|
||||||
|
$(PYTHON) -B tools/fixture_digest.py
|
||||||
|
|
||||||
|
scope-verify:
|
||||||
|
$(PYTHON) -B tools/check_scope.py
|
||||||
|
|
||||||
|
go-test:
|
||||||
|
go test ./gen/go/... ./tests/go
|
||||||
|
|
||||||
|
binding-compile:
|
||||||
|
rustc --crate-type lib gen/rust/protocol.rs -o /tmp/versevdi-protocol-generated.rlib
|
||||||
|
swiftc -typecheck gen/swift/Protocol.swift
|
||||||
|
|
||||||
|
conformance:
|
||||||
|
$(PYTHON) -B tools/fixture_digest.py
|
||||||
|
go run ./tools/go-conformance
|
||||||
|
$(PYTHON) tools/run_native_conformance.py
|
||||||
|
|
||||||
|
frame-verify:
|
||||||
|
$(PYTHON) tools/validate_frames.py
|
||||||
|
|
||||||
|
clean-generated:
|
||||||
|
$(PYTHON) tools/generate.py --check
|
||||||
|
|
||||||
|
verify: generate proto-lint proto-breaking source-verify scope-verify go-test binding-compile conformance frame-verify clean-generated
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
Binary file not shown.
@@ -0,0 +1,114 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Check Phase 3A source boundaries, generated provenance, and secret canaries."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import pathlib
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = pathlib.Path(__file__).resolve().parents[1]
|
||||||
|
FORBIDDEN_WIRE_FIELDS = {
|
||||||
|
"provider_url",
|
||||||
|
"vm_address",
|
||||||
|
"machine_id",
|
||||||
|
"machine_address",
|
||||||
|
"direct_host",
|
||||||
|
"private_key",
|
||||||
|
"pairing_key",
|
||||||
|
"rtsp_url",
|
||||||
|
"gamestream",
|
||||||
|
"apollo_url",
|
||||||
|
"windows_password",
|
||||||
|
}
|
||||||
|
SECRET_PATTERNS = (
|
||||||
|
re.compile(rb"-----BEGIN [A-Z ]+PRIVATE KEY-----"),
|
||||||
|
re.compile(rb"\bAKIA[0-9A-Z]{16}\b"),
|
||||||
|
re.compile(rb"\bgh[pousr]_[A-Za-z0-9]{20,}\b"),
|
||||||
|
re.compile(rb"\bsk-[A-Za-z0-9]{20,}\b"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def fail(message: str) -> None:
|
||||||
|
raise ValueError(message)
|
||||||
|
|
||||||
|
|
||||||
|
def check_generated_provenance() -> None:
|
||||||
|
schema_path = ROOT / "schemas/control-v1.schema.json"
|
||||||
|
generator_path = ROOT / "tools/generate.py"
|
||||||
|
manifest = json.loads((ROOT / "gen/manifest.json").read_text(encoding="utf-8"))
|
||||||
|
schema_hash = hashlib.sha256(schema_path.read_bytes()).hexdigest()
|
||||||
|
generator_hash = hashlib.sha256(generator_path.read_bytes()).hexdigest()
|
||||||
|
if manifest.get("schema_sha256") != schema_hash:
|
||||||
|
fail("generated manifest schema hash is stale")
|
||||||
|
if manifest.get("generator_sha256") != generator_hash:
|
||||||
|
fail("generated manifest generator hash is stale")
|
||||||
|
if manifest.get("protocol_version") != (ROOT / "VERSION").read_text(encoding="utf-8").strip():
|
||||||
|
fail("generated manifest protocol version is stale")
|
||||||
|
|
||||||
|
|
||||||
|
def check_manifest_schema() -> None:
|
||||||
|
schema = json.loads((ROOT / "schemas/control-v1.schema.json").read_text(encoding="utf-8"))
|
||||||
|
definitions = schema.get("$defs", {})
|
||||||
|
for name in ("ConnectionManifest", "ManifestGateway", "ManifestTunnel", "ManifestProfile", "ManifestBounds", "GrantReference"):
|
||||||
|
properties = definitions.get(name, {}).get("properties", {})
|
||||||
|
forbidden = sorted(FORBIDDEN_WIRE_FIELDS.intersection(properties))
|
||||||
|
if forbidden:
|
||||||
|
fail(f"{name} exposes forbidden wire fields: {forbidden}")
|
||||||
|
|
||||||
|
|
||||||
|
def check_text_boundaries() -> None:
|
||||||
|
paths = [
|
||||||
|
ROOT / "openapi/control-v1.yaml",
|
||||||
|
ROOT / "schemas/control-v1.schema.json",
|
||||||
|
ROOT / "proto/versevdi/control/v1/control.proto",
|
||||||
|
ROOT / "proto/versevdi/tunnel/v1/tunnel.proto",
|
||||||
|
ROOT / "frames/datagram-v1.md",
|
||||||
|
ROOT / "frames/registry.json",
|
||||||
|
ROOT / "registries/features.json",
|
||||||
|
ROOT / "registries/datagrams.json",
|
||||||
|
]
|
||||||
|
for path in paths:
|
||||||
|
text = path.read_text(encoding="utf-8").lower()
|
||||||
|
for field in FORBIDDEN_WIRE_FIELDS:
|
||||||
|
if field in text:
|
||||||
|
fail(f"{path.relative_to(ROOT)} contains forbidden wire field {field}")
|
||||||
|
|
||||||
|
generated_paths = list((ROOT / "gen").rglob("*"))
|
||||||
|
for path in generated_paths:
|
||||||
|
if not path.is_file() or path.name == "manifest.json" or path.suffix in {".pb", ".binpb"}:
|
||||||
|
continue
|
||||||
|
text = path.read_text(encoding="utf-8").lower()
|
||||||
|
for field in FORBIDDEN_WIRE_FIELDS:
|
||||||
|
if field in text:
|
||||||
|
fail(f"generated output {path.relative_to(ROOT)} contains forbidden wire field {field}")
|
||||||
|
|
||||||
|
|
||||||
|
def check_secret_canaries() -> None:
|
||||||
|
for path in ROOT.rglob("*"):
|
||||||
|
if not path.is_file() or ".git" in path.parts or path.name in {"LICENSE"}:
|
||||||
|
continue
|
||||||
|
data = path.read_bytes()
|
||||||
|
for pattern in SECRET_PATTERNS:
|
||||||
|
if pattern.search(data):
|
||||||
|
fail(f"secret canary matched in {path.relative_to(ROOT)}")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
check_generated_provenance()
|
||||||
|
check_manifest_schema()
|
||||||
|
check_text_boundaries()
|
||||||
|
check_secret_canaries()
|
||||||
|
print("Protocol scope and provenance validation passed")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
raise SystemExit(main())
|
||||||
|
except (OSError, ValueError, json.JSONDecodeError) as exc:
|
||||||
|
print(f"scope: {exc}", file=sys.stderr)
|
||||||
|
raise SystemExit(1)
|
||||||
Reference in New Issue
Block a user