Skip to content

feat: enhance security by preventing raw host object leaks in proxy and enclave - #74

Merged
frontegg-david merged 3 commits into
release/2.15.xfrom
enhance-security-code
Jul 25, 2026
Merged

feat: enhance security by preventing raw host object leaks in proxy and enclave#74
frontegg-david merged 3 commits into
release/2.15.xfrom
enhance-security-code

Conversation

@frontegg-david

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

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • Security
    • Hardened secure proxy behavior for non-configurable, non-writable pinned properties: object/function pinned values are blocked with a SecurityError, while primitive pinned values still work.
    • Strengthened host-value boundary handling for custom globals to prevent exposing host object graphs through nested and chained access.
    • Kept sandbox realm intrinsics and constructor behavior working as expected.
  • Tests
    • Added regression coverage for sandbox escape attempts and proxy invariant attack scenarios involving pinned properties.
  • Documentation
    • Clarified how globals are safely serialized/wrapped and added a “Host Value Boundary” explanation for pinned values.

@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: dbd5de84-1355-40db-a9fc-25e546127b7c

📥 Commits

Reviewing files that changed from the base of the PR and between d35273b and c77d358.

📒 Files selected for processing (2)
  • docs/enclave/core-libraries/enclave-browser/security-architecture.mdx
  • docs/enclave/core-libraries/enclave-vm/double-vm.mdx

📝 Walkthrough

Walkthrough

This change prevents host-owned object and function references from escaping through pinned properties. Host-global wrapping preserves ownership across nested values and calls, with documentation and tests covering invariants, primitives, constructors, custom globals, and realm intrinsics.

Changes

Host Reference Boundary

Layer / File(s) Summary
Proxy invariant handling
libs/core/src/secure-proxy.ts, libs/core/src/double-vm/parent-vm-bootstrap.ts
Pinned object and function values now throw SecurityError, while pinned primitives remain readable.
Host ownership propagation
libs/core/src/double-vm/parent-vm-bootstrap.ts
Separate host and realm caches, recursive wrapping, call-result handling, and host-mode global injection preserve ownership semantics.
Regression validation and documentation
libs/core/src/__tests__/secure-proxy.spec.ts, libs/core/src/__tests__/enclave.host-reference-leak.spec.ts, docs/enclave/core-libraries/...
Tests and security documentation cover pinned properties, nested reads, constructors, ordinary globals, primitives, and realm intrinsics.

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

Sequence Diagram(s)

sequenceDiagram
  participant HostGlobals as hostConfig.globals
  participant ParentVM as parent VM
  participant Sandbox as sandbox code
  HostGlobals->>ParentVM: inject globals in host mode
  ParentVM->>ParentVM: recursively wrap reachable values
  Sandbox->>ParentVM: access pinned property
  ParentVM-->>Sandbox: return primitive or throw SecurityError
Loading

Possibly related PRs

  • agentfront/enclave#14: Both modify SecureProxy handling for custom globals and recursively reachable host values.
  • agentfront/enclave#71: Both modify SecureProxy invariant handling and related host-reference-leak tests.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: hardening proxy and enclave boundaries to prevent raw host object leaks.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch enhance-security-code

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: 2

🧹 Nitpick comments (4)
libs/core/src/__tests__/enclave.host-reference-leak.spec.ts (2)

287-294: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

dispose() is only reached on the happy path.

Every test in this block calls enclave.dispose() as the last statement, so a failed expectation leaks the enclave (and its worker pool) for the remainder of the run, which can cascade into unrelated timeouts. Track the instance in a let and dispose in afterEach, or wrap the body in try/finally.

