@principles/pd-cli 1.135.0 → 1.135.1
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/dist/commands/legacy-cleanup.d.ts.map +1 -1
- package/dist/commands/legacy-cleanup.js +19 -2
- package/dist/commands/legacy-cleanup.js.map +1 -1
- package/dist/commands/pain-evidence.d.ts +3 -1
- package/dist/commands/pain-evidence.d.ts.map +1 -1
- package/dist/commands/pain-evidence.js +12 -3
- package/dist/commands/pain-evidence.js.map +1 -1
- package/dist/commands/rulecode.d.ts +13 -0
- package/dist/commands/rulecode.d.ts.map +1 -1
- package/dist/commands/rulecode.js +23 -2
- package/dist/commands/rulecode.js.map +1 -1
- package/dist/resolve-workspace.d.ts.map +1 -1
- package/dist/resolve-workspace.js +33 -12
- package/dist/resolve-workspace.js.map +1 -1
- package/dist/services/console-launcher.d.ts +8 -1
- package/dist/services/console-launcher.d.ts.map +1 -1
- package/dist/services/console-launcher.js +45 -3
- package/dist/services/console-launcher.js.map +1 -1
- package/dist/services/pd-config-loader.d.ts.map +1 -1
- package/dist/services/pd-config-loader.js +34 -1
- package/dist/services/pd-config-loader.js.map +1 -1
- package/dist/services/quality-scorecard/strong-model-gate.d.ts +19 -0
- package/dist/services/quality-scorecard/strong-model-gate.d.ts.map +1 -1
- package/dist/services/quality-scorecard/strong-model-gate.js +44 -2
- package/dist/services/quality-scorecard/strong-model-gate.js.map +1 -1
- package/dist/utils/path-security.d.ts +60 -0
- package/dist/utils/path-security.d.ts.map +1 -0
- package/dist/utils/path-security.js +90 -0
- package/dist/utils/path-security.js.map +1 -0
- package/package.json +1 -1
- package/src/commands/legacy-cleanup.ts +19 -2
- package/src/commands/pain-evidence.ts +11 -3
- package/src/commands/rulecode.ts +25 -2
- package/src/resolve-workspace.ts +41 -17
- package/src/services/console-launcher.ts +45 -3
- package/src/services/pd-config-loader.ts +35 -1
- package/src/services/quality-scorecard/strong-model-gate.ts +44 -2
- package/src/utils/path-security.ts +96 -0
- package/tests/commands/legacy-cleanup.test.ts +148 -0
- package/tests/commands/pain-evidence.test.ts +37 -0
- package/tests/commands/pri-393-runtime-config-unification.test.ts +5 -1
- package/tests/commands/product-path-regression.test.ts +9 -4
- package/tests/commands/rulecode.test.ts +135 -0
- package/tests/commands/runtime-diagnostics-export.test.ts +6 -2
- package/tests/resolve-workspace.test.ts +21 -0
- package/tests/services/console-launcher.test.ts +114 -0
- package/tests/services/pd-config-loader.test.ts +8 -1
- package/tests/services/quality-scorecard/strong-model-gate.test.ts +133 -0
- package/tests/utils/path-security.test.ts +180 -0
|
@@ -14,6 +14,7 @@ import * as fs from 'fs';
|
|
|
14
14
|
import * as path from 'path';
|
|
15
15
|
import * as os from 'os';
|
|
16
16
|
import * as yaml from 'js-yaml';
|
|
17
|
+
import { canonicalPath, isPathInside } from '../utils/path-security.js';
|
|
17
18
|
import { validatePdConfig, computeEffectivePdConfig, computeFeatureFlagsFromConfig, redactPdConfig, } from '@principles/core/runtime-v2';
|
|
18
19
|
// ── Constants ────────────────────────────────────────────────────────────────
|
|
19
20
|
export const PD_CONFIG_DIR = '.pd';
|
|
@@ -230,6 +231,31 @@ function loadOpenClawPluginConfig() {
|
|
|
230
231
|
}
|
|
231
232
|
return null;
|
|
232
233
|
}
|
|
234
|
+
/**
|
|
235
|
+
* Validate a candidate config directory before it is joined into a config
|
|
236
|
+
* path for filesystem reads. Candidate dirs are operator-supplied
|
|
237
|
+
* (PD_WORKSPACE_DIR / plugin config / home default), so we normalize with
|
|
238
|
+
* `path.normalize` (pure string, no filesystem access), then reject empty,
|
|
239
|
+
* parent-traversal, or root paths. This keeps the subsequent
|
|
240
|
+
* `path.join(dir, PD_CONFIG_DIR, ...)` inside the intended directory
|
|
241
|
+
* boundary (CWE-22 mitigation).
|
|
242
|
+
*
|
|
243
|
+
* Platform note: no `path.isAbsolute` check — absolute-ness is
|
|
244
|
+
* platform-dependent (a Windows-style path is not absolute on POSIX
|
|
245
|
+
* runners) and relative dirs resolve inside cwd without traversal risk.
|
|
246
|
+
*/
|
|
247
|
+
function assertConfigDirBoundary(dir, source) {
|
|
248
|
+
if (!dir || dir.trim().length === 0) {
|
|
249
|
+
throw new Error(`Invalid config search dir (${source}): path is empty`);
|
|
250
|
+
}
|
|
251
|
+
const normalized = path.normalize(dir);
|
|
252
|
+
if (normalized.split(/[\\/]/).includes('..')) {
|
|
253
|
+
throw new Error(`Invalid config search dir (${source}): "${dir}" contains parent traversal`);
|
|
254
|
+
}
|
|
255
|
+
if (normalized === path.parse(normalized).root) {
|
|
256
|
+
throw new Error(`Invalid config search dir (${source}): "${dir}" resolves to filesystem root`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
233
259
|
/**
|
|
234
260
|
* Search known locations for a .pd/config.yaml that contains a workspace.default field.
|
|
235
261
|
* This runs BEFORE workspace resolution and does NOT require knowing the workspace dir.
|
|
@@ -256,7 +282,14 @@ export function discoverWorkspaceDefault() {
|
|
|
256
282
|
}
|
|
257
283
|
// Search each candidate for .pd/config.yaml with workspace.default
|
|
258
284
|
for (const { dir, source } of candidates) {
|
|
259
|
-
|
|
285
|
+
assertConfigDirBoundary(dir, source);
|
|
286
|
+
// CWE-22: resolve the candidate root once, then verify the joined config
|
|
287
|
+
// path stays inside that root before any filesystem access.
|
|
288
|
+
const candidateRoot = canonicalPath(dir);
|
|
289
|
+
const configPath = path.resolve(candidateRoot, PD_CONFIG_DIR, PD_CONFIG_FILENAME);
|
|
290
|
+
if (!isPathInside(candidateRoot, configPath)) {
|
|
291
|
+
throw new Error(`Invalid config search dir (${source}): "${dir}" escapes its boundary`);
|
|
292
|
+
}
|
|
260
293
|
if (fs.existsSync(configPath)) {
|
|
261
294
|
const workspaceDefault = extractWorkspaceDefault(configPath);
|
|
262
295
|
if (workspaceDefault) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pd-config-loader.js","sourceRoot":"","sources":["../../src/services/pd-config-loader.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"pd-config-loader.js","sourceRoot":"","sources":["../../src/services/pd-config-loader.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,6 +5,25 @@
|
|
|
5
5
|
* to parse responses — no unsafe casts.
|
|
6
6
|
*/
|
|
7
7
|
import type { PainEpisode, LocalEvaluation, StrongModelAdjudication, AdjudicationStatus } from '@principles/core/quality-scorecard';
|
|
8
|
+
/**
|
|
9
|
+
* CWE-918 (SSRF) mitigation for operator-supplied LLM API base URLs.
|
|
10
|
+
*
|
|
11
|
+
* Threat model: OPENAI_BASE_URL is explicitly configured by the Owner /
|
|
12
|
+
* operator in the environment or Runtime Profile — it is trusted operator
|
|
13
|
+
* configuration, NOT untrusted remote input. Local and private-network
|
|
14
|
+
* OpenAI-compatible endpoints (llama.cpp, LM Studio, local gateways,
|
|
15
|
+
* intranet model servers) are legitimate PD runtime targets and must keep
|
|
16
|
+
* working.
|
|
17
|
+
*
|
|
18
|
+
* What stays blocked:
|
|
19
|
+
* - non-http(s) schemes (file:, javascript:, data:, ...)
|
|
20
|
+
* - malformed / unparseable URLs
|
|
21
|
+
* - credentials embedded in the URL (secrets leak into logs/errors)
|
|
22
|
+
*
|
|
23
|
+
* Callers must not allow the host to be rewritten from untrusted input
|
|
24
|
+
* after this validation; the endpoint is derived from this URL only.
|
|
25
|
+
*/
|
|
26
|
+
export declare function assertSafeLlmBaseUrl(rawBaseUrl: string): URL;
|
|
8
27
|
export declare function adjudicate(episode: PainEpisode, localEval: LocalEvaluation, config: {
|
|
9
28
|
modelId: string;
|
|
10
29
|
log: (msg: string) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"strong-model-gate.d.ts","sourceRoot":"","sources":["../../../src/services/quality-scorecard/strong-model-gate.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,WAAW,EACX,eAAe,EACf,uBAAuB,EACvB,kBAAkB,EACnB,MAAM,oCAAoC,CAAC;AAkD5C,wBAAsB,UAAU,CAC9B,OAAO,EAAE,WAAW,EACpB,SAAS,EAAE,eAAe,EAC1B,MAAM,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,GACtD,OAAO,CAAC,uBAAuB,CAAC,
|
|
1
|
+
{"version":3,"file":"strong-model-gate.d.ts","sourceRoot":"","sources":["../../../src/services/quality-scorecard/strong-model-gate.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,WAAW,EACX,eAAe,EACf,uBAAuB,EACvB,kBAAkB,EACnB,MAAM,oCAAoC,CAAC;AAkD5C;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,GAAG,CAc5D;AAED,wBAAsB,UAAU,CAC9B,OAAO,EAAE,WAAW,EACpB,SAAS,EAAE,eAAe,EAC1B,MAAM,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,GACtD,OAAO,CAAC,uBAAuB,CAAC,CA8ElC;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,uBAAuB,CAS3E;AAED,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,eAAe,EAC1B,YAAY,EAAE,uBAAuB,GAAG,IAAI,GAC3C,kBAAkB,CAOpB"}
|
|
@@ -39,10 +39,48 @@ Flags: ${localEval.flags.length > 0 ? localEval.flags.join(', ') : 'none'}
|
|
|
39
39
|
|
|
40
40
|
Do NOT output anything other than this JSON object.`;
|
|
41
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* CWE-918 (SSRF) mitigation for operator-supplied LLM API base URLs.
|
|
44
|
+
*
|
|
45
|
+
* Threat model: OPENAI_BASE_URL is explicitly configured by the Owner /
|
|
46
|
+
* operator in the environment or Runtime Profile — it is trusted operator
|
|
47
|
+
* configuration, NOT untrusted remote input. Local and private-network
|
|
48
|
+
* OpenAI-compatible endpoints (llama.cpp, LM Studio, local gateways,
|
|
49
|
+
* intranet model servers) are legitimate PD runtime targets and must keep
|
|
50
|
+
* working.
|
|
51
|
+
*
|
|
52
|
+
* What stays blocked:
|
|
53
|
+
* - non-http(s) schemes (file:, javascript:, data:, ...)
|
|
54
|
+
* - malformed / unparseable URLs
|
|
55
|
+
* - credentials embedded in the URL (secrets leak into logs/errors)
|
|
56
|
+
*
|
|
57
|
+
* Callers must not allow the host to be rewritten from untrusted input
|
|
58
|
+
* after this validation; the endpoint is derived from this URL only.
|
|
59
|
+
*/
|
|
60
|
+
export function assertSafeLlmBaseUrl(rawBaseUrl) {
|
|
61
|
+
let url;
|
|
62
|
+
try {
|
|
63
|
+
url = new URL(rawBaseUrl);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
throw new Error(`Invalid OPENAI_BASE_URL: "${rawBaseUrl}" is not a valid URL`);
|
|
67
|
+
}
|
|
68
|
+
if (url.protocol !== 'https:' && url.protocol !== 'http:') {
|
|
69
|
+
throw new Error(`Invalid OPENAI_BASE_URL: protocol must be http or https, got "${url.protocol}"`);
|
|
70
|
+
}
|
|
71
|
+
if (url.username || url.password) {
|
|
72
|
+
throw new Error(`Invalid OPENAI_BASE_URL: credentials must not be embedded in the URL`);
|
|
73
|
+
}
|
|
74
|
+
return url;
|
|
75
|
+
}
|
|
42
76
|
export async function adjudicate(episode, localEval, config) {
|
|
43
77
|
const { modelId: strongModelId, log } = config;
|
|
44
78
|
const prompt = buildAdjudicationPrompt(episode, localEval);
|
|
45
|
-
|
|
79
|
+
// CWE-918 (SSRF): validate the operator-supplied base URL before any
|
|
80
|
+
// network request — http(s) only, no embedded credentials. Local and
|
|
81
|
+
// private OpenAI-compatible endpoints remain valid (trusted operator
|
|
82
|
+
// configuration; llama.cpp / LM Studio / local gateways are supported).
|
|
83
|
+
const baseUrl = assertSafeLlmBaseUrl(process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1');
|
|
46
84
|
const apiKey = process.env.OPENAI_API_KEY;
|
|
47
85
|
if (!apiKey) {
|
|
48
86
|
return {
|
|
@@ -55,7 +93,11 @@ export async function adjudicate(episode, localEval, config) {
|
|
|
55
93
|
};
|
|
56
94
|
}
|
|
57
95
|
try {
|
|
58
|
-
|
|
96
|
+
// Build the endpoint on the validated URL: append the chat completions
|
|
97
|
+
// path to the base URL's (possibly empty) path component.
|
|
98
|
+
const endpoint = new URL(baseUrl);
|
|
99
|
+
endpoint.pathname = endpoint.pathname.replace(/\/+$/, '') + '/chat/completions';
|
|
100
|
+
const resp = await fetch(endpoint.toString(), {
|
|
59
101
|
method: 'POST',
|
|
60
102
|
headers: {
|
|
61
103
|
'Content-Type': 'application/json',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"strong-model-gate.js","sourceRoot":"","sources":["../../../src/services/quality-scorecard/strong-model-gate.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAQH,OAAO,EACL,aAAa,EACb,iBAAiB,IAAI,IAAI,EACzB,iBAAiB,EACjB,4BAA4B,EAC5B,0BAA0B,GAC3B,MAAM,oCAAoC,CAAC;AAE5C,SAAS,uBAAuB,CAC9B,OAAoB,EACpB,SAA0B;IAE1B,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAC/B,KAAK,CAAC,KAAK,aAAa,CAAC,CAAC,CAAC,MAAM,SAAS,CAAC,eAAe,CAAC,CAAC,CAAC,QAAQ,SAAS,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE,CACxG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEb,OAAO;;;;;QAKD,OAAO,CAAC,SAAS;YACb,OAAO,CAAC,MAAM;gBACV,OAAO,CAAC,KAAK;cACf,OAAO,CAAC,QAAQ;aACjB,OAAO,CAAC,OAAO;+BACG,OAAO,CAAC,uBAAuB,IAAI,MAAM;uBACjD,OAAO,CAAC,gBAAgB,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM;;yBAEhF,SAAS,CAAC,KAAK;EACtC,WAAW;SACJ,SAAS,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM;;;;;;;;;;;;;;;oDAerB,CAAC;AACrD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,OAAoB,EACpB,SAA0B,EAC1B,MAAuD;IAEvD,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,GAAG,EAAE,GAAG,MAAM,CAAC;IAC/C,MAAM,MAAM,GAAG,uBAAuB,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IAC3D,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"strong-model-gate.js","sourceRoot":"","sources":["../../../src/services/quality-scorecard/strong-model-gate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path containment primitives (CWE-22 boundary guards).
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for "is this filesystem target inside that root?"
|
|
5
|
+
* across pd-cli security boundaries. All containment decisions compare
|
|
6
|
+
* CANONICAL (fully resolved) paths via `path.relative`, never by string
|
|
7
|
+
* prefix — a string `startsWith` on a possibly-relative root is wrong on
|
|
8
|
+
* two counts: (1) a relative root never prefixes an absolute target, and
|
|
9
|
+
* (2) `/work/foo` is a prefix of `/work/foobar` without being a boundary.
|
|
10
|
+
*
|
|
11
|
+
* ── Symlink policy ────────────────────────────────────────────────────────
|
|
12
|
+
* The guarantee provided here is LEXICAL containment: `path.resolve` +
|
|
13
|
+
* `path.relative`, without resolving symlinks. We deliberately do NOT
|
|
14
|
+
* `realpath` the target before containment because:
|
|
15
|
+
* 1. PD's IO roots are operator-supplied workspace directories; symlinks
|
|
16
|
+
* inside the workspace are created by the owner and treated as trusted
|
|
17
|
+
* content.
|
|
18
|
+
* 2. On Windows, junction points (worktree junctions, `node_modules`
|
|
19
|
+
* junctions) resolve to a *different physical location* via `realpath`;
|
|
20
|
+
* realpath-based containment would reject legitimate local workflows
|
|
21
|
+
* (e.g. a worktree whose `node_modules` is junctioned to the main
|
|
22
|
+
* checkout).
|
|
23
|
+
* If a future caller must constrain the physical read target (e.g. reading a
|
|
24
|
+
* file whose path could be a symlink to an untrusted location), that caller
|
|
25
|
+
* must realpath the target FIRST and then run containment on the resolved
|
|
26
|
+
* path — do not weaken this module's contract.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Canonicalize a user/operator-supplied path once. Every derived filesystem
|
|
30
|
+
* target must be compared against this canonical root.
|
|
31
|
+
*/
|
|
32
|
+
export declare function canonicalPath(p: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* True when `candidate` is strictly inside `parent` (canonical comparison).
|
|
35
|
+
*
|
|
36
|
+
* - Both arguments are resolved against cwd first, so relative inputs work.
|
|
37
|
+
* - `candidate === parent` returns false (strict containment). Callers that
|
|
38
|
+
* want to allow the root itself should check equality separately.
|
|
39
|
+
* - Sibling-prefix attacks (`/work/foobar` vs parent `/work/foo`) cannot
|
|
40
|
+
* pass because `path.relative` yields a non-`..`-prefixed path only for
|
|
41
|
+
* real descendants.
|
|
42
|
+
*/
|
|
43
|
+
export declare function isPathInside(parent: string, candidate: string): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Throw unless `candidate` is strictly inside `parent`. `label` names the
|
|
46
|
+
* candidate in the error message (e.g. "--workspace").
|
|
47
|
+
*/
|
|
48
|
+
export declare function assertPathInside(parent: string, candidate: string, label: string): void;
|
|
49
|
+
/**
|
|
50
|
+
* Validate an operator-supplied directory root before it is used as an IO
|
|
51
|
+
* root: rejects empty values, residual parent-traversal segments, and
|
|
52
|
+
* filesystem-root results. Returns the canonical root.
|
|
53
|
+
*
|
|
54
|
+
* No `path.isAbsolute` requirement: absolute-ness is platform-dependent (a
|
|
55
|
+
* Windows-style path like `Z:\work` is not absolute on POSIX runners) and
|
|
56
|
+
* relative paths resolve inside cwd, so they carry no traversal risk. The
|
|
57
|
+
* guards that matter are: empty, parent traversal, and filesystem root.
|
|
58
|
+
*/
|
|
59
|
+
export declare function assertSafeDirectoryRoot(input: string, label: string): string;
|
|
60
|
+
//# sourceMappingURL=path-security.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-security.d.ts","sourceRoot":"","sources":["../../src/utils/path-security.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAIH;;;GAGG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAUvE;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAIvF;AAED;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAe5E"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path containment primitives (CWE-22 boundary guards).
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for "is this filesystem target inside that root?"
|
|
5
|
+
* across pd-cli security boundaries. All containment decisions compare
|
|
6
|
+
* CANONICAL (fully resolved) paths via `path.relative`, never by string
|
|
7
|
+
* prefix — a string `startsWith` on a possibly-relative root is wrong on
|
|
8
|
+
* two counts: (1) a relative root never prefixes an absolute target, and
|
|
9
|
+
* (2) `/work/foo` is a prefix of `/work/foobar` without being a boundary.
|
|
10
|
+
*
|
|
11
|
+
* ── Symlink policy ────────────────────────────────────────────────────────
|
|
12
|
+
* The guarantee provided here is LEXICAL containment: `path.resolve` +
|
|
13
|
+
* `path.relative`, without resolving symlinks. We deliberately do NOT
|
|
14
|
+
* `realpath` the target before containment because:
|
|
15
|
+
* 1. PD's IO roots are operator-supplied workspace directories; symlinks
|
|
16
|
+
* inside the workspace are created by the owner and treated as trusted
|
|
17
|
+
* content.
|
|
18
|
+
* 2. On Windows, junction points (worktree junctions, `node_modules`
|
|
19
|
+
* junctions) resolve to a *different physical location* via `realpath`;
|
|
20
|
+
* realpath-based containment would reject legitimate local workflows
|
|
21
|
+
* (e.g. a worktree whose `node_modules` is junctioned to the main
|
|
22
|
+
* checkout).
|
|
23
|
+
* If a future caller must constrain the physical read target (e.g. reading a
|
|
24
|
+
* file whose path could be a symlink to an untrusted location), that caller
|
|
25
|
+
* must realpath the target FIRST and then run containment on the resolved
|
|
26
|
+
* path — do not weaken this module's contract.
|
|
27
|
+
*/
|
|
28
|
+
import * as path from 'node:path';
|
|
29
|
+
/**
|
|
30
|
+
* Canonicalize a user/operator-supplied path once. Every derived filesystem
|
|
31
|
+
* target must be compared against this canonical root.
|
|
32
|
+
*/
|
|
33
|
+
export function canonicalPath(p) {
|
|
34
|
+
return path.resolve(p);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* True when `candidate` is strictly inside `parent` (canonical comparison).
|
|
38
|
+
*
|
|
39
|
+
* - Both arguments are resolved against cwd first, so relative inputs work.
|
|
40
|
+
* - `candidate === parent` returns false (strict containment). Callers that
|
|
41
|
+
* want to allow the root itself should check equality separately.
|
|
42
|
+
* - Sibling-prefix attacks (`/work/foobar` vs parent `/work/foo`) cannot
|
|
43
|
+
* pass because `path.relative` yields a non-`..`-prefixed path only for
|
|
44
|
+
* real descendants.
|
|
45
|
+
*/
|
|
46
|
+
export function isPathInside(parent, candidate) {
|
|
47
|
+
const root = path.resolve(parent);
|
|
48
|
+
const target = path.resolve(candidate);
|
|
49
|
+
const rel = path.relative(root, target);
|
|
50
|
+
return (rel !== '' &&
|
|
51
|
+
rel !== '..' &&
|
|
52
|
+
!rel.startsWith(`..${path.sep}`) &&
|
|
53
|
+
!path.isAbsolute(rel));
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Throw unless `candidate` is strictly inside `parent`. `label` names the
|
|
57
|
+
* candidate in the error message (e.g. "--workspace").
|
|
58
|
+
*/
|
|
59
|
+
export function assertPathInside(parent, candidate, label) {
|
|
60
|
+
if (!isPathInside(parent, candidate)) {
|
|
61
|
+
throw new Error(`Invalid ${label}: "${candidate}" is outside "${parent}"`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Validate an operator-supplied directory root before it is used as an IO
|
|
66
|
+
* root: rejects empty values, residual parent-traversal segments, and
|
|
67
|
+
* filesystem-root results. Returns the canonical root.
|
|
68
|
+
*
|
|
69
|
+
* No `path.isAbsolute` requirement: absolute-ness is platform-dependent (a
|
|
70
|
+
* Windows-style path like `Z:\work` is not absolute on POSIX runners) and
|
|
71
|
+
* relative paths resolve inside cwd, so they carry no traversal risk. The
|
|
72
|
+
* guards that matter are: empty, parent traversal, and filesystem root.
|
|
73
|
+
*/
|
|
74
|
+
export function assertSafeDirectoryRoot(input, label) {
|
|
75
|
+
if (!input || input.trim().length === 0) {
|
|
76
|
+
throw new Error(`Invalid ${label}: path is empty`);
|
|
77
|
+
}
|
|
78
|
+
// Un-normalized `..` segments that survive normalize() mean the input
|
|
79
|
+
// escaped a parent boundary (e.g. "..\\..\\evil") — reject rather than
|
|
80
|
+
// trust them. Foldable segments ("a/../b") canonicalize safely.
|
|
81
|
+
if (path.normalize(input).split(/[\\/]/).includes('..')) {
|
|
82
|
+
throw new Error(`Invalid ${label}: "${input}" contains parent traversal`);
|
|
83
|
+
}
|
|
84
|
+
const root = canonicalPath(input);
|
|
85
|
+
if (root === path.parse(root).root) {
|
|
86
|
+
throw new Error(`Invalid ${label}: "${input}" resolves to filesystem root`);
|
|
87
|
+
}
|
|
88
|
+
return root;
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=path-security.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-security.js","sourceRoot":"","sources":["../../src/utils/path-security.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,CAAS;IACrC,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AACzB,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,YAAY,CAAC,MAAc,EAAE,SAAiB;IAC5D,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAClC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACvC,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACxC,OAAO,CACL,GAAG,KAAK,EAAE;QACV,GAAG,KAAK,IAAI;QACZ,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC,GAAG,EAAE,CAAC;QAChC,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CACtB,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAc,EAAE,SAAiB,EAAE,KAAa;IAC/E,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CAAC,WAAW,KAAK,MAAM,SAAS,iBAAiB,MAAM,GAAG,CAAC,CAAC;IAC7E,CAAC;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,uBAAuB,CAAC,KAAa,EAAE,KAAa;IAClE,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,WAAW,KAAK,iBAAiB,CAAC,CAAC;IACrD,CAAC;IACD,sEAAsE;IACtE,uEAAuE;IACvE,gEAAgE;IAChE,IAAI,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACxD,MAAM,IAAI,KAAK,CAAC,WAAW,KAAK,MAAM,KAAK,6BAA6B,CAAC,CAAC;IAC5E,CAAC;IACD,MAAM,IAAI,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAClC,IAAI,IAAI,KAAK,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CAAC,WAAW,KAAK,MAAM,KAAK,+BAA+B,CAAC,CAAC;IAC9E,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/package.json
CHANGED
|
@@ -27,6 +27,7 @@ import * as path from 'path';
|
|
|
27
27
|
import * as os from 'os';
|
|
28
28
|
import type { Database } from 'better-sqlite3';
|
|
29
29
|
import { RuntimeStateManager } from '@principles/core/runtime-v2';
|
|
30
|
+
import { assertSafeDirectoryRoot, isPathInside } from '../utils/path-security.js';
|
|
30
31
|
|
|
31
32
|
// ── Types ────────────────────────────────────────────────────────────────────
|
|
32
33
|
|
|
@@ -257,9 +258,21 @@ function glob(pattern: string): string[] {
|
|
|
257
258
|
return results;
|
|
258
259
|
}
|
|
259
260
|
|
|
261
|
+
/**
|
|
262
|
+
* Validate a workspace root before cleanup scans derive paths from it.
|
|
263
|
+
* Returns the canonical root so all derived targets are absolute and
|
|
264
|
+
* containment checks are canonical-vs-canonical (relative workspaces work).
|
|
265
|
+
*/
|
|
266
|
+
function assertCleanupWorkspaceRoot(workspacePath: string): string {
|
|
267
|
+
return assertSafeDirectoryRoot(workspacePath, 'workspace path');
|
|
268
|
+
}
|
|
269
|
+
|
|
260
270
|
function findLegacyTargets(workspacePath: string): CleanupTarget[] {
|
|
261
271
|
const targets: CleanupTarget[] = [];
|
|
262
|
-
|
|
272
|
+
// CWE-22: resolve the workspace root once and verify it is a valid,
|
|
273
|
+
// non-root directory so every derived path stays inside the boundary.
|
|
274
|
+
const workspaceRoot = assertCleanupWorkspaceRoot(workspacePath);
|
|
275
|
+
const stateDir = path.join(workspaceRoot, '.state');
|
|
263
276
|
const archiveTimestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
264
277
|
const archiveDir = path.join(stateDir, 'legacy-archive', archiveTimestamp);
|
|
265
278
|
|
|
@@ -288,7 +301,11 @@ function findLegacyTargets(workspacePath: string): CleanupTarget[] {
|
|
|
288
301
|
if (fs.existsSync(sessionsDir)) {
|
|
289
302
|
for (const file of fs.readdirSync(sessionsDir)) {
|
|
290
303
|
if (!file.endsWith('.json')) continue;
|
|
291
|
-
const filePath = path.
|
|
304
|
+
const filePath = path.resolve(sessionsDir, file);
|
|
305
|
+
// CWE-22: verify the canonical path stays inside the canonical
|
|
306
|
+
// sessions dir before any filesystem access (readdir results are
|
|
307
|
+
// trusted, but defense-in-depth).
|
|
308
|
+
if (!isPathInside(sessionsDir, filePath)) continue;
|
|
292
309
|
try {
|
|
293
310
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
294
311
|
const session = JSON.parse(content);
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import * as fs from 'fs';
|
|
20
20
|
import * as path from 'path';
|
|
21
21
|
import { resolveWorkspaceDir } from '../resolve-workspace.js';
|
|
22
|
+
import { assertSafeDirectoryRoot, isPathInside } from '../utils/path-security.js';
|
|
22
23
|
|
|
23
24
|
interface EvidenceOptions {
|
|
24
25
|
workspace?: string;
|
|
@@ -40,11 +41,14 @@ interface TriggerDecisionEntry {
|
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
/**
|
|
43
|
-
* Get the memory/logs directory for a workspace.
|
|
44
|
+
* Get the memory/logs directory for a workspace (canonical root).
|
|
44
45
|
* SystemLogger writes to <workspace>/memory/logs/SYSTEM_YYYY-MM-DD.log.
|
|
46
|
+
* The workspace root is canonicalized once here so downstream containment
|
|
47
|
+
* checks compare canonical paths (relative --workspace inputs work).
|
|
45
48
|
*/
|
|
46
49
|
function getLogDir(workspaceDir: string): string {
|
|
47
|
-
|
|
50
|
+
const root = assertSafeDirectoryRoot(workspaceDir, 'workspace path');
|
|
51
|
+
return path.join(root, 'memory', 'logs');
|
|
48
52
|
}
|
|
49
53
|
|
|
50
54
|
/**
|
|
@@ -108,7 +112,11 @@ function readRecentDecisions(logDir: string, limit: number): TriggerDecisionEntr
|
|
|
108
112
|
for (const logFile of logFiles) {
|
|
109
113
|
if (allEntries.length >= limit) break;
|
|
110
114
|
|
|
111
|
-
|
|
115
|
+
// CWE-22: verify the canonical log path stays inside the canonical
|
|
116
|
+
// logDir before reading (canonical-vs-canonical containment, so relative
|
|
117
|
+
// workspace roots work).
|
|
118
|
+
const filePath = path.resolve(logDir, logFile);
|
|
119
|
+
if (!isPathInside(logDir, filePath)) continue;
|
|
112
120
|
try {
|
|
113
121
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
114
122
|
const entries = parseTriggerDecisions(content);
|
package/src/commands/rulecode.ts
CHANGED
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
import * as path from 'node:path';
|
|
32
32
|
import * as fs from 'node:fs';
|
|
33
33
|
import type { Command } from 'commander';
|
|
34
|
+
import { isPathInside } from '../utils/path-security.js';
|
|
34
35
|
import {
|
|
35
36
|
RULECODE_SPEC_TEXT,
|
|
36
37
|
checkForbiddenPatterns,
|
|
@@ -113,11 +114,33 @@ function isGoldenTraceCaseInput(value: unknown): value is GoldenTraceCaseInput {
|
|
|
113
114
|
/**
|
|
114
115
|
* Load and validate golden trace cases from a JSON file.
|
|
115
116
|
* Returns either the validated cases or a structured error.
|
|
117
|
+
* Exported for boundary regression tests (internal test surface).
|
|
116
118
|
*/
|
|
117
|
-
function loadGoldenTraceCases(
|
|
119
|
+
export function loadGoldenTraceCases(
|
|
120
|
+
filePath: string,
|
|
121
|
+
workspaceDir?: string,
|
|
122
|
+
): { cases?: GoldenTraceCaseInput[]; error?: { reason: string; nextAction: string } } {
|
|
118
123
|
let raw: string;
|
|
119
124
|
try {
|
|
125
|
+
if (!filePath || filePath.trim().length === 0) {
|
|
126
|
+
throw new Error('golden trace path is empty');
|
|
127
|
+
}
|
|
120
128
|
const resolved = path.resolve(filePath);
|
|
129
|
+
// CWE-22 boundary: canonical containment against the workspace root
|
|
130
|
+
// (rejects sibling-prefix and traversal escapes; relative workspaces
|
|
131
|
+
// canonicalize consistently). Filesystem-root targets are rejected by
|
|
132
|
+
// containment when a workspace root is supplied.
|
|
133
|
+
const normalized = path.normalize(resolved);
|
|
134
|
+
if (normalized.split(/[\\/]/).includes('..')) {
|
|
135
|
+
throw new Error('golden trace path contains parent traversal');
|
|
136
|
+
}
|
|
137
|
+
if (workspaceDir) {
|
|
138
|
+
if (!isPathInside(workspaceDir, resolved)) {
|
|
139
|
+
throw new Error('golden trace path must be inside the workspace directory');
|
|
140
|
+
}
|
|
141
|
+
} else if (normalized === path.parse(normalized).root) {
|
|
142
|
+
throw new Error('golden trace path resolves to filesystem root');
|
|
143
|
+
}
|
|
121
144
|
raw = fs.readFileSync(resolved, 'utf8');
|
|
122
145
|
} catch (err) {
|
|
123
146
|
const reason = err instanceof Error ? err.message : String(err);
|
|
@@ -286,7 +309,7 @@ export async function handleRulecodeReplay(opts: ReplayOptions): Promise<void> {
|
|
|
286
309
|
return;
|
|
287
310
|
}
|
|
288
311
|
|
|
289
|
-
const traceResult = loadGoldenTraceCases(opts.goldenTrace);
|
|
312
|
+
const traceResult = loadGoldenTraceCases(opts.goldenTrace, opts.workspace);
|
|
290
313
|
if (traceResult.error || traceResult.cases === undefined) {
|
|
291
314
|
const { error } = traceResult;
|
|
292
315
|
const output: RulecodeReplayOutput = {
|
package/src/resolve-workspace.ts
CHANGED
|
@@ -15,15 +15,24 @@
|
|
|
15
15
|
|
|
16
16
|
import * as path from 'path';
|
|
17
17
|
import { discoverWorkspaceDefault } from './services/pd-config-loader.js';
|
|
18
|
+
import { assertSafeDirectoryRoot } from './utils/path-security.js';
|
|
18
19
|
|
|
19
20
|
/** Environment variable name for workspace directory. */
|
|
20
21
|
export const WORKSPACE_ENV = 'PD_WORKSPACE_DIR';
|
|
21
22
|
|
|
22
23
|
// ── Internal helpers ────────────────────────────────────────────────────────
|
|
23
24
|
|
|
24
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* Normalize a path to forward slashes for cross-platform string comparison.
|
|
27
|
+
*
|
|
28
|
+
* Comparison-only helper: it never resolves against the filesystem and never
|
|
29
|
+
* feeds a filesystem operation, so it intentionally uses `path.normalize`
|
|
30
|
+
* (pure string normalization) instead of `path.resolve`. Callers compare two
|
|
31
|
+
* paths for equality after normalization; the workspace root itself is
|
|
32
|
+
* validated by `assertWorkspaceDirInside` before any IO uses it.
|
|
33
|
+
*/
|
|
25
34
|
function normalizePath(p: string): string {
|
|
26
|
-
return path.
|
|
35
|
+
return path.normalize(p).replace(/\\/g, '/');
|
|
27
36
|
}
|
|
28
37
|
|
|
29
38
|
/** Emit workspace warnings to stderr. */
|
|
@@ -31,6 +40,31 @@ function emitWarning(msg: string): void {
|
|
|
31
40
|
process.stderr.write(`[PD:workspace] WARNING: ${msg}\n`);
|
|
32
41
|
}
|
|
33
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Validate an operator-supplied workspace root before it is used as an IO
|
|
45
|
+
* root. Delegates to the shared canonical-root validator (empty, parent
|
|
46
|
+
* traversal, filesystem root). Returns nothing; callers keep the original
|
|
47
|
+
* value so downstream resolution semantics are unchanged.
|
|
48
|
+
*/
|
|
49
|
+
function assertWorkspaceDirInside(p: string, source: string): void {
|
|
50
|
+
assertSafeDirectoryRoot(p, source);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Emit a warning when an explicit override differs from config default. */
|
|
54
|
+
function emitWarningIfDiffers(
|
|
55
|
+
override: string,
|
|
56
|
+
configDefault: string | undefined,
|
|
57
|
+
discovered: { configPath?: string } | null,
|
|
58
|
+
): void {
|
|
59
|
+
if (configDefault && normalizePath(override) !== normalizePath(configDefault)) {
|
|
60
|
+
emitWarning(
|
|
61
|
+
`"${override}" differs from config default "${configDefault}" ` +
|
|
62
|
+
`(source: ${discovered?.configPath}). Using explicit override. ` +
|
|
63
|
+
`Consider updating workspace.default in config.`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
34
68
|
// ── Public API ──────────────────────────────────────────────────────────────
|
|
35
69
|
|
|
36
70
|
/**
|
|
@@ -46,34 +80,24 @@ export function resolveWorkspaceDir(workspaceDir?: string): string {
|
|
|
46
80
|
|
|
47
81
|
// Step 2: Check --workspace flag (highest priority)
|
|
48
82
|
if (workspaceDir) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
`--workspace "${workspaceDir}" differs from config default "${configDefault}" ` +
|
|
52
|
-
`(source: ${discovered.configPath}). Using explicit flag. ` +
|
|
53
|
-
`Consider updating workspace.default in config.`,
|
|
54
|
-
);
|
|
55
|
-
}
|
|
83
|
+
assertWorkspaceDirInside(workspaceDir, '--workspace');
|
|
84
|
+
emitWarningIfDiffers(workspaceDir, configDefault, discovered);
|
|
56
85
|
return workspaceDir;
|
|
57
86
|
}
|
|
58
87
|
|
|
59
88
|
// Step 3: Check PD_WORKSPACE_DIR env var
|
|
60
89
|
const envWorkspace = process.env.PD_WORKSPACE_DIR?.trim();
|
|
61
90
|
if (envWorkspace) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
`PD_WORKSPACE_DIR "${envWorkspace}" differs from config default "${configDefault}" ` +
|
|
65
|
-
`(source: ${discovered.configPath}). Using env var. ` +
|
|
66
|
-
`Consider aligning or updating workspace.default.`,
|
|
67
|
-
);
|
|
68
|
-
}
|
|
91
|
+
assertWorkspaceDirInside(envWorkspace, WORKSPACE_ENV);
|
|
92
|
+
emitWarningIfDiffers(envWorkspace, configDefault, discovered);
|
|
69
93
|
return envWorkspace;
|
|
70
94
|
}
|
|
71
95
|
|
|
72
96
|
// Step 4: Use discovered config default
|
|
73
97
|
if (configDefault) {
|
|
98
|
+
assertWorkspaceDirInside(configDefault, 'workspace.default');
|
|
74
99
|
return configDefault;
|
|
75
100
|
}
|
|
76
|
-
|
|
77
101
|
// Step 5: No resolution possible — throw (preserves current behavior)
|
|
78
102
|
throw new Error(
|
|
79
103
|
'No workspace directory configured. Set --workspace <path>, ' +
|