Disk-scan guard
Part of Genshin mods. A scan from a root reads every folder on the disk, and an agent that runs one holds a core and the disk for minutes, while the file it wants has a known home. The sessions in this repository ran about six such scans in a stretch, looking for a yt-dlp, a capture and a model's package, each with a home already written down. The rule that says so is in the throughput skill; the guard enforces it at the call, so a command that breaks it never starts.
How it works
Every Bash command is read before it runs. The command is split at its separators (&&, ||, ;, |, &, a newline and parentheses) outside quotes, so a quoted argument stays whole, and in each part, every find, du, grep and ls is read with its arguments after it:
find,du: refused when any argument is a root.ls: refused when it has the capital-Rand any argument is a root (a lowercase-ris only a reverse sort).grep: refused when it recurses (-r,-R,--recursive, in any flag group) and a path argument is a root. The operand of-e,-f,--regexpor--file, attached or separate, is a pattern and never a flag or a path, and every argument after a--is positional; with a pattern flag every positional argument is a path, and otherwise the first is the pattern.
A root is /, a drive's folder such as /c or C:\, /Users, /home, a user's home folder (/Users/<name>, /c/Users/<name>, C:\Users\<name>, /home/<name>), or the home folder by name (~, $HOME, ${HOME}, $USERPROFILE). Windows spellings, a trailing slash and a trailing glob (/*, which the shell expands to the root's entries) read as the same root. A command passed as one quoted argument to a shell (bash -c "find / …"), and the body of a command substitution ($(…) or backticks, which run even inside double quotes, a $(…) body running to its closing parenthesis past any a quote holds), are read again on their own, to three levels.
A refused command is denied with a message that names the known homes, so the next attempt goes where the file is:
- the repo, searched with
rgorgit grep; ~/Esposter/genshin-parity, for game data and captures;~/.claude/genshin-persona, for the voice;- a pinned tool (FFmpeg, yt-dlp) through
resolvePinnedTool; - a package through
require.resolvefrom the package that depends on it.
A find inside the repo or inside ~/Esposter/genshin-parity is not a root, so it runs as it always did.
flowchart TD
Command[A Bash command] --> Split[Split at its separators]
Split --> Scan{A find, du, grep or ls with a root among its paths}
Scan -->|no| Run[The command runs]
Scan -->|yes| Deny[Denied, naming the known homes]
Split --> Nested{A quoted shell command or a command substitution}
Nested -->|read again on its own| Scan
What it costs
The check is a regular expression over the command's text, a few microseconds per call (3.2 µs, the median of twenty runs of a thousand calls, measured on this machine). It runs in the plugin's own process, so a call pays no start-up: a standalone script would cost about 90 ms per call here, most of it Node's start.
When it does not refuse
- A scan from anywhere that is not a root or a home folder, such as
find packages,du -sh ~/Desktop/Softwareorls -R packages. - A grep without recursion from a root, and an
ls -r(a reverse sort). - A root named as data: a commit message, or a pattern argument of
grep -r.
Key files
| File | Role |
|---|---|
packages/genshin-mods/src/services/diskScan/registerDiskScan.ts | The Bash call hook: denies a refused command and lets every other through |
packages/genshin-mods/src/services/diskScan/getDiskScanRefusal.ts | The pure check over the command's text, and the deny message |
packages/genshin-mods/src/services/diskScan/getDiskScanRefusal.test.ts | The refused and the allowed commands, one case each |
Notes
- The guard covers the Bash tool. A PowerShell command that scans a drive (
Get-ChildItem C:\ -Recurse) is not read yet. - It has no command and no switch: it runs in every session where the plugin is enabled, as the delegation guard does.
- A scan the guard misses is still caught by the machine watcher, which stops an orphaned
find,du,greporrgonce it has burnt half a minute of CPU.