@legionworks/facet 1.9.0
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/LICENSE-APACHE +202 -0
- package/LICENSE-MIT +23 -0
- package/README.md +158 -0
- package/dist/gallery/chunk-4g8rem85.css +656 -0
- package/dist/gallery/chunk-c6s9k9ty.js +5970 -0
- package/dist/gallery/frame/artifact.css +8424 -0
- package/dist/gallery/frame/chunks/abnfDiagram-N423BO3Z-jj74szbm.js +155 -0
- package/dist/gallery/frame/chunks/architecture-TIHT7OUA-cvdjn7dt.js +10 -0
- package/dist/gallery/frame/chunks/architectureDiagram-T3A2C74G-n4z77eg6.js +8675 -0
- package/dist/gallery/frame/chunks/blockDiagram-VBNYF7ZC-7xsqzrd1.js +3529 -0
- package/dist/gallery/frame/chunks/c4Diagram-5PPSVZJV-z2zjs7wx.js +2413 -0
- package/dist/gallery/frame/chunks/classDiagram-JCYQIIEL-ve8rngqp.js +47 -0
- package/dist/gallery/frame/chunks/classDiagram-v2-OCEON4UE-8cfaxb8z.js +47 -0
- package/dist/gallery/frame/chunks/cose-bilkent-JH36ORCC-sn92qexx.js +4822 -0
- package/dist/gallery/frame/chunks/cynefin-VYW2F7L2-tqjv913d.js +10 -0
- package/dist/gallery/frame/chunks/cynefinDiagram-MW4NZA55-c1c0dj12.js +500 -0
- package/dist/gallery/frame/chunks/dagre-VZM6K2ZE-7tyg4yt2.js +476 -0
- package/dist/gallery/frame/chunks/diagram-7IWD3JNH-y4eng9sc.js +526 -0
- package/dist/gallery/frame/chunks/diagram-B4RE2ZJO-rn3p7j5h.js +673 -0
- package/dist/gallery/frame/chunks/diagram-LBJQPF4R-w4a2cm9q.js +255 -0
- package/dist/gallery/frame/chunks/diagram-Q27KOJAE-aqc2v7wy.js +575 -0
- package/dist/gallery/frame/chunks/diagram-UB23O5K3-xncy8nyd.js +355 -0
- package/dist/gallery/frame/chunks/ebnfDiagram-BXEA7PRR-acwgxz16.js +175 -0
- package/dist/gallery/frame/chunks/erDiagram-JOGREHBK-mzp5nswt.js +1332 -0
- package/dist/gallery/frame/chunks/eventmodeling-45OFAUF4-sg07qr0c.js +10 -0
- package/dist/gallery/frame/chunks/flowDiagram-UKHOOZJN-2e5ygs44.js +29 -0
- package/dist/gallery/frame/chunks/ganttDiagram-PKOTCBZU-p2hhh7hr.js +2620 -0
- package/dist/gallery/frame/chunks/gitGraph-TEB2WS4Q-j6kw0naa.js +10 -0
- package/dist/gallery/frame/chunks/gitGraphDiagram-DS77QQ5N-4z9z8wjm.js +1347 -0
- package/dist/gallery/frame/chunks/info-DKCQHKI2-mpt80jd3.js +10 -0
- package/dist/gallery/frame/chunks/infoDiagram-6WML65LV-5rqm3mgg.js +65 -0
- package/dist/gallery/frame/chunks/ishikawaDiagram-WSZJBQD7-b51z96jy.js +975 -0
- package/dist/gallery/frame/chunks/journeyDiagram-NVQOT4AX-9jbfp0pk.js +1272 -0
- package/dist/gallery/frame/chunks/kanban-definition-27J2QSJJ-4x0vx4se.js +1116 -0
- package/dist/gallery/frame/chunks/katex-xfgk95tv.js +14034 -0
- package/dist/gallery/frame/chunks/markdown-1s5vwkf2.js +9 -0
- package/dist/gallery/frame/chunks/markdown-2h6bwsms.js +3239 -0
- package/dist/gallery/frame/chunks/markdown-2x113yh8.js +107 -0
- package/dist/gallery/frame/chunks/markdown-30b7rwmp.js +1943 -0
- package/dist/gallery/frame/chunks/markdown-356bfj4g.js +2031 -0
- package/dist/gallery/frame/chunks/markdown-44n9ea69.js +2406 -0
- package/dist/gallery/frame/chunks/markdown-56enrf2r.js +39 -0
- package/dist/gallery/frame/chunks/markdown-5avymm8t.js +725 -0
- package/dist/gallery/frame/chunks/markdown-6r4svars.js +33 -0
- package/dist/gallery/frame/chunks/markdown-6zz8efx2.js +24 -0
- package/dist/gallery/frame/chunks/markdown-7jp6prnb.js +19 -0
- package/dist/gallery/frame/chunks/markdown-809k7q41.js +55 -0
- package/dist/gallery/frame/chunks/markdown-82xmzhzd.js +1076 -0
- package/dist/gallery/frame/chunks/markdown-92n37gd4.js +7627 -0
- package/dist/gallery/frame/chunks/markdown-ag2sssf8.js +36 -0
- package/dist/gallery/frame/chunks/markdown-b4s7ntq2.js +19 -0
- package/dist/gallery/frame/chunks/markdown-c9k0fvp1.js +100 -0
- package/dist/gallery/frame/chunks/markdown-ceefymn8.js +125 -0
- package/dist/gallery/frame/chunks/markdown-d1wfbs70.js +243 -0
- package/dist/gallery/frame/chunks/markdown-dy5sgjhc.js +11022 -0
- package/dist/gallery/frame/chunks/markdown-dz3z5w30.js +30868 -0
- package/dist/gallery/frame/chunks/markdown-eg6ngz7y.js +30340 -0
- package/dist/gallery/frame/chunks/markdown-ff5wvb43.js +1149 -0
- package/dist/gallery/frame/chunks/markdown-ffg06nfz.js +86 -0
- package/dist/gallery/frame/chunks/markdown-fvfncadf.js +472 -0
- package/dist/gallery/frame/chunks/markdown-gvyszqpv.js +19 -0
- package/dist/gallery/frame/chunks/markdown-gx1p0ew0.js +88 -0
- package/dist/gallery/frame/chunks/markdown-gxye5frz.js +65 -0
- package/dist/gallery/frame/chunks/markdown-h7pzv34e.js +105 -0
- package/dist/gallery/frame/chunks/markdown-hab789t9.js +47 -0
- package/dist/gallery/frame/chunks/markdown-hjp6sj8t.js +125 -0
- package/dist/gallery/frame/chunks/markdown-jka64qgp.js +1993 -0
- package/dist/gallery/frame/chunks/markdown-k8xkn8mw.js +56 -0
- package/dist/gallery/frame/chunks/markdown-m7frq8x8.js +112 -0
- package/dist/gallery/frame/chunks/markdown-p740v4qm.js +36 -0
- package/dist/gallery/frame/chunks/markdown-pxfeh231.js +57 -0
- package/dist/gallery/frame/chunks/markdown-pzgd8z3s.js +87 -0
- package/dist/gallery/frame/chunks/markdown-qx3cngz2.js +1673 -0
- package/dist/gallery/frame/chunks/markdown-rawhfvvc.js +85 -0
- package/dist/gallery/frame/chunks/markdown-rd8w0ng9.js +49 -0
- package/dist/gallery/frame/chunks/markdown-rm2yxfjj.js +2773 -0
- package/dist/gallery/frame/chunks/markdown-s1jatrnk.js +993 -0
- package/dist/gallery/frame/chunks/markdown-sgfg5vvq.js +59 -0
- package/dist/gallery/frame/chunks/markdown-t4ethv9d.js +409 -0
- package/dist/gallery/frame/chunks/markdown-tnmabqvz.js +36 -0
- package/dist/gallery/frame/chunks/markdown-tvdpppw8.js +5292 -0
- package/dist/gallery/frame/chunks/markdown-v0x27twr.js +22 -0
- package/dist/gallery/frame/chunks/markdown-v14td68x.js +36 -0
- package/dist/gallery/frame/chunks/markdown-w6sgd54z.js +36 -0
- package/dist/gallery/frame/chunks/markdown-xw8dsk8b.js +2125 -0
- package/dist/gallery/frame/chunks/markdown-y18mve73.js +85 -0
- package/dist/gallery/frame/chunks/markdown-yengsms5.js +427 -0
- package/dist/gallery/frame/chunks/markdown-z1wc0v0d.js +1826 -0
- package/dist/gallery/frame/chunks/markdown-zazx9h4n.js +329 -0
- package/dist/gallery/frame/chunks/mermaid-de7p4v0x.js +30 -0
- package/dist/gallery/frame/chunks/mindmap-definition-FAOFIHXS-ra44cbem.js +1216 -0
- package/dist/gallery/frame/chunks/packet-7NZHBO7P-xppm5913.js +10 -0
- package/dist/gallery/frame/chunks/pegDiagram-VL7TDLO6-a2a4wg03.js +163 -0
- package/dist/gallery/frame/chunks/pie-RZYD4A2V-hnbxk152.js +10 -0
- package/dist/gallery/frame/chunks/pieDiagram-7S7Q4E2Y-f1vxjtmx.js +311 -0
- package/dist/gallery/frame/chunks/quadrantDiagram-CIZ2JOQS-ywafa6kh.js +1390 -0
- package/dist/gallery/frame/chunks/radar-I7S5WNFK-ewqd27se.js +10 -0
- package/dist/gallery/frame/chunks/railroad-3IZDKUUU-5hy4bdx3.js +10 -0
- package/dist/gallery/frame/chunks/railroad-abnf-AHOZXSZD-bh5sxz0d.js +10 -0
- package/dist/gallery/frame/chunks/railroad-ebnf-EBAXGLYW-6b9c3zeh.js +10 -0
- package/dist/gallery/frame/chunks/railroad-peg-LSFZ7HO6-avc8v39s.js +10 -0
- package/dist/gallery/frame/chunks/railroadDiagram-AXF67PYL-qc61wax4.js +131 -0
- package/dist/gallery/frame/chunks/requirementDiagram-LRYGKXZP-9bs4786c.js +1295 -0
- package/dist/gallery/frame/chunks/sankeyDiagram-W5VNT64P-awt1em8b.js +1339 -0
- package/dist/gallery/frame/chunks/sequenceDiagram-SI44F4Z6-qaf9mz3t.js +4324 -0
- package/dist/gallery/frame/chunks/sizeCapture-X5ZJPWSS-9fq685xd.js +64 -0
- package/dist/gallery/frame/chunks/stateDiagram-OKZ733FA-vx7a7hh0.js +456 -0
- package/dist/gallery/frame/chunks/stateDiagram-v2-UEYNNEHI-x4m6ws5t.js +46 -0
- package/dist/gallery/frame/chunks/swimlanes-SLNWSIFB-e4mxm1gh.js +8287 -0
- package/dist/gallery/frame/chunks/swimlanesDiagram-ULZ7WXOC-0h93e2qq.js +42 -0
- package/dist/gallery/frame/chunks/timeline-definition-Z64GVDOM-wp0bhbzg.js +1549 -0
- package/dist/gallery/frame/chunks/treeView-QDETBFTQ-hcx7n0vt.js +10 -0
- package/dist/gallery/frame/chunks/treemap-6X3UGDF4-av02skdw.js +10 -0
- package/dist/gallery/frame/chunks/vennDiagram-T6HMQDX7-pmvk1p7n.js +2566 -0
- package/dist/gallery/frame/chunks/wardley-OPB4EBWU-njsp6e40.js +10 -0
- package/dist/gallery/frame/chunks/wardleyDiagram-T6FBY63Y-eggmnv4z.js +979 -0
- package/dist/gallery/frame/chunks/xychartDiagram-ELKLHX3M-q40tnpe5.js +1954 -0
- package/dist/gallery/frame/frame.css +183 -0
- package/dist/gallery/frame/runtime/chart.js +42744 -0
- package/dist/gallery/frame/runtime/html.js +14 -0
- package/dist/gallery/frame/runtime/markdown.js +1403 -0
- package/dist/gallery/frame/runtime/mermaid.js +32 -0
- package/dist/gallery/frame/runtime/svg.js +14 -0
- package/dist/gallery/frame/runtime/tsx.js +52 -0
- package/dist/gallery/index.html +104 -0
- package/docs/reference/cli.md +163 -0
- package/docs/reference/export.md +106 -0
- package/docs/reference/html.md +215 -0
- package/docs/reference/http.md +85 -0
- package/docs/reference/mcp.md +78 -0
- package/docs/reference/security.md +119 -0
- package/docs/reference/storage.md +93 -0
- package/docs/reference/tsx.md +101 -0
- package/docs/reference/validation.md +212 -0
- package/package.json +89 -0
- package/scripts/build-gallery.ts +127 -0
- package/scripts/launch-netns.sh +67 -0
- package/skills/facet/SKILL.md +100 -0
- package/src/cli/client.ts +311 -0
- package/src/cli/commands/create.ts +49 -0
- package/src/cli/commands/doctor.ts +249 -0
- package/src/cli/commands/export.ts +179 -0
- package/src/cli/commands/instantiate.ts +35 -0
- package/src/cli/commands/list.ts +49 -0
- package/src/cli/commands/open.ts +75 -0
- package/src/cli/commands/pin.ts +39 -0
- package/src/cli/commands/promote.ts +49 -0
- package/src/cli/commands/publish.ts +122 -0
- package/src/cli/commands/read-back.ts +56 -0
- package/src/cli/commands/status.ts +114 -0
- package/src/cli/commands/watch.ts +146 -0
- package/src/cli/main.ts +522 -0
- package/src/cli/output.ts +106 -0
- package/src/cli/parser.ts +376 -0
- package/src/cli/presenter.ts +212 -0
- package/src/cli/service-metadata.ts +134 -0
- package/src/cli/spawn-service.ts +326 -0
- package/src/css.d.ts +4 -0
- package/src/gallery-web/app.ts +1581 -0
- package/src/gallery-web/export-menu.ts +121 -0
- package/src/gallery-web/export.ts +112 -0
- package/src/gallery-web/favicon.ts +54 -0
- package/src/gallery-web/frame/entries/chart.ts +6 -0
- package/src/gallery-web/frame/entries/html.ts +6 -0
- package/src/gallery-web/frame/entries/markdown.ts +6 -0
- package/src/gallery-web/frame/entries/mermaid.ts +6 -0
- package/src/gallery-web/frame/entries/svg.ts +6 -0
- package/src/gallery-web/frame/entries/tsx.ts +6 -0
- package/src/gallery-web/frame/frame-payload.ts +91 -0
- package/src/gallery-web/frame/renderer-validation.ts +13 -0
- package/src/gallery-web/frame/renderers/chart.ts +182 -0
- package/src/gallery-web/frame/renderers/dompurify-shim.ts +112 -0
- package/src/gallery-web/frame/renderers/html.ts +48 -0
- package/src/gallery-web/frame/renderers/markdown.ts +81 -0
- package/src/gallery-web/frame/renderers/mermaid.ts +120 -0
- package/src/gallery-web/frame/renderers/registry.ts +260 -0
- package/src/gallery-web/frame/renderers/svg.ts +393 -0
- package/src/gallery-web/frame/renderers/tsx.ts +61 -0
- package/src/gallery-web/frame/runtime.ts +501 -0
- package/src/gallery-web/frame/styles/artifact.css +8424 -0
- package/src/gallery-web/frame/styles/frame.css +183 -0
- package/src/gallery-web/frame/styles/html-source.css +23 -0
- package/src/gallery-web/frame/view-box.ts +33 -0
- package/src/gallery-web/frame-html.ts +72 -0
- package/src/gallery-web/index.html +104 -0
- package/src/gallery-web/session.ts +107 -0
- package/src/gallery-web/sse-client.ts +212 -0
- package/src/gallery-web/styles/app.css +420 -0
- package/src/gallery-web/styles/tokens.css +112 -0
- package/src/gallery-web/styles/verdict.css +186 -0
- package/src/gallery-web/swap.ts +64 -0
- package/src/gallery-web/theme.ts +40 -0
- package/src/gallery-web/view-state.ts +93 -0
- package/src/harness-adapters/claude-code/README.md +10 -0
- package/src/harness-adapters/claude-code/facet.sh +4 -0
- package/src/harness-adapters/codex/README.md +10 -0
- package/src/harness-adapters/codex/facet.sh +4 -0
- package/src/harness-adapters/mcp/README.md +13 -0
- package/src/harness-adapters/mcp/cli-bridge.ts +216 -0
- package/src/harness-adapters/mcp/main.ts +15 -0
- package/src/harness-adapters/mcp/server.ts +164 -0
- package/src/harness-adapters/mcp/tool-schemas.ts +53 -0
- package/src/harness-adapters/opencode/README.md +10 -0
- package/src/harness-adapters/opencode/facet.sh +4 -0
- package/src/runtime/compiled-entrypoints.ts +69 -0
- package/src/service/dispatcher.ts +717 -0
- package/src/service/export.ts +218 -0
- package/src/service/http-utils.ts +55 -0
- package/src/service/lexical/expectations.ts +210 -0
- package/src/service/lifecycle/idle-controller.ts +125 -0
- package/src/service/lifecycle/orphan-cleanup.ts +128 -0
- package/src/service/lifecycle/process-lock.ts +235 -0
- package/src/service/main.ts +136 -0
- package/src/service/process.ts +240 -0
- package/src/service/router-guards.ts +239 -0
- package/src/service/router.ts +627 -0
- package/src/service/security/auth.ts +168 -0
- package/src/service/security/host-origin.ts +138 -0
- package/src/service/security/http-guards.ts +13 -0
- package/src/service/security/leases.ts +182 -0
- package/src/service/security/token-store.ts +138 -0
- package/src/service/server.ts +356 -0
- package/src/service/store/database.ts +61 -0
- package/src/service/store/evidence-retention.ts +234 -0
- package/src/service/store/migrations.ts +167 -0
- package/src/service/store/repository-lifecycle.ts +121 -0
- package/src/service/store/repository.ts +732 -0
- package/src/service/store/schema.ts +218 -0
- package/src/service/stored-verdict.ts +97 -0
- package/src/service/stream.ts +301 -0
- package/src/service/verdict-enrichment.ts +45 -0
- package/src/shared/build/frame-bundle-plugins.ts +61 -0
- package/src/shared/build-mode.ts +28 -0
- package/src/shared/config/evidence-read.ts +98 -0
- package/src/shared/config/limits.ts +56 -0
- package/src/shared/config/paths.ts +96 -0
- package/src/shared/contracts/artifact-types.ts +2 -0
- package/src/shared/contracts/artifact.ts +98 -0
- package/src/shared/contracts/commands/_shared.ts +59 -0
- package/src/shared/contracts/commands/guards.ts +86 -0
- package/src/shared/contracts/commands/index.ts +204 -0
- package/src/shared/contracts/commands/names.ts +35 -0
- package/src/shared/contracts/commands/requests.ts +149 -0
- package/src/shared/contracts/commands/results.ts +200 -0
- package/src/shared/contracts/envelope.ts +140 -0
- package/src/shared/contracts/events.ts +41 -0
- package/src/shared/contracts/observed-counts.ts +19 -0
- package/src/shared/contracts/renderers.ts +6 -0
- package/src/shared/contracts/validation.ts +296 -0
- package/src/shared/errors/facet-error.ts +127 -0
- package/src/shared/errors/store-error.ts +68 -0
- package/src/shared/evidence-image.ts +37 -0
- package/src/shared/export.ts +31 -0
- package/src/shared/html/policy.ts +108 -0
- package/src/shared/html/style-vocabulary.ts +352 -0
- package/src/shared/logging/logger.ts +108 -0
- package/src/shared/logging/redact.ts +49 -0
- package/src/shared/security/frozen-csp.ts +13 -0
- package/src/shared/storage-version.ts +2 -0
- package/src/shared/tsx/execution.ts +29 -0
- package/src/shared/tsx/import-policy.ts +94 -0
- package/src/shared/util/dir-permissions.ts +94 -0
- package/src/shared/util/mermaid-nodes.ts +230 -0
- package/src/shared/util/process.ts +67 -0
- package/src/shared/util/time.ts +21 -0
- package/src/shared/version.ts +3 -0
- package/src/validation/sandbox/limits.ts +41 -0
- package/src/validation/sandbox/netns.ts +177 -0
- package/src/validation/tier0/chart.ts +177 -0
- package/src/validation/tier0/dom-shim.ts +54 -0
- package/src/validation/tier0/html.ts +408 -0
- package/src/validation/tier0/markdown.ts +285 -0
- package/src/validation/tier0/mermaid.ts +98 -0
- package/src/validation/tier0/runner.ts +504 -0
- package/src/validation/tier0/svg.ts +321 -0
- package/src/validation/tier0/tsx/allowlist-resolver.ts +77 -0
- package/src/validation/tier0/tsx/ast-policy-capabilities.ts +238 -0
- package/src/validation/tier0/tsx/ast-policy-shared.ts +71 -0
- package/src/validation/tier0/tsx/ast-policy.ts +181 -0
- package/src/validation/tier0/tsx/compiler.ts +156 -0
- package/src/validation/tier0/worker-dispatch.ts +211 -0
- package/src/validation/tier0/worker-entry.ts +96 -0
- package/src/validation/tier0/worker-input.ts +75 -0
- package/src/validation/tier1/browser-process.ts +147 -0
- package/src/validation/tier1/cdp-pipe.ts +281 -0
- package/src/validation/tier1/entries/chart.ts +5 -0
- package/src/validation/tier1/entries/html.ts +6 -0
- package/src/validation/tier1/entries/markdown.ts +5 -0
- package/src/validation/tier1/entries/mermaid.ts +5 -0
- package/src/validation/tier1/entries/svg.ts +5 -0
- package/src/validation/tier1/entries/tsx.ts +5 -0
- package/src/validation/tier1/frame-target.ts +162 -0
- package/src/validation/tier1/harness-entry.ts +261 -0
- package/src/validation/tier1/harness.ts +215 -0
- package/src/validation/tier1/isolated-probe.ts +94 -0
- package/src/validation/tier1/launcher.ts +177 -0
- package/src/validation/tier1/limits.ts +80 -0
- package/src/validation/tier1/nonce.ts +17 -0
- package/src/validation/tier1/protocol-probe.ts +519 -0
- package/src/validation/tier1/runner.ts +1122 -0
- package/src/validation/tier1/verdict.ts +246 -0
- package/src/validation/tier1/webp.ts +69 -0
- package/templates/README.md +88 -0
- package/templates/bar-compare.vl.json +63 -0
- package/templates/capacity-report.tsx +102 -0
- package/templates/decision-record.md +24 -0
- package/templates/deployment-state.mmd +40 -0
- package/templates/exemplar.md +91 -0
- package/templates/facet-story.tsx +799 -0
- package/templates/fleet-dashboard.html +141 -0
- package/templates/html-release-ledger.html +91 -0
- package/templates/html-status-report.html +114 -0
- package/templates/incident-console.tsx +147 -0
- package/templates/legion-boundaries.mmd +23 -0
- package/templates/legion-flow.mmd +15 -0
- package/templates/legion-sequence.mmd +14 -0
- package/templates/legion-state.mmd +7 -0
- package/templates/metric-card.svg +29 -0
- package/templates/observability-map.svg +101 -0
- package/templates/pipeline-audit.md +108 -0
- package/templates/release-metrics.vl.json +98 -0
- package/templates/service-topology.mmd +47 -0
- package/templates/status-report.md +32 -0
- package/templates/system-map.svg +53 -0
- package/templates/timeseries.vl.json +71 -0
- package/templates/tsx-interactive-counter.tsx +19 -0
- package/templates/tsx-status-report.tsx +31 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Network namespace launcher for the Tier 0 parse worker.
|
|
3
|
+
*
|
|
4
|
+
* The wrapper enforces egress denial by kernel topology, NOT by
|
|
5
|
+
* flagging the artifact: `unshare --map-current-user --net` creates a
|
|
6
|
+
* rootless network namespace in which `lo` is DOWN and no other
|
|
7
|
+
* interface exists. The worker cannot reach any addressable host
|
|
8
|
+
* because there is no route to one — `fetch`, `XMLHttpRequest`,
|
|
9
|
+
* `WebSocket`, raw sockets, WebRTC, and any future IP API all see the
|
|
10
|
+
* same empty namespace.
|
|
11
|
+
*
|
|
12
|
+
* The reference spike at `.facet-build/phase-0-spikes/egress/` proved
|
|
13
|
+
* this candidate blocks 0/12 known browser escape channels on the
|
|
14
|
+
* Chromium build; the same boundary holds for a Bun process.
|
|
15
|
+
*
|
|
16
|
+
* If `unshare` / user namespaces are unavailable in the host
|
|
17
|
+
* environment, `probeNetnsSupport()` returns `false` so callers can
|
|
18
|
+
* surface a typed `tier0_unavailable` error instead of silently
|
|
19
|
+
* running un-sandboxed.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { spawn, spawnSync, type ChildProcess } from "node:child_process";
|
|
23
|
+
import { statSync } from "node:fs";
|
|
24
|
+
|
|
25
|
+
import { TIER0_MEMORY_CAP_BYTES } from "./limits";
|
|
26
|
+
import { InsecureLevelSchema } from "../../shared/contracts/validation";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The unshare wrapper. The shell fragment applies `ulimit -v` (RLIMIT_AS)
|
|
30
|
+
* so the worker cannot grow past `TIER0_MEMORY_CAP_BYTES` regardless of
|
|
31
|
+
* what library code inside it does, then `exec`s `unshare` so the
|
|
32
|
+
* `unshare` PID is reused and signals reach the inner process.
|
|
33
|
+
*
|
|
34
|
+
* `--` terminates unshare's own option parsing so a worker argv
|
|
35
|
+
* beginning with `-` cannot be mistaken for a wrapper flag.
|
|
36
|
+
*/
|
|
37
|
+
const MEMORY_LIMIT_PREFIX = ["ulimit -v", TIER0_MEMORY_CAP_BYTES, "; "].join(" ");
|
|
38
|
+
|
|
39
|
+
// Acceptance-only seams let the proof exercise both branches on one host.
|
|
40
|
+
const FORCE_UNAVAILABLE_ENV = "FACET_TIER0_FORCE_NETNS_UNAVAILABLE";
|
|
41
|
+
const DIRECT_EXEC_MARKER_ENV = "FACET_TIER0_DIRECT_EXEC_MARKER";
|
|
42
|
+
|
|
43
|
+
export interface NetnsProbe {
|
|
44
|
+
/** True iff `unshare --map-current-user --net` is runnable as the current user. */
|
|
45
|
+
readonly available: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* When `available` is `false`, a short reason suitable for an error
|
|
48
|
+
* body — e.g. "unshare: operation not permitted" (user namespaces
|
|
49
|
+
* disabled) or "unshare: not found".
|
|
50
|
+
*/
|
|
51
|
+
readonly reason: string | null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Run a single `unshare --map-current-user --net -- true` invocation
|
|
56
|
+
* to detect whether rootless user namespaces are enabled in the host.
|
|
57
|
+
* The result is cached per process because the probe itself is
|
|
58
|
+
* expensive and the capability does not change while the service runs.
|
|
59
|
+
*/
|
|
60
|
+
let cachedProbe: NetnsProbe | null = null;
|
|
61
|
+
|
|
62
|
+
export function probeNetnsSupport(): NetnsProbe {
|
|
63
|
+
if (process.env[FORCE_UNAVAILABLE_ENV] === "1") {
|
|
64
|
+
return { available: false, reason: "forced unavailable for acceptance proof" };
|
|
65
|
+
}
|
|
66
|
+
if (cachedProbe !== null) return cachedProbe;
|
|
67
|
+
try {
|
|
68
|
+
// spawnSync, NOT spawn: `exit` fires on a later tick, so an async probe
|
|
69
|
+
// read synchronously ALWAYS observed "not resolved" and cached a false
|
|
70
|
+
// negative for the process lifetime — disabling Tier 0 wherever the
|
|
71
|
+
// capability is in fact present.
|
|
72
|
+
const probe = spawnSync("unshare", ["--map-current-user", "--net", "--", "/bin/true"], {
|
|
73
|
+
stdio: "ignore",
|
|
74
|
+
});
|
|
75
|
+
if (probe.error !== undefined)
|
|
76
|
+
return (cachedProbe = { available: false, reason: `unshare: ${probe.error.message}` });
|
|
77
|
+
if (probe.signal !== null)
|
|
78
|
+
return (cachedProbe = { available: false, reason: `unshare killed by ${probe.signal}` });
|
|
79
|
+
if (probe.status === 0) return (cachedProbe = { available: true, reason: null });
|
|
80
|
+
return (cachedProbe = {
|
|
81
|
+
available: false,
|
|
82
|
+
reason: `unshare exited with code ${probe.status}`,
|
|
83
|
+
});
|
|
84
|
+
} catch (error) {
|
|
85
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
86
|
+
return (cachedProbe = { available: false, reason: `unshare probe threw: ${message}` });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Returns the path to the `unshare` binary, asserting it exists.
|
|
92
|
+
* Surfacing a typed error here (rather than letting `spawn` fail with
|
|
93
|
+
* ENOENT later) gives the parent a single, deterministic
|
|
94
|
+
* `tier0_unavailable` failure mode for "binary missing".
|
|
95
|
+
*/
|
|
96
|
+
export function resolveUnsharePath(): string {
|
|
97
|
+
const candidates = [
|
|
98
|
+
"/usr/bin/unshare",
|
|
99
|
+
"/bin/unshare",
|
|
100
|
+
"/usr/local/bin/unshare",
|
|
101
|
+
"/home/linuxbrew/.linuxbrew/bin/unshare",
|
|
102
|
+
];
|
|
103
|
+
for (const candidate of candidates) {
|
|
104
|
+
try {
|
|
105
|
+
if (statSync(candidate).isFile()) return candidate;
|
|
106
|
+
} catch {
|
|
107
|
+
// try next candidate
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
throw new Error("unshare binary not found in known locations");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Spawn the worker inside a rootless network namespace.
|
|
115
|
+
*
|
|
116
|
+
* The returned `ChildProcess` has STDIN, STDOUT, and STDERR piped so
|
|
117
|
+
* the parent can stream the input payload and parse the bounded
|
|
118
|
+
* result. Memory cap and netns are applied by the wrapper command;
|
|
119
|
+
* wall-clock + output cap are enforced by the parent runner on top of
|
|
120
|
+
* the returned streams.
|
|
121
|
+
*
|
|
122
|
+
* The caller keeps STDIN open for the worker's bounded NDJSON protocol
|
|
123
|
+
* and closes it only when tearing down the worker pool.
|
|
124
|
+
*/
|
|
125
|
+
export function spawnNetnsWorker(args: readonly string[]): ChildProcess {
|
|
126
|
+
return spawnWorker(args, true);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Spawn the same capped worker without the network namespace boundary. */
|
|
130
|
+
export function spawnDirectWorker(args: readonly string[]): ChildProcess {
|
|
131
|
+
return spawnWorker(args, false);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Select the Tier 0 isolation boundary for the configured insecure level. */
|
|
135
|
+
export function resolveTier0Isolation(level: 0 | 1 | 2 | 3): "netns" | "direct" {
|
|
136
|
+
const parsedLevel = InsecureLevelSchema.parse(level);
|
|
137
|
+
return parsedLevel <= 1 ? "netns" : "direct";
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function spawnWorker(args: readonly string[], useNetns: boolean): ChildProcess {
|
|
141
|
+
const bunPath = process.execPath;
|
|
142
|
+
// Compose the shell command. `sh -c` is required because the wrapper
|
|
143
|
+
// applies `ulimit` before exec-ing unshare — ulimit is a shell builtin
|
|
144
|
+
// and has no freestanding form. After the wrapper runs, unshare
|
|
145
|
+
// execs into the bun binary with the caller-supplied args, so the
|
|
146
|
+
// long-running worker is the bun process (not unshare or sh).
|
|
147
|
+
const tail = [quoteShell(bunPath), ...args.map(quoteShell)].join(" ");
|
|
148
|
+
const isolation = useNetns ? "unshare --map-current-user --net -- " : "";
|
|
149
|
+
const marker =
|
|
150
|
+
!useNetns && process.env[DIRECT_EXEC_MARKER_ENV] !== undefined
|
|
151
|
+
? `: > ${quoteShell(process.env[DIRECT_EXEC_MARKER_ENV]!)}`
|
|
152
|
+
: "";
|
|
153
|
+
const shellCommand = `${MEMORY_LIMIT_PREFIX}${marker.length > 0 ? `${marker}; ` : ""}exec ${isolation}${tail}`;
|
|
154
|
+
return spawn("/bin/sh", ["-c", shellCommand], {
|
|
155
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
156
|
+
// Detach from the parent's controlling terminal so SIGINT to the
|
|
157
|
+
// parent does not cascade into the worker.
|
|
158
|
+
detached: false,
|
|
159
|
+
env: {
|
|
160
|
+
...process.env,
|
|
161
|
+
// Force a deterministic locale so worker diagnostics don't pull
|
|
162
|
+
// in the host's UTF-8 assumptions about error message text.
|
|
163
|
+
LC_ALL: "C.UTF-8",
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Quote a single argv element for the shell command. Single-quotes
|
|
170
|
+
* the value and escapes embedded single quotes; safe for paths and
|
|
171
|
+
* flags that contain no shell metacharacters.
|
|
172
|
+
*/
|
|
173
|
+
function quoteShell(value: string): string {
|
|
174
|
+
if (value.length === 0) return "''";
|
|
175
|
+
if (/^[A-Za-z0-9._/=+-]+$/.test(value)) return value;
|
|
176
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
177
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tier 0 chart (Vega-Lite) parser.
|
|
3
|
+
*
|
|
4
|
+
* Two-step validation:
|
|
5
|
+
*
|
|
6
|
+
* 1. STRUCTURAL zod check on the raw spec — we forbid any `data`
|
|
7
|
+
* loader that references an external URL. Inline `data.values`
|
|
8
|
+
* and inline datasets are allowed because they cannot trigger a
|
|
9
|
+
* network fetch (and netns would block it anyway — but rejecting
|
|
10
|
+
* at Tier 0 makes the policy explicit on the wire).
|
|
11
|
+
*
|
|
12
|
+
* 2. `vega-lite.compile()` — produces a Vega spec from the input
|
|
13
|
+
* Vega-Lite spec WITHOUT a DOM. `compile()` is documented as a
|
|
14
|
+
* pure transform: it returns the compiled spec or throws a
|
|
15
|
+
* `vlCompileError`. We have verified empirically that `compile()`
|
|
16
|
+
* does not require a browser or `document` (verified against
|
|
17
|
+
* vega-lite@6 in this build).
|
|
18
|
+
*
|
|
19
|
+
* The Tier 0 verdict reports `graphCount: 1` for a successfully
|
|
20
|
+
* compiled chart (one rendered visualization) and `errorCount: 1` for
|
|
21
|
+
* a malformed or external-data spec.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { z } from "zod";
|
|
25
|
+
import { compile } from "vega-lite";
|
|
26
|
+
|
|
27
|
+
import type { DiscriminativeError, VerdictObserved } from "../../shared/contracts/validation";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Vega-Lite spec surface — STRICT subset. We accept only the data
|
|
31
|
+
* forms that cannot reference an external URL: inline `values` and an
|
|
32
|
+
* unnamed inline dataset reference. A `data` property whose value is
|
|
33
|
+
* a string or `{ url: … }` is rejected at the zod layer.
|
|
34
|
+
*/
|
|
35
|
+
const InlineDataSchema = z.union([
|
|
36
|
+
z.object({ values: z.array(z.unknown()) }).strict(),
|
|
37
|
+
z.null(),
|
|
38
|
+
z.undefined(),
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
const VegaLiteSpecSchema = z
|
|
42
|
+
.object({
|
|
43
|
+
$schema: z.string().optional(),
|
|
44
|
+
description: z.string().optional(),
|
|
45
|
+
// Leave the raw data value open here so the dedicated check below can
|
|
46
|
+
// report the security-specific external-data error instead of a generic
|
|
47
|
+
// schema failure.
|
|
48
|
+
data: z.unknown().optional(),
|
|
49
|
+
mark: z.union([z.string(), z.record(z.string(), z.unknown())]).optional(),
|
|
50
|
+
encoding: z.record(z.string(), z.unknown()).optional(),
|
|
51
|
+
layer: z.array(z.record(z.string(), z.unknown())).optional(),
|
|
52
|
+
transform: z.array(z.record(z.string(), z.unknown())).optional(),
|
|
53
|
+
width: z.union([z.number(), z.record(z.string(), z.unknown())]).optional(),
|
|
54
|
+
height: z.union([z.number(), z.record(z.string(), z.unknown())]).optional(),
|
|
55
|
+
})
|
|
56
|
+
.passthrough();
|
|
57
|
+
|
|
58
|
+
export interface ChartParseOk {
|
|
59
|
+
readonly status: "ok";
|
|
60
|
+
readonly observed: VerdictObserved;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface ChartParseFail {
|
|
64
|
+
readonly status: "error";
|
|
65
|
+
readonly observed: VerdictObserved;
|
|
66
|
+
readonly errors: readonly DiscriminativeError[];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export type ChartParseResult = ChartParseOk | ChartParseFail;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Parse the source bytes as a Vega-Lite chart spec. First the
|
|
73
|
+
* structural shape is validated (zod) so any external `data` reference
|
|
74
|
+
* is rejected before we hand the spec to the compiler; then
|
|
75
|
+
* `vega-lite.compile()` is called to confirm the spec is well-formed
|
|
76
|
+
* Vega-Lite.
|
|
77
|
+
*/
|
|
78
|
+
export function parseChart(bytes: Uint8Array): ChartParseResult {
|
|
79
|
+
const text = new TextDecoder("utf-8", { fatal: false }).decode(bytes);
|
|
80
|
+
let spec: unknown;
|
|
81
|
+
try {
|
|
82
|
+
spec = JSON.parse(text);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
85
|
+
return {
|
|
86
|
+
status: "error",
|
|
87
|
+
observed: {
|
|
88
|
+
rendererRootSvgCount: 0,
|
|
89
|
+
graphCount: 0,
|
|
90
|
+
mermaidNodeCount: 0,
|
|
91
|
+
visibleSvgCount: 0,
|
|
92
|
+
errorCount: 1,
|
|
93
|
+
opaqueRegionCount: 0,
|
|
94
|
+
externalImageCount: 0,
|
|
95
|
+
},
|
|
96
|
+
errors: [{ code: "chart_json_error", message }],
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// VegaLiteSpecSchema is strict about the top-level `data` shape —
|
|
101
|
+
// any spec whose `data` is a string or `{ url: … }` fails with a
|
|
102
|
+
// precise path so the wire response can point at the bad field.
|
|
103
|
+
const topLevel = VegaLiteSpecSchema.safeParse(spec);
|
|
104
|
+
if (!topLevel.success) {
|
|
105
|
+
return {
|
|
106
|
+
status: "error",
|
|
107
|
+
observed: {
|
|
108
|
+
rendererRootSvgCount: 0,
|
|
109
|
+
graphCount: 0,
|
|
110
|
+
mermaidNodeCount: 0,
|
|
111
|
+
visibleSvgCount: 0,
|
|
112
|
+
errorCount: 1,
|
|
113
|
+
opaqueRegionCount: 0,
|
|
114
|
+
externalImageCount: 0,
|
|
115
|
+
},
|
|
116
|
+
errors: topLevel.error.issues.slice(0, 5).map((issue) => ({
|
|
117
|
+
code: issue.code === "invalid_type" ? "chart_invalid_type" : "chart_invalid_spec",
|
|
118
|
+
message: `${issue.path.join(".") || "(root)"}: ${issue.message}`,
|
|
119
|
+
})),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
// Re-check the data field specifically to surface a precise error.
|
|
123
|
+
const dataCheck = InlineDataSchema.safeParse(topLevel.data.data);
|
|
124
|
+
if (!dataCheck.success) {
|
|
125
|
+
return {
|
|
126
|
+
status: "error",
|
|
127
|
+
observed: {
|
|
128
|
+
rendererRootSvgCount: 0,
|
|
129
|
+
graphCount: 0,
|
|
130
|
+
mermaidNodeCount: 0,
|
|
131
|
+
visibleSvgCount: 0,
|
|
132
|
+
errorCount: 1,
|
|
133
|
+
opaqueRegionCount: 0,
|
|
134
|
+
externalImageCount: 0,
|
|
135
|
+
},
|
|
136
|
+
errors: [
|
|
137
|
+
{
|
|
138
|
+
code: "chart_external_data_rejected",
|
|
139
|
+
message:
|
|
140
|
+
"Vega-Lite spec references external data (data.url / data string / loader); only inline data is allowed at Tier 0",
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
const compiled = compile(topLevel.data as unknown as Parameters<typeof compile>[0]);
|
|
148
|
+
void compiled.spec;
|
|
149
|
+
return {
|
|
150
|
+
status: "ok",
|
|
151
|
+
observed: {
|
|
152
|
+
rendererRootSvgCount: 1,
|
|
153
|
+
graphCount: 1,
|
|
154
|
+
mermaidNodeCount: 0,
|
|
155
|
+
visibleSvgCount: 1,
|
|
156
|
+
errorCount: 0,
|
|
157
|
+
opaqueRegionCount: 0,
|
|
158
|
+
externalImageCount: 0,
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
} catch (error) {
|
|
162
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
163
|
+
return {
|
|
164
|
+
status: "error",
|
|
165
|
+
observed: {
|
|
166
|
+
rendererRootSvgCount: 0,
|
|
167
|
+
graphCount: 0,
|
|
168
|
+
mermaidNodeCount: 0,
|
|
169
|
+
visibleSvgCount: 0,
|
|
170
|
+
errorCount: 1,
|
|
171
|
+
opaqueRegionCount: 0,
|
|
172
|
+
externalImageCount: 0,
|
|
173
|
+
},
|
|
174
|
+
errors: [{ code: "chart_compile_error", message }],
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* linkedom-based DOM shim for the Tier 0 worker.
|
|
3
|
+
*
|
|
4
|
+
* Mermaid and other browser-shaped libraries perform import-time
|
|
5
|
+
* initialization against `globalThis.document`, `window`, and the
|
|
6
|
+
* `implementation.createHTMLDocument` API. Bun's worker process has
|
|
7
|
+
* none of these by default, so a naive `import mermaid from "mermaid"`
|
|
8
|
+
* throws `DOMPurify.addHook is not a function` (or its equivalent)
|
|
9
|
+
* before the parser ever sees source.
|
|
10
|
+
*
|
|
11
|
+
* This module installs the shim at IMPORT TIME (top-level) so any
|
|
12
|
+
* subsequent `import "mermaid"` sees a DOM. It is structural only —
|
|
13
|
+
* it never executes any artifact source. The worker's netns prevents
|
|
14
|
+
* any library-initiated network egress.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { parseHTML } from "linkedom";
|
|
18
|
+
|
|
19
|
+
let installed = false;
|
|
20
|
+
export let domShimDocument: Document;
|
|
21
|
+
|
|
22
|
+
function installDomShim(): void {
|
|
23
|
+
if (installed) return;
|
|
24
|
+
installed = true;
|
|
25
|
+
const { document, window } = parseHTML("<!DOCTYPE html><html><body></body></html>");
|
|
26
|
+
domShimDocument = document as unknown as Document;
|
|
27
|
+
// DOMPurify's isSupported check looks for `implementation.createHTMLDocument`.
|
|
28
|
+
// linkedom ships without an implementation object; we attach a minimal shim
|
|
29
|
+
// that returns a freshly-parsed document. This is structural only —
|
|
30
|
+
// we never serialize against this DOM.
|
|
31
|
+
const fakeImpl = {
|
|
32
|
+
createHTMLDocument: (html: string): Document => {
|
|
33
|
+
const r = parseHTML(html);
|
|
34
|
+
return r.document as unknown as Document;
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
Object.defineProperty(document, "implementation", { value: fakeImpl, configurable: true });
|
|
38
|
+
const g = globalThis as unknown as Record<string, unknown>;
|
|
39
|
+
g["document"] = document;
|
|
40
|
+
g["window"] = window;
|
|
41
|
+
g["Element"] = window.Element;
|
|
42
|
+
g["HTMLElement"] = window.HTMLElement;
|
|
43
|
+
g["Node"] = window.Node;
|
|
44
|
+
g["DocumentFragment"] = window.DocumentFragment;
|
|
45
|
+
g["HTMLTemplateElement"] = window.HTMLTemplateElement;
|
|
46
|
+
g["NodeFilter"] = window.NodeFilter;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Top-level install — runs synchronously when this module is first
|
|
50
|
+
// imported. Any module that subsequently imports `mermaid` (which has
|
|
51
|
+
// import-time DOM checks) sees the structural DOM in place.
|
|
52
|
+
installDomShim();
|
|
53
|
+
|
|
54
|
+
export const domShimInstalled = true;
|