@pipobscure/bundle 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/HISTORY.md +1924 -0
  2. package/README.md +623 -0
  3. package/bundle.run +0 -0
  4. package/dist/api.d.ts +147 -0
  5. package/dist/api.d.ts.map +1 -0
  6. package/dist/api.js +174 -0
  7. package/dist/api.js.map +1 -0
  8. package/dist/archive.d.ts +115 -0
  9. package/dist/archive.d.ts.map +1 -0
  10. package/dist/archive.js +188 -0
  11. package/dist/archive.js.map +1 -0
  12. package/dist/audit.d.ts +78 -0
  13. package/dist/audit.d.ts.map +1 -0
  14. package/dist/audit.js +119 -0
  15. package/dist/audit.js.map +1 -0
  16. package/dist/cli.d.ts +23 -0
  17. package/dist/cli.d.ts.map +1 -0
  18. package/dist/cli.js +555 -0
  19. package/dist/cli.js.map +1 -0
  20. package/dist/files.d.ts +53 -0
  21. package/dist/files.d.ts.map +1 -0
  22. package/dist/files.js +118 -0
  23. package/dist/files.js.map +1 -0
  24. package/dist/index.d.ts +10 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +35 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/launch.d.ts +97 -0
  29. package/dist/launch.d.ts.map +1 -0
  30. package/dist/launch.js +267 -0
  31. package/dist/launch.js.map +1 -0
  32. package/dist/main.d.ts +3 -0
  33. package/dist/main.d.ts.map +1 -0
  34. package/dist/main.js +19 -0
  35. package/dist/main.js.map +1 -0
  36. package/dist/manifest.d.ts +139 -0
  37. package/dist/manifest.d.ts.map +1 -0
  38. package/dist/manifest.js +504 -0
  39. package/dist/manifest.js.map +1 -0
  40. package/dist/oidc.d.ts +40 -0
  41. package/dist/oidc.d.ts.map +1 -0
  42. package/dist/oidc.js +320 -0
  43. package/dist/oidc.js.map +1 -0
  44. package/dist/preload.d.ts +14 -0
  45. package/dist/preload.d.ts.map +1 -0
  46. package/dist/preload.js +38 -0
  47. package/dist/preload.js.map +1 -0
  48. package/dist/provider.d.ts +83 -0
  49. package/dist/provider.d.ts.map +1 -0
  50. package/dist/provider.js +206 -0
  51. package/dist/provider.js.map +1 -0
  52. package/dist/record.d.ts +2 -0
  53. package/dist/record.d.ts.map +1 -0
  54. package/dist/record.js +23 -0
  55. package/dist/record.js.map +1 -0
  56. package/dist/recorder.d.ts +64 -0
  57. package/dist/recorder.d.ts.map +1 -0
  58. package/dist/recorder.js +111 -0
  59. package/dist/recorder.js.map +1 -0
  60. package/dist/register.d.ts +2 -0
  61. package/dist/register.d.ts.map +1 -0
  62. package/dist/register.js +28 -0
  63. package/dist/register.js.map +1 -0
  64. package/dist/sea.d.ts +97 -0
  65. package/dist/sea.d.ts.map +1 -0
  66. package/dist/sea.js +220 -0
  67. package/dist/sea.js.map +1 -0
  68. package/dist/sigstore.d.ts +112 -0
  69. package/dist/sigstore.d.ts.map +1 -0
  70. package/dist/sigstore.js +385 -0
  71. package/dist/sigstore.js.map +1 -0
  72. package/dist/skill.d.ts +36 -0
  73. package/dist/skill.d.ts.map +1 -0
  74. package/dist/skill.js +108 -0
  75. package/dist/skill.js.map +1 -0
  76. package/package.json +84 -0
  77. package/shell-base +2 -0
  78. package/skills/audit-bundle/SKILL.md +271 -0
  79. package/src/api.ts +293 -0
  80. package/src/archive.ts +312 -0
  81. package/src/audit.ts +206 -0
  82. package/src/cli.ts +575 -0
  83. package/src/files.ts +156 -0
  84. package/src/index.ts +114 -0
  85. package/src/launch.ts +336 -0
  86. package/src/main.ts +20 -0
  87. package/src/manifest.ts +615 -0
  88. package/src/oidc.ts +372 -0
  89. package/src/preload.ts +40 -0
  90. package/src/provider.ts +270 -0
  91. package/src/record.ts +25 -0
  92. package/src/recorder.ts +166 -0
  93. package/src/register.ts +30 -0
  94. package/src/sea.ts +341 -0
  95. package/src/sigstore.ts +492 -0
  96. package/src/skill.ts +132 -0
  97. package/src/types/node-vfs.d.ts +90 -0
  98. package/src/types/node-zip.d.ts +85 -0
@@ -0,0 +1,206 @@
1
+ import * as VFS from 'node:vfs';
2
+ import * as ZLIB from 'node:zlib';
3
+ import * as CRYPTO from 'node:crypto';
4
+ import * as PATH from 'node:path';
5
+ import * as FS from 'node:fs';
6
+ import { AUTHORITY, signatureOf, verifySync } from './manifest.js';
7
+ // A `node:vfs` file provider for signed archives — `.bundle` files — layered on
8
+ // the built-in `ZipProvider`. It is what turns "this archive is signed" into
9
+ // "this archive is what runs":
10
+ //
11
+ // * At mount time (`open()` below) the whole-file hash is recomputed, the
12
+ // signature over it is checked against the leaf certificate in
13
+ // `AUTHORITY.PEM`, and that chain is anchored in the trust store. An
14
+ // archive that fails any of those never becomes a filesystem at all.
15
+ //
16
+ // * At *fetch* time every member is hashed as it is read and compared with
17
+ // the digest recorded for it in the archive that was verified at mount.
18
+ // The mount-time hash already covers every member's bytes, but it covers
19
+ // them *as they were when the file was hashed* — a `ZipFile` reads members
20
+ // lazily from an open fd, so anything that rewrites the file underneath a
21
+ // running program would otherwise be served unchecked. Verified content is
22
+ // kept (members are an application's own files, not the runtime), so each
23
+ // member is read and hashed at most once and what later reads see is the
24
+ // copy that was verified, not a fresh read of the file.
25
+ //
26
+ // Registering this with `vfs.registerProvider()` puts it ahead of the built-in
27
+ // ZIP provider, so `--vfs-load` hands it the source first. It claims files by
28
+ // extension (`.bundle`) *and* by content — anything carrying our signature
29
+ // marker — so renaming a signed archive cannot quietly downgrade it to the
30
+ // unverified built-in provider.
31
+ export const EXTENSION = '.bundle';
32
+ const READ_FLAGS = FS.constants.O_WRONLY | FS.constants.O_RDWR | FS.constants.O_CREAT |
33
+ FS.constants.O_TRUNC | FS.constants.O_APPEND | FS.constants.O_EXCL;
34
+ /**
35
+ * Verify `path` and return a provider that serves it, or throw. `options` are
36
+ * the same as `register()`'s.
37
+ */
38
+ export function open(path, options) {
39
+ const opts = settings(options);
40
+ const resolved = PATH.resolve(path);
41
+ // One ZipFile is opened here and handed to both the verification and the
42
+ // provider: the central directory is read once, and the digests the
43
+ // provider checks against are the ones the verified hash covered.
44
+ const archive = ZLIB.ZipFile.openSync(resolved);
45
+ try {
46
+ const res = verifySync(resolved, {
47
+ archive, deep: opts.deep, extraRoots: opts.extraRoots,
48
+ trustedRoot: opts.trustedRoot, identity: opts.identity, issuer: opts.issuer,
49
+ });
50
+ const acceptable = res.state === 'valid' || (opts.allowUntrusted && res.state === 'valid-untrusted');
51
+ if (!acceptable)
52
+ throw refusal(resolved, res);
53
+ return new BundleProvider(archive, { hashAlg: res.hashAlg, digests: res.digests });
54
+ }
55
+ catch (err) {
56
+ archive.closeSync();
57
+ throw err;
58
+ }
59
+ }
60
+ /**
61
+ * Register this provider with `node:vfs` so the `--vfs-load` startup flag
62
+ * selects it for signed archives. Meant to be preloaded, before `--vfs-load`
63
+ * picks a provider:
64
+ *
65
+ * node --experimental-vfs -r @pipobscure/bundle/register --vfs-load=app.bundle
66
+ */
67
+ export function register(options) {
68
+ const opts = settings(options);
69
+ VFS.registerProvider({
70
+ name: opts.name,
71
+ canHandle: (resolvedPath, stats) => stats.isFile() && claims(resolvedPath, opts),
72
+ create: (resolvedPath) => open(resolvedPath, opts),
73
+ });
74
+ return opts;
75
+ }
76
+ /**
77
+ * A read-only `ZipProvider` that will not hand out a member's content until
78
+ * that content has been hashed and matched against the digest recorded for it.
79
+ */
80
+ export class BundleProvider extends VFS.ZipProvider {
81
+ #archive;
82
+ #hashAlg;
83
+ #digests;
84
+ #verified;
85
+ /**
86
+ * `digests` is the member-name -> hex-digest map from a `verifySync()` of
87
+ * this very archive; `hashAlg` the algorithm those digests are in.
88
+ */
89
+ constructor(archive, { hashAlg = 'sha256', digests = new Map() } = {}) {
90
+ super(archive);
91
+ this.#archive = archive;
92
+ this.#hashAlg = hashAlg;
93
+ this.#digests = digests;
94
+ this.#verified = new VFS.MemoryProvider();
95
+ // The manifest itself carries no digest of its own — it is what names
96
+ // the algorithms — but its bytes were read during the mount-time check,
97
+ // which the whole-file hash covered. Keep that copy so every path the
98
+ // mount serves comes from verified bytes.
99
+ if (archive.has(AUTHORITY)) {
100
+ const entry = archive.getSync(AUTHORITY);
101
+ this.#keep(AUTHORITY, entry.contentSync(), entry.mode);
102
+ }
103
+ }
104
+ // A signed archive is a fixed artifact: any write would invalidate the
105
+ // signature it was mounted on, so the mount is read-only regardless of how
106
+ // the underlying archive was opened.
107
+ get readonly() { return true; }
108
+ async open(path, flags, mode) {
109
+ const name = normalize(path);
110
+ if (reads(flags) && this.#check(name))
111
+ return this.#verified.open(path, flags, mode);
112
+ return super.open(path, flags, mode);
113
+ }
114
+ openSync(path, flags, mode) {
115
+ const name = normalize(path);
116
+ if (reads(flags) && this.#check(name))
117
+ return this.#verified.openSync(path, flags, mode);
118
+ return super.openSync(path, flags, mode);
119
+ }
120
+ // Whether `name` can be served from verified content, reading and checking
121
+ // it on first use. `false` means this is not a member with a recorded
122
+ // digest (a directory, or nothing at all) and the base provider should
123
+ // answer — including with the ENOENT/EISDIR it would normally raise.
124
+ #check(name) {
125
+ if (this.#verified.existsSync(`/${name}`))
126
+ return true;
127
+ const recorded = this.#digests.get(name);
128
+ if (recorded === undefined)
129
+ return false;
130
+ const entry = this.#archive.getSync(name);
131
+ const content = entry.contentSync();
132
+ const actual = CRYPTO.createHash(this.#hashAlg).update(content).digest('hex');
133
+ if (actual !== recorded) {
134
+ throw Object.assign(new Error(`bundle: content of '${name}' does not match its signed digest ` +
135
+ `(expected ${recorded}, got ${actual})`), { code: 'ERR_BUNDLE_INTEGRITY', member: name });
136
+ }
137
+ this.#keep(name, content, entry.mode);
138
+ return true;
139
+ }
140
+ #keep(name, content, mode) {
141
+ const dir = PATH.posix.dirname(`/${name}`);
142
+ if (dir !== '/' && dir !== '.')
143
+ this.#verified.mkdirSync(dir, { recursive: true });
144
+ this.#verified.writeFileSync(`/${name}`, content, { mode: mode || 0o444 });
145
+ }
146
+ }
147
+ // Whether this provider should back `resolvedPath` (already known to be a
148
+ // file): by name for the extensions it owns, and by content for anything
149
+ // carrying our signature marker.
150
+ function claims(resolvedPath, opts) {
151
+ const lower = resolvedPath.toLowerCase();
152
+ if (opts.extensions.some((ext) => lower.endsWith(ext)))
153
+ return true;
154
+ return opts.claimSigned && signatureOf(resolvedPath) !== null;
155
+ }
156
+ function refusal(path, res) {
157
+ const detail = res.subject ? `${res.reason} [${res.subject.replace(/\n/g, ', ')}]` : res.reason;
158
+ return Object.assign(new Error(`bundle: refusing to mount '${path}': ${res.state} — ${detail}`), { code: 'ERR_BUNDLE_UNTRUSTED', state: res.state });
159
+ }
160
+ // VFS paths are normalized to `/`-rooted POSIX paths; ZIP member names
161
+ // have no leading slash.
162
+ function normalize(path) {
163
+ return path.startsWith('/') ? path.slice(1) : path;
164
+ }
165
+ // Whether `flags` opens purely for reading — the only case verified content can
166
+ // answer. Anything else goes to the base provider, which refuses it (EROFS)
167
+ // because this provider is read-only. Mirrors how `node:fs` itself reads flags:
168
+ // a string means what it says, a number is a bitmask, anything else is 'r'.
169
+ function reads(flags) {
170
+ if (typeof flags === 'string')
171
+ return flags === 'r';
172
+ if (typeof flags !== 'number')
173
+ return true;
174
+ return (flags & READ_FLAGS) === 0;
175
+ }
176
+ const kSettings = Symbol('bundle.settings');
177
+ function settings(options = {}) {
178
+ if (options[kSettings])
179
+ return options;
180
+ const opts = options;
181
+ return {
182
+ [kSettings]: true,
183
+ name: opts.name ?? 'bundle',
184
+ extensions: (opts.extensions ?? [EXTENSION]).map((ext) => ext.toLowerCase()),
185
+ claimSigned: opts.claimSigned ?? true,
186
+ extraRoots: pems(opts.roots ?? envList('BUNDLE_ROOTS')),
187
+ allowUntrusted: opts.allowUntrusted ?? envFlag('BUNDLE_ALLOW_UNTRUSTED'),
188
+ deep: opts.deep ?? false,
189
+ identity: opts.identity ?? (process.env['BUNDLE_IDENTITY'] || undefined),
190
+ issuer: opts.issuer ?? (process.env['BUNDLE_ISSUER'] || undefined),
191
+ trustedRoot: opts.trustedRoot ?? (process.env['BUNDLE_SIGSTORE_ROOT'] || undefined),
192
+ };
193
+ }
194
+ // Accepts roots as PEM text or as paths to PEM files, so a caller can pass
195
+ // either and `BUNDLE_ROOTS` can name files.
196
+ function pems(roots) {
197
+ return (roots ?? []).map((root) => (root.includes('-----BEGIN') ? root : FS.readFileSync(root, 'utf-8')));
198
+ }
199
+ function envList(name) {
200
+ return (process.env[name] ?? '').split(PATH.delimiter).filter(Boolean);
201
+ }
202
+ function envFlag(name) {
203
+ const value = process.env[name];
204
+ return value !== undefined && value !== '' && value !== '0' && value !== 'false';
205
+ }
206
+ //# sourceMappingURL=provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=record.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record.d.ts","sourceRoot":"","sources":["../src/record.ts"],"names":[],"mappings":""}
package/dist/record.js ADDED
@@ -0,0 +1,23 @@
1
+ import { preload, sibling } from './preload.js';
2
+ // The preload that puts manifest recording back where `--vfs-manifest` used to
3
+ // be: registering a recording provider is all it does, so `--vfs-load` finds it
4
+ // already in place when it picks a provider for a directory.
5
+ //
6
+ // BUNDLE_MANIFEST=app.manifest node --experimental-vfs \
7
+ // -r @pipobscure/bundle/record --vfs-load=./lib
8
+ //
9
+ // `--import @pipobscure/bundle/record` works just as well: mounting is deferred
10
+ // until both `-r` and `--import` preloads have run, and this module imports
11
+ // cleanly from ESM.
12
+ //
13
+ // Configure it through the environment — a preload takes no arguments:
14
+ //
15
+ // BUNDLE_MANIFEST where to write the file list; unset records nothing
16
+ //
17
+ // For anything more (recording an archive mount, several manifests, keeping an
18
+ // existing list), import `@pipobscure/bundle/recorder` and call `register()` or
19
+ // `recording()` from a preload module of your own.
20
+ preload(() => {
21
+ sibling(import.meta.filename, 'recorder').register();
22
+ });
23
+ //# sourceMappingURL=record.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record.js","sourceRoot":"","sources":["../src/record.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAGhD,+EAA+E;AAC/E,gFAAgF;AAChF,6DAA6D;AAC7D,EAAE;AACF,2DAA2D;AAC3D,sDAAsD;AACtD,EAAE;AACF,gFAAgF;AAChF,4EAA4E;AAC5E,oBAAoB;AACpB,EAAE;AACF,uEAAuE;AACvE,EAAE;AACF,yEAAyE;AACzE,EAAE;AACF,+EAA+E;AAC/E,gFAAgF;AAChF,mDAAmD;AAEnD,OAAO,CAAC,GAAG,EAAE;IACT,OAAO,CAAkB,OAAO,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,QAAQ,EAAE,CAAC;AAC1E,CAAC,CAAC,CAAC"}
@@ -0,0 +1,64 @@
1
+ import * as VFS from 'node:vfs';
2
+ export interface ManifestOptions {
3
+ /**
4
+ * Start a fresh list. Defaults to true only on the main thread: worker
5
+ * threads inherit `execArgv`, so this preload runs in them too, and a
6
+ * worker must append to what the main thread already wrote rather than
7
+ * wipe it mid-run.
8
+ */
9
+ truncate?: boolean | undefined;
10
+ }
11
+ export interface RecorderOptions extends ManifestOptions {
12
+ /** Where to write the file list (default: the `BUNDLE_MANIFEST` variable). */
13
+ manifest?: string | Manifest | undefined;
14
+ /** Identifier reported in diagnostics (default: 'bundle-manifest'). */
15
+ name?: string | undefined;
16
+ }
17
+ /**
18
+ * The manifest file itself: which paths have been written, and the appending.
19
+ * Paths are appended as soon as they are read rather than buffered and flushed
20
+ * at exit, so nothing is lost if the process is killed instead of exiting.
21
+ */
22
+ export declare class Manifest {
23
+ #private;
24
+ constructor(path: string, { truncate }?: ManifestOptions);
25
+ get path(): string;
26
+ /** Every path recorded so far, in the order they were first read. */
27
+ get paths(): string[];
28
+ /**
29
+ * Record one VFS path, once. Best effort: bookkeeping must never break the
30
+ * read that triggered it.
31
+ */
32
+ record(path: string): void;
33
+ }
34
+ /**
35
+ * The shape `recording()` accepts and returns — any provider class. The rest
36
+ * parameter is what lets the wrapper be a mixin over a class whose constructor
37
+ * takes whatever it likes, and hand back one with the same signature.
38
+ */
39
+ type ProviderClass = new (...args: any[]) => VFS.VirtualProvider;
40
+ /**
41
+ * Wrap a provider class so every read through it is recorded in `manifest`.
42
+ * Works over any `VirtualProvider` subclass — a `RealFSProvider` for a
43
+ * directory mount, a `ZipProvider` or the signed-archive provider from
44
+ * './provider.ts' for an archive one — and leaves the constructor signature of
45
+ * the class it wraps untouched:
46
+ *
47
+ * const Recording = recording(VFS.RealFSProvider, new Manifest('app.manifest'));
48
+ * const provider = new Recording('/path/to/directory');
49
+ */
50
+ export declare function recording<T extends ProviderClass>(Base: T, manifest: Manifest): T;
51
+ /**
52
+ * Register a recording `RealFSProvider` so a directory loaded with
53
+ * `--vfs-load` is backed by it. Meant to be preloaded, before the mount is made:
54
+ *
55
+ * BUNDLE_MANIFEST=app.manifest node --experimental-vfs \
56
+ * -r @pipobscure/bundle/record --vfs-load=./lib
57
+ *
58
+ * With no manifest destination there is nothing to record, so nothing is
59
+ * registered and directory mounts are left exactly as node makes them; the
60
+ * return value is then null.
61
+ */
62
+ export declare function register(options?: RecorderOptions): Manifest | null;
63
+ export {};
64
+ //# sourceMappingURL=recorder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../src/recorder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAgChC,MAAM,WAAW,eAAe;IAC5B;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAClC;AAED,MAAM,WAAW,eAAgB,SAAQ,eAAe;IACpD,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;IACzC,uEAAuE;IACvE,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAED;;;;GAIG;AACH,qBAAa,QAAQ;;IAMjB,YAAY,IAAI,EAAE,MAAM,EAAE,EAAE,QAAuB,EAAE,GAAE,eAAoB,EAG1E;IAED,IAAI,IAAI,IAAI,MAAM,CAAuB;IAEzC,qEAAqE;IACrE,IAAI,KAAK,IAAI,MAAM,EAAE,CAA4B;IAEjD;;;OAGG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CASzB;CACJ;AAED;;;;GAIG;AACH,KAAK,aAAa,GAAG,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC,eAAe,CAAC;AAEjE;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,QAAQ,GAAG,CAAC,CA0BjF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,QAAQ,CAAC,OAAO,GAAE,eAAoB,GAAG,QAAQ,GAAG,IAAI,CAevE"}
@@ -0,0 +1,111 @@
1
+ import * as VFS from 'node:vfs';
2
+ import * as FS from 'node:fs';
3
+ import { isMainThread } from 'node:worker_threads';
4
+ /**
5
+ * The manifest file itself: which paths have been written, and the appending.
6
+ * Paths are appended as soon as they are read rather than buffered and flushed
7
+ * at exit, so nothing is lost if the process is killed instead of exiting.
8
+ */
9
+ export class Manifest {
10
+ #path;
11
+ #seen = new Set();
12
+ // Concurrent appends are safe without any coordination — O_APPEND writes do
13
+ // not interleave — which is what lets worker threads share one manifest.
14
+ constructor(path, { truncate = isMainThread } = {}) {
15
+ this.#path = path;
16
+ if (truncate)
17
+ FS.writeFileSync(path, '');
18
+ }
19
+ get path() { return this.#path; }
20
+ /** Every path recorded so far, in the order they were first read. */
21
+ get paths() { return [...this.#seen]; }
22
+ /**
23
+ * Record one VFS path, once. Best effort: bookkeeping must never break the
24
+ * read that triggered it.
25
+ */
26
+ record(path) {
27
+ try {
28
+ const relative = String(path).replace(/^\/+/, '');
29
+ if (relative === '' || this.#seen.has(relative))
30
+ return;
31
+ this.#seen.add(relative);
32
+ FS.appendFileSync(this.#path, `${relative}\n`);
33
+ }
34
+ catch {
35
+ // ignored
36
+ }
37
+ }
38
+ }
39
+ /**
40
+ * Wrap a provider class so every read through it is recorded in `manifest`.
41
+ * Works over any `VirtualProvider` subclass — a `RealFSProvider` for a
42
+ * directory mount, a `ZipProvider` or the signed-archive provider from
43
+ * './provider.ts' for an archive one — and leaves the constructor signature of
44
+ * the class it wraps untouched:
45
+ *
46
+ * const Recording = recording(VFS.RealFSProvider, new Manifest('app.manifest'));
47
+ * const provider = new Recording('/path/to/directory');
48
+ */
49
+ export function recording(Base, manifest) {
50
+ return class Recording extends Base {
51
+ async readFile(path, options) {
52
+ const content = await super.readFile(path, options);
53
+ manifest.record(path);
54
+ return content;
55
+ }
56
+ readFileSync(path, options) {
57
+ const content = super.readFileSync(path, options);
58
+ manifest.record(path);
59
+ return content;
60
+ }
61
+ async open(path, flags, mode) {
62
+ const handle = await super.open(path, flags, mode);
63
+ if (reads(flags))
64
+ manifest.record(path);
65
+ return handle;
66
+ }
67
+ openSync(path, flags, mode) {
68
+ const handle = super.openSync(path, flags, mode);
69
+ if (reads(flags))
70
+ manifest.record(path);
71
+ return handle;
72
+ }
73
+ };
74
+ }
75
+ /**
76
+ * Register a recording `RealFSProvider` so a directory loaded with
77
+ * `--vfs-load` is backed by it. Meant to be preloaded, before the mount is made:
78
+ *
79
+ * BUNDLE_MANIFEST=app.manifest node --experimental-vfs \
80
+ * -r @pipobscure/bundle/record --vfs-load=./lib
81
+ *
82
+ * With no manifest destination there is nothing to record, so nothing is
83
+ * registered and directory mounts are left exactly as node makes them; the
84
+ * return value is then null.
85
+ */
86
+ export function register(options = {}) {
87
+ const configured = options.manifest ?? process.env['BUNDLE_MANIFEST'];
88
+ if (!configured)
89
+ return null;
90
+ const manifest = configured instanceof Manifest ? configured : new Manifest(configured, options);
91
+ const Recording = recording(VFS.RealFSProvider, manifest);
92
+ VFS.registerProvider({
93
+ name: options.name ?? 'bundle-manifest',
94
+ // Directory mounts only: an archive mount has its own provider (and,
95
+ // for a `.bundle`, one that verifies what it serves), and quietly
96
+ // replacing it here would be the wrong trade for a file list.
97
+ canHandle: (_resolvedPath, stats) => stats.isDirectory(),
98
+ create: (resolvedPath) => new Recording(resolvedPath),
99
+ });
100
+ return manifest;
101
+ }
102
+ // Whether `flags` opens for reading. Mirrors how `node:fs` reads them: a string
103
+ // means what it says, a number is a bitmask, anything else is 'r'.
104
+ function reads(flags) {
105
+ if (typeof flags === 'string')
106
+ return flags[0] === 'r' || flags.includes('+');
107
+ if (typeof flags !== 'number')
108
+ return true;
109
+ return (flags & (FS.constants.O_WRONLY | FS.constants.O_TRUNC)) === 0;
110
+ }
111
+ //# sourceMappingURL=recorder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recorder.js","sourceRoot":"","sources":["../src/recorder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=register.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../src/register.ts"],"names":[],"mappings":""}
@@ -0,0 +1,28 @@
1
+ import { preload, sibling } from './preload.js';
2
+ // The preload entry point: registering the signed-archive provider is all this
3
+ // does, so `--vfs-load` finds it already in place when it picks a provider for
4
+ // its source.
5
+ //
6
+ // node --experimental-vfs -r @pipobscure/bundle/register --vfs-load=app.bundle
7
+ //
8
+ // `--import @pipobscure/bundle/register` works just as well: mounting is
9
+ // deferred until both `-r` and `--import` preloads have run, and this module
10
+ // imports cleanly from ESM. Either way the point is that registering is a
11
+ // preload flag and nothing more — no module of your own to write.
12
+ //
13
+ // Configure it through the environment — a preload takes no arguments:
14
+ //
15
+ // BUNDLE_ROOTS extra trusted root certificates, as a
16
+ // path-delimiter-separated list of PEM files
17
+ // BUNDLE_ALLOW_UNTRUSTED accept a good signature whose chain is not anchored
18
+ // in the trust store
19
+ // BUNDLE_IDENTITY require this sigstore signing identity
20
+ // BUNDLE_ISSUER require this sigstore OIDC issuer
21
+ // BUNDLE_SIGSTORE_ROOT path to the sigstore trust root to check against
22
+ //
23
+ // For anything more, import `@pipobscure/bundle/provider` and call `register()`
24
+ // with options from a preload module of your own.
25
+ preload(() => {
26
+ sibling(import.meta.filename, 'provider').register();
27
+ });
28
+ //# sourceMappingURL=register.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register.js","sourceRoot":"","sources":["../src/register.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAGhD,+EAA+E;AAC/E,+EAA+E;AAC/E,cAAc;AACd,EAAE;AACF,iFAAiF;AACjF,EAAE;AACF,yEAAyE;AACzE,6EAA6E;AAC7E,0EAA0E;AAC1E,kEAAkE;AAClE,EAAE;AACF,uEAAuE;AACvE,EAAE;AACF,kEAAkE;AAClE,uEAAuE;AACvE,gFAAgF;AAChF,+CAA+C;AAC/C,mEAAmE;AACnE,8DAA8D;AAC9D,6EAA6E;AAC7E,EAAE;AACF,gFAAgF;AAChF,kDAAkD;AAElD,OAAO,CAAC,GAAG,EAAE;IACT,OAAO,CAAkB,OAAO,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,QAAQ,EAAE,CAAC;AAC1E,CAAC,CAAC,CAAC"}
package/dist/sea.d.ts ADDED
@@ -0,0 +1,97 @@
1
+ import { type Baked, type Mounted } from './launch.ts';
2
+ import { type BuildResult } from './api.ts';
3
+ import type { VerificationResult } from './manifest.ts';
4
+ import type { Signer } from './archive.ts';
5
+ export interface BootstrapOptions extends Baked {
6
+ /** The container to verify and mount (default: `process.execPath`). */
7
+ container?: string | undefined;
8
+ }
9
+ export type { Mounted };
10
+ /**
11
+ * Verify the running container and mount the archive appended to it. Returns
12
+ * where it landed; nothing has been executed out of it yet.
13
+ */
14
+ export declare function mountSelf(options?: BootstrapOptions): Mounted;
15
+ /** Verify the running container, mount it, and run the application inside. */
16
+ export declare function bootstrap(options?: BootstrapOptions): Promise<void>;
17
+ /**
18
+ * Verify the running container without mounting it — for an application that
19
+ * wants to report on its own provenance ("signed by X at Y").
20
+ */
21
+ export declare function verifySelf(options?: BootstrapOptions): VerificationResult;
22
+ /** The default flags the container runs itself with. */
23
+ export declare const SEA_EXEC_ARGV: string[];
24
+ export interface VerifierOptions {
25
+ /**
26
+ * Include the sigstore libraries, so the container can check a
27
+ * sigstore-signed archive rather than degrading it to untrusted
28
+ * (default: true). Costs roughly a megabyte of dependency tree.
29
+ */
30
+ sigstore?: boolean | undefined;
31
+ }
32
+ export interface SeaBaseOptions extends VerifierOptions {
33
+ /** Where to write the base binary. */
34
+ output: string;
35
+ /**
36
+ * The node binary to embed (default: the running one). This is the runtime
37
+ * the finished container ships, so it decides which platform it runs on.
38
+ */
39
+ node?: string | undefined;
40
+ /** Options baked into the stub and handed to `bootstrap()` at startup. */
41
+ bootstrap?: BootstrapOptions | undefined;
42
+ /** Runtime flags the container applies to itself. */
43
+ execArgv?: string[] | undefined;
44
+ /** A prebuilt verifier bundle to embed, instead of building one. */
45
+ verifier?: string | undefined;
46
+ /** Scratch directory for the generated stub and config. */
47
+ scratch?: string | undefined;
48
+ }
49
+ export interface SeaOptions extends SeaBaseOptions {
50
+ /** The application archive to append. Signed or not; it is re-signed here. */
51
+ app: string;
52
+ /** A base built earlier, instead of building one now. */
53
+ base?: string | undefined;
54
+ /** Sign the finished container. Without one it is built but left unsigned. */
55
+ signer?: Signer | undefined;
56
+ hashAlg?: string | undefined;
57
+ signAlg?: string | undefined;
58
+ /** Progress, one line at a time. */
59
+ log?: ((line: string) => void) | undefined;
60
+ }
61
+ export interface SeaBaseResult {
62
+ output: string;
63
+ size: number;
64
+ /** Members of the verifier bundle embedded in the blob. */
65
+ verifier: string[];
66
+ }
67
+ /**
68
+ * Build the SEA base: a node runtime whose injected main mounts this package
69
+ * out of its own blob and hands over to `bootstrap()`. The result is a binary
70
+ * with no application in it yet — append one with `buildSea` or with
71
+ * `sign --prefix`.
72
+ */
73
+ export declare function createSeaBase(options: SeaBaseOptions): Promise<SeaBaseResult>;
74
+ /**
75
+ * Build a self-validating executable: a SEA base with `app` appended and the
76
+ * whole thing signed as one file. Everything the container will check — the
77
+ * runtime, the verifier and the application — is inside what the signature
78
+ * covers.
79
+ */
80
+ export declare function buildSea(options: SeaOptions): Promise<BuildResult>;
81
+ /**
82
+ * The files the embedded verifier needs, as paths relative to this package's
83
+ * root. That is this package's own compiled modules plus, unless turned off,
84
+ * the sigstore libraries — resolved through `node_modules` rather than listed,
85
+ * so the set cannot fall behind the dependency tree.
86
+ */
87
+ export declare function verifierFiles({ sigstore }?: VerifierOptions): string[];
88
+ /**
89
+ * The CommonJS stub injected as the SEA's main script. It runs at the root of
90
+ * the mounted verifier bundle, so requiring this package is a relative path and
91
+ * nothing more; everything else it needs to decide, `launch` decides by looking
92
+ * at the binary's own tail.
93
+ *
94
+ * It stays the one piece no test can exercise from source, so it stays small.
95
+ */
96
+ export declare function stubSource(options: BootstrapOptions): string;
97
+ //# sourceMappingURL=sea.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sea.d.ts","sourceRoot":"","sources":["../src/sea.ts"],"names":[],"mappings":"AAIA,OAAO,EAAwB,KAAK,KAAK,EAAE,KAAK,OAAO,EAAE,MAAM,aAAa,CAAC;AAC7E,OAAO,EAA4B,KAAK,WAAW,EAAE,MAAM,UAAU,CAAC;AACtE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAExD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAyD3C,MAAM,WAAW,gBAAiB,SAAQ,KAAK;IAC3C,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC;AACD,YAAY,EAAE,OAAO,EAAE,CAAC;AAExB;;;GAGG;AACH,wBAAgB,SAAS,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAEjE;AAED,8EAA8E;AAC9E,wBAAsB,SAAS,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAE7E;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,OAAO,GAAE,gBAAqB,GAAG,kBAAkB,CAE7E;AAID,wDAAwD;AACxD,eAAO,MAAM,aAAa,UAA0C,CAAC;AAErE,MAAM,WAAW,eAAe;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAClC;AAED,MAAM,WAAW,cAAe,SAAQ,eAAe;IACnD,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,0EAA0E;IAC1E,SAAS,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAC;IACzC,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAChC,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,2DAA2D;IAC3D,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC;AAED,MAAM,WAAW,UAAW,SAAQ,cAAc;IAC9C,8EAA8E;IAC9E,GAAG,EAAE,MAAM,CAAC;IACZ,yDAAyD;IACzD,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,oCAAoC;IACpC,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;CAC9C;AAED,MAAM,WAAW,aAAa;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CA+DnF;AAED;;;;;GAKG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CA4BxE;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,EAAE,QAAe,EAAE,GAAE,eAAoB,GAAG,MAAM,EAAE,CAWjF;AAuCD;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,CAyB5D"}