@markii/lua 0.17.0-next.2 → 0.17.0-next.3

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.
@@ -199,9 +199,9 @@ export function buildCapabilities(config) {
199
199
  // this function nested POST/PATCH wiring inside "if GET is granted",
200
200
  // which silently produced no `net.post` at all for a POST-only grant.
201
201
  //
202
- // The `net` table itself, and every one of its three methods, is now
203
- // ALWAYS wired, regardless of grants, provider wiring, or tier (GitHub
204
- // capability-stub finding, batch 7 #47). Before this, an ungranted
202
+ // The `net` table itself, and every one of its three methods, is
203
+ // ALWAYS wired, regardless of grants, provider wiring, or tier. Were it
204
+ // not, an ungranted
205
205
  // script saw a missing global (`net` itself omitted when nothing was
206
206
  // granted, or a missing method when a specific host/verb wasn't) and
207
207
  // failed with an ordinary Lua "attempt to index/call a nil value" —
@@ -270,8 +270,8 @@ local __smd_net_get = __smd_net_get_raw
270
270
  -- a dynamic global lookup inside \`net.fetch_json\`'s own body. Otherwise a
271
271
  -- script could do \`__smd_json_decode = function(t) return t end\` before a
272
272
  -- LATER \`net.fetch_json\` call and neuter the decoder's own depth guard and
273
- -- array-marker stripping entirely (adversarial finding A2) -- the same
274
- -- rebinding risk \`./json-decode\`'s own doc comment (finding A1) already
273
+ -- array-marker stripping entirely -- the same
274
+ -- rebinding risk \`./json-decode\`'s own doc comment already
275
275
  -- closes for the primitives the decoder uses internally.
276
276
  local __smd_net_get_json_decode = __smd_json_decode
277
277
  __smd_net_get_raw = nil
@@ -415,7 +415,7 @@ end
415
415
  return undefined;
416
416
  // Depth/node budget, checked BEFORE the stored value is ever encoded
417
417
  // to text and handed to Lua — mirrors `net.fetch_json`'s own
418
- // pre-check exactly (adversarial finding B2). A host-stored value is
418
+ // pre-check exactly. A host-stored value is
419
419
  // exactly as untrusted as a remote fetch body — a bundle's
420
420
  // `.cache/*.json` file, for instance, can be edited by anything with
421
421
  // write access to the bundle, not just this sandbox's own WRITE side
@@ -424,16 +424,14 @@ end
424
424
  // already capped.
425
425
  //
426
426
  // An entry that fails the budget check, isn't a plain object at all,
427
- // or carries a missing/non-finite `storedAtMs` (adversarial finding:
428
- // a poisoned host snapshot like `{value: 'x'}` with no `storedAtMs`,
427
+ // or carries a missing/non-finite `storedAtMs` (a poisoned host snapshot like `{value: 'x'}` with no `storedAtMs`,
429
428
  // or `storedAtMs: 'abc'`, would otherwise reach the Lua-side
430
429
  // `now - storedAtMs` arithmetic in `cache.get` below and throw a
431
430
  // script-error, wedging the note permanently instead of self-healing)
432
431
  // — or that cannot be `JSON.stringify`'d at all (cyclic, or
433
432
  // BigInt-bearing — something other than this sandbox's own WRITE
434
433
  // side must have written it, since that side already rejects both) —
435
- // is SELF-HEALED rather than denied (orchestrator decision, #6
436
- // verification notes): it is treated as a cache MISS, exactly as if
434
+ // is SELF-HEALED rather than denied (by design): it is treated as a cache MISS, exactly as if
437
435
  // `key` had never been stored. `cache.get`'s Lua body (below) then
438
436
  // calls `fn()` and writes the fresh, already-capped result back
439
437
  // through `__smd_cache_set_raw`, which quietly repairs the stored
@@ -441,7 +439,7 @@ end
441
439
  // capability denial is reserved for the fresh recompute itself
442
440
  // failing the WRITE side's own caps, which is unchanged.
443
441
  //
444
- // Finiteness alone isn't enough (pentest finding N-1): a poisoned
442
+ // Finiteness alone isn't enough: a poisoned
445
443
  // snapshot with a huge FUTURE `storedAtMs` (e.g.
446
444
  // `Number.MAX_SAFE_INTEGER * 1000`) is finite, so it used to pass
447
445
  // straight through to the Lua-side `(now - storedAtMs) < ttl*1000`
@@ -501,10 +499,10 @@ local __smd_cache_get = __smd_cache_get_raw
501
499
  local __smd_cache_set = __smd_cache_set_raw
502
500
  local __smd_now_ms = __smd_now_ms_raw
503
501
  -- Captured into locals HERE, at prelude-definition time -- NOT resolved as
504
- -- dynamic globals inside cache.get's own body (adversarial findings A2 and
505
- -- D1). Without this, a script could do \`__smd_json_decode = function(t)
506
- -- return t end\` (A2) or, more seriously, \`__smd_marshal_root = function(v)
507
- -- return v end\` (D1) before a LATER cache.get call: the marshal-root
502
+ -- dynamic globals inside cache.get's own body. Without this, a script
503
+ -- could do \`__smd_json_decode = function(t)
504
+ -- return t end\` or, more seriously, \`__smd_marshal_root = function(v)
505
+ -- return v end\` before a LATER cache.get call: the marshal-root
508
506
  -- rebind would send an UNBOUNDED, uncapped table straight through
509
507
  -- wasmoon's own eager table->JS conversion into the store the moment the
510
508
  -- rebound "identity" function handed it back, since the write side
@@ -68,8 +68,7 @@ import { type MarshalLimits } from './marshal.js';
68
68
  * cap that already bounds the JS-side parse.
69
69
  *
70
70
  * ## Every Lua primitive this decoder uses is pinned at PRELUDE-DEFINITION
71
- * ## time, not resolved as a dynamic global at call time (adversarial
72
- * ## finding A1)
71
+ * ## time, not resolved as a dynamic global at call time
73
72
  *
74
73
  * `string.byte`/`sub`/`find`/`char`, `table.concat`, `tonumber`,
75
74
  * `math.floor`, and `error` are all captured into locals ONCE, at the top
@@ -84,6 +83,6 @@ import { type MarshalLimits } from './marshal.js';
84
83
  * the depth guard entirely, since `fail()` would then no longer actually
85
84
  * raise). This mirrors `./marshal`'s own `buildMarshalPrelude`, which
86
85
  * already does exactly this for the same reason (see that module's doc
87
- * comment referencing sandbox-audit finding F-1).
86
+ * comment).
88
87
  */
89
88
  export declare function buildJsonDecodePrelude(limits: MarshalLimits): string;
@@ -69,8 +69,7 @@ import { ARRAY_MARKER } from './marshal.js';
69
69
  * cap that already bounds the JS-side parse.
70
70
  *
71
71
  * ## Every Lua primitive this decoder uses is pinned at PRELUDE-DEFINITION
72
- * ## time, not resolved as a dynamic global at call time (adversarial
73
- * ## finding A1)
72
+ * ## time, not resolved as a dynamic global at call time
74
73
  *
75
74
  * `string.byte`/`sub`/`find`/`char`, `table.concat`, `tonumber`,
76
75
  * `math.floor`, and `error` are all captured into locals ONCE, at the top
@@ -85,7 +84,7 @@ import { ARRAY_MARKER } from './marshal.js';
85
84
  * the depth guard entirely, since `fail()` would then no longer actually
86
85
  * raise). This mirrors `./marshal`'s own `buildMarshalPrelude`, which
