Skip to content

Conversation

@ghostwriternr
Copy link
Member

@ghostwriternr ghostwriternr commented Nov 5, 2025

Add ability to mount S3-compatible buckets as local directories using s3fs-fuse.

Key features:

  • Automatic provider detection from endpoint URL
  • Automatic s3fs flag configuration per provider
  • Credential injection via environment variables
  • Support for both sandbox and session objects

Supported providers:

  • Cloudflare R2
  • Amazon S3
  • Google Cloud Storage
  • MinIO
  • Custom S3-compatible services (with manual configuration)

Example:

const sandbox = getSandbox(env.Sandbox, 'user-123');
await sandbox.mountBucket('my-bucket', '/mnt/data', {
  endpoint: `https://${env.CLOUDFLARE_ACCOUNT_ID}.r2.cloudflarestorage.com`,
  credentials: {
    accessKeyId: env.R2_ACCESS_KEY_ID,
    secretAccessKey: env.R2_SECRET_ACCESS_KEY
  }
});

// Use mounted bucket
await sandbox.exec('ls /mnt/data');

Includes E2E tests and comprehensive error handling.

Fixes #130

Enable sandboxes to mount S3-compatible buckets as local filesystem
paths using s3fs-fuse. This allows code executing in sandboxes to
read and write files directly to cloud storage using standard file
operations.

The implementation provides automatic credential detection from
environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
and intelligent provider detection from endpoint URLs. Supported
providers include AWS S3, Cloudflare R2, Google Cloud Storage,
MinIO, Backblaze B2, Wasabi, and DigitalOcean Spaces.

Each provider has optimized s3fs flags (e.g., R2 requires
nomixupload and endpoint=auto) to ensure reliable operation. Users
can override these defaults by providing custom s3fsOptions.
Remove examples and verbose logging to keep the codebase clean.
Inline single-use injectCredentials method. Update CI workflow to
pass R2 credentials from GitHub secrets instead of relying on
local .env setup.
Apply stricter criteria for v1 by reducing provider list from 8 to 4.
Remove backblaze, wasabi, and digitalocean support. Updated type
definitions, detection logic, and test cases accordingly.
Enable bucket mounting/unmounting from session objects returned by
createSession(). Sessions share the filesystem, so mount operations
affect all sessions in the sandbox.
@changeset-bot
Copy link

changeset-bot bot commented Nov 5, 2025

🦋 Changeset detected

Latest commit: 624c099

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@cloudflare/sandbox Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new
Copy link

pkg-pr-new bot commented Nov 5, 2025

Open in StackBlitz

npm i https://pkg.pr.new/cloudflare/sandbox-sdk/@cloudflare/sandbox@190

commit: 624c099

@github-actions
Copy link
Contributor

github-actions bot commented Nov 5, 2025

🐳 Docker Image Published

FROM cloudflare/sandbox:0.0.0-pr-190-f0ac765

Version: 0.0.0-pr-190-f0ac765

You can use this Docker image with the preview package from this PR.

Add shell escaping for user-provided input in mount paths, bucket
names, git URLs, and branch names. Use shellEscape() utility in
shared package for consistent POSIX single-quote escaping.

Fix race condition in mountBucket() by reserving mount path before
executing mount operations.

Fix provider detection to use endsWith() instead of includes() to
prevent malicious subdomain matching.
Switches from environment variables to password files for s3fs authentication,
eliminating credential race conditions and improving isolation. Each mount now
gets a unique password file that's cleaned up on unmount or destroy.

Also fixes s3fs options injection vulnerability by escaping the entire options
string before passing to shell.
R2 mounts passed both endpoint=auto and explicit url= causing
conflicting s3fs configuration. Removed endpoint=auto since
explicit URL is always provided.

Failed unmounts deleted tracking entry while mount stayed active,
orphaning the mount. Move delete into try block to only execute
on successful unmount.
Port 9000 detection was unreliable and could match non-MinIO
services. MinIO buckets still work via safe fallback defaults
(use_path_request_style).
Merge s3fs/fuse installation with runtime packages to reduce
image layer count.
@cloudflare cloudflare deleted a comment from claude bot Nov 6, 2025
@claude
Copy link
Contributor

