2023-01-03 [00:05:22.0699] I will unfortunately skip the call today, I'll be traveling at that time. See you in two weeks! [00:54:12.0578] @annevk wrote in General: > yulia | sick: littledan: I could make the Module Loading call tonight if that's helpful, please lmk https://matrix.to/#/!wbACpffbfxANskIFZq:matrix.org/$oeD0yH7tagpCoRrR4gbgusb0fzM1ydOzWLIasu1T2ng?via=matrix.org&via=mozilla.org&via=igalia.com [04:52:37.0102] > <@nicolo-ribaudo:matrix.org> I will unfortunately skip the call today, I'll be traveling at that time. See you in two weeks! Me too! [06:47:49.0288] Let's declare this time cancelled, if Yulia is also sick [06:47:58.0400] also let's invite Anne tot his room [08:19:27.0746] Do we have an agenda today [09:01:11.0009] We do not. I concur with the overall sentiment. We’ll convene in two weeks. I’ll invite Anne to this room. [09:02:42.0966] Ok (btw I'm also on a sick holiday until Jan 16) [09:03:03.0946] * Ok (btw I'm also on a sick leave until Jan 16) [09:05:13.0895] For agenda building for N*2 weeks from now, I’d like for us to plan to meet with bakkot to improve our understanding of the feedback from the previous plenary. That may be an opportunity to prepare and dry-run a module harmony presentation-of-presentations. [09:06:02.0440] I still am on the hook to refactor the compartments proposal into epic-module-harmony and proposal-module-constructor etc. [09:19:23.0229] Until then, @bakkot, your feedback from plenary, if memory serves is, “no new path to eval”, and when we convene again, we are going to want to break that down and examine whether `import(new Module(new ModuleSource(text)))` qualifies as a new path to eval in the sense that you mean, and whether it’s fatal given the mitigations we have in mind. Not looking to dig in now, since we’ve got a lot of folks away, but I’d like to prime the pump for the next meeting (or a meeting thereafter). [09:20:15.0414] I also nominate following up on the import assertions design discussion, as an agenda item next fortnight. And I nominate peetk and nicolo-ribaudo to lead that discussion :) [09:20:23.0893] We also have a standing invitation to give @annevk the floor to discuss the next steps for import assertions. [09:21:32.0044] > <@kriskowal:matrix.org> We also have a standing invitation to give @annevk the floor to discuss the next steps for import assertions. I'd propose that we organize that like, annevk recaps his understanding of the problem we need to solve, then nicolo-ribaudo / peetk outline the possible solution we've been discussing, then annevk shares thoughts [09:22:09.0440] @annevk are you available +1 fortnight? [09:23:27.0076] Also, thank you littledan, I’m ecstatic to find company in which biweekly isn’t a word. [10:26:30.0385] > <@kriskowal:matrix.org> Until then, @bakkot, your feedback from plenary, if memory serves is, “no new path to eval”, and when we convene again, we are going to want to break that down and examine whether `import(new Module(new ModuleSource(text)))` qualifies as a new path to eval in the sense that you mean, and whether it’s fatal given the mitigations we have in mind. Not looking to dig in now, since we’ve got a lot of folks away, but I’d like to prime the pump for the next meeting (or a meeting thereafter). To be precise, I'm not dead set against having a new path to `eval` - I would just want there to be a very strong reason for it, which I haven't heard yet. [10:39:01.0634] Kris Kowal: yeah that should work [10:39:33.0196] > <@bakkot:matrix.org> To be precise, I'm not dead set against having a new path to `eval` - I would just want there to be a very strong reason for it, which I haven't heard yet. I think the decision about whether we expose a ModuleSource constructor which takes a string argument is extremely separable from everything else. We've heard bakkot say he doesn't want it, ljharb say he wants it, and others seem OK multiple ways. I don't think the decision here will affect any other part of the APIs we've been discussing, and I think we have a very clear shared understanding of what it would do if it does exist. [10:42:35.0222] (so, I think it should be treated as a post-Stage 2, pre-Stage 3 decision, having been fully scoped out) [10:44:41.0399] (still fine to hear out bakkot more, but I think he was plenty clear in plenary?) [10:46:49.0036] (I think the answer to the question is: clearly yes) [10:47:18.0543] > <@bakkot:matrix.org> To be precise, I'm not dead set against having a new path to `eval` - I would just want there to be a very strong reason for it, which I haven't heard yet. Great, this is clear. We can focus on building 1. the strongest reason and 2. the work-arounds other proposals (specifically module binding static analysis) can be recovered in the absence of ModuleSource(text). [10:50:15.0077] That is, being able to parse a string’s static imports and exports is currently subsumed by the ModuleSource(text) constructor but does not necessarily need to be on the path to importing the text. That can be recovered either by a parse bindings function sitting somewhere else, or by rendering module source instances constructed by ModuleSource(text) unusable (which is consistent with what a CSP would do anyway) [10:50:46.0051] And the motivation for parsing bindings is bundle generation, in which case, a path to eval is not needed. [10:51:17.0304] * And the motivation for parsing bindings is bundle, web archive, or import map generation, in which case, a path to eval is not needed. [10:53:57.0222] I suspect that the strongest case for a path-to-eval is a debugging environment, with hot module replacement occurring client-side, such that the runtime behavior more closely resembles a production environment than is possible to achieve with the current tooling ecosystem. [11:02:31.0555] > bundle, web archive, or import map generation Those seem like somewhat niche use cases which would be adequately met by userland parsers, to me? Doesn't seem like support for those things would need to be built into the language. [11:02:55.0951] We already have pure-JS bundlers, after all. [11:04:37.0477] (I'm not saying they're niche things for developers to do, just that they're niche things to do _in code which is shipped to users_; when building something into the language the latter is more relevant.) [11:05:19.0566] I agree that the value is bounded in this way. [11:07:47.0218] And other motivating use cases I can expound upon do not involve web browsers, so I expect them to be less convincing. [11:13:45.0698] For example, at Agoric, we would like to ship programs to workers as zip files containing the original sources. The workers would use the ModuleSource constructor to build out the module graph. We’re also avoiding source-to-source transforms so that audits are more transparent. The auditor can review the code and not worry about holes that would be opened by generated code. We’re currently unable to do this because our shim has to funnel module code through eval, so we necessarily have to do a module-to-program transform, and since that is prohibitively expensive, our zip files currently contain a JSON blob that contains the program and the bindings. [11:15:02.0455] And notably, Agoric/Endo execution environments do not employ CSP but do harden the worker realm and confine guest programs, so the trade-offs are different. It would be fair to call our case niche, but not unimportant. [11:15:35.0135] Rather, I’d call it nascent. [11:17:53.0622] (For example, the very common babel facebook/regenerator runtime introduces thawn-objects in a way that would not be obvious to an auditor and would open a program to interference that would otherwise not be possible.) [11:19:05.0665] A consequence of the current language limitations is that a debugger attached to a production heap snapshot can’t match stack traces with local code artifacts. [11:34:24.0568] If you could import a reified-but-not-evaluated `Module` and structuredClone it to a worker, would that eliminate your need for `eval` and therefore your need to bundle? [11:35:04.0882] (import or otherwise obtain in some manner other than constructing from a string, as would be provided some of these proposals IIUC) [11:35:27.0449] * If you could import a reified-but-not-evaluated `Module` and `postMessag` it to a worker, would that eliminate your need for `eval` and therefore your need to bundle? [11:35:31.0523] * If you could import a reified-but-not-evaluated `Module` and `postMessage` it to a worker, would that eliminate your need for `eval` and therefore your need to bundle? [11:35:43.0882] postMessage is not available in the particular context. [11:36:15.0530] Though a suitable postMessage could be implemented in terms of ModuleSource. [11:36:48.0712] Wait, so how are you getting stuff into a worker without postMessage? [11:37:05.0909] I’m using the term Worker loosely. This is a Node.js or XS child process. [11:37:32.0792] Node has postMessage - though I guess not to actual child processes spawned with `exec` or whatever. [11:38:42.0176] Aye, and process isolation is key. XS too. The application can resume from snapshot. [11:43:01.0647] Since you're targeting specific platforms, can you not hook into the runtime with native code and get at bindings that way? Or roll your own native `ModuleSource`-equivalent constructor, for that matter? [11:43:33.0635] I guess V8 might not expose the things you'd need, though that's in principle solvable. [11:43:39.0069] XS implements ModuleSource effectively. We are working in that direction, yes. [11:46:56.0754] There’s also a course to having these applications portable-to-web using XS on WASM. This is of course, not ideal, but certainly suitable for evidence of motivation :-P [11:55:42.0130] And also the course of using a Service Worker to pop the zip open. My feeling is that’s got its own trade-offs. [11:57:43.0232] And of these options, having ModuleSource provided by the language is the most portable. [11:58:20.0866] The alternatives require assumptions of a shared foundation that does not exist. [13:57:01.0862] Spawning a new process is fundamentally not portable to begin with, so I'm not sure why portability matters here [14:11:53.0737] Spawning, the conceit that all content everything is a URL backed by an ambient fetch capability, and module loading can (and I reckoon should) be made orthogonal. The portion of the problem pertaining to spawning is expressible in a hook that differs by platform and, with `ModuleSource`, nothing else is different. Transporting module sources in an archive doesn’t require any I/O. [14:12:07.0846] * Spawning, the conceit that all content everything is a URL backed by an ambient fetch capability, and module loading can (and I reckoon should) be made orthogonal. The portion of the problem pertaining to spawning is expressible in a hook that differs by platform and, with `ModuleSource`, nothing else is different. Drawing module sources out of an archive doesn’t require any I/O. [14:12:39.0625] Well, neither does parsing the bindings out of a string, in principle [14:12:57.0242] Not requiring I/O is a pretty big deal. Dynamic import currently creates an exfiltration channel. [14:13:20.0379] > <@bakkot:matrix.org> Well, neither does parsing the bindings out of a string, in principle And executing JavaScript, in principle! [14:15:08.0868] Well, right - evaluating JS is a weird and dangerous thing to do, so it's best not to expose a language-level capability for doing that, but specialist applications can (and do) do it in userland if they really need to. Seems like this use of `eval` is pretty analogous, to me. [14:15:10.0547] So, one of the intertwingled concerns is that Agoric/Endo prevents that exfiltration channel. Currently, we do that by censoring the word `import`, which has a number of inconvenient false positives and artificially limits how much extant code can participate on the platform. [14:15:40.0352] > <@bakkot:matrix.org> Well, right - evaluating JS is a weird and dangerous thing to do, so it's best not to expose a language-level capability for doing that, but specialist applications can (and do) do it in userland if they really need to. Seems like this use of `eval` is pretty analogous, to me. Agreed. [14:16:16.0616] And equally worthy of existence, in my opinion. [14:17:10.0139] ... Which is to say, not, or do you have a different impression of the net benefit of `eval` than I do? [14:17:11.0487] Except direct eval. That can die in a fire. [14:18:10.0176] > <@bakkot:matrix.org> ... Which is to say, not, or do you have a different impression of the net benefit of `eval` than I do? My impression is that we’d either be stuck with IE5 era JScript or gasp VBScript if eval hadn’t been part of the language. [14:18:22.0773] That... is not my impression. [14:18:59.0888] CommonJS certainly would not have happened. [14:19:22.0752] I know opinions vary on the point, but I have few regrets. [14:21:05.0403] (ESM would not have happened if CJS hadn’t happened. TC39’s position in 2009 was to wait and see whether the ecosystem had an appetite for modules, which would not have been observable otherwise.) [14:21:29.0789] Besides, the ecosystem didn’t really want it until they had it. [14:22:48.0161] I don't think it's really that plausible that we'd've never had a module system at all in the absence of `eval`, but in any case, we do now have such a system, so that can't be an argument for `eval` being something we would design into the language _now_. [14:31:58.0147] How sympathetic are you to jsfiddle as a specialist application that would benefit from eval-but-modules? [14:34:22.0898] I don't think jsfiddle uses eval? [14:34:27.0711] So not particularly sympathetic. [14:36:23.0867] At his point in the argument, would you say that the best way for Module constructor to Stage 2 would be to preserve the concept of a module source object (e.g., the source for a module block or a WebAssembly.Module) and omit the ModuleSource constructor? [14:36:58.0727] I do not imagine finding better arguments for ModuleSource from text than the above. [14:37:01.0980] bakkot: Do you consider this decision a Stage 2 blocker? IMO it seems OK to leave it an open question to resolve before Stage 3. [14:37:21.0939] I think for Stage 2 we should sort of understand the decision space fairly well, and I think we do [14:37:32.0169] I think in general that for any proposal to advance all components of that proposal should be justified, and I am not yet convinced that the ModuleSource constructor is justified, so yes. [14:38:12.0351] As to whether it's at Stage 2 or Stage 3 concern, personally this decision feels like a fairly major one, so personally I would prefer it be made before Stage 2, but the distinction is pretty artificial given that no one is shipping before stage 3 anyway, so I don't feel strongly about that. [14:38:22.0586] * As to whether it's a pre-Stage 2 or pre-Stage 3 concern, personally this decision feels like a fairly major one, so personally I would prefer it be made before Stage 2, but the distinction is pretty artificial given that no one is shipping before stage 3 anyway, so I don't feel strongly about that. [14:38:51.0851] Do you also contend that surfacing the bindings parser is not sufficiently justified? (The performance hit for drawing in Babel into production is significant. I hope that justifies it.) [14:39:06.0712] Oh, you can do a _lot_ better than pulling Babel into production [14:39:11.0634] You don't need a full parser just to get the bindings out [14:39:21.0800] Stage 2 implies all sorts of other important qualities, like "is the committee sort of committed to doing something here" and "do we have a concrete first draft"; it is really common to have disagreements about major semantic points when something goes to Stage 2 [14:39:59.0514] If shipping a parser specialized to the problem of getting bindings is still not performant enough, then that's maybe something to talk about, though I'm not sure it would make sense as part of this proposal in the absence of a ModuleSource constructor [14:40:10.0088] > <@bakkot:matrix.org> You don't need a full parser just to get the bindings out I’m sure you need at least the lexer and a partial parser for sequences introduced by import and export. [14:40:32.0227] Yup, but those things are lot smaller and faster than babel's parser. [14:40:45.0636] In particular not having to construct trees saves a lot of time. [14:41:05.0857] That’ll run you about 16kloc. We’ve got guybedford (Guy Bedford)’s for that. [14:41:15.0774] https://github.com/guybedford/es-module-lexer [14:41:44.0716] <1kloc [14:42:47.0337] > <@littledan:matrix.org> Stage 2 implies all sorts of other important qualities, like "is the committee sort of committed to doing something here" and "do we have a concrete first draft"; it is really common to have disagreements about major semantic points when something goes to Stage 2 Eh, well, depend on what you mean by "major semantic points". In theory stage 2 signifies "all _major_ semantics, syntax and API are covered", and "does this add a new eval" is IMO a major semantic point. But like I said I don't feel that strongly about this, as long as it's understood that agreeing to stage 2 doesn't signify consensus on this question either way. [14:42:53.0798] That’s…much smaller than his other one. Surprising! [14:43:36.0006] our full JS parser isn't even 16k semantic LoC [14:43:49.0027] (it's like 3-5k I think) [14:46:02.0786] Oh, pardon. I’m off by ten. I’m looking at Guy’s CJS lexer (which we use in prod at Agoric) and it’s 1.6K. [14:48:01.0505] So, agreed, it’s consequently practical to statically analyze the bindings of a module in production without ModuleSource(text).bindings or a lower-powered Module.parse(text). So making a web page that generates bundles isn’t that much trouble. [14:50:03.0348] > <@bakkot:matrix.org> Eh, well, depend on what you mean by "major semantic points". In theory stage 2 signifies "all _major_ semantics, syntax and API are covered", and "does this add a new eval" is IMO a major semantic point. But like I said I don't feel that strongly about this, as long as it's understood that agreeing to stage 2 doesn't signify consensus on this question either way. Yeah, the draft definitely has to make a strong stab at all of those points, including this one. But committee members can still disagree with them (look at where R&T is). [14:50:32.0690] anyway sorry lawyering here is beside the point [14:51:19.0567] not trying to deny your right to block this proposal (which has another sort of existential question, about whether we want the importHook to be hookable at all) [14:54:19.0506] There are a number of such questions. The folks at Moddable discovered that implementing Module and ModuleSource made it possible for their fuzzer to discover bugs in their module system that were previously too impractical to cover. They’ve fixed the bugs, but it highlights that it’s hard to get ESM right. There’s certainly some philosophical wiggle-room whether composing ESM with callbacks creates more bugs than it reveals. [14:54:54.0911] I guess I felt like the ordering should be more like, we focus first on whether we want importHook to be a thing at all (as Shu raised), and then we'd resolve lower-order things like whether the ModuleSource constructor should exist [14:55:40.0882] That is certainly the most efficient way to prune the tree. [14:55:52.0817] > <@kriskowal:matrix.org> There are a number of such questions. The folks at Moddable discovered that implementing Module and ModuleSource made it possible for their fuzzer to discover bugs in their module system that were previously too impractical to cover. They’ve fixed the bugs, but it highlights that it’s hard to get ESM right. There’s certainly some philosophical wiggle-room whether composing ESM with callbacks creates more bugs than it reveals. yes, the importHook certainly makes a bunch of things observable that previously weren't. Analogous to Proxy. Doesn't necessarily mean that the previous state was a bug. [14:56:25.0363] > <@littledan:matrix.org> I guess I felt like the ordering should be more like, we focus first on whether we want importHook to be a thing at all (as Shu raised), and then we'd resolve lower-order things like whether the ModuleSource constructor should exist so I was mapping these to Stage 2 and 3, but we could resolve them both before Stage 2 also [15:11:51.0325] > <@littledan:matrix.org> https://github.com/guybedford/es-module-lexer I assume this is sufficient to accept exactly and only grammatically valid sources. That requires block matching, but not AST generation. The other motivation for a full parse is that `eval('module {' + text + '}')` is an inevitable work-around for the lack of `ModuleSource`, which is much worse if text is not a valid source. [15:12:22.0440] > <@littledan:matrix.org> https://github.com/guybedford/es-module-lexer * I assume this is sufficient to accept exactly and only grammatically valid sources. That requires block matching, but not AST generation. The other motivation for a full parse is that `eval('module {' + text + '}')` is an inevitable work-around for the lack of `ModuleSource`, which is much worse if text is not constrained to be a valid module source. 2023-01-09 [03:43:09.0072] My major open question in response to bakkot's points at plenary is how this qualifies as a new path to eval - we discussed this at the last loader meeting and came to the conclusion that a `ModuleSource` constructor was in fact **not** a new path to eval, because the "evalness" of the ModuleSource is done through dynamic import. The constructor itself **does not eval** - eval only happens at import - which is already possible. [03:43:18.0511] * My major open question in response to bakkot's points at plenary is how this qualifies as a new path to eval - we discussed this at the last loader meeting and came to the conclusion that a `ModuleSource` constructor was in fact **not** a new path to eval, because the "evalness" of the ModuleSource is done through dynamic import. The constructor itself **does not eval** - eval only happens at import - which is already possible right now. [03:43:37.0702] * My major open question in response to bakkot's points at plenary is how this qualifies as a new path to eval - we discussed this at the last loader meeting and came to the conclusion that a `ModuleSource` constructor was in fact **not** a new path to eval, because the "evalness" of the ModuleSource is done through the existing dynamic import. The constructor itself **does not eval** - eval only happens at import - which is already possible right now. [03:44:25.0498] I would still like to invite bakkot to a loader meeting so we can discuss this in detail - I am likely missing something, or I am not understanding your concern correctly. [07:30:48.0834] I think this is discussed more concretely as we discuss mitigations. Clearly the whole thing (ModuleSource + import()) put together was a path to eval. I guess the question was whether bakkot would accept a mitigation which was, "ModuleSource produces marked things which cannot be passed to import() but otherwise work" (but I'm not so convinced the feature is very useful, and we were discussing this above) [08:04:32.0926] Luca Casonato: as littledan says, ModuleSource + dynamic import is eval, to my view. yes, in some (but not all) hosts it possible to use `data` URIs with dynamic import already, but a.) that is not widely supported especially given CSP and so is not a reliable way to `eval` and b.) `data` URIs are very obviously a kludge, whereas a ModuleSource constructor would be a first-class part of the language, and we should be concerned about making paths to `eval` more usable, not just whether they exist at all. also it is not at all clear to me what the use case for ModuleSource is other than `eval`; the cases discussed about were all either `eval`-like or a desire from a single library to have a built-in way of parsing out bindings. I think an affirmative case needs to be made for this feature (as with any feature), and to convince me personally that case needs to be something other than "we would like it to be easier to do `eval`" (or there needs to be an extremely strong reason to make it easier to do `eval`). [09:39:01.0363] well, it's eval for a module, which is otherwise more or less a missing capability [09:39:57.0301] I would ideally like to find a motivation for this path to eval that does not require others to buy into the SES group’s long-term motivation to make a safe path to eval, that does not rest on CSP, since I know that is contentious. But, that is personally my motivation. Making an easy path to lift text makes it easier to build a sandbox, and making that part of the language makes it easier to write portable sandboxes. One of the drawbacks of the compartment shim is that we have to censor the words `eval` and `import` from source text in order to enforce confinement. There are false positives that a native implementation wouldn’t suffer from. [09:40:42.0741] Making it eaiser to make sandboxes has the emergent effect of making sandboxes safer. [09:41:11.0697] And given that the design is compatible with CSP, I feel like this is a win-win. [09:41:17.0769] Kris, I'm glad you're describing your actual use case, as this makes it a lot easier to follow [09:41:21.0417] It's quite hard for me to follow a use case argument which centers on the censorship mechanism, since I really don't understand what sorts of restrictions you have in how you evolve that. [09:41:45.0784] maybe you could give some more details there? [09:43:00.0697] Yeah, in the SES shim, confinement is enforced at runtime and it’s a production performance and developer-experience requirement that not entrain rigorous static analysis or on-the-fly transformations. [09:44:28.0620] The heart of the confinement mechanism points four of JavaScript’s sharpest edges at each other: We use use direct eval in sloppy mode inside a with block that puts an opaque proxy on the stack. [09:44:48.0369] That’s…a summary. [09:45:11.0882] In any case, there are a number of obvious ways to escape lexical containment. [09:46:15.0920] Collectively “undeniable intrinsics”. So, for example, (async()=>{}) gives you access to the async function prototype. That’s pretty straightforward to confine with the hardening of shared intrinsics. [09:47:04.0630] In the most basic case, {} and [] give you access to shared intrinsics in ways that can’t be denied with the lexical environment. [09:47:59.0681] Likewise, `import` escapes confinement. So, we use a careful regex to identify any pattern that *might* be an invocation of import and refuse to evaluate such programs. [09:48:33.0859] The Evaluators proposal would allow us to hook the behavior of `import` for confined programs. [09:49:44.0479] We also ban direct `eval` similarly, but not so much because of confinement concerns, but because it is impossible to faithfully emulate the intended behavior in the shim. [09:52:00.0850] In order to support modules, we have no choice but to convert modules into programs at compile time, which is a source-to-source transformation that reduces debuggability. We also eschew source maps because they can be used to confuse auditors. [09:53:26.0520] So we have a module-to-program transform, which necessarily generates both the module functor source code and the bindings in a JSON envelope, and a runtime that handles these “module sources” reconstructs the linkage from the declared bindings and a sort of calling convention. [09:55:19.0271] The net result is a Zip file that contains JSON enveloped sources which is not as debuggable or reviewable as we’d like. This is the artifact that must be trusted when you grant capabilities to the guest program, so this is the artifact that gets fingerprinted for integrity checks. It’s also, notably, not hosted on the web, and modules not hosted on the web are not currently possible to evaluate across all JavaScript hosts. [09:56:01.0492] There are hacks, but they are hacks, and hacks and confinement are somewhat inimical. [09:58:22.0315] And again, our position is not that we hope to capture all JavaScript in hardened JavaScript. Our position is that it should be possible to get to hardened JavaScript from ordinary mutable implicitly permissive one-big-sandbox JavaScript. We in fact *require* that as a starting point since anything else would preclude evolution of the host environment over time. [10:00:13.0723] * Yeah, in the SES shim, confinement is enforced at runtime and it’s a production performance and developer-experience requirement that we not entrain rigorous static analysis or on-the-fly transformations. [10:29:48.0363] bakkot: so your concern is not that it is a **new** path to eval, but rather that it may make an existing path to eval more ergonomic? As for the usecases that can justify an unconstructable `ModuleSource`: - multiple instantiation modules (including module expressions & declarations) - low level `script-src: no-eval` module passing between workers - manual instantiation and module instrospection (we've seen this in WASM using `WebAssembly.Module`, for which `ModuleSource` would be the JS equivalent) [10:34:43.0147] Luca Casonato: it is a new path to eval in many contexts (e.g. anywhere with CSP, and therefore in any library), and also it makes an existing path to eval more ergonomic; both are concerns are important to me [10:35:27.0212] I do not think that bakkot objects to the concept of an object that represents a module source, which has intersection semantics relevant to other proposals and does not imply a path to eval. Correct me if I’m wrong. [10:36:09.0121] right - none of my `eval` concerns apply to `ModuleSource` which does not take a string argument; I am fine with a static import syntax which gives you an object you can clone between workers and so on [10:36:18.0979] Oh, bakkot our intention is _not_ to be a new path to eval with CSP. `no-unsafe-eval` would render a `ModuleSource` instance lifted from text inert. [10:37:39.0999] > <@bakkot:matrix.org> right - none of my `eval` concerns apply to `ModuleSource` which does not take a string argument; I am fine with a static import syntax which gives you an object you can clone between workers and so on This is to say we don’t need to convince bakkot of importing module sources or module blocks with sources, again correct me if I’m wrong. [10:38:30.0498] there are at least some contexts which allow `no-unsafe-eval` but do not allow `data` URIs (primarily because there are libraries with this requirement), so constructible ModuleSource + dynamic import would still constitute a new way to `eval` in such contexts [10:38:36.0085] * there are at least some CSPs which allow `no-unsafe-eval` but do not allow `data` URIs (primarily because there are libraries with this requirement), so constructible ModuleSource + dynamic import would still constitute a new way to `eval` in such contexts [10:38:52.0240] By inert I mean that `import(new Module(new ModuleSource('')))` would unconditionally return a rejected promise under a `no-unsafe-eval` CSP, and would poison any module that takes it as a transitive dependency as well. [10:39:26.0842] Otherwise I would agree that would constitute a new path to eval in situations that should have none. [10:40:51.0931] > <@bakkot:matrix.org> there are at least some CSPs which allow `no-unsafe-eval` but do not allow `data` URIs (primarily because there are libraries with this requirement), so constructible ModuleSource + dynamic import would still constitute a new way to `eval` in such contexts Is there a way to better communicate an intention that constructible module sources must not create a new path to eval under any existing CSP that should disallow it? [10:41:57.0564] well, if your CSP allows `no-unsafe-eval`, then it _should_ allow `eval`, but I still don't think it is a good idea for the language to provide a new way to `eval` in that context [10:42:21.0435] and `import` is _not_ a way to `eval` in that context, which is good [10:45:00.0404] I mean disallows `no-unsafe-eval`, to be clear. [10:45:48.0133] I agree that if we do end up with a constructible `ModuleSource` it must not be importable in a `no-unsafe-eval` context; I don't think there was any confusion about that [10:45:54.0773] There’s a strong possibility I don’t know CSP well enough to have this conversation. I’m hoping for instruction. [10:46:32.0698] the relevant fact is that `no-unsafe-eval` does _not_ restricted use of `data:` URIs; those are controlled by the use of a `data:` scheme expression in the CSP [10:46:36.0457] * I mean a CSP that disallows use of `eval`, to be clear. [10:46:59.0941] so it is possible to have CSP in which `eval` is allowed and `data:` URIs are not, in which context `ModuleSource` + dynamic import is unambiguously a new path to eval [10:47:39.0950] Is there a way we can frame it such that it would not be? [10:47:53.0385] not that I can think of? [10:48:07.0890] though all this discussion about CSP is all a little bit outside my main objection. I agree that it is _technically_ true that in at least some contexts `import` can be used for `eval` because of the existence of `data` URIs, though it is also even more technically true that this does not not apply in all contexts in which `ModuleSource` would work (if I understand correctly). however, I don't think this technical point is all that important; even granting it, it seems to me that `import` + constructible `ModuleSource` would a new first-class path to `eval` in a way that `import('data:')` is not , the latter being kludgey and not universally acceptable or available. [10:49:28.0498] That is, how does one describe all CSPs in which `import(new Module(new ModuleSource(text)))` must fail? [10:50:18.0028] Yeah, I’m not interested in any contexts where `import('data:')` is a workaround for eval CSP restrictions except insofar as that the proposal should not introduce loopholes in CSP. [10:50:25.0979] and while I am not dead-set against having a new _or new-ish_ first-class path to `eval` I would want it to have a strong justification, which I have not heard yet. whether this is _actually_ new or merely new-ish is not all that important to me - either way I still would want there to be a strong justification. [10:50:40.0961] I am interested in environment in which there are no such workarounds, ones where import(doesNotEvenTakeURL). [10:50:58.0081] > <@kriskowal:matrix.org> That is, how does one describe all CSPs in which `import(new Module(new ModuleSource(text)))` must fail? those which forbid `unsafe-eval`, I think is an accurate description [10:51:37.0894] So it is possible that the CSP tangent was entirely us talking past each other. Thanks. [10:52:35.0089] If I read correctly, we’re generally in agreement that ModuleSource(text) would not introduce a CSP loophole. [10:52:42.0171] right [10:53:37.0603] > <@bakkot:matrix.org> and while I am not dead-set against having a new _or new-ish_ first-class path to `eval` I would want it to have a strong justification, which I have not heard yet. whether this is _actually_ new or merely new-ish is not all that important to me - either way I still would want there to be a strong justification. This is my prior understanding and I suggest we focus on articulating a strong justification. [10:54:27.0774] @bakkot You would not happen to have seen the bits about confining JavaScript guests without CSP above before they scrolled away? [10:54:31.0444] (the bit about CSP was just to say that `import` + `data:` URIs does not already constitute a path to `eval` in all contexts - there are CSPs which allow `eval` but do not allow importing `data:` URIs, so adding constructible `ModuleSource` would be in fact granting a path to `eval` via `import` which did not previously exist in that context. but as I say this is all kind of a technical point rather than my main objection.) [10:57:52.0218] > <@kriskowal:matrix.org> @bakkot You would not happen to have seen the bits about confining JavaScript guests without CSP above before they scrolled away? I did read it but am lacking context to understand why your design requires the ability to `eval` modules from within a module, as opposed to having a compartment or something which would allow you to hook `import` within that context and provide a module that way [10:58:54.0120] We certainly need both. The key is that the platform is backed by zipped applications (no URLs and no fetch capability) [10:59:51.0476] Why does the `import` hook on a compartment (or whatever compartments are called now) not suffice here? [11:00:16.0296] The import hook returns Module instances with ModuleSources, from text dug out of Zip files. [11:01:12.0320] That's one possible design; certainly there are other designs for `import` which don't require a constructible `ModuleSource`, no? [11:01:56.0355] you could have a `defineImport(specifier: string, source: string)` capability available to the thing which created the compartment, or something, no? [11:02:42.0397] that would still be eval-like, but I am a lot less concerned about `eval` which is specifically defined in terms of compartments and which is defined from the outer compartment for use by the inner compartment, as opposed to one which is ambiently available [11:02:48.0750] (forgive me if I'm using the wrong terminology here) [11:17:43.0583] That’s an interesting clarification. [11:29:57.0592] > <@bakkot:matrix.org> there are at least some CSPs which allow `no-unsafe-eval` but do not allow `data` URIs (primarily because there are libraries with this requirement), so constructible ModuleSource + dynamic import would still constitute a new way to `eval` in such contexts What CSP actually prevents `import("data:...")`? A no eval `script-src` doesn't in Chrome 👀 (example: https://dash.deno.com/playground/csptest) [11:33:34.0859] also if i specify `default-src 'none'` - and same also in FF [11:34:14.0167] > <@bakkot:matrix.org> that would still be eval-like, but I am a lot less concerned about `eval` which is specifically defined in terms of compartments and which is defined from the outer compartment for use by the inner compartment, as opposed to one which is ambiently available Yeah, re terminology, by ambiently available, do you mean available by reference? An ocap interpretation of the term ambient would mean that it is registered to the parent host and available to all peers by a forgeable name. [11:48:24.0735] > <@lucacasonato:matrix.org> What CSP actually prevents `import("data:...")`? A no eval `script-src` doesn't in Chrome 👀 (example: https://dash.deno.com/playground/csptest) Oh apparently nonce'd script tags have an implicit `script-src 'strict-dynamic'' behavior for imports - TIL! [11:48:27.0331] > <@lucacasonato:matrix.org> What CSP actually prevents `import("data:...")`? A no eval `script-src` doesn't in Chrome 👀 (example: https://dash.deno.com/playground/csptest) nonces are automatically inherited by imports, I think? try `unsafe-inline` instead of nonces in the CSP [11:50:03.0623] yup, ok with that I can make a CSP that can do eval, but no `import("data:...")`: `script-src 'unsafe-inline' 'unsafe-eval'` [11:50:41.0327] So your concern is that module source would allow `import()` to eval with just `unsafe-eval` specified, whereas right now you need `script-src data:` or `script-src blob:`? [11:50:59.0708] > <@kriskowal:matrix.org> Yeah, re terminology, by ambiently available, do you mean available by reference? An ocap interpretation of the term ambient would mean that it is registered to the parent host and available to all peers by a forgeable name. I mean like any library can use this capability to `eval` in the current context, rather than only being able to do `eval` inside of a specially-constructed box [11:52:00.0216] > <@lucacasonato:matrix.org> So your concern is that module source would allow `import()` to eval with just `unsafe-eval` specified, whereas right now you need `script-src data:` or `script-src blob:`? well, again, that is a technical point. yes, I am concerned a little bit about that, but the larger concern is that I do not regard `data:` URIs as being a first-class path to `eval` built in to the language, for this and other reasons, while `ModuleSource` would be. [11:52:50.0787] i see - thank you for the clarification. [11:56:25.0471] For all intents and purposes contexts with dynamic import and data URLs would have a constructable (all be it async) `ModuleSource` in the form of `(await import("data:application/javascript,abc", }{ reflect: "module" }).source` anyway, so for me the lack of an explicit `ModuleSource` constructor is not the end of the world [11:57:10.0411] And in these contexts this factory would be subject to CSP - so no new path to eval here [11:57:33.0861] So, Module in this proposal is providing exactly as limiting a scope as Compartments, even if Compartments are framed as a registry of source strings. If you’ll forgive a reductio absurdam, `const c = new Compartment({ importHook(specifier) { return import(specifier, {reflect: true}) } ); c.registerSource('name', text); c.import('name')` is equivalent. The key here is that `Module` is a box. [11:57:48.0808] * And in these contexts this factory would be subject to CSP - so no new path to eval here (the exact same CSP a direct `import({"data:...")` is subject to today) [11:57:57.0651] * And in these contexts this factory would be subject to CSP - so no new path to eval here (the exact same CSP a direct `import("data:...")` is subject to today) [11:58:35.0769] * For all intents and purposes contexts with dynamic import and data URLs would have a constructable (all be it async) `ModuleSource` in the form of `(await import("data:application/javascript,abc", { reflect: "module" }).source` anyway, so for me the lack of an explicit `ModuleSource` constructor is not the end of the world [11:58:46.0725] My hope is that `Module` is in fact as much a box as the box you like in `Compartments`! :-) [12:00:05.0773] And `Evaluators` give us another box, where we can capture the dynamic `import` behavior for non-modules. Out of these primitives, `Compartment` can be implemented in user code. [12:00:38.0913] And hardened JavaScript can be faithfully implemented in user code, as it cannot with a shim. [12:00:39.0877] > <@kriskowal:matrix.org> So, Module in this proposal is providing exactly as limiting a scope as Compartments, even if Compartments are framed as a registry of source strings. If you’ll forgive a reductio absurdam, `const c = new Compartment({ importHook(specifier) { return import(specifier, {reflect: true}) } ); c.registerSource('name', text); c.import('name')` is equivalent. The key here is that `Module` is a box. again, I'm concerned not just with _whether_ the thing is possible, but _how accessible_ it is. I agree that some designs for compartments can be bludgeoned into being a current-context `eval` but that doesn't mean all possible designs are equivalent. [12:02:07.0915] `eval` already exists, but we've largely been able to convince people not to use it. I don't want to make a new `eval` and leave it lying around in another easily accessible form. [12:02:21.0021] Well, if you’re satisfied by some API impedance, that’s overall good news since we care more about what’s possible than how coherent it is. [12:03:08.0877] And Compartments are not all that cumbersome. [12:03:18.0205] I am certainly _more_ comfortable with less accessible APIs but not automatically entirely comfortable with them [12:03:26.0702] * I am certainly _more_ comfortable with less accessible new ways to `eval` but not automatically entirely comfortable with them [12:04:54.0195] Would this be a sufficiently inaccessible affordance: `new Compartment({ importHook(specifier) { return { source }; } }).import('specifier')`? [12:04:56.0170] though in addition I think if Compartments the use case then a design which exposes the capability only to compartments is more palatable for that reason [12:05:25.0588] That would make me feel a lot better than `import(new Module(new ModuleSource(source)))`, certainly [12:09:55.0849] Though I would still be hesitant to add that if the only use case is your source-from-zip-files thing [12:10:11.0711] So we would recover the ergonomics in user code like: ``` const loadModuleSource = async source => new Compartment({ importHook: () => ({ source }) }).import('.', { reflect: true }); ``` [12:11:10.0418] Why do you want to recover the ergonomics? [12:11:18.0534] zip-files in this case represent a class including databases, local storage, &c, if that makes any difference in substance. [12:11:28.0084] Not really. [12:12:17.0726] And in all these cases, the point is that it’s possible to draw a circle around a working set as a reviewable, confineable artifact, which would not otherwise be possible to isolate in this way. [12:12:46.0347] that is, those things are not a use case on their own - if you want to `eval` code from local storage, my question will be why do you want that [12:13:19.0514] I think I understand your particular use case, which makes cloning an opaque object to a Worker not viable, but am not convinced by that use case alone [12:13:25.0539] In our case, the artifacts are smart contracts, responsible for handling money on behalf of multiple parties. [12:14:34.0950] Right, which is to say, an extremely unusual case, unlikely to generalize to something needed by other applications [12:15:01.0005] Doug Crockford used to refer to the generalization of the case as mash-ups. [12:16:34.0513] Doug Crockford talked about wanting to be able to shell out to a separate process and communicate code to that process in a transparent representation? [12:18:26.0530] Hardly so specifically, but Doug built AdSafe at Yahoo, which does isolate and evaluate arbitrary guest programs. [12:20:48.0302] Program isolation is mostly orthogonal from `eval` - generally one can serve the guest programs from a server, as programs, and not need to `eval` them (assuming the language has isolation abilities at all, which would need to be built out anyway, as we are doing with realms and so on) [12:21:26.0768] Your particular use case prevents this but, again, your particular use case seems to me to be extremely unusual [12:22:53.0207] So I would still want to hear other use cases before adding this feature, as I would for any feature, although they can be somewhat less compelling as long as the new path to `eval` is less accessible [12:23:30.0821] * So I would still want to hear other use cases before adding this feature, as I would for any feature, although they can be somewhat less compelling than I would want for a new _easily-accessible_ path to `eval` 2023-01-10 [17:10:49.0600] Could anyone summarize the conclusions of this conversation? [19:47:09.0449] Kevin might be convinced that a path from module text to evaluated behavior is permissible if 1. no new loopholes to CSP are introduced (and we agree that this is not a concern) 2. the capability is not too easy to find or use, for example made possibly only through a Compartment importHook that returns `{ source }` (but presumably not a Module importHook, and with no ModuleSource constructor. We agree that this does not limit expressible behavior.) and 2. motivation more compelling than just Agoric’s use case can be found. The use cases presented as yet are not sufficiently compelling. [19:49:19.0406] To that end, I might reach out to the community at large to build a coalition. It seems we need some combination of quantity and quality of use cases that we have not yet presented. I believe these exist and we need testimonials. [19:54:37.0783] my primary use case is being able to test module-level syntax features, like TLA, without necessarily needing a filesystem to be present. That's already what I use Function for (and potentially AsyncFunction, GeneratorFunction, and AsyncGeneratorFunction as well). [20:01:27.0250] ljharb, could you elaborate on what the testing is for? I take it that this is for a polyfill, rather than test262-related (where we could add extra host callbacks) [20:02:36.0963] I take it that the use case has to be something that bakkot can be convinced is important enough (since he questioned that above); maybe context from ljharb could meet that requirement [20:06:50.0373] yes, polyfills or environment testing packages - like “has object spread” or “has top-level await” [20:07:19.0317] or “has Unicode named exports”, or any new syntax we add to the top level of Modules in the future [20:07:56.0611] until we ship a syntax detection mechanism (which we may never do), eval is the only viable alternative. [20:12:00.0204] Could you say more about why ModuleSource helps you do something that you can't do with plain eval? [20:17:37.0874] I’m also motivated by ljharb’s use case, but in the interest of demonstrating that I’ve listened to bakkot carefully, I imagine that module blocks + eval are sufficient to that end and would not require ModuleSource. [20:18:07.0852] nope, because i can’t test for the syntax in an engine that lacks module blocks [20:18:15.0457] i need a purely API-based solution [20:18:16.0656] * I’m also motivated by ljharb’s use case, but in the interest of demonstrating that I’ve listened to bakkot carefully, I imagine that module blocks + eval are sufficient to that end and would not require ModuleSource(text) [20:18:36.0064] module blocks would presumably need to contain valid-in-the-current-engine syntax anyway [20:18:37.0049] oh i see what you mean, inside Function, put a module block [20:18:47.0132] oh wait yeah [20:19:11.0005] yeah that seems adequate for this use case if I understand the use case correctly [20:20:06.0210] That’s not to say I wouldn’t favor ModuleSource(x) over eval('module {' + x + '})') for that purpose, but we would only need one or the other strictly speaking. [20:20:19.0921] Function, to be clear, never eval :-) [20:20:28.0218] but yeah, same. [20:20:31.0452] Yeah, fair. [20:20:45.0286] Never direct-eval, anyway. [20:22:18.0716] i just avoid it like a bad word, direct or not [20:22:47.0698] ljharb: kind of a tangent, but why do you need to feature-test syntax anyway? is it just to decide which polyfill to load or is there some other case it comes up? [20:23:03.0706] ("deciding which polyfill to load" is an important use case, I'm just wondering if there's others I'm missing) [20:23:21.0243] i use it constantly in test suites [20:23:42.0484] (also even if Function + a module block works for me, i still have the consistency argument that every other way of passing around code-to-be-executed has a constructor) [20:23:48.0064] We do eval-based syntax testing to discover intrinsics we need to freeze, tame, and trim. [20:23:57.0833] (In `ses` shim.) [20:24:39.0168] I’m sympathetic to the consistency argument, but it doesn’t pass the bakkot test. [20:25:42.0695] I feel very strongly that the best proposals triangulate a void implied by two other features, the way Array.from and async imply the existence of Array.fromAsync. [20:26:06.0343] Or the way Array and iterators imply iterator helpers. [20:30:20.0312] if the only goal was to fill the void left by `eval` only handling the Script parse goal, the way to fill it would be `eval(source, { type: 'module' })` or something, which... I mean, I find that instinctively distasteful, but the reason I don't like it is because it's enhancing `eval`, which is also my objection to all of the other options [20:30:36.0903] it at least has the advantage that it would not be _new_ thing that we would need to tell people not to use [20:32:30.0332] honestly I kind of appreciate the honesty of it - the fundamental question before the committee, do we want to make `eval`-for-modules? if we would not add `eval(source, { type: 'module' })` then we probably should not add a constructible `ModuleSource` either [21:33:00.0866] > <@lucacasonato:matrix.org> My major open question in response to bakkot's points at plenary is how this qualifies as a new path to eval - we discussed this at the last loader meeting and came to the conclusion that a `ModuleSource` constructor was in fact **not** a new path to eval, because the "evalness" of the ModuleSource is done through the existing dynamic import. The constructor itself **does not eval** - eval only happens at import - which is already possible right now. Looks like the solution is to add a new directive for `script-src` CSP to limit import from dynamically constructed `ModuleSource`. e.g. `script-src: unsafe-module-source` [21:34:39.0638] > <@lucacasonato:matrix.org> My major open question in response to bakkot's points at plenary is how this qualifies as a new path to eval - we discussed this at the last loader meeting and came to the conclusion that a `ModuleSource` constructor was in fact **not** a new path to eval, because the "evalness" of the ModuleSource is done through the existing dynamic import. The constructor itself **does not eval** - eval only happens at import - which is already possible right now. * Looks like the solution is to add a new directive for `script-src` CSP to limit import from dynamically constructed `ModuleSource` from string. e.g. `script-src: unsafe-module-source` [21:39:16.0388] 🤔 Is `ModuleSource` unforgeable? via `(module {}).constructor` [21:42:04.0245] What do you mean by unforgeable? [21:44:30.0533] > <@jackworks:matrix.org> Looks like the solution is to add a new directive for `script-src` CSP to limit import from dynamically constructed `ModuleSource` from string. > > e.g. `script-src: unsafe-module-source` As long as `unsafe-module-source` is a more specific variant of `unsafe-eval` (`unsafe-eval` disables both like it does for `wasm-unsafe-eval`) [21:46:12.0305] > <@kriskowal:matrix.org> Great, this is clear. We can focus on building 1. the strongest reason and 2. the work-arounds other proposals (specifically module binding static analysis) can be recovered in the absence of ModuleSource(text). or make `ModuleSource.fromString` a thing and make it potional? [21:53:55.0592] > <@kriskowal:matrix.org> And the motivation for parsing bindings is bundle, web archive, or import map generation, in which case, a path to eval is not needed. further split `ModuleSource` into `ParsedModule` and `ImportableModule`? `ParsedModule` can be used for analyze purpose, but cannot be imported. `ImportableModule` constructor accepts `ParsedModule` or an object that implements Virtual Module Source interface. If there is CSP, it will throw when `ParsedModule` is constructed from a string (or any other TrustedType limitations). [22:05:06.0447] > <@littledan:matrix.org> https://github.com/guybedford/es-module-lexer Every time I want to use es-module-lexer for some fast analyzing it always fails because - I want to analyze TypeScript + JSX - I want to analyze import bindings [22:09:56.0014] > <@kriskowal:matrix.org> I assume this is sufficient to accept exactly and only grammatically valid sources. That requires block matching, but not AST generation. The other motivation for a full parse is that `eval('module {' + text + '}')` is an inevitable work-around for the lack of `ModuleSource`, which is much worse if text is not constrained to be a valid module source. which definitely seems worse `text = '}; run any code; {'` [06:24:11.0956] > <@jackworks:matrix.org> 🤔 Is `ModuleSource` unforgeable? via `(module {}).constructor` In the current module blocks proposal, this is `Module` [06:25:05.0394] > <@ljharb:matrix.org> nope, because i can’t test for the syntax in an engine that lacks module blocks I guess this is mostly an issue if there exists any engine that ships constructable Module before module blocks. [06:27:21.0397] > <@jackworks:matrix.org> Every time I want to use es-module-lexer for some fast analyzing it always fails because > - I want to analyze TypeScript + JSX > - I want to analyze import bindings What do you end up doing instead? [09:28:26.0361] > <@littledan:matrix.org> I guess this is mostly an issue if there exists any engine that ships constructable Module before module blocks. no, it's also an issue for being able to ship syntax-testing code to an engine that doesn't have either one [09:30:33.0228] > <@littledan:matrix.org> What do you end up doing instead? Use a full-powered parser (I am familiar with the TypeScript compiler API) which is costly. [10:08:55.0850] > <@jackworks:matrix.org> which definitely seems worse `text = '}; run any code; {'` It is, but @bakkot makes the case that a lexer with limited parse capabilities, such that it can do the static analysis and balance braces, brackets, and parentheses, is on the order of 1KB, so it’s at least possible to avoid the code injection hazard. Node.js has such a lexer by guybedford (Guy Bedford) and the performance was okay, though only in WASM. [10:09:54.0467] I do think that 1.) performance of native will be unbeatable and 2.) code injection foot-gun do count in favor of ModuleSource, but my understanding is that this is not enough to sway @bakkot. [10:11:00.0867] > <@jackworks:matrix.org> or make `ModuleSource.fromString` a thing and make it potional? I agree that this is a viable mitigation. It at least decouples `Module` from constructible `ModuleSource`. [10:16:30.0435] > <@bakkot:matrix.org> honestly I kind of appreciate the honesty of it - the fundamental question before the committee, do we want to make `eval`-for-modules? if we would not add `eval(source, { type: 'module' })` then we probably should not add a constructible `ModuleSource` either I hear you that we should slice off a portion of Module Harmony that is explicitly eval-but-modules, but I would not propose `eval(source, { type: 'module' })` as the vessel for it under any circumstances. Modules are more analogous to the `Function` constructor form of `eval` since they are reusable and defer execution, but differ because they can import and export. An `eval` form would have to answer a lot of questions about top-level await. The form would clearly be unable to export, and a completion value would stand in the way of an exports namespace. I think that the question of whether to support eval-but-modules is a better framing, but `ModuleSource` is consistent with the prevailing eval aesthetic. [10:17:54.0081] That said, I don’t know whether JSDOM for example would be able to emulate `