@gotgenes/pi-permission-system 30.2.0 → 31.0.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/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [31.0.0](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v30.2.0...pi-permission-system-v31.0.0) (2026-09-02)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **pi-permission-system:** **breaking:** gate a path named as a for or select loop operand ([6189d81](https://github.com/gotgenes/pi-packages/commit/6189d81dc48a26d572bacb91a1b2e7c062634ad9)), closes [#839](https://github.com/gotgenes/pi-packages/issues/839)
|
|
14
|
+
* **pi-permission-system:** **breaking:** gate a path named as a case subject ([dfd2f8d](https://github.com/gotgenes/pi-packages/commit/dfd2f8d682c39dcfe6a903cdeadde18b08a7cc60)), closes [#839](https://github.com/gotgenes/pi-packages/issues/839)
|
|
15
|
+
|
|
16
|
+
### Documentation
|
|
17
|
+
|
|
18
|
+
* **pi-permission-system:** commit the instrument behind the statement-operand measurement ([6a708b7](https://github.com/gotgenes/pi-packages/commit/6a708b716f038bf192875a0166ae6a6ab012c3d8)), closes [#839](https://github.com/gotgenes/pi-packages/issues/839)
|
|
19
|
+
* **pi-permission-system:** register the statement-operand measurement script ([e3e8799](https://github.com/gotgenes/pi-packages/commit/e3e879935c6f395d4b2387a6e30e0791bb8fb765)), closes [#839](https://github.com/gotgenes/pi-packages/issues/839)
|
|
20
|
+
* **pi-permission-system:** correct the drift note in the operand measurement ([e300934](https://github.com/gotgenes/pi-packages/commit/e300934c0aad34b3bfd127695219250ca6f4613f)), closes [#839](https://github.com/gotgenes/pi-packages/issues/839)
|
|
21
|
+
|
|
8
22
|
## [30.2.0](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v30.1.0...pi-permission-system-v30.2.0) (2026-09-02)
|
|
9
23
|
|
|
10
24
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gotgenes/pi-permission-system",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "31.0.0",
|
|
4
4
|
"description": "Permission enforcement extension for the Pi coding agent.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -91,6 +91,7 @@
|
|
|
91
91
|
"verify:public-types": "bash scripts/verify-public-types.sh",
|
|
92
92
|
"measure:core-coverage": "node scripts/measure-core-coverage.mjs",
|
|
93
93
|
"measure:statement-descent": "node scripts/measure-statement-descent.mjs",
|
|
94
|
+
"measure:statement-operands": "node scripts/measure-statement-operands.mjs",
|
|
94
95
|
"measure:wrapper-transparency": "node scripts/measure-wrapper-transparency.mjs",
|
|
95
96
|
"lint:md": "rumdl check *.md docs/**/*.md",
|
|
96
97
|
"lint": "biome check . && eslint . && pnpm run lint:md"
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
} from "#src/access-intent/bash/node-text";
|
|
12
12
|
import type { TSNode } from "#src/access-intent/bash/parser";
|
|
13
13
|
import { redirectEffectForDestination } from "#src/access-intent/bash/redirect-analysis";
|
|
14
|
-
import type
|
|
14
|
+
import { type TokenEffect, UNPROVEN_EFFECT } from "#src/access-intent/effect";
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* A collected path-candidate token paired with the effect its position proved.
|
|
@@ -29,7 +29,8 @@ export interface PathToken {
|
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* Recursively visit the AST and collect resolved text of nodes that
|
|
32
|
-
* represent command arguments or
|
|
32
|
+
* represent command arguments, redirect destinations, or a statement's own
|
|
33
|
+
* path operands.
|
|
33
34
|
*
|
|
34
35
|
* Reads no text from `heredoc_body`, `heredoc_end`, or `comment` subtrees, but
|
|
35
36
|
* still descends an execution host for the commands it hosts — an interpolating
|
|
@@ -46,6 +47,12 @@ export interface PathToken {
|
|
|
46
47
|
export function collectPathCandidateTokens(node: TSNode): PathToken[] {
|
|
47
48
|
if (node.type === "command") return collectCommandTokens(node);
|
|
48
49
|
if (node.type === "file_redirect") return collectRedirectTokens(node);
|
|
50
|
+
if (node.type === "for_statement") {
|
|
51
|
+
return collectStatementOperandTokens(node, "after-in");
|
|
52
|
+
}
|
|
53
|
+
if (node.type === "case_statement") {
|
|
54
|
+
return collectStatementOperandTokens(node, "before-in");
|
|
55
|
+
}
|
|
49
56
|
if (EXECUTION_HOST_TYPES.has(node.type)) {
|
|
50
57
|
return collectHostedExecutionTokens(node);
|
|
51
58
|
}
|
|
@@ -139,6 +146,70 @@ function collectHostedExecutionTokens(node: TSNode): PathToken[] {
|
|
|
139
146
|
return tokens;
|
|
140
147
|
}
|
|
141
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Which side of a statement's `in` keyword carries its path operands.
|
|
151
|
+
*
|
|
152
|
+
* A `for`/`select` word list follows `in`; a `case` subject precedes it.
|
|
153
|
+
*/
|
|
154
|
+
type OperandSide = "before-in" | "after-in";
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Collect the tokens of a statement that names its own path operands, rather
|
|
158
|
+
* than reaching them through a command.
|
|
159
|
+
*
|
|
160
|
+
* A path in a `for`/`select` word list or a `case` subject is a child of the
|
|
161
|
+
* statement node, so the command and redirect collectors never see it and the
|
|
162
|
+
* loop body cannot recover it — `for f in /etc/shadow; do cat $f; done` carries
|
|
163
|
+
* the literal only here, and ADR 0009 declines to resolve the body's `$f`
|
|
164
|
+
* (#839).
|
|
165
|
+
*
|
|
166
|
+
* The two statements ask one question with one parameter — which side of the
|
|
167
|
+
* anonymous `in` keyword is the operand side — so the walk is named here once
|
|
168
|
+
* rather than spelled twice, as `COMMAND_PREFIX_TYPES` is for the two command
|
|
169
|
+
* walkers.
|
|
170
|
+
*
|
|
171
|
+
* Three properties carry the design:
|
|
172
|
+
*
|
|
173
|
+
* 1. A non-operand child falls through to the ordinary recursion, not to
|
|
174
|
+
* nothing. That is what keeps the `do_group` reaching the loop body's
|
|
175
|
+
* commands; searching it for hosted executions alone would silently drop
|
|
176
|
+
* every ordinary body command.
|
|
177
|
+
* 2. An operand-side child outside {@link ARG_NODE_TYPES} falls through the
|
|
178
|
+
* same way, so a bare substitution in the word list is descended for its
|
|
179
|
+
* command as before and its operands keep that command's own attribution
|
|
180
|
+
* (#807) instead of the statement's.
|
|
181
|
+
* 3. An operand-side argument node is read *and* searched for hosted
|
|
182
|
+
* executions, since a `concatenation` can be both — the pairing
|
|
183
|
+
* {@link collectRedirectTokens} already performs on a destination.
|
|
184
|
+
*
|
|
185
|
+
* The token carries {@link UNPROVEN_EFFECT}: no command word owns it and no
|
|
186
|
+
* redirect operator names it, so neither proof source can speak and the gates
|
|
187
|
+
* consult both directional surfaces.
|
|
188
|
+
*/
|
|
189
|
+
function collectStatementOperandTokens(
|
|
190
|
+
node: TSNode,
|
|
191
|
+
operandSide: OperandSide,
|
|
192
|
+
): PathToken[] {
|
|
193
|
+
const tokens: PathToken[] = [];
|
|
194
|
+
let seenIn = false;
|
|
195
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
196
|
+
const child = node.child(i);
|
|
197
|
+
if (!child) continue;
|
|
198
|
+
if (!child.isNamed) {
|
|
199
|
+
if (child.type === "in") seenIn = true;
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
const side: OperandSide = seenIn ? "after-in" : "before-in";
|
|
203
|
+
if (side !== operandSide || !ARG_NODE_TYPES.has(child.type)) {
|
|
204
|
+
tokens.push(...collectPathCandidateTokens(child));
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
tokens.push({ token: resolveNodeText(child), effect: UNPROVEN_EFFECT });
|
|
208
|
+
tokens.push(...collectHostedExecutionTokens(child));
|
|
209
|
+
}
|
|
210
|
+
return tokens;
|
|
211
|
+
}
|
|
212
|
+
|
|
142
213
|
/**
|
|
143
214
|
* Extract the command name from a `command` node.
|
|
144
215
|
* Returns the basename (e.g. `/usr/bin/sed` → `sed`), or undefined
|