plugin-scanner 2.2.112__py3-none-any.whl → 2.2.113__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.
@@ -53,12 +53,16 @@ _CORE_COMMAND_EXTENSION_SPECS: Final[tuple[CommandExtensionSpec, ...]] = (
53
53
  CommandExtensionSpec(
54
54
  extension_id="command.git",
55
55
  name="Git protection",
56
- description="Reviews local and remote Git operations that can discard work or replace history.",
57
- action_classes=("git destructive command",),
58
- risk_classes=("destructive_shell",),
56
+ description=(
57
+ "Reviews local and remote Git operations that can discard work, replace history, "
58
+ "or read a staged index Guard cannot bound."
59
+ ),
60
+ action_classes=("git destructive command", "git index inspection"),
61
+ risk_classes=("destructive_shell", "local_secret_read"),
59
62
  safer_alternatives=(
60
63
  "Preview affected files and refs before destructive repository operations.",
61
64
  "Create a temporary branch or stash before discarding local work.",
65
+ "Inspect the staged index with git diff --cached --check or lockfile excludes.",
62
66
  ),
63
67
  reference_urls=(),
64
68
  required=True,
@@ -42,6 +42,7 @@ COMMAND_ACTION_RISK_CLASSES: dict[str, tuple[str, ...]] = {
42
42
  "github pr body shell substitution": ("execution",),
43
43
  "filesystem destructive command": ("destructive_shell",),
44
44
  "git destructive command": ("destructive_shell",),
45
+ "git index inspection": ("local_secret_read",),
45
46
  "system destructive command": ("destructive_shell",),
46
47
  "windows destructive command": ("destructive_shell",),
47
48
  "kubernetes destructive command": ("destructive_shell", "network_egress"),
@@ -243,6 +244,13 @@ BUILT_IN_COMMAND_RULES = (
243
244
  action_class="GitHub PR body shell substitution",
244
245
  safer_alternative="Use a literal body file whose contents can be reviewed before submission.",
245
246
  ),
247
+ _compatibility_rule(
248
+ rule_id="command.git.index-inspection",
249
+ title="Git index inspection",
250
+ description="Identifies staged-index Git reads that Guard cannot verify as a bounded inspection.",
251
+ action_class="git index inspection",
252
+ safer_alternative="Run git diff --cached --check, or exclude lockfiles and scan the staged patch on stdin.",
253
+ ),
246
254
  _structured_rule(
247
255
  rule_id="command.filesystem.recursive-delete",
248
256
  title="Recursive filesystem deletion",
@@ -131,7 +131,7 @@ def is_low_risk_git_inspection_segment(segment: ShellExecutionSegment) -> bool:
131
131
  if operation == "rev-parse":
132
132
  return _safe_rev_parse_args(args)
133
133
  if operation == "diff":
134
- return _safe_diff_args(args)
134
+ return _safe_diff_args(args) and _git_show_has_execution_free_config(segment, repository_path=repository_path)
135
135
  if operation == "ls-files":
136
136
  return _safe_ls_files_args(args)
137
137
  if operation == "show":
@@ -0,0 +1,408 @@
1
+ """Staged-index Git inspection recognition for allow and extension ownership."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from dataclasses import replace
7
+ from pathlib import Path
8
+ from typing import Final, Literal
9
+
10
+ from .compound_git_inspection import (
11
+ _git_show_has_execution_free_config,
12
+ _safe_repository_path,
13
+ is_low_risk_git_inspection_segment,
14
+ is_low_risk_standalone_git_routine,
15
+ )
16
+ from .secret_file_request_services.shell_tokenization import _shell_segment_primary_command
17
+ from .shell_execution_context import ShellExecutionContext, ShellExecutionSegment, model_shell_execution_context
18
+
19
+ _CONTROL_PREFIXES: Final = frozenset({"!", "elif", "else", "fi", "if", "then"})
20
+ _ALLOWED_CONTROLS: Final = frozenset({"&&", "||", "|", ";", "\n"})
21
+ _RG_BOOLEAN_FLAGS: Final = frozenset({"--ignore-case", "--line-number", "--no-config", "-i", "-in", "-n", "-ni"})
22
+ _GIT_GLOBAL_FLAG_OPTIONS: Final = frozenset(
23
+ {
24
+ "--literal-pathspecs",
25
+ "--no-advice",
26
+ "--no-lazy-fetch",
27
+ "--no-optional-locks",
28
+ "--no-pager",
29
+ "--no-replace-objects",
30
+ }
31
+ )
32
+ _GIT_GLOBAL_VALUE_OPTIONS: Final = frozenset(
33
+ {"--config-env", "--exec-path", "--git-dir", "--namespace", "--super-prefix", "--work-tree", "-C", "-c"}
34
+ )
35
+ _EXECUTION_ROUTING_OPTIONS: Final = frozenset({"-c", "--config-env", "--exec-path"})
36
+ _REPOSITORY_SELECTOR_OPTIONS: Final = frozenset(
37
+ {"--bare", "--git-dir", "--namespace", "--paginate", "--super-prefix", "--work-tree", "-P", "-p"}
38
+ )
39
+ _INDEX_DIFF_FLAGS: Final = frozenset({"--cached", "--staged"})
40
+ _CACHED_DIFF_KIND = Literal["owned", "routed"]
41
+ _MAX_SEGMENTS: Final = 32
42
+
43
+
44
+ def executable_tokens(tokens: tuple[str, ...]) -> tuple[str, ...] | None:
45
+ """Strip shell reserved-word prefixes. Empty means a no-op such as fi."""
46
+
47
+ index = 0
48
+ while index < len(tokens) and tokens[index] in _CONTROL_PREFIXES:
49
+ if tokens[index] == "fi" and len(tokens) != 1:
50
+ return None
51
+ index += 1
52
+ return tokens[index:]
53
+
54
+
55
+ def _static_echo_arg_is_safe(arg: str) -> bool:
56
+ if "`" in arg or "$(" in arg or "<(" in arg or ">(" in arg:
57
+ return False
58
+ return "$" not in arg.replace("$?", "")
59
+
60
+
61
+ def _index_scan_rg_args_are_safe(args: tuple[str, ...]) -> bool:
62
+ """Accept stdin-only ripgrep with one inline pattern after a cached diff."""
63
+
64
+ if os.environ.get("RIPGREP_CONFIG_PATH") and "--no-config" not in args:
65
+ return False
66
+ saw_pattern = False
67
+ for arg in args:
68
+ if arg.startswith("RIPGREP_CONFIG_PATH="):
69
+ return False
70
+ if arg in _RG_BOOLEAN_FLAGS:
71
+ continue
72
+ if not arg or arg.startswith("-"):
73
+ return False
74
+ if saw_pattern:
75
+ return False
76
+ if "$" in arg or any(marker in arg for marker in ("$(", "`", "<(", ">(", "\x00")):
77
+ return False
78
+ saw_pattern = True
79
+ return saw_pattern
80
+
81
+
82
+ def _tokens_are_cached_diff(tokens: tuple[str, ...]) -> bool:
83
+ kind = cached_diff_kind(tokens)
84
+ return kind == "owned"
85
+
86
+
87
+ def cached_diff_kind(tokens: tuple[str, ...]) -> _CACHED_DIFF_KIND | None:
88
+ """Classify one segment as an owned or execution-routed cached diff."""
89
+
90
+ stripped = executable_tokens(tokens)
91
+ if not stripped:
92
+ return None
93
+ command_name, command_index = _shell_segment_primary_command(list(stripped))
94
+ if command_name != "git" or command_index is None:
95
+ return None
96
+ args = stripped[command_index + 1 :]
97
+ index = 0
98
+ routed = False
99
+ while index < len(args):
100
+ token = args[index]
101
+ option_name = token.partition("=")[0]
102
+ if token in _GIT_GLOBAL_FLAG_OPTIONS:
103
+ index += 1
104
+ continue
105
+ if token == "-c" or (token.startswith("-c") and not token.startswith("-C")):
106
+ routed = True
107
+ if token == "-c":
108
+ if index + 1 >= len(args):
109
+ return None
110
+ index += 2
111
+ continue
112
+ index += 1
113
+ continue
114
+ if option_name in _GIT_GLOBAL_VALUE_OPTIONS:
115
+ if option_name in _EXECUTION_ROUTING_OPTIONS:
116
+ routed = True
117
+ if "=" in token:
118
+ if not token.partition("=")[2]:
119
+ return None
120
+ index += 1
121
+ continue
122
+ if index + 1 >= len(args):
123
+ return None
124
+ index += 2
125
+ continue
126
+ if token.startswith("-"):
127
+ index += 1
128
+ continue
129
+ if token != "diff":
130
+ return None
131
+ operands = args[index + 1 :]
132
+ options = operands[: operands.index("--")] if "--" in operands else operands
133
+ if not _INDEX_DIFF_FLAGS.intersection(options):
134
+ return None
135
+ return "routed" if routed else "owned"
136
+ return None
137
+
138
+
139
+ def command_has_execution_routed_git_cached_diff(segments: tuple[tuple[str, ...], ...]) -> bool:
140
+ """Return whether any cached-diff segment overrides Git execution routing."""
141
+
142
+ return any(cached_diff_kind(tokens) == "routed" for tokens in segments)
143
+
144
+
145
+ def command_has_owned_git_cached_diff(segments: tuple[tuple[str, ...], ...]) -> bool:
146
+ """Return whether the command contains a named cached-diff Guard can own."""
147
+
148
+ if command_has_execution_routed_git_cached_diff(segments):
149
+ return False
150
+ return any(cached_diff_kind(tokens) == "owned" for tokens in segments)
151
+
152
+
153
+ def is_low_risk_git_index_inspection(
154
+ command_text: str,
155
+ *,
156
+ cwd: Path | None,
157
+ home_dir: Path | None = None,
158
+ ) -> bool:
159
+ """Recognize a repo-bound staged-index inspection, including if/then scans."""
160
+
161
+ return index_inspection_execution_context(command_text, cwd=cwd, home_dir=home_dir) is not None
162
+
163
+
164
+ def index_inspection_execution_context(
165
+ command_text: str,
166
+ *,
167
+ cwd: Path | None,
168
+ home_dir: Path | None = None,
169
+ ) -> ShellExecutionContext | None:
170
+ """Return the modeled context when a staged-index scan is repo-bound."""
171
+
172
+ if cwd is None:
173
+ return None
174
+ try:
175
+ execution_cwd = cwd.resolve()
176
+ except (OSError, RuntimeError):
177
+ return None
178
+ context = model_shell_execution_context(
179
+ command_text,
180
+ cwd=execution_cwd,
181
+ workspace_root=execution_cwd,
182
+ home_dir=home_dir,
183
+ )
184
+ if not _context_is_low_risk_git_index_inspection(context, home_dir=home_dir):
185
+ return None
186
+ return context
187
+
188
+
189
+ def _segment_has_mutating_redirection(tokens: tuple[str, ...]) -> bool:
190
+ for token in tokens:
191
+ if token in {">", ">>", ">|", "1>", "1>>", "1>|"}:
192
+ return True
193
+ if ">" in token and not token.startswith("--"):
194
+ return True
195
+ return False
196
+
197
+
198
+ def _flow_controls(controls: tuple[str, ...]) -> tuple[str, ...]:
199
+ return tuple(control for control in controls if control != "\n")
200
+
201
+
202
+ def _context_is_low_risk_git_index_inspection(
203
+ context: ShellExecutionContext,
204
+ *,
205
+ home_dir: Path | None,
206
+ ) -> bool:
207
+ if not context.segments or len(context.segments) > _MAX_SEGMENTS:
208
+ return False
209
+ if not context.complete and context.reason_code != "shell_cwd_unresolved_syntax":
210
+ return False
211
+ saw_cached_diff = False
212
+ previous_was_cached_diff = False
213
+ for segment in context.segments:
214
+ if any(control not in _ALLOWED_CONTROLS for control in (*segment.control_before, *segment.control_after)):
215
+ return False
216
+ tokens = executable_tokens(segment.tokens)
217
+ if tokens is None:
218
+ return False
219
+ if not tokens:
220
+ previous_was_cached_diff = False
221
+ continue
222
+ if _segment_has_mutating_redirection(segment.tokens):
223
+ return False
224
+ command_name, command_index = _shell_segment_primary_command(list(tokens))
225
+ if command_name is None or command_index is None:
226
+ return False
227
+ executable = tokens[command_index]
228
+ if "/" in executable or "\\" in executable:
229
+ return False
230
+ args = tokens[command_index + 1 :]
231
+ if command_name == "git":
232
+ if not _git_cached_diff_segment_is_safe(segment, tokens, home_dir=home_dir):
233
+ return False
234
+ saw_cached_diff = True
235
+ previous_was_cached_diff = True
236
+ continue
237
+ if command_name == "echo":
238
+ if not all(_static_echo_arg_is_safe(arg) for arg in args):
239
+ return False
240
+ previous_was_cached_diff = False
241
+ continue
242
+ if command_name == "rg":
243
+ if _flow_controls(segment.control_before) != ("|",) or not previous_was_cached_diff:
244
+ return False
245
+ if any(token.startswith("RIPGREP_CONFIG_PATH=") for token in segment.tokens) and "--no-config" not in args:
246
+ return False
247
+ if not _index_scan_rg_args_are_safe(args):
248
+ return False
249
+ previous_was_cached_diff = False
250
+ continue
251
+ return False
252
+ return saw_cached_diff
253
+
254
+
255
+ def owned_git_index_inspection_action_class(
256
+ command_text: str,
257
+ *,
258
+ cwd: Path | None,
259
+ home_dir: Path | None,
260
+ ) -> str | None:
261
+ """Return the Git-protection action class for an unproven cached diff."""
262
+
263
+ if "git" not in command_text or "diff" not in command_text or "--cached" not in command_text:
264
+ return None
265
+ parsing_cwd = cwd or home_dir or Path.cwd()
266
+ context = model_shell_execution_context(
267
+ command_text,
268
+ cwd=parsing_cwd,
269
+ workspace_root=parsing_cwd,
270
+ home_dir=home_dir,
271
+ )
272
+ segment_tokens = tuple(segment.tokens for segment in context.segments)
273
+ if command_has_execution_routed_git_cached_diff(segment_tokens):
274
+ return None
275
+ if not command_has_owned_git_cached_diff(segment_tokens):
276
+ return None
277
+ if cwd is not None and is_low_risk_git_index_inspection(command_text, cwd=cwd, home_dir=home_dir):
278
+ return None
279
+ if cwd is not None and is_low_risk_standalone_git_routine(context):
280
+ return None
281
+ return "git index inspection"
282
+
283
+
284
+ _SAFE_CACHED_DIFF_FLAGS: Final = frozenset(
285
+ {"--cached", "--check", "--name-only", "--name-status", "--staged", "--stat", "HEAD"}
286
+ )
287
+
288
+
289
+ def _safe_exclude_pathspec(value: str) -> bool:
290
+ if not value.startswith((":!", ":^")):
291
+ return False
292
+ remainder = value[2:]
293
+ if not remainder or remainder.startswith((":", "/", "~")):
294
+ return False
295
+ return _safe_repository_path(remainder)
296
+
297
+
298
+ def _cached_diff_operands_are_safe(args: tuple[str, ...]) -> bool:
299
+ if len(args) > 20:
300
+ return False
301
+ if "--" not in args:
302
+ return bool(_INDEX_DIFF_FLAGS.intersection(args)) and all(
303
+ arg in _SAFE_CACHED_DIFF_FLAGS or arg == "--cached" for arg in args
304
+ )
305
+ separator = args.index("--")
306
+ revisions = args[:separator]
307
+ if not _INDEX_DIFF_FLAGS.intersection(revisions):
308
+ return False
309
+ paths = args[separator + 1 :]
310
+ if not paths or len(paths) > 16:
311
+ return False
312
+ if any(arg not in _SAFE_CACHED_DIFF_FLAGS for arg in revisions):
313
+ return False
314
+ return all(_safe_repository_path(path) or _safe_exclude_pathspec(path) for path in paths)
315
+
316
+
317
+ def _proof_cached_diff_tokens(tokens: tuple[str, ...]) -> tuple[str, ...]:
318
+ command_name, command_index = _shell_segment_primary_command(list(tokens))
319
+ if command_name != "git" or command_index is None:
320
+ return ("git", "diff", "--cached", "--check")
321
+ prefix = tokens[: command_index + 1]
322
+ args = tokens[command_index + 1 :]
323
+ if args[:1] == ("-C",) and len(args) >= 2:
324
+ return (*prefix, "-C", args[1], "diff", "--cached", "--check")
325
+ return (*prefix, "diff", "--cached", "--check")
326
+
327
+
328
+ def _args_have_repository_selector(args: tuple[str, ...]) -> bool:
329
+ index = 0
330
+ while index < len(args):
331
+ token = args[index]
332
+ option_name = token.partition("=")[0]
333
+ if token == "-C" or token.startswith("-C") or option_name in _REPOSITORY_SELECTOR_OPTIONS:
334
+ return True
335
+ if token in _GIT_GLOBAL_FLAG_OPTIONS:
336
+ index += 1
337
+ continue
338
+ if option_name in _GIT_GLOBAL_VALUE_OPTIONS:
339
+ index += 1 if "=" in token else 2
340
+ continue
341
+ if token.startswith("-"):
342
+ index += 1
343
+ continue
344
+ return False
345
+ return False
346
+
347
+
348
+ def _has_unproven_repository_selector(tokens: tuple[str, ...]) -> bool:
349
+ stripped = executable_tokens(tokens)
350
+ if not stripped:
351
+ return True
352
+ command_name, command_index = _shell_segment_primary_command(list(stripped))
353
+ if command_name != "git" or command_index is None:
354
+ return True
355
+ args = stripped[command_index + 1 :]
356
+ if args[:1] == ("-C",):
357
+ if len(args) < 2 or not args[1] or args[1].startswith("-"):
358
+ return True
359
+ return _args_have_repository_selector(args[2:])
360
+ return _args_have_repository_selector(args)
361
+
362
+
363
+ def _git_cached_diff_segment_is_safe(
364
+ segment: ShellExecutionSegment,
365
+ tokens: tuple[str, ...],
366
+ *,
367
+ home_dir: Path | None,
368
+ ) -> bool:
369
+ if not _tokens_are_cached_diff(tokens) or _has_unproven_repository_selector(tokens):
370
+ return False
371
+ operands = git_diff_operands(tokens)
372
+ if operands is None or not _cached_diff_operands_are_safe(operands):
373
+ return False
374
+ synthetic = replace(segment, tokens=_proof_cached_diff_tokens(tokens))
375
+ repository_path = tokens[2] if tokens[:2] == ("git", "-C") and len(tokens) > 2 else None
376
+ return is_low_risk_git_inspection_segment(synthetic) and _git_show_has_execution_free_config(
377
+ synthetic, repository_path=repository_path
378
+ )
379
+
380
+
381
+ def git_diff_operands(tokens: tuple[str, ...]) -> tuple[str, ...] | None:
382
+ kind = cached_diff_kind(tokens)
383
+ if kind != "owned":
384
+ return None
385
+ stripped = executable_tokens(tokens)
386
+ if not stripped:
387
+ return None
388
+ command_name, command_index = _shell_segment_primary_command(list(stripped))
389
+ if command_name != "git" or command_index is None:
390
+ return None
391
+ args = stripped[command_index + 1 :]
392
+ index = 0
393
+ while index < len(args):
394
+ token = args[index]
395
+ option_name = token.partition("=")[0]
396
+ if token in _GIT_GLOBAL_FLAG_OPTIONS:
397
+ index += 1
398
+ continue
399
+ if option_name in _GIT_GLOBAL_VALUE_OPTIONS:
400
+ index += 1 if "=" in token else 2
401
+ continue
402
+ if token.startswith("-"):
403
+ index += 1
404
+ continue
405
+ if token != "diff":
406
+ return None
407
+ return args[index + 1 :]
408
+ return None
@@ -6,6 +6,7 @@ from pathlib import Path
6
6
 
7
7
  from ..compound_git_inspection import is_low_risk_git_inspection_segment
8
8
  from ..git_execution_safety import trusted_git_binary_for_cwd
9
+ from ..git_index_inspection import is_low_risk_git_index_inspection
9
10
  from ..kubernetes_commands import kubernetes_read_only_inventory_args
10
11
  from ..shell_command_wrappers import is_trusted_absolute_command_path
11
12
  from ..shell_execution_context import ShellExecutionContext, ShellExecutionSegment, model_shell_execution_context
@@ -75,7 +76,7 @@ def _looks_like_safe_compound_developer_inspection(
75
76
 
76
77
  graph = _compound_developer_effect_graph(command_text, cwd=cwd, home_dir=home_dir)
77
78
  if graph is None or not graph.context.complete:
78
- return False
79
+ return is_low_risk_git_index_inspection(command_text, cwd=cwd, home_dir=home_dir)
79
80
  silently_verified_effects = {
80
81
  DeveloperShellEffect.DIRECTORY,
81
82
  DeveloperShellEffect.LOCAL_READ,
@@ -151,7 +152,7 @@ def _git_segment_is_silently_verified(
151
152
  return "|" not in (*segment.control_before, *segment.control_after) and is_low_risk_git_inspection_segment(
152
153
  segment
153
154
  )
154
- if operation in {"blame", "branch", "show", "worktree"}:
155
+ if operation in {"blame", "branch", "diff", "show", "worktree"}:
155
156
  return is_low_risk_git_inspection_segment(segment)
156
157
  return operation in {"ls-files", "rev-parse"}
157
158
 
@@ -7,6 +7,7 @@ import re
7
7
  from dataclasses import replace
8
8
  from pathlib import Path
9
9
 
10
+ from ..git_index_inspection import index_inspection_execution_context
10
11
  from ..shell_execution_context import ShellExecutionContext, model_shell_execution_context
11
12
  from .developer_inspection import (
12
13
  _low_risk_compound_developer_execution_context,
@@ -37,11 +38,15 @@ def low_risk_compound_developer_execution_context(
37
38
  )
38
39
  return replace(recovered, command_text=command_text) if recovered is not None else None
39
40
 
40
- return _low_risk_compound_developer_execution_context(
41
- command_text,
42
- cwd=cwd,
43
- home_dir=home_dir,
44
- ) or _bounded_verified_source_edit_execution_context(command_text, home_dir=home_dir)
41
+ return (
42
+ _low_risk_compound_developer_execution_context(
43
+ command_text,
44
+ cwd=cwd,
45
+ home_dir=home_dir,
46
+ )
47
+ or index_inspection_execution_context(command_text, cwd=cwd, home_dir=home_dir)
48
+ or _bounded_verified_source_edit_execution_context(command_text, home_dir=home_dir)
49
+ )
45
50
 
46
51
 
47
52
  def _bounded_verified_source_edit_execution_context(
@@ -10,6 +10,7 @@ from typing import cast
10
10
  from ..command_model import CanonicalCommand
11
11
  from ..compound_git_inspection import is_low_risk_git_inspection_segment, is_low_risk_standalone_git_routine
12
12
  from ..git_execution_safety import git_status_args_are_read_only, git_status_has_execution_free_config
13
+ from ..git_index_inspection import owned_git_index_inspection_action_class
13
14
  from ..kubernetes_commands import kubernetes_secret_read_source
14
15
  from ..shell_command_wrappers import normalize_transparent_shell_command
15
16
  from ..shell_execution_context import ShellExecutionContext, model_shell_execution_context
@@ -266,7 +267,7 @@ def _unverified_git_fetch_request(
266
267
  cwd: Path | None,
267
268
  home_dir: Path | None,
268
269
  ) -> ToolActionRequestMatch | None:
269
- if "git" not in command_text or "fetch" not in command_text:
270
+ if "git" not in command_text:
270
271
  return None
271
272
  parsing_cwd = cwd or home_dir or Path.cwd()
272
273
  context = model_shell_execution_context(
@@ -276,7 +277,24 @@ def _unverified_git_fetch_request(
276
277
  home_dir=home_dir,
277
278
  )
278
279
  if not any(_segment_invokes_git_fetch(segment.tokens) for segment in context.segments):
279
- return None
280
+ action_class = owned_git_index_inspection_action_class(
281
+ command_text,
282
+ cwd=cwd,
283
+ home_dir=home_dir,
284
+ )
285
+ if action_class is None:
286
+ return None
287
+ return ToolActionRequestMatch(
288
+ tool_name=tool_name,
289
+ normalized_tool_name=normalized_tool_name,
290
+ command_text=command_text,
291
+ action_class=action_class,
292
+ reason=(
293
+ "Git cached diff needs a repository-bound index inspection Guard can verify. "
294
+ "Use git diff --cached with check/stat flags or lockfile excludes, "
295
+ "or confirm the exact index read in Guard."
296
+ ),
297
+ )
280
298
  if cwd is not None and is_low_risk_standalone_git_routine(context):
281
299
  return None
282
300
  if context.complete and all(
@@ -1,3 +1,3 @@
1
1
  """Single source of truth for tool version."""
2
2
 
3
- __version__ = "2.2.112"
3
+ __version__ = "2.2.113"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plugin-scanner
3
- Version: 2.2.112
3
+ Version: 2.2.113
4
4
  Summary: Lint, verify, and gate plugin ecosystems for maintainers, CI, and publish workflows.
5
5
  Project-URL: Homepage, https://hol.org/guard
6
6
  Project-URL: Security, https://hol.org/guard/security
@@ -28,7 +28,7 @@ codex_plugin_scanner/trust_scoring.py,sha256=t_Lw13HZ766UCJzTtNNwllZK_k2ks_x-8St
28
28
  codex_plugin_scanner/trust_skill_scoring.py,sha256=e-SrMBgAruzFn4RD0pGpP9rtPXxc2ji_CKpcnFPRh3M,12542
29
29
  codex_plugin_scanner/trust_specs.py,sha256=aYGSB8M9RQp1uwhsRaWlVa1M9Vk51kUIxmBXkW8noZU,10729
30
30
  codex_plugin_scanner/verification.py,sha256=J2nsnYlxNYecSvmSQhV_leImLslCOotIK2FSmEoIjg0,27896
31
- codex_plugin_scanner/version.py,sha256=nlMKlHr2oThwfMO6dbFGUhpNwaC9RvhwkpcoYAVLiQQ,72
31
+ codex_plugin_scanner/version.py,sha256=KgFbHTUEzOtocytyfrjipxG2nB-W_SCggizSg8V1BW0,72
32
32
  codex_plugin_scanner/checks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
33
  codex_plugin_scanner/checks/best_practices.py,sha256=uB3DXLggqxjfyBMH_1yvX4nyFhpIvaRHjTyyueoO-GQ,8473
34
34
  codex_plugin_scanner/checks/claude.py,sha256=cMucH5pmKgQtBnDcycOzMMlKL-Q5HQemPqUmTLF0DzE,10189
@@ -462,8 +462,8 @@ codex_plugin_scanner/guard/runtime/command_activity_lifecycle.py,sha256=N6MZQsCH
462
462
  codex_plugin_scanner/guard/runtime/command_activity_privacy.py,sha256=X9ytuqSTDtIDRlo294MbImLVlUjAnl5-O_FnaaXqfaM,12695
463
463
  codex_plugin_scanner/guard/runtime/command_backup_extensions.py,sha256=9lX8La9_YIicQ8J76iBC9fPGsJhxhH4YzBuzamY0GLU,10105
464
464
  codex_plugin_scanner/guard/runtime/command_builtin_extension_catalog.py,sha256=Yogu6iiOCWQU7DaPr4gfa8gOsTmu-HJns5c2DsaZUrE,2200
465
- codex_plugin_scanner/guard/runtime/command_builtin_extension_registry.py,sha256=0tszfUNg0Km4ROHTxbBbl-em2Aq0vDRZZR8pfzzEkIk,7923
466
- codex_plugin_scanner/guard/runtime/command_builtin_rules.py,sha256=FuaAdtRxdm1iwN0a1p0UiPKF__y3K_BlQFms9NF0Y18,19746
465
+ codex_plugin_scanner/guard/runtime/command_builtin_extension_registry.py,sha256=2gXikIL5m0iO_CNRkZUgb5UMnLKcucX0wFEXySgyZqw,8139
466
+ codex_plugin_scanner/guard/runtime/command_builtin_rules.py,sha256=-sWYS27t0kmE7vRDrnic_hZAnq4O4iMyQQ8wGGsaVXU,20185
467
467
  codex_plugin_scanner/guard/runtime/command_capability.py,sha256=12xyfo2T8YYqnX9NCcKE3eNdKKgRvyJRmgUKjZeh9Fw,25185
468
468
  codex_plugin_scanner/guard/runtime/command_cicd_extensions.py,sha256=QSyP5EP0pdeAKsTf3cZL_mEX3fyihG6KazoD7TdFEJc,6819
469
469
  codex_plugin_scanner/guard/runtime/command_cloud_extensions.py,sha256=-C9NJvqRXbIBZa11t-hh4qHwYkgjinKCoVomsHy1UEU,9601
@@ -506,7 +506,7 @@ codex_plugin_scanner/guard/runtime/command_tokens.py,sha256=GY9ehRZB8O_CGHxGh3xh
506
506
  codex_plugin_scanner/guard/runtime/command_verified_read_candidates.py,sha256=Qh_0bMm3VCp6GUkEeagcLtwYvtO0X1A_HN9_lgU-6eI,6599
507
507
  codex_plugin_scanner/guard/runtime/command_workspace_write_candidates.py,sha256=AANBnFfGC-Jzg3RL6nYkamF3ndn7obB6LhKuaH1DdaE,6633
508
508
  codex_plugin_scanner/guard/runtime/composition_rules.py,sha256=5oo0b1kL6hmFYyMSD9kCKcgA-m6fDKWy4pDeq3i1Ajk,6797
509
- codex_plugin_scanner/guard/runtime/compound_git_inspection.py,sha256=_KDl3nXLAs5X7-EpsijjUqOJDzXGaY0zmsuIMXxX3BM,17705
509
+ codex_plugin_scanner/guard/runtime/compound_git_inspection.py,sha256=XxQ_pqurgsYN621nEEW2RPEZgT1iS8QRVUevZxU9t6A,17787
510
510
  codex_plugin_scanner/guard/runtime/containment_contract.py,sha256=8UN1_-Qmr8i3VmBGddlxz3Vt7Pr3q-HHQBC10hBumq0,17963
511
511
  codex_plugin_scanner/guard/runtime/containment_executor.py,sha256=RYQ2GnxRy3kHPyxTYaVEYjwIL__fMDzL5zsF2EPUHck,16454
512
512
  codex_plugin_scanner/guard/runtime/containment_health.py,sha256=Ji0wdiLjACZL0f-r5JHtOenoXEFv1JkNIELrA0iALkM,13292
@@ -530,6 +530,7 @@ codex_plugin_scanner/guard/runtime/extension_control_resolver.py,sha256=aNmVrVjt
530
530
  codex_plugin_scanner/guard/runtime/extension_evidence.py,sha256=6gS05shn1OI5bmuFxGu5El4rHuICsvpI65WJV7oezmY,10583
531
531
  codex_plugin_scanner/guard/runtime/false_positive_rules.py,sha256=OcXN1FItvuapKosg7kRNiys9YcJN4ALVzN2hKdX4VCw,24316
532
532
  codex_plugin_scanner/guard/runtime/git_execution_safety.py,sha256=D2Ma5jnvTFosfoTt9hmN2c95ZZGLOc1NTxFR9E_57HY,28722
533
+ codex_plugin_scanner/guard/runtime/git_index_inspection.py,sha256=XlQwb_cFmdjfWoTTC3nGzJfjVEt3BIb9Gyt7_q1EQqY,14467
533
534
  codex_plugin_scanner/guard/runtime/git_pathspecs.py,sha256=skNNsz-60pnFUqsaYJI9RRkwzUGGttCF-fvMdpOq7iE,12785
534
535
  codex_plugin_scanner/guard/runtime/github_actions_read_workflow.py,sha256=-SFO8GLLBFw1iURh9TePVFCid1DCJPzKkzx45u92VCY,16904
535
536
  codex_plugin_scanner/guard/runtime/github_capability_contract.py,sha256=c7OLPdG6X_naoOrq-XpFyeoB2i0BvBzj_ynYPTjcILE,13091
@@ -650,7 +651,7 @@ codex_plugin_scanner/guard/runtime/secret_file_request_services/constants_patter
650
651
  codex_plugin_scanner/guard/runtime/secret_file_request_services/credential_exfiltration.py,sha256=y-knVxmSJyMTcgsmnbzBbcbaInmHFIJuejAvBlylLyA,12911
651
652
  codex_plugin_scanner/guard/runtime/secret_file_request_services/destructive_shell_detection.py,sha256=-KO2-JEBIn820jHxdwe0xiSI5EZPtQ8aj6MM13Nz33s,12148
652
653
  codex_plugin_scanner/guard/runtime/secret_file_request_services/developer_inspection.py,sha256=3YImzaN_7G020orTLn6TQ4xoavSgPkdX5D_mzi29z7c,22430
653
- codex_plugin_scanner/guard/runtime/secret_file_request_services/developer_routines.py,sha256=eui3zTGogz-fyPRy_NBT3r4-i6Rm1NpPL2vbnn48ZRg,11095
654
+ codex_plugin_scanner/guard/runtime/secret_file_request_services/developer_routines.py,sha256=UnufG04UyQ7xbwhxZHdzofIwjKlbYUjOPHiewqgBa4k,11240
654
655
  codex_plugin_scanner/guard/runtime/secret_file_request_services/docker_requests.py,sha256=c-8PEQVOG6KbryJaDKZQw9wCIlaniV_ULKPG48BC5Eg,15308
655
656
  codex_plugin_scanner/guard/runtime/secret_file_request_services/encoded_payloads.py,sha256=kiEG_GxK1Rwhji3G8mZE0VZBQ32RDqHoEEIqGgLJI7A,16106
656
657
  codex_plugin_scanner/guard/runtime/secret_file_request_services/git_routines.py,sha256=xsfuu3TPK8Ox1lFUDukN3xHG838-8Bm7FTJgbRChjWI,10335
@@ -684,8 +685,8 @@ codex_plugin_scanner/guard/runtime/secret_file_request_services/shell_request_cl
684
685
  codex_plugin_scanner/guard/runtime/secret_file_request_services/shell_static_safety.py,sha256=h1ew2l9euL5fIQYn2hj8b0PQiM7CUvHGR-WsS-gJ4WA,15237
685
686
  codex_plugin_scanner/guard/runtime/secret_file_request_services/shell_stdin_sources.py,sha256=dvGdc8dJvYjFF29fdQNtixuyQsjUEXo54cx7Pf_RN_g,9887
686
687
  codex_plugin_scanner/guard/runtime/secret_file_request_services/shell_tokenization.py,sha256=2beHCIfbvsPoF2qfqrZ5rpgpJORFf1qeSH9JNPsZTCo,12861
687
- codex_plugin_scanner/guard/runtime/secret_file_request_services/source_edit_context.py,sha256=uPHtXx2o-Wl4dQzI2qXp697lEh_zcVX302d4vDfjA7Q,10869
688
- codex_plugin_scanner/guard/runtime/secret_file_request_services/tool_action_requests.py,sha256=yOQD_b7YyXDmC1Gua_Qz7p1bCCnTC1X8NT9xNPwfsj0,16628
688
+ codex_plugin_scanner/guard/runtime/secret_file_request_services/source_edit_context.py,sha256=iV5Z2PtenR8nWixOta4nxOYcxGYqxGRdupi-x5JjvBw,11067
689
+ codex_plugin_scanner/guard/runtime/secret_file_request_services/tool_action_requests.py,sha256=Q8LUU6SNM8z6tCrX-AYmCwnH_7blbTJ7ypWM5M4vcVk,17352
689
690
  codex_plugin_scanner/guard/runtime/secret_file_request_services/typescript_graphql_safety.py,sha256=OFj97zDy-WjVgbVu76eONG315Z1rPUMapySb-9b52RU,13041
690
691
  codex_plugin_scanner/guard/runtime/secret_file_request_services/upload_arguments.py,sha256=25ZGoYVG5kiCK1SChRt6OdyWuDc5M6CjZS6rSNW7k1U,10579
691
692
  codex_plugin_scanner/guard/schemas/__init__.py,sha256=YFJmxLMTfLclVIQ7ngTwRkeRLdBJEAF_ljc2jJTgvjE,220
@@ -699,8 +700,8 @@ codex_plugin_scanner/integrations/cisco_skill_scanner.py,sha256=3tW9Ya5riLsIbU9Y
699
700
  codex_plugin_scanner/rules/__init__.py,sha256=bhAnNlVvEiAoksf_-Vd4f74JNz66pTINhUO2mMc8kvs,293
700
701
  codex_plugin_scanner/rules/registry.py,sha256=7BaRerM26teDUM0S48d3sPHbqzmq2JhuelC0_ni3364,4580
701
702
  codex_plugin_scanner/rules/specs.py,sha256=gpU0lijxqdSEZMMf28vciQxob-lvBEPcMQ4BG6N_r3I,650
702
- plugin_scanner-2.2.112.dist-info/METADATA,sha256=qjrxsOUFH9jWL_rdeB4NSIYSLrF8Eca9x8Mvtda6E74,47988
703
- plugin_scanner-2.2.112.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
704
- plugin_scanner-2.2.112.dist-info/entry_points.txt,sha256=SLGKi3GvvSsPwave-k2JQt2meG0pM7aaC6VlUy47Crg,122
705
- plugin_scanner-2.2.112.dist-info/licenses/LICENSE,sha256=UbiP0-niTtzc4d0UWk_KLc0SkRQH8AsG36b_YA-XM8E,11358
706
- plugin_scanner-2.2.112.dist-info/RECORD,,
703
+ plugin_scanner-2.2.113.dist-info/METADATA,sha256=fiqzYiXI-fXoa2tKa1TR-zZ9k33Zrj4y4t7qoMj1Jdg,47988
704
+ plugin_scanner-2.2.113.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
705
+ plugin_scanner-2.2.113.dist-info/entry_points.txt,sha256=SLGKi3GvvSsPwave-k2JQt2meG0pM7aaC6VlUy47Crg,122
706
+ plugin_scanner-2.2.113.dist-info/licenses/LICENSE,sha256=UbiP0-niTtzc4d0UWk_KLc0SkRQH8AsG36b_YA-XM8E,11358
707
+ plugin_scanner-2.2.113.dist-info/RECORD,,