feat: vendor permission system source

This commit is contained in:
云服务部-叶林立
2026-08-19 14:35:19 +08:00
parent 198584daf8
commit 410c50a3e5
809 changed files with 157793 additions and 139 deletions
@@ -0,0 +1,127 @@
---
issue: 122
issue_title: "piInfrastructureReadPaths doesn't support glob patterns (**), causing confusing fallback to external_directory"
---
# Glob support for `piInfrastructureReadPaths`
## Problem Statement
`piInfrastructureReadPaths` only supports exact directory prefixes via `isPathWithinDirectory()`.
Users who configure glob patterns (e.g. `/opt/homebrew/**/@earendil-works/pi-coding-agent/**`) see silent failures — the path never matches, and reads fall through to the `external_directory` gate.
The docs say the field "supports `~`", but even `~` expansion is missing for plain entries because `path-utils.ts` never calls `expandHomePath()`.
## Goals
- Support `*` and `?` wildcards in `piInfrastructureReadPaths` entries, using the existing `wildcardMatch()` semantics (where `*` matches any characters including `/`).
- Fix `~` expansion for plain (non-glob) directory entries.
- Update schema description, docs, and example config to reflect glob support.
- Fully backward-compatible — plain directory entries keep prefix-match behavior.
## Non-Goals
- Adding true globstar (`**` vs `*` distinction) — the existing wildcard matcher treats them identically, which is fine for path matching.
- Changing the `wildcardMatch()` implementation itself.
- Glob support for the static `piInfrastructureDirs` (computed internally, always absolute paths).
## Background
The infrastructure read bypass is wired as follows:
1. `PermissionSession.getInfrastructureReadPaths()` returns `config.piInfrastructureReadPaths ?? []`.
2. `PermissionGateHandler` merges these with `session.getInfrastructureDirs()` into a single `infraDirs` array.
3. `describeExternalDirectoryGate()` passes `infraDirs` to `isPiInfrastructureRead()`.
4. `isPiInfrastructureRead()` iterates `infrastructureDirs` and calls `isPathWithinDirectory()` — pure prefix matching.
The existing `wildcardMatch()` in `src/wildcard-matcher.ts` already handles `*` (any characters), `?` (one character), and `~`/`$HOME` expansion.
It is used by the rule evaluator for `external_directory` permission patterns, which is why the reporter's workaround with `external_directory` rules worked.
The original plan (archive/0048) explicitly deferred glob support as an open question: "Starting with directory prefixes (simpler).
Globs can be added later if needed."
This issue is that "later."
## Design Overview
The change is contained in `isPiInfrastructureRead()`.
For each entry in `infrastructureDirs`, detect whether it contains glob characters (`*` or `?`).
If it does, use `wildcardMatch(entry, normalizedPath)`.
If it does not, expand `~` via `expandHomePath()` and use the existing `isPathWithinDirectory()` prefix match.
```typescript
import { expandHomePath } from "./expand-home";
import { wildcardMatch } from "./wildcard-matcher";
function containsGlobChars(value: string): boolean {
return value.includes("*") || value.includes("?");
}
// Inside isPiInfrastructureRead, the loop becomes:
for (const dir of infrastructureDirs) {
if (containsGlobChars(dir)) {
if (wildcardMatch(dir, normalizedPath)) return true;
} else {
if (isPathWithinDirectory(normalizedPath, expandHomePath(dir))) return true;
}
}
```
This is backward-compatible: entries without glob characters behave identically (prefix match), with the bonus that `~` now expands correctly.
Entries with glob characters get full wildcard matching.
The `containsGlobChars` helper is a private function — not exported, no new module.
## Module-Level Changes
| File | Change |
| -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `src/path-utils.ts` | Import `expandHomePath` and `wildcardMatch`. Add private `containsGlobChars()`. Update the loop in `isPiInfrastructureRead()` to branch on glob detection. |
| `schemas/permissions.schema.json` | Update `piInfrastructureReadPaths` description: remove "no globs" caveat, document `*` and `?` support. |
| `docs/configuration.md` | Update the `piInfrastructureReadPaths` row to mention wildcard support and add an example. |
| `tests/pi-infrastructure-read.test.ts` | Add tests for glob patterns matching, glob patterns not matching, `~` expansion in plain entries, and mixed glob + plain entries. |
| `tests/path-utils.test.ts` | Add corresponding `isPiInfrastructureRead` glob tests in the existing describe block. |
## Test Impact Analysis
1. The new glob branch in `isPiInfrastructureRead()` enables unit tests that were previously impossible: verifying that `*` and `?` patterns in infrastructure dirs match versioned/nested paths.
2. No existing tests become redundant — they all exercise the plain-directory branch which is preserved.
3. All existing `isPiInfrastructureRead` tests must stay as-is; they verify the prefix-match path still works.
## TDD Order
1. **test: glob patterns in `isPiInfrastructureRead`**
Add failing tests to `tests/pi-infrastructure-read.test.ts`:
- Glob entry `/opt/homebrew/*/@earendil-works/pi-coding-agent/*` matches a versioned path.
- Glob entry with `**` behaves the same as `*` (matches across `/`).
- Glob entry that doesn't match returns false.
- `?` in a glob entry matches exactly one character.
- Mixed array of plain dirs and glob patterns — both branches work.
- Plain entry with `~` prefix now matches (currently broken).
- Write tool with a glob-matching path is still rejected (read-only guard).
2. **feat: support glob patterns in `piInfrastructureReadPaths`**
Update `src/path-utils.ts`:
- Import `expandHomePath` from `./expand-home` and `wildcardMatch` from `./wildcard-matcher`.
- Add private `containsGlobChars()` helper.
- Update the `infrastructureDirs` loop in `isPiInfrastructureRead()` to branch on glob detection.
Commit: `feat: support glob patterns in piInfrastructureReadPaths (#122)`
3. **test: add glob coverage to `path-utils.test.ts`** Add a few representative glob tests to the `isPiInfrastructureRead` describe block in `tests/path-utils.test.ts` to ensure both test files cover the feature.
Commit: `test: add glob infra-read coverage to path-utils tests`
4. **docs: update schema and docs for glob support**
- Update `schemas/permissions.schema.json`: change `piInfrastructureReadPaths` description to document `*`, `?`, and `~` support.
- Update `docs/configuration.md`: revise the `piInfrastructureReadPaths` row and add a glob example.
Commit: `docs: document piInfrastructureReadPaths glob support (#122)`
## Risks and Mitigations
| Risk | Mitigation |
| ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Overly broad glob pattern (e.g. `/*`) silently auto-allows reads everywhere | This is a user-configured field with the same trust model as `external_directory: allow` rules. The review log already records every `infrastructure_auto_allowed` bypass, making over-broad patterns visible. |
| `wildcardMatch` treats `*` as `.*` (crosses `/`), so `*` and `**` are identical | This matches the existing wildcard semantics used everywhere else in the permission system. Document it clearly — users don't need to learn a different globbing dialect. |
| Plain entries with `~` were silently broken before this change | Adding `expandHomePath()` to the non-glob branch fixes this as a side effect. Backward-compatible because previously `~` entries simply never matched. |
| `containsGlobChars` false-positive on literal `*` or `?` in a path | Filesystem paths virtually never contain literal `*` or `?`. On macOS/Linux these characters are legal but extremely rare and strongly discouraged. The risk is negligible. |
## Open Questions
- None — the design reuses existing infrastructure (`wildcardMatch`, `expandHomePath`) with minimal new code.