@beaugmented/s3-sandbox
A local S3-compatible server for development and isolated tests, derived from S3rver. Requires Node 26.11.1 and ESM.
This fork is maintained by Augmented. Please report issues and request support in this repository.
Install and run
pnpm add -D @beaugmented/s3-sandbox
pnpm exec s3-sandbox --directory ./s3-data --address 127.0.0.1 --port 4568
pnpm exec s3-sandbox --help
The CLI creates the data directory when needed. --configure-bucket <name> [configs...] preconfigures a bucket with optional XML files and can be repeated. --key and --cert enable TLS. --no-vhost-buckets disables bucket hostnames.
Programmatic API
import { mkdtempSync, rmSync } from "node:fs"
import { tmpdir } from "node:os"
import path from "node:path"
import S3Sandbox from "@beaugmented/s3-sandbox"
const directory = mkdtempSync(path.join(tmpdir(), "s3-test-"))
const sandbox = new S3Sandbox({
directory,
address: "127.0.0.1",
port: 0,
silent: true,
configureBuckets: [{ name: "test-bucket" }],
})
try {
const { port } = await sandbox.run()
// Use http://127.0.0.1:${port} as the client's endpoint.
} finally {
await sandbox.close()
rmSync(directory, { recursive: true, force: true })
}
Use a dedicated existing directory for programmatic instances. Port 0 selects an available port; run() resolves to the listening address. Close the server in test cleanup. Stored objects survive restarts unless resetOnClose is enabled.
| Option | Default | Meaning |
|---|---|---|
directory | OS temporary directory + s3-sandbox | Filesystem storage root. |
address | localhost | Host or IP to bind to. |
port | 4568 | HTTP port; 0 selects an available port. |
silent | false | Suppress request logs. |
key, cert, pfx | unset | Node HTTPS server options. |
serviceEndpoint | amazonaws.com | Service hostname suffix for virtual hosts. |
vhostBuckets | true | Recognize bucket names in hostnames. |
resetOnClose | false | Delete all bucket data on server close. |
allowMismatchedSignatures | false | Accept well-formed requests with mismatched signatures. |
configureBuckets | [] | Bucket names and optional raw XML strings or buffers. |
run(callback) and close(callback) retain the callback forms and return the instance. callback() and its alias getMiddleware() return a handler suitable for http.createServer(). Call configureBuckets() before mounting a handler directly. reset() clears stored bucket data.
The server uses Hono internally and extends Node's EventEmitter. Its public API is the S3 sandbox lifecycle and event API; HTTP framework inheritance and context customization are not supported.
Mount in Hono
getHonoMiddleware() mounts the sandbox in a Hono app served by its Node adapter. Pass the same mount path used in the route, without a trailing slash; omit it when mounting at the root. The adapter supplies the raw Node request needed for S3 signing and streaming uploads.
import { Hono } from "hono"
import { serve, type HttpBindings } from "@hono/node-server"
await sandbox.configureBuckets()
const app = new Hono<{ Bindings: HttpBindings }>()
app.use("/s3/*", sandbox.getHonoMiddleware("/s3"))
const server = serve({
fetch: app.fetch,
port: 4568,
overrideGlobalObjects: false,
})
// Use http://localhost:4568/s3 as the S3 client's endpoint.
// Close the host server during cleanup; sandbox.run() was not called.
await new Promise<void>((resolve, reject) =>
server.close((error) => (error ? reject(error) : resolve())),
)
AWS SDK v3
Use S3RVER for both signing keys and enable path-style access:
import { S3Client } from "@aws-sdk/client-s3"
const client = new S3Client({
endpoint: "http://127.0.0.1:4568",
region: "us-east-1",
forcePathStyle: true,
credentials: { accessKeyId: "S3RVER", secretAccessKey: "S3RVER" },
requestChecksumCalculation: "WHEN_REQUIRED",
responseChecksumValidation: "WHEN_REQUIRED",
})
The checksum settings select the S3 behavior currently implemented by the sandbox. Current SDK v3 requests and legacy SDK v2 client contracts are tested.
Supported operations
- Buckets: create, delete, list, object listings with prefixes, delimiters, markers and continuation tokens, location queries, and CORS/website configurations.
- Objects: PUT, multipart form POST, GET, HEAD, copy, single and batch deletion, tags, metadata, byte ranges, and dummy ACL responses.
- Multipart uploads: initiate, upload/copy parts, and complete.
- Authentication: signature versions 2 and 4, presigned URLs, and signed chunked uploads.
- Websites: index/error documents, redirects, routing rules, and virtual-host requests.
This is a partial S3 implementation intended for local development and tests. Unsupported operations return S3-shaped errors.
Object reads support single byte ranges, including bytes=0-0, open-ended ranges, and suffix ranges such as bytes=-10. Ends and suffix lengths beyond the object size are clamped to the available bytes. Unsatisfiable ranges and ranges against empty objects return 416 InvalidRange; malformed and multiple range headers are ignored.
For multipart POST uploads, submit key, metadata, and response-control fields before the file part. File contents stream to storage even when the part has no filename. The server stores the first file and ignores later form fields.
Bucket configuration and websites
Resolve bundled XML examples through their package exports:
import { readFileSync } from "node:fs"
import S3Sandbox from "@beaugmented/s3-sandbox"
const cors = new URL(
import.meta.resolve("@beaugmented/s3-sandbox/example/cors.xml"),
)
const website = new URL(
import.meta.resolve("@beaugmented/s3-sandbox/example/website.xml"),
)
const sandbox = new S3Sandbox({
directory: "./s3-data",
configureBuckets: [
{
name: "site.local",
configs: [readFileSync(cors), readFileSync(website)],
},
],
})
Website requests can use a bucket path such as http://localhost:4568/site.local/. To use http://site.local:4568/, resolve site.local to 127.0.0.1 locally. The uploaded index document must match the bucket's website configuration.
See the AWS examples for CORS and website configuration.
Events
Subscribe before performing object operations:
sandbox.on("event", (event) => {
console.log(event.Records[0].eventName)
})
PUT, POST, COPY, and DELETE operations emit notifications following the S3 event structure.
License and attribution
This fork is licensed under MPL-2.0. Jamie Hall is the original author of S3rver, on which this project is based. NOTICE identifies the inherited material and its original MIT notice. The upstream filesystem suffixes are preserved.
The absorbed multipart implementation also retains Brian White's MIT notice.