🤖 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/__tests__/enclave.host-reference-leak.spec.ts` around lines 287
- 294, Update the tests using enclaveWithHostGlobals so enclave disposal is
guaranteed even when assertions fail. Track each Enclave instance for cleanup in
afterEach, or wrap each test body in try/finally, while preserving the existing
test behavior and ensuring the worker pool is always disposed.

309-312: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert why the PoC failed, not just that it failed.

expect(result.success).toBe(false) passes for any failure — a syntax error, a timeout, or a future refactor that breaks the fixture would keep this green while the membrane regressed. Pin the error to the denial.

💚 Proposed assertion
     const result = await enclave.run(code);
     assertNoRce(result);
     expect(result.success).toBe(false);
+    expect(result.error?.message).toMatch(/blocked/i);
     enclave.dispose();
🤖 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/__tests__/enclave.host-reference-leak.spec.ts` around lines 309
- 312, Strengthen the assertion in the enclave run test by verifying that the
failed result contains the expected membrane-denial error, rather than only
checking result.success is false. Use the result’s existing error field and
assert the specific denial message or identifying error code, while preserving
the existing assertNoRce and disposal behavior.
libs/core/src/__tests__/secure-proxy.spec.ts (1)

413-423: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Test name attributes the throw to the wrong defense, and the freeze is a no-op.

A class's prototype is already non-writable and non-configurable, so Object.freeze(Widget.prototype) changes nothing about what is asserted. More importantly, createSecureProxy defaults to throwOnBlocked: false, so the blocked-property path would return undefined here — the throw comes from the new descriptor branch, not "the blocked-property path". Renaming keeps the regression suite honest about which control is under test.

♻️ Proposed rename
-    it('should not leak a frozen class prototype through the blocked-property path', () => {
+    it('should not leak a class prototype via the non-configurable descriptor path', () => {
       class Widget {
         value = 1;
       }
-      Object.freeze(Widget.prototype);
       const proxy = createSecureProxy(Widget) as unknown as Record<string, unknown>;
 
-      // `prototype` on a class is non-configurable and non-writable, so the old invariant
-      // concession returned it raw — from there `.constructor.constructor` is host Function.
+      // A class `prototype` is non-configurable and non-writable, so the old invariant
+      // concession returned it raw — from there `.constructor.constructor` is host Function.
       expect(() => proxy['prototype']).toThrow(/blocked/i);
     });
🤖 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/__tests__/secure-proxy.spec.ts` around lines 413 - 423, Rename
the test around createSecureProxy to describe the descriptor/invariant defense
rather than the blocked-property path, and remove the redundant
Object.freeze(Widget.prototype) setup. Preserve the assertion that accessing
proxy['prototype'] throws, since the default throwOnBlocked behavior is not the
control under test.
libs/core/src/secure-proxy.ts (1)

493-499: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Descriptor lookup now runs on every get.

Object.getOwnPropertyDescriptor allocates a fresh object per property read on a hot path (recursive proxying already multiplies reads). Consider short-circuiting for Symbol.toStringTag/well-known symbols, or only performing the lookup when the value is object/function — though note the current ordering is what makes the invariant check sound, so any reordering needs care.

🤖 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 493 - 499, Reduce
descriptor-allocation overhead in the proxy get handling around
Object.getOwnPropertyDescriptor by short-circuiting safe Symbol.toStringTag or
other well-known symbol reads before the lookup, while preserving the required
non-configurable, non-writable property invariant checks for other values.
Ensure any fast path cannot expose an object or function across the proxy
barrier.
🤖 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/core/src/secure-proxy.ts`:
- Around line 500-509: Update the documentation under docs/enclave/** for the
exported createSecureProxy API to describe that accessing non-configurable,
non-writable properties containing objects or functions now throws a
SecurityError instead of returning the value. Keep the documentation aligned
with the behavior implemented around descriptor handling.
- Around line 499-510: Update the get-trap logic around descriptor handling in
secure-proxy.ts so non-configurable, non-writable object/function properties are
denied only when the value is host-owned, matching the double-VM path. Preserve
returning exact primitive values and ordinary reads of frozen sandbox-owned
objects; reuse the existing ownership check rather than rejecting every
object-valued descriptor.

---

Nitpick comments:
In `@libs/core/src/__tests__/enclave.host-reference-leak.spec.ts`:
- Around line 287-294: Update the tests using enclaveWithHostGlobals so enclave
disposal is guaranteed even when assertions fail. Track each Enclave instance
for cleanup in afterEach, or wrap each test body in try/finally, while
preserving the existing test behavior and ensuring the worker pool is always
disposed.
- Around line 309-312: Strengthen the assertion in the enclave run test by
verifying that the failed result contains the expected membrane-denial error,
rather than only checking result.success is false. Use the result’s existing
error field and assert the specific denial message or identifying error code,
while preserving the existing assertNoRce and disposal behavior.

In `@libs/core/src/__tests__/secure-proxy.spec.ts`:
- Around line 413-423: Rename the test around createSecureProxy to describe the
descriptor/invariant defense rather than the blocked-property path, and remove
the redundant Object.freeze(Widget.prototype) setup. Preserve the assertion that
accessing proxy['prototype'] throws, since the default throwOnBlocked behavior
is not the control under test.

In `@libs/core/src/secure-proxy.ts`:
- Around line 493-499: Reduce descriptor-allocation overhead in the proxy get
handling around Object.getOwnPropertyDescriptor by short-circuiting safe
Symbol.toStringTag or other well-known symbol reads before the lookup, while
preserving the required non-configurable, non-writable property invariant checks
for other values. Ensure any fast path cannot expose an object or function
across the proxy barrier.
🪄 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: 3e12c6cc-e476-4067-bb77-a690875e882d

📥 Commits

Reviewing files that changed from the base of the PR and between 1ef5de3 and d8ff1ce.

📒 Files selected for processing (4)
  • libs/core/src/__tests__/enclave.host-reference-leak.spec.ts
  • libs/core/src/__tests__/secure-proxy.spec.ts
  • libs/core/src/double-vm/parent-vm-bootstrap.ts
  • libs/core/src/secure-proxy.ts

Comment thread libs/core/src/secure-proxy.ts
Comment thread libs/core/src/secure-proxy.ts

@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: 2

🤖 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 `@docs/enclave/core-libraries/enclave-browser/security-architecture.mdx`:
- Line 143: Correct the malformed sentence in the security guidance by replacing
“Hand sandboxed code plain serialized data” with wording that states sandboxed
code should receive only plain serialized data. Preserve the surrounding
explanation unchanged.
- Around line 141-143: Update the browser implementation’s supplied get trap to
enforce the documented boundary for non-configurable, non-writable own data
properties: return primitive values unchanged, but throw SecurityError for
object or function values before returning descriptor.value, regardless of
throwOnBlocked. Align the behavior in the get-trap logic of
inner-iframe-bootstrap with the security-architecture documentation.
🪄 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: 7b5d832b-2c35-4d57-a8b7-d099d03534a3

📥 Commits

Reviewing files that changed from the base of the PR and between d8ff1ce and d35273b.

📒 Files selected for processing (3)
  • docs/enclave/core-libraries/enclave-browser/security-architecture.mdx
  • libs/core/src/__tests__/enclave.host-reference-leak.spec.ts
  • libs/core/src/__tests__/secure-proxy.spec.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • libs/core/src/tests/secure-proxy.spec.ts
  • libs/core/src/tests/enclave.host-reference-leak.spec.ts

Comment thread docs/enclave/core-libraries/enclave-browser/security-architecture.mdx Outdated
Comment thread docs/enclave/core-libraries/enclave-browser/security-architecture.mdx Outdated
@frontegg-david
frontegg-david merged commit c938a47 into release/2.15.x Jul 25, 2026
4 checks passed
@frontegg-david
frontegg-david deleted the enhance-security-code branch July 25, 2026 20:53
@github-actions

Copy link
Copy Markdown
Contributor

Cherry-pick Created

A cherry-pick PR to main has been automatically created.

Please review and merge if this change should also be in main.

If the cherry-pick is not needed, close the PR.

frontegg-david added a commit that referenced this pull request Jul 26, 2026
…ks in proxy and enclave (#75)

Cherry-picked from #74 (merged to release/2.15.x)
Original commit: c938a47

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: frontegg-david <69419539+frontegg-david@users.noreply.github.com>
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