Files
my-pi/pi-permission-system/docs/plans/archive/0073-wildcard-dotall-multiline.md
T

89 lines
5.2 KiB
Markdown

---
issue: 73
issue_title: "node -e command triggers permission prompt despite \"*\": \"allow\" global fallback"
---
# Wildcard matcher fails on multiline bash commands
## Problem Statement
When a bash command contains newlines (e.g., `node -e "\nimport(...)\n"`), the wildcard matcher's compiled regex `/^.*$/` fails because `.` does not match `\n` by default in JavaScript.
This causes `evaluate()` to find no matching rule — not even the universal `{ surface: "*", pattern: "*" }` catch-all — and fall through to the hard-coded `"ask"` default, producing a spurious permission prompt.
## Goals
- Make `wildcardMatch` (and all callers via `compileWildcardPattern`) correctly match values containing newline characters.
- Zero behavior change for single-line values — this is a strict bug fix.
## Non-Goals
- Changing the permission evaluation model or rule precedence.
- Addressing shell-quote tokenization (covered by #72, already landed).
- Adding multiline-aware pattern syntax (e.g., `**` for newlines) — the existing `*` should simply match everything including newlines.
## Background
- **Permission surface**: `bash` (but the fix is in the generic wildcard module, affecting all surfaces).
- **Module**: `src/wildcard-matcher.ts``compileWildcardPattern()` builds a `RegExp` by escaping literal segments and joining with `.*`.
The regex uses no flags, so `.` does not match `\n`.
- **Call path**: `evaluate()` in `src/rule.ts` calls `wildcardMatch(rule.pattern, value)` for both `surface` and `pattern` fields.
A multiline bash command is the `value` argument to the pattern match.
## Design Overview
Add the `s` (dotAll) flag to the regex constructed in `compileWildcardPattern`.
With `dotAll`, `.` matches any character including line terminators (`\n`, `\r`, `\u2028`, `\u2029`).
```typescript
return {
pattern,
state,
regex: new RegExp(`^${escaped}$`, "s"),
};
```
This is the minimal, correct fix.
No new types, no data-shape changes, no config changes.
## Module-Level Changes
| File | Change |
| ---------------------------------- | --------------------------------------------------------------------------------- |
| `src/wildcard-matcher.ts` | Add `"s"` flag to `new RegExp(...)` in `compileWildcardPattern()`. |
| `tests/wildcard-matcher.test.ts` | Add test cases for multiline values matching `*` and literal-prefix-`*` patterns. |
| `tests/permission-manager.test.ts` | Add integration test: multiline bash command with `"*": "allow"` returns allow. |
## TDD Order
1. **test: multiline value fails to match wildcard `*` pattern (red)**
Add tests in `tests/wildcard-matcher.test.ts`:
- `wildcardMatch("*", "line1\nline2")` → expected `true`
- `wildcardMatch("node *", "node -e \"\nfoo\n\"")` → expected `true`
- `compileWildcardPattern("*", "allow").regex.test("a\nb")` → expected `true`
Commit: `test: cover multiline values in wildcardMatch`
2. **feat: add dotAll flag to wildcard regex** In `src/wildcard-matcher.ts`, change `new RegExp(...)` to include `"s"` flag.
Tests from step 1 go green.
Commit: `fix: add dotAll flag so wildcard`*`matches newlines (#73)`
3. **test: integration — multiline bash command resolves to allow**
Add a test in `tests/permission-manager.test.ts`:
- Config: `{ "*": "allow", bash: { "rm -rf *": "deny" } }`
- `checkPermission("bash", { command: "node -e \"\nimport(...)\n\"" })``state: "allow"`
Commit: `test: multiline bash command resolves allow via universal fallback`
## Risks and Mitigations
| Risk | Mitigation |
| --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Could this silently weaken a permission? | No. The `s` flag only makes `*` match what users already expect it to match. A pattern like `rm -rf *` still only matches strings starting with `rm -rf` — it does not gain the ability to match unrelated multiline strings because the literal prefix anchors it. |
| Over-broad match for patterns containing literal newlines | No user would put literal `\n` in a pattern string in JSON config. The patterns are single-line strings; only values (commands) can be multiline. |
| Breaks existing tests | Existing tests use single-line values. Adding `s` has no effect on strings without newlines. |
## Open Questions
None — the fix is unambiguous.