@stigmer/runner 3.12.0 → 3.12.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.
Files changed (41) hide show
  1. package/dist/.build-fingerprint +1 -1
  2. package/dist/activities/execute-deep-agent/attachment-injector.d.ts +23 -8
  3. package/dist/activities/execute-deep-agent/attachment-injector.js +104 -105
  4. package/dist/activities/execute-deep-agent/attachment-injector.js.map +1 -1
  5. package/dist/activities/execute-deep-agent/prompt-builder.js +11 -1
  6. package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
  7. package/dist/activities/execute-deep-agent/setup.js +7 -3
  8. package/dist/activities/execute-deep-agent/setup.js.map +1 -1
  9. package/dist/middleware/path-normalization.d.ts +15 -2
  10. package/dist/middleware/path-normalization.js +39 -5
  11. package/dist/middleware/path-normalization.js.map +1 -1
  12. package/dist/shared/mcp-enabled-tools.d.ts +6 -2
  13. package/dist/shared/mcp-enabled-tools.js +6 -2
  14. package/dist/shared/mcp-enabled-tools.js.map +1 -1
  15. package/dist/shared/plan-mode-permissions.d.ts +46 -10
  16. package/dist/shared/plan-mode-permissions.js +56 -12
  17. package/dist/shared/plan-mode-permissions.js.map +1 -1
  18. package/dist/shared/zip-extract.d.ts +24 -6
  19. package/dist/shared/zip-extract.js +31 -90
  20. package/dist/shared/zip-extract.js.map +1 -1
  21. package/dist/shared/zip-structure.d.ts +61 -0
  22. package/dist/shared/zip-structure.js +128 -0
  23. package/dist/shared/zip-structure.js.map +1 -0
  24. package/package.json +2 -2
  25. package/src/__test-utils__/zip-fixtures.ts +206 -0
  26. package/src/activities/execute-cursor/__tests__/skill-resolver.test.ts +3 -42
  27. package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +165 -126
  28. package/src/activities/execute-deep-agent/__tests__/plan-mode-path-normalization.test.ts +246 -37
  29. package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +7 -2
  30. package/src/activities/execute-deep-agent/__tests__/subagent-plan-mode-permissions.test.ts +35 -4
  31. package/src/activities/execute-deep-agent/attachment-injector.ts +146 -142
  32. package/src/activities/execute-deep-agent/prompt-builder.ts +11 -1
  33. package/src/activities/execute-deep-agent/setup.ts +7 -3
  34. package/src/middleware/__tests__/path-normalization.test.ts +29 -3
  35. package/src/middleware/path-normalization.ts +42 -5
  36. package/src/shared/__tests__/plan-mode-permissions.test.ts +58 -0
  37. package/src/shared/__tests__/zip-extract.test.ts +106 -89
  38. package/src/shared/mcp-enabled-tools.ts +6 -2
  39. package/src/shared/plan-mode-permissions.ts +59 -12
  40. package/src/shared/zip-extract.ts +35 -117
  41. package/src/shared/zip-structure.ts +181 -0
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Workspace-relative path normalization for permission-rule-bearing graphs
3
- * (issue #429).
3
+ * (issues #429, #528).
4
4
  *
5
5
  * deepagents' permission enforcement canonicalizes tool-call paths BEFORE any
6
6
  * rule or backend runs, and its validation refuses non-absolute paths — on
@@ -20,13 +20,26 @@
20
20
  * resolves relative paths under the workspace root, so act mode keeps a
21
21
  * byte-zero delta.
22
22
  *
23
+ * It also supplies the workspace root when `ls`/`glob`/`grep` are called
24
+ * with NO path argument (issue #528). Those tools' schema default is "/" —
25
+ * the OS ROOT under the legacy backend — and the default is applied inside
26
+ * the tool, after this seam, so under the workspace read boundary a bare
27
+ * first `ls` would otherwise die with `permission denied for read on /`
28
+ * (the same first-turn degradation class #429 fixed). An EXPLICIT "/" is
29
+ * deliberately NOT rewritten: the model asked for the OS root, and the
30
+ * honest answer under plan mode's read boundary is the rules' denial —
31
+ * silently substituting workspace contents would be an answer to a
32
+ * different question.
33
+ *
23
34
  * Invariant — nothing becomes newly reachable: only paths whose resolution
24
35
  * stays INSIDE the workspace root are rewritten. Escaping relatives (`../x`)
25
36
  * and `~`-carrying paths are left raw so today's validation refusal keeps
26
37
  * speaking (a naive join would resolve `..` away and smuggle an out-of-root
27
38
  * read past validation), and absolute paths pass through byte-untouched.
28
39
  * The middleware converts false errors into correct behavior — never a
29
- * refusal into an allowance the rules didn't decide.
40
+ * refusal into an allowance the rules didn't decide. (The absent-path
41
+ * injection honors the same line: it narrows the tool's own OS-root default
42
+ * to the workspace, granting nothing the rules would refuse.)
30
43
  *
31
44
  * Tool matching is by bare built-in name, the house doctrine (an MCP server
32
45
  * is not expected to shadow a built-in name — see shared/tool-kind.ts); the
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Workspace-relative path normalization for permission-rule-bearing graphs
3
- * (issue #429).
3
+ * (issues #429, #528).
4
4
  *
5
5
  * deepagents' permission enforcement canonicalizes tool-call paths BEFORE any
6
6
  * rule or backend runs, and its validation refuses non-absolute paths — on
@@ -20,13 +20,26 @@
20
20
  * resolves relative paths under the workspace root, so act mode keeps a
21
21
  * byte-zero delta.
22
22
  *
23
+ * It also supplies the workspace root when `ls`/`glob`/`grep` are called
24
+ * with NO path argument (issue #528). Those tools' schema default is "/" —
25
+ * the OS ROOT under the legacy backend — and the default is applied inside
26
+ * the tool, after this seam, so under the workspace read boundary a bare
27
+ * first `ls` would otherwise die with `permission denied for read on /`
28
+ * (the same first-turn degradation class #429 fixed). An EXPLICIT "/" is
29
+ * deliberately NOT rewritten: the model asked for the OS root, and the
30
+ * honest answer under plan mode's read boundary is the rules' denial —
31
+ * silently substituting workspace contents would be an answer to a
32
+ * different question.
33
+ *
23
34
  * Invariant — nothing becomes newly reachable: only paths whose resolution
24
35
  * stays INSIDE the workspace root are rewritten. Escaping relatives (`../x`)
25
36
  * and `~`-carrying paths are left raw so today's validation refusal keeps
26
37
  * speaking (a naive join would resolve `..` away and smuggle an out-of-root
27
38
  * read past validation), and absolute paths pass through byte-untouched.
28
39
  * The middleware converts false errors into correct behavior — never a
29
- * refusal into an allowance the rules didn't decide.
40
+ * refusal into an allowance the rules didn't decide. (The absent-path
41
+ * injection honors the same line: it narrows the tool's own OS-root default
42
+ * to the workspace, granting nothing the rules would refuse.)
30
43
  *
31
44
  * Tool matching is by bare built-in name, the house doctrine (an MCP server
32
45
  * is not expected to shadow a built-in name — see shared/tool-kind.ts); the
@@ -38,9 +51,8 @@ import { isAbsolute, relative } from "node:path";
38
51
  import { resolveWorkspacePath } from "../shared/file-change.js";
39
52
  /**
40
53
  * The path-bearing argument of each deepagents built-in filesystem tool.
41
- * `ls`/`glob`/`grep` take a base directory `path` (defaulting to "/" when
42
- * omitted absolute, so untouched here); the file tools take `file_path`.
43
- * Confirmed against the installed deepagents' tool definitions.
54
+ * `ls`/`glob`/`grep` take a base directory `path`; the file tools take
55
+ * `file_path`. Confirmed against the installed deepagents' tool definitions.
44
56
  */
45
57
  const PATH_ARG_BY_TOOL = new Map([
46
58
  ["ls", "path"],
@@ -50,6 +62,14 @@ const PATH_ARG_BY_TOOL = new Map([
50
62
  ["write_file", "file_path"],
51
63
  ["edit_file", "file_path"],
52
64
  ]);
65
+ /**
66
+ * The tools whose path argument is optional with an OS-root ("/") schema
67
+ * default. When the model omits it, this middleware supplies the workspace
68
+ * root instead (issue #528). The file tools are deliberately excluded: an
69
+ * absent `file_path` is a genuine model error, and the tool's own input
70
+ * validation gives the better message.
71
+ */
72
+ const DIR_DEFAULTING_TOOLS = new Set(["ls", "glob", "grep"]);
53
73
  /**
54
74
  * Rewrite `raw` to its workspace-absolute form, or return undefined when the
55
75
  * value must be left untouched (absolute already, `~`-carrying, or escaping
@@ -91,6 +111,20 @@ export function createPathNormalizationMiddleware(config) {
91
111
  if (!argKey)
92
112
  return handler(request);
93
113
  const raw = request.toolCall.args[argKey];
114
+ // Absent base directory on ls/glob/grep: the middleware sees the
115
+ // model's raw args, BEFORE the tool's zod parse applies the "/"
116
+ // (OS root) schema default — so the omission must be filled here,
117
+ // where the workspace root is known. An explicit "/" is not this
118
+ // case and flows through to an honest rule denial (header doctrine).
119
+ if (raw == null && DIR_DEFAULTING_TOOLS.has(request.toolCall.name)) {
120
+ return handler({
121
+ ...request,
122
+ toolCall: {
123
+ ...request.toolCall,
124
+ args: { ...request.toolCall.args, [argKey]: rootDir },
125
+ },
126
+ });
127
+ }
94
128
  if (typeof raw !== "string")
95
129
  return handler(request);
96
130
  const normalized = normalizeWorkspacePathArg(raw, rootDir);
@@ -1 +1 @@
1
- {"version":3,"file":"path-normalization.js","sourceRoot":"","sources":["../../src/middleware/path-normalization.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAGhE;;;;;GAKG;AACH,MAAM,gBAAgB,GAAgC,IAAI,GAAG,CAAC;IAC5D,CAAC,IAAI,EAAE,MAAM,CAAC;IACd,CAAC,MAAM,EAAE,MAAM,CAAC;IAChB,CAAC,MAAM,EAAE,MAAM,CAAC;IAChB,CAAC,WAAW,EAAE,WAAW,CAAC;IAC1B,CAAC,YAAY,EAAE,WAAW,CAAC;IAC3B,CAAC,WAAW,EAAE,WAAW,CAAC;CAC3B,CAAC,CAAC;AAOH;;;;GAIG;AACH,MAAM,UAAU,yBAAyB,CACvC,GAAW,EACX,OAAe;IAEf,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,SAAS,CAAC;IAC1D,wEAAwE;IACxE,wEAAwE;IACxE,wCAAwC;IACxC,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,SAAS,CAAC;IAEnD,MAAM,EAAE,YAAY,EAAE,GAAG,oBAAoB,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;IAEnE,wEAAwE;IACxE,uEAAuE;IACvE,uEAAuE;IACvE,6BAA6B;IAC7B,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IAC5C,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,SAAS,CAAC;IAE9D,OAAO,YAAY,CAAC;AACtB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,iCAAiC,CAC/C,MAA+B;IAE/B,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC;IAE3B,OAAO;QACL,IAAI,EAAE,6BAA6B;QAEnC,YAAY,CAAC,OAAO,EAAE,OAAO;YAC3B,MAAM,MAAM,GAAG,gBAAgB,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YAC3D,IAAI,CAAC,MAAM;gBAAE,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC;YAErC,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAC1C,IAAI,OAAO,GAAG,KAAK,QAAQ;gBAAE,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC;YAErD,MAAM,UAAU,GAAG,yBAAyB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;YAC3D,IAAI,UAAU,KAAK,SAAS;gBAAE,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC;YAEtD,OAAO,OAAO,CAAC;gBACb,GAAG,OAAO;gBACV,QAAQ,EAAE;oBACR,GAAG,OAAO,CAAC,QAAQ;oBACnB,IAAI,EAAE,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE;iBACzD;aACF,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"path-normalization.js","sourceRoot":"","sources":["../../src/middleware/path-normalization.ts"],"names":[],"mappings":"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"}
@@ -21,8 +21,12 @@
21
21
  * identity space as the sub-agent McpAccess filter and the approval-policy
22
22
  * maps. An enabled name the server does not expose is warned and dropped
23
23
  * (enforce the intersection): the restriction still holds and the run
24
- * proceeds with the valid subset; apply-time validation against
25
- * discovered_capabilities is the server-side follow-up that catches typos.
24
+ * proceeds with the valid subset. The server-side half (issue #402) rejects
25
+ * such names at apply time once the referenced server has discovered
26
+ * capabilities (stigmer-server validateEnabledToolsStep /
27
+ * validateDefaultEnabledToolsStep); this runtime leniency remains the
28
+ * safety net for manifests applied before a server's first connect and for
29
+ * toolsets that changed since the last discovery.
26
30
  */
27
31
  /**
28
32
  * Compute the effective allow-list for one resolved MCP server.
@@ -21,8 +21,12 @@
21
21
  * identity space as the sub-agent McpAccess filter and the approval-policy
22
22
  * maps. An enabled name the server does not expose is warned and dropped
23
23
  * (enforce the intersection): the restriction still holds and the run
24
- * proceeds with the valid subset; apply-time validation against
25
- * discovered_capabilities is the server-side follow-up that catches typos.
24
+ * proceeds with the valid subset. The server-side half (issue #402) rejects
25
+ * such names at apply time once the referenced server has discovered
26
+ * capabilities (stigmer-server validateEnabledToolsStep /
27
+ * validateDefaultEnabledToolsStep); this runtime leniency remains the
28
+ * safety net for manifests applied before a server's first connect and for
29
+ * toolsets that changed since the last discovery.
26
30
  */
27
31
  /**
28
32
  * Compute the effective allow-list for one resolved MCP server.
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-enabled-tools.js","sourceRoot":"","sources":["../../src/shared/mcp-enabled-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CACnC,iBAAgD,EAChD,mBAAkD;IAElD,IAAI,iBAAiB,IAAI,iBAAiB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtD,OAAO,CAAC,GAAG,iBAAiB,CAAC,CAAC;IAChC,CAAC;IACD,IAAI,mBAAmB,IAAI,mBAAmB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1D,OAAO,CAAC,GAAG,mBAAmB,CAAC,CAAC;IAClC,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,yBAAyB,CACvC,IAAY,EACZ,KAAmB,EACnB,YAA2C;IAE3C,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC;IACpB,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,CAAC;IACtC,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAEhE,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACrD,MAAM,OAAO,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;IACrE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO,CAAC,IAAI,CACV,iBAAiB,IAAI,2BAA2B,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;YACtE,qEAAqE;YACrE,OAAO,QAAQ,CAAC,MAAM,sCAAsC;YAC5D,uDAAuD,CACxD,CAAC;IACJ,CAAC;IAED,OAAO,CAAC,GAAG,CACT,iBAAiB,IAAI,8BAA8B,KAAK,CAAC,MAAM,GAAG;QAClE,yBAAyB,QAAQ,CAAC,MAAM,EAAE,CAC3C,CAAC;IAEF,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAA6D;IAE7D,MAAM,MAAM,GAA6B,EAAE,CAAC;IAC5C,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;YACxB,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC;QACjD,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"mcp-enabled-tools.js","sourceRoot":"","sources":["../../src/shared/mcp-enabled-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CACnC,iBAAgD,EAChD,mBAAkD;IAElD,IAAI,iBAAiB,IAAI,iBAAiB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtD,OAAO,CAAC,GAAG,iBAAiB,CAAC,CAAC;IAChC,CAAC;IACD,IAAI,mBAAmB,IAAI,mBAAmB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1D,OAAO,CAAC,GAAG,mBAAmB,CAAC,CAAC;IAClC,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,yBAAyB,CACvC,IAAY,EACZ,KAAmB,EACnB,YAA2C;IAE3C,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC;IACpB,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,CAAC;IACtC,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAEhE,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACrD,MAAM,OAAO,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;IACrE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO,CAAC,IAAI,CACV,iBAAiB,IAAI,2BAA2B,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;YACtE,qEAAqE;YACrE,OAAO,QAAQ,CAAC,MAAM,sCAAsC;YAC5D,uDAAuD,CACxD,CAAC;IACJ,CAAC;IAED,OAAO,CAAC,GAAG,CACT,iBAAiB,IAAI,8BAA8B,KAAK,CAAC,MAAM,GAAG;QAClE,yBAAyB,QAAQ,CAAC,MAAM,EAAE,CAC3C,CAAC;IAEF,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAA6D;IAE7D,MAAM,MAAM,GAA6B,EAAE,CAAC;IAC5C,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;YACxB,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC;QACjD,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -2,12 +2,26 @@
2
2
  * The Plan-mode filesystem permission rules — the enforcement twin of
3
3
  * `plan-mode-prompt.ts` (which carries the instruction half of the contract).
4
4
  *
5
- * Plan mode is read-only BY CONSTRUCTION on the native harness: these rules
6
- * deny every filesystem write operation at the tool level so
7
- * write_file/edit_file cannot mutate the workspace regardless of what the
8
- * model was told. Rules are first-match-wins with a permissive default, so a
9
- * single deny-all-writes rule is sufficient. (The Cursor harness has no
10
- * tool-level lever and enforces plan mode via its prompt prefix instead.)
5
+ * Plan mode is contained BY CONSTRUCTION on the native harness: these rules
6
+ * deny every filesystem write everywhere AND scope reads to the session
7
+ * workspace (issue #528 — owner ruling: the workspace is plan mode's read
8
+ * boundary on cloud and desktop runners alike). Without the read boundary,
9
+ * model-provided absolute paths reached anywhere the process account could
10
+ * read the pod filesystem (including /proc/self/environ) on cloud runners,
11
+ * the user's whole home directory on desktop — while plan mode still carries
12
+ * exfiltration-capable tools (web_fetch, MCP). Rules are first-match-wins
13
+ * with a permissive default (deepagents' decidePathAccess), so order is
14
+ * load-bearing: the workspace read-allow must precede the read-deny.
15
+ *
16
+ * The workspace-root pattern is matched as a STRING against the raw tool-call
17
+ * path (micromatch, dot:true), before the backend touches disk. That is
18
+ * exactly why the legitimate out-of-workspace reads keep working: skills,
19
+ * attachments, and the approved plan live in the platform dir but are
20
+ * addressed through the `{workspace}/.stigmer` symlink (see
21
+ * shared/workspace/stigmer-link.ts), so their path strings are in-root even
22
+ * though the bytes are not. The same holds for multi-workspace local entries
23
+ * (`{workspace}/{name}` symlinks). A realpath-based boundary would break
24
+ * both; do not "harden" this into one.
11
25
  *
12
26
  * Applied in execute-deep-agent/setup.ts to the parent graph AND threaded
13
27
  * into every compiled sub-agent graph: deepagents' parent-permission
@@ -18,9 +32,9 @@
18
32
  *
19
33
  * Rules travel with a companion: every graph that carries them also installs
20
34
  * the path-normalization middleware (middleware/path-normalization.ts,
21
- * issue #429), because deepagents' rule validation refuses workspace-relative
22
- * paths outright without the shim, prompt-compliant relative READS die in
23
- * validation instead of just working. Both are derived from the same
35
+ * issues #429/#528), because deepagents' rule validation refuses
36
+ * workspace-relative paths outright and its `ls`/`glob`/`grep` schema default
37
+ * of "/" would deny the bare first listing. Both are derived from the same
24
38
  * expression at each composition site so they cannot drift apart.
25
39
  *
26
40
  * Invariant: never combine these rules with a shell-capable (sandbox)
@@ -30,4 +44,26 @@
30
44
  * for shell capability on both the parent and sub-agent backends.
31
45
  */
32
46
  import type { FilesystemPermission } from "deepagents";
33
- export declare const PLAN_MODE_PERMISSIONS: FilesystemPermission[];
47
+ /**
48
+ * Backslash-escape every character micromatch/picomatch treats as glob
49
+ * syntax, so the result matches the input literally. micromatch exports no
50
+ * escape API of its own, and this is correctness, not caution: an unescaped
51
+ * `(` in a desktop project path would make the workspace read-allow rule
52
+ * silently never match — bricking every plan-mode read for that workspace.
53
+ * Semantics are pinned end-to-end through deepagents' own matcher by the
54
+ * special-character workspace suite in plan-mode-path-normalization.test.ts.
55
+ */
56
+ export declare function escapeGlobLiteral(literal: string): string;
57
+ /**
58
+ * Build the plan-mode rule set for a graph whose filesystem backend is
59
+ * rooted at `workspaceRootDir`. The three rules read as the policy:
60
+ * reads allowed in the workspace, reads denied everywhere else, writes
61
+ * denied everywhere.
62
+ *
63
+ * `{root}/**` matches the root itself as well as its subtree (verified
64
+ * against the installed micromatch), so one allow pattern suffices. The
65
+ * root is `path.resolve`d first because enforcement canonicalizes incoming
66
+ * paths (collapsed slashes, no trailing separator) before matching — a
67
+ * trailing slash in the pattern would silently match nothing.
68
+ */
69
+ export declare function buildPlanModePermissions(workspaceRootDir: string): FilesystemPermission[];
@@ -2,12 +2,26 @@
2
2
  * The Plan-mode filesystem permission rules — the enforcement twin of
3
3
  * `plan-mode-prompt.ts` (which carries the instruction half of the contract).
4
4
  *
5
- * Plan mode is read-only BY CONSTRUCTION on the native harness: these rules
6
- * deny every filesystem write operation at the tool level so
7
- * write_file/edit_file cannot mutate the workspace regardless of what the
8
- * model was told. Rules are first-match-wins with a permissive default, so a
9
- * single deny-all-writes rule is sufficient. (The Cursor harness has no
10
- * tool-level lever and enforces plan mode via its prompt prefix instead.)
5
+ * Plan mode is contained BY CONSTRUCTION on the native harness: these rules
6
+ * deny every filesystem write everywhere AND scope reads to the session
7
+ * workspace (issue #528 — owner ruling: the workspace is plan mode's read
8
+ * boundary on cloud and desktop runners alike). Without the read boundary,
9
+ * model-provided absolute paths reached anywhere the process account could
10
+ * read the pod filesystem (including /proc/self/environ) on cloud runners,
11
+ * the user's whole home directory on desktop — while plan mode still carries
12
+ * exfiltration-capable tools (web_fetch, MCP). Rules are first-match-wins
13
+ * with a permissive default (deepagents' decidePathAccess), so order is
14
+ * load-bearing: the workspace read-allow must precede the read-deny.
15
+ *
16
+ * The workspace-root pattern is matched as a STRING against the raw tool-call
17
+ * path (micromatch, dot:true), before the backend touches disk. That is
18
+ * exactly why the legitimate out-of-workspace reads keep working: skills,
19
+ * attachments, and the approved plan live in the platform dir but are
20
+ * addressed through the `{workspace}/.stigmer` symlink (see
21
+ * shared/workspace/stigmer-link.ts), so their path strings are in-root even
22
+ * though the bytes are not. The same holds for multi-workspace local entries
23
+ * (`{workspace}/{name}` symlinks). A realpath-based boundary would break
24
+ * both; do not "harden" this into one.
11
25
  *
12
26
  * Applied in execute-deep-agent/setup.ts to the parent graph AND threaded
13
27
  * into every compiled sub-agent graph: deepagents' parent-permission
@@ -18,9 +32,9 @@
18
32
  *
19
33
  * Rules travel with a companion: every graph that carries them also installs
20
34
  * the path-normalization middleware (middleware/path-normalization.ts,
21
- * issue #429), because deepagents' rule validation refuses workspace-relative
22
- * paths outright without the shim, prompt-compliant relative READS die in
23
- * validation instead of just working. Both are derived from the same
35
+ * issues #429/#528), because deepagents' rule validation refuses
36
+ * workspace-relative paths outright and its `ls`/`glob`/`grep` schema default
37
+ * of "/" would deny the bare first listing. Both are derived from the same
24
38
  * expression at each composition site so they cannot drift apart.
25
39
  *
26
40
  * Invariant: never combine these rules with a shell-capable (sandbox)
@@ -29,7 +43,37 @@
29
43
  * it is the mode that clears `shellEnv`, and `shellEnv` is the single switch
30
44
  * for shell capability on both the parent and sub-agent backends.
31
45
  */
32
- export const PLAN_MODE_PERMISSIONS = [
33
- { operations: ["write"], paths: ["/**"], mode: "deny" },
34
- ];
46
+ import { resolve } from "node:path";
47
+ /**
48
+ * Backslash-escape every character micromatch/picomatch treats as glob
49
+ * syntax, so the result matches the input literally. micromatch exports no
50
+ * escape API of its own, and this is correctness, not caution: an unescaped
51
+ * `(` in a desktop project path would make the workspace read-allow rule
52
+ * silently never match — bricking every plan-mode read for that workspace.
53
+ * Semantics are pinned end-to-end through deepagents' own matcher by the
54
+ * special-character workspace suite in plan-mode-path-normalization.test.ts.
55
+ */
56
+ export function escapeGlobLiteral(literal) {
57
+ return literal.replace(/[\\*?()[\]{}!+@]/g, "\\$&");
58
+ }
59
+ /**
60
+ * Build the plan-mode rule set for a graph whose filesystem backend is
61
+ * rooted at `workspaceRootDir`. The three rules read as the policy:
62
+ * reads allowed in the workspace, reads denied everywhere else, writes
63
+ * denied everywhere.
64
+ *
65
+ * `{root}/**` matches the root itself as well as its subtree (verified
66
+ * against the installed micromatch), so one allow pattern suffices. The
67
+ * root is `path.resolve`d first because enforcement canonicalizes incoming
68
+ * paths (collapsed slashes, no trailing separator) before matching — a
69
+ * trailing slash in the pattern would silently match nothing.
70
+ */
71
+ export function buildPlanModePermissions(workspaceRootDir) {
72
+ const canonicalRoot = resolve(workspaceRootDir);
73
+ return [
74
+ { operations: ["read"], paths: [`${escapeGlobLiteral(canonicalRoot)}/**`] },
75
+ { operations: ["read"], paths: ["/**"], mode: "deny" },
76
+ { operations: ["write"], paths: ["/**"], mode: "deny" },
77
+ ];
78
+ }
35
79
  //# sourceMappingURL=plan-mode-permissions.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"plan-mode-permissions.js","sourceRoot":"","sources":["../../src/shared/plan-mode-permissions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAIH,MAAM,CAAC,MAAM,qBAAqB,GAA2B;IAC3D,EAAE,UAAU,EAAE,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE;CACxD,CAAC"}
1
+ {"version":3,"file":"plan-mode-permissions.js","sourceRoot":"","sources":["../../src/shared/plan-mode-permissions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAe;IAC/C,OAAO,OAAO,CAAC,OAAO,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC;AACtD,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,wBAAwB,CACtC,gBAAwB;IAExB,MAAM,aAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAChD,OAAO;QACL,EAAE,UAAU,EAAE,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,iBAAiB,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE;QAC3E,EAAE,UAAU,EAAE,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE;QACtD,EAAE,UAAU,EAAE,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE;KACxD,CAAC;AACJ,CAAC"}
@@ -1,10 +1,24 @@
1
1
  /**
2
- * Minimal, dependency-free ZIP archive parser.
2
+ * Skill-artifact ZIP extraction: text-decoded entries with non-fatal
3
+ * structural failure.
3
4
  *
4
- * Handles the local file header format, supporting stored (method 0) and
5
- * deflated (method 8) entries. Returns parsed entries as path + content
6
- * pairs consumers bring their own write mechanism (WorkspaceBackend,
7
- * node:fs, etc.).
5
+ * Structural parsing lives in zip-structure.ts (central-directory-based
6
+ * see that module's doc for why local-header walks are never acceptable,
7
+ * issue #450). This module owns the skill-artifact *policy* on top of it:
8
+ *
9
+ * Input without a readable central directory is not given a second
10
+ * chance on purpose: every artifact reaching the runner was validated at
11
+ * push by a central-directory-based reader on both editions (OSS:
12
+ * `safearchive/zip` in stigmer-server's skill storage; cloud:
13
+ * commons-compress `ZipFile` in SkillArtifactExtractor), so a missing
14
+ * EOCD here can only mean a truncated or corrupted download — and the
15
+ * honest result for that is the documented empty return, not a guess.
16
+ * (The attachment injector consumes the same structural layer under the
17
+ * opposite policy — fail-hard — because its input is untrusted.)
18
+ *
19
+ * Handles stored (method 0) and deflated (method 8) entries. Returns
20
+ * parsed entries as path + content pairs — consumers bring their own
21
+ * write mechanism (WorkspaceBackend, node:fs, etc.).
8
22
  *
9
23
  * Extracted from skill-writer.ts so both the deep-agent and Cursor
10
24
  * harnesses can share the same ZIP parsing logic without coupling to
@@ -23,7 +37,11 @@ export interface ZipFileEntry {
23
37
  * entries whose filename (basename or full path) matches any excluded name.
24
38
  *
25
39
  * Returns an empty array for empty, truncated, or non-ZIP input rather
26
- * than throwing — callers treat missing artifacts as non-fatal.
40
+ * than throwing — callers treat missing artifacts as non-fatal. Errors
41
+ * while *decompressing* a structurally valid entry (corrupt deflate
42
+ * stream, unsupported compression method) do propagate: at that point
43
+ * the archive's structure vouched for the entry, and silently dropping
44
+ * it would be the exact corruption this module exists to prevent.
27
45
  */
28
46
  export declare function extractZipFileEntries(zipBytes: Uint8Array, options?: {
29
47
  exclude?: readonly string[];
@@ -1,16 +1,31 @@
1
1
  /**
2
- * Minimal, dependency-free ZIP archive parser.
2
+ * Skill-artifact ZIP extraction: text-decoded entries with non-fatal
3
+ * structural failure.
3
4
  *
4
- * Handles the local file header format, supporting stored (method 0) and
5
- * deflated (method 8) entries. Returns parsed entries as path + content
6
- * pairs consumers bring their own write mechanism (WorkspaceBackend,
7
- * node:fs, etc.).
5
+ * Structural parsing lives in zip-structure.ts (central-directory-based
6
+ * see that module's doc for why local-header walks are never acceptable,
7
+ * issue #450). This module owns the skill-artifact *policy* on top of it:
8
+ *
9
+ * Input without a readable central directory is not given a second
10
+ * chance on purpose: every artifact reaching the runner was validated at
11
+ * push by a central-directory-based reader on both editions (OSS:
12
+ * `safearchive/zip` in stigmer-server's skill storage; cloud:
13
+ * commons-compress `ZipFile` in SkillArtifactExtractor), so a missing
14
+ * EOCD here can only mean a truncated or corrupted download — and the
15
+ * honest result for that is the documented empty return, not a guess.
16
+ * (The attachment injector consumes the same structural layer under the
17
+ * opposite policy — fail-hard — because its input is untrusted.)
18
+ *
19
+ * Handles stored (method 0) and deflated (method 8) entries. Returns
20
+ * parsed entries as path + content pairs — consumers bring their own
21
+ * write mechanism (WorkspaceBackend, node:fs, etc.).
8
22
  *
9
23
  * Extracted from skill-writer.ts so both the deep-agent and Cursor
10
24
  * harnesses can share the same ZIP parsing logic without coupling to
11
25
  * a specific workspace abstraction.
12
26
  */
13
27
  import { createInflateRaw } from "node:zlib";
28
+ import { EOCD_MIN_SIZE, parseZipStructure } from "./zip-structure.js";
14
29
  /**
15
30
  * Parse and decompress all file entries from a ZIP archive.
16
31
  *
@@ -18,16 +33,22 @@ import { createInflateRaw } from "node:zlib";
18
33
  * entries whose filename (basename or full path) matches any excluded name.
19
34
  *
20
35
  * Returns an empty array for empty, truncated, or non-ZIP input rather
21
- * than throwing — callers treat missing artifacts as non-fatal.
36
+ * than throwing — callers treat missing artifacts as non-fatal. Errors
37
+ * while *decompressing* a structurally valid entry (corrupt deflate
38
+ * stream, unsupported compression method) do propagate: at that point
39
+ * the archive's structure vouched for the entry, and silently dropping
40
+ * it would be the exact corruption this module exists to prevent.
22
41
  */
23
42
  export async function extractZipFileEntries(zipBytes, options) {
24
- if (zipBytes.length < 4)
43
+ if (zipBytes.length < EOCD_MIN_SIZE)
25
44
  return [];
26
45
  let entries;
27
46
  try {
28
- entries = parseZipEntries(zipBytes);
47
+ entries = parseZipStructure(zipBytes);
29
48
  }
30
- catch {
49
+ catch (err) {
50
+ console.warn("[zip-extract] archive has no readable central directory " +
51
+ `(truncated or corrupt download?): ${err instanceof Error ? err.message : String(err)}`);
31
52
  return [];
32
53
  }
33
54
  const excludeSet = new Set(options?.exclude ?? []);
@@ -50,87 +71,7 @@ function isExcluded(name, excludeSet) {
50
71
  const basename = name.includes("/") ? name.slice(name.lastIndexOf("/") + 1) : name;
51
72
  return excludeSet.has(basename);
52
73
  }
53
- function parseZipEntries(data) {
54
- const entries = [];
55
- const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
56
- let offset = 0;
57
- while (offset < data.length - 4) {
58
- const signature = view.getUint32(offset, true);
59
- if (signature !== 0x04034b50)
60
- break; // Local file header signature
61
- const generalFlags = view.getUint16(offset + 6, true);
62
- const hasDataDescriptor = (generalFlags & 0x08) !== 0;
63
- const compressionMethod = view.getUint16(offset + 8, true);
64
- let compressedSize = view.getUint32(offset + 18, true);
65
- let uncompressedSize = view.getUint32(offset + 22, true);
66
- const fileNameLength = view.getUint16(offset + 26, true);
67
- const extraFieldLength = view.getUint16(offset + 28, true);
68
- const fileNameStart = offset + 30;
69
- const fileName = new TextDecoder().decode(data.subarray(fileNameStart, fileNameStart + fileNameLength));
70
- const dataStart = fileNameStart + fileNameLength + extraFieldLength;
71
- if (hasDataDescriptor && compressedSize === 0) {
72
- const sizes = findDataDescriptor(data, view, dataStart, compressionMethod);
73
- compressedSize = sizes.compressedSize;
74
- uncompressedSize = sizes.uncompressedSize;
75
- }
76
- const compressedData = data.subarray(dataStart, dataStart + compressedSize);
77
- entries.push({
78
- name: fileName,
79
- isDirectory: fileName.endsWith("/"),
80
- compressedData,
81
- compressionMethod,
82
- uncompressedSize,
83
- });
84
- let nextOffset = dataStart + compressedSize;
85
- if (hasDataDescriptor) {
86
- if (nextOffset + 4 <= data.length && view.getUint32(nextOffset, true) === 0x08074b50) {
87
- nextOffset += 16; // signature(4) + crc(4) + compressedSize(4) + uncompressedSize(4)
88
- }
89
- else {
90
- nextOffset += 12; // crc(4) + compressedSize(4) + uncompressedSize(4)
91
- }
92
- }
93
- offset = nextOffset;
94
- }
95
- return entries;
96
- }
97
- /**
98
- * Scan forward from dataStart to find the data descriptor that contains
99
- * the actual compressed and uncompressed sizes. Looks for either the
100
- * optional signature 0x08074b50 or falls back to scanning the central
101
- * directory for the matching entry.
102
- */
103
- function findDataDescriptor(data, view, dataStart, _compressionMethod) {
104
- for (let pos = dataStart; pos < data.length - 16; pos++) {
105
- const sig = view.getUint32(pos, true);
106
- if (sig === 0x08074b50) {
107
- return {
108
- compressedSize: view.getUint32(pos + 8, true),
109
- uncompressedSize: view.getUint32(pos + 12, true),
110
- };
111
- }
112
- if (sig === 0x04034b50 || sig === 0x02014b50) {
113
- const descStart = pos - 12;
114
- if (descStart >= dataStart) {
115
- return {
116
- compressedSize: view.getUint32(descStart + 4, true),
117
- uncompressedSize: view.getUint32(descStart + 8, true),
118
- };
119
- }
120
- break;
121
- }
122
- }
123
- for (let pos = dataStart; pos < data.length - 4; pos++) {
124
- const sig = view.getUint32(pos, true);
125
- if (sig === 0x04034b50 || sig === 0x02014b50 || sig === 0x08074b50) {
126
- const compressedSize = sig === 0x08074b50
127
- ? view.getUint32(pos + 8, true)
128
- : pos - dataStart;
129
- return { compressedSize, uncompressedSize: 0 };
130
- }
131
- }
132
- return { compressedSize: data.length - dataStart, uncompressedSize: 0 };
133
- }
74
+ // ─── Decompression ───────────────────────────────────────────────────────
134
75
  async function decompressEntry(entry) {
135
76
  if (entry.compressionMethod === 0) {
136
77
  return new TextDecoder().decode(entry.compressedData);
@@ -1 +1 @@
1
- {"version":3,"file":"zip-extract.js","sourceRoot":"","sources":["../../src/shared/zip-extract.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"zip-extract.js","sourceRoot":"","sources":["../../src/shared/zip-extract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAA2B,MAAM,oBAAoB,CAAC;AAW/F;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,QAAoB,EACpB,OAAyC;IAEzC,IAAI,QAAQ,CAAC,MAAM,GAAG,aAAa;QAAE,OAAO,EAAE,CAAC;IAE/C,IAAI,OAA6B,CAAC;IAClC,IAAI,CAAC;QACH,OAAO,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IACxC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CACV,0DAA0D;YAC1D,qCAAqC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CACxF,CAAC;QACF,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,OAAO,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC;IACnD,MAAM,OAAO,GAAmB,EAAE,CAAC;IAEnC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,KAAK,CAAC,WAAW;YAAE,SAAS;QAChC,IAAI,UAAU,CAAC,KAAK,CAAC,IAAI,EAAE,UAAU,CAAC;YAAE,SAAS;QAEjD,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC,KAAK,CAAC,CAAC;QAC7C,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;IAC9C,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,UAAU,CAAC,IAAY,EAAE,UAA+B;IAC/D,IAAI,UAAU,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACxC,IAAI,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAEtC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACnF,OAAO,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;AAClC,CAAC;AAED,4EAA4E;AAE5E,KAAK,UAAU,eAAe,CAAC,KAAyB;IACtD,IAAI,KAAK,CAAC,iBAAiB,KAAK,CAAC,EAAE,CAAC;QAClC,OAAO,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;IACxD,CAAC;IAED,IAAI,KAAK,CAAC,iBAAiB,KAAK,CAAC,EAAE,CAAC;QAClC,OAAO,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC7C,MAAM,OAAO,GAAG,gBAAgB,EAAE,CAAC;YACnC,MAAM,MAAM,GAAa,EAAE,CAAC;YAC5B,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;YAC1D,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YAC1E,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YAC5B,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC;QACjD,CAAC,CAAC,CAAC;IACL,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,uCAAuC,KAAK,CAAC,iBAAiB,EAAE,CAAC,CAAC;AACpF,CAAC"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Policy-free structural ZIP parsing: end-of-central-directory (EOCD)
3
+ * location plus central-directory walk, producing one record per entry.
4
+ *
5
+ * Parsing is *central-directory-based* — never a front-to-back walk of
6
+ * local file headers. This is a correctness decision, not a style choice
7
+ * (issues #450 and #567): local headers of streaming entries
8
+ * (general-purpose flag bit 3) carry zeroed sizes, and the only
9
+ * local-only way to recover them is scanning the payload for the
10
+ * data-descriptor signature — which silently truncates any stored entry
11
+ * whose *content* happens to contain those four bytes, and
12
+ * desynchronizes every entry after it. The central directory always
13
+ * carries the real sizes, and every consumer here holds the complete
14
+ * archive bytes, so nothing a local-header walk could offer is needed.
15
+ *
16
+ * This layer maps bytes to entry records and nothing else. Policy — what
17
+ * a structural failure means, which entries are acceptable, how payloads
18
+ * are decoded — belongs to the consumers, and they differ on purpose:
19
+ *
20
+ * - shared/zip-extract.ts (skill artifacts): structural failure is
21
+ * NON-FATAL — both editions' push gates validated every artifact
22
+ * with a central-directory-based reader before storage, so a defect
23
+ * here can only be a truncated or corrupted download.
24
+ * - execute-deep-agent/attachment-injector.ts (user attachments):
25
+ * structural failure is FAIL-HARD — the input is an untrusted
26
+ * upload and nothing upstream vouched for it.
27
+ *
28
+ * ZIP64 is deliberately unsupported: both consumers cap input far below
29
+ * every ZIP64 threshold (skill push gates: 100MB / 10,000 files;
30
+ * attachment uploads: 10MB).
31
+ *
32
+ * Throws plain `Error`s on structural defects (no valid EOCD, a central
33
+ * directory or local header record that does not parse, a payload slice
34
+ * that runs past the end of the buffer); consumers translate those into
35
+ * their own error models.
36
+ */
37
+ export interface ZipStructuralEntry {
38
+ /** Entry path exactly as recorded in the central directory. */
39
+ readonly name: string;
40
+ readonly isDirectory: boolean;
41
+ readonly compressionMethod: number;
42
+ /**
43
+ * The central directory's declared uncompressed size — authoritative for
44
+ * pre-extraction accounting (e.g. ZIP-bomb budgeting), but still a
45
+ * *declaration*: a consumer that distrusts its input must enforce it
46
+ * against the actual decompressed output.
47
+ */
48
+ readonly uncompressedSize: number;
49
+ /** The entry's raw payload slice (a view, not a copy) of the archive buffer. */
50
+ readonly compressedData: Uint8Array;
51
+ }
52
+ /** Fixed size of the EOCD record, excluding the variable-length comment. */
53
+ export declare const EOCD_MIN_SIZE = 22;
54
+ /**
55
+ * Enumerate the archive's entries from its central directory.
56
+ *
57
+ * Throws on any structural defect: no valid EOCD, a central directory or
58
+ * local header record that doesn't parse, or a payload slice that runs
59
+ * past the end of the buffer.
60
+ */
61
+ export declare function parseZipStructure(data: Uint8Array): ZipStructuralEntry[];