87
86
  * already does exactly this for the same reason (see that module's doc
88
- * comment referencing sandbox-audit finding F-1).
87
+ * comment).
89
88
  */
90
89
  export function buildJsonDecodePrelude(limits) {
91
90
  return `
@@ -246,7 +245,7 @@ function __smd_json_decode(__smd_json_text)
246
245
  -- \`{"${ARRAY_MARKER}":true,"1":"a","2":"b","x":"kept"}\` would decode
247
246
  -- to a Lua table carrying that same key, and later marshal to the
248
247
  -- host as a JS ARRAY -- silently reshaping attacker-controlled data
249
- -- and dropping its real keys (adversarial finding A4). Dropping
248
+ -- and dropping its real keys (see marshal.ts). Dropping
250
249
  -- (rather than rejecting the whole response) keeps an otherwise
251
250
  -- ordinary API response usable even if it happens to use this exact
252
251
  -- field name for something unrelated.
@@ -281,7 +280,7 @@ function __smd_json_decode(__smd_json_text)
281
280
  local value = decode_value(depth + 1)
282
281
  n = n + 1
283
282
  -- A JSON null ARRAY ELEMENT decodes to Lua \`false\`, not an absent
284
- -- slot (orchestrator decision, adversarial finding C2): an absent
283
+ -- slot (by design): an absent
285
284
  -- slot would leave a hole (\`#\`/\`ipairs\` stop early, and returning
286
285
  -- the table to the host fails \`MARK_MARSHAL:key-type\` since the walk
287
286
  -- no longer sees sequential \`1..n\` keys) -- reproducing issue #6's
@@ -78,8 +78,8 @@ export interface JsonTableConfig {
78
78
  * PRELUDE-DEFINITION TIME -- before any untrusted script code runs -- so a
79
79
  * later script reassigning any of those globals (or `json` itself) cannot
80
80
  * neuter the type check or reach past the reused decoder/marshal walk. This
81
- * is the same discipline `./json-decode` (finding A1) and `./capabilities`
82
- * (findings A2/D1) already apply to their own internal calls into these
81
+ * is the same discipline `./json-decode` and `./capabilities`
82
+ * already apply to their own internal calls into these
83
83
  * exact two functions.
84
84
  */
85
85
  export declare function buildJsonTable(config: JsonTableConfig): {
@@ -64,8 +64,8 @@ import { checkJsonWithinLimits, finalizeMarshaledValue, } from './marshal.js';
64
64
  * PRELUDE-DEFINITION TIME -- before any untrusted script code runs -- so a
65
65
  * later script reassigning any of those globals (or `json` itself) cannot
66
66
  * neuter the type check or reach past the reused decoder/marshal walk. This
67
- * is the same discipline `./json-decode` (finding A1) and `./capabilities`
68
- * (findings A2/D1) already apply to their own internal calls into these
67
+ * is the same discipline `./json-decode` and `./capabilities`
68
+ * already apply to their own internal calls into these
69
69
  * exact two functions.
70
70
  */
71
71
  export function buildJsonTable(config) {
package/dist/marshal.d.ts CHANGED
@@ -121,8 +121,7 @@ export declare function checkJsonWithinLimits(value: unknown, limits: MarshalLim
121
121
  * True array marker set by the Lua-side marshal walk (see
122
122
  * `buildMarshalPrelude`). Exported (not just module-private) so
123
123
  * `./json-decode`'s JSON decoder can recognize and DROP this exact key if
124
- * it ever appears as an object key in attacker-controlled JSON (adversarial
125
- * finding A4): without that, a remote response body like
124
+ * it ever appears as an object key in attacker-controlled JSON: without that, a remote response body like
126
125
  * `{"__smd_is_array":true,"1":"a","2":"b","x":"kept"}` would decode to an
127
126
  * ordinary Lua table carrying this same marker key, and `finalizeMarshaledValue`
128
127
  * below — which has no way to tell "the trusted marshal walk set this" apart
package/dist/marshal.js CHANGED
@@ -78,9 +78,9 @@ export function buildMarshalPrelude(limits) {
78
78
  -- once per engine, before any untrusted user code runs), so the walk below
79
79
  -- closes over these as upvalues fixed to the genuine primitives -- immune to
80
80
  -- a later script doing e.g. \`error = function() end\` in the shared globals
81
- -- table before this walk runs. See finding F-1 in the sandbox audit: the
82
- -- walk previously resolved error/type/pairs/math.floor as DYNAMIC GLOBAL
83
- -- lookups, which a script could rebind to neuter its own caps.
81
+ -- table before this walk runs. Resolving error/type/pairs/math.floor as
82
+ -- DYNAMIC GLOBAL lookups would let a script rebind them to neuter its own
83
+ -- caps.
84
84
  local error, type, pairs, floor, sfind = error, type, pairs, math.floor, string.find
85
85
 
86
86
  local function __smd_marshal(value, seen, depth, budget)
@@ -222,8 +222,7 @@ export function checkJsonWithinLimits(value, limits) {
222
222
  * True array marker set by the Lua-side marshal walk (see
223
223
  * `buildMarshalPrelude`). Exported (not just module-private) so
224
224
  * `./json-decode`'s JSON decoder can recognize and DROP this exact key if
225
- * it ever appears as an object key in attacker-controlled JSON (adversarial
226
- * finding A4): without that, a remote response body like
225
+ * it ever appears as an object key in attacker-controlled JSON: without that, a remote response body like
227
226
  * `{"__smd_is_array":true,"1":"a","2":"b","x":"kept"}` would decode to an
228
227
  * ordinary Lua table carrying this same marker key, and `finalizeMarshaledValue`
229
228
  * below — which has no way to tell "the trusted marshal walk set this" apart
package/dist/sandbox.js CHANGED
@@ -476,8 +476,7 @@ export async function runScript(options) {
476
476
  // throws synchronously inside the try — e.g. `finalizeMarshaledValue`
477
477
  // recursing deep enough to overflow the JS call stack, which today is
478
478
  // safe only INCIDENTALLY because wasmoon's own `getValue` conversion
479
- // overflows first on sufficiently deep input (see the sandbox audit's
480
- // finding F-1 "also recommended" note) — so that guarantee no longer
479
+ // overflows first on sufficiently deep input — so that guarantee no longer
481
480
  // depends on that ordering holding forever. Classified the same as any
482
481
  // other unclassified failure: a plain `'runtime'` failure, never a raw
483
482
  // throw out of `runScript`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markii/lua",
3
- "version": "0.17.0-next.2",
3
+ "version": "0.17.0-next.3",
4
4
  "description": "Sandboxed Lua 5.4 (wasmoon) execution runtime for Mark's document scripting: an empty-env global whitelist, two-tier capability-gated net/cache/bundle access, instruction-count/wall-clock/memory limits, and depth/size-capped Lua<->JS marshaling.",
5
5
  "keywords": [
6
6
  "markdown",
@@ -44,8 +44,8 @@
44
44
  "lint": "eslint ."
45
45
  },
46
46
  "dependencies": {
47
- "@markii/bundle": "^0.17.0-next.2",
48
- "@markii/runtime": "^0.17.0-next.2",
47
+ "@markii/bundle": "^0.17.0-next.3",
48
+ "@markii/runtime": "^0.17.0-next.3",
49
49
  "wasmoon": "^1.16.0"
50
50
  }
51
51
  }