@stigmer/runner 3.15.1 → 3.15.2-dev.20260913223433
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/README.md +2 -4
- package/dist/.build-fingerprint +1 -1
- package/dist/__test-utils__/execution-record-fixture.d.ts +14 -0
- package/dist/__test-utils__/execution-record-fixture.js +4 -0
- package/dist/__test-utils__/execution-record-fixture.js.map +1 -1
- package/dist/__test-utils__/file-review-projection.d.ts +40 -0
- package/dist/__test-utils__/file-review-projection.js +136 -0
- package/dist/__test-utils__/file-review-projection.js.map +1 -0
- package/dist/__test-utils__/harness-contract/types.d.ts +23 -1
- package/dist/__test-utils__/harness-contract/types.js +15 -0
- package/dist/__test-utils__/harness-contract/types.js.map +1 -1
- package/dist/__test-utils__/hermetic-activity.d.ts +44 -12
- package/dist/__test-utils__/hermetic-activity.js +52 -14
- package/dist/__test-utils__/hermetic-activity.js.map +1 -1
- package/dist/__test-utils__/model-registry-fixture.d.ts +32 -7
- package/dist/__test-utils__/model-registry-fixture.js +31 -7
- package/dist/__test-utils__/model-registry-fixture.js.map +1 -1
- package/dist/__test-utils__/turn-input-fixture.d.ts +1 -1
- package/dist/__test-utils__/turn-input-fixture.js +1 -1
- package/dist/__test-utils__/turn-input-fixture.js.map +1 -1
- package/dist/activities/call-agent.js +2 -2
- package/dist/activities/call-agent.js.map +1 -1
- package/dist/activities/execute-cursor/__test-utils__/contract-subject.js +45 -3
- package/dist/activities/execute-cursor/__test-utils__/contract-subject.js.map +1 -1
- package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.d.ts +0 -9
- package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js +2 -41
- package/dist/activities/execute-cursor/__test-utils__/hermetic-cursor.js.map +1 -1
- package/dist/activities/execute-cursor/adapter.d.ts +7 -5
- package/dist/activities/execute-cursor/adapter.js +7 -5
- package/dist/activities/execute-cursor/adapter.js.map +1 -1
- package/dist/activities/execute-cursor/approval-policy.d.ts +5 -2
- package/dist/activities/execute-cursor/approval-policy.js +5 -2
- package/dist/activities/execute-cursor/approval-policy.js.map +1 -1
- package/dist/activities/execute-cursor/approval-state.d.ts +16 -8
- package/dist/activities/execute-cursor/approval-state.js +6 -3
- package/dist/activities/execute-cursor/approval-state.js.map +1 -1
- package/dist/activities/execute-cursor/cas-observations.d.ts +14 -2
- package/dist/activities/execute-cursor/cas-observations.js +19 -2
- package/dist/activities/execute-cursor/cas-observations.js.map +1 -1
- package/dist/activities/execute-cursor/cursor-capabilities.d.ts +8 -4
- package/dist/activities/execute-cursor/cursor-capabilities.js +21 -4
- package/dist/activities/execute-cursor/cursor-capabilities.js.map +1 -1
- package/dist/activities/execute-cursor/message-translator.d.ts +12 -20
- package/dist/activities/execute-cursor/message-translator.js +19 -32
- package/dist/activities/execute-cursor/message-translator.js.map +1 -1
- package/dist/activities/execute-cursor/prompt-builder.d.ts +48 -58
- package/dist/activities/execute-cursor/prompt-builder.js +76 -95
- package/dist/activities/execute-cursor/prompt-builder.js.map +1 -1
- package/dist/activities/execute-cursor/turn-boundary.d.ts +27 -55
- package/dist/activities/execute-cursor/turn-boundary.js +30 -90
- package/dist/activities/execute-cursor/turn-boundary.js.map +1 -1
- package/dist/activities/execute-cursor/turn-settle.d.ts +11 -7
- package/dist/activities/execute-cursor/turn-settle.js +37 -49
- package/dist/activities/execute-cursor/turn-settle.js.map +1 -1
- package/dist/activities/execute-cursor/turn-setup.d.ts +16 -24
- package/dist/activities/execute-cursor/turn-setup.js +29 -59
- package/dist/activities/execute-cursor/turn-setup.js.map +1 -1
- package/dist/activities/execute-cursor/turn-stream.d.ts +3 -8
- package/dist/activities/execute-cursor/turn-stream.js +9 -22
- package/dist/activities/execute-cursor/turn-stream.js.map +1 -1
- package/dist/activities/execute-cursor/turn.js +5 -1
- package/dist/activities/execute-cursor/turn.js.map +1 -1
- package/dist/activities/execute-deep-agent/__test-utils__/contract-subject.d.ts +93 -0
- package/dist/activities/execute-deep-agent/__test-utils__/contract-subject.js +348 -0
- package/dist/activities/execute-deep-agent/__test-utils__/contract-subject.js.map +1 -0
- package/dist/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.d.ts +170 -0
- package/dist/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.js +225 -0
- package/dist/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.js.map +1 -0
- package/dist/activities/execute-deep-agent/__test-utils__/hitl-script.d.ts +24 -0
- package/dist/activities/execute-deep-agent/__test-utils__/hitl-script.js +41 -0
- package/dist/activities/execute-deep-agent/__test-utils__/hitl-script.js.map +1 -0
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model-module.d.ts +54 -0
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model-module.js +72 -0
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model-module.js.map +1 -0
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.d.ts +187 -23
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js +238 -27
- package/dist/activities/execute-deep-agent/__test-utils__/scripted-model.js.map +1 -1
- package/dist/activities/execute-deep-agent/adapter.d.ts +41 -0
- package/dist/activities/execute-deep-agent/adapter.js +78 -0
- package/dist/activities/execute-deep-agent/adapter.js.map +1 -0
- package/dist/activities/execute-deep-agent/cas-capture-observer.d.ts +8 -0
- package/dist/activities/execute-deep-agent/cas-capture-observer.js +9 -0
- package/dist/activities/execute-deep-agent/cas-capture-observer.js.map +1 -1
- package/dist/activities/execute-deep-agent/deep-agent-capabilities.d.ts +31 -0
- package/dist/activities/execute-deep-agent/deep-agent-capabilities.js +40 -0
- package/dist/activities/execute-deep-agent/deep-agent-capabilities.js.map +1 -0
- package/dist/activities/execute-deep-agent/execution-state.d.ts +4 -11
- package/dist/activities/execute-deep-agent/execution-state.js +4 -12
- package/dist/activities/execute-deep-agent/execution-state.js.map +1 -1
- package/dist/activities/execute-deep-agent/hitl.d.ts +49 -47
- package/dist/activities/execute-deep-agent/hitl.js +77 -103
- package/dist/activities/execute-deep-agent/hitl.js.map +1 -1
- package/dist/activities/execute-deep-agent/prompt-builder.d.ts +51 -23
- package/dist/activities/execute-deep-agent/prompt-builder.js +115 -76
- package/dist/activities/execute-deep-agent/prompt-builder.js.map +1 -1
- package/dist/activities/execute-deep-agent/status-builder-shared.d.ts +18 -32
- package/dist/activities/execute-deep-agent/status-builder-shared.js +10 -65
- package/dist/activities/execute-deep-agent/status-builder-shared.js.map +1 -1
- package/dist/activities/execute-deep-agent/streaming-side-effects.d.ts +10 -8
- package/dist/activities/execute-deep-agent/streaming-side-effects.js +12 -14
- package/dist/activities/execute-deep-agent/streaming-side-effects.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-tracker.d.ts +36 -13
- package/dist/activities/execute-deep-agent/subagent-tracker.js +76 -39
- package/dist/activities/execute-deep-agent/subagent-tracker.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-transformer.d.ts +25 -28
- package/dist/activities/execute-deep-agent/subagent-transformer.js +29 -82
- package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
- package/dist/activities/execute-deep-agent/subagent-wiring.d.ts +3 -2
- package/dist/activities/execute-deep-agent/subagent-wiring.js +2 -2
- package/dist/activities/execute-deep-agent/subagent-wiring.js.map +1 -1
- package/dist/activities/execute-deep-agent/turn-settle.d.ts +42 -0
- package/dist/activities/execute-deep-agent/turn-settle.js +146 -0
- package/dist/activities/execute-deep-agent/turn-settle.js.map +1 -0
- package/dist/activities/execute-deep-agent/turn-setup.d.ts +212 -0
- package/dist/activities/execute-deep-agent/turn-setup.js +430 -0
- package/dist/activities/execute-deep-agent/turn-setup.js.map +1 -0
- package/dist/activities/execute-deep-agent/turn-stream.d.ts +97 -0
- package/dist/activities/execute-deep-agent/turn-stream.js +242 -0
- package/dist/activities/execute-deep-agent/turn-stream.js.map +1 -0
- package/dist/activities/execute-deep-agent/turn.d.ts +42 -0
- package/dist/activities/execute-deep-agent/turn.js +116 -0
- package/dist/activities/execute-deep-agent/turn.js.map +1 -0
- package/dist/activities/execute-deep-agent/v3-event-recorder.d.ts +6 -5
- package/dist/activities/execute-deep-agent/v3-event-recorder.js +6 -5
- package/dist/activities/execute-deep-agent/v3-event-recorder.js.map +1 -1
- package/dist/activities/execute-deep-agent/v3-events.d.ts +1 -1
- package/dist/activities/execute-deep-agent/v3-events.js +1 -1
- package/dist/activities/execute-deep-agent/v3-status-builder.d.ts +60 -17
- package/dist/activities/execute-deep-agent/v3-status-builder.js +72 -57
- package/dist/activities/execute-deep-agent/v3-status-builder.js.map +1 -1
- package/dist/harness/approval-decisions.d.ts +61 -0
- package/dist/harness/approval-decisions.js +102 -0
- package/dist/harness/approval-decisions.js.map +1 -0
- package/dist/harness/capabilities.d.ts +16 -5
- package/dist/harness/capabilities.js +9 -0
- package/dist/harness/capabilities.js.map +1 -1
- package/dist/harness/capture.d.ts +144 -0
- package/dist/harness/capture.js +219 -0
- package/dist/harness/capture.js.map +1 -0
- package/dist/harness/persist-chokepoint.d.ts +19 -4
- package/dist/harness/persist-chokepoint.js +17 -4
- package/dist/harness/persist-chokepoint.js.map +1 -1
- package/dist/harness/run-turn.js +115 -29
- package/dist/harness/run-turn.js.map +1 -1
- package/dist/harness/terminal-table.d.ts +40 -11
- package/dist/harness/terminal-table.js +58 -19
- package/dist/harness/terminal-table.js.map +1 -1
- package/dist/harness/turn-context.d.ts +141 -76
- package/dist/harness/turn-context.js +194 -99
- package/dist/harness/turn-context.js.map +1 -1
- package/dist/harness/types.d.ts +65 -12
- package/dist/harness/types.js +8 -2
- package/dist/harness/types.js.map +1 -1
- package/dist/harness-adapters.d.ts +12 -8
- package/dist/harness-adapters.js +17 -9
- package/dist/harness-adapters.js.map +1 -1
- package/dist/middleware/approval-gate.d.ts +1 -1
- package/dist/middleware/approval-gate.js +1 -1
- package/dist/middleware/approval-gate.js.map +1 -1
- package/dist/middleware/cost-advisory.d.ts +39 -0
- package/dist/middleware/{cost-cap.js → cost-advisory.js} +37 -55
- package/dist/middleware/cost-advisory.js.map +1 -0
- package/dist/middleware/index.d.ts +23 -12
- package/dist/middleware/index.js +26 -18
- package/dist/middleware/index.js.map +1 -1
- package/dist/middleware/types.d.ts +3 -2
- package/dist/runner-manager.js +1 -5
- package/dist/runner-manager.js.map +1 -1
- package/dist/runner.js +1 -5
- package/dist/runner.js.map +1 -1
- package/dist/shared/attachment-resolver.d.ts +41 -18
- package/dist/shared/attachment-resolver.js +178 -46
- package/dist/shared/attachment-resolver.js.map +1 -1
- package/dist/shared/attachment-zip.d.ts +85 -0
- package/dist/shared/attachment-zip.js +185 -0
- package/dist/shared/attachment-zip.js.map +1 -0
- package/dist/shared/connect-backfill.js +5 -4
- package/dist/shared/connect-backfill.js.map +1 -1
- package/dist/shared/cost-guard.d.ts +3 -3
- package/dist/shared/cost-guard.js +3 -3
- package/dist/shared/execution-status-writer.d.ts +5 -5
- package/dist/shared/execution-status-writer.js +5 -5
- package/dist/shared/extract-structured-output.d.ts +8 -7
- package/dist/shared/extract-structured-output.js +8 -7
- package/dist/shared/extract-structured-output.js.map +1 -1
- package/dist/shared/filereview/cas-progress.d.ts +1 -18
- package/dist/shared/filereview/cas-progress.js +4 -0
- package/dist/shared/filereview/cas-progress.js.map +1 -1
- package/dist/shared/filereview/cas-touched.d.ts +56 -0
- package/dist/shared/filereview/cas-touched.js +63 -0
- package/dist/shared/filereview/cas-touched.js.map +1 -0
- package/dist/shared/filereview/index.d.ts +2 -1
- package/dist/shared/filereview/index.js +2 -1
- package/dist/shared/filereview/index.js.map +1 -1
- package/dist/shared/mcp-resolver.d.ts +4 -5
- package/dist/shared/mcp-resolver.js +4 -5
- package/dist/shared/mcp-resolver.js.map +1 -1
- package/dist/shared/persist-decision.d.ts +3 -4
- package/dist/shared/persist-decision.js +3 -4
- package/dist/shared/persist-decision.js.map +1 -1
- package/dist/shared/plan-mode-permissions.d.ts +2 -2
- package/dist/shared/plan-mode-permissions.js +2 -2
- package/dist/shared/plan-mode-prompt.d.ts +1 -1
- package/dist/shared/plan-mode-prompt.js +1 -1
- package/dist/shared/prompt-sections.d.ts +163 -0
- package/dist/shared/prompt-sections.js +164 -0
- package/dist/shared/prompt-sections.js.map +1 -0
- package/dist/shared/skill-resolver.d.ts +4 -2
- package/dist/shared/skill-resolver.js +4 -2
- package/dist/shared/skill-resolver.js.map +1 -1
- package/dist/shared/tool-rounds.d.ts +32 -0
- package/dist/shared/tool-rounds.js +43 -0
- package/dist/shared/tool-rounds.js.map +1 -1
- package/dist/shared/tool-row.d.ts +34 -0
- package/dist/shared/tool-row.js +52 -0
- package/dist/shared/tool-row.js.map +1 -1
- package/dist/shared/workspace/session-provision.d.ts +3 -3
- package/dist/shared/workspace/session-provision.js +3 -3
- package/dist/shared/workspace/stigmer-link.d.ts +3 -3
- package/dist/shared/workspace/stigmer-link.js +3 -3
- package/dist/shared/workspace/writeback-coordinator.d.ts +15 -22
- package/dist/shared/workspace/writeback-coordinator.js +16 -53
- package/dist/shared/workspace/writeback-coordinator.js.map +1 -1
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js +1 -1
- package/package.json +4 -4
- package/src/__test-utils__/__tests__/harness-contract-self-check.test.ts +1 -0
- package/src/__test-utils__/execution-record-fixture.ts +18 -2
- package/src/__test-utils__/file-review-projection.ts +156 -0
- package/src/__test-utils__/git-workspace-fixture.ts +58 -0
- package/src/__test-utils__/harness-boot-order-child.ts +7 -2
- package/src/__test-utils__/harness-contract/contract.ts +33 -20
- package/src/__test-utils__/harness-contract/recording-sink.ts +11 -1
- package/src/__test-utils__/harness-contract/runtime-contract.ts +328 -10
- package/src/__test-utils__/harness-contract/scripted-adapter.ts +38 -6
- package/src/__test-utils__/harness-contract/types.ts +27 -3
- package/src/__test-utils__/hermetic-activity.ts +52 -14
- package/src/__test-utils__/model-registry-fixture.ts +32 -7
- package/src/__test-utils__/module-specifiers.ts +24 -0
- package/src/__test-utils__/turn-input-fixture.ts +2 -2
- package/src/__tests__/harness-boot-order.test.ts +9 -2
- package/src/activities/call-agent.ts +2 -2
- package/src/activities/execute-cursor/__test-utils__/contract-subject.ts +47 -3
- package/src/activities/execute-cursor/__test-utils__/hermetic-cursor.ts +2 -48
- package/src/activities/execute-cursor/__tests__/approval-decisions-agree.test.ts +1 -1
- package/src/activities/execute-cursor/__tests__/approval-gate.test.ts +6 -1
- package/src/activities/execute-cursor/__tests__/build-prompt.test.ts +52 -27
- package/src/activities/execute-cursor/__tests__/cas-observations.test.ts +25 -0
- package/src/activities/execute-cursor/__tests__/goldens/prompt.enhanced.everything.prompt.md +166 -0
- package/src/activities/execute-cursor/__tests__/goldens/prompt.enhanced.plan-mode.prompt.md +186 -0
- package/src/activities/execute-cursor/__tests__/goldens/prompt.hitl.recovery.prompt.md +191 -0
- package/src/activities/execute-cursor/__tests__/goldens/prompt.hitl.reinvocation.prompt.md +13 -0
- package/src/activities/execute-cursor/__tests__/goldens/prompt.resumed.prefixed.prompt.md +32 -0
- package/src/activities/execute-cursor/__tests__/goldens/prompt.resumed.raw.prompt.md +1 -0
- package/src/activities/execute-cursor/__tests__/hermetic/file-review-capture.test.ts +1 -2
- package/src/activities/execute-cursor/__tests__/hermetic/goldens/sdk-error-at-create.status.json +4 -0
- package/src/activities/execute-cursor/__tests__/hermetic/harness-contract.test.ts +24 -4
- package/src/activities/execute-cursor/__tests__/hermetic/stream-self-stop-arms.test.ts +6 -5
- package/src/activities/execute-cursor/__tests__/hermetic/thrown-error-arms.test.ts +9 -0
- package/src/activities/execute-cursor/__tests__/hitl-resume-history.test.ts +11 -8
- package/src/activities/execute-cursor/__tests__/prompt-goldens.test.ts +229 -0
- package/src/activities/execute-cursor/__tests__/same-identity-reproposal.test.ts +3 -3
- package/src/activities/execute-cursor/__tests__/sequential-gate-resume.test.ts +2 -2
- package/src/activities/execute-cursor/__tests__/turn-boundary.test.ts +78 -234
- package/src/activities/execute-cursor/__tests__/turn-stream.test.ts +1 -26
- package/src/activities/execute-cursor/adapter.ts +7 -5
- package/src/activities/execute-cursor/approval-policy.ts +5 -2
- package/src/activities/execute-cursor/approval-state.ts +16 -8
- package/src/activities/execute-cursor/cas-observations.ts +20 -2
- package/src/activities/execute-cursor/cursor-capabilities.ts +24 -5
- package/src/activities/execute-cursor/message-translator.ts +29 -41
- package/src/activities/execute-cursor/prompt-builder.ts +99 -157
- package/src/activities/execute-cursor/turn-boundary.ts +34 -140
- package/src/activities/execute-cursor/turn-settle.ts +37 -51
- package/src/activities/execute-cursor/turn-setup.ts +30 -70
- package/src/activities/execute-cursor/turn-stream.ts +8 -31
- package/src/activities/execute-cursor/turn.ts +5 -1
- package/src/activities/execute-deep-agent/__test-utils__/contract-subject.ts +391 -0
- package/src/activities/execute-deep-agent/__test-utils__/hermetic-deep-agent.ts +313 -0
- package/src/activities/execute-deep-agent/__test-utils__/hitl-script.ts +48 -0
- package/src/activities/execute-deep-agent/__test-utils__/scripted-model-module.ts +85 -0
- package/src/activities/execute-deep-agent/__test-utils__/scripted-model.ts +383 -36
- package/src/activities/execute-deep-agent/__tests__/adapter-graph-is-sdk-free.test.ts +76 -0
- package/src/activities/execute-deep-agent/__tests__/adapter-is-temporal-free.test.ts +47 -0
- package/src/activities/execute-deep-agent/__tests__/adapter.test.ts +80 -0
- package/src/activities/execute-deep-agent/__tests__/cas-capture-backend.test.ts +1 -1
- package/src/activities/execute-deep-agent/__tests__/execution-state-extended.test.ts +0 -2
- package/src/activities/execute-deep-agent/__tests__/execution-state.test.ts +0 -3
- package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.build-from-plan.prompt.md +172 -0
- package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.everything.prompt.md +167 -0
- package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.minimal.prompt.md +36 -0
- package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.plan-mode.prompt.md +178 -0
- package/src/activities/execute-deep-agent/__tests__/goldens/system-prompt.skills-below-threshold.prompt.md +158 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/file-review-capture.test.ts +210 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/approve-all-lease.turn1.status.json +74 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/approve-all-lease.turn2.status.json +104 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/approve.turn2.status.json +78 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/file-review-capture.status.json +138 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/gate.turn1.status.json +68 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/inline-artifact.turn1.status.json +154 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/inline-artifact.turn2.status.json +116 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/pause.loop.after-tool.status.json +69 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/pause.loop.mid-tool.status.json +67 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/plain-turn.status.json +47 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/platform-stop.status.json +68 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/recursion-limit.persisted.status.json +280 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/reject.turn2.status.json +77 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/resolution-error.status.json +18 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/sequential-gates.turn2.status.json +99 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/sequential-gates.turn3.status.json +109 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/skip.turn2.status.json +76 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/structured-output.text-fallback.status.json +51 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/structured-output.tier2.status.json +51 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/structured-output.tool-strategy.status.json +56 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/sub-agent-delegation.status.json +90 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/tool-call.status.json +70 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/goldens/worker-shutdown.status.json +71 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/harness-contract.test.ts +144 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/hitl-approve-all-lease.test.ts +184 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/hitl-round-trips.test.ts +203 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/hitl-sequential-gates.test.ts +170 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/inline-artifact-across-gate.test.ts +175 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/pause-vs-shutdown.test.ts +206 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/plain-turn.test.ts +142 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/platform-stop.test.ts +126 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/recursion-limit.test.ts +139 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/resolution-error.test.ts +122 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/structured-output.test.ts +236 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/sub-agent-delegation.test.ts +161 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/tool-call.test.ts +157 -0
- package/src/activities/execute-deep-agent/__tests__/hermetic/workspace-lock-timeout.test.ts +152 -0
- package/src/activities/execute-deep-agent/__tests__/hitl.test.ts +50 -122
- package/src/activities/execute-deep-agent/__tests__/inline-publisher.test.ts +8 -6
- package/src/activities/execute-deep-agent/__tests__/plan-mode-path-normalization.test.ts +2 -2
- package/src/activities/execute-deep-agent/__tests__/prompt-builder.test.ts +35 -35
- package/src/activities/execute-deep-agent/__tests__/prompt-goldens.test.ts +247 -0
- package/src/activities/execute-deep-agent/__tests__/scripted-model.test.ts +329 -0
- package/src/activities/execute-deep-agent/__tests__/status-builder-shared.test.ts +1 -20
- package/src/activities/execute-deep-agent/__tests__/streaming-side-effects.test.ts +78 -0
- package/src/activities/execute-deep-agent/__tests__/subagent-gitignored-capture.test.ts +1 -1
- package/src/activities/execute-deep-agent/__tests__/subagent-plan-mode-permissions.test.ts +2 -2
- package/src/activities/execute-deep-agent/__tests__/subagent-tracker.test.ts +57 -31
- package/src/activities/execute-deep-agent/__tests__/subagent-transformer.test.ts +19 -79
- package/src/activities/execute-deep-agent/__tests__/subagent-wiring.test.ts +15 -15
- package/src/activities/execute-deep-agent/__tests__/summarization-verification.test.ts +1 -1
- package/src/activities/execute-deep-agent/__tests__/turn-stream.test.ts +391 -0
- package/src/activities/execute-deep-agent/__tests__/v3-status-builder.test.ts +343 -38
- package/src/activities/execute-deep-agent/__tests__/vision-input.test.ts +5 -5
- package/src/activities/execute-deep-agent/adapter.ts +94 -0
- package/src/activities/execute-deep-agent/cas-capture-observer.ts +11 -0
- package/src/activities/execute-deep-agent/deep-agent-capabilities.ts +43 -0
- package/src/activities/execute-deep-agent/execution-state.ts +4 -13
- package/src/activities/execute-deep-agent/hitl.ts +100 -123
- package/src/activities/execute-deep-agent/prompt-builder.ts +141 -115
- package/src/activities/execute-deep-agent/status-builder-shared.ts +21 -84
- package/src/activities/execute-deep-agent/streaming-side-effects.ts +13 -19
- package/src/activities/execute-deep-agent/subagent-tracker.ts +77 -42
- package/src/activities/execute-deep-agent/subagent-transformer.ts +46 -105
- package/src/activities/execute-deep-agent/subagent-wiring.ts +5 -4
- package/src/activities/execute-deep-agent/turn-settle.ts +164 -0
- package/src/activities/execute-deep-agent/turn-setup.ts +541 -0
- package/src/activities/execute-deep-agent/turn-stream.ts +313 -0
- package/src/activities/execute-deep-agent/turn.ts +131 -0
- package/src/activities/execute-deep-agent/v3-event-recorder.ts +6 -5
- package/src/activities/execute-deep-agent/v3-events.ts +1 -1
- package/src/activities/execute-deep-agent/v3-status-builder.ts +84 -63
- package/src/harness/__tests__/approval-decisions.test.ts +116 -0
- package/src/harness/__tests__/capture.test.ts +268 -0
- package/src/harness/__tests__/mount-skills.test.ts +169 -0
- package/src/harness/__tests__/persist-chokepoint.test.ts +38 -2
- package/src/harness/__tests__/run-turn-deterministic.test.ts +94 -0
- package/src/harness/__tests__/run-turn.test.ts +39 -23
- package/src/harness/__tests__/turn-attachments-vision.test.ts +80 -0
- package/src/harness/__tests__/turn-context.test.ts +73 -26
- package/src/harness/approval-decisions.ts +104 -0
- package/src/harness/capabilities.ts +16 -5
- package/src/harness/capture.ts +301 -0
- package/src/harness/persist-chokepoint.ts +24 -4
- package/src/harness/run-turn.ts +120 -29
- package/src/harness/terminal-table.ts +61 -20
- package/src/harness/turn-context.ts +208 -111
- package/src/harness/types.ts +66 -12
- package/src/harness-adapters.ts +17 -9
- package/src/middleware/__tests__/approval-gate.test.ts +1 -1
- package/src/middleware/__tests__/cost-advisory.test.ts +113 -0
- package/src/middleware/approval-gate.ts +2 -2
- package/src/middleware/{cost-cap.ts → cost-advisory.ts} +40 -70
- package/src/middleware/index.ts +29 -29
- package/src/middleware/types.ts +3 -2
- package/src/runner-manager.ts +0 -8
- package/src/runner.ts +0 -8
- package/src/shared/__tests__/attachment-naming.test.ts +6 -19
- package/src/shared/__tests__/attachment-resolver.test.ts +158 -8
- package/src/shared/__tests__/attachment-zip.test.ts +236 -0
- package/src/shared/__tests__/bedrock-adapter.test.ts +1 -1
- package/src/shared/__tests__/blueprint-resolver.test.ts +43 -0
- package/src/shared/__tests__/cost-guard.test.ts +3 -3
- package/src/shared/__tests__/prompt-sections.test.ts +167 -0
- package/src/shared/__tests__/secret-leak-scan.test.ts +2 -2
- package/src/{activities/execute-deep-agent → shared}/__tests__/stamp-flowed-rows.test.ts +33 -10
- package/src/shared/__tests__/vertex-adapter.test.ts +1 -1
- package/src/shared/attachment-resolver.ts +218 -55
- package/src/shared/attachment-zip.ts +281 -0
- package/src/shared/connect-backfill.ts +9 -11
- package/src/shared/cost-guard.ts +3 -3
- package/src/shared/execution-status-writer.ts +5 -5
- package/src/shared/extract-structured-output.ts +8 -7
- package/src/shared/filereview/__tests__/capture.test.ts +86 -4
- package/src/shared/filereview/__tests__/cas-progress.test.ts +2 -1
- package/src/shared/filereview/cas-progress.ts +5 -19
- package/src/shared/filereview/cas-touched.ts +89 -0
- package/src/shared/filereview/index.ts +4 -2
- package/src/shared/mcp-resolver.ts +4 -5
- package/src/shared/persist-decision.ts +3 -4
- package/src/shared/plan-mode-permissions.ts +2 -2
- package/src/shared/plan-mode-prompt.ts +1 -1
- package/src/shared/prompt-sections.ts +256 -0
- package/src/shared/skill-resolver.ts +4 -2
- package/src/shared/tool-rounds.ts +48 -0
- package/src/shared/tool-row.ts +61 -0
- package/src/shared/workspace/__tests__/stigmer-link.test.ts +7 -22
- package/src/shared/workspace/__tests__/writeback-coordinator.test.ts +20 -61
- package/src/shared/workspace/session-provision.ts +3 -3
- package/src/shared/workspace/stigmer-link.ts +3 -3
- package/src/shared/workspace/writeback-coordinator.ts +16 -64
- package/src/tools/index.ts +1 -1
- package/dist/activities/execute-cursor/capture-flow.d.ts +0 -142
- package/dist/activities/execute-cursor/capture-flow.js +0 -285
- package/dist/activities/execute-cursor/capture-flow.js.map +0 -1
- package/dist/activities/execute-cursor/command-provenance.d.ts +0 -48
- package/dist/activities/execute-cursor/command-provenance.js +0 -38
- package/dist/activities/execute-cursor/command-provenance.js.map +0 -1
- package/dist/activities/execute-deep-agent/attachment-injector.d.ts +0 -134
- package/dist/activities/execute-deep-agent/attachment-injector.js +0 -386
- package/dist/activities/execute-deep-agent/attachment-injector.js.map +0 -1
- package/dist/activities/execute-deep-agent/command-provenance.d.ts +0 -61
- package/dist/activities/execute-deep-agent/command-provenance.js +0 -72
- package/dist/activities/execute-deep-agent/command-provenance.js.map +0 -1
- package/dist/activities/execute-deep-agent/environment.d.ts +0 -24
- package/dist/activities/execute-deep-agent/environment.js +0 -58
- package/dist/activities/execute-deep-agent/environment.js.map +0 -1
- package/dist/activities/execute-deep-agent/event-recorder.d.ts +0 -21
- package/dist/activities/execute-deep-agent/event-recorder.js +0 -67
- package/dist/activities/execute-deep-agent/event-recorder.js.map +0 -1
- package/dist/activities/execute-deep-agent/index.d.ts +0 -15
- package/dist/activities/execute-deep-agent/index.js +0 -918
- package/dist/activities/execute-deep-agent/index.js.map +0 -1
- package/dist/activities/execute-deep-agent/mcp-gate.d.ts +0 -28
- package/dist/activities/execute-deep-agent/mcp-gate.js +0 -22
- package/dist/activities/execute-deep-agent/mcp-gate.js.map +0 -1
- package/dist/activities/execute-deep-agent/post-stream.d.ts +0 -23
- package/dist/activities/execute-deep-agent/post-stream.js +0 -71
- package/dist/activities/execute-deep-agent/post-stream.js.map +0 -1
- package/dist/activities/execute-deep-agent/setup.d.ts +0 -136
- package/dist/activities/execute-deep-agent/setup.js +0 -767
- package/dist/activities/execute-deep-agent/setup.js.map +0 -1
- package/dist/activities/execute-deep-agent/stamp-flowed-rows.d.ts +0 -36
- package/dist/activities/execute-deep-agent/stamp-flowed-rows.js +0 -56
- package/dist/activities/execute-deep-agent/stamp-flowed-rows.js.map +0 -1
- package/dist/activities/execute-deep-agent/status-builder.d.ts +0 -93
- package/dist/activities/execute-deep-agent/status-builder.js +0 -365
- package/dist/activities/execute-deep-agent/status-builder.js.map +0 -1
- package/dist/activities/execute-deep-agent/streaming-terminal.d.ts +0 -21
- package/dist/activities/execute-deep-agent/streaming-terminal.js +0 -80
- package/dist/activities/execute-deep-agent/streaming-terminal.js.map +0 -1
- package/dist/activities/execute-deep-agent/streaming-v3.d.ts +0 -13
- package/dist/activities/execute-deep-agent/streaming-v3.js +0 -174
- package/dist/activities/execute-deep-agent/streaming-v3.js.map +0 -1
- package/dist/activities/execute-deep-agent/streaming.d.ts +0 -81
- package/dist/activities/execute-deep-agent/streaming.js +0 -160
- package/dist/activities/execute-deep-agent/streaming.js.map +0 -1
- package/dist/middleware/cost-cap.d.ts +0 -22
- package/dist/middleware/cost-cap.js.map +0 -1
- package/dist/middleware/graceful-stop.d.ts +0 -17
- package/dist/middleware/graceful-stop.js +0 -63
- package/dist/middleware/graceful-stop.js.map +0 -1
- package/dist/shared/skill-writer.d.ts +0 -75
- package/dist/shared/skill-writer.js +0 -207
- package/dist/shared/skill-writer.js.map +0 -1
- package/src/activities/execute-cursor/__tests__/capture-flow.test.ts +0 -1032
- package/src/activities/execute-cursor/__tests__/command-provenance.test.ts +0 -240
- package/src/activities/execute-cursor/__tests__/progress-substrate.test.ts +0 -169
- package/src/activities/execute-cursor/capture-flow.ts +0 -379
- package/src/activities/execute-cursor/command-provenance.ts +0 -73
- package/src/activities/execute-deep-agent/__tests__/attachment-injector.test.ts +0 -1161
- package/src/activities/execute-deep-agent/__tests__/command-provenance.test.ts +0 -252
- package/src/activities/execute-deep-agent/__tests__/environment.test.ts +0 -108
- package/src/activities/execute-deep-agent/__tests__/event-recorder.test.ts +0 -150
- package/src/activities/execute-deep-agent/__tests__/hitl-integration.test.ts +0 -226
- package/src/activities/execute-deep-agent/__tests__/hitl-reject.test.ts +0 -318
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-approve-all.test.ts +0 -351
- package/src/activities/execute-deep-agent/__tests__/hitl-resume-history.test.ts +0 -293
- package/src/activities/execute-deep-agent/__tests__/index.test.ts +0 -99
- package/src/activities/execute-deep-agent/__tests__/mcp-gate.test.ts +0 -44
- package/src/activities/execute-deep-agent/__tests__/post-stream.test.ts +0 -112
- package/src/activities/execute-deep-agent/__tests__/sequential-gate-resume.test.ts +0 -358
- package/src/activities/execute-deep-agent/__tests__/status-builder.test.ts +0 -1933
- package/src/activities/execute-deep-agent/__tests__/streaming-terminal.test.ts +0 -40
- package/src/activities/execute-deep-agent/__tests__/streaming-v3.test.ts +0 -597
- package/src/activities/execute-deep-agent/__tests__/streaming.test.ts +0 -508
- package/src/activities/execute-deep-agent/attachment-injector.ts +0 -627
- package/src/activities/execute-deep-agent/command-provenance.ts +0 -102
- package/src/activities/execute-deep-agent/environment.ts +0 -76
- package/src/activities/execute-deep-agent/event-recorder.ts +0 -95
- package/src/activities/execute-deep-agent/index.ts +0 -1072
- package/src/activities/execute-deep-agent/mcp-gate.ts +0 -37
- package/src/activities/execute-deep-agent/post-stream.ts +0 -109
- package/src/activities/execute-deep-agent/setup.ts +0 -1081
- package/src/activities/execute-deep-agent/stamp-flowed-rows.ts +0 -64
- package/src/activities/execute-deep-agent/status-builder.ts +0 -481
- package/src/activities/execute-deep-agent/streaming-terminal.ts +0 -106
- package/src/activities/execute-deep-agent/streaming-v3.ts +0 -277
- package/src/activities/execute-deep-agent/streaming.ts +0 -313
- package/src/middleware/__tests__/cost-cap.test.ts +0 -192
- package/src/middleware/__tests__/graceful-stop.test.ts +0 -105
- package/src/middleware/graceful-stop.ts +0 -86
- package/src/shared/__tests__/skill-writer.test.ts +0 -372
- package/src/shared/skill-writer.ts +0 -266
|
@@ -1,627 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Attachment injection for deep agent execution.
|
|
3
|
-
*
|
|
4
|
-
* Downloads attachments from artifact storage (cloud) or local filesystem,
|
|
5
|
-
* validates ZIP archives with security guards, and writes files into the
|
|
6
|
-
* workspace via the platform mount namespace (.stigmer/inputs/).
|
|
7
|
-
*
|
|
8
|
-
* Security model: attachments are untrusted user uploads. ZIP archives are
|
|
9
|
-
* parsed from the central directory — the format's authoritative index —
|
|
10
|
-
* via the shared structural layer (@stigmer/zip-structure; issue #567,
|
|
11
|
-
* which killed this module's local-header walk: it silently truncated
|
|
12
|
-
* stored streaming entries and rejected Go-default archives outright).
|
|
13
|
-
* Every entry is validated for path traversal, zip bombs, and format
|
|
14
|
-
* integrity BEFORE any extraction occurs, and because the central
|
|
15
|
-
* directory's sizes are declarations an attacker controls, decompression
|
|
16
|
-
* re-enforces them: an entry whose actual output disagrees with its
|
|
17
|
-
* declared size aborts the injection.
|
|
18
|
-
*
|
|
19
|
-
* Error model: fail-hard. Any attachment failure aborts the entire injection
|
|
20
|
-
* and propagates a descriptive error. Attachments are explicit user inputs —
|
|
21
|
-
* running with partial inputs produces silently incorrect results. This is
|
|
22
|
-
* deliberately the OPPOSITE policy from the skill-artifact reader
|
|
23
|
-
* (shared/zip-extract.ts, non-fatal empty return): skill artifacts were
|
|
24
|
-
* structurally vouched for by the push gates, attachments never are.
|
|
25
|
-
*
|
|
26
|
-
* Duplicate names are NOT a failure (issue #364): a default-derived mount
|
|
27
|
-
* path that collides is renamed with the platform's `stem-2.ext` semantics
|
|
28
|
-
* (shared/attachment-naming.ts) and the rename is disclosed in the prompt's
|
|
29
|
-
* Input Files section. Only two attachments EXPLICITLY pinning the same
|
|
30
|
-
* `mountPath` still abort — that is a user contradiction no rename can
|
|
31
|
-
* honestly resolve.
|
|
32
|
-
*/
|
|
33
|
-
|
|
34
|
-
import { readFile } from "node:fs/promises";
|
|
35
|
-
import { createInflateRaw } from "node:zlib";
|
|
36
|
-
import { posix } from "node:path";
|
|
37
|
-
import type { Attachment } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/spec_pb";
|
|
38
|
-
import type { WorkspaceBackend } from "../../shared/workspace/types.js";
|
|
39
|
-
import type { ArtifactStorage } from "../../shared/artifact-storage.js";
|
|
40
|
-
import { mintAttachmentDownloadUrl } from "../../shared/attachment-download-urls.js";
|
|
41
|
-
import { allocateUniqueName } from "../../shared/attachment-naming.js";
|
|
42
|
-
import type {
|
|
43
|
-
VisionBudget,
|
|
44
|
-
VisionDegradedReason,
|
|
45
|
-
VisionImage,
|
|
46
|
-
} from "../../shared/attachment-vision.js";
|
|
47
|
-
import {
|
|
48
|
-
EOCD_MIN_SIZE,
|
|
49
|
-
parseZipStructure,
|
|
50
|
-
type ZipStructuralEntry,
|
|
51
|
-
} from "@stigmer/zip-structure";
|
|
52
|
-
|
|
53
|
-
// ── Constants ────────────────────────────────────────────────────────
|
|
54
|
-
|
|
55
|
-
const MAX_ZIP_FILES = 1000;
|
|
56
|
-
const MAX_ZIP_EXTRACTED_SIZE = 100 * 1024 * 1024; // 100 MB
|
|
57
|
-
const DEFAULT_INPUTS_PREFIX = ".stigmer/inputs";
|
|
58
|
-
|
|
59
|
-
export { MAX_ZIP_FILES, MAX_ZIP_EXTRACTED_SIZE };
|
|
60
|
-
|
|
61
|
-
// ── Types ────────────────────────────────────────────────────────────
|
|
62
|
-
|
|
63
|
-
export interface InjectedFile {
|
|
64
|
-
/** The final on-disk basename — after any duplicate rename, so it always
|
|
65
|
-
* agrees with {@link path} and with the vision payload's image label. */
|
|
66
|
-
readonly filename: string;
|
|
67
|
-
readonly path: string;
|
|
68
|
-
readonly sizeBytes: number;
|
|
69
|
-
/**
|
|
70
|
-
* The attachment's original filename, present only when a duplicate name
|
|
71
|
-
* was renamed (shared/attachment-naming.ts) — rendered as disclosure in
|
|
72
|
-
* the prompt's Input Files section.
|
|
73
|
-
*/
|
|
74
|
-
readonly renamedFrom?: string;
|
|
75
|
-
/** Present when the attachment was accepted into the turn's vision payload. */
|
|
76
|
-
readonly vision?: VisionImage;
|
|
77
|
-
/**
|
|
78
|
-
* Present when the attachment was plausibly an image but could not ride
|
|
79
|
-
* inline (see {@link VisionDegradedReason}) — disclosed in the system prompt
|
|
80
|
-
* so the agent never silently ignores a photo the user believes it can see.
|
|
81
|
-
* Attachments that were never image-shaped carry neither field.
|
|
82
|
-
*/
|
|
83
|
-
readonly visionDegraded?: VisionDegradedReason;
|
|
84
|
-
/**
|
|
85
|
-
* A download URL for the attachment's stored object, listed in the system
|
|
86
|
-
* prompt's Input Files section so the agent can hand the file to a tool
|
|
87
|
-
* whose backend cannot read the sandbox filesystem (issue #532). Minted
|
|
88
|
-
* whenever the attachment has a `storageKey` and a usable storage; never
|
|
89
|
-
* present on extracted ZIP entries (no attachment-level object). Absent
|
|
90
|
-
* when there is nothing to mint or the mint failed (non-fatal — policy,
|
|
91
|
-
* degrade log, and prompt wording live in
|
|
92
|
-
* shared/attachment-download-urls.ts).
|
|
93
|
-
*/
|
|
94
|
-
readonly downloadUrl?: string;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export interface InjectAttachmentsOptions {
|
|
98
|
-
readonly backend: WorkspaceBackend;
|
|
99
|
-
readonly attachments: readonly Attachment[];
|
|
100
|
-
/**
|
|
101
|
-
* Artifact storage for cloud-mode attachment download. `undefined` when the
|
|
102
|
-
* runner could not build a store (proxy misconfig). With no attachments this is
|
|
103
|
-
* never touched (the function early-returns); a cloud-mode attachment that
|
|
104
|
-
* genuinely needs storage surfaces a clear {@link AttachmentInjectionError}
|
|
105
|
-
* rather than dereferencing undefined.
|
|
106
|
-
*/
|
|
107
|
-
readonly storage: ArtifactStorage | undefined;
|
|
108
|
-
readonly isLocalMode: boolean;
|
|
109
|
-
/**
|
|
110
|
-
* The turn's vision selector (attachment-vision.ts owns all policy).
|
|
111
|
-
* `undefined` disables inline image delivery; file materialization is
|
|
112
|
-
* identical either way — vision is strictly additive. Archives (`extract`)
|
|
113
|
-
* are never offered: an image inside a ZIP has no attachment-level bytes.
|
|
114
|
-
*/
|
|
115
|
-
readonly visionBudget?: VisionBudget;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export interface ZipEntryInfo {
|
|
119
|
-
readonly relativePath: string;
|
|
120
|
-
readonly uncompressedSize: number;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// ── Errors ───────────────────────────────────────────────────────────
|
|
124
|
-
|
|
125
|
-
export class AttachmentInjectionError extends Error {
|
|
126
|
-
readonly attachmentFilename: string;
|
|
127
|
-
readonly reason: string;
|
|
128
|
-
|
|
129
|
-
constructor(attachmentFilename: string, reason: string) {
|
|
130
|
-
super(`Attachment '${attachmentFilename}': ${reason}`);
|
|
131
|
-
this.name = "AttachmentInjectionError";
|
|
132
|
-
this.attachmentFilename = attachmentFilename;
|
|
133
|
-
this.reason = reason;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export class AttachmentValidationError extends Error {
|
|
138
|
-
readonly attachmentFilename: string;
|
|
139
|
-
readonly reason: string;
|
|
140
|
-
|
|
141
|
-
constructor(attachmentFilename: string, reason: string) {
|
|
142
|
-
super(`Attachment '${attachmentFilename}': ${reason}`);
|
|
143
|
-
this.name = "AttachmentValidationError";
|
|
144
|
-
this.attachmentFilename = attachmentFilename;
|
|
145
|
-
this.reason = reason;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// ── ZIP Validation (pure functions) ──────────────────────────────────
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Validate a ZIP archive for safe extraction. Returns the manifest of
|
|
153
|
-
* file entries (directories excluded) if the archive passes all checks.
|
|
154
|
-
*
|
|
155
|
-
* Security checks enforced:
|
|
156
|
-
* 1. Valid ZIP format (readable central directory; fail-hard, see module doc)
|
|
157
|
-
* 2. No absolute paths (entries starting with / or \)
|
|
158
|
-
* 3. No path traversal (.. components)
|
|
159
|
-
* 4. No null bytes in filenames
|
|
160
|
-
* 5. No duplicate entry paths (a contradictory manifest)
|
|
161
|
-
* 6. Only supported compression methods (stored, deflate) — checked here
|
|
162
|
-
* so an unsupported entry can never abort extraction after earlier
|
|
163
|
-
* entries were already written
|
|
164
|
-
* 7. Non-empty archive (at least one file entry)
|
|
165
|
-
* 8. File count within limits (max 1000)
|
|
166
|
-
* 9. Total declared uncompressed size within limits (max 100 MB) —
|
|
167
|
-
* declarations are re-enforced against actual output at decompression
|
|
168
|
-
*/
|
|
169
|
-
export function validateZipForExtraction(
|
|
170
|
-
zipData: Buffer,
|
|
171
|
-
sourceFilename: string,
|
|
172
|
-
): ZipEntryInfo[] {
|
|
173
|
-
return parseAndValidateZip(zipData, sourceFilename).map(
|
|
174
|
-
({ relativePath, uncompressedSize }) => ({ relativePath, uncompressedSize }),
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* A validated file entry, still carrying its payload slice. Validation and
|
|
180
|
-
* extraction share these records — parsing happens exactly once, so "the
|
|
181
|
-
* manifest promised a file extraction cannot find" is unrepresentable
|
|
182
|
-
* (the old two-walker design silently skipped such entries).
|
|
183
|
-
*/
|
|
184
|
-
interface ValidatedZipEntry extends ZipEntryInfo {
|
|
185
|
-
readonly compressionMethod: number;
|
|
186
|
-
readonly compressedData: Uint8Array;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function parseAndValidateZip(
|
|
190
|
-
zipData: Buffer,
|
|
191
|
-
sourceFilename: string,
|
|
192
|
-
): ValidatedZipEntry[] {
|
|
193
|
-
if (zipData.length < EOCD_MIN_SIZE) {
|
|
194
|
-
throw new AttachmentValidationError(
|
|
195
|
-
sourceFilename,
|
|
196
|
-
"not a valid ZIP archive (file too small)",
|
|
197
|
-
);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
let structural: ZipStructuralEntry[];
|
|
201
|
-
try {
|
|
202
|
-
structural = parseZipStructure(zipData);
|
|
203
|
-
} catch (err) {
|
|
204
|
-
throw new AttachmentValidationError(
|
|
205
|
-
sourceFilename,
|
|
206
|
-
`not a valid ZIP archive (${err instanceof Error ? err.message : String(err)})`,
|
|
207
|
-
);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const entries: ValidatedZipEntry[] = [];
|
|
211
|
-
const seenPaths = new Set<string>();
|
|
212
|
-
let totalUncompressed = 0;
|
|
213
|
-
|
|
214
|
-
for (const entry of structural) {
|
|
215
|
-
if (entry.name.includes("\u0000")) {
|
|
216
|
-
throw new AttachmentValidationError(
|
|
217
|
-
sourceFilename,
|
|
218
|
-
"contains a filename with null bytes and cannot be safely extracted",
|
|
219
|
-
);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
if (entry.isDirectory) continue;
|
|
223
|
-
|
|
224
|
-
if (entry.name.startsWith("/") || entry.name.startsWith("\\")) {
|
|
225
|
-
throw new AttachmentValidationError(
|
|
226
|
-
sourceFilename,
|
|
227
|
-
`contains an absolute path entry and cannot be safely extracted: ${entry.name}`,
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (hasPathTraversal(entry.name)) {
|
|
232
|
-
throw new AttachmentValidationError(
|
|
233
|
-
sourceFilename,
|
|
234
|
-
`contains a path traversal entry and cannot be safely extracted: ${entry.name}`,
|
|
235
|
-
);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
if (seenPaths.has(entry.name)) {
|
|
239
|
-
throw new AttachmentValidationError(
|
|
240
|
-
sourceFilename,
|
|
241
|
-
`contains duplicate entry '${entry.name}' and cannot be safely extracted`,
|
|
242
|
-
);
|
|
243
|
-
}
|
|
244
|
-
seenPaths.add(entry.name);
|
|
245
|
-
|
|
246
|
-
if (entry.compressionMethod !== 0 && entry.compressionMethod !== 8) {
|
|
247
|
-
throw new AttachmentValidationError(
|
|
248
|
-
sourceFilename,
|
|
249
|
-
`entry '${entry.name}' uses unsupported compression method ${entry.compressionMethod}`,
|
|
250
|
-
);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
entries.push({
|
|
254
|
-
relativePath: entry.name,
|
|
255
|
-
uncompressedSize: entry.uncompressedSize,
|
|
256
|
-
compressionMethod: entry.compressionMethod,
|
|
257
|
-
compressedData: entry.compressedData,
|
|
258
|
-
});
|
|
259
|
-
totalUncompressed += entry.uncompressedSize;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
if (entries.length === 0) {
|
|
263
|
-
throw new AttachmentValidationError(
|
|
264
|
-
sourceFilename,
|
|
265
|
-
"is an empty ZIP archive (no file entries)",
|
|
266
|
-
);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
if (entries.length > MAX_ZIP_FILES) {
|
|
270
|
-
throw new AttachmentValidationError(
|
|
271
|
-
sourceFilename,
|
|
272
|
-
`contains ${entries.length} files (limit: ${MAX_ZIP_FILES})`,
|
|
273
|
-
);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (totalUncompressed > MAX_ZIP_EXTRACTED_SIZE) {
|
|
277
|
-
const sizeMb = (totalUncompressed / (1024 * 1024)).toFixed(1);
|
|
278
|
-
const limitMb = (MAX_ZIP_EXTRACTED_SIZE / (1024 * 1024)).toFixed(0);
|
|
279
|
-
throw new AttachmentValidationError(
|
|
280
|
-
sourceFilename,
|
|
281
|
-
`would extract to ${sizeMb} MB (limit: ${limitMb} MB)`,
|
|
282
|
-
);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
return entries.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// ── Attachment Injection ─────────────────────────────────────────────
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Download, validate, and inject all attachments into the workspace.
|
|
292
|
-
*
|
|
293
|
-
* Resolves all mount paths before any downloads begin: duplicate
|
|
294
|
-
* default-derived names are renamed (never fatal — see module doc), and an
|
|
295
|
-
* explicit-mountPath contradiction throws before any bytes move.
|
|
296
|
-
* On any failure, throws AttachmentInjectionError with an actionable message.
|
|
297
|
-
*/
|
|
298
|
-
export async function injectAttachments(opts: InjectAttachmentsOptions): Promise<InjectedFile[]> {
|
|
299
|
-
const { backend, attachments, storage, isLocalMode, visionBudget } = opts;
|
|
300
|
-
|
|
301
|
-
if (attachments.length === 0) return [];
|
|
302
|
-
|
|
303
|
-
// Phase 1: Pre-compute mount paths and detect collisions
|
|
304
|
-
const mountPaths = resolveMountPaths(attachments);
|
|
305
|
-
|
|
306
|
-
// Phase 2: Download, validate, and write each attachment
|
|
307
|
-
const injectedFiles: InjectedFile[] = [];
|
|
308
|
-
|
|
309
|
-
for (const attachment of attachments) {
|
|
310
|
-
const content = await downloadAttachment(attachment, storage, isLocalMode);
|
|
311
|
-
const { path: mountPath, renamedFrom } = mountPaths.get(attachment)!;
|
|
312
|
-
|
|
313
|
-
if (attachment.extract) {
|
|
314
|
-
const entries = parseAndValidateZip(content, attachment.filename);
|
|
315
|
-
const extracted = await extractZipToWorkspace(
|
|
316
|
-
entries, mountPath, backend, attachment.filename,
|
|
317
|
-
);
|
|
318
|
-
// A renamed mount DIR is visible through every extracted path; the
|
|
319
|
-
// entries themselves were not renamed, so they carry no renamedFrom
|
|
320
|
-
// (per-file disclosure would misattribute the rename). Extracted
|
|
321
|
-
// entries also carry no downloadUrl: the stored object is the ZIP, not
|
|
322
|
-
// any listed file, and a URL to bytes that differ from the listing
|
|
323
|
-
// would be a false promise.
|
|
324
|
-
injectedFiles.push(...extracted);
|
|
325
|
-
} else {
|
|
326
|
-
await backend.writeFileBuffer(mountPath, content);
|
|
327
|
-
// The final basename is the canonical filename: after a duplicate
|
|
328
|
-
// rename the original attachment.filename no longer names the file on
|
|
329
|
-
// disk, and the vision label must match what the prompt lists or the
|
|
330
|
-
// agent sees two images with one indistinguishable name.
|
|
331
|
-
const filename = posix.basename(mountPath);
|
|
332
|
-
// The bytes are already in hand for the workspace write — offer them to
|
|
333
|
-
// the vision budget before they go out of scope (the sniff decides
|
|
334
|
-
// eligibility; the budget owns every size/count rule).
|
|
335
|
-
const vision = visionBudget?.offer(filename, attachment.contentType, content);
|
|
336
|
-
const downloadUrl = await mintAttachmentDownloadUrl(
|
|
337
|
-
storage, attachment.storageKey, filename,
|
|
338
|
-
);
|
|
339
|
-
injectedFiles.push({
|
|
340
|
-
filename,
|
|
341
|
-
path: mountPath,
|
|
342
|
-
sizeBytes: content.length,
|
|
343
|
-
...(renamedFrom !== undefined ? { renamedFrom } : {}),
|
|
344
|
-
...(vision?.kind === "accepted" ? { vision: vision.image } : {}),
|
|
345
|
-
...(vision?.kind === "degraded" ? { visionDegraded: vision.reason } : {}),
|
|
346
|
-
...(downloadUrl !== undefined ? { downloadUrl } : {}),
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
console.log(
|
|
352
|
-
`[attachment-injector] Injected ${injectedFiles.length} file(s) into workspace`,
|
|
353
|
-
);
|
|
354
|
-
|
|
355
|
-
return injectedFiles;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// ── Internal Helpers ─────────────────────────────────────────────────
|
|
359
|
-
|
|
360
|
-
interface ResolvedMountPath {
|
|
361
|
-
readonly path: string;
|
|
362
|
-
/** Present when a default-derived name was uniquified (issue #364). */
|
|
363
|
-
readonly renamedFrom?: string;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
function resolveMountPaths(
|
|
367
|
-
attachments: readonly Attachment[],
|
|
368
|
-
): Map<Attachment, ResolvedMountPath> {
|
|
369
|
-
const result = new Map<Attachment, ResolvedMountPath>();
|
|
370
|
-
const pathToAttachment = new Map<string, Attachment>();
|
|
371
|
-
|
|
372
|
-
// Pass 1: explicit mount paths claim their exact targets first. Two
|
|
373
|
-
// attachments explicitly pinning the SAME path is a user contradiction no
|
|
374
|
-
// rename can honestly resolve — keep rejecting with the actionable message.
|
|
375
|
-
for (const attachment of attachments) {
|
|
376
|
-
if (!attachment.mountPath) continue;
|
|
377
|
-
const mountPath = resolveExplicitMountPath(attachment);
|
|
378
|
-
const existing = pathToAttachment.get(mountPath);
|
|
379
|
-
|
|
380
|
-
if (existing) {
|
|
381
|
-
throw new AttachmentInjectionError(
|
|
382
|
-
attachment.filename,
|
|
383
|
-
`mount path '${mountPath}' collides with attachment '${existing.filename}'. ` +
|
|
384
|
-
"Set distinct mountPath values on the attachments to resolve this conflict.",
|
|
385
|
-
);
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
pathToAttachment.set(mountPath, attachment);
|
|
389
|
-
result.set(attachment, { path: mountPath });
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// Pass 2: default-derived names (`.stigmer/inputs/{filename}`) uniquify
|
|
393
|
-
// around everything already taken — other defaults AND explicit paths that
|
|
394
|
-
// landed inside the inputs prefix — instead of failing the execution
|
|
395
|
-
// (issue #364). The taken-set is seeded with the basenames the explicit
|
|
396
|
-
// pass claimed directly under the prefix.
|
|
397
|
-
const takenNames = new Set<string>();
|
|
398
|
-
for (const path of pathToAttachment.keys()) {
|
|
399
|
-
if (path.startsWith(`${DEFAULT_INPUTS_PREFIX}/`)) {
|
|
400
|
-
const rest = path.slice(DEFAULT_INPUTS_PREFIX.length + 1);
|
|
401
|
-
if (rest.length > 0 && !rest.includes("/")) takenNames.add(rest);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
for (const attachment of attachments) {
|
|
405
|
-
if (attachment.mountPath) continue;
|
|
406
|
-
const { name, renamedFrom } = allocateUniqueName(
|
|
407
|
-
deriveDefaultFilename(attachment),
|
|
408
|
-
takenNames,
|
|
409
|
-
);
|
|
410
|
-
const path = `${DEFAULT_INPUTS_PREFIX}/${name}`;
|
|
411
|
-
pathToAttachment.set(path, attachment);
|
|
412
|
-
result.set(
|
|
413
|
-
attachment,
|
|
414
|
-
renamedFrom !== undefined ? { path, renamedFrom } : { path },
|
|
415
|
-
);
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
return result;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
function resolveExplicitMountPath(attachment: Attachment): string {
|
|
422
|
-
const cleaned = attachment.mountPath.replace(/^\/+/, "");
|
|
423
|
-
if (cleaned.length === 0) {
|
|
424
|
-
throw new AttachmentInjectionError(
|
|
425
|
-
attachment.filename,
|
|
426
|
-
"mountPath resolves to an empty path after removing leading slashes",
|
|
427
|
-
);
|
|
428
|
-
}
|
|
429
|
-
// A caller-supplied mount path is untrusted. Stripping leading slashes does
|
|
430
|
-
// not stop `..` segments from climbing out of the workspace root on the
|
|
431
|
-
// non-`.stigmer/` branch (the `.stigmer/`-routed branch is already guarded
|
|
432
|
-
// by LocalWorkspaceBackend.resolvePath). Reject any path that normalizes to
|
|
433
|
-
// an escape before it reaches the backend write.
|
|
434
|
-
if (escapesRoot(cleaned)) {
|
|
435
|
-
throw new AttachmentInjectionError(
|
|
436
|
-
attachment.filename,
|
|
437
|
-
`mount path '${attachment.mountPath}' escapes the workspace root`,
|
|
438
|
-
);
|
|
439
|
-
}
|
|
440
|
-
return cleaned;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
// Derives the single-component filename an attachment without an explicit
|
|
444
|
-
// mountPath materializes under (the name that pass 2 uniquifies).
|
|
445
|
-
function deriveDefaultFilename(attachment: Attachment): string {
|
|
446
|
-
const rawName = attachment.filename || deriveFilename(attachment.storageKey);
|
|
447
|
-
if (!rawName) {
|
|
448
|
-
throw new AttachmentInjectionError(
|
|
449
|
-
"(unknown)",
|
|
450
|
-
"attachment has neither filename nor storageKey — cannot determine mount path",
|
|
451
|
-
);
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
// The filename is untrusted; reduce it to a single path component so it lands
|
|
455
|
-
// directly under the inputs prefix rather than steering the write elsewhere.
|
|
456
|
-
const filename = posix.basename(rawName);
|
|
457
|
-
if (filename === "" || filename === "." || filename === "..") {
|
|
458
|
-
throw new AttachmentInjectionError(
|
|
459
|
-
rawName,
|
|
460
|
-
`filename '${rawName}' does not yield a usable name for materialization`,
|
|
461
|
-
);
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
return filename;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
// escapesRoot reports whether a workspace-relative mount path would climb out
|
|
468
|
-
// of the workspace root once normalized. Mount paths are forward-slash,
|
|
469
|
-
// workspace-relative strings, so posix normalization is the correct semantics.
|
|
470
|
-
function escapesRoot(relPath: string): boolean {
|
|
471
|
-
const normalized = posix.normalize(relPath);
|
|
472
|
-
return (
|
|
473
|
-
normalized === ".." ||
|
|
474
|
-
normalized.startsWith("../") ||
|
|
475
|
-
posix.isAbsolute(normalized)
|
|
476
|
-
);
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
function deriveFilename(storageKey: string): string {
|
|
480
|
-
if (!storageKey) return "";
|
|
481
|
-
const parts = storageKey.split("/");
|
|
482
|
-
return parts[parts.length - 1] || "";
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
async function downloadAttachment(
|
|
486
|
-
attachment: Attachment,
|
|
487
|
-
storage: ArtifactStorage | undefined,
|
|
488
|
-
isLocalMode: boolean,
|
|
489
|
-
): Promise<Buffer> {
|
|
490
|
-
// Local mode fast path: read directly from filesystem
|
|
491
|
-
if (isLocalMode && attachment.localPath) {
|
|
492
|
-
try {
|
|
493
|
-
return await readFile(attachment.localPath);
|
|
494
|
-
} catch (err) {
|
|
495
|
-
throw new AttachmentInjectionError(
|
|
496
|
-
attachment.filename,
|
|
497
|
-
`failed to read local file '${attachment.localPath}': ${err instanceof Error ? err.message : String(err)}`,
|
|
498
|
-
);
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
// Cloud mode: download via artifact storage
|
|
503
|
-
if (!attachment.storageKey) {
|
|
504
|
-
throw new AttachmentInjectionError(
|
|
505
|
-
attachment.filename,
|
|
506
|
-
"missing storageKey — cannot download attachment from storage",
|
|
507
|
-
);
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
// The attachment must come from storage, but the runner could not build one
|
|
511
|
-
// (proxy misconfig). Fail with an actionable message rather than dereferencing
|
|
512
|
-
// undefined — consistent with the degraded posture, never a silent skip.
|
|
513
|
-
if (!storage) {
|
|
514
|
-
throw new AttachmentInjectionError(
|
|
515
|
-
attachment.filename,
|
|
516
|
-
`artifact storage is unavailable, so this attachment (key: ${attachment.storageKey}) cannot be downloaded`,
|
|
517
|
-
);
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
try {
|
|
521
|
-
return await storage.download(attachment.storageKey);
|
|
522
|
-
} catch (err) {
|
|
523
|
-
if (err instanceof AttachmentInjectionError) throw err;
|
|
524
|
-
throw new AttachmentInjectionError(
|
|
525
|
-
attachment.filename,
|
|
526
|
-
`failed to download from storage (key: ${attachment.storageKey}): ${err instanceof Error ? err.message : String(err)}`,
|
|
527
|
-
);
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
async function extractZipToWorkspace(
|
|
532
|
-
entries: readonly ValidatedZipEntry[],
|
|
533
|
-
mountDir: string,
|
|
534
|
-
backend: WorkspaceBackend,
|
|
535
|
-
sourceFilename: string,
|
|
536
|
-
): Promise<InjectedFile[]> {
|
|
537
|
-
const cleanMountDir = mountDir.replace(/\/+$/, "");
|
|
538
|
-
const injected: InjectedFile[] = [];
|
|
539
|
-
|
|
540
|
-
for (const entry of entries) {
|
|
541
|
-
const content = await decompressEntry(entry, sourceFilename);
|
|
542
|
-
const targetPath = `${cleanMountDir}/${entry.relativePath}`;
|
|
543
|
-
|
|
544
|
-
await backend.writeFileBuffer(targetPath, content);
|
|
545
|
-
injected.push({
|
|
546
|
-
filename: posix.basename(entry.relativePath),
|
|
547
|
-
path: targetPath,
|
|
548
|
-
sizeBytes: content.length,
|
|
549
|
-
});
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
return injected;
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
/**
|
|
556
|
-
* Decompress a validated entry, enforcing its declared uncompressed size.
|
|
557
|
-
*
|
|
558
|
-
* The size cap in parseAndValidateZip budgets *declared* sizes, which the
|
|
559
|
-
* archive author controls — a crafted archive can declare 1 KB and inflate
|
|
560
|
-
* to gigabytes. Enforcement is therefore two-sided and fail-hard: inflation
|
|
561
|
-
* aborts the moment output exceeds the declaration, and an undershoot (or a
|
|
562
|
-
* stored payload whose length disagrees) fails too, because a manifest that
|
|
563
|
-
* misdescribes its own contents is exactly the corrupt-input class this
|
|
564
|
-
* module must never extract from.
|
|
565
|
-
*/
|
|
566
|
-
async function decompressEntry(
|
|
567
|
-
entry: ValidatedZipEntry,
|
|
568
|
-
sourceFilename: string,
|
|
569
|
-
): Promise<Buffer> {
|
|
570
|
-
const declared = entry.uncompressedSize;
|
|
571
|
-
|
|
572
|
-
if (entry.compressionMethod === 0) {
|
|
573
|
-
if (entry.compressedData.length !== declared) {
|
|
574
|
-
throw new AttachmentValidationError(
|
|
575
|
-
sourceFilename,
|
|
576
|
-
`entry '${entry.relativePath}' payload is ${entry.compressedData.length} bytes ` +
|
|
577
|
-
`but the archive declares ${declared} — corrupt or crafted archive`,
|
|
578
|
-
);
|
|
579
|
-
}
|
|
580
|
-
return Buffer.from(entry.compressedData);
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
// Deflate — the only other method parseAndValidateZip admits.
|
|
584
|
-
return new Promise<Buffer>((resolve, reject) => {
|
|
585
|
-
const inflate = createInflateRaw();
|
|
586
|
-
const chunks: Buffer[] = [];
|
|
587
|
-
let produced = 0;
|
|
588
|
-
inflate.on("data", (chunk: Buffer) => {
|
|
589
|
-
produced += chunk.length;
|
|
590
|
-
if (produced > declared) {
|
|
591
|
-
inflate.destroy();
|
|
592
|
-
reject(new AttachmentValidationError(
|
|
593
|
-
sourceFilename,
|
|
594
|
-
`entry '${entry.relativePath}' decompresses past its declared size of ` +
|
|
595
|
-
`${declared} bytes — corrupt or crafted archive`,
|
|
596
|
-
));
|
|
597
|
-
return;
|
|
598
|
-
}
|
|
599
|
-
chunks.push(chunk);
|
|
600
|
-
});
|
|
601
|
-
inflate.on("end", () => {
|
|
602
|
-
if (produced !== declared) {
|
|
603
|
-
reject(new AttachmentValidationError(
|
|
604
|
-
sourceFilename,
|
|
605
|
-
`entry '${entry.relativePath}' decompressed to ${produced} bytes ` +
|
|
606
|
-
`but the archive declares ${declared} — corrupt or crafted archive`,
|
|
607
|
-
));
|
|
608
|
-
return;
|
|
609
|
-
}
|
|
610
|
-
resolve(Buffer.concat(chunks));
|
|
611
|
-
});
|
|
612
|
-
inflate.on("error", reject);
|
|
613
|
-
inflate.end(Buffer.from(entry.compressedData));
|
|
614
|
-
});
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
function hasPathTraversal(filePath: string): boolean {
|
|
618
|
-
const normalized = posix.normalize(filePath);
|
|
619
|
-
if (normalized.startsWith("../") || normalized === "..") return true;
|
|
620
|
-
|
|
621
|
-
const segments = filePath.split(/[/\\]/);
|
|
622
|
-
for (const seg of segments) {
|
|
623
|
-
if (seg === "..") return true;
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
return false;
|
|
627
|
-
}
|