pi-crew 0.9.35 → 0.9.36
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 +29 -0
- package/dist/build-meta.json +6 -6
- package/dist/index.mjs +5 -2
- package/dist/index.mjs.map +2 -2
- package/package.json +1 -1
- package/src/runtime/async-runner.ts +1 -1
- package/src/runtime/verification-gates.ts +9 -4
- package/src/state/instinct-store.ts +5 -0
- package/src/workflows/intermediate-store.ts +5 -0
package/package.json
CHANGED
|
@@ -210,7 +210,7 @@ export const BACKGROUND_RUNNER_ENV_ALLOWLIST: string[] = [
|
|
|
210
210
|
// Phase 1.5: worker-thread atomic writer opt-in (RFC 15).
|
|
211
211
|
"PI_CREW_WORKER_ATOMIC_WRITER",
|
|
212
212
|
"PI_TEAMS_WORKER_ATOMIC_WRITER",
|
|
213
|
-
// Phase 1.5 #1: verification env sanitization opt-
|
|
213
|
+
// Phase 1.5 #1: verification env sanitization opt-out (RFC 13 §6).
|
|
214
214
|
"PI_CREW_VERIFICATION_SANITIZE_ENV",
|
|
215
215
|
"PI_TEAMS_VERIFICATION_SANITIZE_ENV",
|
|
216
216
|
"PI_CREW_VERIFICATION_PRESERVE_ENV",
|
|
@@ -25,8 +25,9 @@ import { redactSecretString } from "../utils/redaction.ts";
|
|
|
25
25
|
* against adversarial workers; this kills the leak at the source by never
|
|
26
26
|
* giving the verification process the secret in the first place.
|
|
27
27
|
*
|
|
28
|
-
* Opt-
|
|
29
|
-
*
|
|
28
|
+
* Opt-out via `PI_CREW_VERIFICATION_SANITIZE_ENV=0` or
|
|
29
|
+
* `PI_TEAMS_VERIFICATION_SANITIZE_ENV=0` to disable sanitization (e.g. for
|
|
30
|
+
* tests that legitimately need API access). Escape hatch:
|
|
30
31
|
* `PI_CREW_VERIFICATION_PRESERVE_ENV=KEY1,KEY2,...` lets users explicitly opt
|
|
31
32
|
* specific secrets back in (audited via the allowlist validator).
|
|
32
33
|
*/
|
|
@@ -62,9 +63,13 @@ const VERIFICATION_ENV_ALLOWLIST: readonly string[] = [
|
|
|
62
63
|
"NPM_CONFIG_GLOBALCONFIG",
|
|
63
64
|
];
|
|
64
65
|
|
|
65
|
-
/** Whether env sanitization for verification is enabled (
|
|
66
|
+
/** Whether env sanitization for verification is enabled (opt-out: enabled by default). */
|
|
66
67
|
export function isVerificationEnvSanitizeEnabled(): boolean {
|
|
67
|
-
|
|
68
|
+
// Opt-out: explicitly disabled via env var set to '0'.
|
|
69
|
+
if (process.env.PI_CREW_VERIFICATION_SANITIZE_ENV === "0" || process.env.PI_TEAMS_VERIFICATION_SANITIZE_ENV === "0") {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
return true;
|
|
68
73
|
}
|
|
69
74
|
|
|
70
75
|
/**
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
+
import { assertSafePathId } from "../utils/safe-paths.ts";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Represents a learned instinct that guides agent behavior.
|
|
@@ -120,6 +121,7 @@ export class InstinctStore {
|
|
|
120
121
|
if (!savedInstinct.projectId) {
|
|
121
122
|
throw new Error("Project-scoped instinct requires a projectId");
|
|
122
123
|
}
|
|
124
|
+
assertSafePathId("projectId", savedInstinct.projectId);
|
|
123
125
|
this.appendInstinctToFile(this.getProjectInstinctPath(savedInstinct.projectId), savedInstinct);
|
|
124
126
|
}
|
|
125
127
|
|
|
@@ -163,6 +165,7 @@ export class InstinctStore {
|
|
|
163
165
|
* @returns Array of instincts for the project
|
|
164
166
|
*/
|
|
165
167
|
getProjectInstincts(projectId: string): Instinct[] {
|
|
168
|
+
assertSafePathId("projectId", projectId);
|
|
166
169
|
const projectInstincts = this.readInstinctsFromFile(this.getProjectInstinctPath(projectId));
|
|
167
170
|
const globalInstincts = this.readInstinctsFromFile(this.getGlobalInstinctPath());
|
|
168
171
|
return [...projectInstincts, ...globalInstincts];
|
|
@@ -183,6 +186,7 @@ export class InstinctStore {
|
|
|
183
186
|
.readdirSync(projectsDir, { withFileTypes: true })
|
|
184
187
|
.filter((e) => e.isDirectory())
|
|
185
188
|
.map((e) => e.name)) {
|
|
189
|
+
assertSafePathId("projectId", projectId);
|
|
186
190
|
const filePath = path.join(projectsDir, projectId, INSTINCT_FILE);
|
|
187
191
|
const instincts = this.readInstinctsFromFile(filePath);
|
|
188
192
|
const index = instincts.findIndex((i) => i.id === instinctId);
|
|
@@ -239,6 +243,7 @@ export class InstinctStore {
|
|
|
239
243
|
.readdirSync(projectsDir, { withFileTypes: true })
|
|
240
244
|
.filter((e) => e.isDirectory())
|
|
241
245
|
.map((e) => e.name)) {
|
|
246
|
+
assertSafePathId("projectId", projectId);
|
|
242
247
|
const filePath = path.join(projectsDir, projectId, INSTINCT_FILE);
|
|
243
248
|
instincts = this.readInstinctsFromFile(filePath);
|
|
244
249
|
index = instincts.findIndex((i) => i.id === instinctId);
|
|
@@ -56,6 +56,11 @@ export function ensureIntermediateDir(config: Partial<IntermediateStoreConfig> =
|
|
|
56
56
|
* @returns Path to the written file
|
|
57
57
|
*/
|
|
58
58
|
export function writeIntermediate(config: Partial<IntermediateStoreConfig>, phase: string, stepId: string, data: unknown): string {
|
|
59
|
+
// Security: validate phase/stepId before building the filename to prevent
|
|
60
|
+
// path traversal via poisoned phase or stepId.
|
|
61
|
+
if (!isSafePathId(phase) || !isSafePathId(stepId)) {
|
|
62
|
+
throw new Error("Invalid phase or stepId for intermediate store");
|
|
63
|
+
}
|
|
59
64
|
const dir = ensureIntermediateDir(config);
|
|
60
65
|
const filename = `${phase}-${stepId}.json`;
|
|
61
66
|
const filePath = path.join(dir, filename);
|