title: 'Code Execution via data: URI Module Specifier Handed to import()'
id: ATR-2026-02620
rule_version: 1
status: experimental
description: |
  Detects code execution smuggled through the SPECIFIER of a dynamic import() rather than through eval() or new Function(). `import('data:text/javascript,<code>')` is a fully supported ES module loader path in Node and in browsers: the inline body becomes a real module and runs at import time. Attacker-controlled module ids reaching import() are the primitive behind CVE-2025-67489 (@vitejs/plugin-rsc RCE through unsafe dynamic imports in RSC server function APIs), where the attacker never needed eval to be reachable.
  MEASURED GAP. The payload held as true_positive #1 -- an inline data: module whose body fetches an absolute URL with a process.env token appended -- evaluated through the engine matched only ATR-2026-00002 (generic indirect prompt injection) on llm_input/llm_output, and on tool_call matched only ATR-2026-00161/00012/00061 -- none of which key on the execution primitive. The eval() sibling of the same payload fires ATR-2026-00110/00126, so the eval-shaped half of this family is covered and the import-shaped half was not.
  SCOPE, DELIBERATELY NARROWED. A bare `import('data:text/javascript,...')` is NOT flagged. That construct is documented in the Node ESM manual and is the standard way tooling materialises a module from a string, since there is no eval() for modules -- test-runner ESM shims, loader hooks and REPL glue all emit it, and so does the documentation teaching it. Flagging the bare form would fire on Node's own docs. What this rule requires is the inline module body ALSO reaching for a capability an inline shim has no reason to touch: process/environment, an outbound request, or a child process. That conjunction, not the data: URI, is where the precision comes from.
  KNOWN GAP, DELIBERATELY LEFT OPEN. The base64-bodied form (`import('data:text/javascript;base64,...')`) is NOT covered. A condition on it existed in review and was removed: it fires on ordinary bundler output, because esbuild's `--loader:.js=dataurl`, webpack's `type: "asset/inline"` and Storybook's inline-CSF emit exactly `import("data:text/javascript;base64, <24+ chars>")` for wholly benign modules, and nothing inside a single event distinguishes those from a payload -- the engine only base64-decodes on the scanSkill() path, never in evaluate(). Flagging every inline base64 module would charge every bundler user for the evasion. So base64 bodies are an admitted blind spot rather than a broad condition.
  BRIDGE, AND WHY IT IS SHAPED THIS WAY. The span between the specifier's comma and the capability token may not cross a raw quote or newline, and may cross at most ONE escaped quote, immediately before the capability token. Both limits are load-bearing. Without the first, the match walks out of the specifier into surrounding prose. Without the second, a JSON-encoded tool argument -- which is how production presents tool_args -- lets `\"` end the specifier and the match continues through ordinary English into an unrelated `process.env` further down the line. The opening quote is REQUIRED for the same reason: `import(data:text/javascript,...)` unquoted is not valid JavaScript, it only ever occurs in prose, and allowing it made the rule fire on changelogs, markdown tables, CI logs, Semgrep/Sigma rules that detect this very attack, and on this rule's own MEASURED GAP paragraph. The body must also START immediately after the comma with a non-whitespace character, which is what a data: URI looks like; a truncated log line that opens the specifier and never closes it (`import("data:text/javascript, at Module.load (...) - check process.env.NODE_OPTIONS`) otherwise walked straight into prose. An attacker who inserts a leading space evades this, and that is the price paid for the log-noise class.
  WHAT STILL FIRES ON BENIGN TEXT, MEASURED. Re-measured after the above on 12,931 benign corpus samples (data/benign-corpus-extended/, all shapes plus scanSkill): 0 fired. On 44 adversarially authored benign inputs, 5 still fire, and all 5 are the irreducible class -- text that contains a COMPLETE, correctly quoted payload, or an inline module that genuinely reaches a capability. They are: this rule's own MEASURED GAP paragraph and true_positive fixtures; a scanner's unit-test fixture carrying the payload as a string; a non-English document quoting the payload verbatim to warn against it; an inline module that reads `process.env.NODE_ENV` for a build; and an ESM shim whose inline body calls `require(`. Note that `process.env` alone carries NO true positive in this rule's fixture set -- every true positive matches on fetch(//, globalThis.process, child_process, sendBeacon or require( first -- so that alternative is the weakest part of the conjunction and is kept only because an inline module that returns the environment to its caller is a real attack shape.
author: ATR Community
date: 2026/08/23
schema_version: '0.1'
detection_tier: pattern
maturity: test
severity: high
references:
  cve:
    - CVE-2025-67489
  owasp_llm:
    - LLM05:2025
  owasp_agentic:
    - ASI04:2026
  mitre_atlas:
    - AML.T0053 - LLM Plugin Compromise
    - AML.T0011 - User Execution
