@markii/bundle 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # @markii/bundle
2
2
 
3
- Bundle (`.mkbundle`) storage and policy handling for [Mark](https://github.com/sadigaxund/markii): manifest parsing/validation, a bundle-relative path-jail, the zip storage form (via `fflate`), a Node-only directory storage form (`./fs` subpath), and a capability-restricted script view. No React, no markdown parsing.
3
+ Bundle (`.mkz`) storage and policy handling for [Markii](https://github.com/sadigaxund/markii): manifest parsing/validation, a bundle-relative path-jail, the zip storage form (via `fflate`), a Node-only directory storage form (`./fs` subpath), and a capability-restricted script view. No React, no markdown parsing.
4
+
5
+ The legacy `.mkbundle` extension is still recognized for one more release; new bundles are always written as `.mkz`.
4
6
 
5
7
  See the [repository](https://github.com/sadigaxund/markii) for the format spec and the reference library as a whole.
package/dist/fs.js CHANGED
@@ -116,7 +116,7 @@ async function resolveInsideRoot(rootAbs, relPath) {
116
116
  * Writes `data` to an already symlink-checked `target` that is known to
117
117
  * exist, refusing to write through a hard link (ESCAPE 3: `st_nlink > 1`
118
118
  * means some other path — possibly outside the bundle entirely, e.g.
119
- * `ln b.mkbundle/../victim.txt b.mkbundle/cache/hard` — refers to the exact same
119
+ * `ln b.mkz/../victim.txt b.mkz/cache/hard` — refers to the exact same
120
120
  * inode, so writing here would also silently modify that other path).
121
121
  *
122
122
  * Opens the file first *without* truncating (`'r+'`), `fstat`s the open
@@ -212,6 +212,24 @@ export function openDirBundle(rootDir) {
212
212
  throw err;
213
213
  }
214
214
  },
215
+ async size(path) {
216
+ // Routes through the exact same `normalizeOrThrow` + `resolveInsideRoot`
217
+ // choke point `read`/`exists` use, so this can never be used to stat
218
+ // outside the bundle root or through a symlink (see `resolveInsideRoot`'s
219
+ // doc comment) — the whole point of C-1's fix is a size check a caller
220
+ // can trust as much as it trusts `read` itself.
221
+ const relPath = normalizeOrThrow(path);
222
+ const { target } = await resolveInsideRoot(rootAbs, relPath);
223
+ try {
224
+ const info = await stat(target);
225
+ return info.isFile() ? info.size : undefined;
226
+ }
227
+ catch (err) {
228
+ if (isEnoent(err))
229
+ return undefined;
230
+ throw err;
231
+ }
232
+ },
215
233
  };
216
234
  }
217
235
  /**
@@ -11,6 +11,17 @@ export interface BundleManifest {
11
11
  mark: string;
12
12
  permissions?: BundlePermissions;
13
13
  uses?: string[];
14
+ /**
15
+ * Optional bundle-relative path to the document to open, overriding the
16
+ * conventional `note.mk.md`. `parseManifest` only checks that this is a
17
+ * string; it does not reject `../` or an absolute path here. Path-jailing
18
+ * is done once, at use time, by the consumer's `normalizeBundlePath` (see
19
+ * `./paths.ts`) — matching the parity of every other manifest field, none
20
+ * of which pre-jail path-shaped values either. Keeping the single jail
21
+ * point at use time avoids duplicating (and risking drift from) that
22
+ * logic here.
23
+ */
24
+ document?: string;
14
25
  [key: string]: unknown;
15
26
  }
16
27
  export interface BundlePermissions {
package/dist/manifest.js CHANGED
@@ -1,6 +1,11 @@
1
1
  /** The current spec version this package's default manifests declare. */
2
2
  export const CURRENT_SPEC_VERSION = '0.1.0';
3
- const KNOWN_TOP_LEVEL_KEYS = new Set(['mark', 'permissions', 'uses']);
3
+ const KNOWN_TOP_LEVEL_KEYS = new Set([
4
+ 'mark',
5
+ 'permissions',
6
+ 'uses',
7
+ 'document',
8
+ ]);
4
9
  const KNOWN_FS_GRANTS = new Set(['read', 'write:cache/']);
5
10
  // Simplified but structurally correct semver: MAJOR.MINOR.PATCH with
6
11
  // optional prerelease/build metadata. Good enough for a "shape" check —
@@ -124,6 +129,22 @@ export function parseManifest(json) {
124
129
  uses = obj.uses;
125
130
  }
126
131
  }
132
+ // --- document (optional) ---
133
+ // Only the type is checked here (must be a string). Whether it's a usable
134
+ // relative path (no `../` escape, not absolute) is left to the consumer's
135
+ // `normalizeBundlePath` at use time — see the type-level doc comment above
136
+ // for why: no other manifest field pre-jails a path-shaped value either,
137
+ // so enforcing it here would be inconsistent and would duplicate the one
138
+ // real jail point.
139
+ let document;
140
+ if (obj.document !== undefined) {
141
+ if (typeof obj.document !== 'string') {
142
+ errors.push('"document" must be a string');
143
+ }
144
+ else {
145
+ document = obj.document;
146
+ }
147
+ }
127
148
  // --- unknown top-level keys: forward-compat warning, not an error ---
128
149
  for (const key of Object.keys(obj)) {
129
150
  if (!KNOWN_TOP_LEVEL_KEYS.has(key)) {
@@ -138,6 +159,8 @@ export function parseManifest(json) {
138
159
  manifest.permissions = permissions;
139
160
  if (uses !== undefined)
140
161
  manifest.uses = uses;
162
+ if (document !== undefined)
163
+ manifest.document = document;
141
164
  return { ok: true, manifest, warnings };
142
165
  }
143
166
  /** A minimal, valid manifest for a freshly promoted bundle: no permissions granted, no packs declared. */
@@ -22,8 +22,9 @@ export interface ScriptView {
22
22
  * trust (their own note, a dev/test harness) — but it must always be an
23
23
  * explicit, named opt-in at the call site, never `createScriptView`'s
24
24
  * default. See the DEFECT-10 note on `createScriptView` for why: an
25
- * untrusted `.mkbundle` opened from elsewhere must never be able to grant
26
- * itself capabilities merely by declaring them in its own manifest.
25
+ * untrusted `.mkz` bundle (or its legacy `.mkbundle` counterpart) opened
26
+ * from elsewhere must never be able to grant itself capabilities merely by
27
+ * declaring them in its own manifest.
27
28
  */
28
29
  export declare function grantAllDeclaredPermissions(manifest: BundleManifest): BundlePermissions;
29
30
  /**
@@ -31,7 +32,7 @@ export declare function grantAllDeclaredPermissions(manifest: BundleManifest): B
31
32
  *
32
33
  * DEFECT 10 / spec §10: "Capabilities are declared in the manifest, granted
33
34
  * by the user" — declaring is not granting. `manifest.permissions` is
34
- * whatever the (possibly untrusted) `.mkbundle` *asks for*; `grantedPermissions`
35
+ * whatever the (possibly untrusted) `.mkz` bundle *asks for*; `grantedPermissions`
35
36
  * is whatever the user has actually *approved* for this note (e.g. via a
36
37
  * permission-prompt UI, remembered per note and re-prompted if scripts
37
38
  * change — see §10). The capability this view actually exposes is the
@@ -7,8 +7,9 @@ import { isWriteAllowed } from './paths.js';
7
7
  * trust (their own note, a dev/test harness) — but it must always be an
8
8
  * explicit, named opt-in at the call site, never `createScriptView`'s
9
9
  * default. See the DEFECT-10 note on `createScriptView` for why: an
10
- * untrusted `.mkbundle` opened from elsewhere must never be able to grant
11
- * itself capabilities merely by declaring them in its own manifest.
10
+ * untrusted `.mkz` bundle (or its legacy `.mkbundle` counterpart) opened
11
+ * from elsewhere must never be able to grant itself capabilities merely by
12
+ * declaring them in its own manifest.
12
13
  */
13
14
  export function grantAllDeclaredPermissions(manifest) {
14
15
  return manifest.permissions ?? {};
@@ -18,7 +19,7 @@ export function grantAllDeclaredPermissions(manifest) {
18
19
  *
19
20
  * DEFECT 10 / spec §10: "Capabilities are declared in the manifest, granted
20
21
  * by the user" — declaring is not granting. `manifest.permissions` is
21
- * whatever the (possibly untrusted) `.mkbundle` *asks for*; `grantedPermissions`
22
+ * whatever the (possibly untrusted) `.mkz` bundle *asks for*; `grantedPermissions`
22
23
  * is whatever the user has actually *approved* for this note (e.g. via a
23
24
  * permission-prompt UI, remembered per note and re-prompted if scripts
24
25
  * change — see §10). The capability this view actually exposes is the
package/dist/storage.d.ts CHANGED
@@ -22,6 +22,18 @@ export interface BundleStorage {
22
22
  /** All file paths currently in the bundle, bundle-relative, sorted. */
23
23
  list(): Promise<string[]>;
24
24
  exists(path: string): Promise<boolean>;
25
+ /**
26
+ * Returns the file's byte length, or `undefined` if no such path exists —
27
+ * WITHOUT reading (let alone inflating) its contents. This is what lets a
28
+ * caller (e.g. `buildBundleSnapshot` in `apps/vscode`) enforce a size
29
+ * budget by skipping an over-budget file before it is ever materialized in
30
+ * memory, rather than reading it whole and only then discovering it was too
31
+ * big. Must route through the exact same path-jail/symlink-refusal a
32
+ * `read` of the same path would (see the class doc comment above) — a
33
+ * caller must never be able to learn the size of, or prove the existence
34
+ * of, a path `read`/`write` would refuse.
35
+ */
36
+ size(path: string): Promise<number | undefined>;
25
37
  }
26
38
  /**
27
39
  * The shared choke point every `BundleStorage` implementation calls before
package/dist/zip.js CHANGED
@@ -31,6 +31,16 @@ function createMapStorage(map) {
31
31
  const normalized = normalizeOrThrow(path);
32
32
  return Promise.resolve(map.has(normalized));
33
33
  },
34
+ size(path) {
35
+ // The map already holds each entry's fully-decompressed bytes (this
36
+ // archive form eagerly inflates every entry up front, under
37
+ // `openZipBundle`'s own per-entry/total decompression-bomb caps — see
38
+ // that function's doc comment), so this is just the stored buffer's
39
+ // length: no re-inflation, no touching the archive bytes again.
40
+ const normalized = normalizeOrThrow(path);
41
+ const data = map.get(normalized);
42
+ return Promise.resolve(data === undefined ? undefined : data.length);
43
+ },
34
44
  };
35
45
  }
36
46
  // ---------------------------------------------------------------------------
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@markii/bundle",
3
- "version": "0.3.1",
4
- "description": "Bundle (.mkbundle) storage and policy layer for Mark: manifest handling, the bundle-relative path-jail, zip (fflate) and Node directory storage forms, and a capability-restricted script view. No React, no parsing.",
3
+ "version": "0.5.0",
4
+ "description": "Bundle (.mkz, formerly .mkbundle) storage and policy layer for Markii: manifest handling, the bundle-relative path-jail, zip (fflate) and Node directory storage forms, and a capability-restricted script view. No React, no parsing.",
5
5
  "keywords": [
6
6
  "markdown",
7
7
  "mark",