title: JavaScript Sandbox Escape by Acquiring the Function Constructor Reflectively
id: ATR-2026-02681
rule_version: 2
status: experimental
description: |
  Detects code handed to an agent code-execution / expression-evaluation tool that reaches the Function constructor WITHOUT writing the well-known x.constructor.constructor ladder, by pulling the "constructor" property off a prototype through a reflective accessor -- Reflect.get(Reflect.getPrototypeOf( async function(){}), "constructor") or Object.getOwnPropertyDescriptor( Object.getPrototypeOf(Object), "constructor").value -- and then invoking it on a source string that returns the global object. Mined from CVE-2025-53626 (@pdfme/common expression evaluation) and its sibling CVE-2025-61927 (happy-dom VM context escape). ATR already detects the direct ladder (ATR-2026-00436 / ATR-2026-01953); those rules key on the literal ".constructor.constructor" or "process.mainModule.require", so the reflective spelling -- which produces the identical Function object -- passes them untouched. The rule requires BOTH halves in one event: the reflective acquisition of "constructor" from a prototype, and a quoted source string whose body returns this/globalThis/global/process. Either half alone is ordinary metaprogramming and is deliberately not matched.
author: ATR Community (CVE sweep)
date: 2026/08/23
schema_version: '0.1'
detection_tier: pattern
maturity: test
severity: critical
references:
  cve:
    - CVE-2025-53626
    - CVE-2025-61927
  cwe:
    - CWE-94
    - CWE-693
    - CWE-1321
  owasp_llm:
    - LLM05:2025
    - LLM06:2025
  owasp_agentic:
    - ASI05:2026
    - ASI04:2026
  mitre_attack:
    - T1059.007 - JavaScript
  mitre_atlas:
    - AML.T0053 - AI Agent Tool Invocation
  external:
    - https://nvd.nist.gov/vuln/detail/CVE-2025-53626
    - https://nvd.nist.gov/vuln/detail/CVE-2025-61927
metadata_provenance:
  cve: human-reviewed
  cwe: human-reviewed
  owasp_llm: human-reviewed
  owasp_agentic: human-reviewed
  mitre_attack: human-reviewed
  mitre_atlas: human-reviewed
compliance:
  eu_ai_act:
    - article: '15'
      context: Article 15 (accuracy, robustness and cybersecurity) requires resilience against attempts to alter system behaviour by exploiting vulnerabilities; this rule detects a sandbox escape that converts a restricted expression evaluator into arbitrary host code execution.
      strength: primary
    - article: '9'
      context: Article 9 (risk management system) requires identified risks to be treated; this rule is the runtime treatment for the code-sandbox escape risk class.
      strength: secondary
  nist_ai_rmf:
    - subcategory: MG.2.3
      context: Sandbox escape from an agent code-execution tool is an identified AI risk requiring an active runtime countermeasure; this rule implements it.
      strength: primary
    - subcategory: MS.2.7
      context: Measuring AI system security by testing whether restricted evaluators can be escaped; this rule is the detection instrument for that measurement.
      strength: secondary
  iso_42001:
    - clause: '8.1'
      context: ISO/IEC 42001 Clause 8.1 (operational planning and control) is operationalised by detecting expression payloads that break the evaluator's containment.
      strength: primary
    - clause: '8.3'
      context: ISO/IEC 42001 Clause 8.3 (AI risk treatment) is implemented by alerting on the reflective Function-constructor acquisition.
      strength: secondary
tags:
  category: privilege-escalation
  subcategory: js-sandbox-escape-reflective
  scan_target: agent
  confidence: high
  suppress_in_code_blocks: true
agent_source:
  type: tool_call
  framework:
    - any
  provider:
    - any
detection:
  condition: any
  conditions:
    - field: content
      operator: regex
      value: (?:\\{0,2}["'][\w-]{0,24}(?:code|script|expression|expr|snippet|program)\\{0,2}["']\s*:\s*\\{0,2}["']|[\w-]{0,24}(?:code|script|expression|expr|snippet|program)\s*=\s*\\{0,2}["'])[^\n]{0,100}(?:getownpropertydescriptor|reflect\.get)\s*\(\s*(?:object|reflect)\.getprototypeof[^\n]{0,40}\\?["']constructor\\?["'][^\n]{0,120}\\?["'`]\s*return\s+(?:this|globalthis|global|process)\b
      description: 'Reflective acquisition of the constructor slot from a prototype, followed on the same line by a quoted source string that returns the global object -- the Function constructor obtained without ever writing .constructor.constructor, then invoked to hand back the host realm. The whole thing must sit inside a CODE-VALUED TOOL ARGUMENT ("code": "...", "script": "...", expression="..."), which is the channel the title claims and the only one that separates evaluating the payload from merely writing it down. See false_positives.'
    - field: content
      operator: regex
      value: (?:\\{0,2}["'][\w-]{0,24}(?:code|script|expression|expr|snippet|program)\\{0,2}["']\s*:\s*\\{0,2}["']|[\w-]{0,24}(?:code|script|expression|expr|snippet|program)\s*=\s*\\{0,2}["'])[^\n]{0,100}(?:object|reflect)\.getprototypeof\s*\(\s*(?:async\s+function|function\s*\*)[^\n]{0,40}\\?["']?constructor\\?["']?[^\n]{0,80}\\?["'`]\s*return\s+(?:this|globalthis|global|process)\b
      description: 'The AsyncFunction / GeneratorFunction ladder in a code-valued tool argument: the prototype of an async or generator function literal is taken, its constructor read (quoted or dotted), and that constructor invoked on a quoted source string returning the global object. Reading the AsyncFunction constructor alone is a legitimate feature-detection idiom and is NOT matched -- only its invocation on a global-returning body, inside a code argument.'
  false_positives:
    - Serialization and deep-clone helpers that read a descriptor for an ordinary property, e.g. Object.getOwnPropertyDescriptor(Object.getPrototypeOf(instance), 'render') -- the property name must literally be 'constructor'
    - A class-identity check such as Object.getPrototypeOf(o).constructor === Object -- no quoted return-the-global source string follows, so nothing matches
    - Prototype-pollution defences that inspect Object.prototype's own descriptors (e.g. for '__proto__') -- the property name is not 'constructor'
    - |-
      ADVERSARIAL REVIEW 2026-08-24, FIXED. Rule v1 matched the escape anywhere in `field: content`. Twelve freshly written benign samples were run through the engine and ALL TWELVE fired. The decisive ones were not exotic: a vm2 sandbox-hardening test asserting the escape is BLOCKED, an isolated-vm regression test, an ESLint "examples of incorrect code" block, a static-analysis fixture array, a sandbox library changelog, a Japanese security blog, a fenced code block in an article explaining CVE-2025-53626, and ATR's own test_cases block. Every one of those is something a coding agent writes to a FILE.
      That is not a coincidence. On a tool_call event `field: content` resolves to fields.content, and src/hook-handler.ts sets that from toolInput.content -- the body of a Write/Edit call. So rule v1's dominant production surface was file writes, not evaluation, which is the exact opposite of what its own title says. Fixed by requiring the payload to sit in a CODE-VALUED ARGUMENT (a code/script/expression-style key, then a quote). "content" is deliberately NOT in that key list: writing a file is not evaluating it. tags.suppress_in_code_blocks was added as a second layer for fenced and backticked article text.
    - 'KNOWN RESIDUAL: code that legitimately needs the global object and reaches for it through this ladder -- a globalThis fallback such as Object.getPrototypeOf(async function(){}).constructor("return this")() -- still fires if it is submitted to an evaluation tool. It is a real idiom, but inside an agent''s code-execution argument it is also indistinguishable from the escape, and the well-behaved spelling (globalThis, or plain Function) is available. Triage, not a pattern change.'
    - 'KNOWN GAP: a payload written to a file and evaluated later by a second step is no longer matched here. Rule v1 claimed that ground and paid for it with a 12/12 false-positive rate on benign security and test content.'
response:
  actions:
    - alert
  message_template: |
    [ATR-2026-02681] Code submitted to an execution/evaluation tool acquires the Function constructor through a reflective accessor and invokes it on a source string that returns the global object. This is a sandbox escape, not metaprogramming -- do not evaluate it.
confidence: 85
