pi-codex-marketplace 0.6.0 → 0.6.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-codex-marketplace",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "Bridge Package for Codex and Claude Marketplace compatibility in Pi — 極簡 /codex-marketplace 純文字指令(add/list/install/update/disable/enable/remove/forget)、單一 Global Bridge State、當下最新安裝與即時投影",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -25,10 +25,10 @@ export function containedPathSyntax(p: string): PathSyntax {
25
25
  if (p.includes('\\')) return { ok: false, reason: 'backslash' };
26
26
  if (isAbsolute(p)) return { ok: false, reason: 'absolute path' };
27
27
  if (!p.startsWith('./')) return { ok: false, reason: 'not ./ relative' };
28
- const segments = p.split('/');
29
- // "./x" -> segments ['', '.', 'x']; a bare "." or ".." component anywhere is rejected
30
- const rest = segments.slice(1);
31
- for (const seg of rest) {
28
+ if (p === './') return { ok: true };
29
+ // The mandatory "./" prefix is not a path component; validate only the declared remainder.
30
+ const segments = p.slice(2).split('/');
31
+ for (const seg of segments) {
32
32
  if (seg === '' || seg === '.' || seg === '..') {
33
33
  return { ok: false, reason: `dot or parent segment '${seg}'` };
34
34
  }
@@ -214,7 +214,14 @@ function walkTree(
214
214
  return { fileCount, totalBytes, budgetBlocked };
215
215
  }
216
216
 
217
- function checkSymlinkContainment(canonicalRoot: string, entries: SnapshotEntry[], findings: ValidationFinding[]): void {
217
+ function checkSymlinkContainment(root: string, entries: SnapshotEntry[], findings: ValidationFinding[]): void {
218
+ // Canonicalize the owning root once before building the prefix — the raw root may itself
219
+ // contain symlink components (macOS TMPDIR: /var → /private/var), which would make every
220
+ // realpath'd target look like it escapes (same pattern as resolveContained in contained.ts).
221
+ let canonicalRoot = root;
222
+ try {
223
+ canonicalRoot = realpathSync.native(root);
224
+ } catch {}
218
225
  for (const e of entries) {
219
226
  if (e.type !== 'symlink') continue;
220
227
  const abs = join(canonicalRoot, ...e.relPath.split('/'));
@@ -246,6 +253,28 @@ function checkSymlinkContainment(canonicalRoot: string, entries: SnapshotEntry[]
246
253
  outcome: `symlink target '${canonical}' escapes owning root`,
247
254
  }),
248
255
  );
256
+ continue;
257
+ }
258
+ // CONTEXT: Contained Symlink — canonical target must be a regular file or directory;
259
+ // special-file targets (FIFO/socket/device) are Blocking Findings (same rule as
260
+ // resolveContained in contained.ts). statSync follows the symlink.
261
+ let st: ReturnType<typeof statSync> | undefined;
262
+ try {
263
+ st = statSync(abs);
264
+ } catch {
265
+ continue; // vanished between walk and check; broken/looping already handled above
266
+ }
267
+ if (!st.isFile() && !st.isDirectory()) {
268
+ findings.push(
269
+ blocking({
270
+ code: CODE.CONTAINED_SYMLINK_VIOLATION,
271
+ phase: 'validation',
272
+ target: 'source',
273
+ pointer: e.relPath,
274
+ rule: RULE.CONTAINED_SYMLINK_VIOLATION,
275
+ outcome: `contained symlink target is a special file: '${canonical}' (${st.isFIFO() ? 'fifo' : 'other'})`,
276
+ }),
277
+ );
249
278
  }
250
279
  }
251
280
  }