@codyswann/lisa 2.295.2 → 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.
- package/dist/core/upstream-evidence-manifest.js +2 -2
- package/dist/opencode/plugin-templates/parity-safety-net-heredoc.py +20 -1
- package/dist/opencode/plugin-templates/parity-safety-net.sh +10 -4
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/hooks/parity-safety-net-heredoc.py +20 -1
- package/plugins/lisa/hooks/parity-safety-net.sh +10 -4
- package/plugins/lisa-agy/hooks/parity-safety-net-heredoc.py +20 -1
- package/plugins/lisa-agy/hooks/parity-safety-net.sh +10 -4
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/hooks/parity-safety-net-heredoc.py +20 -1
- package/plugins/lisa-copilot/hooks/parity-safety-net.sh +10 -4
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/parity-safety-net-heredoc.py +20 -1
- package/plugins/lisa-cursor/hooks/parity-safety-net.sh +10 -4
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/hooks/parity-safety-net-heredoc.py +20 -1
- package/plugins/src/base/hooks/parity-safety-net.sh +10 -4
|
@@ -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": "
|
|
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": "
|
|
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",
|
|
@@ -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
|
|
100
|
-
#
|
|
101
|
-
#
|
|
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
|
-
|
|
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"
|
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.
|
|
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": {
|
|
@@ -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
|
|
100
|
-
#
|
|
101
|
-
#
|
|
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
|
-
|
|
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
|
|
100
|
-
#
|
|
101
|
-
#
|
|
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
|
-
|
|
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
|
|
100
|
-
#
|
|
101
|
-
#
|
|
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
|
-
|
|
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
|
|
100
|
-
#
|
|
101
|
-
#
|
|
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
|
-
|
|
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-openclaw",
|
|
3
|
-
"version": "2.295.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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"
|
|
@@ -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
|
|
100
|
-
#
|
|
101
|
-
#
|
|
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
|
-
|
|
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"
|