@hraness/ghostget 0.17.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +458 -0
- package/LICENSE +21 -0
- package/README.md +1359 -0
- package/bunfig.toml +2 -0
- package/dist/apple-photos-client.js +704 -0
- package/dist/beeper-client.js +4602 -0
- package/dist/client.js +1605 -0
- package/dist/index-26yq8q16.js +31 -0
- package/dist/index-9t13b4k7.js +508 -0
- package/dist/index-axwm9b3v.js +5 -0
- package/dist/index-gwk7rbyj.js +30 -0
- package/dist/index.js +701 -0
- package/dist/messaging.js +1130 -0
- package/dist/omni-client.js +1406 -0
- package/dist/whatsapp-client.js +355 -0
- package/docs/imessage-direct-provider.md +106 -0
- package/docs/rental-listings.md +94 -0
- package/package.json +600 -0
- package/skills/ghostget/SKILL.md +251 -0
- package/skills/ghostget/agents/openai.yaml +4 -0
- package/skills/ghostget/references/adapter-contract.md +181 -0
- package/skills/ghostget/references/apple-photos.md +115 -0
- package/skills/ghostget/references/article-drafts.md +235 -0
- package/skills/ghostget/references/code-owned-provider-scaffold.md +134 -0
- package/skills/ghostget/references/cross-posting-video.md +139 -0
- package/skills/ghostget/references/cross-posting.md +82 -0
- package/skills/ghostget/references/derivation.md +390 -0
- package/skills/ghostget/references/hraness-social-profile-stats.json +269 -0
- package/skills/ghostget/references/install.md +66 -0
- package/skills/ghostget/references/linkedin-adapter.md +442 -0
- package/skills/ghostget/references/messaging.md +168 -0
- package/skills/ghostget/references/meta-comet-contract.md +153 -0
- package/skills/ghostget/references/platform-patterns.md +122 -0
- package/skills/ghostget/references/provider-plugins.md +496 -0
- package/skills/ghostget/references/reddit-flair.md +78 -0
- package/skills/ghostget/references/safety-and-state.md +214 -0
- package/skills/ghostget/references/settlement-and-duplicate-cleanup.md +79 -0
- package/skills/ghostget/references/social-platform-routing.md +66 -0
- package/skills/ghostget/references/social-profile-stats.md +195 -0
- package/skills/ghostget/references/social-video-platform-routing.md +89 -0
- package/skills/ghostget/references/whatsapp-adapter.md +101 -0
- package/skills/ghostget/references/x-adapter.md +332 -0
- package/skills/ghostget/references/x-ai-disclosure.md +71 -0
- package/src/apple-photos-cli.ts +639 -0
- package/src/apple-photos-client-types.ts +168 -0
- package/src/apple-photos-client.ts +201 -0
- package/src/apple-photos-contact-evidence.ts +838 -0
- package/src/apple-photos-local-source.ts +1131 -0
- package/src/args.ts +2053 -0
- package/src/article-draft-document.ts +417 -0
- package/src/article-draft-embeds.ts +123 -0
- package/src/article-draft-images.ts +214 -0
- package/src/assets/adapter-template/wrench-adapter.json +40 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.json +1500 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.v1.0.0.json +129 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.v1.1.0.json +207 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.v2.0.0.json +1463 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.v2.1.0.json +1463 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.v2.2.0.json +1475 -0
- package/src/assets/adapters/beeper/wrench-web-adapter.v2.3.0.json +1488 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.json +684 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.0.0.json +581 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.1.0.json +581 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.2.0.json +581 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.3.0.json +615 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.4.0.json +684 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.5.0.json +684 -0
- package/src/assets/adapters/bluesky/wrench-web-adapter.v1.6.0.json +684 -0
- package/src/assets/adapters/clasificados/wrench-web-adapter.json +54 -0
- package/src/assets/adapters/facebook/wrench-web-adapter.json +750 -0
- package/src/assets/adapters/facebook/wrench-web-adapter.v1.0.0.json +706 -0
- package/src/assets/adapters/facebook/wrench-web-adapter.v1.1.0.json +703 -0
- package/src/assets/adapters/facebook/wrench-web-adapter.v1.2.0.json +750 -0
- package/src/assets/adapters/facebook-group/wrench-web-adapter.json +682 -0
- package/src/assets/adapters/facebook-group/wrench-web-adapter.v1.0.0.json +583 -0
- package/src/assets/adapters/facebook-group/wrench-web-adapter.v1.1.0.json +682 -0
- package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.json +437 -0
- package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.0.0.json +383 -0
- package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.1.0.json +437 -0
- package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.1.1.json +437 -0
- package/src/assets/adapters/facebook-page/wrench-web-adapter.json +880 -0
- package/src/assets/adapters/facebook-page/wrench-web-adapter.v1.0.0.json +740 -0
- package/src/assets/adapters/facebook-page/wrench-web-adapter.v1.1.0.json +880 -0
- package/src/assets/adapters/github/wrench-web-adapter.json +69 -0
- package/src/assets/adapters/github/wrench-web-adapter.v1.0.0.json +42 -0
- package/src/assets/adapters/github/wrench-web-adapter.v1.1.0.json +69 -0
- package/src/assets/adapters/gmail/wrench-adapter.json +161 -0
- package/src/assets/adapters/gmail/wrench-adapter.v1.2.0.json +157 -0
- package/src/assets/adapters/hacker-news/wrench-web-adapter.json +315 -0
- package/src/assets/adapters/hacker-news/wrench-web-adapter.v1.0.0.json +315 -0
- package/src/assets/adapters/imessage/wrench-web-adapter.json +236 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.json +719 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.0.0.json +627 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.1.0.json +609 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.2.0.json +640 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.3.0.json +671 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.4.0.json +710 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.5.0.json +710 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.6.0.json +719 -0
- package/src/assets/adapters/instagram/wrench-web-adapter.v1.7.0.json +719 -0
- package/src/assets/adapters/linkedin/wrench-adapter.json +442 -0
- package/src/assets/adapters/linkedin/wrench-adapter.v0.4.0.json +124 -0
- package/src/assets/adapters/linkedin/wrench-adapter.v1.0.0.json +411 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.json +914 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.0.0.json +604 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.1.0.json +604 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.10.0.json +809 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.11.0.json +809 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.12.0.json +820 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.13.0.json +819 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.14.0.json +819 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.15.0.json +819 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.16.0.json +874 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.17.0.json +874 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.18.0.json +874 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.19.0.json +874 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.2.0.json +731 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.3.0.json +768 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.4.0.json +808 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.5.0.json +808 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.6.0.json +808 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.7.0.json +825 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.8.0.json +792 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.v1.9.0.json +809 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.json +855 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.0.0.json +577 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.1.0.json +604 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.10.0.json +712 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.11.0.json +712 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.12.0.json +712 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.2.0.json +653 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.3.0.json +712 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.4.0.json +713 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.5.0.json +712 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.6.0.json +713 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.7.0.json +713 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.8.0.json +713 -0
- package/src/assets/adapters/reddit/wrench-web-adapter.v1.9.0.json +713 -0
- package/src/assets/adapters/substack/wrench-web-adapter.json +936 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.0.0.json +817 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.1.0.json +812 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.2.0.json +812 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.3.0.json +902 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.4.0.json +936 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.5.0.json +936 -0
- package/src/assets/adapters/substack/wrench-web-adapter.v1.6.0.json +936 -0
- package/src/assets/adapters/threads/wrench-web-adapter.json +692 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.0.0.json +629 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.1.0.json +623 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.2.0.json +622 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.3.0.json +622 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.4.0.json +622 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.5.0.json +699 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.6.0.json +693 -0
- package/src/assets/adapters/threads/wrench-web-adapter.v1.7.0.json +692 -0
- package/src/assets/adapters/tiktok/wrench-web-adapter.json +762 -0
- package/src/assets/adapters/tiktok/wrench-web-adapter.v1.0.0.json +681 -0
- package/src/assets/adapters/tiktok/wrench-web-adapter.v1.1.0.json +708 -0
- package/src/assets/adapters/tiktok/wrench-web-adapter.v1.2.0.json +762 -0
- package/src/assets/adapters/tiktok/wrench-web-adapter.v1.3.0.json +762 -0
- package/src/assets/adapters/twitch/wrench-web-adapter.json +42 -0
- package/src/assets/adapters/twitch/wrench-web-adapter.v1.0.0.json +42 -0
- package/src/assets/adapters/whatsapp/wrench-web-adapter.json +400 -0
- package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.0.0.json +355 -0
- package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.1.0.json +386 -0
- package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.2.0.json +386 -0
- package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.3.0.json +400 -0
- package/src/assets/adapters/x/wrench-adapter.json +749 -0
- package/src/assets/adapters/x/wrench-adapter.v1.0.0.json +700 -0
- package/src/assets/adapters/x/wrench-adapter.v1.1.0.json +707 -0
- package/src/assets/adapters/x/wrench-web-adapter.json +712 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.1.0.json +579 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.10.0.json +685 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.11.0.json +712 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.12.0.json +712 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.13.0.json +712 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.2.0.json +578 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.3.0.json +610 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.4.0.json +617 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.5.0.json +634 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.6.0.json +632 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.7.0.json +649 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.8.0.json +649 -0
- package/src/assets/adapters/x/wrench-web-adapter.v1.9.0.json +683 -0
- package/src/assets/adapters/youtube/wrench-web-adapter.json +561 -0
- package/src/assets/adapters/youtube/wrench-web-adapter.v1.0.0.json +465 -0
- package/src/assets/adapters/youtube/wrench-web-adapter.v1.1.0.json +519 -0
- package/src/assets/adapters/youtube/wrench-web-adapter.v1.2.0.json +561 -0
- package/src/assets/adapters/youtube/wrench-web-adapter.v1.3.0.json +561 -0
- package/src/assets/code-owned-provider-template/plugin.test.ts.template +54 -0
- package/src/assets/code-owned-provider-template/plugin.ts.template +102 -0
- package/src/assets/code-owned-provider-template/runtime.internal.test.ts.template +44 -0
- package/src/assets/code-owned-provider-template/runtime.ts.template +59 -0
- package/src/assets/code-owned-provider-template/wrench-adapter.json.template +33 -0
- package/src/assets/fonts/nebula-sans/LICENSE.txt +96 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Book.woff2 +0 -0
- package/src/assets/fonts/nebula-sans/PROVENANCE.md +14 -0
- package/src/assets.d.ts +4 -0
- package/src/auth.ts +1157 -0
- package/src/beeper-client-types.ts +169 -0
- package/src/beeper-client.ts +250 -0
- package/src/beeper-contact-interactions-cli.ts +180 -0
- package/src/beeper-contact-interactions.ts +1612 -0
- package/src/beeper-message-bundle-v1.ts +257 -0
- package/src/beeper-message-like-me-cli.ts +84 -0
- package/src/beeper-message-like-me-export.ts +2274 -0
- package/src/beeper-message-like-me-recovery.ts +931 -0
- package/src/beeper-message-like-me-source.ts +4034 -0
- package/src/browser-admission.ts +928 -0
- package/src/browser-snapshots.ts +260 -0
- package/src/browser.ts +3873 -0
- package/src/canonical-json.ts +60 -0
- package/src/catalog-cli.ts +638 -0
- package/src/cli.ts +208 -0
- package/src/client-types.ts +301 -0
- package/src/client.ts +2726 -0
- package/src/confirmed-write-failure.ts +44 -0
- package/src/confirmed-write-model.ts +72 -0
- package/src/confirmed-write-platform.ts +1055 -0
- package/src/confirmed-write-program.ts +224 -0
- package/src/confirmed-write-runtime.ts +16 -0
- package/src/cursor-token.ts +228 -0
- package/src/derivation-dnr-readiness.ts +367 -0
- package/src/derivation-file-chooser.ts +384 -0
- package/src/derivation-network-boundary.ts +835 -0
- package/src/derivation-network-guard.ts +782 -0
- package/src/derivation-network-proxy-helper.ts +440 -0
- package/src/derivation-network-proxy.ts +385 -0
- package/src/derive-command-helper.ts +693 -0
- package/src/derive-fixtures.ts +343 -0
- package/src/derive-review.ts +980 -0
- package/src/derive.ts +4754 -0
- package/src/fixtures/beeper-message-like-me-v1/accounts.ndjson +2 -0
- package/src/fixtures/beeper-message-like-me-v1/conversations.ndjson +1 -0
- package/src/fixtures/beeper-message-like-me-v1/manifest.json +1 -0
- package/src/fixtures/beeper-message-like-me-v1/messages.ndjson +2 -0
- package/src/fixtures/beeper-message-like-me-v1/participants.ndjson +3 -0
- package/src/fixtures/beeper-message-like-me-v1/reactions.ndjson +1 -0
- package/src/fixtures/beeper-message-like-me-v1/tombstones.ndjson +1 -0
- package/src/ghostget.ts +3672 -0
- package/src/gmail-capture.ts +1244 -0
- package/src/har-internal.ts +1242 -0
- package/src/har.ts +743 -0
- package/src/index.ts +218 -0
- package/src/invocation-read-platform.ts +55 -0
- package/src/invocation-read-program.ts +93 -0
- package/src/invocation-read-runtime.ts +11 -0
- package/src/linked-device-lifecycle-admission.ts +450 -0
- package/src/linked-device-lifecycle-journal.ts +1560 -0
- package/src/linked-device-lifecycle-runtime.ts +1602 -0
- package/src/local-cli-admission.ts +135 -0
- package/src/local-cli-contracts.ts +271 -0
- package/src/local-cli-execution.ts +98 -0
- package/src/local-cli-surface-contract.ts +1510 -0
- package/src/local-cli-tool-identity.ts +336 -0
- package/src/media/archive.ts +2325 -0
- package/src/media/args.ts +303 -0
- package/src/media/cli.ts +287 -0
- package/src/media/doctor.ts +475 -0
- package/src/media/ffmpeg.ts +1330 -0
- package/src/media/http-capture.ts +711 -0
- package/src/media/http-probe.ts +593 -0
- package/src/media/http.ts +387 -0
- package/src/media/index.ts +10 -0
- package/src/media/local-transcription.ts +613 -0
- package/src/media/lock.ts +321 -0
- package/src/media/manifest.ts +1712 -0
- package/src/media/metadata.ts +906 -0
- package/src/media/process.ts +723 -0
- package/src/media/revision.ts +166 -0
- package/src/media/runtime-closure.ts +1063 -0
- package/src/media/source-router.ts +187 -0
- package/src/media/transcriber-config.ts +1122 -0
- package/src/media/transcript-persistence-model.ts +15 -0
- package/src/media/transcript-persistence-platform.ts +89 -0
- package/src/media/transcript-persistence-program.ts +39 -0
- package/src/media/transcript-persistence-runtime.ts +24 -0
- package/src/media/transcript.ts +616 -0
- package/src/media/utf8-order.ts +30 -0
- package/src/media/whisper-cpp.ts +464 -0
- package/src/media/whisper-language.ts +58 -0
- package/src/media/yt-dlp.ts +439 -0
- package/src/message-like-me-agentic-messaging.ts +882 -0
- package/src/messaging-action-store.ts +793 -0
- package/src/messaging-runtime.ts +2136 -0
- package/src/messaging-store.ts +539 -0
- package/src/messaging-types.ts +1663 -0
- package/src/messaging.ts +334 -0
- package/src/model.ts +2671 -0
- package/src/oauth-google.ts +888 -0
- package/src/omni-client-types.ts +306 -0
- package/src/omni-client.ts +2225 -0
- package/src/omni-limits.ts +34 -0
- package/src/omni-model.ts +1614 -0
- package/src/omni-request.ts +379 -0
- package/src/omni-runtime.ts +1632 -0
- package/src/operation-deadline.ts +189 -0
- package/src/path-helper.ts +1656 -0
- package/src/pinned-https.ts +194 -0
- package/src/plan-assets.ts +832 -0
- package/src/platform-catalog.ts +1832 -0
- package/src/plugins/beeper-linked-device/plugin.ts +310 -0
- package/src/plugins/bluesky-web/plugin.ts +178 -0
- package/src/plugins/clasificados-web/plugin.ts +66 -0
- package/src/plugins/github-web/plugin.ts +80 -0
- package/src/plugins/gmail-official/plugin.ts +110 -0
- package/src/plugins/hacker-news-web/plugin.ts +52 -0
- package/src/plugins/imessage-direct/plugin.ts +195 -0
- package/src/plugins/imessage-direct/vendor/0001-fix-keep-AppleScript-send-payloads-out-of-child-argv.patch +1430 -0
- package/src/plugins/imessage-direct/vendor/0002-feat-rpc-add-exact-chat-lookup.patch +225 -0
- package/src/plugins/imessage-direct/vendor/provenance.json +80 -0
- package/src/plugins/linkedin-official/plugin.ts +84 -0
- package/src/plugins/linkedin-web/plugin.ts +412 -0
- package/src/plugins/meta-web/plugin.ts +317 -0
- package/src/plugins/reddit-web/flair.ts +492 -0
- package/src/plugins/reddit-web/plugin.ts +477 -0
- package/src/plugins/substack-web/plugin.ts +124 -0
- package/src/plugins/tiktok-web/plugin.ts +67 -0
- package/src/plugins/twitch-web/plugin.ts +54 -0
- package/src/plugins/whatsapp-linked-device/plugin.ts +105 -0
- package/src/plugins/x-official/plugin.ts +64 -0
- package/src/plugins/x-web/plugin.ts +488 -0
- package/src/plugins/youtube-web/plugin.ts +101 -0
- package/src/portable-run-recovery.ts +466 -0
- package/src/process-identity.ts +501 -0
- package/src/profile-clone-helper.ts +104 -0
- package/src/provider-catalog-views.ts +51 -0
- package/src/provider-context.ts +69 -0
- package/src/provider-contract-definitions-x.ts +45 -0
- package/src/provider-contract-definitions.ts +948 -0
- package/src/provider-contract-input-gmail.ts +176 -0
- package/src/provider-contract-input-linkedin.ts +77 -0
- package/src/provider-contract-input-x.ts +304 -0
- package/src/provider-contract-planning.ts +23 -0
- package/src/provider-contract-semantic-identity.ts +54 -0
- package/src/provider-contracts.ts +251 -0
- package/src/provider-http.ts +723 -0
- package/src/provider-plugin-auth.ts +36 -0
- package/src/provider-plugin-builtins.ts +229 -0
- package/src/provider-plugin-cleanup-barrier.ts +229 -0
- package/src/provider-plugin-cleanup-execution.ts +79 -0
- package/src/provider-plugin-cleanup-resource.ts +470 -0
- package/src/provider-plugin-contract-identity.ts +707 -0
- package/src/provider-plugin-host.ts +1972 -0
- package/src/provider-plugin-identifiers.ts +60 -0
- package/src/provider-plugin-invocation-lease.ts +889 -0
- package/src/provider-plugin-lifecycle-kernel.ts +566 -0
- package/src/provider-plugin-lifecycle.ts +1455 -0
- package/src/provider-plugin-module-analysis.ts +336 -0
- package/src/provider-plugin-package.ts +2530 -0
- package/src/provider-plugin-portable-authority.ts +106 -0
- package/src/provider-plugin-portable-catalog.ts +94 -0
- package/src/provider-plugin-portable-identity.ts +315 -0
- package/src/provider-plugin-portable-runtime.ts +2057 -0
- package/src/provider-plugin-protocol.ts +1625 -0
- package/src/provider-plugin-registry.ts +3342 -0
- package/src/provider-plugin-store.ts +2155 -0
- package/src/provider-plugin-test-fixture.ts +5 -0
- package/src/provider-plugin.ts +4907 -0
- package/src/provider-plugins.generated.ts +43 -0
- package/src/provider-plugins.ts +8 -0
- package/src/provider-subject.ts +41 -0
- package/src/provider.ts +315 -0
- package/src/providers/beeper-local-runtime.ts +5426 -0
- package/src/providers/beeper-local.ts +2739 -0
- package/src/providers/beeper-messaging.ts +462 -0
- package/src/providers/beeper-omni.ts +1159 -0
- package/src/providers/bluesky-web-runtime.ts +2895 -0
- package/src/providers/bluesky-web.ts +1614 -0
- package/src/providers/clasificados-web-runtime.ts +255 -0
- package/src/providers/clasificados-web.ts +503 -0
- package/src/providers/contact-projection.ts +301 -0
- package/src/providers/github-read-model.ts +94 -0
- package/src/providers/github-read-platform.ts +165 -0
- package/src/providers/github-read-program.ts +97 -0
- package/src/providers/github-web-runtime.ts +320 -0
- package/src/providers/github-web.ts +403 -0
- package/src/providers/gmail-api.ts +2834 -0
- package/src/providers/gmail-omni.ts +830 -0
- package/src/providers/gmail.ts +1130 -0
- package/src/providers/hacker-news-web-runtime.ts +220 -0
- package/src/providers/hacker-news-web.ts +809 -0
- package/src/providers/imessage-direct-install.ts +354 -0
- package/src/providers/imessage-direct-messaging.ts +532 -0
- package/src/providers/imessage-direct-omni.ts +400 -0
- package/src/providers/imessage-direct-runtime.ts +1498 -0
- package/src/providers/imessage-direct.ts +351 -0
- package/src/providers/instagram-video-foundations.ts +913 -0
- package/src/providers/instagram-web-profile-browser.ts +600 -0
- package/src/providers/iso-bmff.ts +338 -0
- package/src/providers/linkedin-company-platform.ts +42 -0
- package/src/providers/linkedin-company-program.ts +88 -0
- package/src/providers/linkedin-contact-failure.ts +84 -0
- package/src/providers/linkedin-contact-platform.ts +45 -0
- package/src/providers/linkedin-contact-program.ts +91 -0
- package/src/providers/linkedin-profile-activity-failure.ts +76 -0
- package/src/providers/linkedin-profile-activity-platform.ts +41 -0
- package/src/providers/linkedin-profile-activity-program.ts +67 -0
- package/src/providers/linkedin-read-failure.ts +52 -0
- package/src/providers/linkedin-self-platform.ts +53 -0
- package/src/providers/linkedin-self-program.ts +122 -0
- package/src/providers/linkedin-web-article-browser.ts +794 -0
- package/src/providers/linkedin-web-bootstrap.ts +146 -0
- package/src/providers/linkedin-web-contact.ts +830 -0
- package/src/providers/linkedin-web-feed-browser.ts +832 -0
- package/src/providers/linkedin-web-feed.ts +864 -0
- package/src/providers/linkedin-web-post-browser.ts +653 -0
- package/src/providers/linkedin-web-profile-browser.ts +683 -0
- package/src/providers/linkedin-web-runtime.ts +2388 -0
- package/src/providers/linkedin-web.ts +3305 -0
- package/src/providers/linkedin.ts +1885 -0
- package/src/providers/meta-bootstrap.ts +1043 -0
- package/src/providers/meta-facebook-group.ts +690 -0
- package/src/providers/meta-marketplace-relay.ts +747 -0
- package/src/providers/meta-omni.ts +373 -0
- package/src/providers/meta-relay-bundle.ts +1049 -0
- package/src/providers/meta-web-descriptors.ts +1740 -0
- package/src/providers/meta-web-runtime.ts +3388 -0
- package/src/providers/meta-web.ts +2931 -0
- package/src/providers/read-failure.ts +139 -0
- package/src/providers/reddit-omni.ts +462 -0
- package/src/providers/reddit-web-runtime.ts +1929 -0
- package/src/providers/reddit-web.ts +1611 -0
- package/src/providers/rental-listings.ts +460 -0
- package/src/providers/substack-omni.ts +406 -0
- package/src/providers/substack-video-mp4.ts +33 -0
- package/src/providers/substack-web-runtime.ts +2812 -0
- package/src/providers/substack-web.ts +1502 -0
- package/src/providers/tiktok-video-mp4.ts +33 -0
- package/src/providers/tiktok-web-runtime.ts +770 -0
- package/src/providers/tiktok-web.ts +2786 -0
- package/src/providers/twitch-web-runtime.ts +226 -0
- package/src/providers/twitch-web.ts +274 -0
- package/src/providers/whatsapp-account-identity.ts +55 -0
- package/src/providers/whatsapp-contact-projection-helper.ts +874 -0
- package/src/providers/whatsapp-contact-projection-protocol.ts +403 -0
- package/src/providers/whatsapp-interaction-projection-helper.ts +1452 -0
- package/src/providers/whatsapp-interaction-projection-protocol.ts +462 -0
- package/src/providers/whatsapp-message-export-projection-protocol.ts +532 -0
- package/src/providers/whatsapp-messaging.ts +132 -0
- package/src/providers/whatsapp-omni.ts +586 -0
- package/src/providers/whatsapp-web-runtime.ts +3108 -0
- package/src/providers/whatsapp-web.ts +1037 -0
- package/src/providers/x-image-provenance.ts +523 -0
- package/src/providers/x-made-with-ai.ts +106 -0
- package/src/providers/x-transaction-id.ts +413 -0
- package/src/providers/x-web-runtime.ts +3485 -0
- package/src/providers/x-web.ts +2332 -0
- package/src/providers/x.ts +1889 -0
- package/src/providers/youtube-web-runtime.ts +1400 -0
- package/src/providers/youtube-web.ts +1221 -0
- package/src/read-admission-runtime.ts +91 -0
- package/src/read-client.ts +521 -0
- package/src/read-effect-platform.ts +7 -0
- package/src/read-effect-runtime.ts +14 -0
- package/src/read-effect.ts +52 -0
- package/src/read-projection-admission.ts +866 -0
- package/src/read-projections.ts +3151 -0
- package/src/recovery.ts +1492 -0
- package/src/reviewed-template.ts +146 -0
- package/src/run-journal.ts +1272 -0
- package/src/runtime.ts +5116 -0
- package/src/scripts/generate-provider-plugin-catalog.ts +121 -0
- package/src/scripts/install-whatsapp-protocol.sh +232 -0
- package/src/scripts/recover-install-stages.ts +1491 -0
- package/src/scripts/resolve-state-home.ts +30 -0
- package/src/scripts/scaffold-web-provider.ts +973 -0
- package/src/scripts/sync-bundled-adapters.ts +695 -0
- package/src/session-secrets.ts +1183 -0
- package/src/state-helper.bunfig.toml +2 -0
- package/src/state-helper.ts +2914 -0
- package/src/storage.ts +3842 -0
- package/src/transport-policy.ts +2 -0
- package/src/usage.ts +182 -0
- package/src/version.ts +2 -0
- package/src/web-session-authentication-policy.ts +136 -0
- package/src/web-session-cleanup-admission.ts +2344 -0
- package/src/web-session-cleanup-join.ts +99 -0
- package/src/web-session-client.ts +996 -0
- package/src/web-session-contract-definitions.ts +820 -0
- package/src/web-session-contract-planning.ts +42 -0
- package/src/web-session-contracts.ts +228 -0
- package/src/web-session-cookies.ts +500 -0
- package/src/web-session-execution.ts +385 -0
- package/src/web-session-http.ts +373 -0
- package/src/web-session-read-errors.ts +19 -0
- package/src/web-session-read-runtime.ts +124 -0
- package/src/web-session-recovery.ts +818 -0
- package/src/web-session-sites.ts +58 -0
- package/src/web-session-template.ts +994 -0
- package/src/web-session.ts +93 -0
- package/src/whatsapp-client-binding.ts +21 -0
- package/src/whatsapp-client-types.ts +75 -0
- package/src/whatsapp-client.ts +395 -0
- package/src/whatsapp-export-coordinate.ts +17 -0
- package/src/whatsapp-message-bundle-v2.ts +195 -0
- package/src/whatsapp-message-like-me-cli.ts +195 -0
- package/src/whatsapp-message-like-me-export.ts +1072 -0
- package/src/whatsapp-message-like-me-source.ts +1012 -0
- package/tsconfig.json +67 -0
|
@@ -0,0 +1,835 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { existsSync, lstatSync } from "node:fs";
|
|
3
|
+
import { connect as connectLocal, type Socket } from "node:net";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
createDerivationGuardExtension,
|
|
9
|
+
DERIVATION_GUARD_PROXY_CONFIG,
|
|
10
|
+
DERIVATION_GUARD_PROXY_READY,
|
|
11
|
+
derivationGuardControlSocketPath,
|
|
12
|
+
derivationProxyPolicySha256,
|
|
13
|
+
parseProxyHelperConfig,
|
|
14
|
+
parseProxyHelperReady,
|
|
15
|
+
proxyHelperConfigContent,
|
|
16
|
+
proxyHelperReadyContent,
|
|
17
|
+
readGuardPrivateFile,
|
|
18
|
+
verifyDerivationGuardExtension,
|
|
19
|
+
verifyGuardPrivateFile,
|
|
20
|
+
writeGuardPrivateFile,
|
|
21
|
+
type DerivationNetworkGuard,
|
|
22
|
+
type DerivationProxyHelperConfig,
|
|
23
|
+
type GuardDirectoryIdentity,
|
|
24
|
+
} from "./derivation-network-guard";
|
|
25
|
+
import { sha256 } from "./canonical-json";
|
|
26
|
+
import {
|
|
27
|
+
captureProcessOwnerIdentity,
|
|
28
|
+
processOwnerStatus,
|
|
29
|
+
type ProcessOwnerIdentity,
|
|
30
|
+
type ProcessOwnerStatus,
|
|
31
|
+
} from "./process-identity";
|
|
32
|
+
|
|
33
|
+
const proxyHelperPath = join(
|
|
34
|
+
dirname(fileURLToPath(import.meta.url)),
|
|
35
|
+
"derivation-network-proxy-helper.ts",
|
|
36
|
+
);
|
|
37
|
+
const trustedBunConfigPath = join(
|
|
38
|
+
dirname(fileURLToPath(import.meta.url)),
|
|
39
|
+
"state-helper.bunfig.toml",
|
|
40
|
+
);
|
|
41
|
+
const PROXY_START_TIMEOUT_MS = 15_000;
|
|
42
|
+
const PROXY_CONTROL_TIMEOUT_MS = 5_000;
|
|
43
|
+
const PROXY_CONTROL_MAX_BYTES = 64 * 1024;
|
|
44
|
+
const helperFailureEnvironmentKey = "GHOSTGET_DERIVATION_PROXY_FAIL_FOR_TEST";
|
|
45
|
+
|
|
46
|
+
export type DerivationProxyHelperFailureForTest =
|
|
47
|
+
| "after-proxy"
|
|
48
|
+
| "after-control-listen"
|
|
49
|
+
| "after-ready-write";
|
|
50
|
+
|
|
51
|
+
type ProxyControlResponse = {
|
|
52
|
+
readonly schemaVersion: 1;
|
|
53
|
+
readonly ok: true;
|
|
54
|
+
readonly derivationId: string;
|
|
55
|
+
readonly policySha256: string;
|
|
56
|
+
readonly port: number;
|
|
57
|
+
readonly owner: ProcessOwnerIdentity;
|
|
58
|
+
readonly adopted: boolean;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
type BoundaryDependencies = {
|
|
62
|
+
readonly inspectOwner: (owner: ProcessOwnerIdentity) => ProcessOwnerStatus;
|
|
63
|
+
readonly monotonicNow: () => number;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export class UnsafeDerivationNetworkBoundaryCleanupError extends Error {
|
|
67
|
+
constructor(cause: unknown) {
|
|
68
|
+
super(
|
|
69
|
+
"derivation network helper could not be proved quiescent; private state was preserved",
|
|
70
|
+
{ cause },
|
|
71
|
+
);
|
|
72
|
+
this.name = "UnsafeDerivationNetworkBoundaryCleanupError";
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
class DerivationProxyControlUnavailableError extends Error {
|
|
77
|
+
readonly code: "ENOENT" | "ECONNREFUSED";
|
|
78
|
+
|
|
79
|
+
constructor(code: "ENOENT" | "ECONNREFUSED", cause: unknown) {
|
|
80
|
+
super("derivation proxy control is unavailable", { cause });
|
|
81
|
+
this.name = "DerivationProxyControlUnavailableError";
|
|
82
|
+
this.code = code;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
class DerivationProxyControlMissingError extends Error {
|
|
87
|
+
constructor() {
|
|
88
|
+
super("derivation proxy control is missing");
|
|
89
|
+
this.name = "DerivationProxyControlMissingError";
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function exactKeys(record: Record<string, unknown>, expected: readonly string[]): boolean {
|
|
94
|
+
const actual = Object.keys(record).sort();
|
|
95
|
+
const sorted = [...expected].sort();
|
|
96
|
+
return actual.length === sorted.length
|
|
97
|
+
&& actual.every((key, index) => key === sorted[index]);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function parseOwner(value: unknown): ProcessOwnerIdentity {
|
|
101
|
+
if (
|
|
102
|
+
typeof value !== "object"
|
|
103
|
+
|| value === null
|
|
104
|
+
|| Array.isArray(value)
|
|
105
|
+
|| !exactKeys(value as Record<string, unknown>, ["pid", "bootId", "processStartId"])
|
|
106
|
+
) throw new Error("derivation proxy control response is malformed");
|
|
107
|
+
const record = value as Record<string, unknown>;
|
|
108
|
+
if (
|
|
109
|
+
!Number.isSafeInteger(record.pid)
|
|
110
|
+
|| (record.pid as number) < 1
|
|
111
|
+
|| typeof record.bootId !== "string"
|
|
112
|
+
|| !/^[a-f0-9]{64}$/u.test(record.bootId)
|
|
113
|
+
|| typeof record.processStartId !== "string"
|
|
114
|
+
|| !/^[a-f0-9]{64}$/u.test(record.processStartId)
|
|
115
|
+
) throw new Error("derivation proxy control response is malformed");
|
|
116
|
+
return {
|
|
117
|
+
pid: record.pid as number,
|
|
118
|
+
bootId: record.bootId,
|
|
119
|
+
processStartId: record.processStartId,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function parseControlResponse(value: unknown): ProxyControlResponse {
|
|
124
|
+
if (
|
|
125
|
+
typeof value !== "object"
|
|
126
|
+
|| value === null
|
|
127
|
+
|| Array.isArray(value)
|
|
128
|
+
|| !exactKeys(value as Record<string, unknown>, [
|
|
129
|
+
"schemaVersion", "ok", "derivationId", "policySha256", "port", "owner", "adopted",
|
|
130
|
+
])
|
|
131
|
+
) throw new Error("derivation proxy control response is malformed");
|
|
132
|
+
const record = value as Record<string, unknown>;
|
|
133
|
+
if (
|
|
134
|
+
record.schemaVersion !== 1
|
|
135
|
+
|| record.ok !== true
|
|
136
|
+
|| typeof record.derivationId !== "string"
|
|
137
|
+
|| !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u.test(record.derivationId)
|
|
138
|
+
|| typeof record.policySha256 !== "string"
|
|
139
|
+
|| !/^[a-f0-9]{64}$/u.test(record.policySha256)
|
|
140
|
+
|| !Number.isSafeInteger(record.port)
|
|
141
|
+
|| (record.port as number) < 1
|
|
142
|
+
|| (record.port as number) > 65_535
|
|
143
|
+
|| typeof record.adopted !== "boolean"
|
|
144
|
+
) throw new Error("derivation proxy control response is malformed");
|
|
145
|
+
return {
|
|
146
|
+
schemaVersion: 1,
|
|
147
|
+
ok: true,
|
|
148
|
+
derivationId: record.derivationId,
|
|
149
|
+
policySha256: record.policySha256,
|
|
150
|
+
port: record.port as number,
|
|
151
|
+
owner: parseOwner(record.owner),
|
|
152
|
+
adopted: record.adopted,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function sameOwner(left: ProcessOwnerIdentity, right: ProcessOwnerIdentity): boolean {
|
|
157
|
+
return left.pid === right.pid
|
|
158
|
+
&& left.bootId === right.bootId
|
|
159
|
+
&& left.processStartId === right.processStartId;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function currentUserOwns(uid: number | bigint): boolean {
|
|
163
|
+
return typeof process.getuid !== "function" || BigInt(process.getuid()) === BigInt(uid);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function pathIsAbsent(path: string): boolean {
|
|
167
|
+
try {
|
|
168
|
+
lstatSync(path);
|
|
169
|
+
return false;
|
|
170
|
+
} catch (error) {
|
|
171
|
+
if (
|
|
172
|
+
typeof error === "object"
|
|
173
|
+
&& error !== null
|
|
174
|
+
&& "code" in error
|
|
175
|
+
&& error.code === "ENOENT"
|
|
176
|
+
) return true;
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function assertPrivateDirectoryIdentity(
|
|
182
|
+
path: string,
|
|
183
|
+
expected: GuardDirectoryIdentity,
|
|
184
|
+
): void {
|
|
185
|
+
const stats = lstatSync(path, { bigint: true });
|
|
186
|
+
if (
|
|
187
|
+
!stats.isDirectory()
|
|
188
|
+
|| stats.isSymbolicLink()
|
|
189
|
+
|| !currentUserOwns(stats.uid)
|
|
190
|
+
|| (stats.mode & 0o777n) !== 0o700n
|
|
191
|
+
|| stats.dev.toString() !== expected.device
|
|
192
|
+
|| stats.ino.toString() !== expected.inode
|
|
193
|
+
) throw new Error("derivation proxy evidence directory changed identity");
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Return true only for an absent endpoint. An existing path must be the exact
|
|
198
|
+
* private socket shape before Ghostget will connect to it or consider fallback.
|
|
199
|
+
*/
|
|
200
|
+
function privateControlSocketIsAbsent(path: string): boolean {
|
|
201
|
+
try {
|
|
202
|
+
const stats = lstatSync(path, { bigint: true });
|
|
203
|
+
if (
|
|
204
|
+
!stats.isSocket()
|
|
205
|
+
|| stats.isSymbolicLink()
|
|
206
|
+
|| !currentUserOwns(stats.uid)
|
|
207
|
+
|| (stats.mode & 0o777n) !== 0o600n
|
|
208
|
+
) throw new Error("derivation proxy control path is unsafe");
|
|
209
|
+
return false;
|
|
210
|
+
} catch (error) {
|
|
211
|
+
if (
|
|
212
|
+
typeof error === "object"
|
|
213
|
+
&& error !== null
|
|
214
|
+
&& "code" in error
|
|
215
|
+
&& error.code === "ENOENT"
|
|
216
|
+
) return true;
|
|
217
|
+
throw error;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
type ProxyControlBinding = {
|
|
222
|
+
readonly controlNonce: string;
|
|
223
|
+
readonly policySha256: string;
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
function proxyControl(
|
|
227
|
+
socketPath: string,
|
|
228
|
+
derivationId: string,
|
|
229
|
+
command: "status" | "adopt" | "close",
|
|
230
|
+
binding: ProxyControlBinding,
|
|
231
|
+
): Promise<ProxyControlResponse> {
|
|
232
|
+
return new Promise((resolve, reject) => {
|
|
233
|
+
const socket: Socket = connectLocal({ path: socketPath });
|
|
234
|
+
const chunks: Buffer[] = [];
|
|
235
|
+
let total = 0;
|
|
236
|
+
let settled = false;
|
|
237
|
+
let connected = false;
|
|
238
|
+
const timer = setTimeout(() => fail(new Error("derivation proxy control timed out")), PROXY_CONTROL_TIMEOUT_MS);
|
|
239
|
+
const cleanup = (): void => {
|
|
240
|
+
clearTimeout(timer);
|
|
241
|
+
socket.off("connect", onConnect);
|
|
242
|
+
socket.off("data", onData);
|
|
243
|
+
socket.off("end", onEnd);
|
|
244
|
+
socket.off("error", onError);
|
|
245
|
+
};
|
|
246
|
+
const fail = (error: Error): void => {
|
|
247
|
+
if (settled) return;
|
|
248
|
+
settled = true;
|
|
249
|
+
cleanup();
|
|
250
|
+
socket.destroy();
|
|
251
|
+
reject(error);
|
|
252
|
+
};
|
|
253
|
+
const onConnect = (): void => {
|
|
254
|
+
connected = true;
|
|
255
|
+
socket.write(`${JSON.stringify({
|
|
256
|
+
schemaVersion: 1,
|
|
257
|
+
command,
|
|
258
|
+
derivationId,
|
|
259
|
+
nonce: binding.controlNonce,
|
|
260
|
+
policySha256: binding.policySha256,
|
|
261
|
+
})}\n`);
|
|
262
|
+
};
|
|
263
|
+
const onData = (chunk: Buffer): void => {
|
|
264
|
+
total += chunk.byteLength;
|
|
265
|
+
if (total > PROXY_CONTROL_MAX_BYTES) {
|
|
266
|
+
fail(new Error("derivation proxy control response is too large"));
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
chunks.push(Buffer.from(chunk));
|
|
270
|
+
};
|
|
271
|
+
const onEnd = (): void => {
|
|
272
|
+
if (settled) return;
|
|
273
|
+
settled = true;
|
|
274
|
+
cleanup();
|
|
275
|
+
try {
|
|
276
|
+
const text = new TextDecoder("utf-8", { fatal: true }).decode(
|
|
277
|
+
Buffer.concat(chunks, total),
|
|
278
|
+
);
|
|
279
|
+
resolve(parseControlResponse(JSON.parse(text) as unknown));
|
|
280
|
+
} catch (error) {
|
|
281
|
+
reject(error instanceof Error ? error : new Error("derivation proxy control failed"));
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
const onError = (error: Error): void => {
|
|
285
|
+
const code = "code" in error ? error.code : undefined;
|
|
286
|
+
if (!connected && (code === "ENOENT" || code === "ECONNREFUSED")) {
|
|
287
|
+
fail(new DerivationProxyControlUnavailableError(code, error));
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
fail(new Error("derivation proxy control failed", { cause: error }));
|
|
291
|
+
};
|
|
292
|
+
socket.once("connect", onConnect);
|
|
293
|
+
socket.on("data", onData);
|
|
294
|
+
socket.once("end", onEnd);
|
|
295
|
+
socket.once("error", onError);
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async function proxyControlAtBoundPath(input: {
|
|
300
|
+
readonly derivationId: string;
|
|
301
|
+
readonly command: "status" | "adopt" | "close";
|
|
302
|
+
readonly binding: ProxyControlBinding;
|
|
303
|
+
}): Promise<ProxyControlResponse> {
|
|
304
|
+
const path = derivationGuardControlSocketPath(input.derivationId);
|
|
305
|
+
if (privateControlSocketIsAbsent(path)) throw new DerivationProxyControlMissingError();
|
|
306
|
+
try {
|
|
307
|
+
const response = await proxyControl(
|
|
308
|
+
path,
|
|
309
|
+
input.derivationId,
|
|
310
|
+
input.command,
|
|
311
|
+
input.binding,
|
|
312
|
+
);
|
|
313
|
+
if (response.derivationId !== input.derivationId) {
|
|
314
|
+
throw new Error("derivation proxy control changed derivation identity");
|
|
315
|
+
}
|
|
316
|
+
return response;
|
|
317
|
+
} catch (error) {
|
|
318
|
+
// Only an absent/refused current endpoint permits the exact-owner cleanup
|
|
319
|
+
// migration. Malformed, foreign, reset, and timed-out responses preserve.
|
|
320
|
+
if (error instanceof DerivationProxyControlUnavailableError) {
|
|
321
|
+
const absent = privateControlSocketIsAbsent(path);
|
|
322
|
+
if (error.code === "ENOENT" && absent) {
|
|
323
|
+
throw new DerivationProxyControlMissingError();
|
|
324
|
+
}
|
|
325
|
+
if (absent) throw new DerivationProxyControlMissingError();
|
|
326
|
+
}
|
|
327
|
+
throw error;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async function waitForProxyOwnerQuiescence(
|
|
332
|
+
owner: ProcessOwnerIdentity,
|
|
333
|
+
inspectOwner: BoundaryDependencies["inspectOwner"],
|
|
334
|
+
monotonicNow: BoundaryDependencies["monotonicNow"] = () => performance.now(),
|
|
335
|
+
): Promise<void> {
|
|
336
|
+
const startedAt = monotonicNow();
|
|
337
|
+
if (!Number.isFinite(startedAt)) {
|
|
338
|
+
throw new Error("derivation network proxy cleanup monotonic clock is non-finite");
|
|
339
|
+
}
|
|
340
|
+
let previous = startedAt;
|
|
341
|
+
let waited = false;
|
|
342
|
+
for (;;) {
|
|
343
|
+
const status = inspectOwner(owner);
|
|
344
|
+
if (status === "different-or-dead") return;
|
|
345
|
+
const current = monotonicNow();
|
|
346
|
+
if (!Number.isFinite(current)) {
|
|
347
|
+
throw new Error("derivation network proxy cleanup monotonic clock is non-finite");
|
|
348
|
+
}
|
|
349
|
+
if (current < previous) {
|
|
350
|
+
throw new Error("derivation network proxy cleanup monotonic clock moved backward");
|
|
351
|
+
}
|
|
352
|
+
if (waited && current === previous) {
|
|
353
|
+
throw new Error("derivation network proxy cleanup monotonic clock did not advance");
|
|
354
|
+
}
|
|
355
|
+
previous = current;
|
|
356
|
+
if (current - startedAt >= PROXY_CONTROL_TIMEOUT_MS) {
|
|
357
|
+
throw new Error(
|
|
358
|
+
status === "unknown"
|
|
359
|
+
? "derivation network proxy cleanup cannot prove process quiescence"
|
|
360
|
+
: "derivation network proxy cleanup did not settle",
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
await Bun.sleep(25);
|
|
364
|
+
waited = true;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
async function signalBoundOwnerAtAbsentControl(input: {
|
|
369
|
+
readonly derivationId: string;
|
|
370
|
+
readonly owner: ProcessOwnerIdentity;
|
|
371
|
+
readonly dependencies?: Partial<BoundaryDependencies>;
|
|
372
|
+
}): Promise<void> {
|
|
373
|
+
const controlPath = derivationGuardControlSocketPath(input.derivationId);
|
|
374
|
+
if (!privateControlSocketIsAbsent(controlPath)) {
|
|
375
|
+
throw new Error("derivation proxy control endpoint could not be authenticated for cleanup");
|
|
376
|
+
}
|
|
377
|
+
const inspectOwner = input.dependencies?.inspectOwner ?? processOwnerStatus;
|
|
378
|
+
const initial = inspectOwner(input.owner);
|
|
379
|
+
if (initial === "different-or-dead") return;
|
|
380
|
+
if (initial !== "exact-live-owner") {
|
|
381
|
+
throw new Error("derivation network proxy owner cannot be verified for cleanup");
|
|
382
|
+
}
|
|
383
|
+
const rebound = inspectOwner(input.owner);
|
|
384
|
+
if (rebound === "different-or-dead") return;
|
|
385
|
+
if (rebound !== "exact-live-owner") {
|
|
386
|
+
throw new Error("derivation network proxy owner cannot be verified for cleanup");
|
|
387
|
+
}
|
|
388
|
+
if (!privateControlSocketIsAbsent(controlPath)) {
|
|
389
|
+
throw new Error("derivation proxy control endpoint appeared before cleanup");
|
|
390
|
+
}
|
|
391
|
+
try {
|
|
392
|
+
process.kill(input.owner.pid, "SIGTERM");
|
|
393
|
+
} catch (signalError) {
|
|
394
|
+
const afterSignal = inspectOwner(input.owner);
|
|
395
|
+
if (afterSignal === "different-or-dead") return;
|
|
396
|
+
throw new Error("derivation network proxy owner could not be terminated", {
|
|
397
|
+
cause: signalError,
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
await waitForProxyOwnerQuiescence(
|
|
401
|
+
input.owner,
|
|
402
|
+
inspectOwner,
|
|
403
|
+
input.dependencies?.monotonicNow,
|
|
404
|
+
);
|
|
405
|
+
if (!privateControlSocketIsAbsent(controlPath)) {
|
|
406
|
+
throw new Error("derivation proxy control endpoint appeared during cleanup");
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Recover an interrupted initialization only from its exact private config and
|
|
412
|
+
* readiness records. No caller-supplied PID is accepted by this boundary.
|
|
413
|
+
*/
|
|
414
|
+
export async function closeInterruptedDerivationNetworkBoundary(input: {
|
|
415
|
+
readonly derivationId: string;
|
|
416
|
+
readonly directory: string;
|
|
417
|
+
readonly directoryIdentity: GuardDirectoryIdentity;
|
|
418
|
+
readonly socketDirectory: string;
|
|
419
|
+
readonly socketIdentity: GuardDirectoryIdentity;
|
|
420
|
+
readonly dependencies?: Partial<BoundaryDependencies>;
|
|
421
|
+
}): Promise<ProcessOwnerIdentity | null> {
|
|
422
|
+
const controlPath = derivationGuardControlSocketPath(input.derivationId);
|
|
423
|
+
assertPrivateDirectoryIdentity(input.directory, input.directoryIdentity);
|
|
424
|
+
const readyPath = join(input.directory, DERIVATION_GUARD_PROXY_READY);
|
|
425
|
+
if (pathIsAbsent(readyPath)) {
|
|
426
|
+
assertPrivateDirectoryIdentity(input.directory, input.directoryIdentity);
|
|
427
|
+
if (!privateControlSocketIsAbsent(controlPath)) {
|
|
428
|
+
throw new Error("interrupted derivation has an unbound network control endpoint");
|
|
429
|
+
}
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const configPath = join(input.directory, DERIVATION_GUARD_PROXY_CONFIG);
|
|
434
|
+
const configRead = readGuardPrivateFile(configPath, 128 * 1024);
|
|
435
|
+
const readyRead = readGuardPrivateFile(readyPath, 64 * 1024);
|
|
436
|
+
let configValue: unknown;
|
|
437
|
+
let readyValue: unknown;
|
|
438
|
+
try {
|
|
439
|
+
configValue = JSON.parse(configRead.content) as unknown;
|
|
440
|
+
readyValue = JSON.parse(readyRead.content) as unknown;
|
|
441
|
+
} catch (error) {
|
|
442
|
+
throw new Error("interrupted derivation network evidence is malformed", { cause: error });
|
|
443
|
+
}
|
|
444
|
+
const config = parseProxyHelperConfig(configValue);
|
|
445
|
+
const ready = parseProxyHelperReady(readyValue);
|
|
446
|
+
if (
|
|
447
|
+
config.derivationId !== input.derivationId
|
|
448
|
+
|| ready.derivationId !== input.derivationId
|
|
449
|
+
|| config.directoryIdentity.device !== input.directoryIdentity.device
|
|
450
|
+
|| config.directoryIdentity.inode !== input.directoryIdentity.inode
|
|
451
|
+
|| config.socketDirectory !== input.socketDirectory
|
|
452
|
+
|| config.socketIdentity.device !== input.socketIdentity.device
|
|
453
|
+
|| config.socketIdentity.inode !== input.socketIdentity.inode
|
|
454
|
+
|| ready.policySha256 !== config.policySha256
|
|
455
|
+
) throw new Error("interrupted derivation network evidence changed identity");
|
|
456
|
+
verifyGuardPrivateFile(
|
|
457
|
+
configPath,
|
|
458
|
+
configRead.evidence,
|
|
459
|
+
proxyHelperConfigContent(config),
|
|
460
|
+
);
|
|
461
|
+
verifyGuardPrivateFile(
|
|
462
|
+
readyPath,
|
|
463
|
+
readyRead.evidence,
|
|
464
|
+
proxyHelperReadyContent(ready),
|
|
465
|
+
);
|
|
466
|
+
assertPrivateDirectoryIdentity(input.directory, input.directoryIdentity);
|
|
467
|
+
|
|
468
|
+
const inspectOwner = input.dependencies?.inspectOwner ?? processOwnerStatus;
|
|
469
|
+
let statusResponse: ProxyControlResponse;
|
|
470
|
+
try {
|
|
471
|
+
statusResponse = await proxyControlAtBoundPath({
|
|
472
|
+
derivationId: input.derivationId,
|
|
473
|
+
command: "status",
|
|
474
|
+
binding: config,
|
|
475
|
+
});
|
|
476
|
+
} catch (error) {
|
|
477
|
+
if (!(error instanceof DerivationProxyControlMissingError)) throw error;
|
|
478
|
+
await signalBoundOwnerAtAbsentControl({
|
|
479
|
+
derivationId: input.derivationId,
|
|
480
|
+
owner: ready.owner,
|
|
481
|
+
dependencies: { ...input.dependencies, inspectOwner },
|
|
482
|
+
});
|
|
483
|
+
return ready.owner;
|
|
484
|
+
}
|
|
485
|
+
if (
|
|
486
|
+
statusResponse.policySha256 !== ready.policySha256
|
|
487
|
+
|| statusResponse.port !== ready.port
|
|
488
|
+
|| !sameOwner(statusResponse.owner, ready.owner)
|
|
489
|
+
) throw new Error("interrupted derivation network status changed identity");
|
|
490
|
+
if (inspectOwner(ready.owner) !== "exact-live-owner") {
|
|
491
|
+
throw new Error("interrupted derivation network owner cannot be verified");
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
let closeResponse: ProxyControlResponse;
|
|
495
|
+
try {
|
|
496
|
+
closeResponse = await proxyControlAtBoundPath({
|
|
497
|
+
derivationId: input.derivationId,
|
|
498
|
+
command: "close",
|
|
499
|
+
binding: config,
|
|
500
|
+
});
|
|
501
|
+
} catch (error) {
|
|
502
|
+
if (!(error instanceof DerivationProxyControlMissingError)) throw error;
|
|
503
|
+
await signalBoundOwnerAtAbsentControl({
|
|
504
|
+
derivationId: input.derivationId,
|
|
505
|
+
owner: ready.owner,
|
|
506
|
+
dependencies: { ...input.dependencies, inspectOwner },
|
|
507
|
+
});
|
|
508
|
+
return ready.owner;
|
|
509
|
+
}
|
|
510
|
+
if (
|
|
511
|
+
closeResponse.policySha256 !== ready.policySha256
|
|
512
|
+
|| closeResponse.port !== ready.port
|
|
513
|
+
|| !sameOwner(closeResponse.owner, ready.owner)
|
|
514
|
+
) throw new Error("interrupted derivation network control changed identity");
|
|
515
|
+
await waitForProxyOwnerQuiescence(
|
|
516
|
+
ready.owner,
|
|
517
|
+
inspectOwner,
|
|
518
|
+
input.dependencies?.monotonicNow,
|
|
519
|
+
);
|
|
520
|
+
if (!privateControlSocketIsAbsent(controlPath)) {
|
|
521
|
+
throw new Error("interrupted derivation control endpoint remains after cleanup");
|
|
522
|
+
}
|
|
523
|
+
return ready.owner;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function expectedProxyConfig(input: {
|
|
527
|
+
readonly derivationId: string;
|
|
528
|
+
readonly directoryIdentity: GuardDirectoryIdentity;
|
|
529
|
+
readonly socketDirectory: string;
|
|
530
|
+
readonly socketIdentity: GuardDirectoryIdentity;
|
|
531
|
+
readonly browserDomains: readonly string[];
|
|
532
|
+
readonly parentOwner: ProcessOwnerIdentity;
|
|
533
|
+
readonly controlNonce: string;
|
|
534
|
+
readonly policySha256: string;
|
|
535
|
+
}): DerivationProxyHelperConfig {
|
|
536
|
+
return Object.freeze({
|
|
537
|
+
schemaVersion: 1,
|
|
538
|
+
kind: "wrench-derivation-proxy-config",
|
|
539
|
+
...input,
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
export async function createDerivationNetworkBoundary(input: {
|
|
544
|
+
readonly derivationId: string;
|
|
545
|
+
readonly directory: string;
|
|
546
|
+
readonly directoryIdentity: GuardDirectoryIdentity;
|
|
547
|
+
readonly socketDirectory: string;
|
|
548
|
+
readonly socketIdentity: GuardDirectoryIdentity;
|
|
549
|
+
readonly browserDomains: readonly string[];
|
|
550
|
+
readonly failHelperAtForTest?: DerivationProxyHelperFailureForTest;
|
|
551
|
+
}): Promise<DerivationNetworkGuard> {
|
|
552
|
+
derivationGuardControlSocketPath(input.derivationId);
|
|
553
|
+
if (input.failHelperAtForTest !== undefined && process.env.NODE_ENV !== "test") {
|
|
554
|
+
throw new Error("derivation proxy helper fault injection is available only in tests");
|
|
555
|
+
}
|
|
556
|
+
const extension = createDerivationGuardExtension(input.directory, input.browserDomains);
|
|
557
|
+
const policySha256 = derivationProxyPolicySha256(input.browserDomains);
|
|
558
|
+
const controlNonce = randomBytes(32).toString("hex");
|
|
559
|
+
const parentOwner = captureProcessOwnerIdentity(process.pid);
|
|
560
|
+
const config = expectedProxyConfig({
|
|
561
|
+
derivationId: input.derivationId,
|
|
562
|
+
directoryIdentity: input.directoryIdentity,
|
|
563
|
+
socketDirectory: input.socketDirectory,
|
|
564
|
+
socketIdentity: input.socketIdentity,
|
|
565
|
+
browserDomains: input.browserDomains,
|
|
566
|
+
parentOwner,
|
|
567
|
+
controlNonce,
|
|
568
|
+
policySha256,
|
|
569
|
+
});
|
|
570
|
+
const configFile = writeGuardPrivateFile(
|
|
571
|
+
join(input.directory, DERIVATION_GUARD_PROXY_CONFIG),
|
|
572
|
+
proxyHelperConfigContent(config),
|
|
573
|
+
);
|
|
574
|
+
const child = Bun.spawn([
|
|
575
|
+
process.execPath,
|
|
576
|
+
"--no-env-file",
|
|
577
|
+
"--no-install",
|
|
578
|
+
"--no-macros",
|
|
579
|
+
"--no-addons",
|
|
580
|
+
`--config=${trustedBunConfigPath}`,
|
|
581
|
+
proxyHelperPath,
|
|
582
|
+
], {
|
|
583
|
+
cwd: input.directory,
|
|
584
|
+
env: input.failHelperAtForTest === undefined
|
|
585
|
+
? { NODE_ENV: "production" }
|
|
586
|
+
: {
|
|
587
|
+
NODE_ENV: "test",
|
|
588
|
+
[helperFailureEnvironmentKey]: input.failHelperAtForTest,
|
|
589
|
+
},
|
|
590
|
+
stdin: "ignore",
|
|
591
|
+
stdout: "ignore",
|
|
592
|
+
stderr: "ignore",
|
|
593
|
+
detached: true,
|
|
594
|
+
});
|
|
595
|
+
const readyPath = join(input.directory, DERIVATION_GUARD_PROXY_READY);
|
|
596
|
+
const deadline = Date.now() + PROXY_START_TIMEOUT_MS;
|
|
597
|
+
let readyRead: ReturnType<typeof readGuardPrivateFile> | null = null;
|
|
598
|
+
try {
|
|
599
|
+
for (;;) {
|
|
600
|
+
if (existsSync(readyPath)) {
|
|
601
|
+
try {
|
|
602
|
+
readyRead = readGuardPrivateFile(readyPath, 64 * 1024);
|
|
603
|
+
break;
|
|
604
|
+
} catch {
|
|
605
|
+
// The helper creates the ready path with O_EXCL before the write
|
|
606
|
+
// finishes. Keep polling until the file is complete or the deadline.
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
const exit = await Promise.race([
|
|
610
|
+
child.exited.then((code) => ({ exited: true as const, code })),
|
|
611
|
+
Bun.sleep(25).then(() => ({ exited: false as const, code: 0 })),
|
|
612
|
+
]);
|
|
613
|
+
if (exit.exited) throw new Error("derivation proxy helper exited before readiness");
|
|
614
|
+
if (Date.now() >= deadline) throw new Error("derivation proxy helper readiness timed out");
|
|
615
|
+
}
|
|
616
|
+
const ready = parseProxyHelperReady(JSON.parse(readyRead.content) as unknown);
|
|
617
|
+
if (
|
|
618
|
+
ready.derivationId !== input.derivationId
|
|
619
|
+
|| ready.policySha256 !== policySha256
|
|
620
|
+
|| ready.owner.pid !== child.pid
|
|
621
|
+
|| processOwnerStatus(ready.owner) !== "exact-live-owner"
|
|
622
|
+
) throw new Error("derivation proxy helper readiness did not match its launch");
|
|
623
|
+
const guard: DerivationNetworkGuard = Object.freeze({
|
|
624
|
+
schemaVersion: 1,
|
|
625
|
+
kind: "contained-mv3-dnr-proxy",
|
|
626
|
+
extension,
|
|
627
|
+
proxy: Object.freeze({
|
|
628
|
+
policySha256,
|
|
629
|
+
controlNonce,
|
|
630
|
+
port: ready.port,
|
|
631
|
+
owner: ready.owner,
|
|
632
|
+
parentOwner,
|
|
633
|
+
configFile,
|
|
634
|
+
readyFile: readyRead.evidence,
|
|
635
|
+
}),
|
|
636
|
+
});
|
|
637
|
+
const response = await proxyControlAtBoundPath({
|
|
638
|
+
derivationId: input.derivationId,
|
|
639
|
+
command: "status",
|
|
640
|
+
binding: guard.proxy,
|
|
641
|
+
});
|
|
642
|
+
if (
|
|
643
|
+
response.policySha256 !== policySha256
|
|
644
|
+
|| response.port !== ready.port
|
|
645
|
+
|| !sameOwner(response.owner, ready.owner)
|
|
646
|
+
|| response.adopted
|
|
647
|
+
) throw new Error("derivation proxy helper control did not match readiness");
|
|
648
|
+
child.unref();
|
|
649
|
+
return guard;
|
|
650
|
+
} catch (error) {
|
|
651
|
+
const settlesWithin = async (milliseconds: number): Promise<boolean> => {
|
|
652
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
653
|
+
try {
|
|
654
|
+
return await Promise.race([
|
|
655
|
+
child.exited.then(() => true as const, () => true as const),
|
|
656
|
+
new Promise<false>((resolve) => {
|
|
657
|
+
timer = setTimeout(() => resolve(false), milliseconds);
|
|
658
|
+
}),
|
|
659
|
+
]);
|
|
660
|
+
} finally {
|
|
661
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
try {
|
|
665
|
+
process.kill(child.pid, "SIGTERM");
|
|
666
|
+
} catch {
|
|
667
|
+
// The helper may already have exited.
|
|
668
|
+
}
|
|
669
|
+
if (!await settlesWithin(2_000)) {
|
|
670
|
+
try {
|
|
671
|
+
process.kill(child.pid, "SIGKILL");
|
|
672
|
+
} catch {
|
|
673
|
+
// A concurrently exited helper is safe once child.exited settles.
|
|
674
|
+
}
|
|
675
|
+
if (!await settlesWithin(2_000)) {
|
|
676
|
+
child.unref();
|
|
677
|
+
throw new UnsafeDerivationNetworkBoundaryCleanupError(error);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
throw error;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/** Bind the helper to durable session metadata before the creating CLI exits. */
|
|
685
|
+
export async function adoptDerivationNetworkBoundary(input: {
|
|
686
|
+
readonly derivationId: string;
|
|
687
|
+
readonly guard: DerivationNetworkGuard;
|
|
688
|
+
}): Promise<void> {
|
|
689
|
+
derivationGuardControlSocketPath(input.derivationId);
|
|
690
|
+
assertGuardDerivationIdentity(input.guard, input.derivationId);
|
|
691
|
+
if (processOwnerStatus(input.guard.proxy.owner) !== "exact-live-owner") {
|
|
692
|
+
throw new Error("derivation network proxy helper is unavailable before adoption");
|
|
693
|
+
}
|
|
694
|
+
if (processOwnerStatus(input.guard.proxy.parentOwner) !== "exact-live-owner") {
|
|
695
|
+
throw new Error("derivation network proxy parent changed before adoption");
|
|
696
|
+
}
|
|
697
|
+
const response = await proxyControlAtBoundPath({
|
|
698
|
+
derivationId: input.derivationId,
|
|
699
|
+
command: "adopt",
|
|
700
|
+
binding: input.guard.proxy,
|
|
701
|
+
});
|
|
702
|
+
if (
|
|
703
|
+
response.policySha256 !== input.guard.proxy.policySha256
|
|
704
|
+
|| response.port !== input.guard.proxy.port
|
|
705
|
+
|| !sameOwner(response.owner, input.guard.proxy.owner)
|
|
706
|
+
|| !response.adopted
|
|
707
|
+
) throw new Error("derivation network proxy adoption changed identity");
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
function expectedReady(guard: DerivationNetworkGuard, derivationId: string) {
|
|
711
|
+
return {
|
|
712
|
+
schemaVersion: 1 as const,
|
|
713
|
+
kind: "wrench-derivation-proxy-ready" as const,
|
|
714
|
+
derivationId,
|
|
715
|
+
policySha256: guard.proxy.policySha256,
|
|
716
|
+
port: guard.proxy.port,
|
|
717
|
+
owner: guard.proxy.owner,
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
function assertGuardDerivationIdentity(
|
|
722
|
+
guard: DerivationNetworkGuard,
|
|
723
|
+
derivationId: string,
|
|
724
|
+
): void {
|
|
725
|
+
const content = proxyHelperReadyContent(expectedReady(guard, derivationId));
|
|
726
|
+
if (
|
|
727
|
+
Buffer.byteLength(content, "utf8") !== guard.proxy.readyFile.byteLength
|
|
728
|
+
|| sha256(content) !== guard.proxy.readyFile.sha256
|
|
729
|
+
) throw new Error("derivation network guard does not match its derivation ID");
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
export async function verifyDerivationNetworkBoundary(input: {
|
|
733
|
+
readonly derivationId: string;
|
|
734
|
+
readonly directory: string;
|
|
735
|
+
readonly directoryIdentity: GuardDirectoryIdentity;
|
|
736
|
+
readonly socketDirectory: string;
|
|
737
|
+
readonly socketIdentity: GuardDirectoryIdentity;
|
|
738
|
+
readonly browserDomains: readonly string[];
|
|
739
|
+
readonly guard: DerivationNetworkGuard;
|
|
740
|
+
readonly dependencies?: Partial<BoundaryDependencies>;
|
|
741
|
+
}): Promise<void> {
|
|
742
|
+
derivationGuardControlSocketPath(input.derivationId);
|
|
743
|
+
const inspectOwner = input.dependencies?.inspectOwner ?? processOwnerStatus;
|
|
744
|
+
assertGuardDerivationIdentity(input.guard, input.derivationId);
|
|
745
|
+
if (input.guard.proxy.policySha256 !== derivationProxyPolicySha256(input.browserDomains)) {
|
|
746
|
+
throw new Error("derivation network proxy policy changed");
|
|
747
|
+
}
|
|
748
|
+
verifyDerivationGuardExtension(input.directory, input.browserDomains, input.guard.extension);
|
|
749
|
+
const config = expectedProxyConfig({
|
|
750
|
+
derivationId: input.derivationId,
|
|
751
|
+
directoryIdentity: input.directoryIdentity,
|
|
752
|
+
socketDirectory: input.socketDirectory,
|
|
753
|
+
socketIdentity: input.socketIdentity,
|
|
754
|
+
browserDomains: input.browserDomains,
|
|
755
|
+
parentOwner: input.guard.proxy.parentOwner,
|
|
756
|
+
controlNonce: input.guard.proxy.controlNonce,
|
|
757
|
+
policySha256: input.guard.proxy.policySha256,
|
|
758
|
+
});
|
|
759
|
+
verifyGuardPrivateFile(
|
|
760
|
+
join(input.directory, DERIVATION_GUARD_PROXY_CONFIG),
|
|
761
|
+
input.guard.proxy.configFile,
|
|
762
|
+
proxyHelperConfigContent(config),
|
|
763
|
+
);
|
|
764
|
+
verifyGuardPrivateFile(
|
|
765
|
+
join(input.directory, DERIVATION_GUARD_PROXY_READY),
|
|
766
|
+
input.guard.proxy.readyFile,
|
|
767
|
+
proxyHelperReadyContent(expectedReady(input.guard, input.derivationId)),
|
|
768
|
+
);
|
|
769
|
+
if (inspectOwner(input.guard.proxy.owner) !== "exact-live-owner") {
|
|
770
|
+
throw new Error("derivation network proxy helper is not the exact live owner");
|
|
771
|
+
}
|
|
772
|
+
const response = await proxyControlAtBoundPath({
|
|
773
|
+
derivationId: input.derivationId,
|
|
774
|
+
command: "status",
|
|
775
|
+
binding: input.guard.proxy,
|
|
776
|
+
});
|
|
777
|
+
if (
|
|
778
|
+
response.policySha256 !== input.guard.proxy.policySha256
|
|
779
|
+
|| response.port !== input.guard.proxy.port
|
|
780
|
+
|| !sameOwner(response.owner, input.guard.proxy.owner)
|
|
781
|
+
|| !response.adopted
|
|
782
|
+
) throw new Error("derivation network proxy helper changed identity");
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
export async function closeDerivationNetworkBoundary(input: {
|
|
786
|
+
readonly derivationId: string;
|
|
787
|
+
readonly guard: DerivationNetworkGuard;
|
|
788
|
+
readonly dependencies?: Partial<BoundaryDependencies>;
|
|
789
|
+
}): Promise<void> {
|
|
790
|
+
const controlPath = derivationGuardControlSocketPath(input.derivationId);
|
|
791
|
+
const inspectOwner = input.dependencies?.inspectOwner ?? processOwnerStatus;
|
|
792
|
+
assertGuardDerivationIdentity(input.guard, input.derivationId);
|
|
793
|
+
const initial = inspectOwner(input.guard.proxy.owner);
|
|
794
|
+
if (initial === "different-or-dead") {
|
|
795
|
+
if (!privateControlSocketIsAbsent(controlPath)) {
|
|
796
|
+
throw new Error("derivation proxy control endpoint remains after its owner changed");
|
|
797
|
+
}
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
if (initial !== "exact-live-owner") {
|
|
801
|
+
throw new Error("derivation network proxy owner cannot be verified for cleanup");
|
|
802
|
+
}
|
|
803
|
+
let response: ProxyControlResponse;
|
|
804
|
+
try {
|
|
805
|
+
response = await proxyControlAtBoundPath({
|
|
806
|
+
derivationId: input.derivationId,
|
|
807
|
+
command: "close",
|
|
808
|
+
binding: input.guard.proxy,
|
|
809
|
+
});
|
|
810
|
+
} catch (error) {
|
|
811
|
+
if (!(error instanceof DerivationProxyControlMissingError)) throw error;
|
|
812
|
+
// Legacy schema-v1 sessions may retain an exact-live owner after their old
|
|
813
|
+
// agent-browser-owned control socket vanished. Never launch or speak the
|
|
814
|
+
// new protocol on that old path; terminate only the revalidated owner.
|
|
815
|
+
await signalBoundOwnerAtAbsentControl({
|
|
816
|
+
derivationId: input.derivationId,
|
|
817
|
+
owner: input.guard.proxy.owner,
|
|
818
|
+
dependencies: { ...input.dependencies, inspectOwner },
|
|
819
|
+
});
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
if (
|
|
823
|
+
response.policySha256 !== input.guard.proxy.policySha256
|
|
824
|
+
|| response.port !== input.guard.proxy.port
|
|
825
|
+
|| !sameOwner(response.owner, input.guard.proxy.owner)
|
|
826
|
+
) throw new Error("derivation network proxy close acknowledgement changed identity");
|
|
827
|
+
await waitForProxyOwnerQuiescence(
|
|
828
|
+
input.guard.proxy.owner,
|
|
829
|
+
inspectOwner,
|
|
830
|
+
input.dependencies?.monotonicNow,
|
|
831
|
+
);
|
|
832
|
+
if (!privateControlSocketIsAbsent(controlPath)) {
|
|
833
|
+
throw new Error("derivation proxy control endpoint remains after authenticated cleanup");
|
|
834
|
+
}
|
|
835
|
+
}
|