@ic-reactor/mcp 0.0.0-stage → 0.0.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/.github/ISSUE_TEMPLATE/bug_report.yml +108 -0
- package/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +77 -0
- package/.github/actions/setup-rust-wasm/action.yml +25 -0
- package/.github/dependabot.yml +107 -0
- package/.github/workflows/auth-latest.yml +67 -0
- package/.github/workflows/b3pay-site.yml +49 -0
- package/.github/workflows/ci.yml +430 -0
- package/.github/workflows/codeql.yml +41 -0
- package/.github/workflows/dependency-review.yml +68 -0
- package/.github/workflows/docs-check.yml +98 -0
- package/.github/workflows/docs.yml +265 -0
- package/.github/workflows/e2e.yml +90 -0
- package/.github/workflows/examples-published.yml +97 -0
- package/.github/workflows/release.yml +326 -0
- package/.husky/pre-commit +12 -0
- package/.prettierignore +109 -0
- package/.prettierrc +6 -0
- package/AGENTS.md +103 -0
- package/CHANGELOG.md +3268 -0
- package/CLAUDE.md +93 -0
- package/CNAME +1 -0
- package/CODE_OF_CONDUCT.md +7 -0
- package/CONTRIBUTING.md +433 -0
- package/LICENSE +21 -0
- package/README.md +2 -2
- package/SECURITY.md +74 -0
- package/b3pay-module.json +194 -0
- package/docs/.remarkrc.json +10 -0
- package/docs/README.md +64 -0
- package/docs/astro.config.mjs +205 -0
- package/docs/favicon.svg +15 -0
- package/docs/package.json +39 -0
- package/docs/public/favicon.svg +15 -0
- package/docs/public/ic-reactor-social-card.png +0 -0
- package/docs/scripts/b3pay-static.mjs +22 -0
- package/docs/scripts/check-example-docs.mjs +214 -0
- package/docs/scripts/check-links.mjs +212 -0
- package/docs/scripts/fix-dist-links.js +125 -0
- package/docs/scripts/fix-frontmatter.js +71 -0
- package/docs/scripts/remove-redundant-h1.cjs +48 -0
- package/docs/scripts/rewrite-typedoc-links.js +110 -0
- package/docs/scripts/split-index.js +69 -0
- package/docs/src/assets/ic-reactor-hero-dark.svg +23 -0
- package/docs/src/assets/ic-reactor-hero-light.svg +23 -0
- package/docs/src/assets/ic-reactor-lockup-dark.svg +15 -0
- package/docs/src/assets/ic-reactor-lockup-light.svg +15 -0
- package/docs/src/assets/ic-reactor-mark-dark.svg +14 -0
- package/docs/src/assets/ic-reactor-mark-light.svg +14 -0
- package/docs/src/assets/layers/candid-core-layer-icon-dark.svg +4 -0
- package/docs/src/assets/layers/candid-core-layer-icon-light.svg +4 -0
- package/docs/src/assets/layers/candid-profile-layer-icon-dark.svg +4 -0
- package/docs/src/assets/layers/candid-profile-layer-icon-light.svg +4 -0
- package/docs/src/assets/layers/ic-reactor-layer-icon-dark.svg +4 -0
- package/docs/src/assets/layers/ic-reactor-layer-icon-light.svg +4 -0
- package/docs/src/components/LibraryCards.astro +172 -0
- package/docs/src/components/LibraryIcon.astro +28 -0
- package/docs/src/components/SiteTitle.astro +241 -0
- package/docs/src/content/docs/404.mdx +19 -0
- package/docs/src/content/docs/build/agent-tools.mdx +593 -0
- package/docs/src/content/docs/build/icrc1-wallet.mdx +619 -0
- package/docs/src/content/docs/build/ssr-per-user.mdx +552 -0
- package/docs/src/content/docs/examples/canister-studio.mdx +95 -0
- package/docs/src/content/docs/examples/icrc-ledger.mdx +45 -0
- package/docs/src/content/docs/examples/index.mdx +38 -0
- package/docs/src/content/docs/examples/next-ssr.mdx +73 -0
- package/docs/src/content/docs/examples/node-agent-tool.mdx +80 -0
- package/docs/src/content/docs/examples/vite-wallet.mdx +71 -0
- package/docs/src/content/docs/guides/auth.mdx +1181 -0
- package/docs/src/content/docs/guides/client.mdx +551 -0
- package/docs/src/content/docs/guides/deploy.mdx +180 -0
- package/docs/src/content/docs/guides/errors.mdx +475 -0
- package/docs/src/content/docs/guides/getting-started.mdx +620 -0
- package/docs/src/content/docs/guides/reads.mdx +586 -0
- package/docs/src/content/docs/guides/ssr.mdx +529 -0
- package/docs/src/content/docs/guides/testing.mdx +401 -0
- package/docs/src/content/docs/guides/values.mdx +261 -0
- package/docs/src/content/docs/guides/vite-plugin.mdx +690 -0
- package/docs/src/content/docs/guides/writes.mdx +572 -0
- package/docs/src/content/docs/ic-reactor/index.mdx +167 -0
- package/docs/src/content/docs/index.mdx +65 -0
- package/docs/src/content/docs/migrating-from-3.mdx +535 -0
- package/docs/src/content/docs/packages/core.mdx +186 -0
- package/docs/src/content/docs/packages/mcp.mdx +163 -0
- package/docs/src/content/docs/packages/react.mdx +207 -0
- package/docs/src/content/docs/packages/vite-plugin.mdx +73 -0
- package/docs/src/content/docs/versioning.mdx +104 -0
- package/docs/src/content.config.ts +7 -0
- package/docs/src/styles/custom.css +189 -0
- package/docs/tsconfig.typedoc.json +26 -0
- package/docs/typedoc-entry.ts +17 -0
- package/docs/typedoc.json +37 -0
- package/e2e/Cargo.lock +646 -0
- package/e2e/Cargo.toml +3 -0
- package/e2e/README.md +101 -0
- package/e2e/frontends/build.mjs +94 -0
- package/e2e/frontends/index.html +10 -0
- package/e2e/global-setup.ts +65 -0
- package/e2e/icp.yaml +38 -0
- package/e2e/package.json +40 -0
- package/e2e/setup.ts +6 -0
- package/e2e/src/actor/Cargo.toml +14 -0
- package/e2e/src/actor/hello_actor.did +10 -0
- package/e2e/src/actor/src/lib.rs +73 -0
- package/e2e/src/calls.test.ts +132 -0
- package/e2e/src/declarations/hello_actor.envelope.json +175 -0
- package/e2e/src/declarations/hello_actor.ts +15 -0
- package/e2e/src/errors.test.ts +107 -0
- package/e2e/src/frontend-headers.test.ts +253 -0
- package/e2e/src/identity.test.ts +61 -0
- package/e2e/src/react.test.tsx +59 -0
- package/e2e/src/replica.ts +70 -0
- package/e2e/src/vite-env.test.ts +108 -0
- package/e2e/test.sh +47 -0
- package/e2e/tsconfig.json +20 -0
- package/e2e/tsconfig.typecheck.json +9 -0
- package/e2e/vitest.config.ts +22 -0
- package/eslint.config.mjs +100 -0
- package/evals/PREREGISTRATION.md +1346 -0
- package/evals/README.md +756 -0
- package/evals/conditions/thin/condition.json +8 -0
- package/evals/conditions/thin/docs/candid-core-cli.README.md +90 -0
- package/evals/conditions/thin/docs/candid-core-schema.README.md +339 -0
- package/evals/conditions/thin/package.json +25 -0
- package/evals/conditions/thin/starter/src/generated/icrc1.ts +57 -0
- package/evals/conditions/thin-guide/condition.json +10 -0
- package/evals/conditions/thin-guide/docs/llms.txt +292 -0
- package/evals/conditions/v3/condition.json +9 -0
- package/evals/conditions/v3/docs/ic-reactor-core.llms.txt +232 -0
- package/evals/conditions/v3/docs/ic-reactor-react.llms.txt +280 -0
- package/evals/conditions/v3/docs/llms-full.txt +1053 -0
- package/evals/conditions/v3/package.json +26 -0
- package/evals/conditions/v3/starter/src/declarations/icrc1.did.d.ts +95 -0
- package/evals/conditions/v3/starter/src/declarations/icrc1.did.js +140 -0
- package/evals/conditions/v4/PORTING.md +384 -0
- package/evals/conditions/v4/README.md +106 -0
- package/evals/conditions/v4/condition.json +5 -0
- package/evals/conditions/v4/docs/llms.txt +323 -0
- package/evals/conditions/v4/package.json +27 -0
- package/evals/conditions/v4/starter/src/generated/icrc1.ts +78 -0
- package/evals/conditions/v4-proto/condition.json +6 -0
- package/evals/conditions/v4-proto/docs/llms.txt +291 -0
- package/evals/conditions/v4-proto/lib/SHIM-NOTES.md +20 -0
- package/evals/conditions/v4-proto/lib/package.json +27 -0
- package/evals/conditions/v4-proto/lib/src/client.ts +471 -0
- package/evals/conditions/v4-proto/lib/src/errors.ts +214 -0
- package/evals/conditions/v4-proto/lib/src/index.ts +36 -0
- package/evals/conditions/v4-proto/lib/src/principal.ts +175 -0
- package/evals/conditions/v4-proto/lib/src/service.ts +69 -0
- package/evals/conditions/v4-proto/lib/src/types.ts +174 -0
- package/evals/conditions/v4-proto/lib/test/runtime.test.ts +170 -0
- package/evals/conditions/v4-proto/lib/test/tsconfig.json +10 -0
- package/evals/conditions/v4-proto/lib/test/types.test-d.ts +145 -0
- package/evals/conditions/v4-proto/lib/test/vitest.config.mjs +8 -0
- package/evals/conditions/v4-proto/lib/tsconfig.json +17 -0
- package/evals/conditions/v4-proto/package.json +26 -0
- package/evals/conditions/v4-proto/starter/src/generated/icrc1.service.ts +19 -0
- package/evals/conditions/v4-proto/starter/src/generated/icrc1.ts +57 -0
- package/evals/drive.mjs +1221 -0
- package/evals/gate.mjs +161 -0
- package/evals/harness/aggregate.mjs +534 -0
- package/evals/harness/aggregate.test.mjs +344 -0
- package/evals/harness/assemble.mjs +216 -0
- package/evals/harness/assemble.test.mjs +143 -0
- package/evals/harness/auth.mjs +91 -0
- package/evals/harness/check-docs.mjs +157 -0
- package/evals/harness/declarations/icrc1.did.d.ts +95 -0
- package/evals/harness/declarations/icrc1.did.js +140 -0
- package/evals/harness/drive-auth.test.mjs +584 -0
- package/evals/harness/drive.test.mjs +509 -0
- package/evals/harness/fake-auth.ts +67 -0
- package/evals/harness/fake-ledger.ts +178 -0
- package/evals/harness/gate-plan.mjs +104 -0
- package/evals/harness/gate-plan.test.mjs +243 -0
- package/evals/harness/icrc1.did +88 -0
- package/evals/harness/judge.mjs +63 -0
- package/evals/harness/judge.test.mjs +81 -0
- package/evals/harness/leak-scan.mjs +1373 -0
- package/evals/harness/leak-scan.test.mjs +1300 -0
- package/evals/harness/probes/amounts.ts +14 -0
- package/evals/harness/probes/node-tool.probe.ts +48 -0
- package/evals/harness/probes/probe.mjs +149 -0
- package/evals/harness/probes/react-wallet.probe.ts +96 -0
- package/evals/harness/public/support/auth.ts +38 -0
- package/evals/harness/public/support/fake-replica.d.ts +28 -0
- package/evals/harness/public/support/fake-replica.js +434 -0
- package/evals/harness/public/support/ledger.idl.d.ts +95 -0
- package/evals/harness/public/support/ledger.idl.js +140 -0
- package/evals/harness/public/support/world.ts +179 -0
- package/evals/harness/runs.mjs +139 -0
- package/evals/harness/sandbox.mjs +88 -0
- package/evals/harness/sandbox.test.mjs +134 -0
- package/evals/harness/ship.mjs +264 -0
- package/evals/harness/ship.test.mjs +552 -0
- package/evals/harness/world.ts +291 -0
- package/evals/package.json +26 -0
- package/evals/pnpm-lock.yaml +1537 -0
- package/evals/pnpm-workspace.yaml +12 -0
- package/evals/results/2026-09-30T14-22-05-924Z/pilot.json +384 -0
- package/evals/results/2026-09-30T14-22-05-924Z/pilot.rescanned.json +404 -0
- package/evals/results/2026-09-30T14-22-05-924Z/pilot.rescanned.rescored.json +404 -0
- package/evals/results/2026-09-30T14-22-05-924Z/plan.json +240 -0
- package/evals/results/2026-09-30T14-22-05-924Z/summary.json +1800 -0
- package/evals/results/2026-09-30T14-22-05-924Z/summary.rescanned.json +1843 -0
- package/evals/results/2026-09-30T14-22-05-924Z/summary.rescanned.rescored.json +2100 -0
- package/evals/results/2026-09-30T15-48-23-343Z/pilot.json +404 -0
- package/evals/results/2026-09-30T15-48-23-343Z/pilot.rescored.json +404 -0
- package/evals/results/2026-09-30T15-48-23-343Z/plan.json +242 -0
- package/evals/results/2026-09-30T15-48-23-343Z/summary.json +1843 -0
- package/evals/results/2026-09-30T15-48-23-343Z/summary.rescored.json +2195 -0
- package/evals/results/2026-10-03-beta1-gate/pilot.json +162 -0
- package/evals/results/2026-10-03-beta1-gate/plan.json +141 -0
- package/evals/results/2026-10-03-beta1-gate/summary.json +983 -0
- package/evals/results/2026-10-05-ga-gate/contaminated-run.json +55 -0
- package/evals/results/2026-10-05-ga-gate/pilot.json +162 -0
- package/evals/results/2026-10-05-ga-gate/plan.json +457 -0
- package/evals/results/2026-10-05-ga-gate/summary.json +1037 -0
- package/evals/results/2026-10-05-ga-gate/summary.rescanned.json +1037 -0
- package/evals/results/README.md +31 -0
- package/evals/runs-pilot.log +150 -0
- package/evals/runs-pilot2.log +150 -0
- package/evals/score.mjs +256 -0
- package/evals/setup.mjs +547 -0
- package/evals/tasks/node-tool/faulty/thin-accepts-exponent/meta.json +5 -0
- package/evals/tasks/node-tool/faulty/thin-accepts-exponent/src/index.ts +121 -0
- package/evals/tasks/node-tool/faulty/thin-anonymous-transfer/meta.json +5 -0
- package/evals/tasks/node-tool/faulty/thin-anonymous-transfer/src/index.ts +116 -0
- package/evals/tasks/node-tool/faulty/thin-every-reject-may-have-executed/meta.json +9 -0
- package/evals/tasks/node-tool/faulty/thin-every-reject-may-have-executed/src/index.ts +118 -0
- package/evals/tasks/node-tool/faulty/thin-fetches-root-key-off-local/meta.json +5 -0
- package/evals/tasks/node-tool/faulty/thin-fetches-root-key-off-local/src/index.ts +118 -0
- package/evals/tasks/node-tool/faulty/thin-ignores-root-key/meta.json +5 -0
- package/evals/tasks/node-tool/faulty/thin-ignores-root-key/src/index.ts +117 -0
- package/evals/tasks/node-tool/faulty/thin-misclassifies-reject-codes/meta.json +9 -0
- package/evals/tasks/node-tool/faulty/thin-misclassifies-reject-codes/src/index.ts +119 -0
- package/evals/tasks/node-tool/faulty/thin-no-nat64-cap/meta.json +9 -0
- package/evals/tasks/node-tool/faulty/thin-no-nat64-cap/src/index.ts +118 -0
- package/evals/tasks/node-tool/faulty/thin-number-amount/meta.json +10 -0
- package/evals/tasks/node-tool/faulty/thin-number-amount/src/index.ts +117 -0
- package/evals/tasks/node-tool/faulty/thin-truncates-fraction-digits/meta.json +5 -0
- package/evals/tasks/node-tool/faulty/thin-truncates-fraction-digits/src/index.ts +120 -0
- package/evals/tasks/node-tool/faulty/thin-unvalidated-recipient/meta.json +5 -0
- package/evals/tasks/node-tool/faulty/thin-unvalidated-recipient/src/index.ts +118 -0
- package/evals/tasks/node-tool/faulty/v3-retry-call-errors/meta.json +11 -0
- package/evals/tasks/node-tool/faulty/v3-retry-call-errors/src/index.ts +149 -0
- package/evals/tasks/node-tool/faulty/v4-anonymous-identity-sent/meta.json +5 -0
- package/evals/tasks/node-tool/faulty/v4-anonymous-identity-sent/src/index.ts +71 -0
- package/evals/tasks/node-tool/faulty/v4-never-may-have-executed/meta.json +9 -0
- package/evals/tasks/node-tool/faulty/v4-never-may-have-executed/src/index.ts +84 -0
- package/evals/tasks/node-tool/faulty/v4-proto-never-may-have-executed/meta.json +9 -0
- package/evals/tasks/node-tool/faulty/v4-proto-never-may-have-executed/src/index.ts +72 -0
- package/evals/tasks/node-tool/faulty/v4-proto-refuses-nat64-max/meta.json +5 -0
- package/evals/tasks/node-tool/faulty/v4-proto-refuses-nat64-max/src/index.ts +72 -0
- package/evals/tasks/node-tool/faulty/v4-refuses-nat64-max/meta.json +5 -0
- package/evals/tasks/node-tool/faulty/v4-refuses-nat64-max/src/index.ts +84 -0
- package/evals/tasks/node-tool/hidden/node-tool.test.ts +312 -0
- package/evals/tasks/node-tool/prompt.md +55 -0
- package/evals/tasks/node-tool/prompt.minimal.md +30 -0
- package/evals/tasks/node-tool/solutions/thin/reference/src/index.ts +118 -0
- package/evals/tasks/node-tool/solutions/thin/reference-module-scope/src/index.ts +145 -0
- package/evals/tasks/node-tool/solutions/thin-guide/reference/src/index.ts +118 -0
- package/evals/tasks/node-tool/solutions/thin-guide/reference-module-scope/src/index.ts +145 -0
- package/evals/tasks/node-tool/solutions/v3/reference/src/index.ts +140 -0
- package/evals/tasks/node-tool/solutions/v3/reference-module-scope/src/index.ts +153 -0
- package/evals/tasks/node-tool/solutions/v4/reference/src/index.ts +84 -0
- package/evals/tasks/node-tool/solutions/v4/reference-module-scope/src/index.ts +105 -0
- package/evals/tasks/node-tool/solutions/v4-proto/reference/src/index.ts +72 -0
- package/evals/tasks/node-tool/solutions/v4-proto/reference-module-scope/src/index.ts +87 -0
- package/evals/tasks/node-tool/starter/src/contract.ts +31 -0
- package/evals/tasks/node-tool/starter/src/index.ts +7 -0
- package/evals/tasks/node-tool/starter/test/smoke.test.ts +22 -0
- package/evals/tasks/node-tool/starter/tsconfig.json +17 -0
- package/evals/tasks/node-tool/task.json +63 -0
- package/evals/tasks/react-wallet/faulty/thin-accepts-exponent/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/thin-accepts-exponent/src/Wallet.tsx +216 -0
- package/evals/tasks/react-wallet/faulty/thin-anonymous-transfer/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/thin-anonymous-transfer/src/Wallet.tsx +212 -0
- package/evals/tasks/react-wallet/faulty/thin-both-auth-buttons/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/thin-both-auth-buttons/src/Wallet.tsx +210 -0
- package/evals/tasks/react-wallet/faulty/thin-constant-balance-key/meta.json +8 -0
- package/evals/tasks/react-wallet/faulty/thin-constant-balance-key/src/Wallet.tsx +213 -0
- package/evals/tasks/react-wallet/faulty/thin-no-nat64-cap/meta.json +9 -0
- package/evals/tasks/react-wallet/faulty/thin-no-nat64-cap/src/Wallet.tsx +213 -0
- package/evals/tasks/react-wallet/faulty/thin-number-balance/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/thin-number-balance/src/Wallet.tsx +213 -0
- package/evals/tasks/react-wallet/faulty/thin-transfer-as-query/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/thin-transfer-as-query/src/Wallet.tsx +254 -0
- package/evals/tasks/react-wallet/faulty/thin-truncates-fraction-digits/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/thin-truncates-fraction-digits/src/Wallet.tsx +215 -0
- package/evals/tasks/react-wallet/faulty/thin-unguarded-balance-effect/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/thin-unguarded-balance-effect/src/Wallet.tsx +214 -0
- package/evals/tasks/react-wallet/faulty/v3-http-4xx-unknown/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/v3-http-4xx-unknown/src/Wallet.tsx +224 -0
- package/evals/tasks/react-wallet/faulty/v3-mutation-retry-3/meta.json +12 -0
- package/evals/tasks/react-wallet/faulty/v3-mutation-retry-3/src/Wallet.tsx +225 -0
- package/evals/tasks/react-wallet/faulty/v3-unknown-as-error/meta.json +11 -0
- package/evals/tasks/react-wallet/faulty/v3-unknown-as-error/src/Wallet.tsx +182 -0
- package/evals/tasks/react-wallet/faulty/v4-every-reject-unknown/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/v4-every-reject-unknown/src/Wallet.tsx +160 -0
- package/evals/tasks/react-wallet/faulty/v4-keep-previous-data/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/v4-keep-previous-data/src/Wallet.tsx +166 -0
- package/evals/tasks/react-wallet/faulty/v4-proto-every-reject-unknown/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/v4-proto-every-reject-unknown/src/Wallet.tsx +161 -0
- package/evals/tasks/react-wallet/faulty/v4-proto-keep-previous-data/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/v4-proto-keep-previous-data/src/Wallet.tsx +163 -0
- package/evals/tasks/react-wallet/faulty/v4-proto-retry-spread/meta.json +12 -0
- package/evals/tasks/react-wallet/faulty/v4-proto-retry-spread/src/Wallet.tsx +162 -0
- package/evals/tasks/react-wallet/faulty/v4-proto-status-not-idle/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/v4-proto-status-not-idle/src/Wallet.tsx +163 -0
- package/evals/tasks/react-wallet/faulty/v4-retry-spread/meta.json +12 -0
- package/evals/tasks/react-wallet/faulty/v4-retry-spread/src/Wallet.tsx +163 -0
- package/evals/tasks/react-wallet/faulty/v4-status-not-idle/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/v4-status-not-idle/src/Wallet.tsx +162 -0
- package/evals/tasks/react-wallet/hidden/react-wallet.test.ts +536 -0
- package/evals/tasks/react-wallet/prompt.md +88 -0
- package/evals/tasks/react-wallet/prompt.minimal.md +51 -0
- package/evals/tasks/react-wallet/solutions/thin/reference/src/Wallet.tsx +213 -0
- package/evals/tasks/react-wallet/solutions/thin/reference-module-scope/src/Wallet.tsx +227 -0
- package/evals/tasks/react-wallet/solutions/thin-guide/reference/src/Wallet.tsx +213 -0
- package/evals/tasks/react-wallet/solutions/thin-guide/reference-module-scope/src/Wallet.tsx +227 -0
- package/evals/tasks/react-wallet/solutions/v3/reference/src/Wallet.tsx +224 -0
- package/evals/tasks/react-wallet/solutions/v3/reference-module-scope/src/Wallet.tsx +230 -0
- package/evals/tasks/react-wallet/solutions/v4/reference/src/Wallet.tsx +160 -0
- package/evals/tasks/react-wallet/solutions/v4/reference-module-scope/src/Wallet.tsx +178 -0
- package/evals/tasks/react-wallet/solutions/v4-proto/reference/src/Wallet.tsx +161 -0
- package/evals/tasks/react-wallet/solutions/v4-proto/reference-module-scope/src/Wallet.tsx +161 -0
- package/evals/tasks/react-wallet/starter/src/Wallet.tsx +12 -0
- package/evals/tasks/react-wallet/starter/src/auth.ts +16 -0
- package/evals/tasks/react-wallet/starter/src/config.ts +14 -0
- package/evals/tasks/react-wallet/starter/src/ii-auth.ts +43 -0
- package/evals/tasks/react-wallet/starter/src/main.tsx +15 -0
- package/evals/tasks/react-wallet/starter/test/smoke.test.ts +34 -0
- package/evals/tasks/react-wallet/starter/tsconfig.json +18 -0
- package/evals/tasks/react-wallet/task.json +84 -0
- package/examples/canister-studio/.stackblitzrc +4 -0
- package/examples/canister-studio/README.md +183 -0
- package/examples/canister-studio/did/ckbtc_minter.did +806 -0
- package/examples/canister-studio/did/icp_ledger.did +586 -0
- package/examples/canister-studio/did/icrc1.did +88 -0
- package/examples/canister-studio/package.json +45 -0
- package/examples/canister-studio/scripts/smoke.ts +188 -0
- package/examples/canister-studio/src/calls/CacheKeyPanel.tsx +104 -0
- package/examples/canister-studio/src/calls/ConfirmWrite.tsx +265 -0
- package/examples/canister-studio/src/calls/ErrorPanel.test.tsx +113 -0
- package/examples/canister-studio/src/calls/ErrorPanel.tsx +214 -0
- package/examples/canister-studio/src/calls/MethodRunner.test.tsx +354 -0
- package/examples/canister-studio/src/calls/MethodRunner.tsx +57 -0
- package/examples/canister-studio/src/calls/ReadRunner.tsx +244 -0
- package/examples/canister-studio/src/calls/ResendPanel.tsx +165 -0
- package/examples/canister-studio/src/calls/ShareLink.tsx +59 -0
- package/examples/canister-studio/src/calls/WriteRunner.test.tsx +678 -0
- package/examples/canister-studio/src/calls/WriteRunner.tsx +670 -0
- package/examples/canister-studio/src/calls/dedupe.ts +189 -0
- package/examples/canister-studio/src/calls/error-info.ts +168 -0
- package/examples/canister-studio/src/calls/history.test.ts +284 -0
- package/examples/canister-studio/src/calls/history.ts +287 -0
- package/examples/canister-studio/src/calls/kinds.ts +68 -0
- package/examples/canister-studio/src/calls/prepare.ts +32 -0
- package/examples/canister-studio/src/calls/query-options.ts +30 -0
- package/examples/canister-studio/src/calls/read.ts +47 -0
- package/examples/canister-studio/src/calls/readback.ts +100 -0
- package/examples/canister-studio/src/calls/record.ts +123 -0
- package/examples/canister-studio/src/calls/safe-calls.test.ts +299 -0
- package/examples/canister-studio/src/calls/share.ts +46 -0
- package/examples/canister-studio/src/calls/testing/doubles.tsx +108 -0
- package/examples/canister-studio/src/calls/testing/fixtures.ts +185 -0
- package/examples/canister-studio/src/calls/useCanisterHandle.ts +35 -0
- package/examples/canister-studio/src/canisters/ckbtc_minter.envelope.json +4228 -0
- package/examples/canister-studio/src/canisters/ckbtc_minter.ts +868 -0
- package/examples/canister-studio/src/canisters/icp_ledger.envelope.json +3454 -0
- package/examples/canister-studio/src/canisters/icp_ledger.ts +584 -0
- package/examples/canister-studio/src/canisters/icrc1.envelope.json +840 -0
- package/examples/canister-studio/src/canisters/icrc1.ts +78 -0
- package/examples/canister-studio/src/components/ActionButton.tsx +32 -0
- package/examples/canister-studio/src/components/AppHeader.tsx +83 -0
- package/examples/canister-studio/src/components/AppShell.tsx +29 -0
- package/examples/canister-studio/src/components/AuthButton.tsx +79 -0
- package/examples/canister-studio/src/components/Badge.tsx +79 -0
- package/examples/canister-studio/src/components/CanisterHeader.tsx +200 -0
- package/examples/canister-studio/src/components/CanisterJump.tsx +130 -0
- package/examples/canister-studio/src/components/Card.tsx +64 -0
- package/examples/canister-studio/src/components/Code.tsx +101 -0
- package/examples/canister-studio/src/components/CopyButton.tsx +126 -0
- package/examples/canister-studio/src/components/Disclosure.tsx +34 -0
- package/examples/canister-studio/src/components/EmptyState.tsx +34 -0
- package/examples/canister-studio/src/components/MethodList.test.tsx +137 -0
- package/examples/canister-studio/src/components/MethodList.tsx +244 -0
- package/examples/canister-studio/src/components/Notice.tsx +36 -0
- package/examples/canister-studio/src/components/ProvenanceBadge.tsx +44 -0
- package/examples/canister-studio/src/components/RouteError.tsx +42 -0
- package/examples/canister-studio/src/components/SectionBoundary.tsx +76 -0
- package/examples/canister-studio/src/components/Spinner.tsx +33 -0
- package/examples/canister-studio/src/components/Tabs.tsx +96 -0
- package/examples/canister-studio/src/components/canister/CanisterLoadFailure.test.tsx +56 -0
- package/examples/canister-studio/src/components/canister/CanisterLoadFailure.tsx +165 -0
- package/examples/canister-studio/src/components/canister/CanisterNotFound.tsx +59 -0
- package/examples/canister-studio/src/components/canister/CanisterPending.tsx +33 -0
- package/examples/canister-studio/src/components/canister/MethodOverview.tsx +93 -0
- package/examples/canister-studio/src/components/canister/MethodWorkspace.test.tsx +201 -0
- package/examples/canister-studio/src/components/canister/MethodWorkspace.tsx +187 -0
- package/examples/canister-studio/src/components/canister/shared-call.test.ts +186 -0
- package/examples/canister-studio/src/components/canister/shared-call.ts +133 -0
- package/examples/canister-studio/src/components/canister/shared-link-loader.test.ts +116 -0
- package/examples/canister-studio/src/components/canister/use-canister-page.ts +44 -0
- package/examples/canister-studio/src/components/canister/workspace-key.test.ts +27 -0
- package/examples/canister-studio/src/components/canister/workspace-key.ts +36 -0
- package/examples/canister-studio/src/components/canister-id-input.ts +69 -0
- package/examples/canister-studio/src/components/canister-names.ts +11 -0
- package/examples/canister-studio/src/components/download.ts +34 -0
- package/examples/canister-studio/src/components/fallbacks.tsx +33 -0
- package/examples/canister-studio/src/components/helpers.test.ts +107 -0
- package/examples/canister-studio/src/components/history/HistoryList.tsx +284 -0
- package/examples/canister-studio/src/components/home/FeaturedLedger.tsx +149 -0
- package/examples/canister-studio/src/components/home/OpenCanister.tsx +99 -0
- package/examples/canister-studio/src/components/home/featured.test.tsx +92 -0
- package/examples/canister-studio/src/components/home/featured.ts +64 -0
- package/examples/canister-studio/src/components/interface/CandidTab.tsx +38 -0
- package/examples/canister-studio/src/components/interface/CompatReport.tsx +92 -0
- package/examples/canister-studio/src/components/interface/CompatibilityTab.tsx +190 -0
- package/examples/canister-studio/src/components/interface/ProjectTab.tsx +196 -0
- package/examples/canister-studio/src/components/interface/TypeScriptTab.tsx +114 -0
- package/examples/canister-studio/src/components/interface/tools.test.tsx +169 -0
- package/examples/canister-studio/src/components/modes.ts +31 -0
- package/examples/canister-studio/src/components/playground/MethodPicker.tsx +67 -0
- package/examples/canister-studio/src/components/playground/PlaygroundForms.tsx +112 -0
- package/examples/canister-studio/src/components/playground/PlaygroundTools.tsx +219 -0
- package/examples/canister-studio/src/components/playground/sample.ts +43 -0
- package/examples/canister-studio/src/components/playground/target.test.tsx +86 -0
- package/examples/canister-studio/src/components/primitives.test.tsx +121 -0
- package/examples/canister-studio/src/components/provenance.ts +38 -0
- package/examples/canister-studio/src/components/route-render.test.tsx +71 -0
- package/examples/canister-studio/src/components/use-task.ts +56 -0
- package/examples/canister-studio/src/components/wallet/BalancePanel.tsx +110 -0
- package/examples/canister-studio/src/components/wallet/LedgerSummary.tsx +63 -0
- package/examples/canister-studio/src/components/wallet/MinterPanel.tsx +255 -0
- package/examples/canister-studio/src/components/wallet/SendPanel.test.tsx +297 -0
- package/examples/canister-studio/src/components/wallet/SendPanel.tsx +446 -0
- package/examples/canister-studio/src/components/wallet/ledgers.test.ts +36 -0
- package/examples/canister-studio/src/components/wallet/ledgers.ts +111 -0
- package/examples/canister-studio/src/forms/JsonPanel.tsx +157 -0
- package/examples/canister-studio/src/forms/MethodForm.test.tsx +369 -0
- package/examples/canister-studio/src/forms/MethodForm.tsx +368 -0
- package/examples/canister-studio/src/forms/TypedForm.tsx +55 -0
- package/examples/canister-studio/src/forms/context.ts +62 -0
- package/examples/canister-studio/src/forms/convert.ts +131 -0
- package/examples/canister-studio/src/forms/edge.test.ts +252 -0
- package/examples/canister-studio/src/forms/engine.test.ts +358 -0
- package/examples/canister-studio/src/forms/engine.ts +1492 -0
- package/examples/canister-studio/src/forms/fields/BytesField.tsx +210 -0
- package/examples/canister-studio/src/forms/fields/Field.tsx +106 -0
- package/examples/canister-studio/src/forms/fields/FieldShell.tsx +101 -0
- package/examples/canister-studio/src/forms/fields/Group.tsx +74 -0
- package/examples/canister-studio/src/forms/fields/ListField.tsx +256 -0
- package/examples/canister-studio/src/forms/fields/Segmented.tsx +78 -0
- package/examples/canister-studio/src/forms/fields/TimestampField.tsx +108 -0
- package/examples/canister-studio/src/forms/fields/UnitsField.tsx +119 -0
- package/examples/canister-studio/src/forms/fields/composites.tsx +389 -0
- package/examples/canister-studio/src/forms/fields/leaves.tsx +350 -0
- package/examples/canister-studio/src/forms/fields.test.tsx +396 -0
- package/examples/canister-studio/src/forms/helpers.test.ts +225 -0
- package/examples/canister-studio/src/forms/hints.ts +128 -0
- package/examples/canister-studio/src/forms/json.ts +119 -0
- package/examples/canister-studio/src/forms/parsing.test.ts +217 -0
- package/examples/canister-studio/src/forms/useMethodForm.ts +251 -0
- package/examples/canister-studio/src/interface/browser.test.ts +23 -0
- package/examples/canister-studio/src/interface/browser.ts +153 -0
- package/examples/canister-studio/src/interface/cache.ts +68 -0
- package/examples/canister-studio/src/interface/cli-cache.test.ts +155 -0
- package/examples/canister-studio/src/interface/cli-cache.ts +125 -0
- package/examples/canister-studio/src/interface/compile.server.test.ts +198 -0
- package/examples/canister-studio/src/interface/compile.server.ts +262 -0
- package/examples/canister-studio/src/interface/compile.test.ts +108 -0
- package/examples/canister-studio/src/interface/compile.ts +305 -0
- package/examples/canister-studio/src/interface/docs.test.ts +75 -0
- package/examples/canister-studio/src/interface/docs.ts +100 -0
- package/examples/canister-studio/src/interface/functions.test.ts +42 -0
- package/examples/canister-studio/src/interface/functions.ts +200 -0
- package/examples/canister-studio/src/interface/live-candid.server.ts +91 -0
- package/examples/canister-studio/src/interface/live-loader.test.ts +515 -0
- package/examples/canister-studio/src/interface/live-loader.ts +364 -0
- package/examples/canister-studio/src/interface/load.test.ts +328 -0
- package/examples/canister-studio/src/interface/load.ts +249 -0
- package/examples/canister-studio/src/interface/read-candid.test.ts +185 -0
- package/examples/canister-studio/src/interface/read-candid.ts +233 -0
- package/examples/canister-studio/src/interface/server-result.ts +42 -0
- package/examples/canister-studio/src/interface/typed.ts +68 -0
- package/examples/canister-studio/src/interface/types.ts +121 -0
- package/examples/canister-studio/src/lib/format.test.ts +107 -0
- package/examples/canister-studio/src/lib/format.ts +139 -0
- package/examples/canister-studio/src/lib/hex.test.ts +43 -0
- package/examples/canister-studio/src/lib/hex.ts +33 -0
- package/examples/canister-studio/src/lib/ids.test.ts +70 -0
- package/examples/canister-studio/src/lib/ids.ts +93 -0
- package/examples/canister-studio/src/lib/reactor.test.ts +55 -0
- package/examples/canister-studio/src/lib/reactor.ts +55 -0
- package/examples/canister-studio/src/lib/registry.test.ts +74 -0
- package/examples/canister-studio/src/lib/registry.ts +238 -0
- package/examples/canister-studio/src/lib/schema.ts +148 -0
- package/examples/canister-studio/src/routeTree.gen.ts +227 -0
- package/examples/canister-studio/src/router.test.ts +290 -0
- package/examples/canister-studio/src/router.tsx +127 -0
- package/examples/canister-studio/src/routes/__root.tsx +58 -0
- package/examples/canister-studio/src/routes/canister.$canisterId.index.tsx +172 -0
- package/examples/canister-studio/src/routes/canister.$canisterId.interface.tsx +98 -0
- package/examples/canister-studio/src/routes/canister.$canisterId.tsx +91 -0
- package/examples/canister-studio/src/routes/history.tsx +55 -0
- package/examples/canister-studio/src/routes/index.tsx +169 -0
- package/examples/canister-studio/src/routes/playground.tsx +286 -0
- package/examples/canister-studio/src/routes/safety.tsx +57 -0
- package/examples/canister-studio/src/routes/wallet.tsx +213 -0
- package/examples/canister-studio/src/safety/LabTransferForm.tsx +84 -0
- package/examples/canister-studio/src/safety/RequestLog.tsx +79 -0
- package/examples/canister-studio/src/safety/SafetyLab.test.tsx +89 -0
- package/examples/canister-studio/src/safety/SafetyLab.tsx +140 -0
- package/examples/canister-studio/src/safety/ScenarioCard.tsx +262 -0
- package/examples/canister-studio/src/safety/lab-run.ts +283 -0
- package/examples/canister-studio/src/safety/mock-ledger.test.ts +99 -0
- package/examples/canister-studio/src/safety/mock-ledger.ts +263 -0
- package/examples/canister-studio/src/safety/scenarios.test.ts +157 -0
- package/examples/canister-studio/src/safety/scenarios.ts +924 -0
- package/examples/canister-studio/src/styles.css +3453 -0
- package/examples/canister-studio/src/styles.test.ts +46 -0
- package/examples/canister-studio/src/values/CallPreview.test.tsx +86 -0
- package/examples/canister-studio/src/values/CallPreview.tsx +148 -0
- package/examples/canister-studio/src/values/ErrValueView.tsx +66 -0
- package/examples/canister-studio/src/values/Inspector.tsx +133 -0
- package/examples/canister-studio/src/values/ReplyView.test.tsx +341 -0
- package/examples/canister-studio/src/values/ReplyView.tsx +164 -0
- package/examples/canister-studio/src/values/candid-text.test.ts +516 -0
- package/examples/canister-studio/src/values/candid-text.ts +479 -0
- package/examples/canister-studio/src/values/hints.ts +58 -0
- package/examples/canister-studio/src/values/json.test.ts +66 -0
- package/examples/canister-studio/src/values/json.ts +31 -0
- package/examples/canister-studio/src/values/leaves.tsx +309 -0
- package/examples/canister-studio/src/values/schema.test.ts +123 -0
- package/examples/canister-studio/src/values/schema.ts +78 -0
- package/examples/canister-studio/src/values/test-router.tsx +21 -0
- package/examples/canister-studio/src/values/tree.tsx +670 -0
- package/examples/canister-studio/src/values/use-sha256.ts +29 -0
- package/examples/canister-studio/tsconfig.json +20 -0
- package/examples/canister-studio/vite.config.ts +50 -0
- package/examples/canister-studio/vitest.config.ts +20 -0
- package/examples/icrc-ledger/.stackblitzrc +4 -0
- package/examples/icrc-ledger/README.md +100 -0
- package/examples/icrc-ledger/ckbtc_minter.did +12 -0
- package/examples/icrc-ledger/icp_ledger.did +147 -0
- package/examples/icrc-ledger/icrc1.did +88 -0
- package/examples/icrc-ledger/index.html +13 -0
- package/examples/icrc-ledger/package.json +40 -0
- package/examples/icrc-ledger/scripts/smoke.ts +151 -0
- package/examples/icrc-ledger/src/App.tsx +71 -0
- package/examples/icrc-ledger/src/ErrorPanel.tsx +105 -0
- package/examples/icrc-ledger/src/MainnetTab.tsx +450 -0
- package/examples/icrc-ledger/src/SandboxTab.test.tsx +379 -0
- package/examples/icrc-ledger/src/SandboxTab.tsx +761 -0
- package/examples/icrc-ledger/src/blocks.test.ts +247 -0
- package/examples/icrc-ledger/src/blocks.ts +140 -0
- package/examples/icrc-ledger/src/canisters/ckbtc_minter.envelope.json +96 -0
- package/examples/icrc-ledger/src/canisters/ckbtc_minter.ts +16 -0
- package/examples/icrc-ledger/src/canisters/icp_ledger.envelope.json +690 -0
- package/examples/icrc-ledger/src/canisters/icp_ledger.ts +223 -0
- package/examples/icrc-ledger/src/canisters/icrc1.envelope.json +840 -0
- package/examples/icrc-ledger/src/canisters/icrc1.ts +78 -0
- package/examples/icrc-ledger/src/format.test.ts +41 -0
- package/examples/icrc-ledger/src/format.ts +68 -0
- package/examples/icrc-ledger/src/ledger.ts +15 -0
- package/examples/icrc-ledger/src/main.tsx +10 -0
- package/examples/icrc-ledger/src/minter.ts +16 -0
- package/examples/icrc-ledger/src/sandbox.test.ts +464 -0
- package/examples/icrc-ledger/src/sandbox.ts +281 -0
- package/examples/icrc-ledger/src/styles.css +391 -0
- package/examples/icrc-ledger/src/transfer-form.test.ts +62 -0
- package/examples/icrc-ledger/src/transfer-form.ts +97 -0
- package/examples/icrc-ledger/tsconfig.json +19 -0
- package/examples/icrc-ledger/vite.config.ts +44 -0
- package/examples/icrc-ledger/vitest.config.ts +12 -0
- package/examples/next-ssr/.stackblitzrc +4 -0
- package/examples/next-ssr/README.md +112 -0
- package/examples/next-ssr/icrc1.did +88 -0
- package/examples/next-ssr/next.config.ts +10 -0
- package/examples/next-ssr/package.json +41 -0
- package/examples/next-ssr/scripts/smoke.ts +169 -0
- package/examples/next-ssr/src/app/account/CertifiedBalances.tsx +58 -0
- package/examples/next-ssr/src/app/account/account.test.tsx +165 -0
- package/examples/next-ssr/src/app/account/page.tsx +82 -0
- package/examples/next-ssr/src/app/api/balance/[ledger]/[principal]/route.ts +10 -0
- package/examples/next-ssr/src/app/globals.css +278 -0
- package/examples/next-ssr/src/app/layout.tsx +34 -0
- package/examples/next-ssr/src/app/page.tsx +46 -0
- package/examples/next-ssr/src/app/providers.browser.test.tsx +127 -0
- package/examples/next-ssr/src/app/providers.test.tsx +73 -0
- package/examples/next-ssr/src/app/providers.tsx +60 -0
- package/examples/next-ssr/src/canisters/icrc1.envelope.json +840 -0
- package/examples/next-ssr/src/canisters/icrc1.ts +78 -0
- package/examples/next-ssr/src/components/BalanceTable.tsx +43 -0
- package/examples/next-ssr/src/components/LedgerCard.test.tsx +96 -0
- package/examples/next-ssr/src/components/LedgerCard.tsx +230 -0
- package/examples/next-ssr/src/components/LedgerError.tsx +32 -0
- package/examples/next-ssr/src/components/LedgerSections.tsx +22 -0
- package/examples/next-ssr/src/components/MyBalances.test.tsx +253 -0
- package/examples/next-ssr/src/components/MyBalances.tsx +129 -0
- package/examples/next-ssr/src/components/SessionBadge.tsx +18 -0
- package/examples/next-ssr/src/components/hydration.test.tsx +259 -0
- package/examples/next-ssr/src/components/server-data.ts +10 -0
- package/examples/next-ssr/src/format.ts +46 -0
- package/examples/next-ssr/src/ledgers.ts +38 -0
- package/examples/next-ssr/src/server/balance-route.test.ts +151 -0
- package/examples/next-ssr/src/server/balance-route.ts +157 -0
- package/examples/next-ssr/src/server/error-summary.test.ts +34 -0
- package/examples/next-ssr/src/server/error-summary.ts +37 -0
- package/examples/next-ssr/src/server/parse-owner.test.ts +39 -0
- package/examples/next-ssr/src/server/parse-owner.ts +34 -0
- package/examples/next-ssr/src/server/prefetch-ledgers.test.ts +137 -0
- package/examples/next-ssr/src/server/prefetch-ledgers.ts +70 -0
- package/examples/next-ssr/src/server/read-balances.test.ts +103 -0
- package/examples/next-ssr/src/server/read-balances.ts +78 -0
- package/examples/next-ssr/src/server/request-client.rsc.test.tsx +110 -0
- package/examples/next-ssr/src/server/request-client.ts +24 -0
- package/examples/next-ssr/src/testing/mock-ledgers.ts +115 -0
- package/examples/next-ssr/src/testing/react-server-dom-turbopack.d.ts +15 -0
- package/examples/next-ssr/src/testing/setup.ts +10 -0
- package/examples/next-ssr/tsconfig.json +31 -0
- package/examples/next-ssr/vitest.config.ts +59 -0
- package/examples/node-agent-tool/.stackblitzrc +4 -0
- package/examples/node-agent-tool/README.md +165 -0
- package/examples/node-agent-tool/icp.yaml +11 -0
- package/examples/node-agent-tool/icrc1.did +88 -0
- package/examples/node-agent-tool/package.json +32 -0
- package/examples/node-agent-tool/src/canisters/icrc1.envelope.json +840 -0
- package/examples/node-agent-tool/src/canisters/icrc1.ts +78 -0
- package/examples/node-agent-tool/src/cli.test.ts +64 -0
- package/examples/node-agent-tool/src/cli.ts +23 -0
- package/examples/node-agent-tool/src/command-line.ts +159 -0
- package/examples/node-agent-tool/src/commands/balance.ts +73 -0
- package/examples/node-agent-tool/src/commands/demo.test.ts +51 -0
- package/examples/node-agent-tool/src/commands/demo.ts +359 -0
- package/examples/node-agent-tool/src/commands/info.ts +69 -0
- package/examples/node-agent-tool/src/commands/reads.test.ts +133 -0
- package/examples/node-agent-tool/src/commands/resend.test.ts +363 -0
- package/examples/node-agent-tool/src/commands/transfer.test.ts +313 -0
- package/examples/node-agent-tool/src/commands/transfer.ts +415 -0
- package/examples/node-agent-tool/src/commands/watch.test.ts +159 -0
- package/examples/node-agent-tool/src/commands/watch.ts +135 -0
- package/examples/node-agent-tool/src/commands/whoami.ts +36 -0
- package/examples/node-agent-tool/src/context.ts +20 -0
- package/examples/node-agent-tool/src/dispatch.ts +70 -0
- package/examples/node-agent-tool/src/failure.test.ts +59 -0
- package/examples/node-agent-tool/src/failure.ts +176 -0
- package/examples/node-agent-tool/src/identity.test.ts +242 -0
- package/examples/node-agent-tool/src/identity.ts +228 -0
- package/examples/node-agent-tool/src/input.ts +110 -0
- package/examples/node-agent-tool/src/ledgers.test.ts +74 -0
- package/examples/node-agent-tool/src/ledgers.ts +62 -0
- package/examples/node-agent-tool/src/mock-ledger.ts +211 -0
- package/examples/node-agent-tool/src/network.test.ts +128 -0
- package/examples/node-agent-tool/src/network.ts +101 -0
- package/examples/node-agent-tool/src/output.test.ts +208 -0
- package/examples/node-agent-tool/src/output.ts +163 -0
- package/examples/node-agent-tool/src/run.ts +73 -0
- package/examples/node-agent-tool/src/test-kit.ts +167 -0
- package/examples/node-agent-tool/tsconfig.json +18 -0
- package/examples/node-agent-tool/vitest.config.ts +8 -0
- package/examples/vite-wallet/.stackblitzrc +4 -0
- package/examples/vite-wallet/Cargo.lock +646 -0
- package/examples/vite-wallet/Cargo.toml +5 -0
- package/examples/vite-wallet/README.md +107 -0
- package/examples/vite-wallet/backend/Cargo.toml +12 -0
- package/examples/vite-wallet/backend/backend.did +37 -0
- package/examples/vite-wallet/backend/src/lib.rs +133 -0
- package/examples/vite-wallet/icp.yaml +44 -0
- package/examples/vite-wallet/index.html +13 -0
- package/examples/vite-wallet/ledger.did +54 -0
- package/examples/vite-wallet/package.json +46 -0
- package/examples/vite-wallet/public/.ic-assets.json5 +26 -0
- package/examples/vite-wallet/scripts/faucet-args.test.ts +30 -0
- package/examples/vite-wallet/scripts/faucet-args.ts +41 -0
- package/examples/vite-wallet/scripts/faucet.ts +41 -0
- package/examples/vite-wallet/src/AddressBook.test.tsx +191 -0
- package/examples/vite-wallet/src/AddressBook.tsx +147 -0
- package/examples/vite-wallet/src/App.tsx +53 -0
- package/examples/vite-wallet/src/Balance.test.tsx +57 -0
- package/examples/vite-wallet/src/Balance.tsx +58 -0
- package/examples/vite-wallet/src/EnvironmentPanel.test.tsx +83 -0
- package/examples/vite-wallet/src/EnvironmentPanel.tsx +103 -0
- package/examples/vite-wallet/src/ErrorNote.tsx +70 -0
- package/examples/vite-wallet/src/Profile.test.tsx +145 -0
- package/examples/vite-wallet/src/Profile.tsx +92 -0
- package/examples/vite-wallet/src/SendIcp.test.tsx +231 -0
- package/examples/vite-wallet/src/SendIcp.tsx +244 -0
- package/examples/vite-wallet/src/SendToContact.test.tsx +113 -0
- package/examples/vite-wallet/src/SendToContact.tsx +102 -0
- package/examples/vite-wallet/src/SignIn.test.tsx +106 -0
- package/examples/vite-wallet/src/SignIn.tsx +120 -0
- package/examples/vite-wallet/src/Wallet.test.tsx +89 -0
- package/examples/vite-wallet/src/account-switch.test.tsx +108 -0
- package/examples/vite-wallet/src/auth/dev-accounts.test.ts +113 -0
- package/examples/vite-wallet/src/auth/dev-accounts.ts +216 -0
- package/examples/vite-wallet/src/auth/local-page.test.ts +28 -0
- package/examples/vite-wallet/src/auth/local-page.ts +20 -0
- package/examples/vite-wallet/src/auth/wallet-auth.test.ts +159 -0
- package/examples/vite-wallet/src/auth/wallet-auth.ts +131 -0
- package/examples/vite-wallet/src/canisters/backend.envelope.json +275 -0
- package/examples/vite-wallet/src/canisters/backend.ts +58 -0
- package/examples/vite-wallet/src/canisters/ledger.envelope.json +451 -0
- package/examples/vite-wallet/src/canisters/ledger.ts +56 -0
- package/examples/vite-wallet/src/client.ts +45 -0
- package/examples/vite-wallet/src/main.tsx +18 -0
- package/examples/vite-wallet/src/optimistic-contacts.ts +113 -0
- package/examples/vite-wallet/src/refusal.ts +17 -0
- package/examples/vite-wallet/src/styles.css +282 -0
- package/examples/vite-wallet/src/test/test-wallet.tsx +322 -0
- package/examples/vite-wallet/src/token.ts +50 -0
- package/examples/vite-wallet/src/transfer.test.ts +178 -0
- package/examples/vite-wallet/src/transfer.ts +110 -0
- package/examples/vite-wallet/src/use-canisters.ts +49 -0
- package/examples/vite-wallet/tsconfig.json +19 -0
- package/examples/vite-wallet/vite.config.ts +47 -0
- package/examples/vite-wallet/vitest.config.ts +17 -0
- package/package.json +9 -4
- package/packages/core/.size-limit.js +47 -0
- package/packages/core/LICENSE +21 -0
- package/packages/core/README.md +84 -0
- package/packages/core/llms.txt +610 -0
- package/packages/core/package.json +89 -0
- package/packages/core/src/call.ts +961 -0
- package/packages/core/src/canister.ts +259 -0
- package/packages/core/src/client.ts +2061 -0
- package/packages/core/src/errors.ts +1079 -0
- package/packages/core/src/hydration.ts +239 -0
- package/packages/core/src/index.ts +23 -0
- package/packages/core/src/keys.ts +108 -0
- package/packages/core/src/management.ts +138 -0
- package/packages/core/src/network.ts +1020 -0
- package/packages/core/src/options.ts +1000 -0
- package/packages/core/src/runtime.ts +50 -0
- package/packages/core/src/testing/fake-replica.ts +1166 -0
- package/packages/core/src/testing/index.ts +16 -0
- package/packages/core/src/testing/test-auth.ts +340 -0
- package/packages/core/src/testing/test-client.ts +559 -0
- package/packages/core/src/types.ts +541 -0
- package/packages/core/src/units.ts +299 -0
- package/packages/core/src/version.ts +4 -0
- package/packages/core/tests/canister-helpers.ts +187 -0
- package/packages/core/tests/canister-id.test.ts +158 -0
- package/packages/core/tests/canister-unwrap.test.ts +94 -0
- package/packages/core/tests/canister.test-d.ts +406 -0
- package/packages/core/tests/canister.test.ts +1921 -0
- package/packages/core/tests/client-account-change.test.ts +131 -0
- package/packages/core/tests/client-agents.test.ts +402 -0
- package/packages/core/tests/client-auth-network.test.ts +719 -0
- package/packages/core/tests/client-disposed.test.ts +403 -0
- package/packages/core/tests/client-helpers.ts +136 -0
- package/packages/core/tests/client-hydration.test.ts +166 -0
- package/packages/core/tests/client-pinned.test.ts +626 -0
- package/packages/core/tests/client-session.test.ts +348 -0
- package/packages/core/tests/client.test-d.ts +181 -0
- package/packages/core/tests/client.test.ts +1162 -0
- package/packages/core/tests/encode.test.ts +171 -0
- package/packages/core/tests/env-canister-id.test.ts +666 -0
- package/packages/core/tests/env-cookie-copies.test.ts +182 -0
- package/packages/core/tests/error-guard.test.ts +222 -0
- package/packages/core/tests/errors.test-d.ts +280 -0
- package/packages/core/tests/errors.test.ts +1359 -0
- package/packages/core/tests/fixtures/README.md +28 -0
- package/packages/core/tests/fixtures/accounts.did +21 -0
- package/packages/core/tests/fixtures/accounts.envelope.json +367 -0
- package/packages/core/tests/fixtures/accounts.ts +26 -0
- package/packages/core/tests/fixtures/archive.did +31 -0
- package/packages/core/tests/fixtures/archive.envelope.json +259 -0
- package/packages/core/tests/fixtures/archive.ts +55 -0
- package/packages/core/tests/fixtures/icrc1.did +88 -0
- package/packages/core/tests/fixtures/icrc1.envelope.json +840 -0
- package/packages/core/tests/fixtures/icrc1.ts +78 -0
- package/packages/core/tests/fixtures/management.did +724 -0
- package/packages/core/tests/fixtures/management.envelope.json +4455 -0
- package/packages/core/tests/fixtures/management.ts +486 -0
- package/packages/core/tests/fixtures/shapes.did +33 -0
- package/packages/core/tests/fixtures/shapes.envelope.json +440 -0
- package/packages/core/tests/fixtures/shapes.ts +56 -0
- package/packages/core/tests/fixtures/skippable.did +15 -0
- package/packages/core/tests/fixtures/skippable.envelope.json +182 -0
- package/packages/core/tests/fixtures/skippable.ts +33 -0
- package/packages/core/tests/func.test.ts +154 -0
- package/packages/core/tests/identity-attributes.test.ts +214 -0
- package/packages/core/tests/keys.test.ts +289 -0
- package/packages/core/tests/management.test.ts +206 -0
- package/packages/core/tests/network-helpers.ts +121 -0
- package/packages/core/tests/network.test-d.ts +78 -0
- package/packages/core/tests/network.test.ts +1051 -0
- package/packages/core/tests/options.test.ts +1594 -0
- package/packages/core/tests/redirect-sign-in.test.ts +393 -0
- package/packages/core/tests/resend.test-d.ts +81 -0
- package/packages/core/tests/resend.test.ts +723 -0
- package/packages/core/tests/skip.test-d.ts +125 -0
- package/packages/core/tests/testing-entry.test.ts +73 -0
- package/packages/core/tests/testing-fake-replica.test.ts +1242 -0
- package/packages/core/tests/testing-test-auth.test-d.ts +84 -0
- package/packages/core/tests/testing-test-auth.test.ts +483 -0
- package/packages/core/tests/testing-test-client.test-d.ts +184 -0
- package/packages/core/tests/testing-test-client.test.ts +1272 -0
- package/packages/core/tests/time-realm.test.ts +63 -0
- package/packages/core/tests/time.test.ts +238 -0
- package/packages/core/tests/traps/README.md +39 -0
- package/packages/core/tests/traps/anonymous-write.test.ts +145 -0
- package/packages/core/tests/traps/classification-table.test.ts +256 -0
- package/packages/core/tests/traps/err-arm.test.ts +144 -0
- package/packages/core/tests/traps/fetch-spy.ts +39 -0
- package/packages/core/tests/traps/invalidation-after-write.test.ts +178 -0
- package/packages/core/tests/traps/ledger.ts +248 -0
- package/packages/core/tests/traps/root-key-off-local.test.ts +130 -0
- package/packages/core/tests/traps/stale-data-across-users.test.ts +173 -0
- package/packages/core/tests/traps/unresolved-name.test.ts +155 -0
- package/packages/core/tests/traps/update-as-query.test.ts +244 -0
- package/packages/core/tests/traps/update-not-resent.test.ts +126 -0
- package/packages/core/tests/traps/update-resent-when-never-delivered.test.ts +159 -0
- package/packages/core/tests/traps.test-d.ts +476 -0
- package/packages/core/tests/units.test-d.ts +118 -0
- package/packages/core/tests/units.test.ts +634 -0
- package/packages/core/tsconfig.json +19 -0
- package/packages/core/tsconfig.typecheck.json +16 -0
- package/packages/core/vitest.config.ts +13 -0
- package/packages/mcp/LICENSE +21 -0
- package/packages/mcp/README.md +189 -0
- package/packages/mcp/package.json +62 -0
- package/packages/mcp/src/candid/candid-text.test.ts +455 -0
- package/packages/mcp/src/candid/candid-text.ts +501 -0
- package/packages/mcp/src/candid/contract.test.ts +398 -0
- package/packages/mcp/src/candid/formats.ts +155 -0
- package/packages/mcp/src/candid/hex.ts +29 -0
- package/packages/mcp/src/candid/json-schema.test.ts +758 -0
- package/packages/mcp/src/candid/json-schema.ts +454 -0
- package/packages/mcp/src/candid/json.test.ts +1207 -0
- package/packages/mcp/src/candid/json.ts +1053 -0
- package/packages/mcp/src/candid/schema-walk.ts +189 -0
- package/packages/mcp/src/candid/type-text.test.ts +179 -0
- package/packages/mcp/src/candid/type-text.ts +238 -0
- package/packages/mcp/src/cli.ts +134 -0
- package/packages/mcp/src/identity.ts +188 -0
- package/packages/mcp/src/interface/cache.test.ts +48 -0
- package/packages/mcp/src/interface/cache.ts +68 -0
- package/packages/mcp/src/interface/cli-cache.test.ts +150 -0
- package/packages/mcp/src/interface/cli-cache.ts +122 -0
- package/packages/mcp/src/interface/compile-worker.test.ts +246 -0
- package/packages/mcp/src/interface/compile-worker.ts +290 -0
- package/packages/mcp/src/interface/compile.test.ts +155 -0
- package/packages/mcp/src/interface/compile.ts +281 -0
- package/packages/mcp/src/interface/docs.test.ts +105 -0
- package/packages/mcp/src/interface/docs.ts +86 -0
- package/packages/mcp/src/interface/fixtures/icrc1.did +88 -0
- package/packages/mcp/src/interface/hex.ts +8 -0
- package/packages/mcp/src/interface/ids.test.ts +71 -0
- package/packages/mcp/src/interface/ids.ts +87 -0
- package/packages/mcp/src/interface/loader.test.ts +804 -0
- package/packages/mcp/src/interface/loader.ts +519 -0
- package/packages/mcp/src/interface/read-candid.test.ts +300 -0
- package/packages/mcp/src/interface/read-candid.ts +259 -0
- package/packages/mcp/src/interface/types.ts +113 -0
- package/packages/mcp/src/network.ts +122 -0
- package/packages/mcp/src/options.ts +149 -0
- package/packages/mcp/src/server/approval.ts +159 -0
- package/packages/mcp/src/server/naming.ts +43 -0
- package/packages/mcp/src/server/results.ts +102 -0
- package/packages/mcp/src/server/server.ts +797 -0
- package/packages/mcp/src/server/tools.ts +116 -0
- package/packages/mcp/src/usage.ts +40 -0
- package/packages/mcp/tests/approval.test.ts +60 -0
- package/packages/mcp/tests/identity.test.ts +234 -0
- package/packages/mcp/tests/naming.test.ts +62 -0
- package/packages/mcp/tests/options.test.ts +140 -0
- package/packages/mcp/tests/server.test.ts +997 -0
- package/packages/mcp/tsconfig.json +19 -0
- package/packages/mcp/tsconfig.typecheck.json +10 -0
- package/packages/mcp/vitest.config.ts +10 -0
- package/packages/react/LICENSE +21 -0
- package/packages/react/README.md +328 -0
- package/packages/react/llms.txt +95 -0
- package/packages/react/package.json +95 -0
- package/packages/react/src/index.tsx +678 -0
- package/packages/react/tests/abandoned-render.test.tsx +816 -0
- package/packages/react/tests/auth.test.tsx +315 -0
- package/packages/react/tests/caller-keys.test.tsx +2334 -0
- package/packages/react/tests/client-time.test.ts +40 -0
- package/packages/react/tests/entry.test.ts +52 -0
- package/packages/react/tests/helpers.tsx +291 -0
- package/packages/react/tests/hydration.test.tsx +262 -0
- package/packages/react/tests/ownership.test.tsx +260 -0
- package/packages/react/tests/provider.server.test.tsx +169 -0
- package/packages/react/tests/query-client.test.tsx +107 -0
- package/packages/react/tests/strict-mode.test.tsx +288 -0
- package/packages/react/tests/suspense.test-d.tsx +357 -0
- package/packages/react/tests/two-query-cores.test.tsx +135 -0
- package/packages/react/tests/types.test-d.tsx +63 -0
- package/packages/react/tsconfig.json +23 -0
- package/packages/react/tsconfig.test.json +8 -0
- package/packages/react/tsconfig.typecheck.json +16 -0
- package/packages/react/vitest.config.ts +39 -0
- package/packages/vite-plugin/LICENSE +21 -0
- package/packages/vite-plugin/README.md +532 -0
- package/packages/vite-plugin/package.json +76 -0
- package/packages/vite-plugin/src/describe-value.test.ts +43 -0
- package/packages/vite-plugin/src/describe-value.ts +167 -0
- package/packages/vite-plugin/src/dev-environment.test.ts +747 -0
- package/packages/vite-plugin/src/dev-environment.ts +510 -0
- package/packages/vite-plugin/src/env.ts +431 -0
- package/packages/vite-plugin/src/fetch.ts +348 -0
- package/packages/vite-plugin/src/file-identity.ts +106 -0
- package/packages/vite-plugin/src/generate.ts +691 -0
- package/packages/vite-plugin/src/index.test.ts +1243 -0
- package/packages/vite-plugin/src/index.ts +2565 -0
- package/packages/vite-plugin/src/interface.ts +339 -0
- package/packages/vite-plugin/src/local-host.ts +34 -0
- package/packages/vite-plugin/src/options.ts +641 -0
- package/packages/vite-plugin/src/principal-text.test.ts +87 -0
- package/packages/vite-plugin/src/principal-text.ts +98 -0
- package/packages/vite-plugin/tests/close.test.ts +141 -0
- package/packages/vite-plugin/tests/environment-mode.test.ts +136 -0
- package/packages/vite-plugin/tests/fake-ic.ts +214 -0
- package/packages/vite-plugin/tests/fetch-sdk.test.ts +27 -0
- package/packages/vite-plugin/tests/fetch.test.ts +1196 -0
- package/packages/vite-plugin/tests/fixtures/fake-cli/bin.js +90 -0
- package/packages/vite-plugin/tests/fixtures/fake-cli/package.json +8 -0
- package/packages/vite-plugin/tests/fixtures/icrc1.did +88 -0
- package/packages/vite-plugin/tests/generation.test.ts +1036 -0
- package/packages/vite-plugin/tests/icp-command.test.ts +194 -0
- package/packages/vite-plugin/tests/icp-timeout.test.ts +83 -0
- package/packages/vite-plugin/tests/no-process-exit.test.ts +83 -0
- package/packages/vite-plugin/tests/on-generated.test.ts +323 -0
- package/packages/vite-plugin/tests/output-collision.test.ts +329 -0
- package/packages/vite-plugin/tests/package.test.ts +62 -0
- package/packages/vite-plugin/tests/project.test.ts +2993 -0
- package/packages/vite-plugin/tests/support.ts +339 -0
- package/packages/vite-plugin/tests/timeout.test.ts +107 -0
- package/packages/vite-plugin/tsconfig.json +23 -0
- package/packages/vite-plugin/tsconfig.typecheck.json +16 -0
- package/packages/vite-plugin/vitest.config.ts +9 -0
- package/pnpm-workspace.yaml +23 -0
- package/scripts/ai-context-files.js +72 -0
- package/scripts/assemble-docs-site.mjs +485 -0
- package/scripts/assemble-docs-site.test.mjs +568 -0
- package/scripts/build-examples.js +58 -0
- package/scripts/check-ai-context.js +963 -0
- package/scripts/check-ai-context.test.mjs +811 -0
- package/scripts/check-exports.mjs +767 -0
- package/scripts/check-exports.test.mjs +1136 -0
- package/scripts/check-snippets/ambient.d.ts +428 -0
- package/scripts/check-snippets/app/generated/backend.did +7 -0
- package/scripts/check-snippets/app/generated/backend.ts +15 -0
- package/scripts/check-snippets/app/generated/icrc1.did +88 -0
- package/scripts/check-snippets/app/generated/icrc1.ts +78 -0
- package/scripts/check-snippets/app/globals.ts +26 -0
- package/scripts/check-snippets/docs/guides/App.tsx +21 -0
- package/scripts/check-snippets/docs/guides/app/providers.tsx +23 -0
- package/scripts/check-snippets/docs/guides/auth-client.ts +47 -0
- package/scripts/check-snippets/docs/guides/canisters/backend.ts +3 -0
- package/scripts/check-snippets/docs/guides/canisters/icrc1.ts +3 -0
- package/scripts/check-snippets/docs/guides/components/LedgerCard.tsx +26 -0
- package/scripts/check-snippets/docs/guides/components/shell.tsx +11 -0
- package/scripts/check-snippets/docs/guides/components/sign-in-page.tsx +29 -0
- package/scripts/check-snippets/docs/guides/components/slow-section.tsx +5 -0
- package/scripts/check-snippets/docs/guides/generated/accounts.ts +95 -0
- package/scripts/check-snippets/docs/guides/generated/ckbtc_minter.ts +40 -0
- package/scripts/check-snippets/docs/guides/generated/icp_ledger.ts +267 -0
- package/scripts/check-snippets/docs/guides/ledger.ts +10 -0
- package/scripts/check-snippets/docs/guides/server/request-client.ts +8 -0
- package/scripts/check-snippets.mjs +993 -0
- package/scripts/check-snippets.test.mjs +338 -0
- package/scripts/check-stack.mjs +501 -0
- package/scripts/check-stack.test.mjs +800 -0
- package/scripts/clean-node-modules.js +67 -0
- package/scripts/example-projects.js +58 -0
- package/scripts/export-budget.mjs +145 -0
- package/scripts/faults.json +6749 -0
- package/scripts/lib/fault-workspace.mjs +210 -0
- package/scripts/lib/io.mjs +29 -0
- package/scripts/lib/io.test.mjs +38 -0
- package/scripts/lib/markdown-fences.mjs +56 -0
- package/scripts/lib/markdown-fences.test.mjs +84 -0
- package/scripts/lib/package-exports.mjs +54 -0
- package/scripts/lib/package-exports.test.mjs +66 -0
- package/scripts/lib/release-pr-steps.mjs +47 -0
- package/scripts/release-tag.mjs +95 -0
- package/scripts/release-tag.test.mjs +278 -0
- package/scripts/release-workflow.test.mjs +98 -0
- package/scripts/release.js +167 -0
- package/scripts/release.test.mjs +347 -0
- package/scripts/removed-v3-names.js +125 -0
- package/scripts/size-app/.size-limit.js +90 -0
- package/scripts/size-app/core.js +3 -0
- package/scripts/size-app/package.json +24 -0
- package/scripts/size-app/react.js +4 -0
- package/scripts/size-app.test.mjs +73 -0
- package/scripts/sync-ai-context-versions.js +102 -0
- package/scripts/sync-example-versions.js +151 -0
- package/scripts/test-examples-published.mjs +1585 -0
- package/scripts/test-examples-published.test.mjs +1586 -0
- package/scripts/traps/any-method-vars-not-a-list.json +5 -0
- package/scripts/traps/canister-method-unknown.json +5 -0
- package/scripts/traps/client-says-who-calls-once.json +5 -0
- package/scripts/traps/client-says-who-calls.json +5 -0
- package/scripts/traps/err-only-on-canister-err.json +14 -0
- package/scripts/traps/format-units-refuses-missing-value.json +5 -0
- package/scripts/traps/format-units-refuses-number.json +5 -0
- package/scripts/traps/format-units-unknown-option.json +5 -0
- package/scripts/traps/guard-err-is-the-methods.json +5 -0
- package/scripts/traps/guard-false-keeps-every-kind.json +5 -0
- package/scripts/traps/internals-not-exported.json +9 -0
- package/scripts/traps/network-refuses-unknown-name.json +5 -0
- package/scripts/traps/never-err-has-no-type.json +5 -0
- package/scripts/traps/options-method-unknown.json +5 -0
- package/scripts/traps/options-vars-wrong-type.json +5 -0
- package/scripts/traps/parse-units-refuses-number.json +5 -0
- package/scripts/traps/parse-units-unknown-option.json +5 -0
- package/scripts/traps/principal-is-not-plain-text.json +14 -0
- package/scripts/traps/query-options-refuses-oneway.json +5 -0
- package/scripts/traps/query-options-refuses-update.json +5 -0
- package/scripts/traps/refuse-next-unknown-option.json +5 -0
- package/scripts/traps/resend-key-reads-the-methods-arg.json +5 -0
- package/scripts/traps/resend-states-its-dedupe-key.json +5 -0
- package/scripts/traps/suspense-refuses-argument-skip.json +5 -0
- package/scripts/traps/suspense-refuses-skippable-read.json +5 -0
- package/scripts/traps/suspense-refuses-vars-skip-fits.json +5 -0
- package/scripts/traps/test-handler-method-unknown.json +5 -0
- package/scripts/traps/test-handler-nat-is-bigint.json +5 -0
- package/scripts/traps/test-handler-takes-the-domain-value.json +5 -0
- package/scripts/traps/vars-of-two-arguments-are-the-tuple.json +5 -0
- package/scripts/typecheck-examples.js +122 -0
- package/scripts/verify-faults.mjs +375 -0
- package/scripts/verify-faults.test.mjs +402 -0
- package/scripts/verify-package-artifacts.mjs +590 -0
- package/scripts/verify-peer-floors.js +399 -0
- package/scripts/verify-peer-floors.test.mjs +422 -0
- package/scripts/verify-test-fails-rules.mjs +104 -0
- package/scripts/verify-test-fails-rules.test.mjs +215 -0
- package/scripts/verify-test-fails.js +371 -0
- package/scripts/verify-traps.mjs +505 -0
- package/scripts/verify-traps.test.mjs +521 -0
- package/scripts/verify-typescript-floor.js +239 -0
- package/skill-packages/ic-reactor/SKILL.md +103 -0
- package/stack.json +8 -0
- package/tsconfig.base.json +23 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,1373 @@
|
|
|
1
|
+
// Leak audit for one agent run, the second line of defence behind the OS
|
|
2
|
+
// sandbox (harness/sandbox.mjs): finds every tool call in the agent's
|
|
3
|
+
// transcript whose path or command reaches outside the run directory, and
|
|
4
|
+
// whether it succeeded.
|
|
5
|
+
//
|
|
6
|
+
// The transcript is what Claude Code prints with `--output-format stream-json
|
|
7
|
+
// --verbose`: one JSON message per line, tool calls as content blocks
|
|
8
|
+
// `{ type: "tool_use", id, name, input }` and their outcomes as
|
|
9
|
+
// `{ type: "tool_result", tool_use_id, is_error, content }`. The scan walks
|
|
10
|
+
// every JSON value for such blocks rather than trusting one exact envelope, so
|
|
11
|
+
// a single JSON document or an array of messages works too.
|
|
12
|
+
//
|
|
13
|
+
// A tool call is a violation when it
|
|
14
|
+
// - names a file or directory (`file_path`, `path`, `notebook_path`, a Glob
|
|
15
|
+
// `pattern`, a Grep `glob`) that resolves outside the run directory;
|
|
16
|
+
// - runs a shell command that reaches outside. Commands are lexed as a shell
|
|
17
|
+
// would (quotes, escapes, operators, redirections, heredocs, `$(…)` and
|
|
18
|
+
// backticks), and each word is judged by its position:
|
|
19
|
+
// - arguments and redirection targets of ordinary commands are paths:
|
|
20
|
+
// normalised (`./../x`, `a/../..`, `$PWD/..`, `${PWD}/..`, `~`, `~user`,
|
|
21
|
+
// `$HOME`) and resolved from the shell's current directory, which `cd`
|
|
22
|
+
// moves; `$TMPDIR`, `$TMP`, `$TEMP`, `$OLDPWD`, any other variable
|
|
23
|
+
// followed by `/`, and a command substitution that builds a path
|
|
24
|
+
// (`$(pwd)/..`, `$(dirname $PWD)`, `$(printf …)`) cannot be resolved and
|
|
25
|
+
// are flagged;
|
|
26
|
+
// - the pattern/program argument of grep, egrep, fgrep, rg, ag, sed, awk,
|
|
27
|
+
// gawk and jq is not a path (`grep "/call" file` reads `file`);
|
|
28
|
+
// - code given to an interpreter (`node -e '…'`, `python -c '…'`, a heredoc
|
|
29
|
+
// fed to one) is not shell: only its string literals are checked — ones
|
|
30
|
+
// under a user-data root (`/Users`, `/home`, `/private`, `/var`, `/tmp`,
|
|
31
|
+
// `/etc`, `/Volumes`, `/opt`, `/root`), `~`, or climbing with `..`;
|
|
32
|
+
// - the text of a heredoc written to a file is data, checked only for
|
|
33
|
+
// string literals under a user-data root (a script written now and run
|
|
34
|
+
// later);
|
|
35
|
+
// - `sh -c` / `bash -c` bodies, `xargs` commands and command substitutions
|
|
36
|
+
// run, so they are scanned as shell in turn;
|
|
37
|
+
// - uses a network tool (WebFetch, WebSearch);
|
|
38
|
+
// - uses the network from the shell. The OS sandbox leaves the network open
|
|
39
|
+
// (the agent CLI needs its API), and `node` runs in it, so this is the only
|
|
40
|
+
// line there. Flagged: a network command (curl, wget, nc, ssh, …); a
|
|
41
|
+
// package manager or git command that reaches a registry or remote (`npm
|
|
42
|
+
// view`, `pnpm add`, `npx <a package the run does not have>`, `pip
|
|
43
|
+
// install`, `git clone`, …); a URL of a non-local host given to an
|
|
44
|
+
// interpreter; and interpreter code (`node -e`, `python -c`, a heredoc or
|
|
45
|
+
// pipe fed to one, or a script written in the run by Write, Edit or a
|
|
46
|
+
// heredoc and then run with an interpreter) that calls the network
|
|
47
|
+
// (`fetch(`, `http(s).get/request`, `net`/`tls`, WebSocket, `urllib`,
|
|
48
|
+
// `requests`, …) towards a non-local host or a host it does not name, or
|
|
49
|
+
// that runs a network command through `child_process` / `subprocess`.
|
|
50
|
+
// String literals are data, not calls: an edit script whose text holds
|
|
51
|
+
// `fetch(` or a URL is not network use, and neither is code that only
|
|
52
|
+
// names local hosts (`localhost`, `127.*`, `[::1]`). Source written to a
|
|
53
|
+
// file is not flagged until it is run. Not followed: tests run by vitest
|
|
54
|
+
// (the agent's own tests name mainnet hosts, which the public scaffold's
|
|
55
|
+
// fake replica answers; a test fetching another URL is not seen) and a
|
|
56
|
+
// host reached through a library such as an `HttpAgent`.
|
|
57
|
+
// A violation is `blocked` when the call failed (`is_error`) or its output
|
|
58
|
+
// reports a permission refusal (`Operation not permitted`, `EPERM`, `EACCES`,
|
|
59
|
+
// `Permission denied`) naming that path — or, when it is the call's only
|
|
60
|
+
// violation, reports one at all. The run is contaminated when any violation
|
|
61
|
+
// was NOT blocked — a successful outside read — or when the transcript holds
|
|
62
|
+
// no parseable JSON (it cannot be audited). The run's own node_modules counts
|
|
63
|
+
// as inside, including the target of a node_modules symlink.
|
|
64
|
+
//
|
|
65
|
+
// The CLI's persisted tool output (PREREGISTRATION.md, Addendum 5). When a
|
|
66
|
+
// tool's output is too large, the CLI saves it under the run's own home and
|
|
67
|
+
// answers the call with a notice instead. A Read (`file_path`) or Grep
|
|
68
|
+
// (`path`) of such a file is not a violation when all of these hold, and is
|
|
69
|
+
// judged as any other path otherwise:
|
|
70
|
+
// - the run directory is `<run>/work`, and the path, resolved as every path
|
|
71
|
+
// here is (lexically: `.`, `..`, `//`, `~`, `$HOME`; no symlink is
|
|
72
|
+
// followed), is exactly
|
|
73
|
+
// `<run>/home/.claude/projects/<one segment>/<one segment>/tool-results/<one file name>`;
|
|
74
|
+
// - the path as written has no `..` segment;
|
|
75
|
+
// - an earlier top-level user message of the transcript is the CLI's answer
|
|
76
|
+
// to a call whose output it saved: its content is one tool_result block,
|
|
77
|
+
// whose whole content is the notice — `<persisted-output>`, then "Output
|
|
78
|
+
// too large (<size>). Full output saved to: <path>", …,
|
|
79
|
+
// `</persisted-output>` — and the message's `tool_use_result`, the CLI's
|
|
80
|
+
// own record of the call, has `persistedOutputPath` equal to that <path>,
|
|
81
|
+
// and `session_id` equal to its session segment. A tool's output is text
|
|
82
|
+
// inside `content` or inside a field of `tool_use_result`; it cannot add
|
|
83
|
+
// that field, so notice-shaped output of a Bash `printf`, a `cat` or a
|
|
84
|
+
// subagent is not a notice;
|
|
85
|
+
// - the notice's <path> is the read's path, byte for byte;
|
|
86
|
+
// - the notice answers an earlier tool call that is not a Read and in which
|
|
87
|
+
// the scan found no violation, blocked or not;
|
|
88
|
+
// - no component of the path below `<run>` is a symlink, as far as the path
|
|
89
|
+
// exists when the scan runs (the driver scans before it removes the run).
|
|
90
|
+
// A Bash command that names the path (`cat <path>`) is still a violation, and
|
|
91
|
+
// so is any other tool or field naming it (Write, Edit, Glob, a Read `path`).
|
|
92
|
+
import { existsSync, lstatSync, realpathSync } from "node:fs"
|
|
93
|
+
import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path"
|
|
94
|
+
import { homedir } from "node:os"
|
|
95
|
+
|
|
96
|
+
const SAFE = new Set([
|
|
97
|
+
"/dev/null",
|
|
98
|
+
"/dev/stdout",
|
|
99
|
+
"/dev/stderr",
|
|
100
|
+
"/dev/stdin",
|
|
101
|
+
"/dev/tty",
|
|
102
|
+
])
|
|
103
|
+
const NETWORK_TOOLS = new Set(["WebFetch", "WebSearch"])
|
|
104
|
+
const PATH_FIELDS = ["file_path", "path", "notebook_path"]
|
|
105
|
+
const OUTSIDE_VARS = new Set(["TMPDIR", "TMP", "TEMP", "OLDPWD"])
|
|
106
|
+
const PERMISSION = /Operation not permitted|EPERM|EACCES|Permission denied/i
|
|
107
|
+
const PATHY_SUBST = /pwd|dirname|realpath|readlink|printf|echo|cd\b|\.\.|\/|\$/
|
|
108
|
+
// Where user data lives: an absolute string literal in code is only suspect
|
|
109
|
+
// under one of these (not `/call`, `/**`, `/api/v2/...`).
|
|
110
|
+
const USER_DATA_ROOT =
|
|
111
|
+
/^\/(Users|home|private|var|tmp|etc|Volumes|opt|root)(\/|$)/
|
|
112
|
+
const PATTERN_COMMANDS = new Set([
|
|
113
|
+
"grep",
|
|
114
|
+
"egrep",
|
|
115
|
+
"fgrep",
|
|
116
|
+
"rg",
|
|
117
|
+
"ag",
|
|
118
|
+
"sed",
|
|
119
|
+
"awk",
|
|
120
|
+
"gawk",
|
|
121
|
+
"jq",
|
|
122
|
+
])
|
|
123
|
+
const INTERPRETERS = new Set([
|
|
124
|
+
"node",
|
|
125
|
+
"nodejs",
|
|
126
|
+
"bun",
|
|
127
|
+
"deno",
|
|
128
|
+
"tsx",
|
|
129
|
+
"ts-node",
|
|
130
|
+
"python",
|
|
131
|
+
"python3",
|
|
132
|
+
"ruby",
|
|
133
|
+
"perl",
|
|
134
|
+
"php",
|
|
135
|
+
])
|
|
136
|
+
const CODE_FLAGS = new Set([
|
|
137
|
+
"-e",
|
|
138
|
+
"--eval",
|
|
139
|
+
"-p",
|
|
140
|
+
"--print",
|
|
141
|
+
"-c",
|
|
142
|
+
"-r",
|
|
143
|
+
"--exec",
|
|
144
|
+
])
|
|
145
|
+
const SHELLS = new Set(["sh", "bash", "zsh", "dash"])
|
|
146
|
+
// The CLI's persisted-output notice, as the whole content of a tool_result.
|
|
147
|
+
const PERSISTED_NOTICE =
|
|
148
|
+
/^<persisted-output>\nOutput too large \([^()\n]+\)\. Full output saved to: ([^\n]+)\n[\s\S]*\n<\/persisted-output>$/
|
|
149
|
+
// Where the CLI persists it, relative to `<run>`: one project, one session.
|
|
150
|
+
const PERSISTED_PATH =
|
|
151
|
+
/^home\/\.claude\/projects\/[^/]+\/[^/]+\/tool-results\/[^/]+$/
|
|
152
|
+
// The tool and field that may read a persisted output back.
|
|
153
|
+
const PERSISTED_READERS = { Read: "file_path", Grep: "path" }
|
|
154
|
+
|
|
155
|
+
// ---------------------------------------------------------------- network
|
|
156
|
+
const words = (text) => new Set(text.trim().split(/\s+/))
|
|
157
|
+
// Commands that reach the network whatever their arguments.
|
|
158
|
+
const NETWORK_COMMANDS = words(`
|
|
159
|
+
curl wget nc ncat netcat telnet socat ssh scp sftp ftp http https xh aria2c
|
|
160
|
+
lynx w3m links elinks dig nslookup host whois
|
|
161
|
+
`)
|
|
162
|
+
// Package-manager subcommands that reach the registry (npm's aliases too).
|
|
163
|
+
const REGISTRY_SUBCOMMANDS = words(`
|
|
164
|
+
install i in ins inst insta instal isnt isnta isntal isntall add a ci
|
|
165
|
+
clean-install ic install-clean install-test it install-ci-test cit update up
|
|
166
|
+
upgrade udpate view v info show search s se find outdated audit publish
|
|
167
|
+
unpublish deprecate dist-tag dist-tags docs home repo bugs issues create
|
|
168
|
+
login adduser logout whoami ping owner access team token star unstar stars
|
|
169
|
+
doctor fetch dedupe ddp
|
|
170
|
+
`)
|
|
171
|
+
const PACKAGE_MANAGERS = words("npm pnpm yarn bun")
|
|
172
|
+
// Subcommands that run a package, installing it first when the run lacks it.
|
|
173
|
+
const PACKAGE_RUN_SUBCOMMANDS = {
|
|
174
|
+
npm: words("exec x"),
|
|
175
|
+
pnpm: words("dlx"),
|
|
176
|
+
yarn: words("dlx"),
|
|
177
|
+
bun: words("x"),
|
|
178
|
+
}
|
|
179
|
+
const PACKAGE_RUNNERS = words("npx pnpx bunx")
|
|
180
|
+
// What every starter has installed: `npx tsc`, `npx vitest` stay local.
|
|
181
|
+
const LOCAL_PACKAGES = words("tsc vitest typescript")
|
|
182
|
+
const GIT_REMOTE = words("clone fetch pull push ls-remote submodule")
|
|
183
|
+
const PIP_REMOTE = words("install download index search wheel")
|
|
184
|
+
// Prefixes that run the command after them.
|
|
185
|
+
const WRAPPERS = words(
|
|
186
|
+
"env command exec nohup time timeout nice stdbuf sudo doas"
|
|
187
|
+
)
|
|
188
|
+
// In interpreted code with its string literals removed: calls that reach the
|
|
189
|
+
// network (JS, Python, Ruby, Perl, PHP). A member call `x.fetch(` is not a
|
|
190
|
+
// global fetch.
|
|
191
|
+
const NETWORK_CALLS = new RegExp(
|
|
192
|
+
[
|
|
193
|
+
String.raw`(?<![\w$.])fetch\s*\(`,
|
|
194
|
+
String.raw`\b(globalThis|window|self)\s*\.\s*fetch\s*\(`,
|
|
195
|
+
String.raw`\bnew\s+(WebSocket|EventSource|XMLHttpRequest)\b`,
|
|
196
|
+
String.raw`\b(https?|http2|net|tls|dgram|dns)\s*\.\s*(get|request|connect|createConnection|lookup|resolve\w*)\s*\(`,
|
|
197
|
+
String.raw`\burlopen\s*\(`,
|
|
198
|
+
String.raw`\brequests\s*\.\s*(get|post|put|patch|head|delete|request|Session)\b`,
|
|
199
|
+
String.raw`^[ \t]*import\s+(urllib|urllib3|http\.client|requests|httpx|aiohttp|socket|ftplib)\b`,
|
|
200
|
+
String.raw`^[ \t]*from\s+(urllib|urllib3|http|requests|httpx|aiohttp|socket|ftplib)(\.\w+)*\s+import\b`,
|
|
201
|
+
String.raw`Net::HTTP|URI\.open|LWP::|HTTP::Tiny|IO::Socket|\bcurl_exec\b|\bfsockopen\b|\bfile_get_contents\s*\(`,
|
|
202
|
+
].join("|"),
|
|
203
|
+
"m"
|
|
204
|
+
)
|
|
205
|
+
// Modules whose loading in JS code means network use, or running commands.
|
|
206
|
+
const NETWORK_MODULES =
|
|
207
|
+
/^(node:)?(https?|http2|net|tls|dgram|dns(\/promises)?|undici|axios|node-fetch|got|ky|ws|superagent|request|open-uri)$/
|
|
208
|
+
const SPAWN_MODULES = /^(node:)?child_process$/
|
|
209
|
+
const SPAWN_CALLS =
|
|
210
|
+
/^[ \t]*(import\s+subprocess\b|from\s+subprocess\s+import\b)|\bos\s*\.\s*(system|popen)\s*\(/m
|
|
211
|
+
// `require("x")`, `import("x")`, `from "x"`, `import "x"` over a skeleton in
|
|
212
|
+
// which every string literal is `"S<n>"`.
|
|
213
|
+
const MODULE_SPECIFIERS =
|
|
214
|
+
/\b(?:require|import)\s*\(\s*"S(\d+)"|\bfrom\s*"S(\d+)"|\bimport\s*"S(\d+)"/g
|
|
215
|
+
const URLS = /\b(?:https?|wss?|ftp):\/\/([^\s/?#'"`<>\\]*)/gi
|
|
216
|
+
|
|
217
|
+
function realpathOrSelf(p) {
|
|
218
|
+
try {
|
|
219
|
+
return realpathSync(p)
|
|
220
|
+
} catch {
|
|
221
|
+
return p
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function roots(runDir) {
|
|
226
|
+
const list = new Set([resolve(runDir), realpathOrSelf(resolve(runDir))])
|
|
227
|
+
const nm = join(runDir, "node_modules")
|
|
228
|
+
if (existsSync(nm)) list.add(realpathOrSelf(nm))
|
|
229
|
+
return [...list]
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const inside = (p, allowed) =>
|
|
233
|
+
allowed.some((root) => p === root || p.startsWith(root + sep))
|
|
234
|
+
|
|
235
|
+
/** `~`, `~user`, `$HOME`, `$PWD`, then resolution from `cwd`. */
|
|
236
|
+
function expand(raw, cwd) {
|
|
237
|
+
let p = raw
|
|
238
|
+
if (p === "~" || p.startsWith("~/")) p = join(homedir(), p.slice(1))
|
|
239
|
+
else if (/^~[A-Za-z_][\w.-]*/.test(p)) {
|
|
240
|
+
const [user, ...rest] = p.slice(1).split("/")
|
|
241
|
+
p = join(dirname(homedir()), user, ...rest)
|
|
242
|
+
}
|
|
243
|
+
p = p
|
|
244
|
+
.replace(/\$\{HOME\}|\$HOME\b/g, homedir())
|
|
245
|
+
.replace(/\$\{PWD\}|\$PWD\b/g, cwd)
|
|
246
|
+
return isAbsolute(p) ? resolve(p) : resolve(cwd, p)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** A file's key in the map of files the run wrote: /private/var = /var. */
|
|
250
|
+
const fileKey = (p) => p.replace(/^\/private(?=\/(var|tmp|etc)\/)/, "")
|
|
251
|
+
|
|
252
|
+
/** The part of a glob pattern before its first wildcard. */
|
|
253
|
+
const globBase = (pattern) => pattern.split(/[*?[{]/)[0] || "."
|
|
254
|
+
|
|
255
|
+
// ---------------------------------------------------------------- the lexer
|
|
256
|
+
|
|
257
|
+
/** Reads a balanced `$( … )` starting after `$(`; returns [body, end]. */
|
|
258
|
+
function readParen(s, i) {
|
|
259
|
+
let depth = 1
|
|
260
|
+
let j = i
|
|
261
|
+
let quote = ""
|
|
262
|
+
for (; j < s.length; j += 1) {
|
|
263
|
+
const ch = s[j]
|
|
264
|
+
if (quote) {
|
|
265
|
+
if (ch === "\\" && quote === '"') j += 1
|
|
266
|
+
else if (ch === quote) quote = ""
|
|
267
|
+
continue
|
|
268
|
+
}
|
|
269
|
+
if (ch === "'" || ch === '"') quote = ch
|
|
270
|
+
else if (ch === "(") depth += 1
|
|
271
|
+
else if (ch === ")") {
|
|
272
|
+
depth -= 1
|
|
273
|
+
if (depth === 0) break
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return [s.slice(i, j), j + 1]
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Lexes a command line into words and operators. A word is
|
|
281
|
+
* `{ text, subs, vars, quoted }`: `text` its value with quotes removed,
|
|
282
|
+
* `subs` the command substitutions active in it, `vars` the variables
|
|
283
|
+
* expanded in it. Heredoc bodies are returned separately, each tied to the
|
|
284
|
+
* segment (by index) whose command reads it.
|
|
285
|
+
*/
|
|
286
|
+
export function lexShell(s) {
|
|
287
|
+
const tokens = []
|
|
288
|
+
const heredocs = []
|
|
289
|
+
const pending = []
|
|
290
|
+
let word = null
|
|
291
|
+
const start = () =>
|
|
292
|
+
(word ??= { text: "", subs: [], vars: [], quoted: false, raw: "" })
|
|
293
|
+
const end = () => {
|
|
294
|
+
if (word) tokens.push({ type: "word", ...word })
|
|
295
|
+
word = null
|
|
296
|
+
}
|
|
297
|
+
const op = (text) => {
|
|
298
|
+
end()
|
|
299
|
+
tokens.push({ type: "op", text })
|
|
300
|
+
}
|
|
301
|
+
const readVar = (i, w) => {
|
|
302
|
+
const m = /^\$(\{[^}]*\}|[A-Za-z_][A-Za-z0-9_]*|[0-9#?@*$!-])/.exec(
|
|
303
|
+
s.slice(i)
|
|
304
|
+
)
|
|
305
|
+
if (!m) return i
|
|
306
|
+
const name = m[1].replace(/^\{|\}$/g, "").replace(/[:#%/].*$/, "")
|
|
307
|
+
w.vars.push({ name, followedBySlash: s[i + m[0].length] === "/" })
|
|
308
|
+
w.text += m[0]
|
|
309
|
+
w.raw += m[0]
|
|
310
|
+
return i + m[0].length - 1
|
|
311
|
+
}
|
|
312
|
+
for (let i = 0; i < s.length; i += 1) {
|
|
313
|
+
const ch = s[i]
|
|
314
|
+
if (ch === "\n") {
|
|
315
|
+
op("\n")
|
|
316
|
+
// Heredoc bodies start on the line after their `<<` operator.
|
|
317
|
+
while (pending.length > 0) {
|
|
318
|
+
const { delimiter, strip, segment } = pending.shift()
|
|
319
|
+
const lines = []
|
|
320
|
+
let j = i + 1
|
|
321
|
+
for (;;) {
|
|
322
|
+
const next = s.indexOf("\n", j)
|
|
323
|
+
const line = s.slice(j, next === -1 ? s.length : next)
|
|
324
|
+
const endLine = strip ? line.replace(/^\t+/, "") : line
|
|
325
|
+
if (endLine.trim() === delimiter) {
|
|
326
|
+
i = next === -1 ? s.length : next
|
|
327
|
+
break
|
|
328
|
+
}
|
|
329
|
+
lines.push(line)
|
|
330
|
+
if (next === -1) {
|
|
331
|
+
i = s.length
|
|
332
|
+
break
|
|
333
|
+
}
|
|
334
|
+
j = next + 1
|
|
335
|
+
}
|
|
336
|
+
heredocs.push({ segment, body: lines.join("\n") })
|
|
337
|
+
}
|
|
338
|
+
continue
|
|
339
|
+
}
|
|
340
|
+
if (ch === " " || ch === "\t") {
|
|
341
|
+
end()
|
|
342
|
+
continue
|
|
343
|
+
}
|
|
344
|
+
if (ch === "#" && !word) {
|
|
345
|
+
// A comment runs to the end of the line.
|
|
346
|
+
const next = s.indexOf("\n", i)
|
|
347
|
+
i = (next === -1 ? s.length : next) - 1
|
|
348
|
+
continue
|
|
349
|
+
}
|
|
350
|
+
if (ch === "\\") {
|
|
351
|
+
start()
|
|
352
|
+
word.text += s[i + 1] ?? ""
|
|
353
|
+
word.raw += ch + (s[i + 1] ?? "")
|
|
354
|
+
i += 1
|
|
355
|
+
continue
|
|
356
|
+
}
|
|
357
|
+
if (ch === "'") {
|
|
358
|
+
const close = s.indexOf("'", i + 1)
|
|
359
|
+
const body = s.slice(i + 1, close === -1 ? s.length : close)
|
|
360
|
+
start()
|
|
361
|
+
word.text += body
|
|
362
|
+
word.raw += `'${body}'`
|
|
363
|
+
word.quoted = true
|
|
364
|
+
i = close === -1 ? s.length : close
|
|
365
|
+
continue
|
|
366
|
+
}
|
|
367
|
+
if (ch === '"') {
|
|
368
|
+
start()
|
|
369
|
+
word.quoted = true
|
|
370
|
+
let j = i + 1
|
|
371
|
+
for (; j < s.length && s[j] !== '"'; j += 1) {
|
|
372
|
+
if (s[j] === "\\") {
|
|
373
|
+
word.text += s[j + 1] ?? ""
|
|
374
|
+
j += 1
|
|
375
|
+
} else if (s[j] === "$" && s[j + 1] === "(") {
|
|
376
|
+
const [body, next] = readParen(s, j + 2)
|
|
377
|
+
word.subs.push({ body, followedBySlash: s[next] === "/" })
|
|
378
|
+
word.text += `$(${body})`
|
|
379
|
+
j = next - 1
|
|
380
|
+
} else if (s[j] === "`") {
|
|
381
|
+
const close = s.indexOf("`", j + 1)
|
|
382
|
+
const body = s.slice(j + 1, close === -1 ? s.length : close)
|
|
383
|
+
word.subs.push({ body, followedBySlash: s[close + 1] === "/" })
|
|
384
|
+
word.text += `\`${body}\``
|
|
385
|
+
j = close === -1 ? s.length : close
|
|
386
|
+
} else if (s[j] === "$") {
|
|
387
|
+
j = readVar(j, word)
|
|
388
|
+
} else word.text += s[j]
|
|
389
|
+
}
|
|
390
|
+
word.raw += s.slice(i, j + 1)
|
|
391
|
+
i = j
|
|
392
|
+
continue
|
|
393
|
+
}
|
|
394
|
+
if (ch === "$" && s[i + 1] === "(") {
|
|
395
|
+
const [body, next] = readParen(s, i + 2)
|
|
396
|
+
start()
|
|
397
|
+
word.subs.push({ body, followedBySlash: s[next] === "/" })
|
|
398
|
+
word.text += `$(${body})`
|
|
399
|
+
word.raw += `$(${body})`
|
|
400
|
+
i = next - 1
|
|
401
|
+
continue
|
|
402
|
+
}
|
|
403
|
+
if (ch === "`") {
|
|
404
|
+
const close = s.indexOf("`", i + 1)
|
|
405
|
+
const body = s.slice(i + 1, close === -1 ? s.length : close)
|
|
406
|
+
start()
|
|
407
|
+
word.subs.push({ body, followedBySlash: s[close + 1] === "/" })
|
|
408
|
+
word.text += `\`${body}\``
|
|
409
|
+
word.raw += `\`${body}\``
|
|
410
|
+
i = close === -1 ? s.length : close
|
|
411
|
+
continue
|
|
412
|
+
}
|
|
413
|
+
if (ch === "$") {
|
|
414
|
+
start()
|
|
415
|
+
i = readVar(i, word)
|
|
416
|
+
continue
|
|
417
|
+
}
|
|
418
|
+
if (ch === "<" && s[i + 1] === "<" && s[i + 2] !== "<") {
|
|
419
|
+
const m = /^<<(-?)\s*(['"]?)([A-Za-z_][\w-]*)\2/.exec(s.slice(i))
|
|
420
|
+
if (m) {
|
|
421
|
+
end()
|
|
422
|
+
const segment = tokens.filter(
|
|
423
|
+
(t) => t.type === "op" && t.text !== "redir"
|
|
424
|
+
).length
|
|
425
|
+
pending.push({ delimiter: m[3], strip: m[1] === "-", segment })
|
|
426
|
+
i += m[0].length - 1
|
|
427
|
+
continue
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
if (ch === "<" && s.slice(i, i + 3) === "<<<") {
|
|
431
|
+
op("herestring")
|
|
432
|
+
i += 2
|
|
433
|
+
continue
|
|
434
|
+
}
|
|
435
|
+
if (ch === ">" || ch === "<") {
|
|
436
|
+
// A leading fd number (`2>`) belongs to the redirection.
|
|
437
|
+
if (word && /^\d+$/.test(word.text) && !word.quoted) word = null
|
|
438
|
+
end()
|
|
439
|
+
// `out`: the target is written (`>`, `>>`, not `>&` / `<`).
|
|
440
|
+
const out = ch === ">" && s[i + 1] !== "&"
|
|
441
|
+
const append = ch === ">" && s[i + 1] === ">"
|
|
442
|
+
if (s[i + 1] === ">" || s[i + 1] === "&") i += 1
|
|
443
|
+
tokens.push({ type: "op", text: "redir", out, append })
|
|
444
|
+
continue
|
|
445
|
+
}
|
|
446
|
+
if (ch === "&" || ch === "|" || ch === ";" || ch === "(" || ch === ")") {
|
|
447
|
+
const two = s.slice(i, i + 2)
|
|
448
|
+
if (two === "&&" || two === "||") {
|
|
449
|
+
op(two)
|
|
450
|
+
i += 1
|
|
451
|
+
} else if (ch === "&" && s[i + 1] === ">") {
|
|
452
|
+
end()
|
|
453
|
+
tokens.push({ type: "op", text: "redir", out: true, append: false })
|
|
454
|
+
i += 1
|
|
455
|
+
} else op(ch)
|
|
456
|
+
continue
|
|
457
|
+
}
|
|
458
|
+
start()
|
|
459
|
+
word.text += ch
|
|
460
|
+
word.raw += ch
|
|
461
|
+
}
|
|
462
|
+
end()
|
|
463
|
+
return { tokens, heredocs }
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Splits tokens into simple commands:
|
|
468
|
+
* { words, redirects, outputs, herestrings, piped }. `outputs` are the
|
|
469
|
+
* redirection targets written to (`{ word, append }`); `piped` says the
|
|
470
|
+
* command reads the previous one's output.
|
|
471
|
+
*/
|
|
472
|
+
function segmentsOf(tokens) {
|
|
473
|
+
const segment = (piped) => ({
|
|
474
|
+
words: [],
|
|
475
|
+
redirects: [],
|
|
476
|
+
outputs: [],
|
|
477
|
+
herestrings: [],
|
|
478
|
+
piped,
|
|
479
|
+
})
|
|
480
|
+
const segments = [segment(false)]
|
|
481
|
+
for (let i = 0; i < tokens.length; i += 1) {
|
|
482
|
+
const t = tokens[i]
|
|
483
|
+
const seg = segments[segments.length - 1]
|
|
484
|
+
if (t.type === "op" && t.text === "redir") {
|
|
485
|
+
const target = tokens[i + 1]
|
|
486
|
+
if (target?.type === "word") {
|
|
487
|
+
seg.redirects.push(target)
|
|
488
|
+
if (t.out) seg.outputs.push({ word: target, append: t.append })
|
|
489
|
+
i += 1
|
|
490
|
+
}
|
|
491
|
+
} else if (t.type === "op" && t.text === "herestring") {
|
|
492
|
+
const body = tokens[i + 1]
|
|
493
|
+
if (body?.type === "word") {
|
|
494
|
+
seg.herestrings.push(body)
|
|
495
|
+
i += 1
|
|
496
|
+
}
|
|
497
|
+
} else if (t.type === "op") {
|
|
498
|
+
segments.push(segment(t.text === "|"))
|
|
499
|
+
} else seg.words.push(t)
|
|
500
|
+
}
|
|
501
|
+
return segments
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// ---------------------------------------------------------------- checks
|
|
505
|
+
|
|
506
|
+
/** String literals in code that point at user data or climb out. */
|
|
507
|
+
function scanCode(code, cwd, check, { relative }) {
|
|
508
|
+
for (const m of code.matchAll(/(['"`])((?:\\.|(?!\1)[^\\\n])*)\1/g)) {
|
|
509
|
+
const literal = m[2].replace(/\$\{[^}]*\}/g, "")
|
|
510
|
+
if (!literal) continue
|
|
511
|
+
const absolute = USER_DATA_ROOT.test(literal)
|
|
512
|
+
const home = /^~($|\/|[A-Za-z_])/.test(literal)
|
|
513
|
+
const climbs = relative && /(^|\/)\.\.(\/|$)/.test(literal)
|
|
514
|
+
if (absolute || home || climbs) {
|
|
515
|
+
check(literal, cwd, "path in interpreted code")
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// ---------------------------------------------------------------- network
|
|
521
|
+
|
|
522
|
+
/** `localhost`, `*.localhost`, `127.*`, `[::1]`, `0.0.0.0`. */
|
|
523
|
+
const isLocalHost = (host) =>
|
|
524
|
+
/^(localhost|.+\.localhost|127\.\d+\.\d+\.\d+|\[::1?\]|0\.0\.0\.0)$/i.test(
|
|
525
|
+
host
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* The URLs in a text, each with its host. A host the text builds
|
|
530
|
+
* (`${host}`, `$HOST`, empty) is not local: it cannot be resolved.
|
|
531
|
+
*/
|
|
532
|
+
function urlsIn(text) {
|
|
533
|
+
return [...text.matchAll(URLS)].map((m) => {
|
|
534
|
+
const authority = m[1].replace(/^[^@]*@/, "")
|
|
535
|
+
const host = authority.startsWith("[")
|
|
536
|
+
? authority.slice(0, authority.indexOf("]") + 1)
|
|
537
|
+
: authority.split(":")[0]
|
|
538
|
+
return { url: m[0], local: isLocalHost(host) }
|
|
539
|
+
})
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Code split into its string literals and a skeleton in which each literal is
|
|
544
|
+
* `"S<n>"`: '…', "…" (to the end of the line at most), `…` and Python's
|
|
545
|
+
* triple quotes. The skeleton is what the code does; the literals are data.
|
|
546
|
+
*/
|
|
547
|
+
function splitCode(code) {
|
|
548
|
+
const literals = []
|
|
549
|
+
let skeleton = ""
|
|
550
|
+
for (let i = 0; i < code.length; i += 1) {
|
|
551
|
+
const ch = code[i]
|
|
552
|
+
const triple = code.slice(i, i + 3)
|
|
553
|
+
if (triple === "'''" || triple === '"""') {
|
|
554
|
+
const close = code.indexOf(triple, i + 3)
|
|
555
|
+
literals.push(code.slice(i + 3, close === -1 ? code.length : close))
|
|
556
|
+
skeleton += `"S${literals.length - 1}"`
|
|
557
|
+
i = close === -1 ? code.length : close + 2
|
|
558
|
+
continue
|
|
559
|
+
}
|
|
560
|
+
if (ch === "'" || ch === '"' || ch === "`") {
|
|
561
|
+
let j = i + 1
|
|
562
|
+
for (; j < code.length && code[j] !== ch; j += 1) {
|
|
563
|
+
if (code[j] === "\\") j += 1
|
|
564
|
+
else if (code[j] === "\n" && ch !== "`") break
|
|
565
|
+
}
|
|
566
|
+
literals.push(code.slice(i + 1, j))
|
|
567
|
+
skeleton += `"S${literals.length - 1}"` + (code[j] === "\n" ? "\n" : "")
|
|
568
|
+
i = j
|
|
569
|
+
continue
|
|
570
|
+
}
|
|
571
|
+
skeleton += ch
|
|
572
|
+
}
|
|
573
|
+
// Comments are prose: `/* … */`, `// …` and `# …` (a `#` after a space).
|
|
574
|
+
skeleton = skeleton
|
|
575
|
+
.replace(/\/\*[\s\S]*?\*\//g, " ")
|
|
576
|
+
.replace(/(^|[\s;{}()])\/\/.*$/gm, "$1")
|
|
577
|
+
.replace(/(^|\s)#.*$/gm, "$1")
|
|
578
|
+
return { skeleton, literals }
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Network use in interpreter code: a network call towards a non-local host,
|
|
583
|
+
* or towards no host the code names (it builds or receives it); and network
|
|
584
|
+
* commands it runs through child_process / subprocess (`shellNet` scans a
|
|
585
|
+
* string literal as a command). Code that names only local hosts is clean.
|
|
586
|
+
*/
|
|
587
|
+
function networkInCode(code, shellNet) {
|
|
588
|
+
const { skeleton, literals } = splitCode(code)
|
|
589
|
+
const modules = [...skeleton.matchAll(MODULE_SPECIFIERS)].map(
|
|
590
|
+
(m) => literals[Number(m[1] ?? m[2] ?? m[3])] ?? ""
|
|
591
|
+
)
|
|
592
|
+
const found = []
|
|
593
|
+
if (SPAWN_CALLS.test(skeleton) || modules.some((m) => SPAWN_MODULES.test(m)))
|
|
594
|
+
for (const literal of literals) found.push(...shellNet(literal))
|
|
595
|
+
const call =
|
|
596
|
+
NETWORK_CALLS.exec(skeleton)?.[0] ??
|
|
597
|
+
modules.find((m) => NETWORK_MODULES.test(m))
|
|
598
|
+
if (call === undefined) return found
|
|
599
|
+
const urls = literals.flatMap(urlsIn)
|
|
600
|
+
if (urls.length === 0) {
|
|
601
|
+
found.push({
|
|
602
|
+
value: call,
|
|
603
|
+
reason: "network call in interpreted code to a host it does not name",
|
|
604
|
+
network: true,
|
|
605
|
+
})
|
|
606
|
+
}
|
|
607
|
+
for (const u of urls) {
|
|
608
|
+
if (!u.local) {
|
|
609
|
+
found.push({
|
|
610
|
+
value: u.url,
|
|
611
|
+
reason: "network call in interpreted code to a non-local host",
|
|
612
|
+
network: true,
|
|
613
|
+
})
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
return found
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/** The words from the command name on: assignments and wrappers skipped. */
|
|
620
|
+
function commandOf(words) {
|
|
621
|
+
let k = 0
|
|
622
|
+
for (;;) {
|
|
623
|
+
while (k < words.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(words[k].text))
|
|
624
|
+
k += 1
|
|
625
|
+
if (k >= words.length || !WRAPPERS.has(basename(words[k].text)))
|
|
626
|
+
return words.slice(k)
|
|
627
|
+
k += 1
|
|
628
|
+
// The wrapper's options, assignments (`env`) and durations (`timeout`).
|
|
629
|
+
while (
|
|
630
|
+
k < words.length &&
|
|
631
|
+
/^(-|[A-Za-z_][A-Za-z0-9_]*=|\d+(\.\d+)?[smhd]?$)/.test(words[k].text)
|
|
632
|
+
)
|
|
633
|
+
k += 1
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/** Whether a package runner's arguments name a package the run lacks. */
|
|
638
|
+
function runsRemotePackage(args) {
|
|
639
|
+
if (args.some((a) => ["--no-install", "--no", "--offline"].includes(a)))
|
|
640
|
+
return false
|
|
641
|
+
const packages = []
|
|
642
|
+
for (let j = 0; j < args.length; j += 1) {
|
|
643
|
+
const a = args[j]
|
|
644
|
+
if (a === "-p" || a === "--package") {
|
|
645
|
+
if (args[j + 1]) packages.push(args[j + 1])
|
|
646
|
+
j += 1
|
|
647
|
+
} else if (a.startsWith("--package=")) packages.push(a.slice(10))
|
|
648
|
+
else if (!a.startsWith("-")) {
|
|
649
|
+
if (packages.length === 0) packages.push(a)
|
|
650
|
+
break
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
return packages.some((p) => !LOCAL_PACKAGES.has(p) && !/^[./]/.test(p))
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Network use by a simple command itself: a network command, a package
|
|
658
|
+
* manager or git reaching a remote, a URL of a non-local host given to an
|
|
659
|
+
* interpreter. (Interpreter code is judged by `networkInCode`.)
|
|
660
|
+
*/
|
|
661
|
+
function networkCommand(words) {
|
|
662
|
+
const cmd = commandOf(words)
|
|
663
|
+
if (cmd.length === 0) return []
|
|
664
|
+
const name = basename(cmd[0].text)
|
|
665
|
+
const args = cmd.slice(1).map((w) => w.text)
|
|
666
|
+
const hit = (reason) => [
|
|
667
|
+
{
|
|
668
|
+
value: cmd
|
|
669
|
+
.map((w) => w.text)
|
|
670
|
+
.join(" ")
|
|
671
|
+
.slice(0, 200),
|
|
672
|
+
reason,
|
|
673
|
+
network: true,
|
|
674
|
+
},
|
|
675
|
+
]
|
|
676
|
+
const sub = args.find((a) => !a.startsWith("-"))
|
|
677
|
+
if (NETWORK_COMMANDS.has(name)) return hit("network command")
|
|
678
|
+
if (
|
|
679
|
+
name === "rsync" &&
|
|
680
|
+
args.some((a) => /^(rsync:\/\/|[\w.-]+(@[\w.-]+)?::?)/.test(a))
|
|
681
|
+
)
|
|
682
|
+
return hit("network command")
|
|
683
|
+
if (PACKAGE_RUNNERS.has(name) || name === "uvx" || name === "pipx") {
|
|
684
|
+
return name === "uvx" || name === "pipx" || runsRemotePackage(args)
|
|
685
|
+
? hit("package runner that installs from the registry")
|
|
686
|
+
: []
|
|
687
|
+
}
|
|
688
|
+
if (PACKAGE_MANAGERS.has(name)) {
|
|
689
|
+
if (args.includes("--offline")) return []
|
|
690
|
+
const rest = args.slice(args.indexOf(sub) + 1)
|
|
691
|
+
if (name === "yarn" && sub === undefined)
|
|
692
|
+
return hit("package manager command that reaches the registry")
|
|
693
|
+
if (PACKAGE_RUN_SUBCOMMANDS[name].has(sub)) {
|
|
694
|
+
return runsRemotePackage(rest)
|
|
695
|
+
? hit("package runner that installs from the registry")
|
|
696
|
+
: []
|
|
697
|
+
}
|
|
698
|
+
if (
|
|
699
|
+
REGISTRY_SUBCOMMANDS.has(sub) ||
|
|
700
|
+
(sub === "init" && rest.some((a) => !a.startsWith("-")))
|
|
701
|
+
)
|
|
702
|
+
return hit("package manager command that reaches the registry")
|
|
703
|
+
}
|
|
704
|
+
if ((name === "pip" || name === "pip3") && PIP_REMOTE.has(sub))
|
|
705
|
+
return hit("package manager command that reaches the registry")
|
|
706
|
+
if (name === "git") {
|
|
707
|
+
let j = 0
|
|
708
|
+
while (j < args.length && args[j].startsWith("-")) {
|
|
709
|
+
if (args[j] === "-C" || args[j] === "-c") j += 1
|
|
710
|
+
j += 1
|
|
711
|
+
}
|
|
712
|
+
if (
|
|
713
|
+
GIT_REMOTE.has(args[j]) ||
|
|
714
|
+
(args[j] === "remote" && args.slice(j + 1).includes("update"))
|
|
715
|
+
)
|
|
716
|
+
return hit("git command that reaches a remote")
|
|
717
|
+
}
|
|
718
|
+
if (INTERPRETERS.has(name)) {
|
|
719
|
+
const found = []
|
|
720
|
+
for (let j = 0; j < args.length; j += 1) {
|
|
721
|
+
const a = args[j]
|
|
722
|
+
if (a === "-m" && /^pip3?$/.test(args[j + 1] ?? "")) {
|
|
723
|
+
return PIP_REMOTE.has(args[j + 2])
|
|
724
|
+
? hit("package manager command that reaches the registry")
|
|
725
|
+
: []
|
|
726
|
+
}
|
|
727
|
+
// Code (`-e …`, `--eval=…`) is judged by networkInCode.
|
|
728
|
+
if (CODE_FLAGS.has(a)) j += 1
|
|
729
|
+
else if (/^--(eval|print)=/.test(a)) continue
|
|
730
|
+
else {
|
|
731
|
+
for (const u of urlsIn(a).filter((u) => !u.local)) {
|
|
732
|
+
found.push({
|
|
733
|
+
value: u.url,
|
|
734
|
+
reason: "URL of a non-local host given to an interpreter",
|
|
735
|
+
network: true,
|
|
736
|
+
})
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
return found
|
|
741
|
+
}
|
|
742
|
+
return []
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/** The words of a simple command that are paths, by command. */
|
|
746
|
+
function pathWords(words) {
|
|
747
|
+
let k = 0
|
|
748
|
+
// Leading `NAME=value` assignments: their values are paths too.
|
|
749
|
+
const out = []
|
|
750
|
+
while (k < words.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(words[k].text)) {
|
|
751
|
+
out.push({ ...words[k], text: words[k].text.replace(/^[^=]*=/, "") })
|
|
752
|
+
k += 1
|
|
753
|
+
}
|
|
754
|
+
const rest = words.slice(k)
|
|
755
|
+
if (rest.length === 0) return { kind: "plain", paths: out }
|
|
756
|
+
const cmd = basename(rest[0].text)
|
|
757
|
+
const args = rest.slice(1)
|
|
758
|
+
if (cmd === "cd") return { kind: "cd", target: args[0], paths: out }
|
|
759
|
+
if (cmd === "xargs") {
|
|
760
|
+
let j = 0
|
|
761
|
+
while (j < args.length && args[j].text.startsWith("-")) j += 1
|
|
762
|
+
return { kind: "nested", words: args.slice(j), paths: out }
|
|
763
|
+
}
|
|
764
|
+
if (SHELLS.has(cmd)) {
|
|
765
|
+
const c = args.findIndex((a) => a.text === "-c")
|
|
766
|
+
if (c !== -1 && args[c + 1])
|
|
767
|
+
return { kind: "shell", script: args[c + 1].text, paths: out }
|
|
768
|
+
return {
|
|
769
|
+
kind: "plain",
|
|
770
|
+
paths: [...out, ...args.filter((a) => !a.text.startsWith("-"))],
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
if (
|
|
774
|
+
INTERPRETERS.has(cmd) ||
|
|
775
|
+
(cmd === "npx" && INTERPRETERS.has(basename(args[0]?.text ?? "")))
|
|
776
|
+
) {
|
|
777
|
+
const code = []
|
|
778
|
+
const paths = [...out]
|
|
779
|
+
for (let j = 0; j < args.length; j += 1) {
|
|
780
|
+
const a = args[j].text
|
|
781
|
+
if (CODE_FLAGS.has(a) && args[j + 1]) {
|
|
782
|
+
code.push(args[j + 1].text)
|
|
783
|
+
j += 1
|
|
784
|
+
} else if (/^--(eval|print)=/.test(a)) code.push(a.replace(/^[^=]*=/, ""))
|
|
785
|
+
else if (!a.startsWith("-")) paths.push(args[j])
|
|
786
|
+
}
|
|
787
|
+
return { kind: "interpreter", code, paths }
|
|
788
|
+
}
|
|
789
|
+
if (PATTERN_COMMANDS.has(cmd)) {
|
|
790
|
+
const paths = [...out]
|
|
791
|
+
let patternSeen = false
|
|
792
|
+
for (let j = 0; j < args.length; j += 1) {
|
|
793
|
+
const a = args[j].text
|
|
794
|
+
if (a === "-e" || a === "--regexp" || a === "--expression") {
|
|
795
|
+
patternSeen = true
|
|
796
|
+
j += 1
|
|
797
|
+
} else if (a === "-f" || a === "--file") {
|
|
798
|
+
if (args[j + 1]) paths.push(args[j + 1])
|
|
799
|
+
j += 1
|
|
800
|
+
} else if (cmd.endsWith("awk") && a === "-v") {
|
|
801
|
+
j += 1
|
|
802
|
+
} else if (a.startsWith("-")) {
|
|
803
|
+
// an option (its numeric value, if any, is not path-like)
|
|
804
|
+
} else if (!patternSeen) patternSeen = true
|
|
805
|
+
else paths.push(args[j])
|
|
806
|
+
}
|
|
807
|
+
return { kind: "plain", paths }
|
|
808
|
+
}
|
|
809
|
+
return { kind: "plain", paths: [...out, ...args] }
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
/** Checks one shell word in a path position. */
|
|
813
|
+
function checkWord(word, cwd, check, flag, scanShell) {
|
|
814
|
+
for (const sub of word.subs) {
|
|
815
|
+
scanShell(sub.body, cwd)
|
|
816
|
+
if (PATHY_SUBST.test(sub.body) || sub.followedBySlash) {
|
|
817
|
+
flag({
|
|
818
|
+
value: `$(${sub.body})`,
|
|
819
|
+
reason: "command substitution in a path",
|
|
820
|
+
})
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
for (const v of word.vars) {
|
|
824
|
+
if (OUTSIDE_VARS.has(v.name)) {
|
|
825
|
+
flag({
|
|
826
|
+
value: `$${v.name}`,
|
|
827
|
+
reason: `$${v.name} points outside the run directory`,
|
|
828
|
+
})
|
|
829
|
+
} else if (v.name !== "HOME" && v.name !== "PWD" && v.followedBySlash) {
|
|
830
|
+
flag({ value: `$${v.name}`, reason: "unresolved variable in a path" })
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
const text = word.text
|
|
834
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(text)) return // a URL
|
|
835
|
+
for (const part of text.split(/[=:,]/)) {
|
|
836
|
+
if (!part || part.startsWith("//")) continue
|
|
837
|
+
if (/\$\(|`/.test(part)) continue // reported as a substitution
|
|
838
|
+
if (/\$\{?(?!HOME\b|PWD\b)[A-Za-z_]/.test(part)) continue // reported as a variable
|
|
839
|
+
const pathy =
|
|
840
|
+
part.includes("/") ||
|
|
841
|
+
part === ".." ||
|
|
842
|
+
part.startsWith("~") ||
|
|
843
|
+
/\$\{?(HOME|PWD)\b/.test(part)
|
|
844
|
+
if (!pathy) continue
|
|
845
|
+
// Expand variables before cutting at a glob character (`${PWD}` has a `{`).
|
|
846
|
+
const expanded = part
|
|
847
|
+
.replace(/\$\{HOME\}|\$HOME\b/g, homedir())
|
|
848
|
+
.replace(/\$\{PWD\}|\$PWD\b/g, cwd)
|
|
849
|
+
check(globBase(expanded), cwd, "path outside the run directory", part)
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
/**
|
|
854
|
+
* Scans a shell command, starting in `cwd`; returns the cwd it leaves.
|
|
855
|
+
* `check(path, cwd, reason, shown?)` records a violation when `path`
|
|
856
|
+
* resolves outside; `flag(v)` records one that cannot be resolved, and
|
|
857
|
+
* network use. `written` maps each file the run has written (Write, Edit, a
|
|
858
|
+
* heredoc or echo into a file) to its text, so that a script written in the
|
|
859
|
+
* run and then run is judged as code; this command's writes are added to it.
|
|
860
|
+
*/
|
|
861
|
+
function scanShell(
|
|
862
|
+
command,
|
|
863
|
+
startCwd,
|
|
864
|
+
allowed,
|
|
865
|
+
check,
|
|
866
|
+
flag,
|
|
867
|
+
written = new Map()
|
|
868
|
+
) {
|
|
869
|
+
let cwd = startCwd
|
|
870
|
+
const { tokens, heredocs } = lexShell(command)
|
|
871
|
+
const segments = segmentsOf(tokens)
|
|
872
|
+
const recurse = (body, at) =>
|
|
873
|
+
scanShell(body, at, allowed, check, flag, written)
|
|
874
|
+
// Network use only, in text that runs as commands (a string given to
|
|
875
|
+
// child_process, a shell script written in the run): its words are not
|
|
876
|
+
// judged as paths here.
|
|
877
|
+
const networkOnly = (body, at) => {
|
|
878
|
+
const found = []
|
|
879
|
+
scanShell(
|
|
880
|
+
body,
|
|
881
|
+
at,
|
|
882
|
+
allowed,
|
|
883
|
+
() => {},
|
|
884
|
+
(v) => v.network && found.push(v),
|
|
885
|
+
new Map(written)
|
|
886
|
+
)
|
|
887
|
+
return found
|
|
888
|
+
}
|
|
889
|
+
const codeNetwork = (code, at, script) => {
|
|
890
|
+
for (const v of networkInCode(code, (text) => networkOnly(text, at))) {
|
|
891
|
+
flag(
|
|
892
|
+
script === undefined
|
|
893
|
+
? v
|
|
894
|
+
: {
|
|
895
|
+
...v,
|
|
896
|
+
value: `${script}: ${v.value}`,
|
|
897
|
+
reason: `${v.reason} (a script written in this run)`,
|
|
898
|
+
}
|
|
899
|
+
)
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
const writtenText = (word, at) => written.get(fileKey(expand(word.text, at)))
|
|
903
|
+
// What the previous command printed, when the scan knows it (an echo, a
|
|
904
|
+
// heredoc through cat, a cat of a file the run wrote): input to a pipe.
|
|
905
|
+
let printed
|
|
906
|
+
segments.forEach((seg, index) => {
|
|
907
|
+
const role = pathWords(seg.words)
|
|
908
|
+
const stdin = seg.piped ? printed : undefined
|
|
909
|
+
printed = undefined
|
|
910
|
+
if (role.kind === "cd") {
|
|
911
|
+
const target = role.target
|
|
912
|
+
if (
|
|
913
|
+
target &&
|
|
914
|
+
(target.subs.length > 0 ||
|
|
915
|
+
target.vars.some((v) => v.name !== "HOME" && v.name !== "PWD"))
|
|
916
|
+
) {
|
|
917
|
+
checkWord(target, cwd, check, flag, recurse)
|
|
918
|
+
return
|
|
919
|
+
}
|
|
920
|
+
const next = expand(target?.text ?? "~", cwd)
|
|
921
|
+
if (!inside(next, allowed)) {
|
|
922
|
+
flag({
|
|
923
|
+
value: `cd ${target?.text ?? ""}`.trim(),
|
|
924
|
+
resolved: next,
|
|
925
|
+
reason: "cd out of the run directory",
|
|
926
|
+
})
|
|
927
|
+
}
|
|
928
|
+
cwd = next
|
|
929
|
+
return
|
|
930
|
+
}
|
|
931
|
+
if (seg.words.length > 0 && !inside(cwd, allowed)) {
|
|
932
|
+
flag({
|
|
933
|
+
value: seg.words
|
|
934
|
+
.map((w) => w.text)
|
|
935
|
+
.join(" ")
|
|
936
|
+
.slice(0, 200),
|
|
937
|
+
resolved: cwd,
|
|
938
|
+
reason: "command runs outside the run directory",
|
|
939
|
+
})
|
|
940
|
+
}
|
|
941
|
+
for (const w of role.paths) checkWord(w, cwd, check, flag, recurse)
|
|
942
|
+
for (const w of seg.redirects) checkWord(w, cwd, check, flag, recurse)
|
|
943
|
+
for (const v of networkCommand(seg.words)) flag(v)
|
|
944
|
+
if (role.kind === "nested") {
|
|
945
|
+
const nested = pathWords(role.words)
|
|
946
|
+
for (const w of nested.paths) checkWord(w, cwd, check, flag, recurse)
|
|
947
|
+
for (const v of networkCommand(role.words)) flag(v)
|
|
948
|
+
if (nested.kind === "interpreter") {
|
|
949
|
+
for (const code of nested.code) {
|
|
950
|
+
scanCode(code, cwd, check, { relative: true })
|
|
951
|
+
codeNetwork(code, cwd)
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
if (nested.kind === "shell") {
|
|
955
|
+
for (const v of networkOnly(nested.script, cwd)) flag(v)
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
if (role.kind === "shell") recurse(role.script, cwd)
|
|
959
|
+
if (role.kind === "interpreter") {
|
|
960
|
+
for (const code of role.code)
|
|
961
|
+
scanCode(code, cwd, check, { relative: true })
|
|
962
|
+
}
|
|
963
|
+
// Network use by code: judged on the command behind any wrapper
|
|
964
|
+
// (`env node -e …`). A script the run wrote and runs now is code too, and
|
|
965
|
+
// so is text piped into an interpreter or a shell.
|
|
966
|
+
const command = commandOf(seg.words)
|
|
967
|
+
const cmd = basename(command[0]?.text ?? "")
|
|
968
|
+
const run = pathWords(command)
|
|
969
|
+
if (run.kind === "interpreter") {
|
|
970
|
+
for (const code of run.code) codeNetwork(code, cwd)
|
|
971
|
+
for (const w of run.paths) {
|
|
972
|
+
const text = writtenText(w, cwd)
|
|
973
|
+
if (text !== undefined) codeNetwork(text, cwd, w.text)
|
|
974
|
+
}
|
|
975
|
+
if (stdin !== undefined && run.code.length === 0) codeNetwork(stdin, cwd)
|
|
976
|
+
}
|
|
977
|
+
if (run.kind === "shell") {
|
|
978
|
+
for (const v of networkOnly(run.script, cwd)) {
|
|
979
|
+
if (role.kind !== "shell") flag(v) // else `recurse` above found it
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
if (SHELLS.has(cmd) && run.kind === "plain") {
|
|
983
|
+
const scripts = run.paths
|
|
984
|
+
.map((w) => [w.text, writtenText(w, cwd)])
|
|
985
|
+
.filter(([, text]) => text !== undefined)
|
|
986
|
+
if (stdin !== undefined) scripts.push(["(stdin)", stdin])
|
|
987
|
+
for (const [name, text] of scripts) {
|
|
988
|
+
for (const v of networkOnly(text, cwd))
|
|
989
|
+
flag({ ...v, value: `${name}: ${v.value}` })
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
// Here-strings and heredocs: fed to a shell they are shell, fed to an
|
|
993
|
+
// interpreter they are code, otherwise they are data being written.
|
|
994
|
+
const fed = [
|
|
995
|
+
...seg.herestrings.map((w) => w.text),
|
|
996
|
+
...heredocs.filter((h) => h.segment === index).map((h) => h.body),
|
|
997
|
+
]
|
|
998
|
+
const fedTo = basename(
|
|
999
|
+
seg.words.find((w) => !/^[A-Za-z_]\w*=/.test(w.text))?.text ?? ""
|
|
1000
|
+
)
|
|
1001
|
+
for (const body of fed) {
|
|
1002
|
+
if (SHELLS.has(fedTo)) recurse(body, cwd)
|
|
1003
|
+
else if (INTERPRETERS.has(fedTo))
|
|
1004
|
+
scanCode(body, cwd, check, { relative: true })
|
|
1005
|
+
else scanCode(body, cwd, check, { relative: false })
|
|
1006
|
+
// `cmd` sees through wrappers (`env node <<EOF`); paths keep `fedTo`.
|
|
1007
|
+
if (INTERPRETERS.has(cmd)) codeNetwork(body, cwd)
|
|
1008
|
+
else if (SHELLS.has(cmd) && !SHELLS.has(fedTo)) {
|
|
1009
|
+
for (const v of networkOnly(body, cwd)) flag(v)
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
// What this command prints, and the files it writes with it.
|
|
1013
|
+
const args = command.slice(1)
|
|
1014
|
+
if (cmd === "echo" || cmd === "printf") {
|
|
1015
|
+
printed = args
|
|
1016
|
+
.filter((w) => !w.text.startsWith("-"))
|
|
1017
|
+
.map((w) => w.text)
|
|
1018
|
+
.join(" ")
|
|
1019
|
+
} else if (cmd === "cat" || cmd === "tee" || cmd === "") {
|
|
1020
|
+
const input =
|
|
1021
|
+
fed.length > 0
|
|
1022
|
+
? fed.join("\n")
|
|
1023
|
+
: cmd === "cat"
|
|
1024
|
+
? args.map((w) => writtenText(w, cwd)).find((t) => t !== undefined)
|
|
1025
|
+
: stdin
|
|
1026
|
+
printed = input
|
|
1027
|
+
if (cmd === "tee" && input !== undefined) {
|
|
1028
|
+
for (const w of args.filter((w) => !w.text.startsWith("-")))
|
|
1029
|
+
written.set(fileKey(expand(w.text, cwd)), input)
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
for (const { word, append } of seg.outputs) {
|
|
1033
|
+
const path = fileKey(expand(word.text, cwd))
|
|
1034
|
+
if (printed === undefined) written.delete(path)
|
|
1035
|
+
else
|
|
1036
|
+
written.set(
|
|
1037
|
+
path,
|
|
1038
|
+
append ? `${written.get(path) ?? ""}${printed}\n` : `${printed}\n`
|
|
1039
|
+
)
|
|
1040
|
+
}
|
|
1041
|
+
if (seg.outputs.length > 0) printed = undefined
|
|
1042
|
+
})
|
|
1043
|
+
return cwd
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
/** Applies an Edit (or one edit of a MultiEdit) to a file the run wrote. */
|
|
1047
|
+
function applyEdit(written, path, edit) {
|
|
1048
|
+
if (typeof edit?.new_string !== "string") return
|
|
1049
|
+
const before = written.get(path)
|
|
1050
|
+
const old = edit.old_string
|
|
1051
|
+
if (
|
|
1052
|
+
before !== undefined &&
|
|
1053
|
+
typeof old === "string" &&
|
|
1054
|
+
old !== "" &&
|
|
1055
|
+
before.includes(old)
|
|
1056
|
+
) {
|
|
1057
|
+
written.set(
|
|
1058
|
+
path,
|
|
1059
|
+
edit.replace_all
|
|
1060
|
+
? before.split(old).join(edit.new_string)
|
|
1061
|
+
: before.replace(old, () => edit.new_string)
|
|
1062
|
+
)
|
|
1063
|
+
} else {
|
|
1064
|
+
// A file the scan has not seen whole (one of the starter's): keep what
|
|
1065
|
+
// was put into it.
|
|
1066
|
+
written.set(path, `${before ?? ""}\n${edit.new_string}`)
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
// ---------------------------------------------------------------- transcripts
|
|
1071
|
+
|
|
1072
|
+
function* toolBlocks(value) {
|
|
1073
|
+
if (Array.isArray(value)) {
|
|
1074
|
+
for (const item of value) yield* toolBlocks(item)
|
|
1075
|
+
} else if (value && typeof value === "object") {
|
|
1076
|
+
if (value.type === "tool_use" || value.type === "tool_result") yield value
|
|
1077
|
+
for (const child of Object.values(value)) yield* toolBlocks(child)
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
/**
|
|
1082
|
+
* The CLI's persisted-output notice in one transcript message (header
|
|
1083
|
+
* comment), as `{ block, path }`: a top-level user message whose content is
|
|
1084
|
+
* one tool_result block, whose whole content is the notice, and whose
|
|
1085
|
+
* `tool_use_result` (the CLI's own record of the call, beside the content the
|
|
1086
|
+
* agent receives) has `persistedOutputPath` equal to the path the notice
|
|
1087
|
+
* names, with the message's `session_id` as that path's session segment.
|
|
1088
|
+
*/
|
|
1089
|
+
function cliNotice(doc) {
|
|
1090
|
+
const content = doc?.type === "user" ? doc.message?.content : undefined
|
|
1091
|
+
if (!Array.isArray(content) || content.length !== 1) return undefined
|
|
1092
|
+
const [block] = content
|
|
1093
|
+
if (block?.type !== "tool_result" || typeof block.tool_use_id !== "string")
|
|
1094
|
+
return undefined
|
|
1095
|
+
const inner = block.content
|
|
1096
|
+
const text =
|
|
1097
|
+
typeof inner === "string"
|
|
1098
|
+
? inner
|
|
1099
|
+
: Array.isArray(inner) &&
|
|
1100
|
+
inner.length === 1 &&
|
|
1101
|
+
inner[0]?.type === "text" &&
|
|
1102
|
+
typeof inner[0].text === "string"
|
|
1103
|
+
? inner[0].text
|
|
1104
|
+
: undefined
|
|
1105
|
+
const match = text === undefined ? null : PERSISTED_NOTICE.exec(text)
|
|
1106
|
+
if (!match) return undefined
|
|
1107
|
+
const path = match[1]
|
|
1108
|
+
const meta = doc.tool_use_result
|
|
1109
|
+
if (
|
|
1110
|
+
!meta ||
|
|
1111
|
+
typeof meta !== "object" ||
|
|
1112
|
+
meta.persistedOutputPath !== path ||
|
|
1113
|
+
typeof doc.session_id !== "string" ||
|
|
1114
|
+
path.split("/").at(-3) !== doc.session_id
|
|
1115
|
+
)
|
|
1116
|
+
return undefined
|
|
1117
|
+
return { block, path }
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
/**
|
|
1121
|
+
* Whether no part of `path` below `<run>` is a symlink, as far as the path
|
|
1122
|
+
* exists now: each component is lstat'ed, so a link is refused even when its
|
|
1123
|
+
* target is gone. What does not exist (the run removed, in a re-audit) has no
|
|
1124
|
+
* link to find.
|
|
1125
|
+
*/
|
|
1126
|
+
function linkFree(path, run) {
|
|
1127
|
+
let at = run
|
|
1128
|
+
for (const part of path.slice(run.length + 1).split("/")) {
|
|
1129
|
+
at = join(at, part)
|
|
1130
|
+
let stat
|
|
1131
|
+
try {
|
|
1132
|
+
stat = lstatSync(at)
|
|
1133
|
+
} catch {
|
|
1134
|
+
return true
|
|
1135
|
+
}
|
|
1136
|
+
if (stat.isSymbolicLink()) return false
|
|
1137
|
+
}
|
|
1138
|
+
return true
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
/** `<run>` for a run directory `<run>/work`, in each spelling roots() has. */
|
|
1142
|
+
function runRoots(runDir) {
|
|
1143
|
+
const work = [resolve(runDir), realpathOrSelf(resolve(runDir))]
|
|
1144
|
+
return [...new Set(work.filter((w) => basename(w) === "work").map(dirname))]
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
function parseTranscript(text) {
|
|
1148
|
+
const docs = []
|
|
1149
|
+
const trimmed = text.trim()
|
|
1150
|
+
if (trimmed.startsWith("[")) {
|
|
1151
|
+
try {
|
|
1152
|
+
docs.push(JSON.parse(trimmed))
|
|
1153
|
+
return docs
|
|
1154
|
+
} catch {
|
|
1155
|
+
// fall through to line-by-line
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
for (const line of text.split("\n")) {
|
|
1159
|
+
const l = line.trim()
|
|
1160
|
+
if (!l.startsWith("{") && !l.startsWith("[")) continue
|
|
1161
|
+
try {
|
|
1162
|
+
docs.push(JSON.parse(l))
|
|
1163
|
+
} catch {
|
|
1164
|
+
// a partial or non-JSON line
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
return docs
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
const resultText = (result) =>
|
|
1171
|
+
typeof result.content === "string"
|
|
1172
|
+
? result.content
|
|
1173
|
+
: JSON.stringify(result.content ?? "")
|
|
1174
|
+
|
|
1175
|
+
/** The run directory a transcript's init message names, if any. */
|
|
1176
|
+
export function transcriptCwd(transcript) {
|
|
1177
|
+
for (const doc of parseTranscript(transcript)) {
|
|
1178
|
+
if (
|
|
1179
|
+
doc?.type === "system" &&
|
|
1180
|
+
doc.subtype === "init" &&
|
|
1181
|
+
typeof doc.cwd === "string"
|
|
1182
|
+
) {
|
|
1183
|
+
return doc.cwd
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
return undefined
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
/**
|
|
1190
|
+
* @param {string} transcript the agent's stdout
|
|
1191
|
+
* @param {{ runDir: string }} options
|
|
1192
|
+
* @returns {{ contaminated: boolean, auditable: boolean, toolCalls: number,
|
|
1193
|
+
* attempts: number, violations: Array<{ tool: string, value: string,
|
|
1194
|
+
* resolved?: string, reason: string, blocked: boolean }>,
|
|
1195
|
+
* persistedReads: Array<{ tool: string, value: string }> }}
|
|
1196
|
+
* `persistedReads` are the reads of the CLI's persisted output that were
|
|
1197
|
+
* not counted as violations (header comment).
|
|
1198
|
+
*/
|
|
1199
|
+
export function scanTranscript(transcript, { runDir }) {
|
|
1200
|
+
const allowed = roots(runDir)
|
|
1201
|
+
const docs = parseTranscript(transcript)
|
|
1202
|
+
if (docs.length === 0) {
|
|
1203
|
+
return {
|
|
1204
|
+
contaminated: true,
|
|
1205
|
+
auditable: false,
|
|
1206
|
+
toolCalls: 0,
|
|
1207
|
+
attempts: 0,
|
|
1208
|
+
violations: [
|
|
1209
|
+
{
|
|
1210
|
+
tool: "-",
|
|
1211
|
+
value: "",
|
|
1212
|
+
reason: "transcript holds no JSON: cannot be audited",
|
|
1213
|
+
blocked: false,
|
|
1214
|
+
},
|
|
1215
|
+
],
|
|
1216
|
+
persistedReads: [],
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
const uses = []
|
|
1220
|
+
const results = new Map()
|
|
1221
|
+
// Transcript order of every block, and the CLI's persisted-output notices.
|
|
1222
|
+
const order = new Map()
|
|
1223
|
+
const notices = []
|
|
1224
|
+
for (const doc of docs.flat()) {
|
|
1225
|
+
const notice = cliNotice(doc)
|
|
1226
|
+
for (const block of toolBlocks(doc)) {
|
|
1227
|
+
order.set(block, order.size)
|
|
1228
|
+
if (block.type === "tool_use" && typeof block.name === "string")
|
|
1229
|
+
uses.push(block)
|
|
1230
|
+
if (
|
|
1231
|
+
block.type === "tool_result" &&
|
|
1232
|
+
typeof block.tool_use_id === "string"
|
|
1233
|
+
) {
|
|
1234
|
+
results.set(block.tool_use_id, block)
|
|
1235
|
+
if (block === notice?.block)
|
|
1236
|
+
notices.push({
|
|
1237
|
+
path: notice.path,
|
|
1238
|
+
at: order.get(block),
|
|
1239
|
+
id: block.tool_use_id,
|
|
1240
|
+
})
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
const violations = []
|
|
1245
|
+
const persistedReads = []
|
|
1246
|
+
const written = new Map()
|
|
1247
|
+
const usesById = new Map()
|
|
1248
|
+
const cleanCalls = new Set()
|
|
1249
|
+
const homes = runRoots(runDir)
|
|
1250
|
+
/** A Read or Grep of the CLI's persisted output (header comment). */
|
|
1251
|
+
const persistedRead = (use, input, field) => {
|
|
1252
|
+
const raw = input[field]
|
|
1253
|
+
if (PERSISTED_READERS[use.name] !== field || raw.split("/").includes(".."))
|
|
1254
|
+
return false
|
|
1255
|
+
const path = expand(raw, resolve(runDir))
|
|
1256
|
+
const at = order.get(use)
|
|
1257
|
+
const run = homes.find(
|
|
1258
|
+
(home) =>
|
|
1259
|
+
path.startsWith(home + sep) &&
|
|
1260
|
+
PERSISTED_PATH.test(path.slice(home.length + 1))
|
|
1261
|
+
)
|
|
1262
|
+
if (run === undefined || !linkFree(path, run)) return false
|
|
1263
|
+
return notices.some((notice) => {
|
|
1264
|
+
const producer = usesById.get(notice.id)
|
|
1265
|
+
return (
|
|
1266
|
+
notice.path === path &&
|
|
1267
|
+
notice.at < at &&
|
|
1268
|
+
producer !== undefined &&
|
|
1269
|
+
order.get(producer) < notice.at &&
|
|
1270
|
+
producer.name !== "Read" &&
|
|
1271
|
+
cleanCalls.has(producer)
|
|
1272
|
+
)
|
|
1273
|
+
})
|
|
1274
|
+
}
|
|
1275
|
+
let shellCwd = resolve(runDir)
|
|
1276
|
+
for (const use of uses) {
|
|
1277
|
+
if (typeof use.id === "string" && !usesById.has(use.id))
|
|
1278
|
+
usesById.set(use.id, use)
|
|
1279
|
+
const found = []
|
|
1280
|
+
const input = use.input && typeof use.input === "object" ? use.input : {}
|
|
1281
|
+
const flag = (v) => found.push(v)
|
|
1282
|
+
const check = (value, cwd, reason, shown = value) => {
|
|
1283
|
+
if (SAFE.has(value)) return
|
|
1284
|
+
const resolved = expand(value, cwd)
|
|
1285
|
+
if (SAFE.has(resolved) || inside(resolved, allowed)) return
|
|
1286
|
+
found.push({ value: shown, resolved, reason })
|
|
1287
|
+
}
|
|
1288
|
+
if (NETWORK_TOOLS.has(use.name)) {
|
|
1289
|
+
found.push({
|
|
1290
|
+
value: JSON.stringify(input).slice(0, 200),
|
|
1291
|
+
reason: "network tool",
|
|
1292
|
+
})
|
|
1293
|
+
}
|
|
1294
|
+
// Accepted reads of persisted output, listed only if the call is clean.
|
|
1295
|
+
const reads = []
|
|
1296
|
+
for (const field of PATH_FIELDS) {
|
|
1297
|
+
if (typeof input[field] === "string" && input[field] !== "") {
|
|
1298
|
+
if (persistedRead(use, input, field)) {
|
|
1299
|
+
reads.push({ tool: use.name, value: input[field] })
|
|
1300
|
+
continue
|
|
1301
|
+
}
|
|
1302
|
+
check(
|
|
1303
|
+
input[field],
|
|
1304
|
+
resolve(runDir),
|
|
1305
|
+
`${field} outside the run directory`
|
|
1306
|
+
)
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
if (use.name === "Glob" && typeof input.pattern === "string") {
|
|
1310
|
+
check(
|
|
1311
|
+
globBase(input.pattern),
|
|
1312
|
+
resolve(runDir),
|
|
1313
|
+
"pattern outside the run directory"
|
|
1314
|
+
)
|
|
1315
|
+
}
|
|
1316
|
+
if (use.name === "Grep" && typeof input.glob === "string") {
|
|
1317
|
+
check(
|
|
1318
|
+
globBase(input.glob),
|
|
1319
|
+
resolve(runDir),
|
|
1320
|
+
"glob outside the run directory"
|
|
1321
|
+
)
|
|
1322
|
+
}
|
|
1323
|
+
if (typeof input.command === "string") {
|
|
1324
|
+
shellCwd = scanShell(
|
|
1325
|
+
input.command,
|
|
1326
|
+
shellCwd,
|
|
1327
|
+
allowed,
|
|
1328
|
+
check,
|
|
1329
|
+
flag,
|
|
1330
|
+
written
|
|
1331
|
+
)
|
|
1332
|
+
}
|
|
1333
|
+
// What the file tools write, for a script run later (scanShell).
|
|
1334
|
+
if (typeof input.file_path === "string" && input.file_path !== "") {
|
|
1335
|
+
const path = fileKey(expand(input.file_path, resolve(runDir)))
|
|
1336
|
+
if (use.name === "Write" && typeof input.content === "string")
|
|
1337
|
+
written.set(path, input.content)
|
|
1338
|
+
else if (use.name === "Edit") applyEdit(written, path, input)
|
|
1339
|
+
else if (use.name === "MultiEdit" && Array.isArray(input.edits)) {
|
|
1340
|
+
for (const edit of input.edits) applyEdit(written, path, edit)
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
if (found.length === 0) {
|
|
1344
|
+
persistedReads.push(...reads)
|
|
1345
|
+
cleanCalls.add(use)
|
|
1346
|
+
continue
|
|
1347
|
+
}
|
|
1348
|
+
const result = results.get(use.id)
|
|
1349
|
+
const text = result === undefined ? "" : resultText(result)
|
|
1350
|
+
const refusals = text.split("\n").filter((line) => PERMISSION.test(line))
|
|
1351
|
+
for (const v of found) {
|
|
1352
|
+
const named = refusals.some(
|
|
1353
|
+
(line) =>
|
|
1354
|
+
(v.value && line.includes(v.value)) ||
|
|
1355
|
+
(v.resolved && line.includes(v.resolved))
|
|
1356
|
+
)
|
|
1357
|
+
const blocked =
|
|
1358
|
+
result !== undefined &&
|
|
1359
|
+
(result.is_error === true ||
|
|
1360
|
+
named ||
|
|
1361
|
+
(found.length === 1 && refusals.length > 0))
|
|
1362
|
+
violations.push({ tool: use.name, ...v, blocked })
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
return {
|
|
1366
|
+
contaminated: violations.some((v) => !v.blocked),
|
|
1367
|
+
auditable: true,
|
|
1368
|
+
toolCalls: uses.length,
|
|
1369
|
+
attempts: violations.length,
|
|
1370
|
+
violations,
|
|
1371
|
+
persistedReads,
|
|
1372
|
+
}
|
|
1373
|
+
}
|