Skip to content

windows: narrow the profile read grant to a listed set - #767

Merged
ig-ant merged 19 commits into
mainfrom
ig/windows-profile-include-list
Nov 9, 2026
Merged

ig-ant merged 19 commits into
mainfrom
ig/windows-profile-include-list

Conversation

@ig-ant

@ig-ant ig-ant commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Narrows the Windows profile read grant from "the whole profile minus exclusions" to a built-in list of tool directories, because 0.0.81 left Documents, Desktop and most of AppData readable to sandboxed commands. initialize() now upgrades an older grant by itself, revoking what left the list.

Summary

  • Unlisted directories get no read access, though parents of exclusions stay listable. srt-win profile grant --include adds paths. Tools only in scoop, .local, uv's folders or elsewhere under .config stop working unless added. Links are not followed.
  • LIST_VERSION goes to 4. initialize() refreshes an older grant and finishes any interrupted one, as the user, with a 30-minute limit and an optional progress callback. It never re-grants after an interrupted forget, a newer record or a different profile.
  • Each revoke is recorded before any walk, checked across the tree, and read back before the grant reads current. One that can't finish warns with profile_stale_allow and is retried; that path may stay readable until then.
  • reset() cancels a running initialize(), and an overlapping call with a different config is rejected.

Accepted trade-off: .m2 is readable whole, including Maven's master password files, so sandboxed code can decrypt passwords stored in settings.xml. Otherwise those builds would run outside the sandbox.

Test plan

CI passes on all platforms, including smoke-profile-read.ps1. On Windows 11 ARM64, an earlier revision upgraded a real 0.0.81 grant: 487 entries removed, judged from the ACLs, in about 70 s, and no run ended current with an unwarned entry. The latest fixes have only run in CI.

Known: forget can leave an inert inherited deny. A write root under a folder with inherited RESTRICTED:(M) loses per-session isolation; that also affects main.

@ig-ant
ig-ant marked this pull request as ready for review October 22, 2026 20:14
ig-ant added 19 commits November 9, 2026 17:55
The walk no longer grants a directory whole unless it is beneath an
inclusion: a built-in list of where per-user tools install themselves
and what they need to run, plus what configuration adds. A directory on
the way to an inclusion or an exclusion stays a container, so the way
down to both is open; any other directory gets no entry at all, and a
session can still open it with a read grant of its own. Files in a
container are granted only inside an inclusion; the files of the
profile root are still granted by name. An inclusion follows a link as
an exclusion does. The list version goes from 3 to 4.

include_refusal says which paths may not be added as inclusions: an
exclusion, anything inside one, the temporary directory or anything
inside it.
`srt-win profile grant --include <PATH>` (repeatable; `--replace-includes`
drops the ones earlier runs recorded) adds a directory or file of the
profile to what the grant makes readable. An inclusion that is an
exclusion, lies inside one, or is or lies inside the temporary directory
is refused; one recorded by an earlier run is looked at again, as the
built-in exclusions may have grown since. The added inclusions are kept
in the marker (ProfileInclude) and reported by `profile status` as
`extra_inclusions`. grant() takes its added paths as one `Added`.

The library passes `include` / `replaceInclusions` (WindowsProfileGrantOptions)
and `profileInclude` (the install, `--profile-include`), reads
`extraInclusions` from the status, and its notice says what the grant
now opens. The README section is rewritten for the listed shape.
`.local` stands for a listed directory and `Documents` for one the list
does not name: the rows that read, deny and write under "a granted
child" use the former; the new R5 shows the latter gets no entry and is
still open to a session's own read grant; N and E use a new child of a
listed container rather than a new top-level directory; I exercises
`--include`, `--replace-includes` and the refusals. The notice test of
the library reads the new wording.
… tree

A failed or partial walk beneath a directory no longer reads as revoked, and
empty directories the grant made are removed when they leave it. Objects that
no longer exist are dropped, and unreachable volumes are retried and reported.
A release that narrows the list now takes effect at the next session start,
and a session does not start while anything it was to revoke stays readable.
…e retried

A refresh that finishes starts the session and lists those paths as a warning.
Each later session start tries them again, on those paths alone.
A revoke killed mid-walk is now finished by the next run, and a repeated
failure stays reported. Retries at session start are bounded and no longer
keep a starting session out. scoop, .local and uv leave the read list.
A killed profile grant records every planned path before writing any. A path
it never wrote and cannot write stayed recorded as granted, which made later
session read denies fail on it. Check the object's access list before keeping
such a row.
mvn expands MAVEN_ARGS unquoted, so literal quotes reached Maven as part of the
path. A path that needs them now uses its 8.3 name, or the variable is withheld.
…ad grant

It unlocks the passwords settings.xml stores encrypted, so the file is denied
and MAVEN_ARGS no longer points at it.
… done

Each revoke is now recorded before any walk and cleared only after the whole
tree is checked, so an interrupted or failing run cannot lose its warning or
its retry. A first grant that was cut short is no longer finished automatically.
An interrupted first install no longer leaves every session refused until the
install is run again. An interrupted forget is still never re-granted.
A folder excluded and then included again kept warning about an entry that was
now the one wanted.
New folders under Programs now get a list entry, and their files their own.
…sion ends

A folder under Programs goes back to a list entry, not an inheriting one.
… closed

Paths that left the grant are re-read from their access lists before it reads
as current, and a refresh runs under initialize()'s guard with the install's
30-minute limit. Only .config\git and .config\pip stay readable under .config.
…takeown

icacls /setowner cannot open a file that denies reading its access list, so the
row's clean-up failed and the grant rightly kept reporting the entry.
… and .m2 is read whole

Status now reads pending revokes, so copies left beneath a directory keep their
warning. A reset() kills a running refresh, and a conflicting initialize() is
refused. Maven's master password files are readable by design again.
The library's signal handler now cancels initialize(), so the run ended with
that cancellation's message instead of naming the signal.
@ig-ant
ig-ant force-pushed the ig/windows-profile-include-list branch from 96b3baa to bff04eb Compare November 9, 2026 22:55
@ig-ant
ig-ant enabled auto-merge (squash) November 9, 2026 22:59
@ig-ant
ig-ant merged commit f5db5a2 into main Nov 9, 2026
16 checks passed
@ig-ant
ig-ant deleted the ig/windows-profile-include-list branch November 9, 2026 23:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants