@codyswann/lisa 2.295.1 → 2.295.3

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.
Files changed (66) hide show
  1. package/dist/core/upstream-evidence-manifest.js +4 -4
  2. package/dist/opencode/plugin-templates/parity-safety-net-heredoc.py +20 -1
  3. package/dist/opencode/plugin-templates/parity-safety-net.sh +10 -4
  4. package/expo/package-lisa/package.lisa.json +2 -0
  5. package/package.json +1 -1
  6. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  7. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  8. package/plugins/lisa/hooks/parity-safety-net-heredoc.py +20 -1
  9. package/plugins/lisa/hooks/parity-safety-net.sh +10 -4
  10. package/plugins/lisa-agy/hooks/parity-safety-net-heredoc.py +20 -1
  11. package/plugins/lisa-agy/hooks/parity-safety-net.sh +10 -4
  12. package/plugins/lisa-agy/plugin.json +1 -1
  13. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  14. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  15. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  16. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  17. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  18. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  19. package/plugins/lisa-copilot/hooks/parity-safety-net-heredoc.py +20 -1
  20. package/plugins/lisa-copilot/hooks/parity-safety-net.sh +10 -4
  21. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  22. package/plugins/lisa-cursor/hooks/parity-safety-net-heredoc.py +20 -1
  23. package/plugins/lisa-cursor/hooks/parity-safety-net.sh +10 -4
  24. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  25. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  26. package/plugins/lisa-expo-agy/plugin.json +1 -1
  27. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  28. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  29. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  31. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  32. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  33. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  34. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  35. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  36. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  37. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  39. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  40. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  41. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  42. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  44. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  46. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  47. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  49. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  51. package/plugins/lisa-rails-agy/plugin.json +1 -1
  52. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  56. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  57. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  59. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  61. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  62. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  64. package/plugins/src/base/hooks/parity-safety-net-heredoc.py +20 -1
  65. package/plugins/src/base/hooks/parity-safety-net.sh +10 -4
  66. package/typescript/package-lisa/package.lisa.json +2 -0
@@ -92,7 +92,7 @@ export const UPSTREAM_EVIDENCE_MANIFEST = Object.freeze({
92
92
  "expo/github-rulesets/playwright.json": "b57ccd12f768a1466a9681528858e59764bd0efedcbcb3a96c2fb40719bbd579",
93
93
  "expo/merge/.claude/settings.json": "e97b33d7ad86b71abba94ab957d725e34c3cafca455e92fba0b29ebabc3b73d1",
94
94
  "expo/merge/.oxlintrc.json": "2a0ea2191abd5b377aed4b525a4a97d3eefb1d9e41c845c02ee0daac75df6b1f",
95
- "expo/package-lisa/package.lisa.json": "bd2a5220b83c55f777c24fccf07f58bb277b526edc31a472232a8f34a8cfedd2",
95
+ "expo/package-lisa/package.lisa.json": "c0b971d10d40215ea0c0fa1bf1635322ed20ede330306e47e028bae8af03444d",
96
96
  "harper-fabric/copy-contents/.prettierignore": "478c782f4c5611187e21584dfd5522e37fc636c5eb03394fea3db45321c6712c",
97
97
  "harper-fabric/copy-contents/gitignore": "d091c40fd0ea3068491f1be0d6510090ae872ca95d0d058ef4595d6492d13897",
98
98
  "harper-fabric/copy-overwrite/.github/dependabot.yml": "e28fde0d906478b3fb7d6b7fc16f7fc5a4a0a20d8160f547f2b4be97c90578b7",
@@ -292,9 +292,9 @@ export const UPSTREAM_EVIDENCE_MANIFEST = Object.freeze({
292
292
  "plugins/src/base/hooks/inject-flow-context.sh": "f4ab07065762d592c0fc36be2e4918e767b293aaa4094b9d85d69010576c6dbf",
293
293
  "plugins/src/base/hooks/inject-rules.sh": "89a3780250e21aefa0eb83235523835ec0f83b25807b1ccc21f7a397a0459842",
294
294
  "plugins/src/base/hooks/install-pkgs.sh": "e9a13faba849a277410dde911ab102ee6f88ef915b33571bb1d4eef904172db5",
295
- "plugins/src/base/hooks/parity-safety-net-heredoc.py": "c5301958ed8ca29b0de7b8521d299cdbc8c1c402df8d8fada6b5f173b6cf5dbe",
295
+ "plugins/src/base/hooks/parity-safety-net-heredoc.py": "5acc70d65304d8ef01bf3837cbf0fdc77aae6d6e47c0fec824692fe7091da20b",
296
296
  "plugins/src/base/hooks/parity-safety-net.agy.sh": "ce9bd2b4566ad9147e4ace56434cffbbe87edb4ccbb57549ab3fd9f4c671ced4",
297
- "plugins/src/base/hooks/parity-safety-net.sh": "b83c5043a0f29b2d8851258acfe6906aab7922cd80f486e25220e55f26a8c0a5",
297
+ "plugins/src/base/hooks/parity-safety-net.sh": "f740f600b255742a50f28e5c9d09427c5783c4084e84052dca46bc1f0c94a187",
298
298
  "plugins/src/base/hooks/setup-jira-cli.sh": "a675f6b17ce7f0d6b9fb7daeba6d2bc59bcb48ef79b034076ea73b2b1e72ee09",
299
299
  "plugins/src/base/hooks/shell-write-nudge.sh": "69839af423f8792b1e52c71097316c3264425553031627c0a2f9409a9d5becd0",
300
300
  "plugins/src/base/hooks/threshold-ratchet-compare.mjs": "8bc4b379d7057b2fecf9cb35820c39efad0a65df7df49073786f996cc2a74810",
@@ -1035,7 +1035,7 @@ export const UPSTREAM_EVIDENCE_MANIFEST = Object.freeze({
1035
1035
  "typescript/github-rulesets/quality-checks.json": "89b9e09267cdb65865728d0e873de9c1225b8ca796e67f06fa8b72ccf6d4515e",
1036
1036
  "typescript/merge/.claude/settings.json": "834e6456cae81f45073bbbb08bf731fef043efb30cae2658cb4a59f17e66d165",
1037
1037
  "typescript/merge/.oxlintrc.json": "4debc093acfd263eb81ae15894073ebf098513204f45f40b83fb8fa5353fa1f8",
1038
- "typescript/package-lisa/package.lisa.json": "7e99286c549c79b1c02c07a4313dcfe5b41b4ebb80a6fbbb8741dd74dc5f454d",
1038
+ "typescript/package-lisa/package.lisa.json": "e661329de6f19550953e3522c0dfbc643397ca9a27e975f23437b64bd50b475c",
1039
1039
  "ui/README.md": "deeb35e767ea5dd2883268835ea3ad21cbad9fa63ec8d8ff5e200f0e2a7d2751",
1040
1040
  "ui/index.html": "a66598558cb95873aa674055d3bc3335654f7c615013bae0fa8effa35f64c351",
1041
1041
  });
@@ -28,6 +28,7 @@ class Marker:
28
28
  end: int
29
29
  delimiter: str
30
30
  strip_tabs: bool
31
+ quoted: bool
31
32
 
32
33
 
33
34
  def shell_tokens(prefix: str) -> list[str] | None:
@@ -357,7 +358,7 @@ def parse_marker(line: str, start: int, offset: int) -> Marker | None:
357
358
  final = index + len(delimiter)
358
359
  if not delimiter:
359
360
  return None
360
- return Marker(offset + start, offset + final, delimiter, strip_tabs)
361
+ return Marker(offset + start, offset + final, delimiter, strip_tabs, quote is not None)
361
362
 
362
363
 
363
364
  def marker_is_closed(command: str, marker: Marker) -> bool:
@@ -370,6 +371,19 @@ def marker_is_closed(command: str, marker: Marker) -> bool:
370
371
  return False
371
372
 
372
373
 
374
+ def single_closed_quoted_nonwriter_heredoc(command: str, marker: Marker) -> bool:
375
+ """Allow guard scanning for inert quoted heredoc payloads outside gh writers."""
376
+ if not marker.quoted or not marker_is_closed(command, marker):
377
+ return False
378
+ line_end = command.find("\n", marker.start)
379
+ if line_end < 0:
380
+ line_end = len(command)
381
+ header = command[:line_end]
382
+ if has_active_command_substitution(header):
383
+ return False
384
+ return True
385
+
386
+
373
387
  def main() -> int:
374
388
  command = sys.stdin.read()
375
389
  if "<<" not in command:
@@ -402,6 +416,11 @@ def main() -> int:
402
416
  ):
403
417
  return MALFORMED
404
418
 
419
+ if len(markers) == 1 and single_closed_quoted_nonwriter_heredoc(
420
+ logical_command, markers[0]
421
+ ):
422
+ return UNSUPPORTED
423
+
405
424
  # Nested substitution plus a heredoc is executable shell syntax unless it
406
425
  # matched the one exact quoted `--body "$(cat ...)"` form above.
407
426
  if has_active_command_substitution(logical_command):
@@ -96,15 +96,21 @@ EOF
96
96
  # remain visible to every built-in and custom rule. Ambiguous or malformed
97
97
  # heredocs fail closed instead of guessing which text the shell would execute.
98
98
  #
99
- # block_heredoc() teaches the remediation the moment the wall is hit: heredoc
100
- # denials overwhelmingly come from `git commit -m "$(cat <<EOF …)"` attempts,
101
- # and a bare denial strands the agent with no path forward (gardener #1789).
99
+ # block_heredoc() teaches the remediation the moment the wall is hit. Commit
100
+ # heredocs need commit-message-file guidance; other heredocs should point agents
101
+ # at script/payload files instead of implying every denial was a commit attempt.
102
102
  block_heredoc() {
103
- block "$1
103
+ if printf '%s' "$command_str" | grep -Eq '(^|[^[:alnum:]_-])git[[:space:]]+([^;&|[:space:]]+[[:space:]]+)*commit([^[:alnum:]_-]|$)'; then
104
+ block "$1
104
105
  Heredoc commit invocations are blocked (the payload is executable shell).
105
106
  Fix: write the commit message to a file and run \`git commit -F <file>\`.
106
107
  Every commit must also carry a Co-authored-by trailer for a supported agent
107
108
  (Claude/Codex/OpenCode) — the commit-msg hook enforces this."
109
+ fi
110
+ block "$1
111
+ Heredoc command invocations are blocked when Lisa cannot prove the payload is
112
+ non-executable text.
113
+ Fix: write the payload to a file and execute or pass that file explicitly."
108
114
  }
109
115
 
110
116
  command_for_guards="$command_str"
@@ -98,6 +98,7 @@
98
98
  },
99
99
  "resolutions": {
100
100
  "@isaacs/brace-expansion": "^5.0.1",
101
+ "brace-expansion": ">=5.0.7",
101
102
  "eslint-plugin-react-hooks": "^7.0.0",
102
103
  "fast-xml-parser": "^5.10.1",
103
104
  "tar": ">=7.5.11",
@@ -105,6 +106,7 @@
105
106
  },
106
107
  "overrides": {
107
108
  "@isaacs/brace-expansion": "^5.0.1",
109
+ "brace-expansion": ">=5.0.7",
108
110
  "eslint-plugin-react-hooks": "^7.0.0",
109
111
  "fast-xml-parser": "^5.10.1",
110
112
  "zod-validation-error": "^4.0.0",
package/package.json CHANGED
@@ -113,7 +113,7 @@
113
113
  "brace-expansion": ">=5.0.6"
114
114
  },
115
115
  "name": "@codyswann/lisa",
116
- "version": "2.295.1",
116
+ "version": "2.295.3",
117
117
  "description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
118
118
  "main": "dist/index.js",
119
119
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "Universal governance: agents, skills, commands, hooks, and rules for all projects.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -28,6 +28,7 @@ class Marker:
28
28
  end: int
29
29
  delimiter: str
30
30
  strip_tabs: bool
31
+ quoted: bool
31
32
 
32
33
 
33
34
  def shell_tokens(prefix: str) -> list[str] | None:
@@ -357,7 +358,7 @@ def parse_marker(line: str, start: int, offset: int) -> Marker | None:
357
358
  final = index + len(delimiter)
358
359
  if not delimiter:
359
360
  return None
360
- return Marker(offset + start, offset + final, delimiter, strip_tabs)
361
+ return Marker(offset + start, offset + final, delimiter, strip_tabs, quote is not None)
361
362
 
362
363
 
363
364
  def marker_is_closed(command: str, marker: Marker) -> bool:
@@ -370,6 +371,19 @@ def marker_is_closed(command: str, marker: Marker) -> bool:
370
371
  return False
371
372
 
372
373
 
374
+ def single_closed_quoted_nonwriter_heredoc(command: str, marker: Marker) -> bool:
375
+ """Allow guard scanning for inert quoted heredoc payloads outside gh writers."""
376
+ if not marker.quoted or not marker_is_closed(command, marker):
377
+ return False
378
+ line_end = command.find("\n", marker.start)
379
+ if line_end < 0:
380
+ line_end = len(command)
381
+ header = command[:line_end]
382
+ if has_active_command_substitution(header):
383
+ return False
384
+ return True
385
+
386
+
373
387
  def main() -> int:
374
388
  command = sys.stdin.read()
375
389
  if "<<" not in command:
@@ -402,6 +416,11 @@ def main() -> int:
402
416
  ):
403
417
  return MALFORMED
404
418
 
419
+ if len(markers) == 1 and single_closed_quoted_nonwriter_heredoc(
420
+ logical_command, markers[0]
421
+ ):
422
+ return UNSUPPORTED
423
+
405
424
  # Nested substitution plus a heredoc is executable shell syntax unless it
406
425
  # matched the one exact quoted `--body "$(cat ...)"` form above.
407
426
  if has_active_command_substitution(logical_command):
@@ -96,15 +96,21 @@ EOF
96
96
  # remain visible to every built-in and custom rule. Ambiguous or malformed
97
97
  # heredocs fail closed instead of guessing which text the shell would execute.
98
98
  #
99
- # block_heredoc() teaches the remediation the moment the wall is hit: heredoc
100
- # denials overwhelmingly come from `git commit -m "$(cat <<EOF …)"` attempts,
101
- # and a bare denial strands the agent with no path forward (gardener #1789).
99
+ # block_heredoc() teaches the remediation the moment the wall is hit. Commit
100
+ # heredocs need commit-message-file guidance; other heredocs should point agents
101
+ # at script/payload files instead of implying every denial was a commit attempt.
102
102
  block_heredoc() {
103
- block "$1
103
+ if printf '%s' "$command_str" | grep -Eq '(^|[^[:alnum:]_-])git[[:space:]]+([^;&|[:space:]]+[[:space:]]+)*commit([^[:alnum:]_-]|$)'; then
104
+ block "$1
104
105
  Heredoc commit invocations are blocked (the payload is executable shell).
105
106
  Fix: write the commit message to a file and run \`git commit -F <file>\`.
106
107
  Every commit must also carry a Co-authored-by trailer for a supported agent
107
108
  (Claude/Codex/OpenCode) — the commit-msg hook enforces this."
109
+ fi
110
+ block "$1
111
+ Heredoc command invocations are blocked when Lisa cannot prove the payload is
112
+ non-executable text.
113
+ Fix: write the payload to a file and execute or pass that file explicitly."
108
114
  }
109
115
 
110
116
  command_for_guards="$command_str"
@@ -28,6 +28,7 @@ class Marker:
28
28
  end: int
29
29
  delimiter: str
30
30
  strip_tabs: bool
31
+ quoted: bool
31
32
 
32
33
 
33
34
  def shell_tokens(prefix: str) -> list[str] | None:
@@ -357,7 +358,7 @@ def parse_marker(line: str, start: int, offset: int) -> Marker | None:
357
358
  final = index + len(delimiter)
358
359
  if not delimiter:
359
360
  return None
360
- return Marker(offset + start, offset + final, delimiter, strip_tabs)
361
+ return Marker(offset + start, offset + final, delimiter, strip_tabs, quote is not None)
361
362
 
362
363
 
363
364
  def marker_is_closed(command: str, marker: Marker) -> bool:
@@ -370,6 +371,19 @@ def marker_is_closed(command: str, marker: Marker) -> bool:
370
371
  return False
371
372
 
372
373
 
374
+ def single_closed_quoted_nonwriter_heredoc(command: str, marker: Marker) -> bool:
375
+ """Allow guard scanning for inert quoted heredoc payloads outside gh writers."""
376
+ if not marker.quoted or not marker_is_closed(command, marker):
377
+ return False
378
+ line_end = command.find("\n", marker.start)
379
+ if line_end < 0:
380
+ line_end = len(command)
381
+ header = command[:line_end]
382
+ if has_active_command_substitution(header):
383
+ return False
384
+ return True
385
+
386
+
373
387
  def main() -> int:
374
388
  command = sys.stdin.read()
375
389
  if "<<" not in command:
@@ -402,6 +416,11 @@ def main() -> int:
402
416
  ):
403
417
  return MALFORMED
404
418
 
419
+ if len(markers) == 1 and single_closed_quoted_nonwriter_heredoc(
420
+ logical_command, markers[0]
421
+ ):
422
+ return UNSUPPORTED
423
+
405
424
  # Nested substitution plus a heredoc is executable shell syntax unless it
406
425
  # matched the one exact quoted `--body "$(cat ...)"` form above.
407
426
  if has_active_command_substitution(logical_command):
@@ -96,15 +96,21 @@ EOF
96
96
  # remain visible to every built-in and custom rule. Ambiguous or malformed
97
97
  # heredocs fail closed instead of guessing which text the shell would execute.
98
98
  #
99
- # block_heredoc() teaches the remediation the moment the wall is hit: heredoc
100
- # denials overwhelmingly come from `git commit -m "$(cat <<EOF …)"` attempts,
101
- # and a bare denial strands the agent with no path forward (gardener #1789).
99
+ # block_heredoc() teaches the remediation the moment the wall is hit. Commit
100
+ # heredocs need commit-message-file guidance; other heredocs should point agents
101
+ # at script/payload files instead of implying every denial was a commit attempt.
102
102
  block_heredoc() {
103
- block "$1
103
+ if printf '%s' "$command_str" | grep -Eq '(^|[^[:alnum:]_-])git[[:space:]]+([^;&|[:space:]]+[[:space:]]+)*commit([^[:alnum:]_-]|$)'; then
104
+ block "$1
104
105
  Heredoc commit invocations are blocked (the payload is executable shell).
105
106
  Fix: write the commit message to a file and run \`git commit -F <file>\`.
106
107
  Every commit must also carry a Co-authored-by trailer for a supported agent
107
108
  (Claude/Codex/OpenCode) — the commit-msg hook enforces this."
109
+ fi
110
+ block "$1
111
+ Heredoc command invocations are blocked when Lisa cannot prove the payload is
112
+ non-executable text.
113
+ Fix: write the payload to a file and execute or pass that file explicitly."
108
114
  }
109
115
 
110
116
  command_for_guards="$command_str"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "AWS CDK-specific Lisa plugin.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -28,6 +28,7 @@ class Marker:
28
28
  end: int
29
29
  delimiter: str
30
30
  strip_tabs: bool
31
+ quoted: bool
31
32
 
32
33
 
33
34
  def shell_tokens(prefix: str) -> list[str] | None:
@@ -357,7 +358,7 @@ def parse_marker(line: str, start: int, offset: int) -> Marker | None:
357
358
  final = index + len(delimiter)
358
359
  if not delimiter:
359
360
  return None
360
- return Marker(offset + start, offset + final, delimiter, strip_tabs)
361
+ return Marker(offset + start, offset + final, delimiter, strip_tabs, quote is not None)
361
362
 
362
363
 
363
364
  def marker_is_closed(command: str, marker: Marker) -> bool:
@@ -370,6 +371,19 @@ def marker_is_closed(command: str, marker: Marker) -> bool:
370
371
  return False
371
372
 
372
373
 
374
+ def single_closed_quoted_nonwriter_heredoc(command: str, marker: Marker) -> bool:
375
+ """Allow guard scanning for inert quoted heredoc payloads outside gh writers."""
376
+ if not marker.quoted or not marker_is_closed(command, marker):
377
+ return False
378
+ line_end = command.find("\n", marker.start)
379
+ if line_end < 0:
380
+ line_end = len(command)
381
+ header = command[:line_end]
382
+ if has_active_command_substitution(header):
383
+ return False
384
+ return True
385
+
386
+
373
387
  def main() -> int:
374
388
  command = sys.stdin.read()
375
389
  if "<<" not in command:
@@ -402,6 +416,11 @@ def main() -> int:
402
416
  ):
403
417
  return MALFORMED
404
418
 
419
+ if len(markers) == 1 and single_closed_quoted_nonwriter_heredoc(
420
+ logical_command, markers[0]
421
+ ):
422
+ return UNSUPPORTED
423
+
405
424
  # Nested substitution plus a heredoc is executable shell syntax unless it
406
425
  # matched the one exact quoted `--body "$(cat ...)"` form above.
407
426
  if has_active_command_substitution(logical_command):
@@ -96,15 +96,21 @@ EOF
96
96
  # remain visible to every built-in and custom rule. Ambiguous or malformed
97
97
  # heredocs fail closed instead of guessing which text the shell would execute.
98
98
  #
99
- # block_heredoc() teaches the remediation the moment the wall is hit: heredoc
100
- # denials overwhelmingly come from `git commit -m "$(cat <<EOF …)"` attempts,
101
- # and a bare denial strands the agent with no path forward (gardener #1789).
99
+ # block_heredoc() teaches the remediation the moment the wall is hit. Commit
100
+ # heredocs need commit-message-file guidance; other heredocs should point agents
101
+ # at script/payload files instead of implying every denial was a commit attempt.
102
102
  block_heredoc() {
103
- block "$1
103
+ if printf '%s' "$command_str" | grep -Eq '(^|[^[:alnum:]_-])git[[:space:]]+([^;&|[:space:]]+[[:space:]]+)*commit([^[:alnum:]_-]|$)'; then
104
+ block "$1
104
105
  Heredoc commit invocations are blocked (the payload is executable shell).
105
106
  Fix: write the commit message to a file and run \`git commit -F <file>\`.
106
107
  Every commit must also carry a Co-authored-by trailer for a supported agent
107
108
  (Claude/Codex/OpenCode) — the commit-msg hook enforces this."
109
+ fi
110
+ block "$1
111
+ Heredoc command invocations are blocked when Lisa cannot prove the payload is
112
+ non-executable text.
113
+ Fix: write the payload to a file and execute or pass that file explicitly."
108
114
  }
109
115
 
110
116
  command_for_guards="$command_str"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -28,6 +28,7 @@ class Marker:
28
28
  end: int
29
29
  delimiter: str
30
30
  strip_tabs: bool
31
+ quoted: bool
31
32
 
32
33
 
33
34
  def shell_tokens(prefix: str) -> list[str] | None:
@@ -357,7 +358,7 @@ def parse_marker(line: str, start: int, offset: int) -> Marker | None:
357
358
  final = index + len(delimiter)
358
359
  if not delimiter:
359
360
  return None
360
- return Marker(offset + start, offset + final, delimiter, strip_tabs)
361
+ return Marker(offset + start, offset + final, delimiter, strip_tabs, quote is not None)
361
362
 
362
363
 
363
364
  def marker_is_closed(command: str, marker: Marker) -> bool:
@@ -370,6 +371,19 @@ def marker_is_closed(command: str, marker: Marker) -> bool:
370
371
  return False
371
372
 
372
373
 
374
+ def single_closed_quoted_nonwriter_heredoc(command: str, marker: Marker) -> bool:
375
+ """Allow guard scanning for inert quoted heredoc payloads outside gh writers."""
376
+ if not marker.quoted or not marker_is_closed(command, marker):
377
+ return False
378
+ line_end = command.find("\n", marker.start)
379
+ if line_end < 0:
380
+ line_end = len(command)
381
+ header = command[:line_end]
382
+ if has_active_command_substitution(header):
383
+ return False
384
+ return True
385
+
386
+
373
387
  def main() -> int:
374
388
  command = sys.stdin.read()
375
389
  if "<<" not in command:
@@ -402,6 +416,11 @@ def main() -> int:
402
416
  ):
403
417
  return MALFORMED
404
418
 
419
+ if len(markers) == 1 and single_closed_quoted_nonwriter_heredoc(
420
+ logical_command, markers[0]
421
+ ):
422
+ return UNSUPPORTED
423
+
405
424
  # Nested substitution plus a heredoc is executable shell syntax unless it
406
425
  # matched the one exact quoted `--body "$(cat ...)"` form above.
407
426
  if has_active_command_substitution(logical_command):
@@ -96,15 +96,21 @@ EOF
96
96
  # remain visible to every built-in and custom rule. Ambiguous or malformed
97
97
  # heredocs fail closed instead of guessing which text the shell would execute.
98
98
  #
99
- # block_heredoc() teaches the remediation the moment the wall is hit: heredoc
100
- # denials overwhelmingly come from `git commit -m "$(cat <<EOF …)"` attempts,
101
- # and a bare denial strands the agent with no path forward (gardener #1789).
99
+ # block_heredoc() teaches the remediation the moment the wall is hit. Commit
100
+ # heredocs need commit-message-file guidance; other heredocs should point agents
101
+ # at script/payload files instead of implying every denial was a commit attempt.
102
102
  block_heredoc() {
103
- block "$1
103
+ if printf '%s' "$command_str" | grep -Eq '(^|[^[:alnum:]_-])git[[:space:]]+([^;&|[:space:]]+[[:space:]]+)*commit([^[:alnum:]_-]|$)'; then
104
+ block "$1
104
105
  Heredoc commit invocations are blocked (the payload is executable shell).
105
106
  Fix: write the commit message to a file and run \`git commit -F <file>\`.
106
107
  Every commit must also carry a Co-authored-by trailer for a supported agent
107
108
  (Claude/Codex/OpenCode) — the commit-msg hook enforces this."
109
+ fi
110
+ block "$1
111
+ Heredoc command invocations are blocked when Lisa cannot prove the payload is
112
+ non-executable text.
113
+ Fix: write the payload to a file and execute or pass that file explicitly."
108
114
  }
109
115
 
110
116
  command_for_guards="$command_str"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "Expo and React Native-specific skills, agents, rules, and MCP servers.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "Harper/Fabric-specific Lisa rules for TypeScript component apps.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "NestJS-specific skills and migration write-protection hooks.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, across Claude and Codex.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "Ruby on Rails-specific skills and hooks for RuboCop and ast-grep scanning on edit.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "TypeScript-specific hooks for formatting, linting, and ast-grep scanning on edit.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "Distributable LLM Wiki kernel — ingest, query, lint, and maintain a git-native markdown knowledge base across Claude and Codex.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "2.295.1",
3
+ "version": "2.295.3",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -28,6 +28,7 @@ class Marker:
28
28
  end: int
29
29
  delimiter: str
30
30
  strip_tabs: bool
31
+ quoted: bool
31
32
 
32
33
 
33
34
  def shell_tokens(prefix: str) -> list[str] | None:
@@ -357,7 +358,7 @@ def parse_marker(line: str, start: int, offset: int) -> Marker | None:
357
358
  final = index + len(delimiter)
358
359
  if not delimiter:
359
360
  return None
360
- return Marker(offset + start, offset + final, delimiter, strip_tabs)
361
+ return Marker(offset + start, offset + final, delimiter, strip_tabs, quote is not None)
361
362
 
362
363
 
363
364
  def marker_is_closed(command: str, marker: Marker) -> bool:
@@ -370,6 +371,19 @@ def marker_is_closed(command: str, marker: Marker) -> bool:
370
371
  return False
371
372
 
372
373
 
374
+ def single_closed_quoted_nonwriter_heredoc(command: str, marker: Marker) -> bool:
375
+ """Allow guard scanning for inert quoted heredoc payloads outside gh writers."""
376
+ if not marker.quoted or not marker_is_closed(command, marker):
377
+ return False
378
+ line_end = command.find("\n", marker.start)
379
+ if line_end < 0:
380
+ line_end = len(command)
381
+ header = command[:line_end]
382
+ if has_active_command_substitution(header):
383
+ return False
384
+ return True
385
+
386
+
373
387
  def main() -> int:
374
388
  command = sys.stdin.read()
375
389
  if "<<" not in command:
@@ -402,6 +416,11 @@ def main() -> int:
402
416
  ):
403
417
  return MALFORMED
404
418
 
419
+ if len(markers) == 1 and single_closed_quoted_nonwriter_heredoc(
420
+ logical_command, markers[0]
421
+ ):
422
+ return UNSUPPORTED
423
+
405
424
  # Nested substitution plus a heredoc is executable shell syntax unless it
406
425
  # matched the one exact quoted `--body "$(cat ...)"` form above.
407
426
  if has_active_command_substitution(logical_command):
@@ -96,15 +96,21 @@ EOF
96
96
  # remain visible to every built-in and custom rule. Ambiguous or malformed
97
97
  # heredocs fail closed instead of guessing which text the shell would execute.
98
98
  #
99
- # block_heredoc() teaches the remediation the moment the wall is hit: heredoc
100
- # denials overwhelmingly come from `git commit -m "$(cat <<EOF …)"` attempts,
101
- # and a bare denial strands the agent with no path forward (gardener #1789).
99
+ # block_heredoc() teaches the remediation the moment the wall is hit. Commit
100
+ # heredocs need commit-message-file guidance; other heredocs should point agents
101
+ # at script/payload files instead of implying every denial was a commit attempt.
102
102
  block_heredoc() {
103
- block "$1
103
+ if printf '%s' "$command_str" | grep -Eq '(^|[^[:alnum:]_-])git[[:space:]]+([^;&|[:space:]]+[[:space:]]+)*commit([^[:alnum:]_-]|$)'; then
104
+ block "$1
104
105
  Heredoc commit invocations are blocked (the payload is executable shell).
105
106
  Fix: write the commit message to a file and run \`git commit -F <file>\`.
106
107
  Every commit must also carry a Co-authored-by trailer for a supported agent
107
108
  (Claude/Codex/OpenCode) — the commit-msg hook enforces this."
109
+ fi
110
+ block "$1
111
+ Heredoc command invocations are blocked when Lisa cannot prove the payload is
112
+ non-executable text.
113
+ Fix: write the payload to a file and execute or pass that file explicitly."
108
114
  }
109
115
 
110
116
  command_for_guards="$command_str"
@@ -27,6 +27,7 @@
27
27
  },
28
28
  "resolutions": {
29
29
  "@isaacs/brace-expansion": "^5.0.1",
30
+ "brace-expansion": ">=5.0.7",
30
31
  "axios": ">=1.15.2",
31
32
  "esbuild": ">=0.28.1",
32
33
  "handlebars": ">=4.7.9",
@@ -40,6 +41,7 @@
40
41
  },
41
42
  "overrides": {
42
43
  "@isaacs/brace-expansion": "^5.0.1",
44
+ "brace-expansion": ">=5.0.7",
43
45
  "axios": ">=1.15.2",
44
46
  "esbuild": ">=0.28.1",
45
47
  "handlebars": ">=4.7.9",