Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
0a0b420
transformabl-core: expand PiiType union + regulatory mapping
davidcrowe Apr 20, 2026
ce40b79
transformabl-core: add Presidio-parity + healthcare PII recognizers
davidcrowe Apr 20, 2026
8559b81
transformabl-core: bump to 0.4.0
davidcrowe Apr 20, 2026
ff4e155
fix(transformabl-core): ReDoS in email PII regex + scan-length cap (0…
davidcrowe Jul 2, 2026
5a0d42b
fix(proxyabl): declare jose as a runtime dependency
davidcrowe Jul 23, 2026
509d18a
fix(explicabl): drop undeclared node-fetch import; use Node 18+ globa…
davidcrowe Jul 23, 2026
3e01a09
docs(identifiabl): fix Quick Start to use the real export name
davidcrowe Jul 23, 2026
6279698
Remove hardcoded conformance badge, report, and CI artifact (#39)
davidcrowe Jul 23, 2026
e6541e3
transformabl-core 0.4.1: redactPii overlap fix (H4) + scan-truncation…
davidcrowe Jul 23, 2026
ff89b75
Package hygiene: per-package LICENSE, engines, tsbuildinfo out of dis…
davidcrowe Jul 23, 2026
7cbc9c2
Correct proxyabl SSRF marketing to match what actually runs (#43)
davidcrowe Jul 23, 2026
ee548e6
Add a zero-config quickstart example: governance in 30 seconds
davidcrowe Jul 23, 2026
98f33d7
Merge remote-tracking branch 'origin/fix/identifiabl-readme-export' i…
davidcrowe Jul 23, 2026
67c1951
Merge remote-tracking branch 'origin/fix/proxyabl-ssrf-wire' into cho…
davidcrowe Jul 23, 2026
b310838
Merge remote-tracking branch 'origin/feat/onramp-examples' into chore…
davidcrowe Jul 23, 2026
1ab41fe
Merge remote-tracking branch 'origin/fix/transformabl-core-0.4.1' int…
davidcrowe Jul 23, 2026
b51a491
Merge remote-tracking branch 'origin/fix/proxyabl-missing-jose-dep' i…
davidcrowe Jul 23, 2026
1356bef
Merge remote-tracking branch 'origin/fix/explicabl-node-fetch' into c…
davidcrowe Jul 23, 2026
1bd855c
Merge remote-tracking branch 'origin/fix/package-hygiene' into chore/…
davidcrowe Jul 23, 2026
8519b79
chore: regenerate lockfile after integrating cleanup PRs
davidcrowe Jul 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 0 additions & 21 deletions .github/workflows/conformance.yml

This file was deleted.

14 changes: 7 additions & 7 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,10 @@ packages/<name>/
- supertest for Express middleware integration tests

### current status
Tests are not yet implemented across packages. Priority order for adding tests:
1. proxyabl-core (security-critical: SSRF, auth modes, proxy execution)
2. transformabl-core (correctness-critical: PII regex patterns)
3. validatabl-core (security-critical: policy enforcement)
4. identifiabl-core (security-critical: JWT verification)
5. limitabl-core (correctness-critical: rate limiting, budget tracking)
6. request-context, explicabl, middleware wrappers
Every `-core` package and the middleware wrappers have vitest suites under
`packages/<name>/__tests__/` (note: `__tests__/`, not `tests/`). `npm test` at
the repo root runs the whole suite; `npm run build` must pass first (the tests
import built workspace packages). Security-critical coverage is real:
proxyabl-core SSRF, transformabl-core PII regex + redaction, validatabl-core
policy enforcement, identifiabl-core JWT verification, limitabl-core
rate/budget/agent-guard.
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Run the full test suite:
npm test
```

This runs Vitest plus the conformance report writer. See `docs/testing.md` for:
This runs the Vitest suite. See `docs/testing.md` for:
- `/__test__/echo` routes
- Scope/RBAC parity checks
- Proxy + echo server validation
Expand Down
22 changes: 14 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,6 @@
</a>
<img src="https://img.shields.io/badge/TypeScript-5.x-blue" alt="TypeScript" />
<img src="https://img.shields.io/badge/Cloud%20Run-ready-4285F4" alt="Cloud Run" />
<a href="https://github.com/agentic-control-plane/GatewayStack/tree/main/docs/conformance.json">
<img
src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fagentic-control-plane%2FGatewayStack%2Fmain%2Fdocs%2Fconformance.json&query=$.version&label=MCP%2FAuth%20Conformance"
alt="MCP Auth Conformance"
/>
</a>
</p>

<p align="center"><strong>See, price, and control every tool call your AI agents make.</strong></p>
Expand Down Expand Up @@ -44,6 +38,16 @@ Hermes Agent uses a native Python plugin instead ([why](https://agenticcontrolpl
pip install hermes-acp && hermes plugins enable acp
```

**Prefer to see the guarantees work first — zero config, no IdP, no backend?**

```bash
git clone https://github.com/agentic-control-plane/GatewayStack
cd GatewayStack/examples/quickstart && npm install && npm start
```

Prints three real decisions — a deny-by-default policy, PII redaction, and a
rate limit — as pure local library code ([`examples/quickstart`](examples/quickstart)).

Your own framework code — drop in a package:

```bash
Expand Down Expand Up @@ -88,9 +92,11 @@ Each layer ships as a framework-agnostic `-core` package plus an Express middlew
| `@gatewaystack/transformabl` | [![npm](https://img.shields.io/npm/v/@gatewaystack/transformabl)](https://www.npmjs.com/package/@gatewaystack/transformabl) | PII detection, redaction, safety classification |
| `@gatewaystack/validatabl` | [![npm](https://img.shields.io/npm/v/@gatewaystack/validatabl)](https://www.npmjs.com/package/@gatewaystack/validatabl) | Deny-by-default policy engine, scope/permission enforcement |
| `@gatewaystack/limitabl` | [![npm](https://img.shields.io/npm/v/@gatewaystack/limitabl)](https://www.npmjs.com/package/@gatewaystack/limitabl) | Rate limits, budget tracking, agent guard |
| `@gatewaystack/proxyabl` | [![npm](https://img.shields.io/npm/v/@gatewaystack/proxyabl)](https://www.npmjs.com/package/@gatewaystack/proxyabl) | Auth mode routing, SSRF protection, identity-aware proxy |
| `@gatewaystack/proxyabl` | [![npm](https://img.shields.io/npm/v/@gatewaystack/proxyabl)](https://www.npmjs.com/package/@gatewaystack/proxyabl) | Auth-mode routing, identity-aware proxy to a configured backend (path-allowlisted). SSRF-safe fetch for arbitrary URLs is a `proxyabl-core` primitive — see note below. |
| `@gatewaystack/explicabl` | [![npm](https://img.shields.io/npm/v/@gatewaystack/explicabl)](https://www.npmjs.com/package/@gatewaystack/explicabl) | Structured audit logging, health endpoints |

> **SSRF note (proxyabl).** The `proxyabl` Express router forwards to an operator-**configured** backend, with tool-name sanitization, path allowlisting, and host/protocol pinning to that backend — the target host is not caller-controlled. The DNS-resolving, private-IP-blocking SSRF engine (`assertUrlSafe` / `executeProxyRequest`) lives in `@gatewaystack/proxyabl-core` and is the primitive to use when you proxy **arbitrary, caller-influenced URLs**; the bundled router does not currently route through it (its threat model is different — a configured backend may legitimately be internal). See [`docs/packages.md`](docs/packages.md#proxyabl).

## Full stack example

Wire all six layers together. Each is optional — use only what you need.
Expand Down Expand Up @@ -175,7 +181,7 @@ AI apps have three actors — user, LLM, backend — and no shared identity laye
| `demos/` | MCP issuer + ChatGPT Apps SDK connectors that mint demo JWTs |
| `tools/` | Echo server, mock tool backend, Cloud Run deploy helper |
| `tests/` | Vitest smoke tests |
| `docs/` | Auth0 walkthroughs, conformance output, endpoint references, troubleshooting |
| `docs/` | Auth0 walkthroughs, endpoint references, troubleshooting |

## Testing

Expand Down
12 changes: 0 additions & 12 deletions docs/conformance.json

This file was deleted.

4 changes: 2 additions & 2 deletions docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,11 @@ docker run -p 8080:8080 \

### CI/CD

`.github/workflows/conformance.yml` runs `npm test` and updates `docs/conformance.json` on every push to main.
`.github/workflows/build.yml` builds the monorepo and runs `npm test` on every push and pull request to main.

**To add deployment:**
```yaml
# Add to conformance.yml after tests pass
# Add to build.yml after tests pass
- name: Deploy to Cloud Run
if: github.ref == 'refs/heads/main'
run: ./tools/deploy/cloud-run.sh apps/gateway-server
Expand Down
4 changes: 2 additions & 2 deletions docs/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ Run the full test suite:
npm test
```

This runs Vitest plus the conformance report writer that updates `docs/conformance.json`.
This runs the Vitest suite across the gateway and core packages.

For detailed testing workflows, see:
- `docs/testing.md` — `/__test__/echo` routes, scope checks, proxy validation
Expand All @@ -126,7 +126,7 @@ For detailed testing workflows, see:
Toggles worth noting:

- `DEMO_MODE=true` swaps in `OAUTH_*_DEMO` overrides so demos can mint JWTs locally.
- `ENABLE_TEST_ROUTES=true` + `TOOL_SCOPE_ALLOWLIST_JSON` expose `/__test__/echo` for conformance runs.
- `ENABLE_TEST_ROUTES=true` + `TOOL_SCOPE_ALLOWLIST_JSON` expose `/__test__/echo` for scope/proxy validation runs.
- `RATE_LIMIT_WINDOW_MS` / `RATE_LIMIT_MAX` tune limitabl without editing TypeScript.
- `.env.example` plus `apps/gateway-server/.env.example` enumerate every knob.

Expand Down
8 changes: 7 additions & 1 deletion docs/packages.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,13 @@ GatewayStack ships as composable npm packages. Each governance layer has a `-cor

`@gatewaystack/proxyabl-core` / `@gatewaystack/proxyabl`

**Execution Control & Identity-Aware Routing.** Five auth modes (API key, forward bearer, service OAuth, user OAuth, none), SSRF protection (host allowlist, private IP blocking), HTTP proxy with timeout/redirect/size controls, multi-provider registry. Express middleware serves PRM/OIDC metadata, enforces scope-to-tool mappings, and injects verified identity into downstream headers.
**Execution Control & Identity-Aware Routing.** Five auth modes (API key, forward bearer, service OAuth, user OAuth, none) and a multi-provider registry.

**`proxyabl-core` — SSRF-safe fetch primitive.** `assertUrlSafe` / `executeProxyRequest` provide host-allowlisting, DNS-resolving private-IP blocking (IPv4 + IPv6, incl. IPv4-mapped IPv6), protocol enforcement, redirect blocking (`redirect: "manual"`), and timeout/response-size caps. Use these directly when you proxy **arbitrary, caller-influenced URLs** — that is the case SSRF protection is for.

**`proxyabl` — Express gateway router.** Forwards to an operator-**configured** backend (`functionsBase` / proxy target). The caller controls the tool name / path, not the host: the router sanitizes the tool name, enforces a path allowlist, and pins the resolved URL's host + protocol to the configured backend. It is a pass-through proxy and **does not currently route through `proxyabl-core`'s `assertUrlSafe` engine** — the two have different threat models (a configured backend may legitimately be an internal address, which the private-IP-blocking engine would reject). The Express middleware also serves PRM/OIDC metadata, enforces scope-to-tool mappings, and injects verified identity into downstream headers.

> Converging these — one canonical `proxyabl-core` engine consumed by both the OSS router and the production gateway (which forked an equivalent) — is tracked in the proxyabl convergence work. Until then, treat `assertUrlSafe` / `executeProxyRequest` as an available primitive, not something the bundled router runs for you.

## explicabl

Expand Down
6 changes: 2 additions & 4 deletions docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
This document covers:
- How to enable internal `/__test__` routes
- Scope/RBAC parity checks
- Vitest + conformance report
- Running the Vitest suite

---

Expand All @@ -21,7 +21,7 @@ These routes are guarded by:
- `TOOL_SCOPE_ALLOWLIST_JSON` (allowed scopes per test route)
- The `X-Required-Scope` header (per-request scope requirement you pass in curl)

They are for validation and conformance testing only, not for production traffic.
They are for validation and scope/proxy testing only, not for production traffic.

---

Expand Down Expand Up @@ -95,12 +95,10 @@ npm test

This runs:
- Vitest against the gateway and core packages (see `vitest.config.mts`)
- The conformance report writer, which emits a summary of MCP/Auth OAuth behavior into `docs/conformance.json`

**Key files:**
- `package.json` → test script
- `vitest.config.mts` → shared test config
- `tests/smoke.test.ts` → placeholder smoke test
- `packages/explicabl-core/src/reporting/saveReport.ts` → writes the conformance report artifact

---
Empty file removed docs/testing/conformance.md
Empty file.
12 changes: 12 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Examples

Runnable GatewayStack examples.

| Example | What it shows | Setup |
|---------|---------------|-------|
| [`quickstart/`](./quickstart) | Deny-by-default policy, PII redaction, and rate limiting — three real decisions printed to your console | **None.** `npm install && npm start` |

The quickstart uses only the framework-agnostic `-core` packages, so it runs
anywhere Node runs, with zero external services. Wiring the same layers as
Express middleware (which does need an IdP for the identity layer) is covered in
the [root README](../README.md#full-stack-example).
32 changes: 32 additions & 0 deletions examples/quickstart/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Quickstart — governance in 30 seconds

Three real GatewayStack decisions running on your machine. **No IdP, no backend,
no config.**

```bash
npm install
npm start
```

You'll see:

```
1 · Deny-by-default policy (validatabl)
✅ ALLOW read_file → Matched rule: allow-reads (allow)
🛑 DENY delete_database → No rules matched; default: deny

2 · PII redaction (transformabl)
in : Email me at [email protected] or call about SSN 123-45-6789.
out: Email me at [EMAIL] or call about SSN [SSN].
detected: email, ssn

3 · Rate limit + budget guard (limitabl)
call 1: ✅ ALLOW
...
call 4: 🛑 DENY → Rate limited. Retry after 60s
```

Everything here is pure `-core` library code — the same logic the Express
middleware wraps. See [`index.ts`](./index.ts); it's ~50 lines. To wire these as
HTTP middleware in front of your own tools/models, see the
[full-stack example](../../README.md#full-stack-example).
69 changes: 69 additions & 0 deletions examples/quickstart/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* GatewayStack — 30-second quickstart.
*
* Three real governance decisions, running entirely on your machine. No IdP, no
* backend, no config. Run it:
*
* npm install && npm start
*
* Everything here is pure library code from the `-core` packages — the same
* logic the Express middleware wraps. If you can see these decisions happen,
* you can drop them into your own gateway.
*/

import { applyPolicies, type PolicySet } from "@gatewaystack/validatabl-core";
import { transformContent } from "@gatewaystack/transformabl-core";
import { LimitablEngine } from "@gatewaystack/limitabl-core";

const ALLOW = "\x1b[32m✅ ALLOW\x1b[0m";
const DENY = "\x1b[31m🛑 DENY \x1b[0m";
const h = (s: string) => console.log(`\n\x1b[1m${s}\x1b[0m`);

// ─────────────────────────────────────────────────────────────────────────
// 1. Deny-by-default policy — a tool call is refused unless a rule allows it.
// ─────────────────────────────────────────────────────────────────────────
h("1 · Deny-by-default policy (validatabl)");

const policy: PolicySet = {
defaultEffect: "deny", // nothing is allowed unless a rule says so
rules: [
{
id: "allow-reads",
effect: "allow",
conditions: [{ field: "tool", operator: "in", value: ["read_file", "list_files"] }],
},
],
};

for (const tool of ["read_file", "delete_database"]) {
const d = applyPolicies(policy, { identity: { sub: "agent-1" }, tool });
console.log(` ${d.allowed ? ALLOW : DENY} ${tool.padEnd(16)} → ${d.reason}`);
}

// ─────────────────────────────────────────────────────────────────────────
// 2. PII redaction — sensitive values are stripped before they leave.
// ─────────────────────────────────────────────────────────────────────────
h("2 · PII redaction (transformabl)");

const raw = "Email me at [email protected] or call about SSN 123-45-6789.";
const result = transformContent(raw, { redaction: { mode: "placeholder" } });

console.log(` in : ${raw}`);
console.log(` out: ${result.content}`);
console.log(` detected: ${result.piiMatches.map((m) => m.type).join(", ") || "none"}`);

// ─────────────────────────────────────────────────────────────────────────
// 3. Rate limit — the 4th call in the window is refused.
// ─────────────────────────────────────────────────────────────────────────
h("3 · Rate limit + budget guard (limitabl)");

const engine = new LimitablEngine({ rateLimit: { windowMs: 60_000, maxRequests: 3 } });
const key = { sub: "agent-1" };

for (let i = 1; i <= 5; i++) {
const r = engine.preflight(key);
console.log(` call ${i}: ${r.allowed ? ALLOW : DENY} ${r.allowed ? "" : "→ " + r.reason}`);
}

h("Next → wire these as Express middleware: see ../../README.md#full-stack-example");
console.log();
17 changes: 17 additions & 0 deletions examples/quickstart/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "gatewaystack-quickstart",
"private": true,
"type": "module",
"description": "30-second, zero-config GatewayStack governance demo",
"scripts": {
"start": "tsx index.ts"
},
"dependencies": {
"@gatewaystack/validatabl-core": "^0.1.0",
"@gatewaystack/transformabl-core": "^0.4.0",
"@gatewaystack/limitabl-core": "^0.1.0"
},
"devDependencies": {
"tsx": "^4.19.2"
}
}
Loading
Loading