Skip to content
Back to skills

Sandbox Aws

ASecurity

Sign AWS requests (S3 and every other SigV4 service) in a Code node or CodeAct action, with NodeTool's signer running on the host

  • 552 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 1, 2026
ai-agentsgonodeawsapi

Works with

  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned September 1, 2026

npx -y skills add nodetool-ai/nodetool --skill sandbox-aws --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Sandbox Aws?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Sandbox Aws
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/nodetool-ai-sandbox-aws/badge)](https://www.skillsdirectory.com/skills/nodetool-ai-sandbox-aws)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: sandbox-aws
description: Sign AWS requests (S3 and every other SigV4 service) in a Code node or CodeAct action, with NodeTool's signer running on the host
---

# AWS SigV4 in the sandbox

Specifier: `@nodetool-ai/sandbox-aws`. Import it at the top of the body.

S3 is an HTTP API, so `fetch` already reaches it. What the guest cannot do is
sign the request: SigV4 is an HMAC-SHA256 chain over a canonical form of the
request, and one wrong byte gives a signature mismatch that names nothing.
This pack is a **host module**: the chain runs on the host and hands back
headers.

**Nothing here sends a request.** Both exports are pure — they take a request
and return a signed one. The call goes out through the guest's own `fetch`, so
the run's fetch cap and its SSRF guard still apply.

## sigv4 — sign a request

```js
import { sigv4 } from "@nodetool-ai/sandbox-aws";

const accessKeyId = await nodetool.secrets.get("AWS_ACCESS_KEY_ID");
const secretAccessKey = await nodetool.secrets.get("AWS_SECRET_ACCESS_KEY");

const signed = await sigv4({
  method: "GET",
  url: "https://my-bucket.s3.us-east-1.amazonaws.com/?list-type=2&max-keys=100",
  region: "us-east-1",
  service: "s3",
  accessKeyId,
  secretAccessKey
});

const res = await fetch(signed.url, { method: signed.method, headers: signed.headers });
return { xml: await res.text() };
```

Options: `method` (default `GET`), `url` (required, absolute), `region`
(default `us-east-1`), `service` (default `s3`), `accessKeyId` and
`secretAccessKey` (required), `sessionToken` (for temporary STS credentials),
`headers`, `body`, `payloadHash`.

Returns `{url, method, headers}` — never the body. Pass the same body value to
`fetch` yourself, so bytes make one trip, not two.

### Writing an object

```js
const body = JSON.stringify(inputs.record);
const signed = await sigv4({
  method: "PUT",
  url: `https://my-bucket.s3.us-east-1.amazonaws.com/${inputs.key}`,
  region: "us-east-1", service: "s3",
  accessKeyId, secretAccessKey,
  headers: { "content-type": "application/json" },
  body
});
await fetch(signed.url, { method: "PUT", headers: signed.headers, body });
```

A `Uint8Array` body works the same way — from `workspace.readBytes` or a
previous `response.bytes()`.

## presign — a URL that authorizes itself

```js
import { presign } from "@nodetool-ai/sandbox-aws";

const url = await presign({
  method: "GET",
  url: "https://my-bucket.s3.us-east-1.amazonaws.com/report.pdf",
  region: "us-east-1", service: "s3",
  expiresIn: 3600,
  accessKeyId, secretAccessKey
});
return { url };
```

Returns the URL as a string. `expiresIn` is seconds, clamped to a week. The
payload is unsigned, which is what makes a presigned `PUT` usable — whoever
holds the URL supplies the body.

## Gotchas

- **Every export is async.** A host call is a round trip.
- **It signs; it does not send.** Nothing reaches the network until you call
  `fetch`.
- **The signature is time-bound.** It is stamped when you call `sigv4`, and AWS
  rejects one more than 15 minutes old. Sign immediately before fetching, not
  at the top of a long function.
- **Other services work too.** Set `service` and `region` — `dynamodb`,
  `sqs`, `lambda`, `bedrock`. Only `s3` skips path normalization, because an
  object key is a key.
- **10 MB of body per signature.** Hash a bigger upload yourself and pass
  `payloadHash`, or use a presigned `PUT`.

Files in this skill

  • SKILL.md3.3 KB
  • package.json861 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…