plugin-scanner 3.2.0__py3-none-any.whl → 3.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codex_plugin_scanner/guard/adapters/bounded_cli_hook_failure.py +2 -2
- codex_plugin_scanner/guard/adapters/codex_daemon_hook_bridge.py +16 -1
- codex_plugin_scanner/guard/adapters/codex_daemon_hook_bridge_flow.py +9 -3
- codex_plugin_scanner/guard/cli/approval_gate_prompt.py +6 -0
- codex_plugin_scanner/guard/cli/extension_builder_commands.py +105 -2
- codex_plugin_scanner/guard/daemon/hook_availability_policy.py +8 -1
- codex_plugin_scanner/guard/daemon/hook_launcher_recovery.py +106 -0
- codex_plugin_scanner/guard/daemon/static/assets/chunks/app-detail-workspace.js +1 -1
- codex_plugin_scanner/guard/daemon/static/assets/chunks/approval-proof-modal.js +1 -1
- codex_plugin_scanner/guard/daemon/static/assets/chunks/audit-workspace.js +1 -1
- codex_plugin_scanner/guard/daemon/static/assets/chunks/extensions-workspace.js +131 -111
- codex_plugin_scanner/guard/daemon/static/assets/chunks/home-protection-module.js +1 -1
- codex_plugin_scanner/guard/daemon/static/assets/chunks/mcp-policy-request-panel.js +1 -1
- codex_plugin_scanner/guard/daemon/static/assets/chunks/policy-workspace-page.js +1 -1
- codex_plugin_scanner/guard/daemon/static/assets/chunks/supply-chain-hub-workspace.js +1 -1
- codex_plugin_scanner/guard/daemon/static/assets/guard-dashboard.js +15 -15
- codex_plugin_scanner/guard/extension_builder/kit.py +43 -6
- codex_plugin_scanner/guard/extension_builder/listing.py +44 -6
- codex_plugin_scanner/guard/extension_builder/listing.v2.schema.json +126 -0
- codex_plugin_scanner/guard/native_policy_test_support.py +3 -1
- codex_plugin_scanner/guard/runtime/package_intent_parser.py +4 -1
- codex_plugin_scanner/guard/runtime/supply_chain_package_eval.py +181 -24
- codex_plugin_scanner/version.py +1 -1
- {plugin_scanner-3.2.0.dist-info → plugin_scanner-3.3.1.dist-info}/METADATA +58 -16
- {plugin_scanner-3.2.0.dist-info → plugin_scanner-3.3.1.dist-info}/RECORD +28 -26
- {plugin_scanner-3.2.0.dist-info → plugin_scanner-3.3.1.dist-info}/WHEEL +0 -0
- {plugin_scanner-3.2.0.dist-info → plugin_scanner-3.3.1.dist-info}/entry_points.txt +0 -0
- {plugin_scanner-3.2.0.dist-info → plugin_scanner-3.3.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -4,10 +4,10 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from ..daemon.hook_availability_policy import (
|
|
6
6
|
EMERGENCY_SAFE_REASON,
|
|
7
|
-
hook_action_is_emergency_safe,
|
|
8
7
|
hook_event_is_permission_request,
|
|
9
8
|
hook_event_pauses_when_unavailable,
|
|
10
9
|
)
|
|
10
|
+
from ..daemon.hook_launcher_recovery import hook_action_is_launcher_recovery_safe
|
|
11
11
|
|
|
12
12
|
_DECISION_HOOK_HARNESSES = frozenset({"grok", "hermes", "openclaw"})
|
|
13
13
|
|
|
@@ -134,7 +134,7 @@ def failure_payload(
|
|
|
134
134
|
pauses
|
|
135
135
|
and not _is_permission_event(event_name)
|
|
136
136
|
and isinstance(payload, dict)
|
|
137
|
-
and
|
|
137
|
+
and hook_action_is_launcher_recovery_safe(payload)
|
|
138
138
|
):
|
|
139
139
|
return _emergency_safe_payload(harness, event_name), 0
|
|
140
140
|
if not pauses:
|
|
@@ -14,6 +14,7 @@ if __package__:
|
|
|
14
14
|
from ..codex_hook_bridge_runtime import bridge_config_from_argv as _parse_bridge_config
|
|
15
15
|
from ..config import MAX_APPROVAL_WAIT_TIMEOUT_SECONDS
|
|
16
16
|
from ..daemon.hook_availability_policy import hook_event_is_permission_request
|
|
17
|
+
from ..daemon.hook_launcher_recovery import hook_action_is_launcher_recovery_safe
|
|
17
18
|
from ..live_process_identity import (
|
|
18
19
|
CODEX_BROWSER_WAIT_PROCESS_KEY,
|
|
19
20
|
CODEX_BROWSER_WAIT_TIMEOUT_SECONDS_KEY,
|
|
@@ -44,6 +45,9 @@ else: # pragma: no cover - exercised by subprocess integration tests
|
|
|
44
45
|
from codex_plugin_scanner.guard.daemon.hook_availability_policy import (
|
|
45
46
|
hook_event_is_permission_request,
|
|
46
47
|
)
|
|
48
|
+
from codex_plugin_scanner.guard.daemon.hook_launcher_recovery import (
|
|
49
|
+
hook_action_is_launcher_recovery_safe,
|
|
50
|
+
)
|
|
47
51
|
from codex_plugin_scanner.guard.live_process_identity import (
|
|
48
52
|
CODEX_BROWSER_WAIT_PROCESS_KEY,
|
|
49
53
|
CODEX_BROWSER_WAIT_TIMEOUT_SECONDS_KEY,
|
|
@@ -232,7 +236,7 @@ def main(
|
|
|
232
236
|
)
|
|
233
237
|
if response is None:
|
|
234
238
|
if launch_integrity_failed:
|
|
235
|
-
response =
|
|
239
|
+
response = _launcher_integrity_response(event_name, data)
|
|
236
240
|
else:
|
|
237
241
|
failure_reason = _OVERLOAD_REASON if daemon_overloaded else _FAIL_CLOSED_REASON
|
|
238
242
|
response = _unavailable_response(event_name, failure_reason, data)
|
|
@@ -248,6 +252,17 @@ def main(
|
|
|
248
252
|
return 0
|
|
249
253
|
|
|
250
254
|
|
|
255
|
+
def _launcher_integrity_response(event_name: str, data: str) -> dict[str, object]:
|
|
256
|
+
"""Keep a bad launcher from approving work, without locking out its repair."""
|
|
257
|
+
|
|
258
|
+
payload = _json_object(data)
|
|
259
|
+
if event_name == "PreToolUse" and payload is not None and hook_action_is_launcher_recovery_safe(payload):
|
|
260
|
+
return _unavailable_response(event_name, _LAUNCH_INTEGRITY_REASON, data)
|
|
261
|
+
if event_name in {"PreToolUse", "PermissionRequest"}:
|
|
262
|
+
return _fail_closed(event_name, _LAUNCH_INTEGRITY_REASON)
|
|
263
|
+
return _unavailable_response(event_name, _LAUNCH_INTEGRITY_REASON, data)
|
|
264
|
+
|
|
265
|
+
|
|
251
266
|
def _bridge_output(
|
|
252
267
|
response: dict[str, object],
|
|
253
268
|
*,
|
|
@@ -17,6 +17,8 @@ from .codex_daemon_hook_auth import _DaemonResponseError
|
|
|
17
17
|
from .codex_daemon_hook_transport import _daemon_response_once, _DaemonGenerationChangedError
|
|
18
18
|
|
|
19
19
|
_DAEMON_START_TIMEOUT_SECONDS = 8
|
|
20
|
+
_DAEMON_RPC_TIMEOUT_SECONDS = 4.0
|
|
21
|
+
_FALLBACK_TIMEOUT_SECONDS = 4.0
|
|
20
22
|
_MINIMUM_OPERATION_SECONDS = 0.01
|
|
21
23
|
_OVERLOAD_RESERVE_MS = 100
|
|
22
24
|
|
|
@@ -42,7 +44,7 @@ def bridge_review_response(
|
|
|
42
44
|
state_path=state_path,
|
|
43
45
|
query=query,
|
|
44
46
|
data=data,
|
|
45
|
-
timeout_seconds=_remaining_seconds(deadline),
|
|
47
|
+
timeout_seconds=min(_remaining_seconds(deadline), _DAEMON_RPC_TIMEOUT_SECONDS),
|
|
46
48
|
)
|
|
47
49
|
|
|
48
50
|
try:
|
|
@@ -199,14 +201,18 @@ def _fallback_response(
|
|
|
199
201
|
fallback_stdout = trusted_launch.run_fallback(
|
|
200
202
|
fallback_command,
|
|
201
203
|
data=data,
|
|
202
|
-
timeout_seconds=_remaining_seconds(deadline),
|
|
204
|
+
timeout_seconds=_remaining_seconds(deadline, cap=_FALLBACK_TIMEOUT_SECONDS),
|
|
203
205
|
)
|
|
204
206
|
if fallback_stdout is None:
|
|
205
207
|
return None
|
|
206
208
|
return _json_object(fallback_stdout.strip()) if fallback_stdout.strip() else {}
|
|
207
209
|
if launch_integrity_failed:
|
|
208
210
|
return None
|
|
209
|
-
return _run_local_fallback(
|
|
211
|
+
return _run_local_fallback(
|
|
212
|
+
fallback_command,
|
|
213
|
+
data=data,
|
|
214
|
+
timeout_seconds=_remaining_seconds(deadline, cap=_FALLBACK_TIMEOUT_SECONDS),
|
|
215
|
+
)
|
|
210
216
|
|
|
211
217
|
|
|
212
218
|
def _run_daemon_start(
|
|
@@ -74,6 +74,12 @@ def prompt_for_approval_gate(
|
|
|
74
74
|
)
|
|
75
75
|
if summary:
|
|
76
76
|
print(summary, file=sys.stderr)
|
|
77
|
+
proof_name = "authenticator code" if gate.totp_enabled else "approval password"
|
|
78
|
+
print(
|
|
79
|
+
f"HOL Guard is waiting for your {proof_name}. Enter it at the next prompt to continue.",
|
|
80
|
+
file=sys.stderr,
|
|
81
|
+
flush=True,
|
|
82
|
+
)
|
|
77
83
|
password = None if gate.totp_enabled else getpass.getpass("Approval password: ")
|
|
78
84
|
totp_code = getpass.getpass("Authenticator code: ") if gate.totp_enabled else None
|
|
79
85
|
return ApprovalGateInput(
|
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import subprocess
|
|
6
8
|
import sys
|
|
7
9
|
from pathlib import Path
|
|
8
10
|
from typing import TextIO
|
|
9
11
|
|
|
10
12
|
from ..extension_builder.errors import BuilderError
|
|
11
|
-
from ..extension_builder.io import canonical_json
|
|
13
|
+
from ..extension_builder.io import canonical_json, list_value, object_value, read_json
|
|
12
14
|
|
|
13
15
|
_METADATA_FLAGS = (
|
|
14
16
|
"slug",
|
|
@@ -21,6 +23,7 @@ _METADATA_FLAGS = (
|
|
|
21
23
|
"launcher",
|
|
22
24
|
"package",
|
|
23
25
|
)
|
|
26
|
+
_HANDOFF_TIMEOUT_SECONDS = 1_500
|
|
24
27
|
|
|
25
28
|
|
|
26
29
|
def configure_extension_builder_parser(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
|
|
@@ -54,7 +57,16 @@ def configure_extension_builder_parser(subparsers: argparse._SubParsersAction[ar
|
|
|
54
57
|
apply.add_argument("--repo", type=Path, required=True, help="HOL Guard source checkout")
|
|
55
58
|
apply.add_argument("--write", action="store_true", help="Explicitly apply the inspected source changes")
|
|
56
59
|
apply.add_argument("--expected-plan", help="Require the plan digest from a previous inspection")
|
|
57
|
-
|
|
60
|
+
handoff = commands.add_parser(
|
|
61
|
+
"handoff", help="Check command-extension inputs and generated projections before opening a pull request"
|
|
62
|
+
)
|
|
63
|
+
handoff.add_argument("--source", type=Path, required=True, help="Canonical command source JSON")
|
|
64
|
+
handoff.add_argument("--fixture", type=Path, required=True, help="Portable command fixture JSON")
|
|
65
|
+
handoff.add_argument(
|
|
66
|
+
"--repo", type=Path, default=Path("."), help="HOL Guard checkout containing generated projections"
|
|
67
|
+
)
|
|
68
|
+
handoff.add_argument("--compiler", type=Path, help="Use an already-built native source compiler")
|
|
69
|
+
for command in (generate, validate, diff, apply, handoff):
|
|
58
70
|
command.add_argument("--json", action="store_true", help="Emit a deterministic JSON result")
|
|
59
71
|
|
|
60
72
|
|
|
@@ -96,6 +108,91 @@ def _generate(args: argparse.Namespace) -> dict[str, object]:
|
|
|
96
108
|
return {**kit.summary(), "generated": True}
|
|
97
109
|
|
|
98
110
|
|
|
111
|
+
def _handoff(args: argparse.Namespace) -> dict[str, object]:
|
|
112
|
+
source = object_value(read_json(args.source), code="source_shape")
|
|
113
|
+
if source.get("schema") != "guard.command-extension-source.v1":
|
|
114
|
+
raise BuilderError("source_schema", "Source must use guard.command-extension-source.v1.")
|
|
115
|
+
extension = object_value(source.get("extension"), code="source_shape")
|
|
116
|
+
extension_id = extension.get("extension_id")
|
|
117
|
+
if not isinstance(extension_id, str) or not extension_id.startswith("command."):
|
|
118
|
+
raise BuilderError("source_identity", "Source must declare a command.* extension ID.")
|
|
119
|
+
try:
|
|
120
|
+
repo = args.repo.resolve(strict=True)
|
|
121
|
+
source_path = args.source.resolve(strict=True)
|
|
122
|
+
fixture_path = args.fixture.resolve(strict=True)
|
|
123
|
+
except OSError as exc:
|
|
124
|
+
raise BuilderError("repository_path", "Repository, source, and fixture paths must exist.") from exc
|
|
125
|
+
expected_source = repo / "contributions" / "command-sources" / f"{extension_id}.json"
|
|
126
|
+
expected_fixture = repo / "tests" / "fixtures" / f"command-source-{extension_id.removeprefix('command.')}.v1.json"
|
|
127
|
+
if source_path != expected_source or fixture_path != expected_fixture:
|
|
128
|
+
raise BuilderError("canonical_paths", "Source and fixture must use their canonical repository paths.")
|
|
129
|
+
trust_map = object_value(
|
|
130
|
+
read_json(repo / "contracts" / "extensions" / "trust-class-map.v1.json"), code="trust_shape"
|
|
131
|
+
)
|
|
132
|
+
if trust_map.get("schemaVersion") != "guard.extension-trust-class-map.v1":
|
|
133
|
+
raise BuilderError("trust_schema", "Trust map must use guard.extension-trust-class-map.v1.")
|
|
134
|
+
classes = object_value(trust_map.get("classes"), code="trust_shape")
|
|
135
|
+
external = list_value(classes.get("external"), maximum=4096)
|
|
136
|
+
if not all(isinstance(item, str) for item in external):
|
|
137
|
+
raise BuilderError("trust_shape", "Trust map must list extension IDs as strings.")
|
|
138
|
+
other_classes = {
|
|
139
|
+
class_name
|
|
140
|
+
for class_name in ("first-party", "trusted-library")
|
|
141
|
+
if extension_id in list_value(classes.get(class_name), maximum=4096)
|
|
142
|
+
}
|
|
143
|
+
if extension_id not in external:
|
|
144
|
+
if other_classes:
|
|
145
|
+
raise BuilderError(
|
|
146
|
+
"trust_class", "External contributions must remain in the reviewed external trust class."
|
|
147
|
+
)
|
|
148
|
+
raise BuilderError("missing_external_trust", "Trust map must list the extension under external.")
|
|
149
|
+
if other_classes:
|
|
150
|
+
raise BuilderError("trust_class", "Trust map must assign the extension to only the external trust class.")
|
|
151
|
+
script = repo / "scripts" / "prepare_extension_contribution.py"
|
|
152
|
+
if script.is_symlink() or not script.is_file():
|
|
153
|
+
raise BuilderError("repository_layout", "Repository preparation tooling is unavailable.")
|
|
154
|
+
command = [
|
|
155
|
+
sys.executable,
|
|
156
|
+
str(script),
|
|
157
|
+
"--check",
|
|
158
|
+
"--source",
|
|
159
|
+
str(source_path),
|
|
160
|
+
"--fixture",
|
|
161
|
+
str(fixture_path),
|
|
162
|
+
]
|
|
163
|
+
if args.compiler is not None:
|
|
164
|
+
command.extend(("--compiler", str(args.compiler)))
|
|
165
|
+
try:
|
|
166
|
+
completed = subprocess.run(
|
|
167
|
+
command,
|
|
168
|
+
cwd=repo,
|
|
169
|
+
capture_output=True,
|
|
170
|
+
text=True,
|
|
171
|
+
timeout=_HANDOFF_TIMEOUT_SECONDS,
|
|
172
|
+
check=False,
|
|
173
|
+
)
|
|
174
|
+
except (OSError, subprocess.TimeoutExpired) as exc:
|
|
175
|
+
raise BuilderError("handoff_validation", "Native handoff validation could not be completed.") from exc
|
|
176
|
+
if completed.returncode:
|
|
177
|
+
raise BuilderError("handoff_validation", "Native source or generated-projection validation failed.")
|
|
178
|
+
try:
|
|
179
|
+
validation = json.loads(completed.stdout)
|
|
180
|
+
except json.JSONDecodeError as exc:
|
|
181
|
+
raise BuilderError("handoff_validation", "Native handoff validation produced an invalid result.") from exc
|
|
182
|
+
if (
|
|
183
|
+
not isinstance(validation, dict)
|
|
184
|
+
or validation.get("ok") is not True
|
|
185
|
+
or validation.get("checked") is not True
|
|
186
|
+
or validation.get("targetCommandsExecuted") != 0
|
|
187
|
+
):
|
|
188
|
+
raise BuilderError("handoff_validation", "Native handoff validation did not complete safely.")
|
|
189
|
+
return {
|
|
190
|
+
"contributionId": extension_id,
|
|
191
|
+
"readyForPullRequest": True,
|
|
192
|
+
"targetCommandsExecuted": 0,
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
|
|
99
196
|
def _operate(args: argparse.Namespace) -> dict[str, object]:
|
|
100
197
|
from ..extension_builder.kit import diff_kits, load_kit
|
|
101
198
|
from ..extension_builder.repository_write import apply_kit
|
|
@@ -104,6 +201,8 @@ def _operate(args: argparse.Namespace) -> dict[str, object]:
|
|
|
104
201
|
command = args.extension_builder_command
|
|
105
202
|
if command == "generate":
|
|
106
203
|
return _generate(args)
|
|
204
|
+
if command == "handoff":
|
|
205
|
+
return _handoff(args)
|
|
107
206
|
if command == "validate":
|
|
108
207
|
return {**load_kit(args.kit).summary(), "validated": True}
|
|
109
208
|
if command == "diff":
|
|
@@ -129,6 +228,10 @@ def _emit(result: dict[str, object], command: str, output: TextIO) -> None:
|
|
|
129
228
|
print("Kits differ." if result["changed"] else "Kits are identical.", file=output)
|
|
130
229
|
for name in ("addedOperations", "removedOperations", "changedOperations", "changedReviews"):
|
|
131
230
|
print(f"{name}: {result[name]}", file=output)
|
|
231
|
+
elif command == "handoff":
|
|
232
|
+
print(f"Contribution handoff is ready for {result['contributionId']}.", file=output)
|
|
233
|
+
print("Source, fixture, external trust mapping, and generated projections agree.", file=output)
|
|
234
|
+
print("No target was executed and active protection was not changed.", file=output)
|
|
132
235
|
else:
|
|
133
236
|
verb = "Generated" if command == "generate" else "Validated"
|
|
134
237
|
print(
|
|
@@ -187,9 +187,10 @@ def availability_harness_response(
|
|
|
187
187
|
) -> dict[str, object]:
|
|
188
188
|
"""Render a schema-valid harness result when native review is unavailable."""
|
|
189
189
|
|
|
190
|
+
from .hook_launcher_recovery import hook_action_is_launcher_recovery_safe
|
|
190
191
|
from .hook_worker_responses import observe_lifecycle_fail_safe_response
|
|
191
192
|
|
|
192
|
-
del
|
|
193
|
+
del guard_home, recording_only
|
|
193
194
|
canonical_lifecycle = _LIFECYCLE_CANONICAL_BY_COMPACT.get(_compact_hook_event_name(event_name))
|
|
194
195
|
if canonical_lifecycle is not None:
|
|
195
196
|
return observe_lifecycle_fail_safe_response(
|
|
@@ -211,6 +212,12 @@ def availability_harness_response(
|
|
|
211
212
|
if pre_tool_event and reason_code.strip() in _INTEGRITY_FAIL_CLOSED_REASON_CODES:
|
|
212
213
|
from .hook_worker_responses import integrity_fail_closed_pre_tool_response
|
|
213
214
|
|
|
215
|
+
if hook_action_is_launcher_recovery_safe(payload, workspace=workspace, home_dir=home_dir):
|
|
216
|
+
return recording_only_pre_tool_response(
|
|
217
|
+
harness,
|
|
218
|
+
reason_code=reason_code,
|
|
219
|
+
reason=reason,
|
|
220
|
+
)
|
|
214
221
|
return integrity_fail_closed_pre_tool_response(
|
|
215
222
|
harness,
|
|
216
223
|
reason=reason,
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Exact repair commands that may start while hook review cannot authenticate."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import shlex
|
|
6
|
+
from collections.abc import Mapping
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from .hook_availability_floor import (
|
|
10
|
+
_BLOCKED_SOURCE_EVENTS,
|
|
11
|
+
_MUTATING_TOOLS,
|
|
12
|
+
_UNSAFE_SHELL_MARKERS,
|
|
13
|
+
_flag_name,
|
|
14
|
+
_payload_command,
|
|
15
|
+
_payload_is_mcp,
|
|
16
|
+
_payload_source_events,
|
|
17
|
+
_tool_name,
|
|
18
|
+
hook_action_is_emergency_safe,
|
|
19
|
+
)
|
|
20
|
+
from .hook_request_parsing import runtime_hook_event_name
|
|
21
|
+
|
|
22
|
+
_LAUNCHER_REPAIR_BINARIES = frozenset({"hol-guard", "plugin-guard"})
|
|
23
|
+
_LAUNCHER_REPAIR_FLAGS = frozenset({"--dry-run", "--force-pypi-reinstall", "--json"})
|
|
24
|
+
_LAUNCHER_REPAIR_HARNESSES = frozenset(
|
|
25
|
+
{
|
|
26
|
+
"antigravity",
|
|
27
|
+
"claude-code",
|
|
28
|
+
"cline",
|
|
29
|
+
"codex",
|
|
30
|
+
"copilot",
|
|
31
|
+
"cursor",
|
|
32
|
+
"gemini",
|
|
33
|
+
"grok",
|
|
34
|
+
"hermes",
|
|
35
|
+
"kimi",
|
|
36
|
+
"omp",
|
|
37
|
+
"openclaw",
|
|
38
|
+
"opencode",
|
|
39
|
+
"paseo",
|
|
40
|
+
"pi",
|
|
41
|
+
"zcode",
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def hook_action_is_launcher_recovery_safe(
|
|
47
|
+
payload: Mapping[str, object],
|
|
48
|
+
*,
|
|
49
|
+
workspace: Path | None = None,
|
|
50
|
+
home_dir: Path | None = None,
|
|
51
|
+
) -> bool:
|
|
52
|
+
"""Allow inspection, or the exact bare command that restores a managed launcher.
|
|
53
|
+
|
|
54
|
+
A path-qualified executable is never treated as the managed launcher. An
|
|
55
|
+
unauthenticated payload reference is not evidence of the tool that will run.
|
|
56
|
+
Every other action stays blocked.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
if "guard_payload_ref" in payload:
|
|
60
|
+
return False
|
|
61
|
+
if hook_action_is_emergency_safe(payload, workspace=workspace, home_dir=home_dir):
|
|
62
|
+
return True
|
|
63
|
+
if runtime_hook_event_name(payload) != "PreToolUse":
|
|
64
|
+
return False
|
|
65
|
+
tool_name = _tool_name(payload)
|
|
66
|
+
if tool_name in _MUTATING_TOOLS and tool_name not in {"bash", "shell"}:
|
|
67
|
+
return False
|
|
68
|
+
if _payload_source_events(payload) & _BLOCKED_SOURCE_EVENTS:
|
|
69
|
+
return False
|
|
70
|
+
if _payload_is_mcp(payload) or tool_name.startswith("plugin-"):
|
|
71
|
+
return False
|
|
72
|
+
command = _payload_command(payload)
|
|
73
|
+
if command is None:
|
|
74
|
+
return False
|
|
75
|
+
return _command_is_launcher_repair(command)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _command_is_launcher_repair(command: str) -> bool:
|
|
79
|
+
stripped = command.strip()
|
|
80
|
+
if not stripped or len(stripped) > 4096:
|
|
81
|
+
return False
|
|
82
|
+
if any(marker in stripped for marker in _UNSAFE_SHELL_MARKERS):
|
|
83
|
+
return False
|
|
84
|
+
try:
|
|
85
|
+
tokens = shlex.split(stripped, posix=True, comments=False)
|
|
86
|
+
except ValueError:
|
|
87
|
+
return False
|
|
88
|
+
if len(tokens) < 2 or tokens[0] not in _LAUNCHER_REPAIR_BINARIES:
|
|
89
|
+
return False
|
|
90
|
+
positional: list[str] = []
|
|
91
|
+
for token in tokens[1:]:
|
|
92
|
+
if token.startswith("-"):
|
|
93
|
+
if _flag_name(token) not in _LAUNCHER_REPAIR_FLAGS or "=" in token:
|
|
94
|
+
return False
|
|
95
|
+
continue
|
|
96
|
+
positional.append(token)
|
|
97
|
+
if positional[:1] == ["install"]:
|
|
98
|
+
return len(positional) == 2 and positional[1] in _LAUNCHER_REPAIR_HARNESSES
|
|
99
|
+
if positional[:1] == ["update"]:
|
|
100
|
+
return len(positional) == 1
|
|
101
|
+
if positional[:1] == ["daemon"]:
|
|
102
|
+
return positional[1:] in (["repair"], ["status"])
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
__all__ = ["hook_action_is_launcher_recovery_safe"]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as reactExports, i as harnessDisplayName, aq as fetchSettings, b3 as runHarnessAction, b4 as GuardHarnessActionError, j as jsxRuntimeExports, S as SectionLabel, A as ActionButton, b5 as HiMiniRocketLaunch, w as HiMiniShieldCheck, am as HiMiniArrowPath, y as formatRelativeTime, P as HiMiniExclamationTriangle, s as HiMiniCheckCircle, b6 as HiMiniTrash, b7 as isGuardDemoMode, b8 as fetchGuardApi, b9 as formatHarnessCommand, ah as HiMiniAdjustmentsHorizontal, ba as fetchApprovalPage, bb as fetchPolicy, x as guardActionDisposition, p as protectionHealthFor, l as useProtectionPresentationState,
|
|
1
|
+
import { r as reactExports, i as harnessDisplayName, aq as fetchSettings, b3 as runHarnessAction, b4 as GuardHarnessActionError, j as jsxRuntimeExports, S as SectionLabel, A as ActionButton, b5 as HiMiniRocketLaunch, w as HiMiniShieldCheck, am as HiMiniArrowPath, y as formatRelativeTime, P as HiMiniExclamationTriangle, s as HiMiniCheckCircle, b6 as HiMiniTrash, b7 as isGuardDemoMode, b8 as fetchGuardApi, b9 as formatHarnessCommand, ah as HiMiniAdjustmentsHorizontal, ba as fetchApprovalPage, bb as fetchPolicy, x as guardActionDisposition, p as protectionHealthFor, l as useProtectionPresentationState, aZ as HiMiniArrowLeft, c as HiMiniChevronRight, t as GuardHero, a5 as ProofStrip, bc as HiMiniHome, Q as HiMiniBolt, bd as appSetupTarget, be as guardActionPresentation, R as Badge, bf as DEFAULT_FILTER_STATE, bg as filterEvidence, bh as sortEvidence, bi as computeMetrics, bj as CommandActivityWorkspace, n as EmptyState, bk as EvidenceFilterBar, bl as EvidenceInsightStrip, bm as EvidenceActionList, bn as EvidenceActionDetail, M as useFocusTrap, bo as policyIdentityKey, K as HiMiniCloud, aJ as Tag, a7 as HiMiniXCircle, bp as clearLabelForScope, bq as HiMiniChartBar } from "../guard-dashboard.js";
|
|
2
2
|
import { A as ApprovalProofModal } from "./approval-proof-modal.js";
|
|
3
3
|
import { u as useHarnessDetection, i as isHarnessDetected } from "./harness-detection.js";
|
|
4
4
|
function HarnessSetupPanel(props) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as reactExports, M as useFocusTrap, al as isApprovalProofSubmitDisabled,
|
|
1
|
+
import { r as reactExports, M as useFocusTrap, al as isApprovalProofSubmitDisabled, aQ as buildApprovalProofCredentials, j as jsxRuntimeExports, S as SectionLabel, an as ApprovalProofFieldInputs, A as ActionButton } from "../guard-dashboard.js";
|
|
2
2
|
function ApprovalProofModal(props) {
|
|
3
3
|
const {
|
|
4
4
|
title,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { j as jsxRuntimeExports, aJ as Tag, y as formatRelativeTime, r as reactExports, A as ActionButton, ck as HiMiniChevronLeft, c as HiMiniChevronRight, bw as IconActionButton, C as HiMiniXMark, bB as GuardModalLayer, cl as HiMiniFunnel, aG as HiMiniMagnifyingGlass, cm as HiMiniArrowDown, cn as HiMiniArrowUp, S as SectionLabel, am as HiMiniArrowPath, bA as HiMiniBugAnt, n as EmptyState, ah as HiMiniAdjustmentsHorizontal, bC as ConnectFlowCard, P as HiMiniExclamationTriangle,
|
|
1
|
+
import { j as jsxRuntimeExports, aJ as Tag, y as formatRelativeTime, r as reactExports, A as ActionButton, ck as HiMiniChevronLeft, c as HiMiniChevronRight, bw as IconActionButton, C as HiMiniXMark, bB as GuardModalLayer, cl as HiMiniFunnel, aG as HiMiniMagnifyingGlass, cm as HiMiniArrowDown, cn as HiMiniArrowUp, S as SectionLabel, am as HiMiniArrowPath, bA as HiMiniBugAnt, n as EmptyState, ah as HiMiniAdjustmentsHorizontal, bC as ConnectFlowCard, P as HiMiniExclamationTriangle, aO as useResolvedApprovalGate, co as runAuditRemediation, R as Badge, ch as isBlockedGuardAction, bs as isSupplyChainAuditEvidence, s as HiMiniCheckCircle, a7 as HiMiniXCircle, i as harnessDisplayName, bU as HiMiniDocumentText, b2 as guardAwareHref, w as HiMiniShieldCheck } from "../guard-dashboard.js";
|
|
2
2
|
import { A as ApprovalProofModal } from "./approval-proof-modal.js";
|
|
3
3
|
import { p as packageWorkbenchEcosystems, f as filterPackageWorkbenchFindings, c as sortPackageWorkbenchFindings, i as isApprovalGateRequiredError } from "./supply-chain-hub-workspace.js";
|
|
4
4
|
import { r as resolveManagerCoverageManagers, a as resolveManagerCoverageStatus } from "./supply-chain-protection-stats.js";
|