claude bot commented Nov 6, 2025

Claude Code Review

This is a well-implemented feature with strong security considerations. The implementation follows SDK patterns and includes comprehensive error handling. A few issues need attention:

Critical: Credential Storage in Memory

Issue: The activeMounts Map stores credentials in memory at packages/sandbox/src/sandbox.ts:264 and packages/sandbox/src/sandbox.ts:292. While these aren't persisted to Durable Object storage (good), storing credentials in memory creates risks:

  • Credentials remain accessible for the lifetime of the DO
  • Could be exposed through debugging/logging if Map is inspected
  • Password files already provide credential access to s3fs; no need to keep credentials in MountInfo

Fix: Remove credentials field from MountInfo type (packages/sandbox/src/storage-mount/types.ts:15). The password file is sufficient for s3fs operation and gets cleaned up properly.

Security: Mount Command Logging

Issue: At packages/sandbox/src/sandbox.ts:466, the full s3fs mount command is logged at debug level. While the password is in a separate file, the command string still contains the endpoint URL which may include sensitive account IDs.

Consider: Either remove this debug log or sanitize the endpoint to hide account identifiers (e.g., replace account ID with ***).

Type Export Missing

Issue: MountInfo is exported from SDK's main index but it's an internal type that shouldn't be in the public API. Users don't need this.

Fix: Remove MountInfo from any public exports in packages/sandbox/src/index.ts. Only MountBucketOptions, BucketCredentials, and BucketProvider should be public.

Verification Needed

The E2E test at tests/e2e/bucket-mounting.test.ts:115 doesn't use shellEscape for mount path, which is inconsistent with the implementation. While test data is controlled, consider updating for consistency.


What Works Well

Security measures:

  • ✓ POSIX shell escaping on all command arguments (shellEscape used consistently)
  • ✓ Password files with 0600 permissions
  • ✓ Random UUID password file paths
  • ✓ Cleanup on error via try-catch
  • ✓ Cleanup on destroy()
  • ✓ Credentials never logged
  • ✓ activeMounts not persisted to DO storage

Architecture:

  • ✓ Provider detection logic clean and testable
  • ✓ Credential priority (explicit > env vars) well-documented
  • ✓ Race condition prevention via mount path reservation
  • ✓ Error classes follow SDK patterns
  • ✓ Types properly defined in shared package

Testing:

  • ✓ E2E test with real R2 bucket
  • ✓ Unit tests for detection logic
  • ✓ CI integration with secrets

Code quality:

  • ✓ Changeset included (minor bump - correct for new feature)
  • ✓ Error messages helpful and actionable
  • ✓ Validation thorough (bucket name regex, URL parsing, path checks)

mountPath: string;
endpoint: string;
provider: BucketProvider | null;
credentials: BucketCredentials;
Copy link
Contributor

Choose a reason for hiding this comment

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

Security concern: Storing credentials in the MountInfo object keeps sensitive data in memory for the lifetime of the Durable Object. Since the password file already provides credential access to s3fs and is properly cleaned up, there's no need to retain credentials here.

Consider removing the credentials field entirely to minimize credential exposure.

const optionsStr = shellEscape(s3fsArgs.join(','));
const mountCmd = `s3fs ${shellEscape(bucket)} ${shellEscape(mountPath)} -o ${optionsStr}`;

this.logger.debug(`Executing mount command: ${mountCmd}`, {
Copy link
Contributor

Choose a reason for hiding this comment

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

Logging the full mount command exposes the endpoint URL which may contain sensitive account IDs (e.g., Cloudflare account ID in R2 URLs). Consider sanitizing the endpoint or removing this debug log.

mountPath,
endpoint: options.endpoint,
provider,
credentials,
Copy link
Contributor

Choose a reason for hiding this comment

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

Credentials stored here remain in memory. Consider removing credentials from MountInfo since the password file is sufficient for s3fs operation.

@alexgusevski
Copy link

Hey @ghostwriternr are there any timelines for when this is pushed? Or maybe if there is a way for us to run this branch on our account? This is the last we need to switch to CF :)

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.

Possible to mount s3 bucket in sandbox?

2 participants