@codyswann/lisa 4.33.12 → 4.33.13
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-hooks/parity-safety-net.sh +53 -4
- package/dist/configs/vitest/scratch-setup.d.ts +34 -0
- package/dist/configs/vitest/scratch-setup.d.ts.map +1 -1
- package/dist/configs/vitest/scratch-setup.js +89 -10
- package/dist/configs/vitest/scratch-setup.js.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +4 -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 +7 -4
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/opencode/plugin-templates/parity-safety-net.sh +53 -4
- 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/parity-safety-net.sh +53 -4
- package/plugins/lisa-agy/hooks/parity-safety-net.sh +53 -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.sh +53 -4
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/parity-safety-net.sh +53 -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.sh +53 -4
- package/typescript/copy-contents/.husky/pre-commit +10 -6
- package/typescript/copy-contents/.husky/pre-push +46 -16
|
@@ -1019,14 +1019,40 @@ readonly GIT_CMD='(^|[^[:alnum:]_-])git[[:space:]]+'"$GIT_GLOBAL_OPTS"
|
|
|
1019
1019
|
# match. The preceding-char class still excludes `.`, `/`, and `-` to keep
|
|
1020
1020
|
# `--rm`-style flags and `foo.rm` names out.
|
|
1021
1021
|
readonly RM_CMD='(^|[^[:alnum:]_./-])([[:alnum:]_./-]*/)?rm'
|
|
1022
|
+
# Where a recursive-forced flag cluster is allowed to END.
|
|
1023
|
+
#
|
|
1024
|
+
# It used to be `([[:space:]]|$)` — whitespace or end of segment — and a closing
|
|
1025
|
+
# parenthesis is neither, so a SUBSHELL-wrapped pipeline fell out of every guard
|
|
1026
|
+
# built on these two patterns while its brace-wrapped twin was caught: `{ find
|
|
1027
|
+
# .git -type d | parallel rm -rf; }` matched because the `;` supplied the
|
|
1028
|
+
# boundary, and `(find .git -type d | parallel rm -rf)` did not (issue #3494).
|
|
1029
|
+
#
|
|
1030
|
+
# So the two STATEMENT-GROUPING terminators join whitespace here, and nothing
|
|
1031
|
+
# else. The general spelling — "any character that cannot be part of a flag",
|
|
1032
|
+
# `([^[:alnum:]-]|$)` — is the more principled statement of the intent, and is
|
|
1033
|
+
# deliberately NOT used: these patterns feed the rm TARGET WALK, the guard with
|
|
1034
|
+
# this file's longest false-positive history, and it would newly match ordinary
|
|
1035
|
+
# prose punctuation (`echo "rm -rf, then …"` on the comma). Paren and semicolon
|
|
1036
|
+
# buy the brace/paren parity the defect is about and buy nothing else.
|
|
1037
|
+
#
|
|
1038
|
+
# The cluster must still END at the boundary, so `-rfoo` is not read as `-rf`.
|
|
1039
|
+
readonly RM_RF_END='([[:space:];)]|$)'
|
|
1022
1040
|
# rm invoked with BOTH a recursive and a force flag — clustered (-rf/-fr, any
|
|
1023
1041
|
# extra letters) or split, in either order. The split gate pairs ANY recursive
|
|
1024
1042
|
# form (short cluster containing r, or --recursive) with ANY force form (short
|
|
1025
1043
|
# cluster containing f, or --force), so mixed spellings like `rm -r --force /`
|
|
1026
1044
|
# and `rm --recursive -f /` cannot slip between the short/short and long/long
|
|
1027
1045
|
# alternations (PR #1976 review).
|
|
1028
|
-
|
|
1029
|
-
|
|
1046
|
+
#
|
|
1047
|
+
# The FLAG TAILS are named separately from the `rm`-anchored patterns because
|
|
1048
|
+
# the find/xargs family below needs the identical flag vocabulary against a
|
|
1049
|
+
# different verb anchor. Before #3494 it carried its own short-cluster-only copy
|
|
1050
|
+
# and therefore missed `rm --recursive --force` — a divergence that could only
|
|
1051
|
+
# happen because the spelling set was written twice.
|
|
1052
|
+
readonly RM_RF_TAIL_CLUSTER='([[:space:]]+-[[:alnum:]-]+)*[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)'"$RM_RF_END"
|
|
1053
|
+
readonly RM_RF_TAIL_SPLIT='(([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)'"$RM_RF_END"'|([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)'"$RM_RF_END"')'
|
|
1054
|
+
readonly RM_RF_CLUSTER="$RM_CMD$RM_RF_TAIL_CLUSTER"
|
|
1055
|
+
readonly RM_RF_SPLIT="$RM_CMD$RM_RF_TAIL_SPLIT"
|
|
1030
1056
|
|
|
1031
1057
|
# 1. Recursive forced delete (`rm -rf`) of a filesystem root, home, or top-level
|
|
1032
1058
|
# wildcard. Two gates ANDed: the statement must invoke `rm` with BOTH a
|
|
@@ -1667,8 +1693,31 @@ fi
|
|
|
1667
1693
|
if matches '(^|[^[:alnum:]_./-])find[[:space:]][^;&|]*-exec[[:space:]]+rm[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)([[:space:]]|$)'; then
|
|
1668
1694
|
block "find -exec rm -rf performs a recursive forced delete on unreviewed paths"
|
|
1669
1695
|
fi
|
|
1670
|
-
|
|
1671
|
-
|
|
1696
|
+
# A command that reads its operands from STDIN and runs a program on them. The
|
|
1697
|
+
# targets are therefore whatever the upstream stage emitted, which is exactly
|
|
1698
|
+
# what makes a recursive forced delete behind one unauditable.
|
|
1699
|
+
#
|
|
1700
|
+
# Three fail-opens closed here (issue #3494), all of them the same mistake —
|
|
1701
|
+
# this rule was written standalone instead of inheriting vocabulary the file
|
|
1702
|
+
# already had:
|
|
1703
|
+
#
|
|
1704
|
+
# 1. The old prefix was `(^|[^[:alnum:]_./-])xargs`, whose excluded class
|
|
1705
|
+
# contains `/`, so the character before `xargs` in `/usr/bin/xargs` was
|
|
1706
|
+
# itself disqualifying and a PATH-SPELLED invocation never matched.
|
|
1707
|
+
# `gxargs` — the GNU findutils spelling shipped by macOS package managers —
|
|
1708
|
+
# failed the same way on the alphanumeric side. RM_CMD above solved this
|
|
1709
|
+
# exact problem for `rm` by putting the boundary before the WHOLE path and
|
|
1710
|
+
# making the directory part optional; that shape is reused verbatim.
|
|
1711
|
+
# 2. `parallel` was absent. It is a drop-in `xargs` substitute reaching the
|
|
1712
|
+
# same delete on the same unaudited stdin, and guard 14 below already had
|
|
1713
|
+
# to name it in its own evidence — it was missing only here.
|
|
1714
|
+
# 3. The flag set was a short-cluster-only copy, so `rm --recursive --force`
|
|
1715
|
+
# was allowed where `rm -rf` was refused. It now uses the SAME tails as the
|
|
1716
|
+
# rm walk, which is what makes that class of divergence unrepeatable.
|
|
1717
|
+
readonly STDIN_DELETER='(^|[^[:alnum:]_./-])([[:alnum:]_./-]*/)?(g?xargs|parallel)[[:space:]]([^;&|]*[[:space:]])?rm'
|
|
1718
|
+
if matches "$STDIN_DELETER$RM_RF_TAIL_CLUSTER" \
|
|
1719
|
+
|| matches "$STDIN_DELETER$RM_RF_TAIL_SPLIT"; then
|
|
1720
|
+
block "xargs/parallel rm -rf performs a recursive forced delete on dynamic stdin input"
|
|
1672
1721
|
fi
|
|
1673
1722
|
|
|
1674
1723
|
# 12. Disk destroyers, always on (exceeds upstream, which only scans these
|
package/package.json
CHANGED
|
@@ -165,7 +165,7 @@
|
|
|
165
165
|
"zod-validation-error": "^4.0.0"
|
|
166
166
|
},
|
|
167
167
|
"name": "@codyswann/lisa",
|
|
168
|
-
"version": "4.33.
|
|
168
|
+
"version": "4.33.13",
|
|
169
169
|
"description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
|
|
170
170
|
"main": "dist/index.js",
|
|
171
171
|
"exports": {
|
|
@@ -313,7 +313,7 @@
|
|
|
313
313
|
"test": "tests"
|
|
314
314
|
},
|
|
315
315
|
"types": "./dist/index.d.ts",
|
|
316
|
-
"lisaReleaseCommit": "
|
|
317
|
-
"gitHead": "
|
|
318
|
-
"lisaReleaseTag": "v4.33.
|
|
316
|
+
"lisaReleaseCommit": "ff18676df240f6a381c4e7f0afed193fef8c0a4f",
|
|
317
|
+
"gitHead": "ff18676df240f6a381c4e7f0afed193fef8c0a4f",
|
|
318
|
+
"lisaReleaseTag": "v4.33.13"
|
|
319
319
|
}
|
|
@@ -1019,14 +1019,40 @@ readonly GIT_CMD='(^|[^[:alnum:]_-])git[[:space:]]+'"$GIT_GLOBAL_OPTS"
|
|
|
1019
1019
|
# match. The preceding-char class still excludes `.`, `/`, and `-` to keep
|
|
1020
1020
|
# `--rm`-style flags and `foo.rm` names out.
|
|
1021
1021
|
readonly RM_CMD='(^|[^[:alnum:]_./-])([[:alnum:]_./-]*/)?rm'
|
|
1022
|
+
# Where a recursive-forced flag cluster is allowed to END.
|
|
1023
|
+
#
|
|
1024
|
+
# It used to be `([[:space:]]|$)` — whitespace or end of segment — and a closing
|
|
1025
|
+
# parenthesis is neither, so a SUBSHELL-wrapped pipeline fell out of every guard
|
|
1026
|
+
# built on these two patterns while its brace-wrapped twin was caught: `{ find
|
|
1027
|
+
# .git -type d | parallel rm -rf; }` matched because the `;` supplied the
|
|
1028
|
+
# boundary, and `(find .git -type d | parallel rm -rf)` did not (issue #3494).
|
|
1029
|
+
#
|
|
1030
|
+
# So the two STATEMENT-GROUPING terminators join whitespace here, and nothing
|
|
1031
|
+
# else. The general spelling — "any character that cannot be part of a flag",
|
|
1032
|
+
# `([^[:alnum:]-]|$)` — is the more principled statement of the intent, and is
|
|
1033
|
+
# deliberately NOT used: these patterns feed the rm TARGET WALK, the guard with
|
|
1034
|
+
# this file's longest false-positive history, and it would newly match ordinary
|
|
1035
|
+
# prose punctuation (`echo "rm -rf, then …"` on the comma). Paren and semicolon
|
|
1036
|
+
# buy the brace/paren parity the defect is about and buy nothing else.
|
|
1037
|
+
#
|
|
1038
|
+
# The cluster must still END at the boundary, so `-rfoo` is not read as `-rf`.
|
|
1039
|
+
readonly RM_RF_END='([[:space:];)]|$)'
|
|
1022
1040
|
# rm invoked with BOTH a recursive and a force flag — clustered (-rf/-fr, any
|
|
1023
1041
|
# extra letters) or split, in either order. The split gate pairs ANY recursive
|
|
1024
1042
|
# form (short cluster containing r, or --recursive) with ANY force form (short
|
|
1025
1043
|
# cluster containing f, or --force), so mixed spellings like `rm -r --force /`
|
|
1026
1044
|
# and `rm --recursive -f /` cannot slip between the short/short and long/long
|
|
1027
1045
|
# alternations (PR #1976 review).
|
|
1028
|
-
|
|
1029
|
-
|
|
1046
|
+
#
|
|
1047
|
+
# The FLAG TAILS are named separately from the `rm`-anchored patterns because
|
|
1048
|
+
# the find/xargs family below needs the identical flag vocabulary against a
|
|
1049
|
+
# different verb anchor. Before #3494 it carried its own short-cluster-only copy
|
|
1050
|
+
# and therefore missed `rm --recursive --force` — a divergence that could only
|
|
1051
|
+
# happen because the spelling set was written twice.
|
|
1052
|
+
readonly RM_RF_TAIL_CLUSTER='([[:space:]]+-[[:alnum:]-]+)*[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)'"$RM_RF_END"
|
|
1053
|
+
readonly RM_RF_TAIL_SPLIT='(([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)'"$RM_RF_END"'|([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)'"$RM_RF_END"')'
|
|
1054
|
+
readonly RM_RF_CLUSTER="$RM_CMD$RM_RF_TAIL_CLUSTER"
|
|
1055
|
+
readonly RM_RF_SPLIT="$RM_CMD$RM_RF_TAIL_SPLIT"
|
|
1030
1056
|
|
|
1031
1057
|
# 1. Recursive forced delete (`rm -rf`) of a filesystem root, home, or top-level
|
|
1032
1058
|
# wildcard. Two gates ANDed: the statement must invoke `rm` with BOTH a
|
|
@@ -1667,8 +1693,31 @@ fi
|
|
|
1667
1693
|
if matches '(^|[^[:alnum:]_./-])find[[:space:]][^;&|]*-exec[[:space:]]+rm[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)([[:space:]]|$)'; then
|
|
1668
1694
|
block "find -exec rm -rf performs a recursive forced delete on unreviewed paths"
|
|
1669
1695
|
fi
|
|
1670
|
-
|
|
1671
|
-
|
|
1696
|
+
# A command that reads its operands from STDIN and runs a program on them. The
|
|
1697
|
+
# targets are therefore whatever the upstream stage emitted, which is exactly
|
|
1698
|
+
# what makes a recursive forced delete behind one unauditable.
|
|
1699
|
+
#
|
|
1700
|
+
# Three fail-opens closed here (issue #3494), all of them the same mistake —
|
|
1701
|
+
# this rule was written standalone instead of inheriting vocabulary the file
|
|
1702
|
+
# already had:
|
|
1703
|
+
#
|
|
1704
|
+
# 1. The old prefix was `(^|[^[:alnum:]_./-])xargs`, whose excluded class
|
|
1705
|
+
# contains `/`, so the character before `xargs` in `/usr/bin/xargs` was
|
|
1706
|
+
# itself disqualifying and a PATH-SPELLED invocation never matched.
|
|
1707
|
+
# `gxargs` — the GNU findutils spelling shipped by macOS package managers —
|
|
1708
|
+
# failed the same way on the alphanumeric side. RM_CMD above solved this
|
|
1709
|
+
# exact problem for `rm` by putting the boundary before the WHOLE path and
|
|
1710
|
+
# making the directory part optional; that shape is reused verbatim.
|
|
1711
|
+
# 2. `parallel` was absent. It is a drop-in `xargs` substitute reaching the
|
|
1712
|
+
# same delete on the same unaudited stdin, and guard 14 below already had
|
|
1713
|
+
# to name it in its own evidence — it was missing only here.
|
|
1714
|
+
# 3. The flag set was a short-cluster-only copy, so `rm --recursive --force`
|
|
1715
|
+
# was allowed where `rm -rf` was refused. It now uses the SAME tails as the
|
|
1716
|
+
# rm walk, which is what makes that class of divergence unrepeatable.
|
|
1717
|
+
readonly STDIN_DELETER='(^|[^[:alnum:]_./-])([[:alnum:]_./-]*/)?(g?xargs|parallel)[[:space:]]([^;&|]*[[:space:]])?rm'
|
|
1718
|
+
if matches "$STDIN_DELETER$RM_RF_TAIL_CLUSTER" \
|
|
1719
|
+
|| matches "$STDIN_DELETER$RM_RF_TAIL_SPLIT"; then
|
|
1720
|
+
block "xargs/parallel rm -rf performs a recursive forced delete on dynamic stdin input"
|
|
1672
1721
|
fi
|
|
1673
1722
|
|
|
1674
1723
|
# 12. Disk destroyers, always on (exceeds upstream, which only scans these
|
|
@@ -1019,14 +1019,40 @@ readonly GIT_CMD='(^|[^[:alnum:]_-])git[[:space:]]+'"$GIT_GLOBAL_OPTS"
|
|
|
1019
1019
|
# match. The preceding-char class still excludes `.`, `/`, and `-` to keep
|
|
1020
1020
|
# `--rm`-style flags and `foo.rm` names out.
|
|
1021
1021
|
readonly RM_CMD='(^|[^[:alnum:]_./-])([[:alnum:]_./-]*/)?rm'
|
|
1022
|
+
# Where a recursive-forced flag cluster is allowed to END.
|
|
1023
|
+
#
|
|
1024
|
+
# It used to be `([[:space:]]|$)` — whitespace or end of segment — and a closing
|
|
1025
|
+
# parenthesis is neither, so a SUBSHELL-wrapped pipeline fell out of every guard
|
|
1026
|
+
# built on these two patterns while its brace-wrapped twin was caught: `{ find
|
|
1027
|
+
# .git -type d | parallel rm -rf; }` matched because the `;` supplied the
|
|
1028
|
+
# boundary, and `(find .git -type d | parallel rm -rf)` did not (issue #3494).
|
|
1029
|
+
#
|
|
1030
|
+
# So the two STATEMENT-GROUPING terminators join whitespace here, and nothing
|
|
1031
|
+
# else. The general spelling — "any character that cannot be part of a flag",
|
|
1032
|
+
# `([^[:alnum:]-]|$)` — is the more principled statement of the intent, and is
|
|
1033
|
+
# deliberately NOT used: these patterns feed the rm TARGET WALK, the guard with
|
|
1034
|
+
# this file's longest false-positive history, and it would newly match ordinary
|
|
1035
|
+
# prose punctuation (`echo "rm -rf, then …"` on the comma). Paren and semicolon
|
|
1036
|
+
# buy the brace/paren parity the defect is about and buy nothing else.
|
|
1037
|
+
#
|
|
1038
|
+
# The cluster must still END at the boundary, so `-rfoo` is not read as `-rf`.
|
|
1039
|
+
readonly RM_RF_END='([[:space:];)]|$)'
|
|
1022
1040
|
# rm invoked with BOTH a recursive and a force flag — clustered (-rf/-fr, any
|
|
1023
1041
|
# extra letters) or split, in either order. The split gate pairs ANY recursive
|
|
1024
1042
|
# form (short cluster containing r, or --recursive) with ANY force form (short
|
|
1025
1043
|
# cluster containing f, or --force), so mixed spellings like `rm -r --force /`
|
|
1026
1044
|
# and `rm --recursive -f /` cannot slip between the short/short and long/long
|
|
1027
1045
|
# alternations (PR #1976 review).
|
|
1028
|
-
|
|
1029
|
-
|
|
1046
|
+
#
|
|
1047
|
+
# The FLAG TAILS are named separately from the `rm`-anchored patterns because
|
|
1048
|
+
# the find/xargs family below needs the identical flag vocabulary against a
|
|
1049
|
+
# different verb anchor. Before #3494 it carried its own short-cluster-only copy
|
|
1050
|
+
# and therefore missed `rm --recursive --force` — a divergence that could only
|
|
1051
|
+
# happen because the spelling set was written twice.
|
|
1052
|
+
readonly RM_RF_TAIL_CLUSTER='([[:space:]]+-[[:alnum:]-]+)*[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)'"$RM_RF_END"
|
|
1053
|
+
readonly RM_RF_TAIL_SPLIT='(([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)'"$RM_RF_END"'|([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)'"$RM_RF_END"')'
|
|
1054
|
+
readonly RM_RF_CLUSTER="$RM_CMD$RM_RF_TAIL_CLUSTER"
|
|
1055
|
+
readonly RM_RF_SPLIT="$RM_CMD$RM_RF_TAIL_SPLIT"
|
|
1030
1056
|
|
|
1031
1057
|
# 1. Recursive forced delete (`rm -rf`) of a filesystem root, home, or top-level
|
|
1032
1058
|
# wildcard. Two gates ANDed: the statement must invoke `rm` with BOTH a
|
|
@@ -1667,8 +1693,31 @@ fi
|
|
|
1667
1693
|
if matches '(^|[^[:alnum:]_./-])find[[:space:]][^;&|]*-exec[[:space:]]+rm[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)([[:space:]]|$)'; then
|
|
1668
1694
|
block "find -exec rm -rf performs a recursive forced delete on unreviewed paths"
|
|
1669
1695
|
fi
|
|
1670
|
-
|
|
1671
|
-
|
|
1696
|
+
# A command that reads its operands from STDIN and runs a program on them. The
|
|
1697
|
+
# targets are therefore whatever the upstream stage emitted, which is exactly
|
|
1698
|
+
# what makes a recursive forced delete behind one unauditable.
|
|
1699
|
+
#
|
|
1700
|
+
# Three fail-opens closed here (issue #3494), all of them the same mistake —
|
|
1701
|
+
# this rule was written standalone instead of inheriting vocabulary the file
|
|
1702
|
+
# already had:
|
|
1703
|
+
#
|
|
1704
|
+
# 1. The old prefix was `(^|[^[:alnum:]_./-])xargs`, whose excluded class
|
|
1705
|
+
# contains `/`, so the character before `xargs` in `/usr/bin/xargs` was
|
|
1706
|
+
# itself disqualifying and a PATH-SPELLED invocation never matched.
|
|
1707
|
+
# `gxargs` — the GNU findutils spelling shipped by macOS package managers —
|
|
1708
|
+
# failed the same way on the alphanumeric side. RM_CMD above solved this
|
|
1709
|
+
# exact problem for `rm` by putting the boundary before the WHOLE path and
|
|
1710
|
+
# making the directory part optional; that shape is reused verbatim.
|
|
1711
|
+
# 2. `parallel` was absent. It is a drop-in `xargs` substitute reaching the
|
|
1712
|
+
# same delete on the same unaudited stdin, and guard 14 below already had
|
|
1713
|
+
# to name it in its own evidence — it was missing only here.
|
|
1714
|
+
# 3. The flag set was a short-cluster-only copy, so `rm --recursive --force`
|
|
1715
|
+
# was allowed where `rm -rf` was refused. It now uses the SAME tails as the
|
|
1716
|
+
# rm walk, which is what makes that class of divergence unrepeatable.
|
|
1717
|
+
readonly STDIN_DELETER='(^|[^[:alnum:]_./-])([[:alnum:]_./-]*/)?(g?xargs|parallel)[[:space:]]([^;&|]*[[:space:]])?rm'
|
|
1718
|
+
if matches "$STDIN_DELETER$RM_RF_TAIL_CLUSTER" \
|
|
1719
|
+
|| matches "$STDIN_DELETER$RM_RF_TAIL_SPLIT"; then
|
|
1720
|
+
block "xargs/parallel rm -rf performs a recursive forced delete on dynamic stdin input"
|
|
1672
1721
|
fi
|
|
1673
1722
|
|
|
1674
1723
|
# 12. Disk destroyers, always on (exceeds upstream, which only scans these
|
|
@@ -1019,14 +1019,40 @@ readonly GIT_CMD='(^|[^[:alnum:]_-])git[[:space:]]+'"$GIT_GLOBAL_OPTS"
|
|
|
1019
1019
|
# match. The preceding-char class still excludes `.`, `/`, and `-` to keep
|
|
1020
1020
|
# `--rm`-style flags and `foo.rm` names out.
|
|
1021
1021
|
readonly RM_CMD='(^|[^[:alnum:]_./-])([[:alnum:]_./-]*/)?rm'
|
|
1022
|
+
# Where a recursive-forced flag cluster is allowed to END.
|
|
1023
|
+
#
|
|
1024
|
+
# It used to be `([[:space:]]|$)` — whitespace or end of segment — and a closing
|
|
1025
|
+
# parenthesis is neither, so a SUBSHELL-wrapped pipeline fell out of every guard
|
|
1026
|
+
# built on these two patterns while its brace-wrapped twin was caught: `{ find
|
|
1027
|
+
# .git -type d | parallel rm -rf; }` matched because the `;` supplied the
|
|
1028
|
+
# boundary, and `(find .git -type d | parallel rm -rf)` did not (issue #3494).
|
|
1029
|
+
#
|
|
1030
|
+
# So the two STATEMENT-GROUPING terminators join whitespace here, and nothing
|
|
1031
|
+
# else. The general spelling — "any character that cannot be part of a flag",
|
|
1032
|
+
# `([^[:alnum:]-]|$)` — is the more principled statement of the intent, and is
|
|
1033
|
+
# deliberately NOT used: these patterns feed the rm TARGET WALK, the guard with
|
|
1034
|
+
# this file's longest false-positive history, and it would newly match ordinary
|
|
1035
|
+
# prose punctuation (`echo "rm -rf, then …"` on the comma). Paren and semicolon
|
|
1036
|
+
# buy the brace/paren parity the defect is about and buy nothing else.
|
|
1037
|
+
#
|
|
1038
|
+
# The cluster must still END at the boundary, so `-rfoo` is not read as `-rf`.
|
|
1039
|
+
readonly RM_RF_END='([[:space:];)]|$)'
|
|
1022
1040
|
# rm invoked with BOTH a recursive and a force flag — clustered (-rf/-fr, any
|
|
1023
1041
|
# extra letters) or split, in either order. The split gate pairs ANY recursive
|
|
1024
1042
|
# form (short cluster containing r, or --recursive) with ANY force form (short
|
|
1025
1043
|
# cluster containing f, or --force), so mixed spellings like `rm -r --force /`
|
|
1026
1044
|
# and `rm --recursive -f /` cannot slip between the short/short and long/long
|
|
1027
1045
|
# alternations (PR #1976 review).
|
|
1028
|
-
|
|
1029
|
-
|
|
1046
|
+
#
|
|
1047
|
+
# The FLAG TAILS are named separately from the `rm`-anchored patterns because
|
|
1048
|
+
# the find/xargs family below needs the identical flag vocabulary against a
|
|
1049
|
+
# different verb anchor. Before #3494 it carried its own short-cluster-only copy
|
|
1050
|
+
# and therefore missed `rm --recursive --force` — a divergence that could only
|
|
1051
|
+
# happen because the spelling set was written twice.
|
|
1052
|
+
readonly RM_RF_TAIL_CLUSTER='([[:space:]]+-[[:alnum:]-]+)*[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)'"$RM_RF_END"
|
|
1053
|
+
readonly RM_RF_TAIL_SPLIT='(([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)'"$RM_RF_END"'|([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)'"$RM_RF_END"')'
|
|
1054
|
+
readonly RM_RF_CLUSTER="$RM_CMD$RM_RF_TAIL_CLUSTER"
|
|
1055
|
+
readonly RM_RF_SPLIT="$RM_CMD$RM_RF_TAIL_SPLIT"
|
|
1030
1056
|
|
|
1031
1057
|
# 1. Recursive forced delete (`rm -rf`) of a filesystem root, home, or top-level
|
|
1032
1058
|
# wildcard. Two gates ANDed: the statement must invoke `rm` with BOTH a
|
|
@@ -1667,8 +1693,31 @@ fi
|
|
|
1667
1693
|
if matches '(^|[^[:alnum:]_./-])find[[:space:]][^;&|]*-exec[[:space:]]+rm[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)([[:space:]]|$)'; then
|
|
1668
1694
|
block "find -exec rm -rf performs a recursive forced delete on unreviewed paths"
|
|
1669
1695
|
fi
|
|
1670
|
-
|
|
1671
|
-
|
|
1696
|
+
# A command that reads its operands from STDIN and runs a program on them. The
|
|
1697
|
+
# targets are therefore whatever the upstream stage emitted, which is exactly
|
|
1698
|
+
# what makes a recursive forced delete behind one unauditable.
|
|
1699
|
+
#
|
|
1700
|
+
# Three fail-opens closed here (issue #3494), all of them the same mistake —
|
|
1701
|
+
# this rule was written standalone instead of inheriting vocabulary the file
|
|
1702
|
+
# already had:
|
|
1703
|
+
#
|
|
1704
|
+
# 1. The old prefix was `(^|[^[:alnum:]_./-])xargs`, whose excluded class
|
|
1705
|
+
# contains `/`, so the character before `xargs` in `/usr/bin/xargs` was
|
|
1706
|
+
# itself disqualifying and a PATH-SPELLED invocation never matched.
|
|
1707
|
+
# `gxargs` — the GNU findutils spelling shipped by macOS package managers —
|
|
1708
|
+
# failed the same way on the alphanumeric side. RM_CMD above solved this
|
|
1709
|
+
# exact problem for `rm` by putting the boundary before the WHOLE path and
|
|
1710
|
+
# making the directory part optional; that shape is reused verbatim.
|
|
1711
|
+
# 2. `parallel` was absent. It is a drop-in `xargs` substitute reaching the
|
|
1712
|
+
# same delete on the same unaudited stdin, and guard 14 below already had
|
|
1713
|
+
# to name it in its own evidence — it was missing only here.
|
|
1714
|
+
# 3. The flag set was a short-cluster-only copy, so `rm --recursive --force`
|
|
1715
|
+
# was allowed where `rm -rf` was refused. It now uses the SAME tails as the
|
|
1716
|
+
# rm walk, which is what makes that class of divergence unrepeatable.
|
|
1717
|
+
readonly STDIN_DELETER='(^|[^[:alnum:]_./-])([[:alnum:]_./-]*/)?(g?xargs|parallel)[[:space:]]([^;&|]*[[:space:]])?rm'
|
|
1718
|
+
if matches "$STDIN_DELETER$RM_RF_TAIL_CLUSTER" \
|
|
1719
|
+
|| matches "$STDIN_DELETER$RM_RF_TAIL_SPLIT"; then
|
|
1720
|
+
block "xargs/parallel rm -rf performs a recursive forced delete on dynamic stdin input"
|
|
1672
1721
|
fi
|
|
1673
1722
|
|
|
1674
1723
|
# 12. Disk destroyers, always on (exceeds upstream, which only scans these
|
|
@@ -1019,14 +1019,40 @@ readonly GIT_CMD='(^|[^[:alnum:]_-])git[[:space:]]+'"$GIT_GLOBAL_OPTS"
|
|
|
1019
1019
|
# match. The preceding-char class still excludes `.`, `/`, and `-` to keep
|
|
1020
1020
|
# `--rm`-style flags and `foo.rm` names out.
|
|
1021
1021
|
readonly RM_CMD='(^|[^[:alnum:]_./-])([[:alnum:]_./-]*/)?rm'
|
|
1022
|
+
# Where a recursive-forced flag cluster is allowed to END.
|
|
1023
|
+
#
|
|
1024
|
+
# It used to be `([[:space:]]|$)` — whitespace or end of segment — and a closing
|
|
1025
|
+
# parenthesis is neither, so a SUBSHELL-wrapped pipeline fell out of every guard
|
|
1026
|
+
# built on these two patterns while its brace-wrapped twin was caught: `{ find
|
|
1027
|
+
# .git -type d | parallel rm -rf; }` matched because the `;` supplied the
|
|
1028
|
+
# boundary, and `(find .git -type d | parallel rm -rf)` did not (issue #3494).
|
|
1029
|
+
#
|
|
1030
|
+
# So the two STATEMENT-GROUPING terminators join whitespace here, and nothing
|
|
1031
|
+
# else. The general spelling — "any character that cannot be part of a flag",
|
|
1032
|
+
# `([^[:alnum:]-]|$)` — is the more principled statement of the intent, and is
|
|
1033
|
+
# deliberately NOT used: these patterns feed the rm TARGET WALK, the guard with
|
|
1034
|
+
# this file's longest false-positive history, and it would newly match ordinary
|
|
1035
|
+
# prose punctuation (`echo "rm -rf, then …"` on the comma). Paren and semicolon
|
|
1036
|
+
# buy the brace/paren parity the defect is about and buy nothing else.
|
|
1037
|
+
#
|
|
1038
|
+
# The cluster must still END at the boundary, so `-rfoo` is not read as `-rf`.
|
|
1039
|
+
readonly RM_RF_END='([[:space:];)]|$)'
|
|
1022
1040
|
# rm invoked with BOTH a recursive and a force flag — clustered (-rf/-fr, any
|
|
1023
1041
|
# extra letters) or split, in either order. The split gate pairs ANY recursive
|
|
1024
1042
|
# form (short cluster containing r, or --recursive) with ANY force form (short
|
|
1025
1043
|
# cluster containing f, or --force), so mixed spellings like `rm -r --force /`
|
|
1026
1044
|
# and `rm --recursive -f /` cannot slip between the short/short and long/long
|
|
1027
1045
|
# alternations (PR #1976 review).
|
|
1028
|
-
|
|
1029
|
-
|
|
1046
|
+
#
|
|
1047
|
+
# The FLAG TAILS are named separately from the `rm`-anchored patterns because
|
|
1048
|
+
# the find/xargs family below needs the identical flag vocabulary against a
|
|
1049
|
+
# different verb anchor. Before #3494 it carried its own short-cluster-only copy
|
|
1050
|
+
# and therefore missed `rm --recursive --force` — a divergence that could only
|
|
1051
|
+
# happen because the spelling set was written twice.
|
|
1052
|
+
readonly RM_RF_TAIL_CLUSTER='([[:space:]]+-[[:alnum:]-]+)*[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)'"$RM_RF_END"
|
|
1053
|
+
readonly RM_RF_TAIL_SPLIT='(([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)'"$RM_RF_END"'|([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)'"$RM_RF_END"')'
|
|
1054
|
+
readonly RM_RF_CLUSTER="$RM_CMD$RM_RF_TAIL_CLUSTER"
|
|
1055
|
+
readonly RM_RF_SPLIT="$RM_CMD$RM_RF_TAIL_SPLIT"
|
|
1030
1056
|
|
|
1031
1057
|
# 1. Recursive forced delete (`rm -rf`) of a filesystem root, home, or top-level
|
|
1032
1058
|
# wildcard. Two gates ANDed: the statement must invoke `rm` with BOTH a
|
|
@@ -1667,8 +1693,31 @@ fi
|
|
|
1667
1693
|
if matches '(^|[^[:alnum:]_./-])find[[:space:]][^;&|]*-exec[[:space:]]+rm[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)([[:space:]]|$)'; then
|
|
1668
1694
|
block "find -exec rm -rf performs a recursive forced delete on unreviewed paths"
|
|
1669
1695
|
fi
|
|
1670
|
-
|
|
1671
|
-
|
|
1696
|
+
# A command that reads its operands from STDIN and runs a program on them. The
|
|
1697
|
+
# targets are therefore whatever the upstream stage emitted, which is exactly
|
|
1698
|
+
# what makes a recursive forced delete behind one unauditable.
|
|
1699
|
+
#
|
|
1700
|
+
# Three fail-opens closed here (issue #3494), all of them the same mistake —
|
|
1701
|
+
# this rule was written standalone instead of inheriting vocabulary the file
|
|
1702
|
+
# already had:
|
|
1703
|
+
#
|
|
1704
|
+
# 1. The old prefix was `(^|[^[:alnum:]_./-])xargs`, whose excluded class
|
|
1705
|
+
# contains `/`, so the character before `xargs` in `/usr/bin/xargs` was
|
|
1706
|
+
# itself disqualifying and a PATH-SPELLED invocation never matched.
|
|
1707
|
+
# `gxargs` — the GNU findutils spelling shipped by macOS package managers —
|
|
1708
|
+
# failed the same way on the alphanumeric side. RM_CMD above solved this
|
|
1709
|
+
# exact problem for `rm` by putting the boundary before the WHOLE path and
|
|
1710
|
+
# making the directory part optional; that shape is reused verbatim.
|
|
1711
|
+
# 2. `parallel` was absent. It is a drop-in `xargs` substitute reaching the
|
|
1712
|
+
# same delete on the same unaudited stdin, and guard 14 below already had
|
|
1713
|
+
# to name it in its own evidence — it was missing only here.
|
|
1714
|
+
# 3. The flag set was a short-cluster-only copy, so `rm --recursive --force`
|
|
1715
|
+
# was allowed where `rm -rf` was refused. It now uses the SAME tails as the
|
|
1716
|
+
# rm walk, which is what makes that class of divergence unrepeatable.
|
|
1717
|
+
readonly STDIN_DELETER='(^|[^[:alnum:]_./-])([[:alnum:]_./-]*/)?(g?xargs|parallel)[[:space:]]([^;&|]*[[:space:]])?rm'
|
|
1718
|
+
if matches "$STDIN_DELETER$RM_RF_TAIL_CLUSTER" \
|
|
1719
|
+
|| matches "$STDIN_DELETER$RM_RF_TAIL_SPLIT"; then
|
|
1720
|
+
block "xargs/parallel rm -rf performs a recursive forced delete on dynamic stdin input"
|
|
1672
1721
|
fi
|
|
1673
1722
|
|
|
1674
1723
|
# 12. Disk destroyers, always on (exceeds upstream, which only scans these
|