Skip to content
Back to skills

Sandbox Supabase

ASecurity

Read and write Supabase tables from a Code node or CodeAct action, with NodeTool's PostgREST request builder running on the host

  • 552 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added September 1, 2026
ai-agentsgonodesecurity

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-supabase --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Sandbox Supabase?

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

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

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-supabase
description: Read and write Supabase tables from a Code node or CodeAct action, with NodeTool's PostgREST request builder running on the host
---

# Supabase in the sandbox

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

Supabase's REST surface is PostgREST: the whole query lives in the URL and the
write semantics live in a `Prefer` header. Both are easy to get subtly wrong —
an upsert without `resolution=merge-duplicates` is just a failed insert — so
this pack does the encoding.

**Nothing here sends a request.** Both exports build one; the guest's own
`fetch` sends it, under the run's fetch cap and SSRF guard.

## from — one table

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

const url = await nodetool.secrets.get("SUPABASE_URL");
const key = await nodetool.secrets.get("SUPABASE_KEY");

const req = await from({
  url, key,
  table: "issues",
  select: "id,title,status",
  filters: { status: "eq.open" },
  order: "created_at.desc",
  limit: 20
});
const res = await fetch(req.url, req);
if (!res.ok) throw new Error(`Supabase select failed: ${res.status}`);
return { rows: res.json };
```

`method` picks the operation:

| method | operation |
|---|---|
| `GET` (default) | select |
| `POST` | insert, or upsert with `onConflict` |
| `PATCH` | update the rows the filters match |
| `DELETE` | delete the rows the filters match |

Options: `url` and `key` (required), `table` (required), `method`, `select`,
`filters`, `order`, `limit`, `offset`, `body`, `onConflict`, `count`,
`returning`, `query`.

**Filters** take either form:

```js
filters: { status: "eq.open", priority: "in.(high,urgent)" }   // raw PostgREST
filters: { age: { gte: 18, lt: 65 } }                          // operator object
```

**Insert**

```js
const req = await from({ url, key, table: "events", method: "POST", body: inputs.rows });
```

**Update the matching rows**

```js
const req = await from({
  url, key, table: "issues", method: "PATCH",
  filters: { id: `eq.${inputs.id}` },
  body: { status: "closed" }
});
```

**Upsert** — `onConflict` names the unique column and turns the insert into a
merge:

```js
const req = await from({
  url, key, table: "users", method: "POST",
  onConflict: "email", body: inputs.users
});
```

A write returns the affected rows. Pass `returning: "minimal"` when you do not
want them back.

## rpc — a Postgres function

```js
import { rpc } from "@nodetool-ai/sandbox-supabase";

const req = await rpc({ url, key, fn: "search_issues", args: { term: inputs.term } });
return { rows: (await fetch(req.url, req)).json };
```

## Gotchas

- **Every export is async.** A host call is a round trip.
- **A `DELETE` or `PATCH` with no filters hits every row.** PostgREST does not
  refuse it. Pass the filters.
- **The anon key obeys row-level security.** A select that returns `[]` against
  a table with rows usually means RLS, not an empty table. The service-role key
  bypasses it — and bypasses it for every row, so keep it out of anything a
  user's prompt can steer.
- **Errors arrive as JSON with a non-2xx status.** Check `res.ok` and read
  `res.json.message`; PostgREST says exactly what it disliked.
- **Supabase caps a page at 1000 rows.** Page with `limit` and `offset`.

Files in this skill

  • SKILL.md3.2 KB
  • package.json886 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…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.