Commit76357cd6Recorded2 Sep 2026Repositorysigil-system

Preserve the trailing-comma exec allowlist, and cover it in the corpus

Message

Measured against 944b328: exec:allowlist:git, used to grant git, and under the migration it RAISED, which every consumer that guards grant-add! turns into silently dropping the whole grant. A stray comma would have turned git from permitted into denied.

The profile now drops empty commands from an allowlist rather than refusing the spec. (sigil grant) still refuses "" outright, which is right for the algebra; the profile owns its own tolerance for a configuration artifact. The empty entry never granted anything reachable (argv[0] is never ""), so this preserves the authority the old engine actually gave and refuses only what was already unreachable.

THE DIFFERENTIAL CORPUS MISSED THIS, and that is the more important half. It passed 460 of 460 while this divergence sat in exactly the shape it did not contain: the corpus had exec:allowlist:ls,git but no trailing comma, no leading comma, no doubled comma and no comma inside a command name. A corpus that omits the awkward spelling of a real config agrees for the wrong reason. All five shapes are in it now.

Also sets the sigil floor to 0.21.0, the release that ships (sigil grant). That was the one line gating this branch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DYn8wxSdjveEgK9NRxFcNA

Changed
 package.sgl                          |  2 +-
 scripts/grant-differential-probe.sgl | 11 +++++++++++
 src/sigil/system/grant.sgl           | 30 ++++++++++++++++++++++++++++++
 3 files changed, 42 insertions(+), 1 deletion(-)
Diff
package.sglmodified
@@ -8,7 +8,7 @@
8
(package
9
name: "sigil-system"
10
version: "0.2.0"
−11
sigil: "0.20"
+11
sigil: "0.21"
12
description: "System capability library: filesystem, processes, and PTY sessions behind a grant-checked surface"
13
url: "https://codeberg.org/sigil/sigil-system"
14
license: "BSD-3-Clause"
scripts/grant-differential-probe.sglmodified
@@ -30,6 +30,17 @@
30
(cons "full-then-al" (list "exec:full" "exec:allowlist:ls"))
31
(cons "al-then-full" (list "exec:allowlist:ls" "exec:full"))
32
(cons "two-allow" (list "exec:allowlist:ls" "exec:allowlist:git"))
+33
;; Configuration artifacts. The corpus MISSED these on the first
+34
;; pass and the differential passed 460 of 460 while a real
+35
;; divergence sat in exactly this shape: a trailing comma turned
+36
;; `git` from permitted into denied. A corpus that omits the
+37
;; awkward spelling of a real config is a corpus that agrees for
+38
;; the wrong reason.
+39
(cons "trailing-comma" (list "exec:allowlist:git,"))
+40
(cons "leading-comma" (list "exec:allowlist:,git"))
+41
(cons "double-comma" (list "exec:allowlist:git,,ls"))
+42
(cons "only-commas" (list "exec:allowlist:,"))
+43
(cons "comma-in-cmd" (list "exec:allowlist:a,b"))
44
(cons "fs-ro-root" (list (string-append "fs:ro:" ROOT)))
45
(cons "fs-rw-root" (list (string-append "fs:rw:" ROOT)))
46
(cons "fs-rw-allow" (list (string-append "fs:rw:" (path-join ROOT "allow"))))
src/sigil/system/grant.sglmodified
@@ -208,6 +208,36 @@
208
(error "grant-add!: fs grant path does not exist" path))
209
(add-facet! g (list 'fs (if (string=? mode "rw") 'rw 'ro) root)))))
210
+211
;; exec:allowlist:<cmd>,<cmd> — DROP EMPTY COMMANDS RATHER THAN
+212
;; REFUSE THE SPEC. This is a compatibility decision with a
+213
;; measurement behind it, not a softening.
+214
;;
+215
;; `(sigil grant)` refuses an empty command outright, which is
+216
;; right for the algebra: "" is not a command and a grant should
+217
;; not carry one. But a TRAILING COMMA is an ordinary
+218
;; configuration artifact, and `exec:allowlist:git,` splits to
+219
;; ("git" ""). Passing that straight through raises, and every
+220
;; consumer that wraps `grant-add!` in a guard then silently
+221
;; drops the WHOLE grant. Measured against 944b328:
+222
;;
+223
;; "exec:allowlist:git," before: add=ok exec "git" => #t
+224
;; after: add=RAISED exec "git" => #f
+225
;;
+226
;; A stray comma would have turned `git` from permitted into
+227
;; denied, silently. The empty entry never granted anything
+228
;; reachable (argv[0] is never ""), so dropping it preserves the
+229
;; authority the old engine actually gave and refuses only what
+230
;; was already unreachable.
+231
((and (>= (length parts) 3)
+232
(string=? (car parts) "exec")
+233
(string=? (cadr parts) "allowlist"))
+234
(let ((cmds (list-remove (lambda (c) (= (string-length c) 0))
+235
(string-split (caddr parts) ","))))
+236
;; An allowlist that is empty after dropping blanks grants
+237
;; nothing, which is what the old engine did with it too.
+238
(unless (null? cmds)
+239
(add-facet! g (list 'exec 'allowlist cmds)))))
+240
241
;; The kinds this profile ENFORCES are the only ones it admits.
242
;; `spec->facet` would also accept net/import/eval/ffi, and
243
;; storing one of those here would record authority that