Skip to content

[Detail Bug] Sandbox: Dependency whitelist in vmOpts.allow is ignored, allowing imports of any package #690

@detail-app

Description

@detail-app

Detail Bug Report

https://app.detail.dev/org_06887db3-bf54-40ab-976d-46c66ab2b840/bugs/bug_7d906711-8afc-44f8-b572-01046edcaed1

Summary

  • Context: The function.js module executes user code in an isolated sandbox by calling isolated-function with security options including permissions and dependency whitelists.
  • Bug: The code unconditionally overrides the entire allow object when passing options to isolated-function, discarding any allow.dependencies or allow.permissions that were provided in vmOpts.
  • Actual vs. expected: When vmOpts.allow.dependencies is provided to restrict which npm packages can be imported, this security restriction is silently ignored. Expected behavior is to merge the permissions while preserving the dependencies whitelist.
  • Impact: This allows untrusted code to import and execute any npm package, bypassing dependency whitelisting intended for security.

Code with Bug

In packages/function/src/function.js:

module.exports = async ({
  url,
  code,
  vmOpts,
  browserWSEndpoint,
  needsNetwork = template.isUsingPage(code),
  source = template(code, needsNetwork),
  ...opts
}) => {
  const permissions = needsNetwork && nodeMajor >= 25 ? ['net'] : []
  const [fn, teardown] = isolatedFunction(source, {
    ...vmOpts,                    
    allow: { permissions },  // <-- BUG 🔴 Completely replaces vmOpts.allow, discarding vmOpts.allow.dependencies
    throwError: false
  })
  const result = await fn(url, browserWSEndpoint, opts)
  await teardown()
  return result
}

Explanation

Because ...vmOpts is applied first and then allow: { permissions } is set afterward, any vmOpts.allow object is overwritten. As a result:

  • vmOpts.allow.dependencies is dropped, so dependency whitelisting is not enforced.
  • vmOpts.allow.permissions is also replaced, causing unexpected permission behavior.

Evidence: a repro passing vmOpts.allow.dependencies: ['lodash'] while user code does require('fs') succeeds (returns { isFulfilled: true, value: 'success' }) instead of failing with a dependency-not-allowed error.

Exploit Scenario

An attacker (or any untrusted user code executed by this sandbox) can bypass an intended vmOpts.allow.dependencies whitelist and import arbitrary modules/packages (e.g., fs or other installed npm packages), even when the caller believes imports are restricted.

Recommended Fix

Merge allow rather than replacing it, preserving vmOpts.allow.dependencies and combining permissions:

const vmOptsAllow = vmOpts?.allow || {}
const [fn, teardown] = isolatedFunction(source, {
  ...vmOpts,
  allow: {
    ...vmOptsAllow,
    permissions: [
      ...(vmOptsAllow.permissions || []),
      ...permissions
    ]
  },
  throwError: false
})

History

This bug was introduced in commit a136140. The commit upgraded the isolated-function dependency from v0.1.46 to v0.1.47 (changing allow from an array to an object), and updated the call site to allow: { permissions } without merging with vmOpts.allow, discarding allow.dependencies.

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