fix: unserialize makes copyRecords without problematic assign semantics
MODIFY
packages/marshal/src/encodeToCapData.js
packages/marshal/src/encodeToCapData.js
@@ -34,6 +34,7 @@ const {
getOwnPropertyDescriptors,
defineProperties,
is,
+ entries,
fromEntries,
freeze,
} = Object;
@@ -299,16 +300,15 @@ export const makeDecodeFromCapData = ({
// primitives pass through
return jsonEncoded;
}
- // Assertions of the above to narrow the type.
- assert(isObject(jsonEncoded));
- if (hasQClass(jsonEncoded)) {
+ if (isArray(jsonEncoded)) {
+ return jsonEncoded.map(encodedVal => decodeFromCapData(encodedVal));
+ } else if (hasQClass(jsonEncoded)) {
const qclass = jsonEncoded[QCLASS];
assert.typeof(
qclass,
'string',
X`invalid qclass typeof ${q(typeof qclass)}`,
);
- assert(!isArray(jsonEncoded));
switch (qclass) {
// Encoding of primitives not handled by JSON
case 'undefined': {
@@ -423,25 +423,17 @@ export const makeDecodeFromCapData = ({
assert.fail(X`unrecognized ${q(QCLASS)} ${q(qclass)}`, TypeError);
}
}
- } else if (isArray(jsonEncoded)) {
- const result = [];
- const { length } = jsonEncoded;
- for (let i = 0; i < length; i += 1) {
- result[i] = decodeFromCapData(jsonEncoded[i]);
- }
- return result;
} else {
assert(typeof jsonEncoded === 'object' && jsonEncoded !== null);
- const result = {};
- for (const name of ownKeys(jsonEncoded)) {
- assert.typeof(
- name,
- 'string',
- X`Property ${name} of ${jsonEncoded} must be a string`,
+ const decodeEntry = ([name, encodedVal]) => {
+ typeof name === 'string' ||
+ assert.fail(
+ X`Property ${q(name)} of ${jsonEncoded} must be a string`,
);
- result[name] = decodeFromCapData(jsonEncoded[name]);
- }
- return result;
+ return [name, decodeFromCapData(encodedVal)];
+ };
+ const decodedEntries = entries(jsonEncoded).map(decodeEntry);
+ return fromEntries(decodedEntries);
}
};
return harden(decodeFromCapData);

MODIFY
packages/marshal/src/encodeToSmallcaps.js
packages/marshal/src/encodeToSmallcaps.js
@@ -28,7 +28,7 @@ import {
const { ownKeys } = Reflect;
const { isArray } = Array;
-const { is, fromEntries } = Object;
+const { is, entries, fromEntries } = Object;
const { details: X, quote: q } = assert;
const BANG = '!'.charCodeAt(0);
@@ -471,17 +471,13 @@ export const makeDecodeFromSmallcaps = ({
return result;
}
- const result = {};
- for (const encodedName of ownKeys(encoding)) {
- // TypeScript confused about `||` control flow so use `if` instead
- // https://github.com/microsoft/TypeScript/issues/50739
- if (typeof encodedName !== 'string') {
+ const decodeEntry = ([encodedName, encodedVal]) => {
+ typeof encodedName === 'string' ||
assert.fail(
X`Property name ${q(
encodedName,
)} of ${encoding} must be a string`,
);
- }
!encodedName.startsWith('#') ||
assert.fail(
X`Unrecognized record type ${q(encodedName)}: ${encoding}`,
@@ -491,9 +487,10 @@ export const makeDecodeFromSmallcaps = ({
assert.fail(
X`Decoded property name ${name} from ${encoding} must be a string`,
);
- result[name] = decodeFromSmallcaps(encoding[encodedName]);
- }
- return result;
+ return [name, decodeFromSmallcaps(encodedVal)];
+ };
+ const decodedEntries = entries(encoding).map(decodeEntry);
+ return fromEntries(decodedEntries);
}
default: {
assert.fail(

MODIFY
packages/marshal/test/test-marshal-capdata.js
packages/marshal/test/test-marshal-capdata.js
@@ -6,6 +6,7 @@ import { passStyleOf } from '../src/passStyleOf.js';
import { makeMarshal } from '../src/marshal.js';
import { makeTagged } from '../src/makeTagged.js';
+import { Far } from '../src/make-far.js';
const { freeze, isFrozen, create, prototype: objectPrototype } = Object;
@@ -37,6 +38,14 @@ export const roundTripPairs = harden([
['abc', 'abc'],
[null, null],
+ // proto problems
+ // The one case where JSON is not a semantic subset of JS
+ // Fails before https://github.com/endojs/endo/issues/1303 fix
+ [{ ['__proto__']: {} }, { ['__proto__']: {} }],
+ // Conflicts with non-overwrite-enable inherited frozen data property
+ // Fails before https://github.com/endojs/endo/issues/1303 fix
+ [{ isPrototypeOf: {} }, { isPrototypeOf: {} }],
+
// Scalars not represented in JSON
[undefined, { '@qclass': 'undefined' }],
[NaN, { '@qclass': 'NaN' }],
@@ -371,3 +380,28 @@ test('records', t => {
shouldThrow(['nonenumStringData'], REC_ONLYENUM);
shouldThrow(['nonenumStringData', 'enumStringData'], REC_ONLYENUM);
});
+
+test('capdata proto problems', t => {
+ function convertValToSlot(_val) {
+ return 'slot';
+ }
+ const exampleAlice = Far('Alice', {});
+ function convertSlotToVal(_slot) {
+ return exampleAlice;
+ }
+ const { serialize: toCapData, unserialize: fromCapData } = makeMarshal(
+ convertValToSlot,
+ convertSlotToVal,
+ {
+ serializeBodyFormat: 'capdata',
+ },
+ );
+ const wrongProto = harden({ ['__proto__']: exampleAlice });
+ const wrongProtoCapData = toCapData(wrongProto);
+ t.deepEqual(wrongProtoCapData, {
+ body: '{"__proto__":{"@qclass":"slot","iface":"Alleged: Alice","index":0}}',
+ slots: ['slot'],
+ });
+ // Fails before https://github.com/endojs/endo/issues/1303 fix
+ t.deepEqual(fromCapData(wrongProtoCapData), wrongProto);
+});

MODIFY
packages/marshal/test/test-marshal-smallcaps.js
packages/marshal/test/test-marshal-smallcaps.js
@@ -398,3 +398,28 @@ test('smallcaps encoding examples', t => {
'hilbert property names',
);
});
+
+test('smallcaps proto problems', t => {
+ function convertValToSlot(_val) {
+ return 'slot';
+ }
+ const exampleAlice = Far('Alice', {});
+ function convertSlotToVal(_slot) {
+ return exampleAlice;
+ }
+ const { serialize: toSmallcaps, unserialize: fromSmallcaps } = makeMarshal(
+ convertValToSlot,
+ convertSlotToVal,
+ {
+ serializeBodyFormat: 'smallcaps',
+ },
+ );
+ const wrongProto = harden({ ['__proto__']: exampleAlice });
+ const wrongProtoSmallcaps = toSmallcaps(wrongProto);
+ t.deepEqual(wrongProtoSmallcaps, {
+ body: '#{"__proto__":"$0.Alleged: Alice"}',
+ slots: ['slot'],
+ });
+ // Fails before https://github.com/endojs/endo/issues/1303 fix
+ t.deepEqual(fromSmallcaps(wrongProtoSmallcaps), wrongProto);
+});

fix: unserialize makes copyRecords without problematic assign semantics (#1304)