Skip to content

Concern regarding dynamic import as text #9

@garronej

Description

@garronej

Hello,

First of all, thank you for the work on this proposal, I think the overall direction is excellent and the static, typed version of import ... with { type: "..." } makes a lot of sense.

However, I’d like to raise a security concern specifically around the dynamic form:

import(url, { type: "text" })

This is not about XSS attacks, those remain fully in scope regardless of this proposal.
The concern is about large-scale blind supply-chain attacks, and how dynamic import-as-text affects the defender’s ability to harden the runtime.


Background: hashed JS assets and blind discovery attacks

Modern web application build systems (Vite, Webpack, etc.) produce assets like:

/assets/index-CDtdNkf4.js
/assets/chunk-XYZ123.js

These filenames include a hash, which means an attacker who gains same-origin execution through a compromised dependency cannot know ahead of time which chunks contain interesting exports, such as:

  • OIDC client instances
  • authenticated fetch wrappers
  • sensitive state machines

To discover these, a typical attack today must:

  1. Parse the HTML to find the entrypoint
  2. fetch that asset
  3. Recursively discover its imports
  4. Parse each chunk
  5. Look for patterns corresponding to sensitive exports

Defenders can currently block step (2) by hardening fetch and other runtime built-ins very early, before any other application code runs.

This is extremely useful for libraries that intentionally treat the runtime as a hostile environment (for example, client-side OIDC implementations that aim to prevent token exfiltration even under supply-chain compromise).


Why dynamic import-as-text changes the threat model

Under this proposal, an attacker can do:

import(`/assets/index-CDtdNkf4.js`, { type: "text" })

And this cannot be prevented by:

  • monkey-patching fetch
  • freezing built-ins during early initialization
  • guarding network primitives

Because dynamic import is a language-level mechanism that defenders cannot intercept.

This means:

The defender loses the ability to mitigate blind discovery attacks
by disabling runtime asset introspection.

Again: this does not impact targeted attacks (the attacker already knows the hash), but it does impact large-scale opportunistic attacks where the adversary does not know the asset graph in advance.

Today, this is still something defenders can block.
With dynamic import-as-text, they cannot.


Why this matters (concrete real-world case)

I am working on a client-side OIDC + PKCE implementation that treats the browser runtime as hostile and aims to prevent token exfiltration even if a dependency in the tree is compromised.

This kind of defensive approach requires limiting introspection of application assets.
If dynamic import-as-text is standardized without an interception/hardening mechanism, this class of protections becomes impossible.

This is a niche use-case, but it is one where security properties materially degrade under this proposal.


Summary

  • Static import ... as text has no issue, its target must be known as build time, it's not levragable for discovring the assets graph.
  • Dynamic import(url, { type: "text" }) introduces a new unpreventable attack surface: language-level introspection of hashed JS assets.
  • This removes a defensive technique currently available to developers working in hostile runtime models.
  • If possible, the dynamic form should allow hosts to intercept / disable it.

I realize this feedback is arriving at Stage 2, but the concern is not niche.
This affects any web application performing token exchange on the client, which includes a large portion of modern SPAs implementing OAuth/OIDC.

Today, most of the ecosystem implicitly treats “same-origin script execution = game over,” but large-scale NPM supply-chain compromises have shown that this assumption is increasingly unsafe. Some implementations (including mine) actively harden the runtime against hostile co-residents by disabling or mediating built-in primitives before any untrusted code loads.

Thank you for your time and for considering the security implications.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions