claude-dev-env 8.43.3 → 8.43.4

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.
@@ -17,6 +17,8 @@ from hooks_constants.pr_lifecycle_skill_gate_constants import (
17
17
  ALL_COMMAND_PREFIX_WORDS,
18
18
  ALL_GITHUB_MCP_TOOL_SUFFIXES,
19
19
  ALL_PREFIX_OPTIONS_WITH_VALUE,
20
+ ALL_PYTHON_EXECUTABLES,
21
+ ALL_SHELL_WRAPPER_EXECUTABLES,
20
22
  ALL_SLASH_COMMAND_MARKERS,
21
23
  ALL_TRANSCRIPT_PATH_FIELDS,
22
24
  COMMAND_SEPARATORS,
@@ -33,8 +35,11 @@ from hooks_constants.pr_lifecycle_skill_gate_constants import (
33
35
  HOOK_SPECIFIC_OUTPUT_KEY,
34
36
  MERGE_PATH_SUFFIX,
35
37
  OPTION_AND_VALUE_WORD_COUNT,
38
+ PATH_SEPARATOR_PATTERN,
36
39
  PERMISSION_DECISION_KEY,
37
40
  PERMISSION_DECISION_REASON_KEY,
41
+ PULL_REQUEST_SCRIPT_NAME,
42
+ PYTHON_OPTIONS_WITH_VALUE,
38
43
  SHELL_TOOL_NAMES,
39
44
  SKILL_NAME,
40
45
  )
@@ -117,12 +122,29 @@ def _words_from_executable(all_words: list[str]) -> list[str]:
117
122
  return []
118
123
 
119
124
 
125
+ def _file_name(path_word: str) -> str:
126
+ return re.split(PATH_SEPARATOR_PATTERN, path_word)[-1].lower()
127
+
128
+
129
+ def _runs_pull_request_script(all_words: list[str]) -> bool:
130
+ script, _ = _next_command_word(all_words, 1, PYTHON_OPTIONS_WITH_VALUE)
131
+ return script is not None and _file_name(script) == PULL_REQUEST_SCRIPT_NAME
132
+
133
+
120
134
  def _matches_shell_command(command: str) -> bool:
121
135
  for each_segment in _command_segments(command):
122
136
  all_command_words = _words_from_executable(each_segment)
123
137
  if not all_command_words:
124
138
  continue
125
- executable = all_command_words[0].lower()
139
+ executable = _file_name(all_command_words[0])
140
+ if executable == PULL_REQUEST_SCRIPT_NAME:
141
+ return True
142
+ if executable in ALL_PYTHON_EXECUTABLES and _runs_pull_request_script(all_command_words):
143
+ return True
144
+ if executable in ALL_SHELL_WRAPPER_EXECUTABLES and any(
145
+ _matches_shell_command(each_argument) for each_argument in all_command_words[1:]
146
+ ):
147
+ return True
126
148
  if executable in {"git", "git.exe"} and _is_git_action(all_command_words):
127
149
  return True
128
150
  if executable in {"gh", "gh.exe"} and _is_gh_action(all_command_words):
@@ -128,6 +128,33 @@ def test_non_action_command_shapes(tmp_path: Path) -> None:
128
128
  assert gate.decision_for(_payload(command, path)) is None, command
129
129
 
130
130
 
131
+ def test_wrapped_and_scripted_action_shapes_are_denied(tmp_path: Path) -> None:
132
+ path = _transcript(tmp_path)
133
+ commands = (
134
+ 'bash -c "git push"',
135
+ "sh -lc 'gh pr create'",
136
+ 'pwsh -NoProfile -Command "git commit -F message.txt"',
137
+ "echo `git push`",
138
+ "python ~/.agents/skills/pull-request/scripts/pull_request.py create --title-file t.txt",
139
+ "python3 -X utf8 'C:\\repo\\scripts\\pull_request.py' edit",
140
+ )
141
+ for command in commands:
142
+ _assert_denied(_payload(command, path))
143
+
144
+
145
+ def test_wrapped_and_scripted_non_action_shapes_are_allowed(tmp_path: Path) -> None:
146
+ path = _transcript(tmp_path)
147
+ commands = (
148
+ 'bash -c "git status"',
149
+ "bash script.sh",
150
+ "cat scripts/pull_request.py",
151
+ "python -m pytest scripts/test_pull_request.py",
152
+ "echo `git log`",
153
+ )
154
+ for command in commands:
155
+ assert gate.decision_for(_payload(command, path)) is None, command
156
+
157
+
131
158
  def test_matching_github_mcp_tools_are_denied(tmp_path: Path) -> None:
132
159
  path = _transcript(tmp_path)
133
160
  for suffix in ("create_pull_request", "merge_pull_request", "enable_pr_auto_merge", "update_pull_request"):
@@ -18,7 +18,7 @@ ALL_GITHUB_MCP_TOOL_SUFFIXES = (
18
18
  "__update_pull_request",
19
19
  )
20
20
  ALL_TRANSCRIPT_PATH_FIELDS = ("transcript_path", "agent_transcript_path")
21
- COMMAND_SEPARATORS = ";|&\n()"
21
+ COMMAND_SEPARATORS = ";|&\n()`"
22
22
  COMMAND_WHITESPACE = " \t\r"
23
23
  OPTION_AND_VALUE_WORD_COUNT = 2
24
24
  GIT_GLOBAL_OPTIONS_WITH_VALUE = frozenset({"-C", "-c", "--git-dir", "--work-tree", "--namespace", "--config-env"})
@@ -31,3 +31,8 @@ ALL_API_ACTION_NAMES = ("auto-merge", "enablePullRequestAutoMerge")
31
31
  ALL_COMMAND_PREFIX_WORDS = frozenset({"env", "sudo", "command", "exec", "nohup", "time"})
32
32
  ALL_PREFIX_OPTIONS_WITH_VALUE = frozenset({"-u", "--unset", "-C", "--chdir", "-g", "--group", "--user"})
33
33
  ENVIRONMENT_ASSIGNMENT_PATTERN = r"^[A-Za-z_][A-Za-z0-9_]*="
34
+ ALL_SHELL_WRAPPER_EXECUTABLES = frozenset({"bash", "sh", "zsh", "dash", "pwsh", "pwsh.exe", "powershell", "powershell.exe"})
35
+ ALL_PYTHON_EXECUTABLES = frozenset({"python", "python3", "python.exe", "python3.exe", "py", "py.exe"})
36
+ PULL_REQUEST_SCRIPT_NAME = "pull_request.py"
37
+ PATH_SEPARATOR_PATTERN = r"[\\/]"
38
+ PYTHON_OPTIONS_WITH_VALUE = frozenset({"-X", "-W"})
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-dev-env",
3
- "version": "8.43.3",
3
+ "version": "8.43.4",
4
4
  "description": "Claude Code development standards — rules, hooks, agents, commands, and skills",
5
5
  "type": "module",
6
6
  "bin": {