Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/actionlint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Source validation only; labels do not attest runtime isolation.
self-hosted-runner:
labels:
- cwlab-ci-isolated
- cwlab-control
12 changes: 9 additions & 3 deletions .github/workflows/codeql-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ jobs:
name: Detect CodeQL languages
# Draft PRs get no runner; ready_for_review re-runs this on the same head.
if: (github.event.action != 'closed') && (github.event.pull_request.draft != true || github.event.action == 'converted_to_draft' || github.event.action == 'closed')
runs-on: ${{ fromJSON(github.workflow_ref == 'ContextualWisdomLab/.github/.github/workflows/codeql-pr.yml@refs/heads/main' && '{"group":"CWL central control","labels":["self-hosted","linux","x64"]}' || '"ubuntu-24.04"') }}
runs-on:
group: ${{ (fromJSON(github.workflow_ref == 'ContextualWisdomLab/.github/.github/workflows/codeql-pr.yml@refs/heads/main' && '{"group":"CWL central control","labels":["self-hosted","linux","x64"]}' || '{"group":"CWL CI isolated","labels":["self-hosted","linux","x64","cwlab-ci-isolated"]}')).group }}
labels: ${{ (fromJSON(github.workflow_ref == 'ContextualWisdomLab/.github/.github/workflows/codeql-pr.yml@refs/heads/main' && '{"group":"CWL central control","labels":["self-hosted","linux","x64"]}' || '{"group":"CWL CI isolated","labels":["self-hosted","linux","x64","cwlab-ci-isolated"]}')).labels }}
timeout-minutes: 5
permissions:
contents: read
Expand Down Expand Up @@ -164,7 +166,9 @@ jobs:
# dependency exactly; the only case where it's genuinely skipped is a
# closed PR, where this job being implicitly skipped too is fine because
# closed PRs need no required check.
runs-on: ${{ fromJSON(github.workflow_ref == 'ContextualWisdomLab/.github/.github/workflows/codeql-pr.yml@refs/heads/main' && '{"group":"CWL central control","labels":["self-hosted","linux","x64"]}' || '"ubuntu-24.04"') }}
runs-on:
group: ${{ (fromJSON(github.workflow_ref == 'ContextualWisdomLab/.github/.github/workflows/codeql-pr.yml@refs/heads/main' && '{"group":"CWL central control","labels":["self-hosted","linux","x64"]}' || '{"group":"CWL CI isolated","labels":["self-hosted","linux","x64","cwlab-ci-isolated"]}')).group }}
labels: ${{ (fromJSON(github.workflow_ref == 'ContextualWisdomLab/.github/.github/workflows/codeql-pr.yml@refs/heads/main' && '{"group":"CWL central control","labels":["self-hosted","linux","x64"]}' || '{"group":"CWL CI isolated","labels":["self-hosted","linux","x64","cwlab-ci-isolated"]}')).labels }}
# Verdict reads have exceeded five minutes; retain the ten-minute control budget.
timeout-minutes: 10
permissions:
Expand Down Expand Up @@ -367,7 +371,9 @@ jobs:
&& github.event.pull_request.state != 'closed'
&& needs.detect-languages.result == 'success'
&& needs.detect-languages.outputs.code == 'true'
runs-on: ${{ fromJSON(github.workflow_ref == 'ContextualWisdomLab/.github/.github/workflows/codeql-pr.yml@refs/heads/main' && '{"group":"CWL central control","labels":["self-hosted","linux","x64"]}' || '"ubuntu-24.04"') }}
runs-on:
group: ${{ (fromJSON(github.workflow_ref == 'ContextualWisdomLab/.github/.github/workflows/codeql-pr.yml@refs/heads/main' && '{"group":"CWL central control","labels":["self-hosted","linux","x64"]}' || '{"group":"CWL CI isolated","labels":["self-hosted","linux","x64","cwlab-ci-isolated"]}')).group }}
labels: ${{ (fromJSON(github.workflow_ref == 'ContextualWisdomLab/.github/.github/workflows/codeql-pr.yml@refs/heads/main' && '{"group":"CWL central control","labels":["self-hosted","linux","x64"]}' || '{"group":"CWL CI isolated","labels":["self-hosted","linux","x64","cwlab-ci-isolated"]}')).labels }}
timeout-minutes: 5
permissions:
contents: read
Expand Down
12 changes: 10 additions & 2 deletions .github/workflows/package-description-boundary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,11 @@ permissions:

jobs:
build-distribution:
runs-on: ubuntu-latest
# Caller-controlled build backends run only on the disposable isolated pool.
# Never fall back to a GitHub-hosted runner for untrusted package code.
runs-on:
group: CWL CI isolated
labels: [self-hosted, linux, x64, cwlab-ci-isolated]
timeout-minutes: 10
steps:
- name: Check out the calling repository
Expand Down Expand Up @@ -127,7 +131,11 @@ jobs:
package-description-boundary:
needs: build-distribution
if: ${{ always() }}
runs-on: ubuntu-latest
# The trusted inspector also stays on the isolated pool. Missing capacity
# must remain an incomplete check, never a hosted fallback or silent pass.
runs-on:
group: CWL CI isolated
labels: [self-hosted, linux, x64, cwlab-ci-isolated]
timeout-minutes: 10
steps:
- name: Reject a failed input producer
Expand Down
15 changes: 15 additions & 0 deletions docs/doctoring/package-description-boundary.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,3 +167,18 @@ metadata descriptions must be identical or the gate fails closed. Published Mark
links using GitHub's `blob/main`, `tree/main`, `master`, or `develop` forms are
blocking `mutable-release-link` findings; release tags and exact commits remain
valid.

## Runner boundary

Both jobs in `.github/workflows/package-description-boundary.yml` now require
`CWL CI isolated` with `self-hosted`, `linux`, `x64`, and
`cwlab-ci-isolated` labels. The producer executes caller-controlled packaging
code; the inspector executes the trusted central gate. Neither job has a
GitHub-hosted fallback. Missing isolated capacity therefore remains incomplete
hosted evidence and cannot become a silent pass.

This is a package-gate slice of the broader all-workflow migration owned by
`ContextualWisdomLab/.github#2565`. It does not provision runners, widen group
access, or prove disposable cleanup. A real caller can be accepted only after
operator-provided group access, isolated capacity, cleanup canary, and fresh
exact-head terminal checks.
20 changes: 17 additions & 3 deletions tests/test_audit_org_codeql_coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,9 +263,13 @@ def test_null_latest_analysis_is_not_coverage() -> None:


def test_audit_codeql_coverage_defaults_now_to_current_time() -> None:
repositories = [covered_by_recent_analysis("DefaultNowRepo", days_ago=0)]
"""Exercise the real default clock with a genuinely current analysis."""
repository = covered_by_recent_analysis("DefaultNowRepo", days_ago=0)
repository["latest_codeql_analysis"]["created_at"] = datetime.now(
timezone.utc
).isoformat()

assert audit.audit_codeql_coverage(repositories) == []
assert audit.audit_codeql_coverage([repository]) == []


def test_load_payload_reads_from_stdin(monkeypatch) -> None:
Expand Down Expand Up @@ -299,7 +303,17 @@ def test_main_fail_path_reports_gaps_from_stdin(monkeypatch, capsys) -> None:
assert "FAIL: 1 repositories have no CodeQL coverage" in captured.err


def test_main_pass_path_reports_from_file_arg(tmp_path, capsys) -> None:
def test_main_pass_path_reports_from_file_arg(tmp_path, capsys, monkeypatch) -> None:
"""Keep the CLI freshness witness bound to the fixture's reference clock."""
class FixedDatetime(datetime):
"""Supply the same reference instant used by the analysis fixture."""

@classmethod
def now(cls, tz=None):
"""Return the fixture clock in the caller's requested timezone."""
return NOW.astimezone(tz) if tz is not None else NOW.replace(tzinfo=None)

