@arnilo/prism-tools 0.0.0-stage → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +418 -0
- package/LICENSE +21 -0
- package/README.md +44 -2
- package/browser/README.md +98 -0
- package/browser/skills/browser-automation/SKILL.md +14 -0
- package/browser/src/actions/cdp.ts +131 -0
- package/browser/src/actions/dialog.ts +25 -0
- package/browser/src/actions/download_release.ts +22 -0
- package/browser/src/actions/index.ts +37 -0
- package/browser/src/actions/locator.ts +70 -0
- package/browser/src/actions/navigate.ts +23 -0
- package/browser/src/actions/screenshot.ts +27 -0
- package/browser/src/actions/scroll.ts +24 -0
- package/browser/src/actions/select_page.ts +21 -0
- package/browser/src/actions/types.ts +114 -0
- package/browser/src/actions/upload.ts +44 -0
- package/browser/src/actions/wait.ts +45 -0
- package/browser/src/browser.ts +197 -0
- package/browser/src/cdp.ts +178 -0
- package/browser/src/checkpoint.ts +189 -0
- package/browser/src/config.ts +72 -0
- package/browser/src/downloads.ts +253 -0
- package/browser/src/errors.ts +31 -0
- package/browser/src/evaluate.ts +101 -0
- package/browser/src/index.ts +129 -0
- package/browser/src/limits.ts +271 -0
- package/browser/src/manager.ts +730 -0
- package/browser/src/network.ts +263 -0
- package/browser/src/observe.ts +178 -0
- package/browser/src/policy.ts +133 -0
- package/browser/src/screenshot.ts +107 -0
- package/browser/src/shared-sandbox.ts +53 -0
- package/browser/src/snapshot.ts +99 -0
- package/browser/src/targets.ts +165 -0
- package/browser/src/tools.ts +480 -0
- package/browser/src/types.ts +316 -0
- package/browser/src/uploads.ts +174 -0
- package/coding/README.md +337 -0
- package/coding/src/agent/acp-operations.ts +146 -0
- package/coding/src/agent/artifacts.ts +38 -0
- package/coding/src/agent/ask-user-decision.ts +623 -0
- package/coding/src/agent/atomic-write.ts +23 -0
- package/coding/src/agent/bounded-file.ts +21 -0
- package/coding/src/agent/checks.ts +301 -0
- package/coding/src/agent/coding-checkpoint.ts +828 -0
- package/coding/src/agent/delete.ts +237 -0
- package/coding/src/agent/diagnostics-tool.ts +163 -0
- package/coding/src/agent/diagnostics.ts +262 -0
- package/coding/src/agent/edit-diff.ts +382 -0
- package/coding/src/agent/edit.ts +322 -0
- package/coding/src/agent/effects.ts +102 -0
- package/coding/src/agent/env.ts +28 -0
- package/coding/src/agent/execution-policy.ts +89 -0
- package/coding/src/agent/file-mutation-queue.ts +72 -0
- package/coding/src/agent/forge/github.ts +603 -0
- package/coding/src/agent/forge/index.ts +16 -0
- package/coding/src/agent/forge/types.ts +185 -0
- package/coding/src/agent/git-aware-repository.ts +305 -0
- package/coding/src/agent/git-exec.ts +326 -0
- package/coding/src/agent/git-status.ts +177 -0
- package/coding/src/agent/git-tools.ts +360 -0
- package/coding/src/agent/git.ts +1074 -0
- package/coding/src/agent/glob-match.ts +146 -0
- package/coding/src/agent/glob.ts +182 -0
- package/coding/src/agent/goal-verify.ts +385 -0
- package/coding/src/agent/grep-worker.ts +74 -0
- package/coding/src/agent/grep.ts +459 -0
- package/coding/src/agent/identity.ts +48 -0
- package/coding/src/agent/index.ts +676 -0
- package/coding/src/agent/language/client.ts +429 -0
- package/coding/src/agent/language/framing.ts +190 -0
- package/coding/src/agent/language/index.ts +23 -0
- package/coding/src/agent/language/intelligence.ts +682 -0
- package/coding/src/agent/language/types.ts +187 -0
- package/coding/src/agent/lifecycle-events.ts +121 -0
- package/coding/src/agent/lifecycle.ts +166 -0
- package/coding/src/agent/limits.ts +214 -0
- package/coding/src/agent/move.ts +178 -0
- package/coding/src/agent/mutation-path.ts +52 -0
- package/coding/src/agent/output-accumulator.ts +304 -0
- package/coding/src/agent/path-utils.ts +120 -0
- package/coding/src/agent/process/index.ts +58 -0
- package/coding/src/agent/process/recovery.ts +508 -0
- package/coding/src/agent/process/sessions-host.ts +222 -0
- package/coding/src/agent/process/sessions-monitor.ts +141 -0
- package/coding/src/agent/process/sessions-recovery.ts +241 -0
- package/coding/src/agent/process/sessions-spawn.ts +613 -0
- package/coding/src/agent/process/sessions-teardown.ts +281 -0
- package/coding/src/agent/process/sessions.ts +30 -0
- package/coding/src/agent/process/types.ts +358 -0
- package/coding/src/agent/read-path-set.ts +126 -0
- package/coding/src/agent/read.ts +713 -0
- package/coding/src/agent/repository/glob.ts +147 -0
- package/coding/src/agent/repository/indexed-search.ts +480 -0
- package/coding/src/agent/repository/list.ts +131 -0
- package/coding/src/agent/repository/operations.ts +22 -0
- package/coding/src/agent/repository/path.ts +98 -0
- package/coding/src/agent/repository/search.ts +277 -0
- package/coding/src/agent/repository/types.ts +243 -0
- package/coding/src/agent/repository/walk.ts +126 -0
- package/coding/src/agent/repository.ts +12 -0
- package/coding/src/agent/shell-background.ts +264 -0
- package/coding/src/agent/shell-output.ts +75 -0
- package/coding/src/agent/shell.ts +526 -0
- package/coding/src/agent/todo.ts +399 -0
- package/coding/src/agent/truncate.ts +251 -0
- package/coding/src/agent/write.ts +170 -0
- package/coding/src/ask-user-host.ts +48 -0
- package/coding/src/config.ts +278 -0
- package/coding/src/hard-deny.ts +97 -0
- package/coding/src/index.ts +318 -0
- package/coding/src/principal-guard.ts +84 -0
- package/coding/src/sandbox.ts +92 -0
- package/coding/src/security/approval.ts +144 -0
- package/coding/src/security/command-rules.ts +57 -0
- package/coding/src/security/docker-cli.ts +222 -0
- package/coding/src/security/docker-sandbox.ts +1205 -0
- package/coding/src/security/e2b-sandbox.ts +853 -0
- package/coding/src/security/egress/dns-pin.ts +88 -0
- package/coding/src/security/egress/index.ts +32 -0
- package/coding/src/security/egress/limits.ts +57 -0
- package/coding/src/security/egress/policy.ts +137 -0
- package/coding/src/security/egress/proxy.ts +599 -0
- package/coding/src/security/egress/types.ts +30 -0
- package/coding/src/security/index.ts +199 -0
- package/coding/src/security/native-sandbox.ts +639 -0
- package/coding/src/security/path-containment.ts +55 -0
- package/coding/src/security/sandbox-coding-operations.ts +392 -0
- package/coding/src/security/sandbox-fs-operations.ts +644 -0
- package/coding/src/security/sandbox-limits.ts +147 -0
- package/coding/src/security/sandbox-tar.ts +308 -0
- package/coding/src/security/sandbox.ts +179 -0
- package/coding/src/security/semaphore.ts +55 -0
- package/coding/src/targets.ts +110 -0
- package/coding/src/workspace/diff.ts +78 -0
- package/coding/src/workspace/errors.ts +29 -0
- package/coding/src/workspace/index.ts +5 -0
- package/coding/src/workspace/merge.ts +151 -0
- package/coding/src/workspace/routing.ts +76 -0
- package/coding/src/workspace/service.ts +584 -0
- package/computer-use/README.md +105 -0
- package/computer-use/skills/computer-use-linux/SKILL.md +17 -0
- package/computer-use/src/classify.ts +53 -0
- package/computer-use/src/command.ts +29 -0
- package/computer-use/src/config.ts +69 -0
- package/computer-use/src/create.ts +170 -0
- package/computer-use/src/devices.ts +247 -0
- package/computer-use/src/env.ts +28 -0
- package/computer-use/src/index.ts +157 -0
- package/computer-use/src/skill.ts +21 -0
- package/openapi/README.md +97 -0
- package/openapi/src/compile.ts +386 -0
- package/openapi/src/config.ts +73 -0
- package/openapi/src/errors.ts +16 -0
- package/openapi/src/execute.ts +241 -0
- package/openapi/src/index.ts +124 -0
- package/openapi/src/limits.ts +63 -0
- package/openapi/src/specs.ts +86 -0
- package/openapi/src/tools.ts +60 -0
- package/openapi/src/types.ts +61 -0
- package/package.json +171 -4
- package/research/README.md +72 -0
- package/research/skills/research-report/SKILL.md +12 -0
- package/research/skills/research-scoping/SKILL.md +10 -0
- package/research/skills/research-synthesis/SKILL.md +10 -0
- package/research/skills/source-triage/SKILL.md +10 -0
- package/research/src/check.ts +71 -0
- package/research/src/config.ts +41 -0
- package/research/src/index.ts +147 -0
- package/research/src/ledger.ts +157 -0
- package/research/src/urls.ts +21 -0
- package/web/README.md +287 -0
- package/web/skills/web-research/SKILL.md +17 -0
- package/web/src/brave.ts +66 -0
- package/web/src/cache.ts +151 -0
- package/web/src/config.ts +210 -0
- package/web/src/controls.ts +128 -0
- package/web/src/domains.ts +114 -0
- package/web/src/exa.ts +60 -0
- package/web/src/firecrawl.ts +160 -0
- package/web/src/index.ts +387 -0
- package/web/src/kagi.ts +73 -0
- package/web/src/limits.ts +55 -0
- package/web/src/native/convert.ts +84 -0
- package/web/src/native/fetch.ts +226 -0
- package/web/src/normalize.ts +131 -0
- package/web/src/obscura/cdp.ts +201 -0
- package/web/src/obscura/classify.ts +35 -0
- package/web/src/obscura/cli.ts +254 -0
- package/web/src/obscura/errors.ts +8 -0
- package/web/src/obscura/index.ts +59 -0
- package/web/src/obscura/limits.ts +92 -0
- package/web/src/obscura/mcp.ts +104 -0
- package/web/src/obscura/playwright.ts +133 -0
- package/web/src/obscura/process.ts +235 -0
- package/web/src/obscura/search-profile.ts +73 -0
- package/web/src/obscura/types.ts +48 -0
- package/web/src/obscura/web.ts +419 -0
- package/web/src/prompt-fetch.ts +88 -0
- package/web/src/render.ts +75 -0
- package/web/src/searxng.ts +78 -0
- package/web/src/slice.ts +139 -0
- package/web/src/tavily.ts +77 -0
- package/web/src/tools.ts +182 -0
- package/web/src/transport.ts +310 -0
- package/web/src/types.ts +199 -0
- package/work/README.md +77 -0
- package/work/THIRD_PARTY_NOTICES +24 -0
- package/work/docling/ocr.py +150 -0
- package/work/sandbox/Dockerfile +36 -0
- package/work/sandbox/soffice.sh +12 -0
- package/work/src/command.ts +29 -0
- package/work/src/config.ts +138 -0
- package/work/src/connectors/cli.ts +343 -0
- package/work/src/connectors/drafts.ts +647 -0
- package/work/src/connectors/errors.ts +8 -0
- package/work/src/connectors/file-bytes.ts +149 -0
- package/work/src/connectors/google-workspace-http.ts +399 -0
- package/work/src/connectors/google-workspace.ts +465 -0
- package/work/src/connectors/gws-tools.ts +378 -0
- package/work/src/connectors/http.ts +123 -0
- package/work/src/connectors/idempotency.ts +235 -0
- package/work/src/connectors/identity.ts +34 -0
- package/work/src/connectors/index.ts +93 -0
- package/work/src/connectors/limits.ts +50 -0
- package/work/src/connectors/m365-tools.ts +308 -0
- package/work/src/connectors/microsoft365-http.ts +428 -0
- package/work/src/connectors/microsoft365.ts +402 -0
- package/work/src/connectors/normalize.ts +120 -0
- package/work/src/connectors/tool-helpers.ts +398 -0
- package/work/src/connectors/tools.ts +42 -0
- package/work/src/connectors/types.ts +399 -0
- package/work/src/diagrams/canonicalize.ts +166 -0
- package/work/src/diagrams/caps.ts +52 -0
- package/work/src/diagrams/embed.ts +340 -0
- package/work/src/diagrams/errors.ts +76 -0
- package/work/src/diagrams/index.ts +81 -0
- package/work/src/diagrams/messages.ts +150 -0
- package/work/src/diagrams/origin.ts +52 -0
- package/work/src/diagrams/telemetry.ts +34 -0
- package/work/src/diagrams/xml.ts +267 -0
- package/work/src/document-extraction/index.ts +563 -0
- package/work/src/document-reader/ambient.d.ts +2 -0
- package/work/src/document-reader/errors.ts +7 -0
- package/work/src/document-reader/index.ts +181 -0
- package/work/src/document-reader/mistral-ocr.ts +318 -0
- package/work/src/document-reader/parsers.ts +171 -0
- package/work/src/documents/caps.ts +135 -0
- package/work/src/documents/diff.ts +269 -0
- package/work/src/documents/errors.ts +44 -0
- package/work/src/documents/fidelity.ts +98 -0
- package/work/src/documents/generate.ts +96 -0
- package/work/src/documents/hash.ts +8 -0
- package/work/src/documents/index.ts +14 -0
- package/work/src/documents/model-schema.ts +639 -0
- package/work/src/documents/parse.ts +252 -0
- package/work/src/documents/patch-history.ts +81 -0
- package/work/src/documents/patch.ts +476 -0
- package/work/src/documents/preview-html.ts +245 -0
- package/work/src/documents/preview.ts +227 -0
- package/work/src/documents/telemetry.ts +36 -0
- package/work/src/documents/translate/docx.ts +292 -0
- package/work/src/documents/translate/pptx.ts +304 -0
- package/work/src/documents/translate/xlsx.ts +137 -0
- package/work/src/documents/types.ts +188 -0
- package/work/src/index.ts +311 -0
- package/work/src/runtime/worker-pool-entry.ts +102 -0
- package/work/src/runtime/worker-pool.ts +267 -0
- package/work/src/sandbox/composition.ts +212 -0
- package/work/src/sandbox/image.ts +6 -0
- package/work/src/sandbox/index.ts +14 -0
- package/work/src/sheets/caps.ts +68 -0
- package/work/src/sheets/csv.ts +351 -0
- package/work/src/sheets/decimal.ts +388 -0
- package/work/src/sheets/errors.ts +52 -0
- package/work/src/sheets/index.ts +62 -0
- package/work/src/sheets/inference.ts +378 -0
- package/work/src/sheets/telemetry.ts +34 -0
- package/work/src/sheets/types.ts +162 -0
- package/work/src/sheets/xlsx.ts +388 -0
- package/work/src/skills/index.ts +8 -0
- package/work/src/skills/load.ts +43 -0
- package/work/src/tools/filesystem.ts +21 -0
- package/work/src/tools/index.ts +1 -0
- package/work/src/tools/local-filesystem.ts +33 -0
- package/work/src/tools/office.ts +311 -0
- package/work/vendor/hermes-agent/LICENSE +21 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/LICENSE +21 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/SKILL.md +196 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_comments.py +289 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_common.py +94 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_create.py +177 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_edit.py +250 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_read.py +149 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_revisions.py +147 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_template.py +70 -0
- package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_validate.py +156 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/LICENSE +21 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/SKILL.md +125 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/_raster.py +76 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/extract_marker.py +88 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/extract_pymupdf.py +101 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_create.py +130 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_fill_form.py +97 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_form_layout.py +168 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_make_form.py +145 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_merge.py +50 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_meta.py +115 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_page_image.py +99 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_read.py +153 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_secure.py +71 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_split.py +84 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_stamp.py +143 -0
- package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_watermark.py +51 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/LICENSE +21 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/SKILL.md +220 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_create.py +214 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_edit.py +436 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_from_template.py +88 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_read.py +131 -0
- package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_render.py +93 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/LICENSE +21 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/SKILL.md +196 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/csv_to_xlsx.py +104 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_create.py +259 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_edit.py +263 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_read.py +160 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_recalc.py +111 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_restructure.py +337 -0
- package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_to_csv.py +69 -0
- package/work/vendor-lock.json +11 -0
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { WorkToolError } from "./errors.js";
|
|
4
|
+
import { resolveWorkLimits } from "./limits.js";
|
|
5
|
+
import type { ResolvedWorkLimits, WorkCliExecResult, WorkCliRunner, WorkLimits } from "./types.js";
|
|
6
|
+
|
|
7
|
+
const FORBIDDEN_TOKENS = new Set([
|
|
8
|
+
"login",
|
|
9
|
+
"logout",
|
|
10
|
+
"setup",
|
|
11
|
+
"doctor",
|
|
12
|
+
"status",
|
|
13
|
+
"request",
|
|
14
|
+
"util",
|
|
15
|
+
"cli",
|
|
16
|
+
"auth",
|
|
17
|
+
"schema", // gws interactive auth + Discovery free-form introspection
|
|
18
|
+
"--debug",
|
|
19
|
+
"--verbose",
|
|
20
|
+
"-debug",
|
|
21
|
+
"-verbose",
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
export function assertSafeArgv(argv: readonly string[]): void {
|
|
25
|
+
if (argv.length === 0) throw new WorkToolError("ERR_PRISM_WORK_ARGV", "CLI argv empty");
|
|
26
|
+
for (const arg of argv) {
|
|
27
|
+
if (typeof arg !== "string" || arg.includes("\0")) throw new WorkToolError("ERR_PRISM_WORK_ARGV", "CLI argv invalid");
|
|
28
|
+
const lower = arg.toLowerCase();
|
|
29
|
+
if (FORBIDDEN_TOKENS.has(lower)) throw new WorkToolError("ERR_PRISM_WORK_FORBIDDEN", `Forbidden CLI token: ${arg}`);
|
|
30
|
+
if (lower.startsWith("--debug") || lower.startsWith("--verbose")) {
|
|
31
|
+
throw new WorkToolError("ERR_PRISM_WORK_FORBIDDEN", `Forbidden CLI flag: ${arg}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// --- Plan 020 Task 3: isolated subprocess environment ------------------------
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Fixed platform base allow-list (canonical casing). The child inherits ONLY these
|
|
40
|
+
* locale/system keys from the host — never ambient variables, so unrelated secrets
|
|
41
|
+
* (e.g. `PRISM_PROOF_SECRET`) cannot reach the subprocess. Future additions are
|
|
42
|
+
* deliberate one-line changes reviewed like any allow-list edit.
|
|
43
|
+
*/
|
|
44
|
+
const BASE_ENV_KEYS: readonly string[] = [
|
|
45
|
+
"PATH",
|
|
46
|
+
"LANG",
|
|
47
|
+
"LC_ALL",
|
|
48
|
+
"TZ",
|
|
49
|
+
"SYSTEMROOT", // Windows system root; SystemRoot below covers hosts exposing the alternate casing
|
|
50
|
+
"SystemRoot",
|
|
51
|
+
"TEMP",
|
|
52
|
+
"TMP",
|
|
53
|
+
"PATHEXT",
|
|
54
|
+
"COMSPEC",
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
/** Fixed controls forced last so neither explicit host env nor per-identity token env can override isolation fields. */
|
|
58
|
+
const RESERVED_ENV = new Set(["home", "climicrosoft365_disabletelemetry"]);
|
|
59
|
+
|
|
60
|
+
const ENV_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
61
|
+
/** Fixed env caps mirroring docker-sandbox defaults (plan 020 Task 0; promotion to WorkLimits is demand-gated). */
|
|
62
|
+
const MAX_ENV_NAMES = 64;
|
|
63
|
+
const MAX_ENV_BYTES = 64 * 1024;
|
|
64
|
+
const IS_WIN32 = process.platform === "win32";
|
|
65
|
+
|
|
66
|
+
/** Fixed platform base: allow-listed host keys only. */
|
|
67
|
+
function baseEnv(host: NodeJS.ProcessEnv): Record<string, string> {
|
|
68
|
+
const out: Record<string, string> = {};
|
|
69
|
+
if (IS_WIN32) {
|
|
70
|
+
// Windows env keys are case-insensitive and child_process picks the first
|
|
71
|
+
// lexicographic duplicate; canonicalize to the allow-list casing so a
|
|
72
|
+
// case-variant duplicate in the host can never be selected.
|
|
73
|
+
const seen = new Set<string>();
|
|
74
|
+
for (const key of BASE_ENV_KEYS) {
|
|
75
|
+
const lower = key.toLowerCase();
|
|
76
|
+
if (seen.has(lower)) continue;
|
|
77
|
+
seen.add(lower);
|
|
78
|
+
const value = host[key];
|
|
79
|
+
if (typeof value === "string") out[key] = value;
|
|
80
|
+
}
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
for (const key of BASE_ENV_KEYS) {
|
|
84
|
+
const value = host[key];
|
|
85
|
+
if (typeof value === "string") out[key] = value;
|
|
86
|
+
}
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** One bounded env layer: rejects NUL, invalid names, non-strings, and reserved/case-variant duplicates. */
|
|
91
|
+
function validateEnvLayer(env: Readonly<Record<string, string>>, layer: string): Record<string, string> {
|
|
92
|
+
const out: Record<string, string> = {};
|
|
93
|
+
const seen = new Set<string>();
|
|
94
|
+
for (const [name, value] of Object.entries(env)) {
|
|
95
|
+
if (name.includes("\0") || value.includes("\0")) throw new WorkToolError("ERR_PRISM_WORK_ENV", `${layer} env contains NUL`);
|
|
96
|
+
if (!ENV_NAME_RE.test(name)) throw new WorkToolError("ERR_PRISM_WORK_ENV", `${layer} env name invalid: ${name}`);
|
|
97
|
+
const folded = name.toLowerCase();
|
|
98
|
+
if (RESERVED_ENV.has(folded)) throw new WorkToolError("ERR_PRISM_WORK_ENV", `${layer} env may not override reserved key: ${name}`);
|
|
99
|
+
if (seen.has(folded)) throw new WorkToolError("ERR_PRISM_WORK_ENV", `${layer} env duplicate key: ${name}`);
|
|
100
|
+
seen.add(folded);
|
|
101
|
+
out[name] = value;
|
|
102
|
+
}
|
|
103
|
+
return out;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function enforceEnvCaps(env: Record<string, string>): void {
|
|
107
|
+
const names = Object.keys(env).length;
|
|
108
|
+
if (names > MAX_ENV_NAMES) throw new WorkToolError("ERR_PRISM_WORK_LIMIT", `CLI env exceeds ${MAX_ENV_NAMES} names`);
|
|
109
|
+
let bytes = 0;
|
|
110
|
+
for (const [name, value] of Object.entries(env)) {
|
|
111
|
+
bytes += Buffer.byteLength(name, "utf8") + Buffer.byteLength(value, "utf8");
|
|
112
|
+
if (bytes > MAX_ENV_BYTES) throw new WorkToolError("ERR_PRISM_WORK_LIMIT", `CLI env exceeds ${MAX_ENV_BYTES} bytes`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function mergeEnv(base: Record<string, string>, layer: Record<string, string>): Record<string, string> {
|
|
117
|
+
const out = { ...base, ...layer };
|
|
118
|
+
enforceEnvCaps(out);
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Construction-time environment: fixed platform base, then explicit host allow-list,
|
|
124
|
+
* then forced reserved controls. Bounded (≤64 names / 64 KiB) and validated before spawn.
|
|
125
|
+
*/
|
|
126
|
+
function buildCliEnvironment(
|
|
127
|
+
host: NodeJS.ProcessEnv,
|
|
128
|
+
explicit: Readonly<Record<string, string>> | undefined,
|
|
129
|
+
configDir: string,
|
|
130
|
+
): Record<string, string> {
|
|
131
|
+
const out = mergeEnv(baseEnv(host), validateEnvLayer(explicit ?? {}, "host"));
|
|
132
|
+
out.HOME = configDir;
|
|
133
|
+
out.CLIMICROSOFT365_DISABLETELEMETRY = "1";
|
|
134
|
+
enforceEnvCaps(out);
|
|
135
|
+
return out;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Per-call late-bound layer: per-identity token env merges over the base; reserved keys are rejected. */
|
|
139
|
+
function mergeTokenEnv(
|
|
140
|
+
base: Readonly<Record<string, string>>,
|
|
141
|
+
token: Readonly<Record<string, string>> | undefined,
|
|
142
|
+
): Record<string, string> {
|
|
143
|
+
if (token === undefined || Object.keys(token).length === 0) return { ...base };
|
|
144
|
+
return mergeEnv({ ...base }, validateEnvLayer(token, "token"));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// --- Plan 020 Task 3: linear output capture ----------------------------------
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Chunk-array output collector: one final `Buffer.concat(chunks, retained)` instead of
|
|
151
|
+
* repeated concat (worst-case O(n²)); retained bytes never exceed the cap because the
|
|
152
|
+
* overflow handler fires before the offending chunk is retained.
|
|
153
|
+
*/
|
|
154
|
+
function collectOutput(limit: number, onOverflow: () => void): { push(chunk: Buffer): void; toString(): string } {
|
|
155
|
+
const chunks: Buffer[] = [];
|
|
156
|
+
let retained = 0;
|
|
157
|
+
return {
|
|
158
|
+
push(chunk) {
|
|
159
|
+
const next = retained + chunk.byteLength;
|
|
160
|
+
if (next > limit) {
|
|
161
|
+
onOverflow();
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
retained = next;
|
|
165
|
+
chunks.push(chunk);
|
|
166
|
+
},
|
|
167
|
+
toString() {
|
|
168
|
+
return Buffer.concat(chunks, retained).toString("utf8");
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function createCliRunner(options: {
|
|
174
|
+
readonly binary: string;
|
|
175
|
+
readonly configDir: string;
|
|
176
|
+
readonly limits?: WorkLimits;
|
|
177
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
178
|
+
/** Test seam: replace process spawn. */
|
|
179
|
+
readonly exec?: (
|
|
180
|
+
argv: readonly string[],
|
|
181
|
+
opts: { env: NodeJS.ProcessEnv; signal?: AbortSignal; limits: ResolvedWorkLimits },
|
|
182
|
+
) => Promise<WorkCliExecResult>;
|
|
183
|
+
}): WorkCliRunner & { readonly limits: ResolvedWorkLimits } {
|
|
184
|
+
const limits = resolveWorkLimits(options.limits);
|
|
185
|
+
if (!options.binary || options.binary.includes("\0") || !path.isAbsolute(options.binary)) {
|
|
186
|
+
throw new WorkToolError("ERR_PRISM_WORK_BINARY", "Host-pinned absolute binary required");
|
|
187
|
+
}
|
|
188
|
+
if (!options.configDir || options.configDir.includes("\0") || !path.isAbsolute(options.configDir)) {
|
|
189
|
+
throw new WorkToolError("ERR_PRISM_WORK_CONFIG", "Isolated absolute configDir required");
|
|
190
|
+
}
|
|
191
|
+
let active = 0;
|
|
192
|
+
|
|
193
|
+
const defaultExec = (
|
|
194
|
+
argv: readonly string[],
|
|
195
|
+
opts: { env: NodeJS.ProcessEnv; signal?: AbortSignal; limits: ResolvedWorkLimits },
|
|
196
|
+
): Promise<WorkCliExecResult> =>
|
|
197
|
+
new Promise((resolve, reject) => {
|
|
198
|
+
assertSafeArgv(argv);
|
|
199
|
+
const child = spawn(options.binary, [...argv], {
|
|
200
|
+
env: opts.env,
|
|
201
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
202
|
+
windowsHide: true,
|
|
203
|
+
});
|
|
204
|
+
let settled = false;
|
|
205
|
+
const finish = (err?: Error, result?: WorkCliExecResult) => {
|
|
206
|
+
if (settled) return;
|
|
207
|
+
settled = true;
|
|
208
|
+
cleanup();
|
|
209
|
+
if (err) reject(err);
|
|
210
|
+
else resolve(result!);
|
|
211
|
+
};
|
|
212
|
+
const onAbort = () => {
|
|
213
|
+
child.kill("SIGKILL");
|
|
214
|
+
finish(new WorkToolError("ERR_PRISM_WORK_ABORTED", "CLI process aborted or timed out"));
|
|
215
|
+
};
|
|
216
|
+
const timeout = setTimeout(onAbort, opts.limits.timeoutMs);
|
|
217
|
+
const cleanup = () => {
|
|
218
|
+
clearTimeout(timeout);
|
|
219
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
220
|
+
};
|
|
221
|
+
// Kill and reject before retaining bytes beyond the stdout/stderr caps.
|
|
222
|
+
const stdout = collectOutput(opts.limits.maxStdoutBytes, () => {
|
|
223
|
+
child.kill("SIGKILL");
|
|
224
|
+
finish(new WorkToolError("ERR_PRISM_WORK_LIMIT", "CLI stdout exceeds byte limit"));
|
|
225
|
+
});
|
|
226
|
+
const stderr = collectOutput(opts.limits.maxStderrBytes, () => {
|
|
227
|
+
child.kill("SIGKILL");
|
|
228
|
+
finish(new WorkToolError("ERR_PRISM_WORK_LIMIT", "CLI stderr exceeds byte limit"));
|
|
229
|
+
});
|
|
230
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
231
|
+
if (opts.signal?.aborted) {
|
|
232
|
+
onAbort();
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
child.stdout?.on("data", (chunk: Buffer) => stdout.push(chunk));
|
|
236
|
+
child.stderr?.on("data", (chunk: Buffer) => stderr.push(chunk));
|
|
237
|
+
child.on("error", (error) => finish(new WorkToolError("ERR_PRISM_WORK_SPAWN", error.message)));
|
|
238
|
+
child.on("close", (code) => {
|
|
239
|
+
finish(undefined, {
|
|
240
|
+
exitCode: code,
|
|
241
|
+
stdout: stdout.toString(),
|
|
242
|
+
stderr: stderr.toString(),
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const execImpl = options.exec ?? defaultExec;
|
|
248
|
+
const env = buildCliEnvironment(process.env, options.env, options.configDir);
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
limits,
|
|
252
|
+
async exec(argv, runOpts) {
|
|
253
|
+
assertSafeArgv(argv);
|
|
254
|
+
if (active >= limits.maxConcurrency) throw new WorkToolError("ERR_PRISM_WORK_LIMIT", "CLI concurrency exhausted");
|
|
255
|
+
active++;
|
|
256
|
+
try {
|
|
257
|
+
const timeout = AbortSignal.timeout(limits.timeoutMs);
|
|
258
|
+
const signal = runOpts?.signal ? AbortSignal.any([runOpts.signal, timeout]) : timeout;
|
|
259
|
+
// Late-bound per-identity token env merges over the base env; never touches argv.
|
|
260
|
+
const execEnv = mergeTokenEnv(env, runOpts?.env);
|
|
261
|
+
return await execImpl(argv, { env: execEnv, signal, limits });
|
|
262
|
+
} finally {
|
|
263
|
+
active--;
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function parseCliJson(stdout: string, limits: ResolvedWorkLimits): unknown {
|
|
270
|
+
const text = stdout.trim();
|
|
271
|
+
if (!text) return null;
|
|
272
|
+
let value: unknown;
|
|
273
|
+
try {
|
|
274
|
+
value = JSON.parse(text);
|
|
275
|
+
} catch {
|
|
276
|
+
throw new WorkToolError("ERR_PRISM_WORK_JSON", "CLI returned invalid JSON");
|
|
277
|
+
}
|
|
278
|
+
assertBoundedJson(value, limits.maxJsonDepth, limits.maxJsonProperties, limits.maxResponseBytes);
|
|
279
|
+
return value;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Strict NDJSON parse for `gws --page-all` streams.
|
|
284
|
+
* Caps lines by maxPaginationPages; each line must be a single JSON value.
|
|
285
|
+
*/
|
|
286
|
+
export function parseCliNdjson(stdout: string, limits: ResolvedWorkLimits): unknown[] {
|
|
287
|
+
const text = stdout.replace(/\r\n/g, "\n").trimEnd();
|
|
288
|
+
if (!text.trim()) return [];
|
|
289
|
+
const lines = text.split("\n");
|
|
290
|
+
if (lines.length > limits.maxPaginationPages) {
|
|
291
|
+
throw new WorkToolError("ERR_PRISM_WORK_LIMIT", "NDJSON page stream exceeds pagination page limit");
|
|
292
|
+
}
|
|
293
|
+
const out: unknown[] = [];
|
|
294
|
+
for (const line of lines) {
|
|
295
|
+
const trimmed = line.trim();
|
|
296
|
+
if (!trimmed) continue;
|
|
297
|
+
let value: unknown;
|
|
298
|
+
try {
|
|
299
|
+
value = JSON.parse(trimmed);
|
|
300
|
+
} catch {
|
|
301
|
+
throw new WorkToolError("ERR_PRISM_WORK_JSON", "CLI returned invalid NDJSON line");
|
|
302
|
+
}
|
|
303
|
+
assertBoundedJson(value, limits.maxJsonDepth, limits.maxJsonProperties, limits.maxResponseBytes);
|
|
304
|
+
out.push(value);
|
|
305
|
+
if (out.length > limits.maxAggregateItems) {
|
|
306
|
+
throw new WorkToolError("ERR_PRISM_WORK_LIMIT", "NDJSON aggregate exceeds item limit");
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return out;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export function assertBoundedJson(value: unknown, maxDepth: number, maxProperties: number, maxBytes: number): void {
|
|
313
|
+
let properties = 0;
|
|
314
|
+
let bytes: number;
|
|
315
|
+
try {
|
|
316
|
+
bytes = Buffer.byteLength(JSON.stringify(value));
|
|
317
|
+
} catch {
|
|
318
|
+
throw new WorkToolError("ERR_PRISM_WORK_JSON", "JSON value is cyclic or unserializable");
|
|
319
|
+
}
|
|
320
|
+
if (bytes > maxBytes) throw new WorkToolError("ERR_PRISM_WORK_LIMIT", "JSON value exceeds byte limit");
|
|
321
|
+
const stack: Array<[unknown, number]> = [[value, 0]];
|
|
322
|
+
while (stack.length) {
|
|
323
|
+
const [entry, depth] = stack.pop()!;
|
|
324
|
+
if (depth > maxDepth) throw new WorkToolError("ERR_PRISM_WORK_LIMIT", "JSON value exceeds depth limit");
|
|
325
|
+
if (entry === null || typeof entry === "string" || typeof entry === "boolean") continue;
|
|
326
|
+
if (typeof entry === "number") {
|
|
327
|
+
if (!Number.isFinite(entry)) throw new WorkToolError("ERR_PRISM_WORK_JSON", "JSON number must be finite");
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
if (typeof entry !== "object") throw new WorkToolError("ERR_PRISM_WORK_JSON", "Value is not JSON");
|
|
331
|
+
const values = Array.isArray(entry) ? entry : Object.values(entry);
|
|
332
|
+
if (!Array.isArray(entry)) {
|
|
333
|
+
for (const key of Object.keys(entry as object)) {
|
|
334
|
+
if (["__proto__", "prototype", "constructor"].includes(key)) {
|
|
335
|
+
throw new WorkToolError("ERR_PRISM_WORK_JSON", "JSON contains forbidden key");
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
properties += values.length;
|
|
339
|
+
if (properties > maxProperties) throw new WorkToolError("ERR_PRISM_WORK_LIMIT", "JSON value exceeds property limit");
|
|
340
|
+
}
|
|
341
|
+
for (const child of values) stack.push([child, depth + 1]);
|
|
342
|
+
}
|
|
343
|
+
}
|