endojs / endo

Endo is a distributed secure JavaScript sandbox, based on SES
Apache License 2.0
827 stars 72 forks source link

Consider a `zone.slot()` abstraction #2397

Open mhofman opened 2 months ago

mhofman commented 2 months ago

What is the Problem Being Solved?

We have a pattern for zone that emerged: prepareFoo(zone, 'foo', ...) for simple entries in a zone. If Foo requires complex (aka multiple prepared entries), it can create a subzone with that name. In some case that leaks onto the "prepare" function itself, which accepts a zone without a tag, assuming it's getting a subzone it ca take over.

However the current prepare pattern is a break in encapsulation: it effectively gives access to the full zone, but relies on the prepareFunction to pinky swear it will only write keys at the provided tag.

This has come up repeatedly in async-flow / orchestration design and similar issues were raised before, e.g. in https://github.com/Agoric/agoric-sdk/pull/7107#discussion_r1125031665

Description of the Design

Introduce a zone.slot(name) API with methods to define an exo or map/subzone in that slot, or create a sub slot (working as a series of prefixes which are joined when inserting the element).

slot.exoClass would no longer require a tag as that comes from the slot itself.

Prepare functions would be updated to only accept slots instead of zone + tag (or zone alone).

The current zone.exo(tag, ...args) would become an alias for zone.slot(tag).setExo(...args), and zone.subzone(tag) is effectively the equivalent of zone.slot(tag).setSubzone(). (prefix set to be bikeshed, but it should convey only a single type of thing can be stored there).

Security Considerations

This allows for better encapsulation of zones when passing them around.

Scaling Considerations

Slots can be heap only as they're transient objects representing an attenuated zone entry.

Test Plan

TBD

Compatibility Considerations

We may need some helper for prepare functions to remain backward compatible with the version accepting zone + tag (and maybe even just zone).

const prepareFoo = withZoneAndTagCompatibility((slot, arg1, arg2) => { ... });

Upgrade Considerations

New methods on existing APIs, adopt at own convenience.

gibson042 commented 2 months ago

Before bikeshedding on specific names, can you clarify the "zone" data model and how this proposes to extend it? I haven't looked in a while, but my understanding is we're starting from something like "each subZone corresponds with a MapStore in its parent zone, and exos/stores in a zone correspond with entries in its MapStore":

flowchart TD
    Baggage[baggage: DurableMapStore] -->|makeDurableZone#40;baggage, baseLabel = 'durableZone'#41;| RootZone[rootZone: DurableZone#40;baggage#41;]
    RootZone -->|subZone#40;'foo'#41;| MakeFoo[/subBaggage = provideDurableMapStore#40;baggage, 'foo'#41;/]
    MakeFoo -->|makeDurableZone#40;subBaggage, `durableZone.foo`#41;| Foo[foo: DurableZone#40;baggage.get#40;'foo'#41;#41;]
    RootZone -->|subZone#40;'bar'#41;| MakeBar[/subBaggage = provideDurableMapStore#40;baggage, 'bar'#41;/]
    MakeBar -->|makeDurableZone#40;subBaggage, `durableZone.bar`#41;| Bar[bar: DurableZone#40;baggage.get#40;'bar'#41;#41;]
    Foo -->|subZone#40;'baz'#41;| MakeBaz[/subBaggage = provideDurableMapStore#40;subBaggage, 'baz'#41;/]
    MakeBaz -->|makeDurableZone#40;subBaggage, `durableZone.bar.baz`#41;| Baz[baz: DurableZone#40;baggage.get#40;'bar'#41;.get#40;'baz'#41;#41;]
    Baz -->|exo#40;'Qux'#41;| MakeQux[/provide#40;subBaggage, 'Qux_kindHandle', …#41;/]
    MakeQux --> Qux[Qux: Exo#40;baggage.get#40;'bar'#41;.get#40;'baz'#41;.get#40;'Qux_kindHandle'#41;#41;]
    Baz -->|exo#40;'Quux'#41;| MakeQuux[/provide#40;subBaggage, 'Quux_kindHandle', …#41;/]
    MakeQuux --> Quux[Qux: Exo#40;baggage.get#40;'bar'#41;.get#40;'baz'#41;.get#40;'Quux_kindHandle'#41;#41;]
mhofman commented 2 months ago

I am not proposing to change the data structure. But to change the API:

flowchart TD
    Baggage[baggage: DurableMapStore] -->|makeDurableZone#40;baggage, baseLabel = 'durableZone'#41;| RootZone[rootZone: DurableZone#40;baggage#41;]
    RootZone -->|slot#40;'foo'#41;.setSubZone#40;#41;| MakeFoo[/subBaggage = provideDurableMapStore#40;baggage, 'foo'#41;/]
    MakeFoo -->|makeDurableZone#40;subBaggage, `durableZone.foo`#41;| Foo[foo: DurableZone#40;baggage.get#40;'foo'#41;#41;]
    RootZone -->|slot#40;'bar'#41;.setSubZone#40;#41;| MakeBar[/subBaggage = provideDurableMapStore#40;baggage, 'bar'#41;/]
    MakeBar -->|makeDurableZone#40;subBaggage, `durableZone.bar`#41;| Bar[bar: DurableZone#40;baggage.get#40;'bar'#41;#41;]
    Foo -->|slot#40;'baz'#41;.setSubZone#40;#41;| MakeBaz[/subBaggage = provideDurableMapStore#40;subBaggage, 'baz'#41;/]
    MakeBaz -->|makeDurableZone#40;subBaggage, `durableZone.bar.baz`#41;| Baz[baz: DurableZone#40;baggage.get#40;'bar'#41;.get#40;'baz'#41;#41;]
    Baz -->|slot#40;'Qux'#41;.setExo#40;#41;| MakeQux[/provide#40;subBaggage, 'Qux_kindHandle', …#41;/]
    MakeQux --> Qux[Qux: Exo#40;baggage.get#40;'bar'#41;.get#40;'baz'#41;.get#40;'Qux_kindHandle'#41;#41;]
    Baz -->|slot#40;'Quux'#41;.setExo#40;#41;| MakeQuux[/provide#40;subBaggage, 'Quux_kindHandle', …#41;/]
    MakeQuux --> Quux[Qux: Exo#40;baggage.get#40;'bar'#41;.get#40;'baz'#41;.get#40;'Quux_kindHandle'#41;#41;]