@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,1063 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { constants, type BigIntStats } from "node:fs";
|
|
3
|
+
import { lstat, open, realpath } from "node:fs/promises";
|
|
4
|
+
import { basename, isAbsolute, resolve } from "node:path";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
runProcess,
|
|
8
|
+
type CommandArgv,
|
|
9
|
+
type ProcessResult,
|
|
10
|
+
type ProcessSuccess,
|
|
11
|
+
type RunProcessOptions,
|
|
12
|
+
} from "./process";
|
|
13
|
+
import { compareUtf8 } from "./utf8-order";
|
|
14
|
+
|
|
15
|
+
export const RUNTIME_CLOSURE_PROFILE = "wrench-media-native-runtime-closure-v1" as const;
|
|
16
|
+
export const RUNTIME_TRACE_TIMEOUT_MS = 10_000;
|
|
17
|
+
export const MAX_RUNTIME_PROCESS_TIMEOUT_MS = 2 * 60 * 60 * 1_000;
|
|
18
|
+
export const MAX_RUNTIME_PROCESS_OUTPUT_BYTES = 16 * 1024 * 1024;
|
|
19
|
+
export const MAX_RUNTIME_TRACE_BYTES = 2 * 1024 * 1024;
|
|
20
|
+
export const MAX_RUNTIME_DEPENDENCIES = 256;
|
|
21
|
+
export const MAX_RUNTIME_DEPENDENCY_BYTES = 1024 * 1024 * 1024;
|
|
22
|
+
export const MAX_RUNTIME_CLOSURE_BYTES = 4 * 1024 * 1024 * 1024;
|
|
23
|
+
export const MAX_RUNTIME_EXECUTABLE_BYTES = 512 * 1024 * 1024;
|
|
24
|
+
|
|
25
|
+
const FILE_READ_CHUNK_BYTES = 1024 * 1024;
|
|
26
|
+
const MAX_PATH_CODE_UNITS = 4_096;
|
|
27
|
+
const SHA256_PATTERN = /^[0-9a-f]{64}$/u;
|
|
28
|
+
const DARWIN_IMAGE_PATTERN = /^dyld\[\d+\]: <[0-9A-Fa-f-]{36}> (\/.*)$/u;
|
|
29
|
+
const DARWIN_LOADED_IMAGE_PATTERN = /^dyld(?:\[\d+\])?: loaded: (\/.*)$/u;
|
|
30
|
+
const DARWIN_PREFIX_PATTERN = /^dyld(?:\[\d+\])?: /u;
|
|
31
|
+
const DARWIN_DELAYED_PATTERN = /^dyld\[\d+\]: move loaded to delayed: [^\r\n]*$/u;
|
|
32
|
+
const GLIBC_PREFIX_PATTERN = /^\s*\d+:\s?(.*)$/u;
|
|
33
|
+
const GLIBC_FILE_PATTERN = /^file=(.+?)\s+\[\d+\];(?:\s+(?:needed by|dynamically loaded by)\s+(.+?)\s+\[\d+\])?.*$/u;
|
|
34
|
+
const GLIBC_OPEN_CLOSE_PATTERN = /^(?:opening|closing) file=(.+?)\s+\[\d+\].*$/u;
|
|
35
|
+
const GLIBC_PHASE_PATH_PATTERN = /^(?:calling init|calling fini|initialize program|transferring control):\s*(.*?)\s*$/u;
|
|
36
|
+
const GLIBC_IGNORABLE_PATTERN = /^(?:dynamic:|entry:|phdr:|activating NODELETE|destroying link map|scope \d+:|object=|runtime linker statistics:)/u;
|
|
37
|
+
const GLIBC_FIND_PATTERN = /^find library=([^\s]+)\s+\[\d+\];\s*searching\s*$/u;
|
|
38
|
+
const GLIBC_SEARCH_PATTERN = /^(?:search path=|search cache=)/u;
|
|
39
|
+
const GLIBC_TRY_PATTERN = /^trying file=(\/.*)$/u;
|
|
40
|
+
const GLIBC_SYNTHETIC_NAMES = new Set(["linux-gate.so.1", "linux-vdso.so.1"]);
|
|
41
|
+
const MINIMAL_RUNTIME_ENVIRONMENT = Object.freeze({
|
|
42
|
+
LANG: "C",
|
|
43
|
+
LC_ALL: "C",
|
|
44
|
+
TZ: "UTC",
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const LINUX_PLATFORM_RUNTIME_PATTERNS = [
|
|
48
|
+
/^ld-(?:linux|musl)[^/]*\.so(?:\..*)?$/u,
|
|
49
|
+
/^ld64\.so(?:\..*)?$/u,
|
|
50
|
+
/^libc\.so(?:\..*)?$/u,
|
|
51
|
+
/^libm\.so(?:\..*)?$/u,
|
|
52
|
+
/^libpthread\.so(?:\..*)?$/u,
|
|
53
|
+
/^libdl\.so(?:\..*)?$/u,
|
|
54
|
+
/^librt\.so(?:\..*)?$/u,
|
|
55
|
+
/^libutil\.so(?:\..*)?$/u,
|
|
56
|
+
/^libresolv\.so(?:\..*)?$/u,
|
|
57
|
+
/^libstdc\+\+\.so(?:\..*)?$/u,
|
|
58
|
+
/^libgcc_s\.so(?:\..*)?$/u,
|
|
59
|
+
/^libc\+\+\.so(?:\..*)?$/u,
|
|
60
|
+
/^libc\+\+abi\.so(?:\..*)?$/u,
|
|
61
|
+
/^libunwind\.so(?:\..*)?$/u,
|
|
62
|
+
] as const;
|
|
63
|
+
|
|
64
|
+
export type RuntimeClosurePlatform = "darwin" | "linux";
|
|
65
|
+
|
|
66
|
+
export type RuntimeClosureErrorCode =
|
|
67
|
+
| "UNSUPPORTED_PLATFORM"
|
|
68
|
+
| "INVALID_EXECUTABLE"
|
|
69
|
+
| "INVALID_EXECUTABLE_HASH"
|
|
70
|
+
| "EXECUTABLE_HASH_MISMATCH"
|
|
71
|
+
| "EXECUTABLE_UNSTABLE"
|
|
72
|
+
| "INVALID_PROCESS_LIMIT"
|
|
73
|
+
| "TRACE_PROCESS_FAILED"
|
|
74
|
+
| "TRACE_TOO_LARGE"
|
|
75
|
+
| "TRACE_MISSING"
|
|
76
|
+
| "TRACE_MALFORMED"
|
|
77
|
+
| "TRACE_UNRESOLVED"
|
|
78
|
+
| "DEPENDENCY_LIMIT"
|
|
79
|
+
| "DEPENDENCY_INVALID"
|
|
80
|
+
| "DEPENDENCY_MISSING"
|
|
81
|
+
| "DEPENDENCY_TOO_LARGE"
|
|
82
|
+
| "DEPENDENCY_UNSTABLE"
|
|
83
|
+
| "DEPENDENCY_AMBIGUOUS";
|
|
84
|
+
|
|
85
|
+
export class RuntimeClosureError extends Error {
|
|
86
|
+
readonly code: RuntimeClosureErrorCode;
|
|
87
|
+
|
|
88
|
+
constructor(code: RuntimeClosureErrorCode, message: string) {
|
|
89
|
+
super(message);
|
|
90
|
+
this.name = "RuntimeClosureError";
|
|
91
|
+
this.code = code;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export type RuntimeTraceEvidence = "dynamic-loader";
|
|
96
|
+
|
|
97
|
+
export type RuntimeTraceParseResult =
|
|
98
|
+
| Readonly<{
|
|
99
|
+
ok: true;
|
|
100
|
+
evidence: RuntimeTraceEvidence;
|
|
101
|
+
loadedPaths: readonly string[];
|
|
102
|
+
}>
|
|
103
|
+
| Readonly<{
|
|
104
|
+
ok: false;
|
|
105
|
+
code:
|
|
106
|
+
| "TRACE_MISSING"
|
|
107
|
+
| "TRACE_MALFORMED"
|
|
108
|
+
| "TRACE_UNRESOLVED";
|
|
109
|
+
message: string;
|
|
110
|
+
}>;
|
|
111
|
+
|
|
112
|
+
export type RuntimeClosureDependency = Readonly<{
|
|
113
|
+
physicalPath: string;
|
|
114
|
+
logicalName: string;
|
|
115
|
+
sha256: string;
|
|
116
|
+
bytes: number;
|
|
117
|
+
}>;
|
|
118
|
+
|
|
119
|
+
export type RuntimeClosureDigestDependency = Readonly<
|
|
120
|
+
Pick<RuntimeClosureDependency, "logicalName" | "sha256" | "bytes">
|
|
121
|
+
>;
|
|
122
|
+
|
|
123
|
+
export type RuntimeClosureRecord = Readonly<{
|
|
124
|
+
profile: typeof RUNTIME_CLOSURE_PROFILE;
|
|
125
|
+
platform: RuntimeClosurePlatform;
|
|
126
|
+
evidence: RuntimeTraceEvidence;
|
|
127
|
+
executableSha256: string;
|
|
128
|
+
closureSha256: string;
|
|
129
|
+
dependencyCount: number;
|
|
130
|
+
dependencyBytes: number;
|
|
131
|
+
dependencies: readonly RuntimeClosureDependency[];
|
|
132
|
+
}>;
|
|
133
|
+
|
|
134
|
+
export type RuntimeClosureAttestation = RuntimeClosureRecord;
|
|
135
|
+
|
|
136
|
+
export type ParseRuntimeClosureRecordResult =
|
|
137
|
+
| Readonly<{ ok: true; record: RuntimeClosureRecord }>
|
|
138
|
+
| Readonly<{ ok: false; message: string }>;
|
|
139
|
+
|
|
140
|
+
export type AttestRuntimeClosureOptions = Readonly<{
|
|
141
|
+
executablePath: string;
|
|
142
|
+
executableSha256: string;
|
|
143
|
+
signal?: AbortSignal;
|
|
144
|
+
platform?: NodeJS.Platform;
|
|
145
|
+
probeArguments?: readonly string[];
|
|
146
|
+
timeoutMs?: number;
|
|
147
|
+
maxStdoutBytes?: number;
|
|
148
|
+
maxStderrBytes?: number;
|
|
149
|
+
}>;
|
|
150
|
+
|
|
151
|
+
export type AttestedRuntimeProcessResult = Readonly<{
|
|
152
|
+
process: ProcessSuccess;
|
|
153
|
+
attestation: RuntimeClosureAttestation;
|
|
154
|
+
}>;
|
|
155
|
+
|
|
156
|
+
export type RuntimeClosureDependencies = Readonly<{
|
|
157
|
+
runProcess: (argv: CommandArgv, options: RunProcessOptions) => Promise<ProcessResult>;
|
|
158
|
+
}>;
|
|
159
|
+
|
|
160
|
+
interface FileIdentity {
|
|
161
|
+
readonly dev: bigint;
|
|
162
|
+
readonly ino: bigint;
|
|
163
|
+
readonly size: bigint;
|
|
164
|
+
readonly mode: bigint;
|
|
165
|
+
readonly mtimeNs: bigint;
|
|
166
|
+
readonly ctimeNs: bigint;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
type VerifiedDependency = Readonly<{
|
|
170
|
+
requestedPath: string;
|
|
171
|
+
physicalPath: string;
|
|
172
|
+
logicalName: string;
|
|
173
|
+
sha256: string;
|
|
174
|
+
bytes: number;
|
|
175
|
+
dev: bigint;
|
|
176
|
+
ino: bigint;
|
|
177
|
+
}>;
|
|
178
|
+
|
|
179
|
+
type VerifiedExecutable = Readonly<{
|
|
180
|
+
physicalPath: string;
|
|
181
|
+
sha256: string;
|
|
182
|
+
identity: FileIdentity;
|
|
183
|
+
}>;
|
|
184
|
+
|
|
185
|
+
const defaultDependencies: RuntimeClosureDependencies = {
|
|
186
|
+
runProcess: (argv, options) => runProcess(argv, options),
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
function hasUnsafeControlCharacter(value: string, allowTraceWhitespace = false): boolean {
|
|
190
|
+
for (const character of value) {
|
|
191
|
+
const point = character.codePointAt(0);
|
|
192
|
+
if (point === undefined) continue;
|
|
193
|
+
if (allowTraceWhitespace && (point === 0x09 || point === 0x0a || point === 0x0d)) {
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (point <= 0x1f || (point >= 0x7f && point <= 0x9f)) return true;
|
|
197
|
+
}
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function isSafeAbsolutePath(value: string): boolean {
|
|
202
|
+
return value.length > 0
|
|
203
|
+
&& value.length <= MAX_PATH_CODE_UNITS
|
|
204
|
+
&& !hasUnsafeControlCharacter(value)
|
|
205
|
+
&& isAbsolute(value)
|
|
206
|
+
&& resolve(value) === value;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Builds the complete environment used by capability probing and inference.
|
|
211
|
+
* No inherited value can affect backend selection, numerics, loading, or output.
|
|
212
|
+
*/
|
|
213
|
+
export function buildWhisperRuntimeEnvironment(
|
|
214
|
+
): Readonly<Record<string, string>> {
|
|
215
|
+
return MINIMAL_RUNTIME_ENVIRONMENT;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function buildRuntimeTraceEnvironment(
|
|
219
|
+
platform: RuntimeClosurePlatform,
|
|
220
|
+
): Readonly<Record<string, string>> {
|
|
221
|
+
const traceEnvironment: Record<string, string> = {
|
|
222
|
+
...buildWhisperRuntimeEnvironment(),
|
|
223
|
+
};
|
|
224
|
+
if (platform === "darwin") {
|
|
225
|
+
traceEnvironment.DYLD_PRINT_LIBRARIES = "1";
|
|
226
|
+
} else {
|
|
227
|
+
traceEnvironment.LD_DEBUG = "files";
|
|
228
|
+
}
|
|
229
|
+
return Object.freeze(traceEnvironment);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function parseDarwinTrace(
|
|
233
|
+
trace: string,
|
|
234
|
+
executablePath: string,
|
|
235
|
+
): RuntimeTraceParseResult {
|
|
236
|
+
const loadedPaths = new Set<string>();
|
|
237
|
+
let sawTraceLine = false;
|
|
238
|
+
let sawExecutable = false;
|
|
239
|
+
|
|
240
|
+
for (const rawLine of trace.split("\n")) {
|
|
241
|
+
const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
|
|
242
|
+
const currentImage = DARWIN_IMAGE_PATTERN.exec(line) ?? DARWIN_LOADED_IMAGE_PATTERN.exec(line);
|
|
243
|
+
if (currentImage !== null) {
|
|
244
|
+
sawTraceLine = true;
|
|
245
|
+
const path = currentImage[1];
|
|
246
|
+
if (path === undefined || !isSafeAbsolutePath(path)) {
|
|
247
|
+
return traceFailure("TRACE_MALFORMED", "The Darwin loader trace contains an unsafe image path.");
|
|
248
|
+
}
|
|
249
|
+
loadedPaths.add(path);
|
|
250
|
+
if (path === executablePath) sawExecutable = true;
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
if (DARWIN_DELAYED_PATTERN.test(line)) {
|
|
254
|
+
sawTraceLine = true;
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
if (DARWIN_PREFIX_PATTERN.test(line)) {
|
|
258
|
+
return traceFailure("TRACE_MALFORMED", "The Darwin loader trace contains an unknown record.");
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (!sawTraceLine) {
|
|
263
|
+
return traceFailure("TRACE_MISSING", "The Darwin loader did not produce trace evidence.");
|
|
264
|
+
}
|
|
265
|
+
if (!sawExecutable) {
|
|
266
|
+
return traceFailure("TRACE_MALFORMED", "The Darwin loader trace does not identify the executable.");
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
ok: true,
|
|
270
|
+
evidence: "dynamic-loader",
|
|
271
|
+
loadedPaths: [...loadedPaths].sort(),
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function parseGlibcTrace(
|
|
276
|
+
trace: string,
|
|
277
|
+
executablePath: string,
|
|
278
|
+
): RuntimeTraceParseResult {
|
|
279
|
+
const loadedPaths = new Set<string>();
|
|
280
|
+
const unresolvedLogicalNames = new Set<string>();
|
|
281
|
+
let sawTraceLine = false;
|
|
282
|
+
let sawExecutable = false;
|
|
283
|
+
let searchedLogicalName: string | null = null;
|
|
284
|
+
let lastSearchCandidate: string | null = null;
|
|
285
|
+
|
|
286
|
+
for (const rawLine of trace.split("\n")) {
|
|
287
|
+
const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
|
|
288
|
+
const prefixed = GLIBC_PREFIX_PATTERN.exec(line);
|
|
289
|
+
if (prefixed === null) continue;
|
|
290
|
+
sawTraceLine = true;
|
|
291
|
+
const body = (prefixed[1] ?? "").trimStart();
|
|
292
|
+
if (body.length === 0) continue;
|
|
293
|
+
if (/not found|cannot open shared object file|error while loading shared libraries/iu.test(body)) {
|
|
294
|
+
return traceFailure("TRACE_UNRESOLVED", "The glibc loader reported an unresolved image.");
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const findRecord = GLIBC_FIND_PATTERN.exec(body);
|
|
298
|
+
if (findRecord !== null) {
|
|
299
|
+
const logicalName = findRecord[1];
|
|
300
|
+
if (logicalName === undefined
|
|
301
|
+
|| logicalName.includes("/")
|
|
302
|
+
|| hasUnsafeControlCharacter(logicalName)) {
|
|
303
|
+
return traceFailure("TRACE_MALFORMED", "The glibc loader trace contains an unsafe search record.");
|
|
304
|
+
}
|
|
305
|
+
searchedLogicalName = logicalName;
|
|
306
|
+
lastSearchCandidate = null;
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
if (GLIBC_SEARCH_PATTERN.test(body)) continue;
|
|
310
|
+
const tryRecord = GLIBC_TRY_PATTERN.exec(body);
|
|
311
|
+
if (tryRecord !== null) {
|
|
312
|
+
const candidate = tryRecord[1];
|
|
313
|
+
if (candidate === undefined || !isSafeAbsolutePath(candidate)) {
|
|
314
|
+
return traceFailure("TRACE_MALFORMED", "The glibc loader trace contains an unsafe search candidate.");
|
|
315
|
+
}
|
|
316
|
+
if (searchedLogicalName !== null && basename(candidate) === searchedLogicalName) {
|
|
317
|
+
lastSearchCandidate = candidate;
|
|
318
|
+
}
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const fileRecord = GLIBC_FILE_PATTERN.exec(body);
|
|
323
|
+
if (fileRecord !== null) {
|
|
324
|
+
const file = fileRecord[1];
|
|
325
|
+
const owner = fileRecord[2];
|
|
326
|
+
if (file === undefined || !recordGlibcFile(file, loadedPaths, unresolvedLogicalNames)) {
|
|
327
|
+
return traceFailure("TRACE_MALFORMED", "The glibc loader trace contains an unsafe file record.");
|
|
328
|
+
}
|
|
329
|
+
const logicalFile = stripGlibcIndex(file);
|
|
330
|
+
if (!isAbsolute(logicalFile)
|
|
331
|
+
&& searchedLogicalName === logicalFile
|
|
332
|
+
&& lastSearchCandidate !== null) {
|
|
333
|
+
loadedPaths.add(lastSearchCandidate);
|
|
334
|
+
searchedLogicalName = null;
|
|
335
|
+
lastSearchCandidate = null;
|
|
336
|
+
}
|
|
337
|
+
if (owner !== undefined) {
|
|
338
|
+
const ownerResult = recordGlibcOwner(owner, executablePath, loadedPaths);
|
|
339
|
+
if (ownerResult === "invalid") {
|
|
340
|
+
return traceFailure("TRACE_MALFORMED", "The glibc loader trace contains an unsafe owner record.");
|
|
341
|
+
}
|
|
342
|
+
if (ownerResult === "executable") sawExecutable = true;
|
|
343
|
+
}
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const openCloseRecord = GLIBC_OPEN_CLOSE_PATTERN.exec(body);
|
|
348
|
+
if (openCloseRecord !== null) {
|
|
349
|
+
const path = openCloseRecord[1];
|
|
350
|
+
if (path === undefined || !recordGlibcFile(path, loadedPaths, unresolvedLogicalNames)) {
|
|
351
|
+
return traceFailure("TRACE_MALFORMED", "The glibc loader trace contains an unsafe open record.");
|
|
352
|
+
}
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const phaseRecord = GLIBC_PHASE_PATH_PATTERN.exec(body);
|
|
357
|
+
if (phaseRecord !== null) {
|
|
358
|
+
const phasePath = stripGlibcIndex(phaseRecord[1] ?? "");
|
|
359
|
+
if (phasePath.length === 0) continue;
|
|
360
|
+
if (!isSafeAbsolutePath(phasePath)) {
|
|
361
|
+
return traceFailure("TRACE_MALFORMED", "The glibc loader trace contains an unsafe phase path.");
|
|
362
|
+
}
|
|
363
|
+
loadedPaths.add(phasePath);
|
|
364
|
+
if (phasePath === executablePath) sawExecutable = true;
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (GLIBC_IGNORABLE_PATTERN.test(body)) continue;
|
|
369
|
+
if (body.includes("file=") || /(?:^|\s)\//u.test(body)) {
|
|
370
|
+
return traceFailure("TRACE_MALFORMED", "The glibc loader trace contains an unknown path record.");
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (!sawTraceLine) {
|
|
375
|
+
return traceFailure("TRACE_MISSING", "The glibc loader did not produce trace evidence.");
|
|
376
|
+
}
|
|
377
|
+
if (!sawExecutable) {
|
|
378
|
+
return traceFailure("TRACE_MALFORMED", "The glibc loader trace does not identify the executable.");
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const resolvedNames = new Set([...loadedPaths].map((path) => basename(path)));
|
|
382
|
+
for (const logicalName of unresolvedLogicalNames) {
|
|
383
|
+
if (!resolvedNames.has(logicalName)) {
|
|
384
|
+
return traceFailure("TRACE_UNRESOLVED", "The glibc loader trace leaves an image unresolved.");
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return {
|
|
389
|
+
ok: true,
|
|
390
|
+
evidence: "dynamic-loader",
|
|
391
|
+
loadedPaths: [...loadedPaths].sort(),
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function recordGlibcFile(
|
|
396
|
+
value: string,
|
|
397
|
+
loadedPaths: Set<string>,
|
|
398
|
+
unresolvedLogicalNames: Set<string>,
|
|
399
|
+
): boolean {
|
|
400
|
+
const path = stripGlibcIndex(value);
|
|
401
|
+
if (path.length === 0 || hasUnsafeControlCharacter(path)) return false;
|
|
402
|
+
if (isAbsolute(path)) {
|
|
403
|
+
if (!isSafeAbsolutePath(path)) return false;
|
|
404
|
+
loadedPaths.add(path);
|
|
405
|
+
return true;
|
|
406
|
+
}
|
|
407
|
+
if (path.includes("/") || path === "." || path === "..") return false;
|
|
408
|
+
if (!GLIBC_SYNTHETIC_NAMES.has(path)) unresolvedLogicalNames.add(path);
|
|
409
|
+
return true;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function recordGlibcOwner(
|
|
413
|
+
value: string,
|
|
414
|
+
executablePath: string,
|
|
415
|
+
loadedPaths: Set<string>,
|
|
416
|
+
): "dependency" | "executable" | "invalid" {
|
|
417
|
+
const path = stripGlibcIndex(value);
|
|
418
|
+
if (!isSafeAbsolutePath(path)) return "invalid";
|
|
419
|
+
loadedPaths.add(path);
|
|
420
|
+
return path === executablePath ? "executable" : "dependency";
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function stripGlibcIndex(value: string): string {
|
|
424
|
+
return value.replace(/\s+\[\d+\]$/u, "");
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function traceFailure(
|
|
428
|
+
code: Extract<RuntimeClosureErrorCode,
|
|
429
|
+
"TRACE_MISSING" | "TRACE_MALFORMED" | "TRACE_UNRESOLVED">,
|
|
430
|
+
message: string,
|
|
431
|
+
): RuntimeTraceParseResult {
|
|
432
|
+
return { ok: false, code, message };
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export function parseRuntimeLoaderTrace(
|
|
436
|
+
trace: string,
|
|
437
|
+
options: Readonly<{
|
|
438
|
+
platform: RuntimeClosurePlatform;
|
|
439
|
+
executablePath: string;
|
|
440
|
+
}>,
|
|
441
|
+
): RuntimeTraceParseResult {
|
|
442
|
+
if (Buffer.byteLength(trace, "utf8") > MAX_RUNTIME_TRACE_BYTES) {
|
|
443
|
+
return traceFailure("TRACE_MALFORMED", "The loader trace exceeds Ghostget media's parse limit.");
|
|
444
|
+
}
|
|
445
|
+
if (hasUnsafeControlCharacter(trace, true) || !isSafeAbsolutePath(options.executablePath)) {
|
|
446
|
+
return traceFailure("TRACE_MALFORMED", "The loader trace contains unsafe input.");
|
|
447
|
+
}
|
|
448
|
+
return options.platform === "darwin"
|
|
449
|
+
? parseDarwinTrace(trace, options.executablePath)
|
|
450
|
+
: parseGlibcTrace(trace, options.executablePath);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
export function isPlatformOwnedRuntimePath(
|
|
454
|
+
path: string,
|
|
455
|
+
platform: RuntimeClosurePlatform,
|
|
456
|
+
): boolean {
|
|
457
|
+
if (!isSafeAbsolutePath(path)) return false;
|
|
458
|
+
if (platform === "darwin") {
|
|
459
|
+
return path.startsWith("/usr/lib/")
|
|
460
|
+
|| path.startsWith("/System/Library/")
|
|
461
|
+
|| path.startsWith("/System/iOSSupport/usr/lib/")
|
|
462
|
+
|| path.startsWith("/System/iOSSupport/System/Library/")
|
|
463
|
+
|| path.startsWith("/Library/Apple/System/Library/");
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const inPlatformDirectory = path.startsWith("/lib/")
|
|
467
|
+
|| path.startsWith("/lib64/")
|
|
468
|
+
|| path.startsWith("/usr/lib/")
|
|
469
|
+
|| path.startsWith("/usr/lib64/");
|
|
470
|
+
if (!inPlatformDirectory) return false;
|
|
471
|
+
const name = basename(path);
|
|
472
|
+
return LINUX_PLATFORM_RUNTIME_PATTERNS.some((pattern) => pattern.test(name));
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function fileIdentity(metadata: BigIntStats): FileIdentity {
|
|
476
|
+
return {
|
|
477
|
+
dev: metadata.dev,
|
|
478
|
+
ino: metadata.ino,
|
|
479
|
+
size: metadata.size,
|
|
480
|
+
mode: metadata.mode,
|
|
481
|
+
mtimeNs: metadata.mtimeNs,
|
|
482
|
+
ctimeNs: metadata.ctimeNs,
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function sameFileIdentity(left: FileIdentity, right: FileIdentity): boolean {
|
|
487
|
+
return left.dev === right.dev
|
|
488
|
+
&& left.ino === right.ino
|
|
489
|
+
&& left.size === right.size
|
|
490
|
+
&& left.mode === right.mode
|
|
491
|
+
&& left.mtimeNs === right.mtimeNs
|
|
492
|
+
&& left.ctimeNs === right.ctimeNs;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
async function hashFileHandle(
|
|
496
|
+
handle: Awaited<ReturnType<typeof open>>,
|
|
497
|
+
bytes: number,
|
|
498
|
+
unstableCode: "DEPENDENCY_UNSTABLE" | "EXECUTABLE_UNSTABLE",
|
|
499
|
+
): Promise<string> {
|
|
500
|
+
const digest = createHash("sha256");
|
|
501
|
+
const chunk = Buffer.allocUnsafe(Math.min(FILE_READ_CHUNK_BYTES, Math.max(1, bytes)));
|
|
502
|
+
let offset = 0;
|
|
503
|
+
while (offset < bytes) {
|
|
504
|
+
const length = Math.min(chunk.byteLength, bytes - offset);
|
|
505
|
+
const read = await handle.read(chunk, 0, length, offset);
|
|
506
|
+
if (read.bytesRead !== length) {
|
|
507
|
+
throw new RuntimeClosureError(
|
|
508
|
+
unstableCode,
|
|
509
|
+
"A runtime file changed while it was being hashed.",
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
digest.update(chunk.subarray(0, read.bytesRead));
|
|
513
|
+
offset += read.bytesRead;
|
|
514
|
+
}
|
|
515
|
+
return digest.digest("hex");
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
async function verifyExecutableSnapshot(requestedPath: string): Promise<VerifiedExecutable> {
|
|
519
|
+
if (!isSafeAbsolutePath(requestedPath)) {
|
|
520
|
+
throw new RuntimeClosureError("INVALID_EXECUTABLE", "The executable path is invalid.");
|
|
521
|
+
}
|
|
522
|
+
let physicalPath: string;
|
|
523
|
+
try {
|
|
524
|
+
physicalPath = await realpath(requestedPath);
|
|
525
|
+
} catch {
|
|
526
|
+
throw new RuntimeClosureError("INVALID_EXECUTABLE", "The executable is missing.");
|
|
527
|
+
}
|
|
528
|
+
if (!isSafeAbsolutePath(physicalPath)) {
|
|
529
|
+
throw new RuntimeClosureError("INVALID_EXECUTABLE", "The executable target is invalid.");
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
let handle: Awaited<ReturnType<typeof open>> | null = null;
|
|
533
|
+
try {
|
|
534
|
+
const pathMetadata = await lstat(physicalPath, { bigint: true });
|
|
535
|
+
if (!pathMetadata.isFile() || pathMetadata.isSymbolicLink()) {
|
|
536
|
+
throw new RuntimeClosureError("INVALID_EXECUTABLE", "The executable is not a physical file.");
|
|
537
|
+
}
|
|
538
|
+
handle = await open(
|
|
539
|
+
physicalPath,
|
|
540
|
+
constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK,
|
|
541
|
+
);
|
|
542
|
+
const beforeMetadata = await handle.stat({ bigint: true });
|
|
543
|
+
if (!beforeMetadata.isFile()
|
|
544
|
+
|| !sameFileIdentity(fileIdentity(pathMetadata), fileIdentity(beforeMetadata))) {
|
|
545
|
+
throw new RuntimeClosureError(
|
|
546
|
+
"EXECUTABLE_UNSTABLE",
|
|
547
|
+
"The executable changed before it could be opened.",
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
if (beforeMetadata.size < 0n
|
|
551
|
+
|| beforeMetadata.size > BigInt(MAX_RUNTIME_EXECUTABLE_BYTES)) {
|
|
552
|
+
throw new RuntimeClosureError(
|
|
553
|
+
"INVALID_EXECUTABLE",
|
|
554
|
+
"The executable exceeds Ghostget media's runtime attestation limit.",
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
const bytes = Number(beforeMetadata.size);
|
|
558
|
+
const sha256 = await hashFileHandle(handle, bytes, "EXECUTABLE_UNSTABLE");
|
|
559
|
+
const afterMetadata = await handle.stat({ bigint: true });
|
|
560
|
+
const afterPathMetadata = await lstat(physicalPath, { bigint: true });
|
|
561
|
+
const repeatedPhysicalPath = await realpath(requestedPath);
|
|
562
|
+
const identity = fileIdentity(beforeMetadata);
|
|
563
|
+
if (!sameFileIdentity(identity, fileIdentity(afterMetadata))
|
|
564
|
+
|| !sameFileIdentity(identity, fileIdentity(afterPathMetadata))
|
|
565
|
+
|| repeatedPhysicalPath !== physicalPath) {
|
|
566
|
+
throw new RuntimeClosureError(
|
|
567
|
+
"EXECUTABLE_UNSTABLE",
|
|
568
|
+
"The executable changed while it was being verified.",
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
return { physicalPath, sha256, identity };
|
|
572
|
+
} catch (error) {
|
|
573
|
+
if (error instanceof RuntimeClosureError) throw error;
|
|
574
|
+
throw new RuntimeClosureError("INVALID_EXECUTABLE", "The executable could not be verified.");
|
|
575
|
+
} finally {
|
|
576
|
+
await handle?.close().catch(() => undefined);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
async function verifyDependency(requestedPath: string): Promise<VerifiedDependency> {
|
|
581
|
+
if (!isSafeAbsolutePath(requestedPath)) {
|
|
582
|
+
throw new RuntimeClosureError("DEPENDENCY_INVALID", "A runtime dependency path is unsafe.");
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
let physicalPath: string;
|
|
586
|
+
try {
|
|
587
|
+
physicalPath = await realpath(requestedPath);
|
|
588
|
+
} catch {
|
|
589
|
+
throw new RuntimeClosureError("DEPENDENCY_MISSING", "A runtime dependency is missing.");
|
|
590
|
+
}
|
|
591
|
+
if (!isSafeAbsolutePath(physicalPath)) {
|
|
592
|
+
throw new RuntimeClosureError("DEPENDENCY_INVALID", "A runtime dependency target is unsafe.");
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
let handle: Awaited<ReturnType<typeof open>> | null = null;
|
|
596
|
+
try {
|
|
597
|
+
const pathMetadata = await lstat(physicalPath, { bigint: true });
|
|
598
|
+
if (!pathMetadata.isFile() || pathMetadata.isSymbolicLink()) {
|
|
599
|
+
throw new RuntimeClosureError(
|
|
600
|
+
"DEPENDENCY_INVALID",
|
|
601
|
+
"A runtime dependency is not a physical regular file.",
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
handle = await open(
|
|
605
|
+
physicalPath,
|
|
606
|
+
constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK,
|
|
607
|
+
);
|
|
608
|
+
const beforeMetadata = await handle.stat({ bigint: true });
|
|
609
|
+
if (!beforeMetadata.isFile()
|
|
610
|
+
|| !sameFileIdentity(fileIdentity(pathMetadata), fileIdentity(beforeMetadata))) {
|
|
611
|
+
throw new RuntimeClosureError(
|
|
612
|
+
"DEPENDENCY_UNSTABLE",
|
|
613
|
+
"A runtime dependency changed before it could be opened.",
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
if (beforeMetadata.size < 0n
|
|
617
|
+
|| beforeMetadata.size > BigInt(MAX_RUNTIME_DEPENDENCY_BYTES)) {
|
|
618
|
+
throw new RuntimeClosureError(
|
|
619
|
+
"DEPENDENCY_TOO_LARGE",
|
|
620
|
+
"A runtime dependency exceeds Ghostget media's per-file limit.",
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
const bytes = Number(beforeMetadata.size);
|
|
625
|
+
const digest = createHash("sha256");
|
|
626
|
+
const chunk = Buffer.allocUnsafe(Math.min(FILE_READ_CHUNK_BYTES, Math.max(1, bytes)));
|
|
627
|
+
let offset = 0;
|
|
628
|
+
while (offset < bytes) {
|
|
629
|
+
const length = Math.min(chunk.byteLength, bytes - offset);
|
|
630
|
+
const read = await handle.read(chunk, 0, length, offset);
|
|
631
|
+
if (read.bytesRead !== length) {
|
|
632
|
+
throw new RuntimeClosureError(
|
|
633
|
+
"DEPENDENCY_UNSTABLE",
|
|
634
|
+
"A runtime dependency changed while it was being hashed.",
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
digest.update(chunk.subarray(0, read.bytesRead));
|
|
638
|
+
offset += read.bytesRead;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
const afterMetadata = await handle.stat({ bigint: true });
|
|
642
|
+
const afterPathMetadata = await lstat(physicalPath, { bigint: true });
|
|
643
|
+
const repeatedPhysicalPath = await realpath(requestedPath);
|
|
644
|
+
if (!sameFileIdentity(fileIdentity(beforeMetadata), fileIdentity(afterMetadata))
|
|
645
|
+
|| !sameFileIdentity(fileIdentity(beforeMetadata), fileIdentity(afterPathMetadata))
|
|
646
|
+
|| repeatedPhysicalPath !== physicalPath) {
|
|
647
|
+
throw new RuntimeClosureError(
|
|
648
|
+
"DEPENDENCY_UNSTABLE",
|
|
649
|
+
"A runtime dependency changed while it was being verified.",
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
return {
|
|
654
|
+
requestedPath,
|
|
655
|
+
physicalPath,
|
|
656
|
+
logicalName: basename(requestedPath),
|
|
657
|
+
sha256: digest.digest("hex"),
|
|
658
|
+
bytes,
|
|
659
|
+
dev: beforeMetadata.dev,
|
|
660
|
+
ino: beforeMetadata.ino,
|
|
661
|
+
};
|
|
662
|
+
} catch (error) {
|
|
663
|
+
if (error instanceof RuntimeClosureError) throw error;
|
|
664
|
+
throw new RuntimeClosureError("DEPENDENCY_MISSING", "A runtime dependency could not be verified.");
|
|
665
|
+
} finally {
|
|
666
|
+
await handle?.close().catch(() => undefined);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function writeLengthPrefixed(digest: ReturnType<typeof createHash>, value: string): void {
|
|
671
|
+
const bytes = Buffer.from(value, "utf8");
|
|
672
|
+
const length = Buffer.allocUnsafe(8);
|
|
673
|
+
length.writeBigUInt64BE(BigInt(bytes.byteLength));
|
|
674
|
+
digest.update(length);
|
|
675
|
+
digest.update(bytes);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
export function computeRuntimeClosureSha256(
|
|
679
|
+
platform: RuntimeClosurePlatform,
|
|
680
|
+
executableSha256: string,
|
|
681
|
+
dependencies: readonly RuntimeClosureDigestDependency[],
|
|
682
|
+
): string {
|
|
683
|
+
if (!SHA256_PATTERN.test(executableSha256)) {
|
|
684
|
+
throw new RuntimeClosureError(
|
|
685
|
+
"INVALID_EXECUTABLE_HASH",
|
|
686
|
+
"The executable identity is not a SHA-256 digest.",
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
const ordered = [...dependencies].sort((left, right) => {
|
|
690
|
+
const byName = compareUtf8(left.logicalName, right.logicalName);
|
|
691
|
+
if (byName !== 0) return byName;
|
|
692
|
+
const byBytes = left.bytes - right.bytes;
|
|
693
|
+
return byBytes !== 0 ? byBytes : compareUtf8(left.sha256, right.sha256);
|
|
694
|
+
});
|
|
695
|
+
const digest = createHash("sha256");
|
|
696
|
+
for (const value of [
|
|
697
|
+
RUNTIME_CLOSURE_PROFILE,
|
|
698
|
+
platform,
|
|
699
|
+
"executable",
|
|
700
|
+
executableSha256,
|
|
701
|
+
"dependency-count",
|
|
702
|
+
String(ordered.length),
|
|
703
|
+
]) {
|
|
704
|
+
writeLengthPrefixed(digest, value);
|
|
705
|
+
}
|
|
706
|
+
for (const dependency of ordered) {
|
|
707
|
+
writeLengthPrefixed(digest, "dependency");
|
|
708
|
+
writeLengthPrefixed(digest, dependency.logicalName);
|
|
709
|
+
writeLengthPrefixed(digest, String(dependency.bytes));
|
|
710
|
+
writeLengthPrefixed(digest, dependency.sha256);
|
|
711
|
+
}
|
|
712
|
+
return digest.digest("hex");
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
function isRecord(value: unknown): value is Readonly<Record<string, unknown>> {
|
|
716
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
function hasExactKeys(value: Readonly<Record<string, unknown>>, keys: readonly string[]): boolean {
|
|
720
|
+
const actual = Object.keys(value).sort();
|
|
721
|
+
const expected = [...keys].sort();
|
|
722
|
+
return actual.length === expected.length
|
|
723
|
+
&& actual.every((key, index) => key === expected[index]);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
function isSafeLogicalName(value: unknown): value is string {
|
|
727
|
+
return typeof value === "string"
|
|
728
|
+
&& value.length > 0
|
|
729
|
+
&& value.length <= 255
|
|
730
|
+
&& !hasUnsafeControlCharacter(value)
|
|
731
|
+
&& basename(value) === value
|
|
732
|
+
&& value !== "."
|
|
733
|
+
&& value !== "..";
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
export function parseRuntimeClosureRecord(value: unknown): ParseRuntimeClosureRecordResult {
|
|
737
|
+
const recordKeys = [
|
|
738
|
+
"profile",
|
|
739
|
+
"platform",
|
|
740
|
+
"evidence",
|
|
741
|
+
"executableSha256",
|
|
742
|
+
"closureSha256",
|
|
743
|
+
"dependencyCount",
|
|
744
|
+
"dependencyBytes",
|
|
745
|
+
"dependencies",
|
|
746
|
+
] as const;
|
|
747
|
+
if (!isRecord(value) || !hasExactKeys(value, recordKeys)) {
|
|
748
|
+
return { ok: false, message: "The runtime closure record does not match Ghostget media's schema." };
|
|
749
|
+
}
|
|
750
|
+
const platform = value["platform"];
|
|
751
|
+
const evidence = value["evidence"];
|
|
752
|
+
const executableSha256 = value["executableSha256"];
|
|
753
|
+
const closureSha256 = value["closureSha256"];
|
|
754
|
+
const dependencyCount = value["dependencyCount"];
|
|
755
|
+
const dependencyBytes = value["dependencyBytes"];
|
|
756
|
+
const rawDependencies = value["dependencies"];
|
|
757
|
+
if (value["profile"] !== RUNTIME_CLOSURE_PROFILE
|
|
758
|
+
|| (platform !== "darwin" && platform !== "linux")
|
|
759
|
+
|| evidence !== "dynamic-loader"
|
|
760
|
+
|| typeof executableSha256 !== "string"
|
|
761
|
+
|| !SHA256_PATTERN.test(executableSha256)
|
|
762
|
+
|| typeof closureSha256 !== "string"
|
|
763
|
+
|| !SHA256_PATTERN.test(closureSha256)
|
|
764
|
+
|| typeof dependencyCount !== "number"
|
|
765
|
+
|| !Number.isSafeInteger(dependencyCount)
|
|
766
|
+
|| dependencyCount < 0
|
|
767
|
+
|| dependencyCount > MAX_RUNTIME_DEPENDENCIES
|
|
768
|
+
|| typeof dependencyBytes !== "number"
|
|
769
|
+
|| !Number.isSafeInteger(dependencyBytes)
|
|
770
|
+
|| dependencyBytes < 0
|
|
771
|
+
|| dependencyBytes > MAX_RUNTIME_CLOSURE_BYTES
|
|
772
|
+
|| !Array.isArray(rawDependencies)
|
|
773
|
+
|| rawDependencies.length !== dependencyCount) {
|
|
774
|
+
return { ok: false, message: "The runtime closure record contains invalid fields." };
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
const dependencies: RuntimeClosureDependency[] = [];
|
|
778
|
+
const logicalNames = new Set<string>();
|
|
779
|
+
const physicalPaths = new Set<string>();
|
|
780
|
+
let countedBytes = 0;
|
|
781
|
+
for (const rawDependency of rawDependencies) {
|
|
782
|
+
if (!isRecord(rawDependency)
|
|
783
|
+
|| !hasExactKeys(rawDependency, ["physicalPath", "logicalName", "sha256", "bytes"])) {
|
|
784
|
+
return { ok: false, message: "A runtime closure dependency is malformed." };
|
|
785
|
+
}
|
|
786
|
+
const physicalPath = rawDependency["physicalPath"];
|
|
787
|
+
const logicalName = rawDependency["logicalName"];
|
|
788
|
+
const sha256 = rawDependency["sha256"];
|
|
789
|
+
const bytes = rawDependency["bytes"];
|
|
790
|
+
if (typeof physicalPath !== "string"
|
|
791
|
+
|| !isSafeAbsolutePath(physicalPath)
|
|
792
|
+
|| !isSafeLogicalName(logicalName)
|
|
793
|
+
|| typeof sha256 !== "string"
|
|
794
|
+
|| !SHA256_PATTERN.test(sha256)
|
|
795
|
+
|| typeof bytes !== "number"
|
|
796
|
+
|| !Number.isSafeInteger(bytes)
|
|
797
|
+
|| bytes < 0
|
|
798
|
+
|| bytes > MAX_RUNTIME_DEPENDENCY_BYTES
|
|
799
|
+
|| logicalNames.has(logicalName)
|
|
800
|
+
|| physicalPaths.has(physicalPath)) {
|
|
801
|
+
return { ok: false, message: "A runtime closure dependency contains invalid fields." };
|
|
802
|
+
}
|
|
803
|
+
countedBytes += bytes;
|
|
804
|
+
if (countedBytes > MAX_RUNTIME_CLOSURE_BYTES) {
|
|
805
|
+
return { ok: false, message: "The runtime closure exceeds Ghostget media's byte limit." };
|
|
806
|
+
}
|
|
807
|
+
logicalNames.add(logicalName);
|
|
808
|
+
physicalPaths.add(physicalPath);
|
|
809
|
+
dependencies.push(Object.freeze({ physicalPath, logicalName, sha256, bytes }));
|
|
810
|
+
}
|
|
811
|
+
const ordered = [...dependencies].sort((left, right) => (
|
|
812
|
+
compareUtf8(left.logicalName, right.logicalName)
|
|
813
|
+
|| compareUtf8(left.physicalPath, right.physicalPath)
|
|
814
|
+
));
|
|
815
|
+
if (dependencies.some((dependency, index) => dependency !== ordered[index])
|
|
816
|
+
|| countedBytes !== dependencyBytes
|
|
817
|
+
|| computeRuntimeClosureSha256(platform, executableSha256, dependencies) !== closureSha256) {
|
|
818
|
+
return { ok: false, message: "The runtime closure record is internally inconsistent." };
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
return {
|
|
822
|
+
ok: true,
|
|
823
|
+
record: Object.freeze({
|
|
824
|
+
profile: RUNTIME_CLOSURE_PROFILE,
|
|
825
|
+
platform,
|
|
826
|
+
evidence,
|
|
827
|
+
executableSha256,
|
|
828
|
+
closureSha256,
|
|
829
|
+
dependencyCount,
|
|
830
|
+
dependencyBytes,
|
|
831
|
+
dependencies: Object.freeze(dependencies),
|
|
832
|
+
}),
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
export function stripRuntimeClosureRecord(
|
|
837
|
+
attestation: RuntimeClosureAttestation,
|
|
838
|
+
): RuntimeClosureRecord {
|
|
839
|
+
const parsed = parseRuntimeClosureRecord(attestation);
|
|
840
|
+
if (!parsed.ok) {
|
|
841
|
+
throw new RuntimeClosureError(
|
|
842
|
+
"DEPENDENCY_INVALID",
|
|
843
|
+
"The runtime closure attestation cannot be persisted safely.",
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
return parsed.record;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
export function sameRuntimeClosureRecord(
|
|
850
|
+
left: RuntimeClosureRecord,
|
|
851
|
+
right: RuntimeClosureRecord,
|
|
852
|
+
): boolean {
|
|
853
|
+
if (left.profile !== right.profile
|
|
854
|
+
|| left.platform !== right.platform
|
|
855
|
+
|| left.evidence !== right.evidence
|
|
856
|
+
|| left.executableSha256 !== right.executableSha256
|
|
857
|
+
|| left.closureSha256 !== right.closureSha256
|
|
858
|
+
|| left.dependencyCount !== right.dependencyCount
|
|
859
|
+
|| left.dependencyBytes !== right.dependencyBytes
|
|
860
|
+
|| left.dependencies.length !== right.dependencies.length) {
|
|
861
|
+
return false;
|
|
862
|
+
}
|
|
863
|
+
return left.dependencies.every((dependency, index) => {
|
|
864
|
+
const other = right.dependencies[index];
|
|
865
|
+
return other !== undefined
|
|
866
|
+
&& dependency.physicalPath === other.physicalPath
|
|
867
|
+
&& dependency.logicalName === other.logicalName
|
|
868
|
+
&& dependency.sha256 === other.sha256
|
|
869
|
+
&& dependency.bytes === other.bytes;
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
function runtimePlatform(platform: NodeJS.Platform): RuntimeClosurePlatform {
|
|
874
|
+
if (platform === "darwin" || platform === "linux") return platform;
|
|
875
|
+
throw new RuntimeClosureError(
|
|
876
|
+
"UNSUPPORTED_PLATFORM",
|
|
877
|
+
"Runtime closure attestation supports only Darwin and glibc Linux.",
|
|
878
|
+
);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
function boundedProcessValue(
|
|
882
|
+
value: number | undefined,
|
|
883
|
+
fallback: number,
|
|
884
|
+
maximum: number,
|
|
885
|
+
): number {
|
|
886
|
+
const selected = value ?? fallback;
|
|
887
|
+
if (!Number.isSafeInteger(selected) || selected <= 0 || selected > maximum) {
|
|
888
|
+
throw new RuntimeClosureError(
|
|
889
|
+
"INVALID_PROCESS_LIMIT",
|
|
890
|
+
"The runtime attestation process limit is invalid.",
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
return selected;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
function sameExecutableSnapshot(left: VerifiedExecutable, right: VerifiedExecutable): boolean {
|
|
897
|
+
return left.physicalPath === right.physicalPath
|
|
898
|
+
&& left.sha256 === right.sha256
|
|
899
|
+
&& sameFileIdentity(left.identity, right.identity);
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
export async function runAttestedRuntimeProcess(
|
|
903
|
+
options: AttestRuntimeClosureOptions,
|
|
904
|
+
dependencies: RuntimeClosureDependencies = defaultDependencies,
|
|
905
|
+
): Promise<AttestedRuntimeProcessResult> {
|
|
906
|
+
const platform = runtimePlatform(options.platform ?? process.platform);
|
|
907
|
+
if (!isSafeAbsolutePath(options.executablePath)) {
|
|
908
|
+
throw new RuntimeClosureError("INVALID_EXECUTABLE", "The executable path is invalid.");
|
|
909
|
+
}
|
|
910
|
+
if (!SHA256_PATTERN.test(options.executableSha256)) {
|
|
911
|
+
throw new RuntimeClosureError(
|
|
912
|
+
"INVALID_EXECUTABLE_HASH",
|
|
913
|
+
"The executable identity is not a SHA-256 digest.",
|
|
914
|
+
);
|
|
915
|
+
}
|
|
916
|
+
const beforeExecutable = await verifyExecutableSnapshot(options.executablePath);
|
|
917
|
+
if (beforeExecutable.sha256 !== options.executableSha256) {
|
|
918
|
+
throw new RuntimeClosureError(
|
|
919
|
+
"EXECUTABLE_HASH_MISMATCH",
|
|
920
|
+
"The executable no longer matches its configured identity.",
|
|
921
|
+
);
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
const traceEnvironment = buildRuntimeTraceEnvironment(platform);
|
|
925
|
+
const probeArguments = options.probeArguments ?? ["--help"];
|
|
926
|
+
const argv: CommandArgv = [beforeExecutable.physicalPath, ...probeArguments];
|
|
927
|
+
const result = await dependencies.runProcess(argv, {
|
|
928
|
+
env: traceEnvironment,
|
|
929
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
930
|
+
timeoutMs: boundedProcessValue(
|
|
931
|
+
options.timeoutMs,
|
|
932
|
+
RUNTIME_TRACE_TIMEOUT_MS,
|
|
933
|
+
MAX_RUNTIME_PROCESS_TIMEOUT_MS,
|
|
934
|
+
),
|
|
935
|
+
maxStdoutBytes: boundedProcessValue(
|
|
936
|
+
options.maxStdoutBytes,
|
|
937
|
+
MAX_RUNTIME_TRACE_BYTES,
|
|
938
|
+
MAX_RUNTIME_PROCESS_OUTPUT_BYTES,
|
|
939
|
+
),
|
|
940
|
+
maxStderrBytes: boundedProcessValue(
|
|
941
|
+
options.maxStderrBytes,
|
|
942
|
+
MAX_RUNTIME_TRACE_BYTES,
|
|
943
|
+
MAX_RUNTIME_TRACE_BYTES,
|
|
944
|
+
),
|
|
945
|
+
});
|
|
946
|
+
const afterExecutable = await verifyExecutableSnapshot(options.executablePath);
|
|
947
|
+
if (afterExecutable.sha256 !== options.executableSha256) {
|
|
948
|
+
throw new RuntimeClosureError(
|
|
949
|
+
"EXECUTABLE_HASH_MISMATCH",
|
|
950
|
+
"The executable changed during runtime attestation.",
|
|
951
|
+
);
|
|
952
|
+
}
|
|
953
|
+
if (!sameExecutableSnapshot(beforeExecutable, afterExecutable)) {
|
|
954
|
+
throw new RuntimeClosureError(
|
|
955
|
+
"EXECUTABLE_UNSTABLE",
|
|
956
|
+
"The executable identity changed during runtime attestation.",
|
|
957
|
+
);
|
|
958
|
+
}
|
|
959
|
+
if (!result.ok) {
|
|
960
|
+
throw new RuntimeClosureError(
|
|
961
|
+
"TRACE_PROCESS_FAILED",
|
|
962
|
+
"The executable failed during bounded runtime closure tracing.",
|
|
963
|
+
);
|
|
964
|
+
}
|
|
965
|
+
if (result.stderrTruncated) {
|
|
966
|
+
throw new RuntimeClosureError("TRACE_TOO_LARGE", "The runtime loader trace was truncated.");
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
const parsed = parseRuntimeLoaderTrace(result.stderr, {
|
|
970
|
+
platform,
|
|
971
|
+
executablePath: beforeExecutable.physicalPath,
|
|
972
|
+
});
|
|
973
|
+
if (!parsed.ok) throw new RuntimeClosureError(parsed.code, parsed.message);
|
|
974
|
+
|
|
975
|
+
const requestedDependencies = parsed.loadedPaths.filter((path) => (
|
|
976
|
+
path !== beforeExecutable.physicalPath && !isPlatformOwnedRuntimePath(path, platform)
|
|
977
|
+
));
|
|
978
|
+
if (requestedDependencies.length > MAX_RUNTIME_DEPENDENCIES) {
|
|
979
|
+
throw new RuntimeClosureError(
|
|
980
|
+
"DEPENDENCY_LIMIT",
|
|
981
|
+
"The runtime closure exceeds Ghostget media's dependency-count limit.",
|
|
982
|
+
);
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
const verifiedDependencies: VerifiedDependency[] = [];
|
|
986
|
+
let dependencyBytes = 0;
|
|
987
|
+
for (const dependencyPath of requestedDependencies) {
|
|
988
|
+
const verified = await verifyDependency(dependencyPath);
|
|
989
|
+
dependencyBytes += verified.bytes;
|
|
990
|
+
if (dependencyBytes > MAX_RUNTIME_CLOSURE_BYTES) {
|
|
991
|
+
throw new RuntimeClosureError(
|
|
992
|
+
"DEPENDENCY_LIMIT",
|
|
993
|
+
"The runtime closure exceeds Ghostget media's aggregate byte limit.",
|
|
994
|
+
);
|
|
995
|
+
}
|
|
996
|
+
verifiedDependencies.push(verified);
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
const byLogicalName = new Map<string, VerifiedDependency>();
|
|
1000
|
+
const byPhysicalIdentity = new Map<string, VerifiedDependency>();
|
|
1001
|
+
for (const dependency of verifiedDependencies) {
|
|
1002
|
+
const logicalCollision = byLogicalName.get(dependency.logicalName);
|
|
1003
|
+
if (logicalCollision !== undefined
|
|
1004
|
+
&& (logicalCollision.dev !== dependency.dev || logicalCollision.ino !== dependency.ino)) {
|
|
1005
|
+
throw new RuntimeClosureError(
|
|
1006
|
+
"DEPENDENCY_AMBIGUOUS",
|
|
1007
|
+
"The runtime closure maps one logical name to multiple physical files.",
|
|
1008
|
+
);
|
|
1009
|
+
}
|
|
1010
|
+
byLogicalName.set(dependency.logicalName, dependency);
|
|
1011
|
+
|
|
1012
|
+
const identity = `${dependency.dev.toString()}:${dependency.ino.toString()}`;
|
|
1013
|
+
const physicalCollision = byPhysicalIdentity.get(identity);
|
|
1014
|
+
if (physicalCollision !== undefined
|
|
1015
|
+
&& physicalCollision.logicalName !== dependency.logicalName) {
|
|
1016
|
+
throw new RuntimeClosureError(
|
|
1017
|
+
"DEPENDENCY_AMBIGUOUS",
|
|
1018
|
+
"The runtime closure maps one physical file to multiple logical names.",
|
|
1019
|
+
);
|
|
1020
|
+
}
|
|
1021
|
+
byPhysicalIdentity.set(identity, dependency);
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
const closureDependencies = [...byPhysicalIdentity.values()]
|
|
1025
|
+
.map((dependency): RuntimeClosureDependency => ({
|
|
1026
|
+
physicalPath: dependency.physicalPath,
|
|
1027
|
+
logicalName: dependency.logicalName,
|
|
1028
|
+
sha256: dependency.sha256,
|
|
1029
|
+
bytes: dependency.bytes,
|
|
1030
|
+
}))
|
|
1031
|
+
.sort((left, right) => compareUtf8(left.logicalName, right.logicalName));
|
|
1032
|
+
const countedBytes = closureDependencies.reduce((total, dependency) => total + dependency.bytes, 0);
|
|
1033
|
+
|
|
1034
|
+
const attestation: RuntimeClosureAttestation = Object.freeze({
|
|
1035
|
+
profile: RUNTIME_CLOSURE_PROFILE,
|
|
1036
|
+
platform,
|
|
1037
|
+
evidence: parsed.evidence,
|
|
1038
|
+
executableSha256: options.executableSha256,
|
|
1039
|
+
closureSha256: computeRuntimeClosureSha256(
|
|
1040
|
+
platform,
|
|
1041
|
+
options.executableSha256,
|
|
1042
|
+
closureDependencies,
|
|
1043
|
+
),
|
|
1044
|
+
dependencyCount: closureDependencies.length,
|
|
1045
|
+
dependencyBytes: countedBytes,
|
|
1046
|
+
dependencies: Object.freeze(closureDependencies),
|
|
1047
|
+
});
|
|
1048
|
+
return Object.freeze({ process: result, attestation });
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
export async function attestRuntimeClosure(
|
|
1052
|
+
options: AttestRuntimeClosureOptions,
|
|
1053
|
+
dependencies: RuntimeClosureDependencies = defaultDependencies,
|
|
1054
|
+
): Promise<RuntimeClosureAttestation> {
|
|
1055
|
+
return (await runAttestedRuntimeProcess(options, dependencies)).attestation;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
export function sameRuntimeClosure(
|
|
1059
|
+
left: RuntimeClosureAttestation,
|
|
1060
|
+
right: RuntimeClosureAttestation,
|
|
1061
|
+
): boolean {
|
|
1062
|
+
return sameRuntimeClosureRecord(left, right);
|
|
1063
|
+
}
|