Hard-deny: PowerShell patterns + drift-proof the label list

Four new compiled-in hard-deny rules covering PowerShell + cmd.exe
catastrophic patterns (mirror of the POSIX 10):

- Remove-Item / del / rd / ri / rm / erase / rmdir targeting C:\
  or user home / appdata
- Format-Volume / Clear-Disk with any flag (= an invocation, not a
  Get-Help lookup)
- iwr | iex pipe form (PowerShell web-to-execute)
- iex (irm ...) parenthesized form

Universal application — no shell-aware scoping yet. PS cmdlet
identifiers are distinctive enough that bash false-positives are
vanishingly unlikely. Shell-aware policy scoping remains a known
follow-up.

Drift-proof the "Always blocked" label list: backend now exposes
hard_deny_rules() via a new mcp_hard_deny_labels Tauri command, and
PolicyTab loads it at mount instead of hardcoding the list. Avoids
the 11→15 manual sync that would have been needed (and that had
already drifted twice this week).

cargo test --lib: 138 passed; 0 failed (118 prior + 20 new fuzz
cases for rules 11-14; hard_deny_rules_count bumped 10 → 14).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
megaproxy 2026-05-26 17:14:42 +01:00
parent f3ab54252e
commit 5b970f8b48
6 changed files with 264 additions and 16 deletions

View file

@ -52,6 +52,32 @@ Durable memory for this project. Read at session start, update before session en
## Session log
### 2026-05-26 — Hard-deny: PowerShell patterns + label list de-duplicated
Mirrors the POSIX hard-deny rules with their Windows/PowerShell equivalents. Four new patterns:
1. **`Remove-Item` / `del` / `rd` / `ri` / `rm` / `erase` / `rmdir` targeting `C:\` / `~` / `$HOME` / `$env:USERPROFILE` / `$env:APPDATA`.** Covers the canonical `Remove-Item -Recurse -Force C:\` along with bare `del C:\` and `rd /S /Q ~`. PS aliases vary per environment so the alternation is wide.
2. **`Format-Volume` / `Clear-Disk` with any flag.** Bare cmdlet mentions (e.g. `Get-Help Format-Volume`) are fine; presence of `-DriveLetter` / `-Number` / similar means an actual invocation.
3. **`iwr|iex` pipe form** — `Invoke-WebRequest`/`Invoke-RestMethod`/`iwr`/`irm`/`curl.exe` piped into `Invoke-Expression`/`iex`. The PS web-to-execute primitive. (`curl` in PS land is an alias for `Invoke-WebRequest` which doesn't pipe-string into anything bash-like; the actual `curl.exe` binary does, hence the literal `curl\.exe`.)
4. **`iex (irm ...)` parenthesized form.** More common than the pipe form in real install one-liners.
**Universal application — no shell-aware policy scoping yet.** PS cmdlet names (`Remove-Item`, `Format-Volume`, `iwr`, `iex`) are distinctive enough that a bash session triggering one is virtually impossible. The "scope rules by `shellKind` of the target pane" work is a known follow-up but doesn't block this.
**Label list de-duplicated.** `PolicyTab.tsx` previously hardcoded the 10 POSIX labels. Adding PS rules would have forced updating both sides — and the comment in the new `mcp_hard_deny_labels` Tauri command notes it had already drifted from the backend twice this week. Now: backend is the SoT, frontend calls `mcpHardDenyLabels()` at panel mount. "Always blocked" section now renders all 14 labels live from the backend.
**Tests:** 20 new fuzz cases (Rule 1114), 3-5 positive + 1-2 negative each. `hard_deny_rules_count` bumped from 10 → 14. **138 passed; 0 failed** on Windows.
**Notes for next time someone adds a hard-deny pattern:**
- Update only `HARD_DENY_PATTERNS` and `hard_deny_rules_count`. The UI list auto-syncs via the Tauri command. README's mention of "10 patterns" is now also drift-prone but lower-stakes.
- PowerShell cmdlets are identified with `-` in the middle (`Remove-Item`). `\bRemove-Item\b` works because the `\b` anchors are between word and non-word chars (R/string-start, m/non-word-after) — the `-` in the middle is fine.
- Common PS quoting forms not yet caught (filed as follow-up if it bites): single-quoted paths (`Remove-Item -Recurse -Force 'C:\'`) and trailing flags after the path (`Remove-Item -Recurse -Force C:\ -Confirm:$false`). The regex anchor requires path → whitespace → end/operator/comment; flag-after-path doesn't fit. Common attacker copy-paste forms put the path last, so this is real-world-fine.
Open follow-ups specific to this session:
- **Shell-aware policy scoping.** Today PS rules apply universally (low false-positive risk but architecturally fuzzy). Per-leaf-shellKind discrimination would let users `Allow write_pane(*) on bash` while still gating PS. Memory'd long-standing follow-up.
- **README drift.** README's "10 hard-deny patterns" mention is stale. Either remove the count or rewrite to enumerate via a build-time script. Low priority.
### 2026-05-26 — Hard-deny rework: fix latent enforcement gaps surfaced by PR-4
Re-enabling the policy test module in PR-4 (the `policy_with` compile fix) exposed **16 pre-existing test failures**. Triaged: 2 wrong assertions, 14 real bugs. Fixed all in one focused pass on `mcp_policy.rs`.