@codyswann/lisa 4.40.0 → 4.41.0
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/all/copy-overwrite/scripts/lisa-cross-worktree-guard.mjs +57 -0
- package/all/copy-overwrite/scripts/lisa-hooks/block-direct-issue-create.sh +17 -0
- package/all/copy-overwrite/scripts/lisa-hooks/block-managed-file-edits.sh +14 -0
- package/all/copy-overwrite/scripts/lisa-hooks/block-no-verify.sh +28 -0
- package/dist/codex/scripts/block-no-verify.sh +21 -0
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +20 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +13 -11
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/opencode/plugin-templates/block-managed-file-edits.sh +14 -0
- package/dist/opencode/plugin-templates/block-no-verify.sh +28 -0
- package/package.json +4 -4
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/hooks/block-direct-issue-create.sh +17 -0
- package/plugins/lisa/hooks/block-managed-file-edits.sh +14 -0
- package/plugins/lisa/hooks/block-no-verify.agy.sh +21 -0
- package/plugins/lisa/hooks/block-no-verify.sh +28 -0
- package/plugins/lisa-agy/hooks/block-direct-issue-create.sh +17 -0
- package/plugins/lisa-agy/hooks/block-managed-file-edits.sh +14 -0
- package/plugins/lisa-agy/hooks/block-no-verify.agy.sh +21 -0
- 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/block-direct-issue-create.sh +17 -0
- package/plugins/lisa-copilot/hooks/block-managed-file-edits.sh +14 -0
- package/plugins/lisa-copilot/hooks/block-no-verify.sh +28 -0
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/block-direct-issue-create.sh +17 -0
- package/plugins/lisa-cursor/hooks/block-managed-file-edits.sh +14 -0
- package/plugins/lisa-cursor/hooks/block-no-verify.sh +28 -0
- 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/block-direct-issue-create.sh +17 -0
- package/plugins/src/base/hooks/block-managed-file-edits.sh +14 -0
- package/plugins/src/base/hooks/block-no-verify.agy.sh +21 -0
- package/plugins/src/base/hooks/block-no-verify.sh +28 -0
- package/rails/copy-contents/scripts/lisa-mutation.sh +8 -1
- package/typescript/copy-overwrite/scripts/check-skipped-required-checks.mjs +101 -1
- package/typescript/copy-overwrite/scripts/lisa-mutation.mjs +20 -14
|
@@ -719,6 +719,20 @@ def shell_script_operand(args):
|
|
|
719
719
|
# not a script file.
|
|
720
720
|
if not token.startswith("--") and "c" in token[1:]:
|
|
721
721
|
return None
|
|
722
|
+
# `-n` is noexec: the shell READS and parses the file, then exits
|
|
723
|
+
# without executing a line of it. `bash -n <file>` is the one shape
|
|
724
|
+
# that puts a path at a command position while provably running
|
|
725
|
+
# nothing — the exact seam between reading and running, and this
|
|
726
|
+
# walk got it wrong (CodySwannGT/lisa#3781). Here that meant a
|
|
727
|
+
# syntax check of any script mentioning a managed path was refused
|
|
728
|
+
# as though it were about to write one.
|
|
729
|
+
#
|
|
730
|
+
# `bash <file>` still executes and is still refused. Noexec is the
|
|
731
|
+
# distinction, not the program.
|
|
732
|
+
if token == "--noexec" or (
|
|
733
|
+
not token.startswith("--") and "n" in token[1:]
|
|
734
|
+
):
|
|
735
|
+
return None
|
|
722
736
|
continue
|
|
723
737
|
return token
|
|
724
738
|
return None
|
|
@@ -24,6 +24,13 @@
|
|
|
24
24
|
# `source`, `.`, `bash < nv.sh`, and an interpreter a `find -exec` or
|
|
25
25
|
# `xargs` puts at a command position.
|
|
26
26
|
#
|
|
27
|
+
# NOT vector 11: `bash -n nv.sh`. `-n` is noexec — the shell reads and parses
|
|
28
|
+
# the file, then exits without running a line — so a syntax check is the one
|
|
29
|
+
# shape that puts a path at a command position while executing nothing. It used
|
|
30
|
+
# to be refused, which made `bash -n` unusable on this file, on every husky
|
|
31
|
+
# hook, and on most of this repository (#3781). The distinction is noexec, not
|
|
32
|
+
# the program: `bash nv.sh` still executes and is still refused.
|
|
33
|
+
#
|
|
27
34
|
# The line below is what lets `lisa apply` tell a downstream copy of this guard
|
|
28
35
|
# that is BEHIND from one that is AHEAD. Byte comparison cannot: both look like
|
|
29
36
|
# "differs from mine", and guessing "behind" is how a fork's stronger guard gets
|
|
@@ -1031,6 +1038,10 @@ def resolve_script(token):
|
|
|
1031
1038
|
|
|
1032
1039
|
SHELL_COMMAND_STRING = object()
|
|
1033
1040
|
SHELL_STDIN = object()
|
|
1041
|
+
# `-n` / `--noexec`: the shell READS and parses its operand, then exits without
|
|
1042
|
+
# executing a line of it. Distinct from the two above because nothing runs at
|
|
1043
|
+
# all — there is no payload to scan and no stdin script to wait for.
|
|
1044
|
+
SHELL_NOEXEC = object()
|
|
1034
1045
|
|
|
1035
1046
|
|
|
1036
1047
|
def shell_execution_operand(args):
|
|
@@ -1061,12 +1072,25 @@ def shell_execution_operand(args):
|
|
|
1061
1072
|
index += 1
|
|
1062
1073
|
continue
|
|
1063
1074
|
if token.startswith("--"):
|
|
1075
|
+
if token == "--noexec":
|
|
1076
|
+
return SHELL_NOEXEC
|
|
1064
1077
|
if token not in SHELL_LONG_OPTIONS_NO_VALUE:
|
|
1065
1078
|
opaque_option_seen = True
|
|
1066
1079
|
index += 1
|
|
1067
1080
|
continue
|
|
1068
1081
|
if "c" in token[1:]:
|
|
1069
1082
|
return SHELL_COMMAND_STRING
|
|
1083
|
+
# `-n` is noexec. `bash -n <file>` is the one shape that puts a path at
|
|
1084
|
+
# a command position while provably executing nothing, so it is the
|
|
1085
|
+
# exact seam between reading and running — and reach got it wrong in
|
|
1086
|
+
# BOTH directions (CodySwannGT/lisa#3781). Here it meant a syntax check
|
|
1087
|
+
# of any file merely DISCUSSING the bypass was refused, which is most of
|
|
1088
|
+
# this repository's own guards and every husky hook.
|
|
1089
|
+
#
|
|
1090
|
+
# `bash <file>` still executes and is still refused. Noexec is the
|
|
1091
|
+
# distinction, not the program: this is not a read-only exemption.
|
|
1092
|
+
if "n" in token[1:]:
|
|
1093
|
+
return SHELL_NOEXEC
|
|
1070
1094
|
index += 1
|
|
1071
1095
|
return args[index] if index < len(args) else SHELL_STDIN
|
|
1072
1096
|
|
|
@@ -1111,6 +1135,10 @@ def executed_scripts(tokens):
|
|
|
1111
1135
|
continue
|
|
1112
1136
|
if operand is SHELL_COMMAND_STRING:
|
|
1113
1137
|
continue
|
|
1138
|
+
if operand is SHELL_NOEXEC:
|
|
1139
|
+
# Nothing runs, so there is nothing to follow and nothing
|
|
1140
|
+
# to fail closed about.
|
|
1141
|
+
continue
|
|
1114
1142
|
else:
|
|
1115
1143
|
continue
|
|
1116
1144
|
if operand is None:
|
package/package.json
CHANGED
|
@@ -171,7 +171,7 @@
|
|
|
171
171
|
"zod-validation-error": "^4.0.0"
|
|
172
172
|
},
|
|
173
173
|
"name": "@codyswann/lisa",
|
|
174
|
-
"version": "4.
|
|
174
|
+
"version": "4.41.0",
|
|
175
175
|
"description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
|
|
176
176
|
"main": "dist/index.js",
|
|
177
177
|
"exports": {
|
|
@@ -319,7 +319,7 @@
|
|
|
319
319
|
"test": "tests"
|
|
320
320
|
},
|
|
321
321
|
"types": "./dist/index.d.ts",
|
|
322
|
-
"lisaReleaseCommit": "
|
|
323
|
-
"gitHead": "
|
|
324
|
-
"lisaReleaseTag": "v4.
|
|
322
|
+
"lisaReleaseCommit": "620ec616954b878092b32a9a356cc20d6972a334",
|
|
323
|
+
"gitHead": "620ec616954b878092b32a9a356cc20d6972a334",
|
|
324
|
+
"lisaReleaseTag": "v4.41.0"
|
|
325
325
|
}
|
|
@@ -1708,6 +1708,23 @@ def executed_operand(argv):
|
|
|
1708
1708
|
# ordinary shell options and do not consume the script path.
|
|
1709
1709
|
if not head.startswith("--") and "c" in head[1:]:
|
|
1710
1710
|
return None
|
|
1711
|
+
# `-n` is noexec: the shell READS and parses the file and then
|
|
1712
|
+
# exits without running a line of it. `bash -n <file>` is the
|
|
1713
|
+
# one shape that puts a path at a command position while
|
|
1714
|
+
# provably executing nothing, so it is the exact seam between
|
|
1715
|
+
# reading and running — and this walk got it wrong in BOTH
|
|
1716
|
+
# directions (CodySwannGT/lisa#3781). A creation-shaped file
|
|
1717
|
+
# earned a false refusal; a file carrying a human-gate marker
|
|
1718
|
+
# earned a false ALLOW, adjudicating as "declared" a command
|
|
1719
|
+
# that filed nothing. The silent direction is the worse one.
|
|
1720
|
+
#
|
|
1721
|
+
# Not a read-only-command exemption: `bash <file>` still runs
|
|
1722
|
+
# the file and is still refused. The distinction is noexec, not
|
|
1723
|
+
# the program.
|
|
1724
|
+
if head == "--noexec" or (
|
|
1725
|
+
not head.startswith("--") and "n" in head[1:]
|
|
1726
|
+
):
|
|
1727
|
+
return None
|
|
1711
1728
|
elif head in NO_SCRIPT_OPTIONS.get(program, set()):
|
|
1712
1729
|
return None
|
|
1713
1730
|
takes_value = head in INTERPRETER_VALUE_OPTIONS.get(program, set())
|
|
@@ -719,6 +719,20 @@ def shell_script_operand(args):
|
|
|
719
719
|
# not a script file.
|
|
720
720
|
if not token.startswith("--") and "c" in token[1:]:
|
|
721
721
|
return None
|
|
722
|
+
# `-n` is noexec: the shell READS and parses the file, then exits
|
|
723
|
+
# without executing a line of it. `bash -n <file>` is the one shape
|
|
724
|
+
# that puts a path at a command position while provably running
|
|
725
|
+
# nothing — the exact seam between reading and running, and this
|
|
726
|
+
# walk got it wrong (CodySwannGT/lisa#3781). Here that meant a
|
|
727
|
+
# syntax check of any script mentioning a managed path was refused
|
|
728
|
+
# as though it were about to write one.
|
|
729
|
+
#
|
|
730
|
+
# `bash <file>` still executes and is still refused. Noexec is the
|
|
731
|
+
# distinction, not the program.
|
|
732
|
+
if token == "--noexec" or (
|
|
733
|
+
not token.startswith("--") and "n" in token[1:]
|
|
734
|
+
):
|
|
735
|
+
return None
|
|
722
736
|
continue
|
|
723
737
|
return token
|
|
724
738
|
return None
|
|
@@ -961,6 +961,10 @@ def resolve_script(token):
|
|
|
961
961
|
|
|
962
962
|
SHELL_COMMAND_STRING = object()
|
|
963
963
|
SHELL_STDIN = object()
|
|
964
|
+
# `-n` / `--noexec`: the shell READS and parses its operand, then exits without
|
|
965
|
+
# executing a line of it. Distinct from the two above because nothing runs at
|
|
966
|
+
# all — there is no payload to scan and no stdin script to wait for.
|
|
967
|
+
SHELL_NOEXEC = object()
|
|
964
968
|
|
|
965
969
|
|
|
966
970
|
def shell_execution_operand(args):
|
|
@@ -991,12 +995,25 @@ def shell_execution_operand(args):
|
|
|
991
995
|
index += 1
|
|
992
996
|
continue
|
|
993
997
|
if token.startswith("--"):
|
|
998
|
+
if token == "--noexec":
|
|
999
|
+
return SHELL_NOEXEC
|
|
994
1000
|
if token not in SHELL_LONG_OPTIONS_NO_VALUE:
|
|
995
1001
|
opaque_option_seen = True
|
|
996
1002
|
index += 1
|
|
997
1003
|
continue
|
|
998
1004
|
if "c" in token[1:]:
|
|
999
1005
|
return SHELL_COMMAND_STRING
|
|
1006
|
+
# `-n` is noexec. `bash -n <file>` is the one shape that puts a path at
|
|
1007
|
+
# a command position while provably executing nothing, so it is the
|
|
1008
|
+
# exact seam between reading and running — and reach got it wrong in
|
|
1009
|
+
# BOTH directions (CodySwannGT/lisa#3781). Here it meant a syntax check
|
|
1010
|
+
# of any file merely DISCUSSING the bypass was refused, which is most of
|
|
1011
|
+
# this repository's own guards and every husky hook.
|
|
1012
|
+
#
|
|
1013
|
+
# `bash <file>` still executes and is still refused. Noexec is the
|
|
1014
|
+
# distinction, not the program: this is not a read-only exemption.
|
|
1015
|
+
if "n" in token[1:]:
|
|
1016
|
+
return SHELL_NOEXEC
|
|
1000
1017
|
index += 1
|
|
1001
1018
|
return args[index] if index < len(args) else SHELL_STDIN
|
|
1002
1019
|
|
|
@@ -1041,6 +1058,10 @@ def executed_scripts(tokens):
|
|
|
1041
1058
|
continue
|
|
1042
1059
|
if operand is SHELL_COMMAND_STRING:
|
|
1043
1060
|
continue
|
|
1061
|
+
if operand is SHELL_NOEXEC:
|
|
1062
|
+
# Nothing runs, so there is nothing to follow and nothing
|
|
1063
|
+
# to fail closed about.
|
|
1064
|
+
continue
|
|
1044
1065
|
else:
|
|
1045
1066
|
continue
|
|
1046
1067
|
if operand is None:
|
|
@@ -24,6 +24,13 @@
|
|
|
24
24
|
# `source`, `.`, `bash < nv.sh`, and an interpreter a `find -exec` or
|
|
25
25
|
# `xargs` puts at a command position.
|
|
26
26
|
#
|
|
27
|
+
# NOT vector 11: `bash -n nv.sh`. `-n` is noexec — the shell reads and parses
|
|
28
|
+
# the file, then exits without running a line — so a syntax check is the one
|
|
29
|
+
# shape that puts a path at a command position while executing nothing. It used
|
|
30
|
+
# to be refused, which made `bash -n` unusable on this file, on every husky
|
|
31
|
+
# hook, and on most of this repository (#3781). The distinction is noexec, not
|
|
32
|
+
# the program: `bash nv.sh` still executes and is still refused.
|
|
33
|
+
#
|
|
27
34
|
# The line below is what lets `lisa apply` tell a downstream copy of this guard
|
|
28
35
|
# that is BEHIND from one that is AHEAD. Byte comparison cannot: both look like
|
|
29
36
|
# "differs from mine", and guessing "behind" is how a fork's stronger guard gets
|
|
@@ -1031,6 +1038,10 @@ def resolve_script(token):
|
|
|
1031
1038
|
|
|
1032
1039
|
SHELL_COMMAND_STRING = object()
|
|
1033
1040
|
SHELL_STDIN = object()
|
|
1041
|
+
# `-n` / `--noexec`: the shell READS and parses its operand, then exits without
|
|
1042
|
+
# executing a line of it. Distinct from the two above because nothing runs at
|
|
1043
|
+
# all — there is no payload to scan and no stdin script to wait for.
|
|
1044
|
+
SHELL_NOEXEC = object()
|
|
1034
1045
|
|
|
1035
1046
|
|
|
1036
1047
|
def shell_execution_operand(args):
|
|
@@ -1061,12 +1072,25 @@ def shell_execution_operand(args):
|
|
|
1061
1072
|
index += 1
|
|
1062
1073
|
continue
|
|
1063
1074
|
if token.startswith("--"):
|
|
1075
|
+
if token == "--noexec":
|
|
1076
|
+
return SHELL_NOEXEC
|
|
1064
1077
|
if token not in SHELL_LONG_OPTIONS_NO_VALUE:
|
|
1065
1078
|
opaque_option_seen = True
|
|
1066
1079
|
index += 1
|
|
1067
1080
|
continue
|
|
1068
1081
|
if "c" in token[1:]:
|
|
1069
1082
|
return SHELL_COMMAND_STRING
|
|
1083
|
+
# `-n` is noexec. `bash -n <file>` is the one shape that puts a path at
|
|
1084
|
+
# a command position while provably executing nothing, so it is the
|
|
1085
|
+
# exact seam between reading and running — and reach got it wrong in
|
|
1086
|
+
# BOTH directions (CodySwannGT/lisa#3781). Here it meant a syntax check
|
|
1087
|
+
# of any file merely DISCUSSING the bypass was refused, which is most of
|
|
1088
|
+
# this repository's own guards and every husky hook.
|
|
1089
|
+
#
|
|
1090
|
+
# `bash <file>` still executes and is still refused. Noexec is the
|
|
1091
|
+
# distinction, not the program: this is not a read-only exemption.
|
|
1092
|
+
if "n" in token[1:]:
|
|
1093
|
+
return SHELL_NOEXEC
|
|
1070
1094
|
index += 1
|
|
1071
1095
|
return args[index] if index < len(args) else SHELL_STDIN
|
|
1072
1096
|
|
|
@@ -1111,6 +1135,10 @@ def executed_scripts(tokens):
|
|
|
1111
1135
|
continue
|
|
1112
1136
|
if operand is SHELL_COMMAND_STRING:
|
|
1113
1137
|
continue
|
|
1138
|
+
if operand is SHELL_NOEXEC:
|
|
1139
|
+
# Nothing runs, so there is nothing to follow and nothing
|
|
1140
|
+
# to fail closed about.
|
|
1141
|
+
continue
|
|
1114
1142
|
else:
|
|
1115
1143
|
continue
|
|
1116
1144
|
if operand is None:
|
|
@@ -1708,6 +1708,23 @@ def executed_operand(argv):
|
|
|
1708
1708
|
# ordinary shell options and do not consume the script path.
|
|
1709
1709
|
if not head.startswith("--") and "c" in head[1:]:
|
|
1710
1710
|
return None
|
|
1711
|
+
# `-n` is noexec: the shell READS and parses the file and then
|
|
1712
|
+
# exits without running a line of it. `bash -n <file>` is the
|
|
1713
|
+
# one shape that puts a path at a command position while
|
|
1714
|
+
# provably executing nothing, so it is the exact seam between
|
|
1715
|
+
# reading and running — and this walk got it wrong in BOTH
|
|
1716
|
+
# directions (CodySwannGT/lisa#3781). A creation-shaped file
|
|
1717
|
+
# earned a false refusal; a file carrying a human-gate marker
|
|
1718
|
+
# earned a false ALLOW, adjudicating as "declared" a command
|
|
1719
|
+
# that filed nothing. The silent direction is the worse one.
|
|
1720
|
+
#
|
|
1721
|
+
# Not a read-only-command exemption: `bash <file>` still runs
|
|
1722
|
+
# the file and is still refused. The distinction is noexec, not
|
|
1723
|
+
# the program.
|
|
1724
|
+
if head == "--noexec" or (
|
|
1725
|
+
not head.startswith("--") and "n" in head[1:]
|
|
1726
|
+
):
|
|
1727
|
+
return None
|
|
1711
1728
|
elif head in NO_SCRIPT_OPTIONS.get(program, set()):
|
|
1712
1729
|
return None
|
|
1713
1730
|
takes_value = head in INTERPRETER_VALUE_OPTIONS.get(program, set())
|
|
@@ -719,6 +719,20 @@ def shell_script_operand(args):
|
|
|
719
719
|
# not a script file.
|
|
720
720
|
if not token.startswith("--") and "c" in token[1:]:
|
|
721
721
|
return None
|
|
722
|
+
# `-n` is noexec: the shell READS and parses the file, then exits
|
|
723
|
+
# without executing a line of it. `bash -n <file>` is the one shape
|
|
724
|
+
# that puts a path at a command position while provably running
|
|
725
|
+
# nothing — the exact seam between reading and running, and this
|
|
726
|
+
# walk got it wrong (CodySwannGT/lisa#3781). Here that meant a
|
|
727
|
+
# syntax check of any script mentioning a managed path was refused
|
|
728
|
+
# as though it were about to write one.
|
|
729
|
+
#
|
|
730
|
+
# `bash <file>` still executes and is still refused. Noexec is the
|
|
731
|
+
# distinction, not the program.
|
|
732
|
+
if token == "--noexec" or (
|
|
733
|
+
not token.startswith("--") and "n" in token[1:]
|
|
734
|
+
):
|
|
735
|
+
return None
|
|
722
736
|
continue
|
|
723
737
|
return token
|
|
724
738
|
return None
|
|
@@ -961,6 +961,10 @@ def resolve_script(token):
|
|
|
961
961
|
|
|
962
962
|
SHELL_COMMAND_STRING = object()
|
|
963
963
|
SHELL_STDIN = object()
|
|
964
|
+
# `-n` / `--noexec`: the shell READS and parses its operand, then exits without
|
|
965
|
+
# executing a line of it. Distinct from the two above because nothing runs at
|
|
966
|
+
# all — there is no payload to scan and no stdin script to wait for.
|
|
967
|
+
SHELL_NOEXEC = object()
|
|
964
968
|
|
|
965
969
|
|
|
966
970
|
def shell_execution_operand(args):
|
|
@@ -991,12 +995,25 @@ def shell_execution_operand(args):
|
|
|
991
995
|
index += 1
|
|
992
996
|
continue
|
|
993
997
|
if token.startswith("--"):
|
|
998
|
+
if token == "--noexec":
|
|
999
|
+
return SHELL_NOEXEC
|
|
994
1000
|
if token not in SHELL_LONG_OPTIONS_NO_VALUE:
|
|
995
1001
|
opaque_option_seen = True
|
|
996
1002
|
index += 1
|
|
997
1003
|
continue
|
|
998
1004
|
if "c" in token[1:]:
|
|
999
1005
|
return SHELL_COMMAND_STRING
|
|
1006
|
+
# `-n` is noexec. `bash -n <file>` is the one shape that puts a path at
|
|
1007
|
+
# a command position while provably executing nothing, so it is the
|
|
1008
|
+
# exact seam between reading and running — and reach got it wrong in
|
|
1009
|
+
# BOTH directions (CodySwannGT/lisa#3781). Here it meant a syntax check
|
|
1010
|
+
# of any file merely DISCUSSING the bypass was refused, which is most of
|
|
1011
|
+
# this repository's own guards and every husky hook.
|
|
1012
|
+
#
|
|
1013
|
+
# `bash <file>` still executes and is still refused. Noexec is the
|
|
1014
|
+
# distinction, not the program: this is not a read-only exemption.
|
|
1015
|
+
if "n" in token[1:]:
|
|
1016
|
+
return SHELL_NOEXEC
|
|
1000
1017
|
index += 1
|
|
1001
1018
|
return args[index] if index < len(args) else SHELL_STDIN
|
|
1002
1019
|
|
|
@@ -1041,6 +1058,10 @@ def executed_scripts(tokens):
|
|
|
1041
1058
|
continue
|
|
1042
1059
|
if operand is SHELL_COMMAND_STRING:
|
|
1043
1060
|
continue
|
|
1061
|
+
if operand is SHELL_NOEXEC:
|
|
1062
|
+
# Nothing runs, so there is nothing to follow and nothing
|
|
1063
|
+
# to fail closed about.
|
|
1064
|
+
continue
|
|
1044
1065
|
else:
|
|
1045
1066
|
continue
|
|
1046
1067
|
if operand is None:
|
|
@@ -1708,6 +1708,23 @@ def executed_operand(argv):
|
|
|
1708
1708
|
# ordinary shell options and do not consume the script path.
|
|
1709
1709
|
if not head.startswith("--") and "c" in head[1:]:
|
|
1710
1710
|
return None
|
|
1711
|
+
# `-n` is noexec: the shell READS and parses the file and then
|
|
1712
|
+
# exits without running a line of it. `bash -n <file>` is the
|
|
1713
|
+
# one shape that puts a path at a command position while
|
|
1714
|
+
# provably executing nothing, so it is the exact seam between
|
|
1715
|
+
# reading and running — and this walk got it wrong in BOTH
|
|
1716
|
+
# directions (CodySwannGT/lisa#3781). A creation-shaped file
|
|
1717
|
+
# earned a false refusal; a file carrying a human-gate marker
|
|
1718
|
+
# earned a false ALLOW, adjudicating as "declared" a command
|
|
1719
|
+
# that filed nothing. The silent direction is the worse one.
|
|
1720
|
+
#
|
|
1721
|
+
# Not a read-only-command exemption: `bash <file>` still runs
|
|
1722
|
+
# the file and is still refused. The distinction is noexec, not
|
|
1723
|
+
# the program.
|
|
1724
|
+
if head == "--noexec" or (
|
|
1725
|
+
not head.startswith("--") and "n" in head[1:]
|
|
1726
|
+
):
|
|
1727
|
+
return None
|
|
1711
1728
|
elif head in NO_SCRIPT_OPTIONS.get(program, set()):
|
|
1712
1729
|
return None
|
|
1713
1730
|
takes_value = head in INTERPRETER_VALUE_OPTIONS.get(program, set())
|
|
@@ -719,6 +719,20 @@ def shell_script_operand(args):
|
|
|
719
719
|
# not a script file.
|
|
720
720
|
if not token.startswith("--") and "c" in token[1:]:
|
|
721
721
|
return None
|
|
722
|
+
# `-n` is noexec: the shell READS and parses the file, then exits
|
|
723
|
+
# without executing a line of it. `bash -n <file>` is the one shape
|
|
724
|
+
# that puts a path at a command position while provably running
|
|
725
|
+
# nothing — the exact seam between reading and running, and this
|
|
726
|
+
# walk got it wrong (CodySwannGT/lisa#3781). Here that meant a
|
|
727
|
+
# syntax check of any script mentioning a managed path was refused
|
|
728
|
+
# as though it were about to write one.
|
|
729
|
+
#
|
|
730
|
+
# `bash <file>` still executes and is still refused. Noexec is the
|
|
731
|
+
# distinction, not the program.
|
|
732
|
+
if token == "--noexec" or (
|
|
733
|
+
not token.startswith("--") and "n" in token[1:]
|
|
734
|
+
):
|
|
735
|
+
return None
|
|
722
736
|
continue
|
|
723
737
|
return token
|
|
724
738
|
return None
|
|
@@ -24,6 +24,13 @@
|
|
|
24
24
|
# `source`, `.`, `bash < nv.sh`, and an interpreter a `find -exec` or
|
|
25
25
|
# `xargs` puts at a command position.
|
|
26
26
|
#
|
|
27
|
+
# NOT vector 11: `bash -n nv.sh`. `-n` is noexec — the shell reads and parses
|
|
28
|
+
# the file, then exits without running a line — so a syntax check is the one
|
|
29
|
+
# shape that puts a path at a command position while executing nothing. It used
|
|
30
|
+
# to be refused, which made `bash -n` unusable on this file, on every husky
|
|
31
|
+
# hook, and on most of this repository (#3781). The distinction is noexec, not
|
|
32
|
+
# the program: `bash nv.sh` still executes and is still refused.
|
|
33
|
+
#
|
|
27
34
|
# The line below is what lets `lisa apply` tell a downstream copy of this guard
|
|
28
35
|
# that is BEHIND from one that is AHEAD. Byte comparison cannot: both look like
|
|
29
36
|
# "differs from mine", and guessing "behind" is how a fork's stronger guard gets
|
|
@@ -1031,6 +1038,10 @@ def resolve_script(token):
|
|
|
1031
1038
|
|
|
1032
1039
|
SHELL_COMMAND_STRING = object()
|
|
1033
1040
|
SHELL_STDIN = object()
|
|
1041
|
+
# `-n` / `--noexec`: the shell READS and parses its operand, then exits without
|
|
1042
|
+
# executing a line of it. Distinct from the two above because nothing runs at
|
|
1043
|
+
# all — there is no payload to scan and no stdin script to wait for.
|
|
1044
|
+
SHELL_NOEXEC = object()
|
|
1034
1045
|
|
|
1035
1046
|
|
|
1036
1047
|
def shell_execution_operand(args):
|
|
@@ -1061,12 +1072,25 @@ def shell_execution_operand(args):
|
|
|
1061
1072
|
index += 1
|
|
1062
1073
|
continue
|
|
1063
1074
|
if token.startswith("--"):
|
|
1075
|
+
if token == "--noexec":
|
|
1076
|
+
return SHELL_NOEXEC
|
|
1064
1077
|
if token not in SHELL_LONG_OPTIONS_NO_VALUE:
|
|
1065
1078
|
opaque_option_seen = True
|
|
1066
1079
|
index += 1
|
|
1067
1080
|
continue
|
|
1068
1081
|
if "c" in token[1:]:
|
|
1069
1082
|
return SHELL_COMMAND_STRING
|
|
1083
|
+
# `-n` is noexec. `bash -n <file>` is the one shape that puts a path at
|
|
1084
|
+
# a command position while provably executing nothing, so it is the
|
|
1085
|
+
# exact seam between reading and running — and reach got it wrong in
|
|
1086
|
+
# BOTH directions (CodySwannGT/lisa#3781). Here it meant a syntax check
|
|
1087
|
+
# of any file merely DISCUSSING the bypass was refused, which is most of
|
|
1088
|
+
# this repository's own guards and every husky hook.
|
|
1089
|
+
#
|
|
1090
|
+
# `bash <file>` still executes and is still refused. Noexec is the
|
|
1091
|
+
# distinction, not the program: this is not a read-only exemption.
|
|
1092
|
+
if "n" in token[1:]:
|
|
1093
|
+
return SHELL_NOEXEC
|
|
1070
1094
|
index += 1
|
|
1071
1095
|
return args[index] if index < len(args) else SHELL_STDIN
|
|
1072
1096
|
|
|
@@ -1111,6 +1135,10 @@ def executed_scripts(tokens):
|
|
|
1111
1135
|
continue
|
|
1112
1136
|
if operand is SHELL_COMMAND_STRING:
|
|
1113
1137
|
continue
|
|
1138
|
+
if operand is SHELL_NOEXEC:
|
|
1139
|
+
# Nothing runs, so there is nothing to follow and nothing
|
|
1140
|
+
# to fail closed about.
|
|
1141
|
+
continue
|
|
1114
1142
|
else:
|
|
1115
1143
|
continue
|
|
1116
1144
|
if operand is None:
|
|
@@ -1708,6 +1708,23 @@ def executed_operand(argv):
|
|
|
1708
1708
|
# ordinary shell options and do not consume the script path.
|
|
1709
1709
|
if not head.startswith("--") and "c" in head[1:]:
|
|
1710
1710
|
return None
|
|
1711
|
+
# `-n` is noexec: the shell READS and parses the file and then
|
|
1712
|
+
# exits without running a line of it. `bash -n <file>` is the
|
|
1713
|
+
# one shape that puts a path at a command position while
|
|
1714
|
+
# provably executing nothing, so it is the exact seam between
|
|
1715
|
+
# reading and running — and this walk got it wrong in BOTH
|
|
1716
|
+
# directions (CodySwannGT/lisa#3781). A creation-shaped file
|
|
1717
|
+
# earned a false refusal; a file carrying a human-gate marker
|
|
1718
|
+
# earned a false ALLOW, adjudicating as "declared" a command
|
|
1719
|
+
# that filed nothing. The silent direction is the worse one.
|
|
1720
|
+
#
|
|
1721
|
+
# Not a read-only-command exemption: `bash <file>` still runs
|
|
1722
|
+
# the file and is still refused. The distinction is noexec, not
|
|
1723
|
+
# the program.
|
|
1724
|
+
if head == "--noexec" or (
|
|
1725
|
+
not head.startswith("--") and "n" in head[1:]
|
|
1726
|
+
):
|
|
1727
|
+
return None
|
|
1711
1728
|
elif head in NO_SCRIPT_OPTIONS.get(program, set()):
|
|
1712
1729
|
return None
|
|
1713
1730
|
takes_value = head in INTERPRETER_VALUE_OPTIONS.get(program, set())
|
|
@@ -719,6 +719,20 @@ def shell_script_operand(args):
|
|
|
719
719
|
# not a script file.
|
|
720
720
|
if not token.startswith("--") and "c" in token[1:]:
|
|
721
721
|
return None
|
|
722
|
+
# `-n` is noexec: the shell READS and parses the file, then exits
|
|
723
|
+
# without executing a line of it. `bash -n <file>` is the one shape
|
|
724
|
+
# that puts a path at a command position while provably running
|
|
725
|
+
# nothing — the exact seam between reading and running, and this
|
|
726
|
+
# walk got it wrong (CodySwannGT/lisa#3781). Here that meant a
|
|
727
|
+
# syntax check of any script mentioning a managed path was refused
|
|
728
|
+
# as though it were about to write one.
|
|
729
|
+
#
|
|
730
|
+
# `bash <file>` still executes and is still refused. Noexec is the
|
|
731
|
+
# distinction, not the program.
|
|
732
|
+
if token == "--noexec" or (
|
|
733
|
+
not token.startswith("--") and "n" in token[1:]
|
|
734
|
+
):
|
|
735
|
+
return None
|
|
722
736
|
continue
|
|
723
737
|
return token
|
|
724
738
|
return None
|