Skip to content

feat: enhance security by clarifying host value boundaries and pinned property behavior in documentation - #76

Merged
frontegg-david merged 5 commits into
release/2.15.xfrom
enhace-ast-parser
Jul 26, 2026
Merged

feat: enhance security by clarifying host value boundaries and pinned property behavior in documentation#76
frontegg-david merged 5 commits into
release/2.15.xfrom
enhace-ast-parser

Conversation

@frontegg-david

@frontegg-david frontegg-david commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • Security Enhancements
    • Added runtime guarding for dynamic computed property access to block constructor/prototype lookups, including reconstructed and “laundered” keys.
    • Strengthened the secure-proxy membrane recursion to fail closed at a deeper threshold (256) to prevent returning unwrapped references.
    • Hardened safe reflection, including wrapping function results and tightening getOwnPropertyDescriptor to block descriptor-based escape paths.
  • Bug Fixes
    • Blocked additional bind-chain and computed-key escape attempts and reduced false positives for benign computed indexing/keys.
  • Tests
    • Expanded regression coverage for the above scenarios.

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b762b4b2-5769-490d-b209-b6fc9abd1746

📥 Commits

Reviewing files that changed from the base of the PR and between fc420e9 and ec36117.

📒 Files selected for processing (3)
  • libs/core/src/__tests__/secure-proxy.spec.ts
  • libs/core/src/double-vm/parent-vm-bootstrap.ts
  • libs/core/src/secure-proxy.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • libs/core/src/tests/secure-proxy.spec.ts
  • libs/core/src/double-vm/parent-vm-bootstrap.ts

📝 Walkthrough

Walkthrough

The PR adds static and runtime protection for laundered dangerous property keys, integrates computed-key guarding into Enclave, and makes secure-proxy recursion, reflection, and descriptor handling fail closed. Regression tests cover escape attempts, recursion limits, reflection, and permitted access.

Changes

Sandbox escape defenses

Layer / File(s) Summary
Static tainted-key detection
libs/ast/src/rules/resource-exhaustion.rule.ts, libs/ast/src/__tests__/resource-exhaustion-tainted-key.spec.ts
ResourceExhaustionRule resolves constructed constructor and prototype keys, reports computed access, and tests blocked and allowed cases.
Runtime computed-key guarding
libs/ast/src/transforms/*, libs/ast/src/index.ts, libs/core/src/enclave.ts
Dynamic computed members are wrapped with a configurable runtime guard, exported through the AST barrel, and applied by Enclave.
Secure-proxy recursion and descriptor hardening
libs/core/src/secure-proxy.ts, libs/core/src/double-vm/parent-vm-bootstrap.ts, libs/browser/src/adapters/inner-iframe-bootstrap.ts
Proxy recursion now fails closed; reflective functions and object-valued descriptors are secured instead of exposing raw references.
Defense regression coverage
libs/core/src/__tests__/*
Tests cover computed-key attacks, bind-chain escapes, recursion overflow, reflection behavior, updated gadget expectations, and permitted deep access.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Enclave
  participant ASTGuard
  participant Sandbox
  participant SecureProxy
  Enclave->>ASTGuard: Transform dynamic computed member accesses
  ASTGuard-->>Enclave: Return guarded code
  Enclave->>Sandbox: Execute transformed code
  Sandbox->>ASTGuard: Resolve computed key
  ASTGuard-->>Sandbox: Return key or throw security error
  Sandbox->>SecureProxy: Traverse wrapped references
  SecureProxy-->>Sandbox: Return proxy or fail closed at recursion limit
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title mentions documentation changes, but this PR is primarily code and test hardening for computed-key and proxy security. Retitle it to describe the actual security hardening, e.g. runtime computed-key guards and secure-proxy recursion fixes.
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch enhace-ast-parser

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
libs/ast/src/transforms/computed-member-guard.transform.ts (1)

114-172: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Silent no-op on non-Program AST leaves dangling __ag_guardKey calls.

guardComputedMemberKeys wraps computed keys and increments guardedCount unconditionally during the walk (lines 127-146), but injectGuardHelper only defines the helper if ast.type === 'Program' (line 167), otherwise it silently returns without injecting anything. If ever called with a non-Program node (the type signature acorn.Node doesn't prevent this), every rewritten computed access would throw an unguarded ReferenceError: __ag_guardKey is not defined at runtime instead of failing with the intended security error — a functional regression rather than a security one, but a confusing one to debug.

♻️ Proposed defensive fix
 export function guardComputedMemberKeys(
-  ast: acorn.Node,
+  ast: acorn.Program,
   parse: (source: string) => acorn.Node,
   options: ComputedMemberGuardOptions = { blockedKeys: DEFAULT_GUARD_BLOCKED_KEYS, throwOnBlocked: true },
 ): ComputedMemberGuardResult {
   const blockedKeys = options.blockedKeys ?? DEFAULT_GUARD_BLOCKED_KEYS;
+  if (ast.type !== 'Program' || !Array.isArray((ast as any).body)) {
+    return { guardedCount: 0 };
+  }
   // Nothing to enforce (e.g. a level that blocks no keys) — leave the code untouched.
   if (blockedKeys.length === 0) {
     return { guardedCount: 0 };
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libs/ast/src/transforms/computed-member-guard.transform.ts` around lines 114
- 172, Ensure guardComputedMemberKeys only rewrites computed members when the
input AST is a Program that can receive the helper, or otherwise fail explicitly
before walking and incrementing guardedCount. Update the interaction between
guardComputedMemberKeys and injectGuardHelper so every generated __ag_guardKey
call has a corresponding injected helper, avoiding silent no-ops for non-Program
nodes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@libs/ast/src/rules/resource-exhaustion.rule.ts`:
- Around line 480-512: Update evaluateArrayJoin so Array.prototype.toString()
uses a comma separator, matching its runtime behavior for multi-element arrays;
retain the existing join handling, including its default comma and explicit
separator arguments.

In `@libs/core/src/__tests__/enclave.bind-depth-escape.spec.ts`:
- Around line 20-30: Extend the Enclave bind-depth test cases to include a chain
exceeding 256 .bind reads, and assert that the result fails with an error
matching /recursion depth/i. Update the assertion for this case so it
specifically verifies the recursion fail-closed policy rather than relying on
assertNoRce, while preserving the existing exploit checks for shorter chains.

In `@libs/core/src/double-vm/parent-vm-bootstrap.ts`:
- Around line 421-429: Replace the raw Error thrown by __ag_createSecureProxy
when the recursion depth exceeds MEMBRANE_MAX_RECURSION_DEPTH with the enclosing
IIFE’s existing __ag_createSafeError helper, preserving the current
security-violation message and fail-closed behavior.

In `@libs/core/src/secure-proxy.ts`:
- Around line 479-483: Validate configuredMaxDepth before applying Math.max in
the recursion-depth setup: reject non-finite values such as NaN and Infinity by
falling back to MIN_MEMBRANE_RECURSION_DEPTH, then clamp valid values so
maxDepth is always a finite fail-closed limit. Update the existing maxDepth
calculation near MIN_MEMBRANE_RECURSION_DEPTH without changing the option
precedence.

---

Nitpick comments:
In `@libs/ast/src/transforms/computed-member-guard.transform.ts`:
- Around line 114-172: Ensure guardComputedMemberKeys only rewrites computed
members when the input AST is a Program that can receive the helper, or
otherwise fail explicitly before walking and incrementing guardedCount. Update
the interaction between guardComputedMemberKeys and injectGuardHelper so every
generated __ag_guardKey call has a corresponding injected helper, avoiding
silent no-ops for non-Program nodes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 539c3fdf-9cc8-4e01-88ec-e93e050d12f1

📥 Commits

Reviewing files that changed from the base of the PR and between c938a47 and e72d335.

📒 Files selected for processing (15)
  • libs/ast/src/__tests__/resource-exhaustion-tainted-key.spec.ts
  • libs/ast/src/index.ts
  • libs/ast/src/rules/resource-exhaustion.rule.ts
  • libs/ast/src/transforms/computed-member-guard.transform.ts
  • libs/ast/src/transforms/index.ts
  • libs/browser/src/adapters/inner-iframe-bootstrap.ts
  • libs/core/src/__tests__/enclave.bind-depth-escape.spec.ts
  • libs/core/src/__tests__/enclave.computed-key-guard.spec.ts
  • libs/core/src/__tests__/enclave.host-reference-leak.spec.ts
  • libs/core/src/__tests__/function-gadget-attacks.spec.ts
  • libs/core/src/__tests__/resource-exhaustion.spec.ts
  • libs/core/src/__tests__/secure-proxy.spec.ts
  • libs/core/src/double-vm/parent-vm-bootstrap.ts
  • libs/core/src/enclave.ts
  • libs/core/src/secure-proxy.ts

Comment thread libs/ast/src/rules/resource-exhaustion.rule.ts
Comment thread libs/core/src/__tests__/enclave.bind-depth-escape.spec.ts
Comment thread libs/core/src/double-vm/parent-vm-bootstrap.ts
Comment thread libs/core/src/secure-proxy.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
libs/core/src/double-vm/parent-vm-bootstrap.ts (1)

421-453: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Depth-cap check runs before the primitive/type check, so a long property chain can throw on a totally safe primitive value.

In __ag_createSecureProxy, the get trap (439-453) recurses unconditionally for every property value (return __ag_createSecureProxy(value, depth + 1);), with no object/function guard first — unlike the corresponding get trap in the main createSecureProxy (944-953), which only recurses for object/function values. Combined with the depth-check being placed before the primitive-type check (422-429), a sufficiently long property/bind chain off __safe_concat/__safe_template that eventually resolves to a plain primitive (e.g. .length, .name) will incorrectly throw a SecurityError for that primitive instead of returning it — primitives can never carry the escape risk this cap defends against.

♻️ Proposed fix
	  function __ag_createSecureProxy(obj, depth) {
	    if (depth === undefined) depth = 0;
	    if (!__ag_Proxy || !__ag_Reflect) return obj;
+	    if (obj === null || (typeof obj !== 'object' && typeof obj !== 'function')) return obj;
	    if (depth > ${MEMBRANE_MAX_RECURSION_DEPTH}) {
	      throw __ag_createSafeError('Security violation: secure-proxy recursion depth exceeded.');
	    }
-	    if (obj === null || (typeof obj !== 'object' && typeof obj !== 'function')) return obj;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libs/core/src/double-vm/parent-vm-bootstrap.ts` around lines 421 - 453,
Update the get trap in __ag_createSecureProxy to recurse through
__ag_createSecureProxy only when the retrieved value is an object or function;
return primitive values directly. Preserve the existing depth-cap behavior for
values that still require proxying, while ensuring safe primitives such as
length or name do not trigger a recursion-depth error.
libs/core/src/secure-proxy.ts (1)

671-722: 🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

Accessor descriptors still leak raw functions from getOwnPropertyDescriptor
libs/core/src/secure-proxy.ts#L678-L721 and libs/core/src/double-vm/parent-vm-bootstrap.ts#L984-L1031 only harden descriptor.value; descriptor.get/descriptor.set still return raw functions, so a descriptor read can bypass the membrane. Throw for non-configurable accessors, proxy get/set for configurable ones, and add accessor-descriptor regression coverage in libs/core/src/__tests__/secure-proxy.spec.ts#L658-L690.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libs/core/src/secure-proxy.ts` around lines 671 - 722, Accessor descriptors
can expose raw getter/setter functions through getOwnPropertyDescriptor. Update
the descriptor handling in libs/core/src/secure-proxy.ts lines 671-722 and
libs/core/src/double-vm/parent-vm-bootstrap.ts lines 983-1031 to throw for
non-configurable accessors and proxy get/set functions for configurable
accessors, preserving proxy invariants. Add regression coverage in
libs/core/src/__tests__/secure-proxy.spec.ts lines 658-690 for both accessor
types and membrane enforcement.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@libs/core/src/double-vm/parent-vm-bootstrap.ts`:
- Around line 421-453: Update the get trap in __ag_createSecureProxy to recurse
through __ag_createSecureProxy only when the retrieved value is an object or
function; return primitive values directly. Preserve the existing depth-cap
behavior for values that still require proxying, while ensuring safe primitives
such as length or name do not trigger a recursion-depth error.

In `@libs/core/src/secure-proxy.ts`:
- Around line 671-722: Accessor descriptors can expose raw getter/setter
functions through getOwnPropertyDescriptor. Update the descriptor handling in
libs/core/src/secure-proxy.ts lines 671-722 and
libs/core/src/double-vm/parent-vm-bootstrap.ts lines 983-1031 to throw for
non-configurable accessors and proxy get/set functions for configurable
accessors, preserving proxy invariants. Add regression coverage in
libs/core/src/__tests__/secure-proxy.spec.ts lines 658-690 for both accessor
types and membrane enforcement.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1c6ea070-462d-4f29-82a6-b33124f72113

📥 Commits

Reviewing files that changed from the base of the PR and between e72d335 and 7161a80.

📒 Files selected for processing (3)
  • libs/core/src/__tests__/secure-proxy.spec.ts
  • libs/core/src/double-vm/parent-vm-bootstrap.ts
  • libs/core/src/secure-proxy.ts

…r descriptors and ensuring safe proxying of getter/setter functions
…r descriptors and ensuring safe proxying of getter/setter functions
@frontegg-david
frontegg-david merged commit 140dbea into release/2.15.x Jul 26, 2026
4 checks passed
@frontegg-david
frontegg-david deleted the enhace-ast-parser branch July 26, 2026 00:46
@github-actions

Copy link
Copy Markdown
Contributor

Cherry-pick Conflict

Automatic cherry-pick to main failed due to merge conflicts.

An issue has been created with manual instructions. Please resolve if this change should also be in main.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant