@poe-platform/safe-js 0.1.25 → 0.1.26

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
@@ -55,6 +55,29 @@ const result = await run(`
55
55
 
56
56
  Properties stay inside the interpreter, not on native host functions. Arrows and object methods remain nonconstructible. Prototype links between callable or exotic objects (such as arrays) and accessor descriptors are unsupported; native `Function.prototype` is never exposed.
57
57
 
58
+ <details>
59
+ <summary>Object inspection and prototypes</summary>
60
+
61
+ Ordinary objects inherit a sandbox-owned `Object.prototype`. Cached inspection works:
62
+
63
+ ```js
64
+ const result = await run(`
65
+ const inspect = ({}).toString;
66
+ return [inspect.call([]), inspect.call(new Date(0)),
67
+ Object.getPrototypeOf({}) === Object.prototype];
68
+ `);
69
+ // result.returnValue: ["[object Array]", "[object Date]", true]
70
+ ```
71
+
72
+ - `Object()` / `new Object()` create ordinary objects; passing an object preserves its identity.
73
+ - `toString`, `valueOf`, `hasOwnProperty`, `propertyIsEnumerable` and `isPrototypeOf` support ordinary inspection. Type tags use sandbox brands, not guest-supplied fields.
74
+ - Intrinsic methods are non-enumerable. Guest constructor prototypes inherit the ordinary Object prototype; explicit null/custom prototypes work with `Object.create`, `Object.setPrototypeOf` and literal `__proto__`. A computed `['__proto__']` remains an own data property.
75
+ - Prototype mutations stay inside the current run or persistent realm and consume its retained-data budget. They never change native prototypes or another realm.
76
+
77
+ Primitive boxing, inherited accessors, symbols and full Array/Function/exotic prototype graphs are unsupported. Use borrowed Object methods for inspecting those supported values. Explicit prototype links and mutated Object intrinsics are not portable checkpoint/copy data; project own data before crossing those boundaries. The conservative `AS011` lint rule still flags explicit `prototype`/`constructor` access; `run()` executes it without automatic linting.
78
+
79
+ </details>
80
+
58
81
  <details>
59
82
  <summary>Dates and clocks</summary>
60
83
 
@@ -6718,6 +6718,8 @@ function graphEntries(value) {
6718
6718
  var guestClosures = /* @__PURE__ */ new WeakSet();
6719
6719
  var functionProperties = /* @__PURE__ */ new WeakMap();
6720
6720
  var prototypes = /* @__PURE__ */ new WeakMap();
6721
+ var intrinsicPrototypes = /* @__PURE__ */ new WeakMap();
6722
+ var intrinsicConstructors = /* @__PURE__ */ new WeakMap();
6721
6723
  var descriptorObjects = /* @__PURE__ */ new WeakSet();
6722
6724
  function registerGuestClosure(closure) {
6723
6725
  guestClosures.add(closure);
@@ -6762,27 +6764,49 @@ function getGuestFunctionProperty(closure, key) {
6762
6764
  }
6763
6765
  return properties === void 0 ? void 0 : Object.getOwnPropertyDescriptor(properties, key)?.value;
6764
6766
  }
6765
- function getSandboxPrototype(value) {
6766
- return prototypes.get(value) ?? null;
6767
+ function installObjectPrototype(budget, prototype, constructor) {
6768
+ prototypes.set(prototype, null);
6769
+ intrinsicPrototypes.set(budget, prototype);
6770
+ const records = [prototype, materializeFunctionProperties(constructor)].map((value) => ({
6771
+ value,
6772
+ descriptors: new Map(Object.entries(Object.getOwnPropertyDescriptors(value)))
6773
+ }));
6774
+ const unchanged = (before, after) => before !== void 0 && after !== void 0 && before.value === after.value && before.writable === after.writable && before.configurable === after.configurable && before.enumerable === after.enumerable;
6775
+ intrinsicConstructors.set(constructor, () => records.every(({ value, descriptors }) => {
6776
+ const current = Object.getOwnPropertyDescriptors(value);
6777
+ return Object.keys(current).length === descriptors.size && Object.keys(current).every((key) => unchanged(descriptors.get(key), current[key]));
6778
+ }));
6779
+ budget.setRetainedValues(prototype, () => records.flatMap(({ value, descriptors }) => Object.entries(Object.getOwnPropertyDescriptors(value)).flatMap(([key, descriptor]) => unchanged(descriptors.get(key), descriptor) ? [] : [key, descriptor.value])));
6780
+ }
6781
+ function releaseObjectPrototype(budget) {
6782
+ const prototype = intrinsicPrototypes.get(budget);
6783
+ if (prototype !== void 0) budget.setRetainedValues(prototype, void 0);
6784
+ intrinsicPrototypes.delete(budget);
6785
+ }
6786
+ function getSandboxPrototype(value, budget) {
6787
+ if (prototypes.has(value)) return prototypes.get(value) ?? null;
6788
+ return budget !== void 0 && isPrototypeRecord(value) ? intrinsicPrototypes.get(budget) ?? null : null;
6767
6789
  }
6768
6790
  function setSandboxPrototype(value, prototype, budget) {
6791
+ if (budget !== void 0 && intrinsicPrototypes.get(budget) === value && prototype !== null) {
6792
+ throw new TypeError("Object.prototype has an immutable null prototype.");
6793
+ }
6769
6794
  if (!isPrototypeRecord(value) || prototype !== null && !isPrototypeRecord(prototype)) {
6770
6795
  throw new TypeError(
6771
6796
  "Prototype links require ordinary sandbox objects; callable and exotic prototype chains are not supported."
6772
6797
  );
6773
6798
  }
6774
- if (getSandboxPrototype(value) === prototype) return;
6799
+ if (prototypes.has(value) && getSandboxPrototype(value, budget) === prototype) return;
6775
6800
  if (!Object.isExtensible(isGuestClosure(value) ? materializeFunctionProperties(value) : value)) {
6776
6801
  throw new TypeError("Cannot change the prototype of a non-extensible object.");
6777
6802
  }
6778
6803
  let depth = 0;
6779
- for (let current = prototype; current !== null; current = getSandboxPrototype(current)) {
6804
+ for (let current = prototype; current !== null; current = getSandboxPrototype(current, budget)) {
6780
6805
  budget?.visitNode();
6781
6806
  assertSandboxDataDepth(depth++);
6782
6807
  if (current === value) throw new TypeError("Cyclic prototype value.");
6783
6808
  }
6784
- if (prototype === null) prototypes.delete(value);
6785
- else prototypes.set(value, prototype);
6809
+ prototypes.set(value, prototype);
6786
6810
  }
6787
6811
  function isPrototypeRecord(value) {
6788
6812
  if (isGuestHostObject(value)) return false;
@@ -6798,6 +6822,8 @@ function hasManagedDescriptors(value) {
6798
6822
  return descriptorObjects.has(value);
6799
6823
  }
6800
6824
  function hasGuestObjectState(value) {
6825
+ const intrinsicUnchanged = intrinsicConstructors.get(value);
6826
+ if (intrinsicUnchanged !== void 0) return !intrinsicUnchanged();
6801
6827
  if (isLiveCapability(value)) return true;
6802
6828
  if (functionProperties.has(value) || prototypes.has(value)) return true;
6803
6829
  return descriptorObjects.has(value) && Object.values(Object.getOwnPropertyDescriptors(value)).some(
@@ -9504,6 +9530,7 @@ function createSandboxClosure(input) {
9504
9530
  if (input.sandbox === true) {
9505
9531
  Object.defineProperty(closure, "sandbox", { value: true });
9506
9532
  }
9533
+ if (input.generator === true) Object.defineProperty(closure, "generator", { value: true });
9507
9534
  if (input.length !== void 0) {
9508
9535
  Object.defineProperty(closure, "length", { value: input.length });
9509
9536
  }
@@ -9791,7 +9818,7 @@ function measureSandboxData(values, options = {}) {
9791
9818
  function reconcileCompiledValues(budget, values, compilation, parent, escaping = []) {
9792
9819
  while (parent?.closed) parent = parent.parent;
9793
9820
  const included = /* @__PURE__ */ new Set();
9794
- const usage = measureSandboxData(values, { compileTickets: included });
9821
+ const usage = measureSandboxData([...values, ...budget.retainedValues()], { compileTickets: included });
9795
9822
  const kept = /* @__PURE__ */ new Set();
9796
9823
  if (parent !== void 0) measureSandboxData(escaping, { compileTickets: kept });
9797
9824
  const transferred = /* @__PURE__ */ new Set();
@@ -25628,6 +25655,9 @@ async function evaluateObjectExpression(node, context) {
25628
25655
  return value;
25629
25656
  }
25630
25657
  if (isObjectPrototypeSetterProperty(property, key.value)) {
25658
+ if (value.value === null || typeof value.value === "object") {
25659
+ setSandboxPrototype(object, value.value, context.budget);
25660
+ }
25631
25661
  continue;
25632
25662
  }
25633
25663
  defineSandboxProperty(object, String(key.value), value.value);
@@ -26432,7 +26462,7 @@ function forInKeys(object, budget) {
26432
26462
  const keys = [];
26433
26463
  const seen = /* @__PURE__ */ new Set();
26434
26464
  let depth = 0;
26435
- for (let current = object; current !== null; current = getSandboxPrototype(current)) {
26465
+ for (let current = object; current !== null; current = getSandboxPrototype(current, budget)) {
26436
26466
  if (depth > 0) budget.visitNode();
26437
26467
  assertSandboxDataDepth(depth++);
26438
26468
  const properties = isGuestClosure(current) ? materializeFunctionProperties(current) : isSandboxClosure(current) ? current.properties ?? {} : current;
@@ -26448,7 +26478,7 @@ function forInKeys(object, budget) {
26448
26478
  function hasForInProperty(object, key, budget) {
26449
26479
  if (isGuestHostObject(object)) return getHostObjectKeys(object).includes(key);
26450
26480
  let depth = 0;
26451
- for (let current = object; current !== null; current = getSandboxPrototype(current)) {
26481
+ for (let current = object; current !== null; current = getSandboxPrototype(current, budget)) {
26452
26482
  if (depth > 0) budget.visitNode();
26453
26483
  assertSandboxDataDepth(depth++);
26454
26484
  const properties = isGuestClosure(current) ? materializeFunctionProperties(current) : isSandboxClosure(current) ? current.properties ?? {} : current;
@@ -27449,7 +27479,7 @@ function applyBinaryOperator(node, left, right, context) {
27449
27479
  throw new TypeError("Function has a non-object prototype in instanceof check.");
27450
27480
  }
27451
27481
  let depth = 0;
27452
- for (let current = getSandboxPrototype(left); current !== null; current = getSandboxPrototype(current)) {
27482
+ for (let current = getSandboxPrototype(left, context.budget); current !== null; current = getSandboxPrototype(current, context.budget)) {
27453
27483
  context.budget.visitNode();
27454
27484
  assertSandboxDataDepth(depth++);
27455
27485
  if (current === prototype) return true;
@@ -27663,7 +27693,7 @@ function getMemberValue(target, property, context) {
27663
27693
  return getPropertyValue(current, property, context);
27664
27694
  }
27665
27695
  if (Object.hasOwn(current, String(property))) return current[String(property)];
27666
- current = getSandboxPrototype(current);
27696
+ current = getSandboxPrototype(current, context.budget);
27667
27697
  if (current !== null) {
27668
27698
  context.budget.visitNode();
27669
27699
  assertSandboxDataDepth(++depth);
@@ -27713,7 +27743,7 @@ function setSandboxProperty(target, property, value, budget) {
27713
27743
  } else {
27714
27744
  if (typeof prototypeOwner === "object" && prototypeOwner !== null) {
27715
27745
  let depth = 0;
27716
- for (let prototype = getSandboxPrototype(prototypeOwner); prototype !== null; prototype = getSandboxPrototype(prototype)) {
27746
+ for (let prototype = getSandboxPrototype(prototypeOwner, budget); prototype !== null; prototype = getSandboxPrototype(prototype, budget)) {
27717
27747
  budget.visitNode();
27718
27748
  assertSandboxDataDepth(depth++);
27719
27749
  const properties = isSandboxClosure(prototype) ? prototype.properties : prototype;
@@ -28089,6 +28119,7 @@ function createInterpretedClosure(node, context, evaluateNode2) {
28089
28119
  function createGeneratorClosure(node, context, evaluateNode2) {
28090
28120
  return createSandboxClosure({
28091
28121
  guest: true,
28122
+ generator: true,
28092
28123
  sandbox: true,
28093
28124
  length: getFunctionLength(node.params),
28094
28125
  ...node.id === void 0 ? {} : { name: node.id.name },
@@ -29827,10 +29858,138 @@ function ownDataValue(value, name) {
29827
29858
  return descriptor?.value;
29828
29859
  }
29829
29860
 
29861
+ // packages/safe-js/src/interp/globals/object.ts
29862
+ function createObjectGlobal(methods, budget) {
29863
+ const construct = ([value]) => {
29864
+ if (value === null || value === void 0) {
29865
+ budget.chargeDataUsage(1);
29866
+ return /* @__PURE__ */ Object.create(null);
29867
+ }
29868
+ if (typeof value !== "object") throw new TypeError("Object primitive boxing is not supported.");
29869
+ return value;
29870
+ };
29871
+ const constructor = createSandboxClosure({
29872
+ guest: true,
29873
+ sandbox: true,
29874
+ name: "Object",
29875
+ length: 1,
29876
+ call: construct,
29877
+ construct
29878
+ });
29879
+ const properties = materializeFunctionProperties(constructor);
29880
+ const prototype = properties.prototype;
29881
+ Object.defineProperty(properties, "prototype", { writable: false });
29882
+ for (const [name, method] of Object.entries(methods)) {
29883
+ Object.defineProperty(properties, name, { value: method, writable: true, configurable: true });
29884
+ }
29885
+ const prototypeMethods = {
29886
+ toString: createSandboxClosure({
29887
+ sandbox: true,
29888
+ name: "toString",
29889
+ length: 0,
29890
+ call: (_args, context) => budget.allocateString(`[object ${typeTag(context?.thisValue)}]`)
29891
+ }),
29892
+ valueOf: createSandboxClosure({
29893
+ sandbox: true,
29894
+ name: "valueOf",
29895
+ length: 0,
29896
+ call: (_args, context) => {
29897
+ const value = requireReceiver(context?.thisValue);
29898
+ if (typeof value !== "object")
29899
+ throw new TypeError("Object primitive boxing is not supported.");
29900
+ return value;
29901
+ }
29902
+ }),
29903
+ hasOwnProperty: createSandboxClosure({
29904
+ sandbox: true,
29905
+ name: "hasOwnProperty",
29906
+ length: 1,
29907
+ call: async ([key], context) => hasOwnSandboxProperty(
29908
+ requireReceiver(context?.thisValue),
29909
+ await sandboxString(key, budget, context),
29910
+ false
29911
+ )
29912
+ }),
29913
+ propertyIsEnumerable: createSandboxClosure({
29914
+ sandbox: true,
29915
+ name: "propertyIsEnumerable",
29916
+ length: 1,
29917
+ call: async ([key], context) => hasOwnSandboxProperty(
29918
+ requireReceiver(context?.thisValue),
29919
+ await sandboxString(key, budget, context),
29920
+ true
29921
+ )
29922
+ }),
29923
+ isPrototypeOf: createSandboxClosure({
29924
+ sandbox: true,
29925
+ name: "isPrototypeOf",
29926
+ length: 1,
29927
+ call: ([value], context) => {
29928
+ if (typeof value !== "object" || value === null) return false;
29929
+ const receiver = requireReceiver(context?.thisValue);
29930
+ let depth = 0;
29931
+ for (let current = getSandboxPrototype(value, budget); current !== null; current = getSandboxPrototype(current, budget)) {
29932
+ budget.visitNode();
29933
+ assertSandboxDataDepth(depth++);
29934
+ if (current === receiver) return true;
29935
+ }
29936
+ return false;
29937
+ }
29938
+ })
29939
+ };
29940
+ for (const [name, method] of Object.entries(prototypeMethods)) {
29941
+ Object.defineProperty(prototype, name, { value: method, writable: true, configurable: true });
29942
+ }
29943
+ markDescriptorObject(prototype);
29944
+ installObjectPrototype(budget, prototype, constructor);
29945
+ return constructor;
29946
+ }
29947
+ function requireReceiver(value) {
29948
+ if (value === null || value === void 0)
29949
+ throw new TypeError("Object method requires a non-null receiver.");
29950
+ return value;
29951
+ }
29952
+ function hasOwnSandboxProperty(value, key, enumerable) {
29953
+ requireReceiver(value);
29954
+ if (isGuestHostObject(value)) return getHostObjectKeys(value).includes(key);
29955
+ let properties;
29956
+ if (isGuestClosure(value)) properties = materializeFunctionProperties(value);
29957
+ else if (isSandboxClosure(value)) {
29958
+ if (key === "length" || key === "name") return !enumerable;
29959
+ properties = value.properties ?? /* @__PURE__ */ Object.create(null);
29960
+ } else if (isSandboxMap(value) || isSandboxSet(value) || isSandboxPromise(value) || isSandboxGenerator(value))
29961
+ return false;
29962
+ else if (isSandboxRegex(value)) return key === "lastIndex" && !enumerable;
29963
+ else properties = Object(value);
29964
+ const descriptor = Object.getOwnPropertyDescriptor(properties, key);
29965
+ return descriptor !== void 0 && (!enumerable || descriptor.enumerable === true);
29966
+ }
29967
+ function typeTag(value) {
29968
+ if (value === void 0) return "Undefined";
29969
+ if (value === null) return "Null";
29970
+ if (typeof value === "string") return "String";
29971
+ if (typeof value === "number") return "Number";
29972
+ if (typeof value === "boolean") return "Boolean";
29973
+ if (isSandboxClosure(value)) {
29974
+ while (value.boundTarget !== void 0) value = value.boundTarget;
29975
+ return value.generator ? "GeneratorFunction" : value.async ? "AsyncFunction" : "Function";
29976
+ }
29977
+ if (Array.isArray(value)) return "Array";
29978
+ if (isSandboxDate(value)) return "Date";
29979
+ if (isSandboxErrorConstructorInstance(value, "Error")) return "Error";
29980
+ if (isSandboxRegex(value)) return "RegExp";
29981
+ if (isSandboxMap(value)) return "Map";
29982
+ if (isSandboxSet(value)) return "Set";
29983
+ if (isSandboxPromise(value)) return "Promise";
29984
+ if (isSandboxGenerator(value)) return "Generator";
29985
+ if (isFloat32Array(value)) return "Float32Array";
29986
+ return "Object";
29987
+ }
29988
+
29830
29989
  // packages/safe-js/src/interp/globals/object-array.ts
29831
29990
  function createObjectArrayGlobals(options) {
29832
29991
  return {
29833
- Object: {
29992
+ Object: createObjectGlobal({
29834
29993
  keys: createSandboxClosure({
29835
29994
  sandbox: true,
29836
29995
  call: ([value]) => budgetSandboxValue2(getOwnEnumerableKeys(value), options.budget),
@@ -29848,7 +30007,11 @@ function createObjectArrayGlobals(options) {
29848
30007
  }),
29849
30008
  hasOwn: createSandboxClosure({
29850
30009
  sandbox: true,
29851
- call: ([value, key]) => Reflect.apply(Object.hasOwn, Object, [isSandboxClosure(value) ? objectProperties(value) : value, key]),
30010
+ call: ([value, key], context) => {
30011
+ if (value === null || value === void 0) throw new TypeError("Cannot convert undefined or null to object.");
30012
+ const name = sandboxString(key, options.budget, context);
30013
+ return typeof name === "string" ? hasOwnSandboxProperty(value, name, false) : name.then((property) => hasOwnSandboxProperty(value, property, false));
30014
+ },
29852
30015
  name: "hasOwn"
29853
30016
  }),
29854
30017
  getOwnPropertyDescriptor: createSandboxClosure({
@@ -29889,7 +30052,7 @@ function createObjectArrayGlobals(options) {
29889
30052
  call: ([value]) => {
29890
30053
  if (isSandboxDate(value)) return getDatePrototype(value, options.budget, options.compileOwner);
29891
30054
  objectProperties(value);
29892
- return getSandboxPrototype(value);
30055
+ return getSandboxPrototype(value, options.budget);
29893
30056
  },
29894
30057
  name: "getPrototypeOf"
29895
30058
  }),
@@ -29964,7 +30127,7 @@ function createObjectArrayGlobals(options) {
29964
30127
  call: ([target, ...sources]) => assignSandboxValues(target, sources, options.budget),
29965
30128
  name: "assign"
29966
30129
  })
29967
- },
30130
+ }, options.budget),
29968
30131
  Array: createSandboxClosure({
29969
30132
  sandbox: true,
29970
30133
  call: (args) => createArrayFromConstructorArgs(args, options.budget),
@@ -30491,6 +30654,7 @@ var RealmState = class {
30491
30654
  this.budget.setRetainedValues(this, this.retainedRoots);
30492
30655
  this.tracker.onFatalRejection((error) => this.poison(error));
30493
30656
  } catch (error) {
30657
+ releaseObjectPrototype(this.budget);
30494
30658
  this.compilation.dispose();
30495
30659
  this.lease.release();
30496
30660
  throw error;
@@ -31057,6 +31221,7 @@ var RealmState = class {
31057
31221
  for (const reference of this.guestReferences.keys()) revokeGuestReference(reference, this);
31058
31222
  this.guestReferences.clear();
31059
31223
  this.budget.setRetainedValues(this, void 0);
31224
+ releaseObjectPrototype(this.budget);
31060
31225
  this.disposal = (async () => {
31061
31226
  const errors = [];
31062
31227
  for (const cleanup of this.cleanups.splice(0).reverse()) {
@@ -32065,6 +32230,7 @@ function run(source, options = {}) {
32065
32230
  }
32066
32231
  });
32067
32232
  } finally {
32233
+ releaseObjectPrototype(budget);
32068
32234
  compilation.dispose();
32069
32235
  operation.release();
32070
32236
  }
@@ -32307,4 +32473,4 @@ export {
32307
32473
  FileSnapshotBackend,
32308
32474
  run
32309
32475
  };
32310
- //# sourceMappingURL=chunk-BWVASPJX.js.map
32476
+ //# sourceMappingURL=chunk-2LVAGOJR.js.map