compliance:
  owasp_agentic:
    - id: ASI04:2026
      context: An attacker-controlled module specifier turns a data-handling path into an execution path, which is unexpected code execution inside the agent's own runtime.
      strength: primary
  owasp_llm:
    - id: LLM05:2025
      context: 'Improper output handling: a string that reached a module loader is executed as code rather than treated as data.'
      strength: primary
  eu_ai_act:
    - article: '15'
      context: Article 15 requires high-risk systems to be resilient against attempts to alter their behaviour; executing an attacker-supplied inline module alters the system's behaviour at runtime.
      strength: primary
    - article: '9'
      context: Code execution through a module loader is a risk this rule surfaces as the monitoring evidence Article 9 risk management requires.
      strength: secondary
    - article: '12'
      context: Detection events give the automatic recording of execution attempts that Article 12 logging expects.
      strength: secondary
  nist_ai_rmf:
    - function: Manage
      subcategory: MG.2.3
      context: Runtime treatment of the risk that an agent's module loader is used as an execution primitive.
      strength: primary
    - function: Map
      subcategory: MP.5.1
      context: Catalogues specifier-borne execution as a distinct risk from eval-borne execution, which is the gap this rule closes.
      strength: secondary
    - function: Measure
      subcategory: MS.2.7
      context: Provides the security and resilience evidence MEASURE 2.7 asks to be documented for code-execution attempts.
      strength: secondary
  iso_42001:
    - clause: '8.1'
      context: 'Clause 8.1 covers control of operational processes; an inline module fetched from a data: URI enters the runtime without any of the review the supply chain otherwise receives.'
      strength: primary
    - clause: '6.2'
      context: Detecting loader-borne code execution is an AIMS information security objective under clause 6.2, operationalised as a runtime control.
      strength: secondary
    - clause: '8.4'
      context: Impact assessment under clause 8.4 must account for arbitrary code running with the agent's own privileges; these events are that path's monitoring evidence.
      strength: secondary
tags:
  category: privilege-escalation
  subcategory: module-loader-execution
  scan_target: both
  confidence: medium
agent_source:
  type: mcp_exchange
  framework:
    - any
  provider:
    - any
detection:
  conditions:
    - field: content
      operator: regex
      value: (?i)\bimport\s*\(\s*(?:\\{0,2}["'`])\s*data:\s*(?:text|application)/(?:x-)?(?:java|ecma)script\s*[,;](?:(?:\\{1,3}["'`])?(?:process\s*\.\s*env|globalThis\s*\.\s*process|child_process|\bfetch\s*\(\s*(?:\\{0,2}["'`])?\s*(?:https?:)?//|sendBeacon|XMLHttpRequest|\bexecSync\b|\bspawnSync\b|\brequire\s*\(|\beval\s*\()|[^\s"'`\\\n](?:[^"'`\\\n]|\\[^"'`\n]){0,39}(?:\\{1,3}["'`])?(?:process\s*\.\s*env|globalThis\s*\.\s*process|child_process|\bfetch\s*\(\s*(?:\\{0,2}["'`])?\s*(?:https?:)?//|sendBeacon|XMLHttpRequest|\bexecSync\b|\bspawnSync\b|\brequire\s*\(|\beval\s*\())
      description: 'Dynamic import of a data: URI JavaScript module whose inline body touches process/env, an outbound request, or a child process'
  condition: any
  false_positives:
    - 'Tooling that materialises a module from a string and legitimately needs process.env inside it -- loader hooks, test-runner ESM shims, bundler virtual modules. Measured as the closest benign shape and the reason the bare data: form is not flagged.'
    - 'Documentation or a security article that quotes a complete, correctly quoted data: URI import payload verbatim -- including this rule''s own MEASURED GAP paragraph and its true_positive inputs. Prose that merely NAMES the construct (`import(data:text/javascript,...)` without quotes, or with typographic quotes) no longer matches.'
    - 'A JSON tool argument whose inline module body itself contains a nested quoted string before its first capability token: the bridge crosses at most one escaped quote, so such a payload is missed rather than a benign neighbour being flagged.'
    - An inline module that legitimately calls fetch() against a relative/same-origin path is NOT flagged; only an absolute or protocol-relative destination counts, since exfiltration needs one.
    - 'MEASURED RESIDUAL: an inline module that reads process.env.NODE_ENV to bake a build flag, and an ESM shim whose inline body calls require(). Both fire and both can be benign; process.env in particular carries no true positive in the fixture set.'
    - 'MEASURED RESIDUAL: a scanner''s own test fixture, or a non-English security document, that carries a complete correctly quoted payload as a literal string. Any pattern rule for this class fires on those.'
response:
  actions:
    - alert
  auto_response_threshold: high
  message_template: |
    [ATR-2026-02620] Dynamic import() of a data: URI module detected. The module body executes at import time with the agent's own privileges. Verify the module specifier is not attacker-controlled (CVE-2025-67489 class).
confidence: 70