monkeypatch.setattr(audit, "datetime", FixedDatetime)
payload_path = tmp_path / "repositories.json"
payload_path.write_text(
json.dumps([covered_by_default_setup("ELUNVERA"), covered_by_recent_analysis("Orgmetra")]),
Expand Down
9 changes: 6 additions & 3 deletions tests/test_codeql_pr_workflow_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -1147,18 +1147,21 @@ def test_codeql_moved_head_requires_forward_ancestry(tmp_path: Path) -> None:
assert enforce.returncode != 0


def test_codeql_control_routing_keeps_pr_workflows_hosted() -> None:
def test_codeql_control_routing_keeps_pr_workflows_isolated() -> None:
"""Only the trusted main revision may request the restricted control group."""
workflow = WORKFLOW_PATH.read_text(encoding="utf-8")
selectors = [line.strip() for line in workflow.splitlines() if line.strip().startswith("runs-on:")]
selectors = [line.strip() for line in workflow.splitlines() if line.startswith(" group:")]
trusted = "ContextualWisdomLab/.github/.github/workflows/codeql-pr.yml@refs/heads/main"
assert len(selectors) == 3
for selector in selectors:
assert f"fromJSON(github.workflow_ref == '{trusted}' && " in selector
choices = re.findall(r"'([^']*)'", selector)
assert choices[0] == trusted
assert json.loads(choices[1]) == {"group": "CWL central control", "labels": ["self-hosted", "linux", "x64"]}
assert json.loads(choices[2]) == "ubuntu-24.04"
assert json.loads(choices[2]) == {
"group": "CWL CI isolated",
"labels": ["self-hosted", "linux", "x64", "cwlab-ci-isolated"],
}


def test_codeql_pr_rejects_invalid_required_run_time(tmp_path: Path) -> None:
Expand Down
14 changes: 14 additions & 0 deletions tests/test_package_description_boundary.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,20 @@ def test_workflow_is_callable_only() -> None:
assert set(_workflow()[True]) == {"workflow_call"}


def test_workflow_runner_is_central_isolated_self_hosted() -> None:
"""Both jobs reject GitHub-hosted fallback and use the isolated pool."""
jobs = _workflow()["jobs"]
expected = {
"group": "CWL CI isolated",
"labels": ["self-hosted", "linux", "x64", "cwlab-ci-isolated"],
}
assert jobs["build-distribution"]["runs-on"] == expected
assert jobs["package-description-boundary"]["runs-on"] == expected
text = _WORKFLOW.read_text(encoding="utf-8")
assert "ubuntu-latest" not in text
assert "ubuntu-24.04" not in text


def test_workflow_checks_out_the_gate_at_its_own_commit() -> None:
"""The caller must run the gate revision it pinned, not whatever main holds."""
steps = _workflow()["jobs"]["package-description-boundary"]["steps"]
Expand Down
17 changes: 12 additions & 5 deletions tests/test_required_review_runner_image_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,17 @@ def test_opencode_review_dispatch_uses_explicit_supported_image(self) -> None:
unittest.main()


def test_codeql_pr_routes_trusted_main_to_control_and_pr_revisions_to_hosted() -> None:
"""Separate short metadata work from model work without granting PR runner access."""
def test_codeql_pr_routes_trusted_main_to_control_and_pr_revisions_to_isolated() -> None:
"""Keep trusted workflow control separate from isolated PR execution."""
workflow = Path(".github/workflows/codeql-pr.yml").read_text()
assert workflow.count('"group":"CWL central control"') == 3
assert workflow.count("github.workflow_ref == 'ContextualWisdomLab/.github/.github/workflows/codeql-pr.yml@refs/heads/main'") == 3
assert workflow.count("|| '\"ubuntu-24.04\"'") == 3
trusted = "github.workflow_ref == 'ContextualWisdomLab/.github/.github/workflows/codeql-pr.yml@refs/heads/main'"
selectors = [line for line in workflow.splitlines() if line.startswith(" group:")]
labels = [line for line in workflow.splitlines() if line.startswith(" labels:")]
assert len(selectors) == len(labels) == 3
for selector, label in zip(selectors, labels, strict=True):
assert trusted in selector and trusted in label
assert '"group":"CWL central control"' in selector
assert '"group":"CWL CI isolated"' in selector
assert '"cwlab-ci-isolated"' in selector and '"cwlab-ci-isolated"' in label
assert '"ubuntu-24.04"' not in selector + label
assert '"group":"CWL MCP remediation"' not in workflow
10 changes: 10 additions & 0 deletions tests/test_sandboxed_web_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -1581,6 +1581,8 @@ def test_isolated_command_resolves_repo_local_launcher_against_sandboxed_cwd(mon
resolution instead goes through the sandboxed-``cwd``-relative path this
fix adds, not a PATH search.
"""
# Model the guest home independently of the host scratch directory.
monkeypatch.setattr(Path, "home", lambda: tmp_path / "synthetic-home")
monkeypatch.setattr(sandboxed_web_e2e.shutil, "which", lambda *_args, **_kwargs: None)
sandbox = tmp_path / "sandbox"
repo = sandbox / "repo"
Expand Down Expand Up @@ -1613,6 +1615,8 @@ def test_isolated_command_resolves_bare_command_via_relative_path_entry(monkeypa
``shutil.which`` to ``None`` here proves resolution instead falls
through to the cwd-anchored ``PATH`` search this fix adds.
"""
# Model the guest home independently of the host scratch directory.
monkeypatch.setattr(Path, "home", lambda: tmp_path / "synthetic-home")
monkeypatch.setattr(sandboxed_web_e2e.shutil, "which", lambda *_args, **_kwargs: None)
sandbox = tmp_path / "sandbox"
repo = sandbox / "repo"
Expand Down Expand Up @@ -1646,6 +1650,8 @@ def test_isolated_command_translates_absolute_workspace_launcher_to_sandbox_moun
that does not exist inside the sandbox, where only ``SANDBOX_MOUNT`` is
bound, so the command would fail to launch there unchanged.
"""
# Model the guest home independently of the host scratch directory.
monkeypatch.setattr(Path, "home", lambda: tmp_path / "synthetic-home")
monkeypatch.setattr(sandboxed_web_e2e.shutil, "which", lambda *_args, **_kwargs: None)
sandbox = tmp_path / "sandbox"
repo = sandbox / "repo"
Expand Down Expand Up @@ -1706,6 +1712,8 @@ def test_isolated_command_rejects_relative_path_entry_escaping_sandbox(monkeypat

def test_isolated_command_rejects_repo_local_path_traversal(monkeypatch, tmp_path):
"""A repo-local launcher path that lexically escapes the sandbox root is still rejected."""
# Model the guest home independently of the host scratch directory.
monkeypatch.setattr(Path, "home", lambda: tmp_path / "synthetic-home")
monkeypatch.setattr(sandboxed_web_e2e.shutil, "which", lambda *_args, **_kwargs: None)
sandbox = tmp_path / "sandbox"
repo = sandbox / "repo"
Expand All @@ -1726,6 +1734,8 @@ def test_isolated_command_rejects_repo_local_path_traversal(monkeypatch, tmp_pat

def test_isolated_command_rejects_explicit_external_path(monkeypatch, tmp_path):
"""An explicit absolute path outside the workspace and bind roots is still rejected."""
# Model the guest home independently of the host scratch directory.
monkeypatch.setattr(Path, "home", lambda: tmp_path / "synthetic-home")
monkeypatch.setattr(sandboxed_web_e2e.shutil, "which", lambda *_args, **_kwargs: None)
sandbox = tmp_path / "sandbox"
repo = sandbox / "repo"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,12 @@ def test_hourly_review_repair_uses_explicit_supported_image(self) -> None:
self.assertIn("labels: [self-hosted, linux, x64]", workflow)

def test_codeql_pr_uses_explicit_supported_image(self) -> None:
"""Require trusted-main control routing and Ubuntu fallback for all three jobs."""
"""Require trusted-main control routing and isolated fallback for all three jobs."""
workflow = CODEQL_PR.read_text(encoding="utf-8")
self.assertNotIn("runs-on: ubuntu-latest", workflow)
selectors = [
line.strip() for line in workflow.splitlines()
if line.strip().startswith("runs-on:")
if line.startswith(" group:")
]
self.assertEqual(len(selectors), 3)
for selector in selectors:
Expand All @@ -64,7 +64,7 @@ def test_codeql_pr_uses_explicit_supported_image(self) -> None:
)
self.assertIn('"group":"CWL central control"', selector)
self.assertIn('"labels":["self-hosted","linux","x64"]', selector)
self.assertIn("|| '\"ubuntu-24.04\"'", selector)
self.assertIn("|| '{\"group\":\"CWL CI isolated\",\"labels\":[\"self-hosted\",\"linux\",\"x64\",\"cwlab-ci-isolated\"]}'", selector)

def test_codeql_scan_dispatch_uses_explicit_supported_image(self) -> None:
"""Require validation, scan, and attempt wake jobs in the dedicated group."""
Expand Down
Loading