@integrity-labs/cloud-broker 0.7.11 → 0.7.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -18
- package/dist/index.js +32 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -39,7 +39,9 @@ See the PRDs ([AWS](../../docs/prds/aws-ephemeral-access.md), [GCP](../../docs/p
|
|
|
39
39
|
|
|
40
40
|
### Supabase
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
Since ENG-7326 the agent **holds no credential**: it queries Supabase through server-side proxy tools that run the call under the grant's scope. `supabase_rest` (PostgREST, read + write) is the primary path; `supabase_query` runs read-only SQL. The operator stores, on the enrolment, the project's REST api_key (a **new-format `sb_secret_…` key** - legacy anon/service_role keys are disabled) and/or a scoped read-only Postgres connection string; the broker injects whichever the call needs at exec time. The scope (`allowed_tables` / `read_only`) is enforced at the broker, independent of RLS.
|
|
43
|
+
|
|
44
|
+
The legacy `supabase_get_credentials` still mints a short-lived Supabase JWT for direct PostgREST/`psql` use, but the proxy tools are preferred (they never hand the agent a secret, and they don't depend on RLS to enforce scope).
|
|
43
45
|
|
|
44
46
|
| Tool | Purpose |
|
|
45
47
|
|---|---|
|
|
@@ -47,12 +49,15 @@ Credentials returned are a short-lived **Supabase JWT** (+ `supabase_url`, `db_h
|
|
|
47
49
|
| `supabase_preview_request` | Dry-run a candidate request. Writes nothing. |
|
|
48
50
|
| `supabase_request_access` | Mint or queue a grant. `service_role` always routes to a human approver; privileged internal roles (`postgres`, `supabase_admin`, …) are hard-denied. |
|
|
49
51
|
| `supabase_poll_grant` | Single-shot status check. Escape hatch. |
|
|
50
|
-
| `
|
|
51
|
-
| `
|
|
52
|
+
| `supabase_describe` | Inspect what an **active grant** can do (role, allowed schemas/tables, read_only, and whether server-side SQL is enrolled) before querying. |
|
|
53
|
+
| `supabase_rest` | **Primary.** Call the project's PostgREST API (read + write) server-side under the grant's scope. The broker injects the api_key; the agent holds nothing. `rpc/` is blocked. |
|
|
54
|
+
| `supabase_query` | Run **read-only SQL** server-side under the grant's scoped DB role (READ ONLY transaction + statement timeout). For system tables / joins REST can't express. Requires a DB connection string on the enrolment. |
|
|
55
|
+
| `supabase_get_credentials` | **Legacy.** Fetch a short-lived Supabase JWT + connection details for an active grant (direct PostgREST/`psql`). Prefer the proxy tools. |
|
|
56
|
+
| `supabase_release_access` | Voluntarily release a grant before TTL. Idempotent. (For the legacy JWT path the token stays valid until its `exp`.) |
|
|
52
57
|
| `supabase_check_approval_channel` | Pre-flight Slack channel reachability for the project's approval channel. |
|
|
53
58
|
| `supabase_list_projects` | Live inventory of enrolled Supabase projects. |
|
|
54
59
|
|
|
55
|
-
See **[`docs/runbooks/supabase-broker-enrollment.md`](../../docs/runbooks/supabase-broker-enrollment.md)** for operator enrollment, the `augmented` JWT-claim → RLS-policy enforcement guide
|
|
60
|
+
See **[`docs/runbooks/supabase-broker-enrollment.md`](../../docs/runbooks/supabase-broker-enrollment.md)** for operator enrollment (storing the REST api_key + DB connection string), the policy-ceiling shape, and the legacy `augmented` JWT-claim → RLS-policy enforcement guide.
|
|
56
61
|
|
|
57
62
|
## Environment
|
|
58
63
|
|
|
@@ -180,27 +185,28 @@ supabase_request_access({
|
|
|
180
185
|
ttl_seconds: 900,
|
|
181
186
|
reason: "read the agents + runs tables to answer the user's status question"
|
|
182
187
|
})
|
|
183
|
-
// → { "grant_id": "...", "status": "active", "
|
|
188
|
+
// → { "grant_id": "...", "status": "active", "expires_at": "..." }
|
|
184
189
|
// `service_role` or write access (read_only:false, unless the project allows writes)
|
|
185
190
|
// returns "pending" instead and pages a human approver.
|
|
186
191
|
|
|
187
|
-
// 4.
|
|
188
|
-
|
|
189
|
-
// → {
|
|
190
|
-
// "credentials": { "access_token": "<JWT>", "supabase_url": "https://<ref>.supabase.co",
|
|
191
|
-
// "db_host": "aws-0-<region>.pooler.supabase.com", "project_ref": "<ref>" } }
|
|
192
|
-
//
|
|
193
|
-
// REST (PostgREST):
|
|
194
|
-
// curl "$supabase_url/rest/v1/agents?select=*" \
|
|
195
|
-
// -H "apikey: <JWT>" -H "Authorization: Bearer <JWT>"
|
|
196
|
-
// psql (session pooler):
|
|
197
|
-
// psql "postgresql://postgres.<project_ref>:<JWT>@<db_host>:5432/postgres"
|
|
192
|
+
// 4. Learn your scope (recommended - also tells you if server-side SQL is enrolled).
|
|
193
|
+
supabase_describe({ grant_id: "..." })
|
|
194
|
+
// → { role, allowed_schemas, allowed_tables, read_only, sql_query: { available, ... } }
|
|
198
195
|
|
|
199
|
-
// 5.
|
|
196
|
+
// 5. Query. You hold NO credential - the broker runs the call server-side and injects the key.
|
|
197
|
+
// REST (primary, read + write):
|
|
198
|
+
supabase_rest({ grant_id: "...", method: "GET", path: "agents?select=*&limit=10" })
|
|
199
|
+
// → { ok: true, status: 200, body: [ ... ] }
|
|
200
|
+
// Read-only SQL (system tables / joins REST can't express; needs a DB connection string enrolled):
|
|
201
|
+
supabase_query({ grant_id: "...", sql: "select count(*) from runs" })
|
|
202
|
+
// → { ok: true, rows: [{ count: 42 }], row_count: 1 }
|
|
203
|
+
// Both return { ok: false, error_code } on a denial - branch on error_code, don't treat it as a tool failure.
|
|
204
|
+
|
|
205
|
+
// 6. (Optional) release early.
|
|
200
206
|
supabase_release_access({ grant_id: "..." })
|
|
201
207
|
```
|
|
202
208
|
|
|
203
|
-
|
|
209
|
+
With the proxy tools the grant's scope (`allowed_tables` / `read_only`) is enforced **at the broker** - read-only grants reject writes, calls outside `allowed_tables` are denied, and `rpc/` is blocked - independent of RLS. (For the legacy `supabase_get_credentials` JWT path, scope instead rides the JWT's `augmented` claim and is only enforced if the project's RLS policies read it - see the [enrollment runbook](../../docs/runbooks/supabase-broker-enrollment.md#enforcing-the-grant-scope-with-rls) for those policy patterns.) Same `pending` / direct-chat-push resolution semantics as AWS.
|
|
204
210
|
|
|
205
211
|
## Running locally
|
|
206
212
|
|
package/dist/index.js
CHANGED
|
@@ -20987,10 +20987,41 @@ var StdioServerTransport = class {
|
|
|
20987
20987
|
|
|
20988
20988
|
// src/turn-initiator-marker.ts
|
|
20989
20989
|
import { readFileSync } from "fs";
|
|
20990
|
+
import { dirname, join } from "path";
|
|
20990
20991
|
var TURN_INITIATOR_MAX_AGE_MS = 5 * 60 * 1e3;
|
|
20992
|
+
var TURN_INITIATOR_LEDGER_FILENAME = ".turn-initiator-ledger.json";
|
|
20993
|
+
function readTurnInitiatorFromLedger(file, maxAgeMs) {
|
|
20994
|
+
const ledgerFile = join(dirname(file), TURN_INITIATOR_LEDGER_FILENAME);
|
|
20995
|
+
let entries;
|
|
20996
|
+
try {
|
|
20997
|
+
const parsed = JSON.parse(readFileSync(ledgerFile, "utf8"));
|
|
20998
|
+
if (!parsed || parsed.v !== 1 || !Array.isArray(parsed.entries)) return "no-ledger";
|
|
20999
|
+
entries = parsed.entries;
|
|
21000
|
+
} catch {
|
|
21001
|
+
return "no-ledger";
|
|
21002
|
+
}
|
|
21003
|
+
const now = Date.now();
|
|
21004
|
+
const fresh = entries.filter(
|
|
21005
|
+
(e) => e && typeof e.channel === "string" && e.channel && typeof e.sender_id === "string" && e.sender_id && typeof e.ts === "number" && Number.isFinite(e.ts) && now - e.ts >= 0 && now - e.ts <= maxAgeMs
|
|
21006
|
+
);
|
|
21007
|
+
if (fresh.length === 1) {
|
|
21008
|
+
const e = fresh[0];
|
|
21009
|
+
return { channel: e.channel, sender_id: e.sender_id };
|
|
21010
|
+
}
|
|
21011
|
+
if (fresh.length >= 2) {
|
|
21012
|
+
process.stderr.write(
|
|
21013
|
+
`turn-initiator: ambiguous (${fresh.length} fresh senders), withholding initiator
|
|
21014
|
+
`
|
|
21015
|
+
);
|
|
21016
|
+
return null;
|
|
21017
|
+
}
|
|
21018
|
+
return "no-ledger";
|
|
21019
|
+
}
|
|
20991
21020
|
function readTurnInitiator(maxAgeMs = TURN_INITIATOR_MAX_AGE_MS) {
|
|
20992
21021
|
const file = process.env["AGT_TURN_INITIATOR_FILE"];
|
|
20993
21022
|
if (!file) return null;
|
|
21023
|
+
const fromLedger = readTurnInitiatorFromLedger(file, maxAgeMs);
|
|
21024
|
+
if (fromLedger !== "no-ledger") return fromLedger;
|
|
20994
21025
|
try {
|
|
20995
21026
|
const raw = readFileSync(file, "utf8");
|
|
20996
21027
|
const m = JSON.parse(raw);
|
|
@@ -21644,7 +21675,7 @@ var supabaseRestShape = supabaseRestSchema.shape;
|
|
|
21644
21675
|
// package.json
|
|
21645
21676
|
var package_default = {
|
|
21646
21677
|
name: "@integrity-labs/cloud-broker",
|
|
21647
|
-
version: "0.7.
|
|
21678
|
+
version: "0.7.12",
|
|
21648
21679
|
description: "Cloud Access Broker \u2014 MCP server that mints scoped, TTL-bounded cloud credentials per agent task. Ships AWS support (aws_request_access, aws_poll_grant, aws_release_access, aws_describe_scope, aws_preview_request, aws_get_credentials \u2014 STS AssumeRole under the hood); GCP, Azure, and Cloudflare land alongside in the same package as the broker grows.",
|
|
21649
21680
|
type: "module",
|
|
21650
21681
|
bin: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@integrity-labs/cloud-broker",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.12",
|
|
4
4
|
"description": "Cloud Access Broker — MCP server that mints scoped, TTL-bounded cloud credentials per agent task. Ships AWS support (aws_request_access, aws_poll_grant, aws_release_access, aws_describe_scope, aws_preview_request, aws_get_credentials — STS AssumeRole under the hood); GCP, Azure, and Cloudflare land alongside in the same package as the broker grows.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|