---
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.
