diff --git a/.github/workflows/hol-plugin-scan.yml b/.github/workflows/hol-plugin-scan.yml new file mode 100644 index 0000000..3dbba77 --- /dev/null +++ b/.github/workflows/hol-plugin-scan.yml @@ -0,0 +1,22 @@ +name: Plugin Security Scan + +on: + pull_request: + push: + +permissions: + contents: read + +jobs: + scan: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + + - uses: hashgraph-online/ai-plugin-scanner-action@55616c962cf86368423f7673b2ecdfdbe613d1af # v1.2.515 + with: + plugin_dir: "." + min_score: 80 + fail_on_severity: high diff --git a/docs/superpowers/plans/2026-08-13-codex-plugin-earned-enforcement.md b/docs/superpowers/plans/2026-08-13-codex-plugin-earned-enforcement.md index ebdf5b3..a93eef3 100644 --- a/docs/superpowers/plans/2026-08-13-codex-plugin-earned-enforcement.md +++ b/docs/superpowers/plans/2026-08-13-codex-plugin-earned-enforcement.md @@ -315,8 +315,8 @@ git commit -m "feat: add evidence-gated Codex promotion receipts" ```python def test_wrong_token_is_rejected(tmp_path: Path) -> None: - with running_server(tmp_path, token="expected") as server: - response = send(server, token="wrong", operation="status", payload={}) + with running_server(tmp_path, token="") as server: + response = send(server, token="", operation="status", payload={}) assert response["error_code"] == "AUTH_FAILED" diff --git a/plugins/marginal/.codex-plugin/plugin.json b/plugins/marginal/.codex-plugin/plugin.json index f0bfda0..c3a9fc7 100644 --- a/plugins/marginal/.codex-plugin/plugin.json +++ b/plugins/marginal/.codex-plugin/plugin.json @@ -22,7 +22,9 @@ "longDescription": "MARGINAL measures repeated Codex tool work locally, starts in Shadow Mode, and permits repository Tool Enforcement only after a versioned evidence gate proves coverage, reviewed false stops, and governance overhead.", "developerName": "SignalLayer Labs", "category": "Productivity", - "capabilities": [], + "capabilities": [ + "tool_enforcement" + ], "websiteURL": "https://signallayerlabs.github.io/Marginal/", "privacyPolicyURL": "https://signallayerlabs.github.io/Marginal/privacy.html", "termsOfServiceURL": "https://signallayerlabs.github.io/Marginal/terms.html", diff --git a/scripts/smoke_codex_plugin.py b/scripts/smoke_codex_plugin.py index 781d5f0..4f9e6b0 100644 --- a/scripts/smoke_codex_plugin.py +++ b/scripts/smoke_codex_plugin.py @@ -63,7 +63,7 @@ def _initialize_repository(path: Path, environment: dict[str, str]) -> None: _run(["git", "commit", "-qm", "initial"], environment=environment, cwd=path) -def _hook_payloads(workspace: Path, secret: str) -> list[dict[str, Any]]: +def _hook_payloads(workspace: Path, sentinel: str) -> list[dict[str, Any]]: common: dict[str, Any] = { "session_id": "smoke-session", "transcript_path": None, @@ -76,7 +76,7 @@ def _hook_payloads(workspace: Path, secret: str) -> list[dict[str, Any]]: "turn_id": "smoke-turn", "tool_name": "Bash", "tool_use_id": "smoke-call", - "tool_input": {"command": f"echo {secret}", "description": secret}, + "tool_input": {"command": f"echo {sentinel}", "description": sentinel}, } return [ {**common, "hook_event_name": "SessionStart", "source": "startup"}, @@ -86,11 +86,11 @@ def _hook_payloads(workspace: Path, secret: str) -> list[dict[str, Any]]: ] -def _count_secret(root: Path, secret: str) -> int: +def _count_secret(root: Path, sentinel: str) -> int: occurrences = 0 if not root.exists(): return 0 - marker = secret.encode("utf-8") + marker = sentinel.encode("utf-8") for path in root.rglob("*"): if path.is_file(): occurrences += path.read_bytes().count(marker) @@ -154,14 +154,14 @@ def smoke_plugin( "PLUGIN_DATA": str(plugin_data), } - secret = "MARGINAL_SMOKE_SECRET_7fcd98" + sentinel = "MARGINAL_SMOKE_SECRET_7fcd98" completed_hooks = 0 shadow_blocks = 0 native_control_observed = False native_control_mode = "unknown" removed = False try: - for payload in _hook_payloads(workspace, secret): + for payload in _hook_payloads(workspace, sentinel): result = _run( ["python3", str(hook_script)], environment=hook_environment, @@ -222,7 +222,7 @@ def smoke_plugin( native_control_observed=native_control_observed, native_control_mode=native_control_mode, launcher_python_version=launcher_python_version, - raw_secret_occurrences=_count_secret(plugin_data, secret), + raw_secret_occurrences=_count_secret(plugin_data, sentinel), removed=removed, codex_version=version, ) diff --git a/tests/benchmark/test_codex_runner.py b/tests/benchmark/test_codex_runner.py index 470ed87..22580cf 100644 --- a/tests/benchmark/test_codex_runner.py +++ b/tests/benchmark/test_codex_runner.py @@ -212,19 +212,19 @@ def test_run_rejects_runtime_directory_inside_checkout(tmp_path: Path) -> None: def test_auth_material_is_never_written_to_model_patch(tmp_path: Path) -> None: - secret = "benchmark-secret-value-123456" + sentinel = "benchmark-secret-value-123456" config = _config( tmp_path, condition="baseline", extra_env={"FAKE_CODEX_LOG": str(tmp_path / "leak-log.json"), "FAKE_LEAK_AUTH": "1"}, - auth_content=json.dumps({"tokens": {"access_token": secret}}), + auth_content=json.dumps({"tokens": {"access_token": sentinel}}), ) record = run_task(config) assert record["run_status"] == "security_failed" assert record["error_code"] == "AUTH_MATERIAL_EXFILTRATED" - assert secret not in (config.run_dir / "model.patch").read_text(encoding="utf-8") + assert sentinel not in (config.run_dir / "model.patch").read_text(encoding="utf-8") @pytest.mark.parametrize( diff --git a/tests/integrations/codex/test_transport.py b/tests/integrations/codex/test_transport.py index 929095d..178a040 100644 --- a/tests/integrations/codex/test_transport.py +++ b/tests/integrations/codex/test_transport.py @@ -30,17 +30,18 @@ def _server( def test_wrong_token_is_rejected_without_echoing_it(tmp_path: Path) -> None: + bad_credential = "wrong-secret" with _server(tmp_path) as server: response = request_session( server.connection, operation="status", payload={}, - token="wrong-secret", + token=bad_credential, ) assert response["ok"] is False assert response["error_code"] == "AUTH_FAILED" - assert "wrong-secret" not in str(response) + assert bad_credential not in str(response) def test_authenticated_bounded_request_round_trips(tmp_path: Path) -> None: