From 0a0b4200903a40d92309496363c432155d684c1b Mon Sep 17 00:00:00 2001
From: David Crowe
Date: Mon, 20 Apr 2026 03:24:57 -0700
Subject: [PATCH 01/13] transformabl-core: expand PiiType union + regulatory
mapping
Prepares for Presidio-parity and HIPAA-relevant recognizers by widening
the PiiType union and adding regulatory-category mappings for the new
types. Patterns themselves land in the next commit.
New types:
- US identifiers: us_bank_number, us_itin, us_passport, us_drivers_license
- International banking: iban
- Healthcare (HIPAA PHI): icd_10, icd_9, npi
- Financial: crypto_wallet
Regulatory mapping adds PCI (bank accounts, IBAN) and HIPAA (all three
healthcare types) attribution so classify.ts can report compliance
categories in ContentMetadata.
---
packages/transformabl-core/src/classify.ts | 9 +++++++++
packages/transformabl-core/src/types.ts | 16 +++++++++++++++-
2 files changed, 24 insertions(+), 1 deletion(-)
diff --git a/packages/transformabl-core/src/classify.ts b/packages/transformabl-core/src/classify.ts
index 89f2780..db3f858 100644
--- a/packages/transformabl-core/src/classify.ts
+++ b/packages/transformabl-core/src/classify.ts
@@ -58,6 +58,15 @@ const PII_TO_REGULATORY: Record = {
phone: ["gdpr"],
date_of_birth: ["gdpr", "coppa", "hipaa"],
ip_address: ["gdpr"],
+ us_bank_number: ["pci", "gdpr"],
+ us_itin: ["pci", "gdpr"],
+ us_passport: ["gdpr"],
+ us_drivers_license: ["gdpr"],
+ iban: ["pci", "gdpr"],
+ icd_10: ["hipaa"],
+ icd_9: ["hipaa"],
+ npi: ["hipaa"],
+ crypto_wallet: ["gdpr"],
};
/**
diff --git a/packages/transformabl-core/src/types.ts b/packages/transformabl-core/src/types.ts
index 171c9e7..e382c90 100644
--- a/packages/transformabl-core/src/types.ts
+++ b/packages/transformabl-core/src/types.ts
@@ -2,12 +2,26 @@
/** Categories of PII that can be detected. */
export type PiiType =
+ // Universal
| "email"
| "phone"
| "ssn"
| "credit_card"
| "ip_address"
- | "date_of_birth";
+ | "date_of_birth"
+ // US identifiers (Presidio parity)
+ | "us_bank_number"
+ | "us_itin"
+ | "us_passport"
+ | "us_drivers_license"
+ // International banking
+ | "iban"
+ // Healthcare (HIPAA PHI)
+ | "icd_10"
+ | "icd_9"
+ | "npi"
+ // Financial
+ | "crypto_wallet";
/** A single PII detection match. */
export interface PiiMatch {
From ce40b7985051aafe87963be162e5ad6b187c5ec3 Mon Sep 17 00:00:00 2001
From: David Crowe
Date: Mon, 20 Apr 2026 03:25:08 -0700
Subject: [PATCH 02/13] transformabl-core: add Presidio-parity + healthcare PII
recognizers
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Extends detectPii with recognizers that Microsoft Presidio ships by
default (US-focused) plus healthcare-specific additions that are
HIPAA-standard but not covered by Presidio's core list.
Added:
- us_bank_number: 8-17 digits (loose by design)
- us_itin: 9XX-[78]X-XXXX — more specific than SSN pattern
- us_passport: 9 digits, or 1 letter + 8 digits
- us_drivers_license: letter + 7-8 digits, or prefixed form
- iban: ISO 13616 (2 letters + 2 digits + 11-30 alphanum)
- icd_10: letter + 2 chars + optional .Xn (up to 4 decimals)
- icd_9: 3 digits + optional .XX, or V/E codes
- npi: 10-digit starting with 1 or 2
- crypto_wallet: BTC (P2PKH + bech32) + ETH (0x + 40 hex)
Extended:
- phone: now matches E.164 international (+)
- ip_address: now matches IPv6 full and compressed forms
Motivation: ACP's content governance layer silently passed through
bank account numbers and ICD diagnosis codes (surfaced by an external
multi-agent privacy benchmark, AgentLeak). These are standard PII
categories in financial and healthcare contexts — a governance layer
that misses them can't credibly claim PII coverage.
Coverage surfaces via existing detectPii() API; no consumer changes
required beyond bumping the dep pin.
26 new unit tests; all 68 tests pass.
---
.../__tests__/detect.test.ts | 156 ++++++++++++++++++
packages/transformabl-core/src/detect.ts | 81 ++++++++-
2 files changed, 229 insertions(+), 8 deletions(-)
diff --git a/packages/transformabl-core/__tests__/detect.test.ts b/packages/transformabl-core/__tests__/detect.test.ts
index 7986fff..f770b74 100644
--- a/packages/transformabl-core/__tests__/detect.test.ts
+++ b/packages/transformabl-core/__tests__/detect.test.ts
@@ -145,6 +145,162 @@ describe("detectPii", () => {
});
});
+ // ─────────────────────────────────────────────────────────────────────
+ // Presidio-parity recognizers (v0.4.0)
+ // ─────────────────────────────────────────────────────────────────────
+
+ describe("us_bank_number", () => {
+ it("detects 10-digit bank account", () => {
+ const matches = detectPii("account 9845302145 for deposit");
+ expect(matches.some((m) => m.type === "us_bank_number")).toBe(true);
+ });
+
+ it("detects 17-digit routing/account combos", () => {
+ const matches = detectPii("wire to 12345678901234567");
+ expect(matches.some((m) => m.type === "us_bank_number")).toBe(true);
+ });
+
+ it("does NOT match 7-digit values (too short)", () => {
+ const matches = detectPii("code 1234567");
+ expect(matches.some((m) => m.type === "us_bank_number")).toBe(false);
+ });
+ });
+
+ describe("us_itin", () => {
+ it("detects ITIN format 9XX-7X-XXXX", () => {
+ const matches = detectPii("ITIN: 912-78-5551");
+ expect(matches.some((m) => m.type === "us_itin")).toBe(true);
+ });
+
+ it("detects ITIN format 9XX-8X-XXXX", () => {
+ const matches = detectPii("taxpayer 988-83-0099");
+ expect(matches.some((m) => m.type === "us_itin")).toBe(true);
+ });
+
+ it("regular SSN 123-45-6789 does not match ITIN pattern", () => {
+ const matches = detectPii("ssn 123-45-6789");
+ expect(matches.some((m) => m.type === "us_itin")).toBe(false);
+ });
+ });
+
+ describe("us_passport", () => {
+ it("detects 9-digit passport", () => {
+ const matches = detectPii("passport 123456789 expires");
+ expect(matches.some((m) => m.type === "us_passport")).toBe(true);
+ });
+
+ it("detects 1-letter + 8-digit format", () => {
+ const matches = detectPii("passport A12345678");
+ expect(matches.some((m) => m.type === "us_passport")).toBe(true);
+ });
+ });
+
+ describe("us_drivers_license", () => {
+ it("detects 1-letter + 7-digit DL", () => {
+ const matches = detectPii("DL D1234567 state CA");
+ expect(matches.some((m) => m.type === "us_drivers_license")).toBe(true);
+ });
+
+ it("detects DL: prefix form", () => {
+ const matches = detectPii("license DL:12345678");
+ expect(matches.some((m) => m.type === "us_drivers_license")).toBe(true);
+ });
+ });
+
+ describe("iban", () => {
+ it("detects German IBAN", () => {
+ const matches = detectPii("wire to DE89370400440532013000");
+ expect(matches.some((m) => m.type === "iban")).toBe(true);
+ });
+
+ it("detects UK IBAN", () => {
+ const matches = detectPii("acct GB82WEST12345698765432");
+ expect(matches.some((m) => m.type === "iban")).toBe(true);
+ });
+
+ it("does NOT match arbitrary long alphanum", () => {
+ const matches = detectPii("token ABCDEFGHIJKLMNOP12345");
+ expect(matches.some((m) => m.type === "iban")).toBe(false);
+ });
+ });
+
+ describe("phone (international)", () => {
+ it("detects +44 UK number", () => {
+ const matches = detectPii("call +44 20 7946 0958");
+ expect(matches.some((m) => m.type === "phone")).toBe(true);
+ });
+
+ it("detects +81 Japan number", () => {
+ const matches = detectPii("support +81-3-1234-5678");
+ expect(matches.some((m) => m.type === "phone")).toBe(true);
+ });
+ });
+
+ describe("ip_address (IPv6)", () => {
+ it("detects full-form IPv6", () => {
+ const matches = detectPii("addr 2001:0db8:85a3:0000:0000:8a2e:0370:7334");
+ expect(matches.some((m) => m.type === "ip_address")).toBe(true);
+ });
+
+ it("detects compressed IPv6", () => {
+ const matches = detectPii("addr 2001:db8::8a2e:370:7334");
+ expect(matches.some((m) => m.type === "ip_address")).toBe(true);
+ });
+ });
+
+ describe("icd_10", () => {
+ it("detects F32.9 (major depressive disorder)", () => {
+ const matches = detectPii("diagnosis code F32.9 confirmed");
+ expect(matches.some((m) => m.type === "icd_10")).toBe(true);
+ });
+
+ it("detects E11.65 (type 2 diabetes)", () => {
+ const matches = detectPii("pt has E11.65 on chart");
+ expect(matches.some((m) => m.type === "icd_10")).toBe(true);
+ });
+
+ it("detects code without decimals", () => {
+ const matches = detectPii("dx: Z00 routine");
+ expect(matches.some((m) => m.type === "icd_10")).toBe(true);
+ });
+ });
+
+ describe("icd_9", () => {
+ it("detects numeric ICD-9 with decimal", () => {
+ const matches = detectPii("legacy code 250.01 on record");
+ expect(matches.some((m) => m.type === "icd_9")).toBe(true);
+ });
+
+ it("detects V-code", () => {
+ const matches = detectPii("status V70.0");
+ expect(matches.some((m) => m.type === "icd_9")).toBe(true);
+ });
+ });
+
+ describe("npi", () => {
+ it("detects 10-digit NPI starting with 1", () => {
+ const matches = detectPii("provider NPI 1234567893 treating");
+ expect(matches.some((m) => m.type === "npi")).toBe(true);
+ });
+ });
+
+ describe("crypto_wallet", () => {
+ it("detects BTC legacy address", () => {
+ const matches = detectPii("send to 1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2");
+ expect(matches.some((m) => m.type === "crypto_wallet")).toBe(true);
+ });
+
+ it("detects BTC bech32 address", () => {
+ const matches = detectPii("payout bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4");
+ expect(matches.some((m) => m.type === "crypto_wallet")).toBe(true);
+ });
+
+ it("detects ETH address", () => {
+ const matches = detectPii("eth 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb7");
+ expect(matches.some((m) => m.type === "crypto_wallet")).toBe(true);
+ });
+ });
+
describe("zero-width character bypass", () => {
it("detects email with zero-width space inside", () => {
const text = "contact john\u200B@example.com for info";
diff --git a/packages/transformabl-core/src/detect.ts b/packages/transformabl-core/src/detect.ts
index 31885a5..8a6a358 100644
--- a/packages/transformabl-core/src/detect.ts
+++ b/packages/transformabl-core/src/detect.ts
@@ -1,13 +1,14 @@
// packages/transformabl-core/src/detect.ts
//
-// Regex-based PII detection.
+// Regex-based PII detection. Recognizer set tracks Microsoft Presidio's
+// default recognizer list (US-focused) plus healthcare-specific additions
+// that are standard under HIPAA but not covered by Presidio's core list.
//
// FUTURE WORK:
// - ML-based NER detection (plug in spaCy, Presidio, or cloud NER APIs)
// - Address detection (street addresses, zip codes)
// - Name detection (requires NER - too many false positives with regex)
-// - International phone number formats
-// - Passport numbers, driver's license numbers
+// - Country-specific identifiers (UK NHS, AU ABN, IN PAN, etc.)
import type { PiiType, PiiMatch } from "./types.js";
import { stripInvisible } from "./normalize.js";
@@ -28,31 +29,95 @@ const BUILTIN_PATTERNS: PiiPattern[] = [
},
{
type: "phone",
- // US phone numbers (10+ digits): (xxx) xxx-xxxx, xxx-xxx-xxxx, +1xxxxxxxxxx
- // Requires area code (3 digits) + 7-digit number to avoid matching short numbers
- pattern: /(?:\+1[-.\s]?)\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b|\b\(?\d{3}\)?[-.\s]\d{3}[-.\s]?\d{4}\b/g,
+ // US 10-digit formats AND international E.164 (+<7-14 digits>).
+ pattern:
+ /(?:\+1[-.\s]?)\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b|\b\(?\d{3}\)?[-.\s]\d{3}[-.\s]?\d{4}\b|\+(?!1[-.\s]?\d)\d{1,3}[-.\s]?\d{1,4}[-.\s]?\d{1,4}[-.\s]?\d{1,9}\b/g,
},
{
type: "ssn",
// US Social Security Numbers: xxx-xx-xxxx
pattern: /\b\d{3}-\d{2}-\d{4}\b/g,
},
+ {
+ type: "us_itin",
+ // US Individual Taxpayer Identification Number: 9XX-(7X|8X)-XXXX where
+ // the middle group starts with 7, 8, or 9. Must be checked BEFORE the
+ // generic SSN pattern since ITIN overlaps structurally with SSN — ITIN
+ // is more specific, so order matters when dedup'ing overlapping hits.
+ pattern: /\b9\d{2}-[78]\d-\d{4}\b/g,
+ },
{
type: "credit_card",
// Major card formats: Visa (13-19), MC (16), Amex (15), Discover (16), Diners (14)
// Matches 13-19 digit sequences with optional separators (space or dash)
pattern: /\b(?:4\d{3}|5[1-5]\d{2}|3[47]\d{2}|6(?:011|5\d{2})|3(?:0[0-5]|[68]\d)\d)[-\s]?\d{4,6}[-\s]?\d{4,5}(?:[-\s]?\d{1,4})?\b/g,
},
+ {
+ type: "us_bank_number",
+ // US bank account numbers: 8-17 digits. Loose by design — banks don't
+ // publish a format. Tightened with word boundaries + minimum length to
+ // avoid matching short IDs. Will false-positive on other long digit
+ // strings; acceptable for a governance layer that prefers over-
+ // redaction on financial context.
+ pattern: /\b\d{8,17}\b/g,
+ },
+ {
+ type: "us_passport",
+ // US passport: 9 digits (older) or 1 letter + 8 digits (newer).
+ pattern: /\b(?:[A-Z]\d{8}|\d{9})\b/g,
+ },
+ {
+ type: "us_drivers_license",
+ // US driver's license formats are state-specific. Approximation:
+ // 1 letter + 7-8 digits, or 7-9 all-digit sequences with "DL:" prefix.
+ // False-positive rate is non-trivial; callers can disable this type.
+ pattern: /\b(?:DL[:\s]?)?(?:[A-Z]\d{7,8}|\d{7,9})\b/g,
+ },
+ {
+ type: "iban",
+ // ISO 13616 IBAN: 2 letters (country) + 2 check digits + 11-30 alphanum.
+ // Optional spaces every 4 chars are stripped by normalize.ts before scan.
+ pattern: /\b[A-Z]{2}\d{2}[A-Z0-9]{11,30}\b/g,
+ },
{
type: "ip_address",
- // IPv4 addresses
- pattern: /\b(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\b/g,
+ // IPv4 OR IPv6 (full + compressed forms). Three alternatives:
+ // (1) IPv4 dotted-quad
+ // (2) IPv6 full form — 8 hex groups separated by `:`
+ // (3) IPv6 compressed — contains `::`, with hex groups on either side
+ pattern:
+ /\b(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\b|\b(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\b|(?
Date: Mon, 20 Apr 2026 03:25:17 -0700
Subject: [PATCH 03/13] transformabl-core: bump to 0.4.0
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Minor version because the PiiType union is additive (no breaking
changes for existing consumers) but the set of types returned by
detectPii() has grown — callers that hard-code a closed set of
expected types should review.
---
package-lock.json | 10 ++++++++--
packages/transformabl-core/package.json | 2 +-
2 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 0bf0456..840f710 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6601,7 +6601,7 @@
},
"packages/explicabl": {
"name": "@gatewaystack/explicabl",
- "version": "0.0.7",
+ "version": "0.0.8",
"license": "MIT",
"dependencies": {
"express": "^4.22.0",
@@ -6716,12 +6716,18 @@
},
"packages/transformabl-core": {
"name": "@gatewaystack/transformabl-core",
- "version": "0.3.0",
+ "version": "0.4.0",
"license": "MIT",
"devDependencies": {
"typescript": "^5.6.3"
}
},
+ "packages/transformabl/node_modules/@gatewaystack/transformabl-core": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@gatewaystack/transformabl-core/-/transformabl-core-0.3.0.tgz",
+ "integrity": "sha512-NWo2inIcHPGFSxSfzNI9VBZcZO9yxS4NVUsq/CvOe3RoOMGq0xt58C1Ex2Oa1uNZzQ3C/mPWzCRLeFbkd6ozIw==",
+ "license": "MIT"
+ },
"packages/validatabl": {
"name": "@gatewaystack/validatabl",
"version": "0.2.0",
diff --git a/packages/transformabl-core/package.json b/packages/transformabl-core/package.json
index 5cae6eb..137780b 100644
--- a/packages/transformabl-core/package.json
+++ b/packages/transformabl-core/package.json
@@ -1,6 +1,6 @@
{
"name": "@gatewaystack/transformabl-core",
- "version": "0.3.0",
+ "version": "0.4.0",
"private": false,
"license": "MIT",
"type": "module",
From ff4e1551908728e145ef398e6caed534a76ac757 Mon Sep 17 00:00:00 2001
From: David Crowe
Date: Thu, 2 Jul 2026 10:15:54 -0700
Subject: [PATCH 04/13] fix(transformabl-core): ReDoS in email PII regex +
scan-length cap (0.4.1)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Unbounded quantifiers around a single mandatory @ made the email pattern
O(n^2): a long alphanumeric run (no @ needed) forced scan-to-end-then-
backtrack from every start position — ~1MB blocked the event loop minutes.
Bound to RFC 5321 limits (local<=64, domain<=255, tld<=24) + a 512KB
detectPii scan cap. Verified: 300k-char attack 4413ms -> 39ms, emails still
detected. Security review 2026-07-01.
---
packages/transformabl-core/package.json | 2 +-
packages/transformabl-core/src/detect.ts | 19 +++++++++++++++++--
2 files changed, 18 insertions(+), 3 deletions(-)
diff --git a/packages/transformabl-core/package.json b/packages/transformabl-core/package.json
index 137780b..c3cbf1b 100644
--- a/packages/transformabl-core/package.json
+++ b/packages/transformabl-core/package.json
@@ -1,6 +1,6 @@
{
"name": "@gatewaystack/transformabl-core",
- "version": "0.4.0",
+ "version": "0.4.1",
"private": false,
"license": "MIT",
"type": "module",
diff --git a/packages/transformabl-core/src/detect.ts b/packages/transformabl-core/src/detect.ts
index 8a6a358..5628d01 100644
--- a/packages/transformabl-core/src/detect.ts
+++ b/packages/transformabl-core/src/detect.ts
@@ -25,7 +25,13 @@ interface PiiPattern {
const BUILTIN_PATTERNS: PiiPattern[] = [
{
type: "email",
- pattern: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
+ // Quantifiers are bounded to RFC 5321 limits (local ≤64, domain ≤255,
+ // TLD ≤24). The previous unbounded `+…+` form around a single mandatory
+ // `@` was quadratic (O(n²)) — a long alphanumeric run with no `@` made
+ // the engine scan-to-end-then-backtrack from every start position, so a
+ // ~1 MB string of `a`s could block the event loop for minutes (ReDoS).
+ // Bounding the work per start position makes it effectively linear.
+ pattern: /[a-zA-Z0-9._%+-]{1,64}@[a-zA-Z0-9.-]{1,255}\.[a-zA-Z]{2,24}/g,
},
{
type: "phone",
@@ -135,6 +141,15 @@ export function detectPii(
// obfuscated span — including the invisible chars themselves.
const { text: scanText, map } = stripInvisible(text);
+ // Defense-in-depth against pathological inputs: cap the length we scan.
+ // Even with bounded patterns, running every pattern over an unbounded body
+ // is a DoS lever (this is an agent/LLM gateway — large tool-call payloads
+ // are normal). PII beyond this cap is not scanned; callers handling very
+ // large bodies should chunk. 512 KB comfortably covers real tool I/O.
+ const MAX_SCAN_LENGTH = 512 * 1024;
+ const boundedScanText =
+ scanText.length > MAX_SCAN_LENGTH ? scanText.slice(0, MAX_SCAN_LENGTH) : scanText;
+
const matches: PiiMatch[] = [];
const allPatterns: Array<{ type: string; pattern: RegExp }> = [
...BUILTIN_PATTERNS,
@@ -146,7 +161,7 @@ export function detectPii(
const regex = new RegExp(pattern.source, pattern.flags);
let match: RegExpExecArray | null;
- while ((match = regex.exec(scanText)) !== null) {
+ while ((match = regex.exec(boundedScanText)) !== null) {
const s = match.index;
const e = match.index + match[0].length;
From 5a0d42bad1ee9d63d5a538ba0d79c9f95ce24658 Mon Sep 17 00:00:00 2001
From: David Crowe
Date: Thu, 23 Jul 2026 10:10:27 -0700
Subject: [PATCH 05/13] fix(proxyabl): declare jose as a runtime dependency
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
@gatewaystack/proxyabl-core imports jose in auth.ts and oidc.ts but never
declared it, so a fresh npm install of the published package failed on
first import with ERR_MODULE_NOT_FOUND — it only worked inside this
monorepo, where jose is hoisted from identifiabl-core. Pin ^6.1.0 to
match identifiabl-core, bump core to 0.1.1 and middleware to 0.0.13, and
give the middleware a jose devDependency for its type-only import so it
typechecks outside the workspace.
---
package-lock.json | 31 +++++++++++++++++++++++++----
packages/proxyabl-core/package.json | 5 ++++-
packages/proxyabl/package.json | 5 +++--
3 files changed, 34 insertions(+), 7 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 0bf0456..1001f3c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6601,7 +6601,7 @@
},
"packages/explicabl": {
"name": "@gatewaystack/explicabl",
- "version": "0.0.7",
+ "version": "0.0.8",
"license": "MIT",
"dependencies": {
"express": "^4.22.0",
@@ -6670,28 +6670,51 @@
},
"packages/proxyabl": {
"name": "@gatewaystack/proxyabl",
- "version": "0.0.12",
+ "version": "0.0.13",
"license": "MIT",
"dependencies": {
- "@gatewaystack/proxyabl-core": "^0.1.0",
+ "@gatewaystack/proxyabl-core": "^0.1.1",
"@gatewaystack/request-context": "0.0.6",
"express": "^4.22.0",
"express-rate-limit": "^8.2.2"
},
"devDependencies": {
"@types/express": "^4.17.21",
+ "jose": "^6.1.0",
"tsx": "^4.19.2",
"typescript": "^5.6.3"
}
},
"packages/proxyabl-core": {
"name": "@gatewaystack/proxyabl-core",
- "version": "0.1.0",
+ "version": "0.1.1",
"license": "MIT",
+ "dependencies": {
+ "jose": "^6.1.0"
+ },
"devDependencies": {
"typescript": "^5.6.3"
}
},
+ "packages/proxyabl-core/node_modules/jose": {
+ "version": "6.2.4",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.4.tgz",
+ "integrity": "sha512-N8acGzVsQy6M/fjFcxtysNc4Q379TcM5dM/qKkNtsHFji88yANnXTr7BLeP75iPnFwBfQzM/jg2BZ9+HZrHCZA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
+ "packages/proxyabl/node_modules/jose": {
+ "version": "6.2.4",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.4.tgz",
+ "integrity": "sha512-N8acGzVsQy6M/fjFcxtysNc4Q379TcM5dM/qKkNtsHFji88yANnXTr7BLeP75iPnFwBfQzM/jg2BZ9+HZrHCZA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
"packages/request-context": {
"name": "@gatewaystack/request-context",
"version": "0.0.6",
diff --git a/packages/proxyabl-core/package.json b/packages/proxyabl-core/package.json
index 5cb5be1..a81c98c 100644
--- a/packages/proxyabl-core/package.json
+++ b/packages/proxyabl-core/package.json
@@ -1,6 +1,6 @@
{
"name": "@gatewaystack/proxyabl-core",
- "version": "0.1.0",
+ "version": "0.1.1",
"private": false,
"license": "MIT",
"type": "module",
@@ -18,6 +18,9 @@
"build": "tsc -p tsconfig.json",
"prepublishOnly": "npm run build"
},
+ "dependencies": {
+ "jose": "^6.1.0"
+ },
"devDependencies": {
"typescript": "^5.6.3"
}
diff --git a/packages/proxyabl/package.json b/packages/proxyabl/package.json
index 4235dd8..f519037 100644
--- a/packages/proxyabl/package.json
+++ b/packages/proxyabl/package.json
@@ -1,6 +1,6 @@
{
"name": "@gatewaystack/proxyabl",
- "version": "0.0.12",
+ "version": "0.0.13",
"private": false,
"license": "MIT",
"type": "module",
@@ -21,11 +21,12 @@
"dependencies": {
"express": "^4.22.0",
"express-rate-limit": "^8.2.2",
- "@gatewaystack/proxyabl-core": "^0.1.0",
+ "@gatewaystack/proxyabl-core": "^0.1.1",
"@gatewaystack/request-context": "0.0.6"
},
"devDependencies": {
"@types/express": "^4.17.21",
+ "jose": "^6.1.0",
"typescript": "^5.6.3",
"tsx": "^4.19.2"
}
From 509d18aaae1452b5c821eed6de664171918c35e1 Mon Sep 17 00:00:00 2001
From: David Crowe
Date: Thu, 23 Jul 2026 10:56:47 -0700
Subject: [PATCH 06/13] fix(explicabl): drop undeclared node-fetch import; use
Node 18+ global fetch
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
health.ts imported `node-fetch` but the package declared only express and
express-rate-limit. Because dist/index.js re-exports health.js, the published
@gatewaystack/explicabl@0.0.8 threw ERR_MODULE_NOT_FOUND on ANY import from a
fresh install — including createConsoleLogger, which has nothing to do with the
health route. Same defect class as the proxyabl-core/jose break (PR #35).
The two call sites use only standard WHATWG fetch semantics (.ok/.status,
{method,headers,body}), so Node 18+'s global fetch is a drop-in. Removing the
import also makes the README's 'zero dependencies beyond Express' claim true.
Add engines.node >=18 to make the global-fetch requirement explicit; bump to
0.0.9. Verified: packed tarball installs into an empty project and imports with
all 5 exports; full suite (159 tests) green.
---
packages/explicabl/package.json | 5 ++++-
packages/explicabl/src/health.ts | 1 -
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/packages/explicabl/package.json b/packages/explicabl/package.json
index 4a360ce..efd17e7 100644
--- a/packages/explicabl/package.json
+++ b/packages/explicabl/package.json
@@ -1,6 +1,6 @@
{
"name": "@gatewaystack/explicabl",
- "version": "0.0.8",
+ "version": "0.0.9",
"private": false,
"license": "MIT",
"type": "module",
@@ -18,6 +18,9 @@
"build": "tsc -p tsconfig.json",
"prepublishOnly": "npm run build"
},
+ "engines": {
+ "node": ">=18"
+ },
"dependencies": {
"express": "^4.22.0",
"express-rate-limit": "^8.2.2"
diff --git a/packages/explicabl/src/health.ts b/packages/explicabl/src/health.ts
index e4a4654..91aee1d 100644
--- a/packages/explicabl/src/health.ts
+++ b/packages/explicabl/src/health.ts
@@ -1,5 +1,4 @@
import { Router, type Request, type RequestHandler } from "express";
-import fetch from "node-fetch";
import rateLimit from "express-rate-limit";
import { timingSafeEqual } from "node:crypto";
From 3e01a09e4b4240f931cda1b39d455e0e5b929c6f Mon Sep 17 00:00:00 2001
From: David Crowe
Date: Thu, 23 Jul 2026 10:59:58 -0700
Subject: [PATCH 07/13] docs(identifiabl): fix Quick Start to use the real
export name
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The README's Basic Usage and later examples imported and called
`createIdentifiablMiddleware`, which the package does not export — the Express
middleware is `identifiabl(config)` (createIdentifiablVerifier is the
non-Express verifier). Copy-pasting the Quick Start failed at the import line.
Rename all five occurrences to the real export; config shape and req.user
usage already match identifiabl()'s behavior, so no other changes needed.
---
packages/identifiabl/README.md | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/packages/identifiabl/README.md b/packages/identifiabl/README.md
index 6b401c3..80ef011 100644
--- a/packages/identifiabl/README.md
+++ b/packages/identifiabl/README.md
@@ -30,12 +30,12 @@ npm install @gatewaystack/identifiabl
```typescript
import express from 'express';
-import { createIdentifiablMiddleware } from '@gatewaystack/identifiabl';
+import { identifiabl } from '@gatewaystack/identifiabl';
const app = express();
// Add identity verification to all routes
-app.use(createIdentifiablMiddleware({
+app.use(identifiabl({
issuer: 'https://your-tenant.auth0.com/',
audience: 'https://gateway.local/api',
jwksUri: 'https://your-tenant.auth0.com/.well-known/jwks.json'
@@ -59,7 +59,7 @@ app.get('/api/data', (req, res) => {
app.get('/health', (req, res) => res.json({ ok: true }));
// Protected routes (auth required)
-app.use('/protected', createIdentifiablMiddleware({
+app.use('/protected', identifiabl({
issuer: process.env.OAUTH_ISSUER,
audience: process.env.OAUTH_AUDIENCE,
jwksUri: process.env.OAUTH_JWKS_URI
@@ -400,7 +400,7 @@ identifiabl is optimized for production:
### Custom Claims Extraction
```typescript
-app.use(createIdentifiablMiddleware({
+app.use(identifiabl({
issuer: process.env.OAUTH_ISSUER,
audience: process.env.OAUTH_AUDIENCE,
jwksUri: process.env.OAUTH_JWKS_URI,
@@ -418,7 +418,7 @@ app.use(createIdentifiablMiddleware({
```typescript
// Enforce tenant ID in token
-app.use(createIdentifiablMiddleware({
+app.use(identifiabl({
issuer: process.env.OAUTH_ISSUER,
audience: process.env.OAUTH_AUDIENCE,
jwksUri: process.env.OAUTH_JWKS_URI,
From 6279698ee609162481e6b4a21ef7e6e47ed74b96 Mon Sep 17 00:00:00 2001
From: David Crowe
Date: Thu, 23 Jul 2026 13:16:38 -0700
Subject: [PATCH 08/13] Remove hardcoded conformance badge, report, and CI
artifact (#39)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
saveReport.ts wrote all five conformance categories (pkce, jwt_verify,
scope_enforcement, allowlist, expiry) as "pass" with a fresh timestamp,
consulting no test — and npm test ran it after every suite. The committed
docs/conformance.json reflected that, and the README badge read its version.
For a repo-visibility push this is the single highest-risk artifact: one
file-read of saveReport.ts discredits every other claim.
Rather than fake a conformance suite, remove it. PKCE is not implemented in
this repo (the OAuth/PKCE flow is delegated to Auth0; the only "pkce"
reference is a ChatGPT-DCR detection heuristic), so that category cannot be
honestly asserted at all. The other four map to real unit-tested code, but
badging library unit tests as "OAuth 2.1 + MCP Authorization conformance"
overclaims the same way. An absent badge is honest; a hardcoded one is not.
A real MCP Authorization conformance suite is a separate, larger effort.
Removed:
- README "MCP/Auth Conformance" badge
- docs/conformance.json
- packages/explicabl-core/src/reporting/saveReport.ts (+ compiled .js)
- docs/testing/conformance.md (empty)
- .github/workflows/conformance.yml (redundant: build.yml already runs tests;
its only distinct job was uploading conformance.json)
- the conformance:report npm script; stripped it from the test script
- conformance mentions in operations/testing/deployment docs + CONTRIBUTING
Build green, 159 tests pass, npm test no longer regenerates the file.
---
.github/workflows/conformance.yml | 21 -------------------
CONTRIBUTING.md | 2 +-
README.md | 8 +------
docs/conformance.json | 12 -----------
docs/deployment.md | 4 ++--
docs/operations.md | 4 ++--
docs/testing.md | 6 ++----
docs/testing/conformance.md | 0
package.json | 3 +--
.../src/reporting/saveReport.js | 16 --------------
.../src/reporting/saveReport.ts | 16 --------------
11 files changed, 9 insertions(+), 83 deletions(-)
delete mode 100644 .github/workflows/conformance.yml
delete mode 100644 docs/conformance.json
delete mode 100644 docs/testing/conformance.md
delete mode 100644 packages/explicabl-core/src/reporting/saveReport.js
delete mode 100644 packages/explicabl-core/src/reporting/saveReport.ts
diff --git a/.github/workflows/conformance.yml b/.github/workflows/conformance.yml
deleted file mode 100644
index ec1324a..0000000
--- a/.github/workflows/conformance.yml
+++ /dev/null
@@ -1,21 +0,0 @@
-name: Conformance
-
-on: [push, pull_request]
-
-# 🔐 Restrict GITHUB_TOKEN permissions (least privilege)
-permissions:
- contents: read
-
-jobs:
- test:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- - uses: actions/setup-node@v4
- with: { node-version: '20' }
- - run: npm ci
- - run: npm run test
- - uses: actions/upload-artifact@v4
- with:
- name: conformance-json
- path: docs/conformance.json
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 842f6ae..173be9d 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -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
diff --git a/README.md b/README.md
index d4b42b4..d588474 100644
--- a/README.md
+++ b/README.md
@@ -8,12 +8,6 @@
-
-
-
See, price, and control every tool call your AI agents make.
@@ -175,7 +169,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
diff --git a/docs/conformance.json b/docs/conformance.json
deleted file mode 100644
index 963d627..0000000
--- a/docs/conformance.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "version": "0.1.0",
- "spec": "Apps SDK OAuth 2.1 + MCP Authorization (subset)",
- "categories": {
- "pkce": "pass",
- "jwt_verify": "pass",
- "scope_enforcement": "pass",
- "allowlist": "pass",
- "expiry": "pass"
- },
- "timestamp": "2026-02-07T07:46:57.508Z"
-}
\ No newline at end of file
diff --git a/docs/deployment.md b/docs/deployment.md
index aaac788..edb6691 100644
--- a/docs/deployment.md
+++ b/docs/deployment.md
@@ -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
diff --git a/docs/operations.md b/docs/operations.md
index 81d3363..57d7b9a 100644
--- a/docs/operations.md
+++ b/docs/operations.md
@@ -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
@@ -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.
diff --git a/docs/testing.md b/docs/testing.md
index d526506..65edcca 100644
--- a/docs/testing.md
+++ b/docs/testing.md
@@ -3,7 +3,7 @@
This document covers:
- How to enable internal `/__test__` routes
- Scope/RBAC parity checks
-- Vitest + conformance report
+- Running the Vitest suite
---
@@ -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.
---
@@ -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
---
\ No newline at end of file
diff --git a/docs/testing/conformance.md b/docs/testing/conformance.md
deleted file mode 100644
index e69de29..0000000
diff --git a/package.json b/package.json
index c7779d1..c3cfc46 100644
--- a/package.json
+++ b/package.json
@@ -20,9 +20,8 @@
"build:admin": "npm --workspace apps/admin-ui run build",
"start:server": "npm --workspace apps/gateway-server run start",
"start:admin": "npm --workspace apps/admin-ui run preview",
- "test": "vitest run --config ./vitest.config.mts --reporter=verbose && npm run conformance:report",
+ "test": "vitest run --config ./vitest.config.mts --reporter=verbose",
"test:watch": "vitest --config ./vitest.config.mts",
- "conformance:report": "tsx packages/explicabl-core/src/reporting/saveReport.ts",
"demo:mcp": "npm-run-all -p demo:issuer demo:gateway demo:mcp-server",
"demo:issuer": "npm run -w @gatewaystack/demo-mcp-server dev",
"demo:mcp-server": "npm run -w @gatewaystack/demo-mcp-server dev",
diff --git a/packages/explicabl-core/src/reporting/saveReport.js b/packages/explicabl-core/src/reporting/saveReport.js
deleted file mode 100644
index 3e90f4a..0000000
--- a/packages/explicabl-core/src/reporting/saveReport.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import fs from "node:fs";
-const out = {
- version: "0.1.0",
- spec: "Apps SDK OAuth 2.1 + MCP Authorization (subset)",
- categories: {
- pkce: "pass",
- jwt_verify: "pass",
- scope_enforcement: "pass",
- allowlist: "pass",
- expiry: "pass"
- },
- timestamp: new Date().toISOString()
-};
-fs.mkdirSync("docs", { recursive: true });
-fs.writeFileSync("docs/conformance.json", JSON.stringify(out, null, 2));
-console.log("[conformance] wrote docs/conformance.json");
diff --git a/packages/explicabl-core/src/reporting/saveReport.ts b/packages/explicabl-core/src/reporting/saveReport.ts
deleted file mode 100644
index 7161784..0000000
--- a/packages/explicabl-core/src/reporting/saveReport.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import * as fs from "node:fs";
-const out = {
- version: "0.1.0",
- spec: "Apps SDK OAuth 2.1 + MCP Authorization (subset)",
- categories: {
- pkce: "pass",
- jwt_verify: "pass",
- scope_enforcement: "pass",
- allowlist: "pass",
- expiry: "pass"
- },
- timestamp: new Date().toISOString()
-};
-fs.mkdirSync("docs", { recursive: true });
-fs.writeFileSync("docs/conformance.json", JSON.stringify(out, null, 2));
-console.log("[conformance] wrote docs/conformance.json");
From e6541e3a7b758e94c6c0a7c7426683556104b268 Mon Sep 17 00:00:00 2001
From: David Crowe
Date: Thu, 23 Jul 2026 13:33:00 -0700
Subject: [PATCH 09/13] transformabl-core 0.4.1: redactPii overlap fix (H4) +
scan-truncation flag (M2)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Builds on the ReDoS fix in #30 (this branch's base). Adds the two remaining
0.4.1 items from #40 and bumps the facade so consumers can receive it.
H4 — redactPii overlap corruption (src/redact.ts):
Matches were substituted by descending start with each match's own stale
`end`. When an inner overlapping match was replaced first, it shifted the text
so an outer match's `end` sliced the wrong point, leaving trailing PII digits
behind in placeholder/remove modes. #30's new recognizers make overlaps
routine (us_bank_number's \d{8,17} covers the same run as npi /
us_drivers_license). Fix: filter to the requested types, merge overlapping
matches into maximal intervals, then substitute the disjoint spans
right-to-left so no substitution can invalidate another's offsets. The merged
span is masked/removed/labelled as a unit (placeholder uses the widest
contributing type). match.value always equals text.slice(start,end), so
non-overlapping behavior is unchanged.
M2 — silent PII-scan truncation (src/detect.ts):
#30 capped scanning at 512 KB with no signal, so PII past the cap flowed
through unredacted and unlogged — fail-open that wasn't loud. Add
detectPiiDetailed() returning { matches, scanTruncated, scannedLength,
totalLength } and a scanTruncated flag on TransformResult; detectPii stays a
thin back-compatible wrapper. Exported MAX_PII_SCAN_LENGTH. Fixed the
now-false normalize.ts comment (detection no longer scans the full input).
Facade: bump @gatewaystack/transformabl-core range ^0.3.0 -> ^0.4.1 (^0.3.0
can never resolve 0.4.x) and the facade to 0.3.0 (re-exports detectPiiDetailed
+ MAX_PII_SCAN_LENGTH). Republish needed after publishing core 0.4.1.
Tests: redact.overlap.test.ts (partial/identical/contained overlaps, types
filter, no-surviving-substring acceptance) and scanTruncation.test.ts
(flag on/off, PII before vs past the cap, transform surfaces it). Full suite
198 green.
---
package-lock.json | 12 +--
.../__tests__/redact.overlap.test.ts | 87 +++++++++++++++++++
.../__tests__/scanTruncation.test.ts | 60 +++++++++++++
packages/transformabl-core/src/detect.ts | 63 ++++++++++++--
packages/transformabl-core/src/normalize.ts | 6 +-
packages/transformabl-core/src/redact.ts | 68 ++++++++++++---
packages/transformabl-core/src/transform.ts | 6 +-
packages/transformabl-core/src/types.ts | 7 ++
packages/transformabl/package.json | 4 +-
packages/transformabl/src/index.ts | 2 +
10 files changed, 280 insertions(+), 35 deletions(-)
create mode 100644 packages/transformabl-core/__tests__/redact.overlap.test.ts
create mode 100644 packages/transformabl-core/__tests__/scanTruncation.test.ts
diff --git a/package-lock.json b/package-lock.json
index 840f710..b136574 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6702,10 +6702,10 @@
},
"packages/transformabl": {
"name": "@gatewaystack/transformabl",
- "version": "0.2.0",
+ "version": "0.3.0",
"license": "MIT",
"dependencies": {
- "@gatewaystack/transformabl-core": "^0.3.0"
+ "@gatewaystack/transformabl-core": "^0.4.1"
},
"devDependencies": {
"typescript": "^5.6.3"
@@ -6716,18 +6716,12 @@
},
"packages/transformabl-core": {
"name": "@gatewaystack/transformabl-core",
- "version": "0.4.0",
+ "version": "0.4.1",
"license": "MIT",
"devDependencies": {
"typescript": "^5.6.3"
}
},
- "packages/transformabl/node_modules/@gatewaystack/transformabl-core": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/@gatewaystack/transformabl-core/-/transformabl-core-0.3.0.tgz",
- "integrity": "sha512-NWo2inIcHPGFSxSfzNI9VBZcZO9yxS4NVUsq/CvOe3RoOMGq0xt58C1Ex2Oa1uNZzQ3C/mPWzCRLeFbkd6ozIw==",
- "license": "MIT"
- },
"packages/validatabl": {
"name": "@gatewaystack/validatabl",
"version": "0.2.0",
diff --git a/packages/transformabl-core/__tests__/redact.overlap.test.ts b/packages/transformabl-core/__tests__/redact.overlap.test.ts
new file mode 100644
index 0000000..119a307
--- /dev/null
+++ b/packages/transformabl-core/__tests__/redact.overlap.test.ts
@@ -0,0 +1,87 @@
+// Overlapping-match redaction (H4 / #40).
+//
+// Detection routinely emits overlapping spans — e.g. `us_bank_number`
+// (`\d{8,17}`) covers the same digit run as `npi` / `us_drivers_license`. The
+// old descending-start substitution let an inner match's replacement shift the
+// text so an outer match's stale `end` sliced the wrong point, leaving trailing
+// PII digits behind in placeholder/remove modes. These lock in the
+// merge-into-maximal-intervals fix.
+
+import { describe, it, expect } from "vitest";
+import { redactPii } from "../src/redact.js";
+import type { PiiMatch } from "../src/types.js";
+
+describe("redactPii — overlapping matches merge into maximal intervals", () => {
+ // A partial overlap that, under the old code, left the digits "23" behind:
+ // the inner npi span (higher start) was substituted first, shifting the text
+ // so the outer us_bank_number span's stale `end` sliced past its real end.
+ const text = "id 1234567890123 end MORE";
+ const overlapping: PiiMatch[] = [
+ { type: "us_bank_number", value: "1234567890123", start: 3, end: 16 }, // outer
+ { type: "npi", value: "234", start: 4, end: 7 }, // inner, higher start
+ ];
+
+ it("placeholder mode: the outer span is replaced once and no digits survive", () => {
+ const result = redactPii(text, overlapping, { mode: "placeholder" });
+ expect(result).toBe("id [US_BANK_NUMBER] end MORE");
+ expect(result).not.toMatch(/\d/);
+ });
+
+ it("remove mode: the outer span is removed cleanly and no digits survive", () => {
+ const result = redactPii(text, overlapping, { mode: "remove" });
+ expect(result).toBe("id end MORE");
+ expect(result).not.toMatch(/\d/);
+ });
+
+ it("acceptance (#40): output contains no substring of any original match value", () => {
+ for (const mode of ["placeholder", "remove"] as const) {
+ const result = redactPii(text, overlapping, { mode });
+ for (const m of overlapping) {
+ expect(result).not.toContain(m.value);
+ }
+ expect(result).not.toMatch(/\d/);
+ }
+ });
+
+ it("labels the merged span by its widest contributing type", () => {
+ const result = redactPii(text, overlapping, { mode: "placeholder" });
+ expect(result).toContain("[US_BANK_NUMBER]");
+ expect(result).not.toContain("[NPI]");
+ });
+
+ it("identical spans (same start and end) collapse to one replacement", () => {
+ // us_bank_number and us_drivers_license both match an 8-digit run.
+ const t = "acct 12345678 ok";
+ const same: PiiMatch[] = [
+ { type: "us_bank_number", value: "12345678", start: 5, end: 13 },
+ { type: "us_drivers_license", value: "12345678", start: 5, end: 13 },
+ ];
+ const result = redactPii(t, same, { mode: "placeholder" });
+ expect(result).toBe("acct [US_BANK_NUMBER] ok");
+ expect(result).not.toMatch(/\d/);
+ });
+
+ it("respects `types`: only the requested type's span is redacted", () => {
+ // Only redact npi. Its [4,7] span ("234") is replaced; the surrounding
+ // bank digits are the caller's explicit choice to keep, and the merge must
+ // not sweep the excluded us_bank_number span in.
+ const result = redactPii(text, overlapping, { mode: "placeholder", types: ["npi"] });
+ expect(result).toBe("id 1[NPI]567890123 end MORE");
+ });
+
+ it("mask mode masks the whole merged span (keeps only edge chars)", () => {
+ const result = redactPii(text, overlapping, { mode: "mask" });
+ // Full 13-char span masked keeping 2 at each edge: "12" + 9*'*' + "23".
+ expect(result).toBe("id 12*********23 end MORE");
+ });
+
+ it("non-overlapping matches are unaffected by the merge", () => {
+ const t = "mail x@y.com then 123-45-6789 z";
+ const matches: PiiMatch[] = [
+ { type: "email", value: "x@y.com", start: 5, end: 12 },
+ { type: "ssn", value: "123-45-6789", start: 18, end: 29 },
+ ];
+ const result = redactPii(t, matches, { mode: "placeholder" });
+ expect(result).toBe("mail [EMAIL] then [SSN] z");
+ });
+});
diff --git a/packages/transformabl-core/__tests__/scanTruncation.test.ts b/packages/transformabl-core/__tests__/scanTruncation.test.ts
new file mode 100644
index 0000000..307297c
--- /dev/null
+++ b/packages/transformabl-core/__tests__/scanTruncation.test.ts
@@ -0,0 +1,60 @@
+// PII-scan truncation loudness (M2 / #40).
+//
+// detectPii caps scanning at MAX_PII_SCAN_LENGTH (512 KB). Before this fix the
+// cap was silent, so PII past it flowed through unredacted AND unlogged —
+// a fail-open that wasn't loud. detectPiiDetailed / TransformResult now carry a
+// `scanTruncated` flag so a consumer can tell "scanned clean" from "not fully
+// scanned."
+
+import { describe, it, expect } from "vitest";
+import {
+ detectPii,
+ detectPiiDetailed,
+ MAX_PII_SCAN_LENGTH,
+} from "../src/detect.js";
+import { transformContent } from "../src/transform.js";
+
+describe("scan-cap truncation is reported (not silent)", () => {
+ it("does not flag truncation for normal-sized input", () => {
+ const r = detectPiiDetailed("contact john@example.com please");
+ expect(r.scanTruncated).toBe(false);
+ expect(r.totalLength).toBe("contact john@example.com please".length);
+ expect(r.scannedLength).toBe(r.totalLength);
+ expect(r.matches.some((m) => m.type === "email")).toBe(true);
+ });
+
+ it("flags truncation and does NOT scan PII past the cap", () => {
+ const filler = "a".repeat(MAX_PII_SCAN_LENGTH + 100);
+ const input = `${filler} leak@example.com`;
+ const r = detectPiiDetailed(input);
+
+ expect(r.scanTruncated).toBe(true);
+ expect(r.scannedLength).toBe(MAX_PII_SCAN_LENGTH);
+ expect(r.totalLength).toBe(input.length);
+ // The email is beyond the cap, so it is (knowingly) not found — the flag
+ // is what makes that honest rather than a silent miss.
+ expect(r.matches.some((m) => m.type === "email")).toBe(false);
+ });
+
+ it("still finds PII that falls before the cap in an over-cap input", () => {
+ const input = `early@example.com ${"a".repeat(MAX_PII_SCAN_LENGTH)} late@example.com`;
+ const r = detectPiiDetailed(input);
+ expect(r.scanTruncated).toBe(true);
+ const emails = r.matches.filter((m) => m.type === "email");
+ expect(emails).toHaveLength(1);
+ expect(emails[0].value).toBe("early@example.com");
+ });
+
+ it("transformContent surfaces scanTruncated on the result", () => {
+ const small = transformContent("john@example.com");
+ expect(small.scanTruncated).toBe(false);
+
+ const big = transformContent("a".repeat(MAX_PII_SCAN_LENGTH + 1));
+ expect(big.scanTruncated).toBe(true);
+ });
+
+ it("detectPii (array wrapper) stays behaviourally identical to detectPiiDetailed().matches", () => {
+ const input = "ssn 123-45-6789 and mail a@b.co";
+ expect(detectPii(input)).toEqual(detectPiiDetailed(input).matches);
+ });
+});
diff --git a/packages/transformabl-core/src/detect.ts b/packages/transformabl-core/src/detect.ts
index 5628d01..992aad7 100644
--- a/packages/transformabl-core/src/detect.ts
+++ b/packages/transformabl-core/src/detect.ts
@@ -126,14 +126,57 @@ const BUILTIN_PATTERNS: PiiPattern[] = [
},
];
+/**
+ * Defense-in-depth against pathological inputs: cap the length we scan.
+ * Even with bounded patterns, running every pattern over an unbounded body
+ * is a DoS lever (this is an agent/LLM gateway — large tool-call payloads
+ * are normal). PII beyond this cap is not scanned; callers handling very
+ * large bodies should chunk. 512 KB comfortably covers real tool I/O.
+ */
+export const MAX_PII_SCAN_LENGTH = 512 * 1024;
+
+/** Result of a PII scan, including whether the scan cap truncated the input. */
+export interface DetectionResult {
+ /** All matches found, in original-text coordinates, sorted by start. */
+ matches: PiiMatch[];
+ /**
+ * True when the (invisible-stripped) input was longer than
+ * {@link MAX_PII_SCAN_LENGTH} and PII past the cap was NOT scanned. When
+ * true, absence of matches beyond the cap is not evidence of absence — the
+ * caller must treat this as a loud fail-open signal, not silently pass the
+ * unscanned tail through as clean.
+ */
+ scanTruncated: boolean;
+ /** Number of characters actually scanned. */
+ scannedLength: number;
+ /** Full length of the invisible-stripped input. */
+ totalLength: number;
+}
+
/**
* Detect PII in text content.
* Returns all matches with their positions.
+ *
+ * Back-compatible thin wrapper over {@link detectPiiDetailed}. Callers that
+ * need to know whether the scan cap truncated the input (M2 / #40) should use
+ * {@link detectPiiDetailed} and inspect `scanTruncated`.
*/
export function detectPii(
text: string,
customPatterns?: Array<{ type: string; pattern: RegExp }>
): PiiMatch[] {
+ return detectPiiDetailed(text, customPatterns).matches;
+}
+
+/**
+ * Detect PII and report scan-cap truncation.
+ * Returns matches plus a `scanTruncated` flag so a truncated scan is never a
+ * silent fail-open.
+ */
+export function detectPiiDetailed(
+ text: string,
+ customPatterns?: Array<{ type: string; pattern: RegExp }>
+): DetectionResult {
// Strip zero-width / invisible characters before scanning so an attacker
// cannot defeat the regex by inserting them inside PII values (e.g.
// `john\u200B@example.com`). Matches found in the stripped form are
@@ -141,14 +184,10 @@ export function detectPii(
// obfuscated span — including the invisible chars themselves.
const { text: scanText, map } = stripInvisible(text);
- // Defense-in-depth against pathological inputs: cap the length we scan.
- // Even with bounded patterns, running every pattern over an unbounded body
- // is a DoS lever (this is an agent/LLM gateway — large tool-call payloads
- // are normal). PII beyond this cap is not scanned; callers handling very
- // large bodies should chunk. 512 KB comfortably covers real tool I/O.
- const MAX_SCAN_LENGTH = 512 * 1024;
- const boundedScanText =
- scanText.length > MAX_SCAN_LENGTH ? scanText.slice(0, MAX_SCAN_LENGTH) : scanText;
+ const scanTruncated = scanText.length > MAX_PII_SCAN_LENGTH;
+ const boundedScanText = scanTruncated
+ ? scanText.slice(0, MAX_PII_SCAN_LENGTH)
+ : scanText;
const matches: PiiMatch[] = [];
const allPatterns: Array<{ type: string; pattern: RegExp }> = [
@@ -187,5 +226,11 @@ export function detectPii(
// Sort by position
matches.sort((a, b) => a.start - b.start);
- return matches;
+
+ return {
+ matches,
+ scanTruncated,
+ scannedLength: boundedScanText.length,
+ totalLength: scanText.length,
+ };
}
diff --git a/packages/transformabl-core/src/normalize.ts b/packages/transformabl-core/src/normalize.ts
index 362dfec..9727093 100644
--- a/packages/transformabl-core/src/normalize.ts
+++ b/packages/transformabl-core/src/normalize.ts
@@ -37,8 +37,10 @@ const INVISIBLE_CHAR_RE = /[\u200B-\u200D\u2060\uFEFF]/;
// Defensive upper bound on the normalization pass. Callers should enforce
// their own payload-size limits upstream; this cap exists so an oversized
// input cannot itself turn the O(n) character walk into a DoS vector.
-// PII detection still runs on the full original input — the oversized-
-// input path just skips ZW-normalization rather than iterating.
+// Over this length ZW-normalization is skipped (the input is returned
+// unchanged). Note the detection scan has its own, smaller cap
+// (MAX_PII_SCAN_LENGTH, 512 KB) past which PII is not scanned — detectPii
+// truncates and detectPiiDetailed reports it via `scanTruncated`.
const MAX_NORMALIZE_LENGTH = 1_000_000;
export function stripInvisible(input: string): StrippedText {
diff --git a/packages/transformabl-core/src/redact.ts b/packages/transformabl-core/src/redact.ts
index c3e12f2..8508508 100644
--- a/packages/transformabl-core/src/redact.ts
+++ b/packages/transformabl-core/src/redact.ts
@@ -2,7 +2,16 @@
//
// PII redaction: mask, remove, or replace detected PII.
-import type { PiiMatch, RedactionConfig, RedactionMode } from "./types.js";
+import type { PiiMatch, PiiType, RedactionConfig, RedactionMode } from "./types.js";
+
+/** A maximal interval formed by merging overlapping matches. */
+interface MergedSpan {
+ start: number;
+ end: number;
+ /** Type of the widest contributing match — used for the placeholder label. */
+ repType: PiiType;
+ repWidth: number;
+}
/**
* Redact PII matches from text.
@@ -11,6 +20,15 @@ import type { PiiMatch, RedactionConfig, RedactionMode } from "./types.js";
* - "mask": Replace middle characters with mask char (e.g., "jo**@example.com")
* - "remove": Remove the PII entirely
* - "placeholder": Replace with type label (e.g., "[EMAIL]")
+ *
+ * Overlapping matches are merged into maximal intervals before substitution.
+ * Detection routinely produces overlaps — e.g. `us_bank_number` (`\d{8,17}`)
+ * spans the same digit run as `npi` / `us_drivers_license`. Substituting them
+ * independently (the old descending-start loop) let an inner match's
+ * replacement shift the text so an outer match's now-stale `end` sliced the
+ * wrong point, leaving trailing PII digits behind in placeholder/remove modes
+ * (H4). Merging first guarantees the spans handed to substitution are
+ * disjoint, so no substitution can invalidate another's offsets.
*/
export function redactPii(
text: string,
@@ -26,30 +44,58 @@ export function redactPii(
? new Set(config.types)
: null; // null = redact all
- // Process matches in reverse order to preserve positions
- const sorted = [...matches].sort((a, b) => b.start - a.start);
- let result = text;
+ // Only redact matches the caller asked for. Filtering BEFORE the merge is
+ // what keeps an excluded type from being swept into a redacted span just
+ // because it overlapped an included one. Drop zero-width/inverted spans too.
+ const selected = matches.filter(
+ (m) => (!typesToRedact || typesToRedact.has(m.type)) && m.end > m.start
+ );
+ if (selected.length === 0) return text;
- for (const match of sorted) {
- if (typesToRedact && !typesToRedact.has(match.type)) continue;
+ // Merge overlapping spans into maximal intervals. Sort ascending by start
+ // (then end) and extend the current interval whenever the next match starts
+ // before the current one ends. Touching spans (start === prev end) stay
+ // separate — they are already disjoint and safe to substitute independently.
+ const asc = [...selected].sort((a, b) => a.start - b.start || a.end - b.end);
+ const merged: MergedSpan[] = [];
+ for (const m of asc) {
+ const width = m.end - m.start;
+ const last = merged[merged.length - 1];
+ if (last && m.start < last.end) {
+ if (m.end > last.end) last.end = m.end;
+ if (width > last.repWidth) {
+ last.repType = m.type;
+ last.repWidth = width;
+ }
+ } else {
+ merged.push({ start: m.start, end: m.end, repType: m.type, repWidth: width });
+ }
+ }
- let replacement: string;
+ // Substitute right-to-left so earlier offsets stay valid. Spans are disjoint,
+ // so the substring being replaced is exactly the original PII span — no other
+ // span's coordinates can have shifted underneath it.
+ let result = text;
+ for (let i = merged.length - 1; i >= 0; i--) {
+ const span = merged[i];
+ const original = text.slice(span.start, span.end);
+ let replacement: string;
switch (mode) {
case "mask":
- replacement = maskValue(match.value, maskChar, maskKeep);
+ replacement = maskValue(original, maskChar, maskKeep);
break;
case "remove":
replacement = "";
break;
case "placeholder":
replacement = config?.placeholder
- ? config.placeholder.replace("{TYPE}", match.type.toUpperCase())
- : `[${match.type.toUpperCase()}]`;
+ ? config.placeholder.replace("{TYPE}", span.repType.toUpperCase())
+ : `[${span.repType.toUpperCase()}]`;
break;
}
- result = result.slice(0, match.start) + replacement + result.slice(match.end);
+ result = result.slice(0, span.start) + replacement + result.slice(span.end);
}
return result;
diff --git a/packages/transformabl-core/src/transform.ts b/packages/transformabl-core/src/transform.ts
index d644754..065e2f2 100644
--- a/packages/transformabl-core/src/transform.ts
+++ b/packages/transformabl-core/src/transform.ts
@@ -3,7 +3,7 @@
// Full transformation pipeline: detect → classify → redact → extract metadata.
import type { TransformConfig, TransformResult } from "./types.js";
-import { detectPii } from "./detect.js";
+import { detectPiiDetailed } from "./detect.js";
import { redactPii } from "./redact.js";
import { classifyContent } from "./classify.js";
import { extractMetadata } from "./metadata.js";
@@ -23,7 +23,8 @@ export function transformContent(
config?: TransformConfig
): TransformResult {
// 1. Detect PII
- const piiMatches = detectPii(content, config?.customPatterns);
+ const detection = detectPiiDetailed(content, config?.customPatterns);
+ const piiMatches = detection.matches;
// 2. Classify
const classification =
@@ -54,5 +55,6 @@ export function transformContent(
classification,
metadata,
transformed: redacted !== content,
+ scanTruncated: detection.scanTruncated,
};
}
diff --git a/packages/transformabl-core/src/types.ts b/packages/transformabl-core/src/types.ts
index e382c90..4fbb3c2 100644
--- a/packages/transformabl-core/src/types.ts
+++ b/packages/transformabl-core/src/types.ts
@@ -102,6 +102,13 @@ export interface TransformResult {
metadata: ContentMetadata;
/** Whether any transformation was applied. */
transformed: boolean;
+ /**
+ * True when the input exceeded the PII scan cap (512 KB of invisible-
+ * stripped text) and content past the cap was NOT scanned for PII. A
+ * consumer must surface this rather than treat the result as a clean pass —
+ * fail-open must be loud (M2 / #40).
+ */
+ scanTruncated: boolean;
}
/** Configuration for the full transformation pipeline. */
diff --git a/packages/transformabl/package.json b/packages/transformabl/package.json
index e32affa..0518084 100644
--- a/packages/transformabl/package.json
+++ b/packages/transformabl/package.json
@@ -1,6 +1,6 @@
{
"name": "@gatewaystack/transformabl",
- "version": "0.2.0",
+ "version": "0.3.0",
"private": false,
"license": "MIT",
"type": "module",
@@ -19,7 +19,7 @@
"prepublishOnly": "npm run build"
},
"dependencies": {
- "@gatewaystack/transformabl-core": "^0.3.0"
+ "@gatewaystack/transformabl-core": "^0.4.1"
},
"peerDependencies": {
"express": "^4.0.0 || ^5.0.0"
diff --git a/packages/transformabl/src/index.ts b/packages/transformabl/src/index.ts
index 66a7dbd..94a04da 100644
--- a/packages/transformabl/src/index.ts
+++ b/packages/transformabl/src/index.ts
@@ -80,6 +80,8 @@ export function transformabl(config?: TransformablMiddlewareConfig): RequestHand
export {
transformContent,
detectPii,
+ detectPiiDetailed,
+ MAX_PII_SCAN_LENGTH,
redactPii,
classifyContent,
extractMetadata,
From ff89b751b1a51a9b9139adab2926c80d72cec5f4 Mon Sep 17 00:00:00 2001
From: David Crowe
Date: Thu, 23 Jul 2026 14:05:58 -0700
Subject: [PATCH 10/13] Package hygiene: per-package LICENSE, engines,
tsbuildinfo out of dist, dep fixes (#42)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Class-level packaging defects from the 2026-07-23 sweep, across the 13
published packages.
- **LICENSE shipped in zero tarballs.** Every package.json lists "LICENSE" in
`files`, but the file existed only at repo root — npm only auto-includes
LICENSE from the package dir, so every tarball claimed MIT and shipped no
license. Added a per-package LICENSE (copy of root) and ensured "LICENSE"
(and README.md where present) is in each `files` list. `npm pack --dry-run`
now shows LICENSE in the tarball.
- **tsconfig.tsbuildinfo shipped in dist** of the 10 packages that set
`tsBuildInfoFile: dist/...` — a dead dev file in every tarball. Moved it to
the package root (`tsconfig.tsbuildinfo`), which is outside the `files`
whitelist, so it no longer ships. Verified no *.tsbuildinfo remains in any
dist. (Already covered by .gitignore, so none get tracked.)
- **No `engines` field anywhere** despite ESM-only + global-fetch reliance.
Added `"node": ">=18"` to all published packages.
- **request-context / observability-core had no prepublishOnly build** —
stale-dist publish risk. Added `prepublishOnly: npm run build` wherever a
build script exists but the hook was missing.
- **proxyabl pinned request-context exactly** (`0.0.6`) while identifiabl uses
`^` — a future request-context patch would force a proxyabl republish.
Loosened to `^0.0.6`.
- **identifiabl declared unused `jose@^5`** — its source/dist never import jose
(only a stale doc comment referenced it), and it caused a dual-jose install
(5.x hoisted + 6.x under core). Dropped it.
Build clean, full suite 159 green.
Out of scope here (flagged): the transformabl facade range ^0.3.0 -> ^0.4.1 is
handled in the 0.4.1 release PR (#45). The four declared-but-unused deps in the
"badge-padding" item live in gatewaystack-connect (the prod repo), not here.
Also noted: packages/explicabl-core/ contains the package NAMED
@gatewaystack/observability-core — a dir/name mismatch worth a follow-up.
---
packages/explicabl-core/LICENSE | 21 +++++++++++++++++++++
packages/explicabl-core/package.json | 5 ++++-
packages/explicabl/LICENSE | 21 +++++++++++++++++++++
packages/explicabl/package.json | 3 +++
packages/explicabl/tsconfig.json | 2 +-
packages/identifiabl-core/LICENSE | 21 +++++++++++++++++++++
packages/identifiabl-core/package.json | 7 ++++++-
packages/identifiabl-core/tsconfig.json | 2 +-
packages/identifiabl/LICENSE | 21 +++++++++++++++++++++
packages/identifiabl/package.json | 4 +++-
packages/limitabl-core/LICENSE | 21 +++++++++++++++++++++
packages/limitabl-core/package.json | 3 +++
packages/limitabl-core/tsconfig.json | 2 +-
packages/limitabl/LICENSE | 21 +++++++++++++++++++++
packages/limitabl/package.json | 3 +++
packages/limitabl/tsconfig.json | 2 +-
packages/proxyabl-core/LICENSE | 21 +++++++++++++++++++++
packages/proxyabl-core/package.json | 3 +++
packages/proxyabl-core/tsconfig.json | 2 +-
packages/proxyabl/LICENSE | 21 +++++++++++++++++++++
packages/proxyabl/package.json | 5 ++++-
packages/proxyabl/tsconfig.json | 2 +-
packages/request-context/LICENSE | 21 +++++++++++++++++++++
packages/request-context/package.json | 8 ++++++--
packages/transformabl-core/LICENSE | 21 +++++++++++++++++++++
packages/transformabl-core/package.json | 3 +++
packages/transformabl-core/tsconfig.json | 2 +-
packages/transformabl/LICENSE | 21 +++++++++++++++++++++
packages/transformabl/package.json | 3 +++
packages/transformabl/tsconfig.json | 2 +-
packages/validatabl-core/LICENSE | 21 +++++++++++++++++++++
packages/validatabl-core/package.json | 3 +++
packages/validatabl-core/tsconfig.json | 2 +-
packages/validatabl/LICENSE | 21 +++++++++++++++++++++
packages/validatabl/package.json | 3 +++
packages/validatabl/tsconfig.json | 2 +-
36 files changed, 330 insertions(+), 16 deletions(-)
create mode 100644 packages/explicabl-core/LICENSE
create mode 100644 packages/explicabl/LICENSE
create mode 100644 packages/identifiabl-core/LICENSE
create mode 100644 packages/identifiabl/LICENSE
create mode 100644 packages/limitabl-core/LICENSE
create mode 100644 packages/limitabl/LICENSE
create mode 100644 packages/proxyabl-core/LICENSE
create mode 100644 packages/proxyabl/LICENSE
create mode 100644 packages/request-context/LICENSE
create mode 100644 packages/transformabl-core/LICENSE
create mode 100644 packages/transformabl/LICENSE
create mode 100644 packages/validatabl-core/LICENSE
create mode 100644 packages/validatabl/LICENSE
diff --git a/packages/explicabl-core/LICENSE b/packages/explicabl-core/LICENSE
new file mode 100644
index 0000000..c29d293
--- /dev/null
+++ b/packages/explicabl-core/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 Reducibl
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/explicabl-core/package.json b/packages/explicabl-core/package.json
index da1df19..8cdfed5 100644
--- a/packages/explicabl-core/package.json
+++ b/packages/explicabl-core/package.json
@@ -2,5 +2,8 @@
"name": "@gatewaystack/observability-core",
"version": "0.0.1",
"main": "dist/index.js",
- "types": "dist/index.d.ts"
+ "types": "dist/index.d.ts",
+ "engines": {
+ "node": ">=18"
+ }
}
diff --git a/packages/explicabl/LICENSE b/packages/explicabl/LICENSE
new file mode 100644
index 0000000..c29d293
--- /dev/null
+++ b/packages/explicabl/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 Reducibl
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/explicabl/package.json b/packages/explicabl/package.json
index 4a360ce..2bc657c 100644
--- a/packages/explicabl/package.json
+++ b/packages/explicabl/package.json
@@ -26,5 +26,8 @@
"@types/express": "^4.17.21",
"typescript": "^5.6.3",
"tsx": "^4.19.2"
+ },
+ "engines": {
+ "node": ">=18"
}
}
diff --git a/packages/explicabl/tsconfig.json b/packages/explicabl/tsconfig.json
index f5bc8df..8cb43ee 100644
--- a/packages/explicabl/tsconfig.json
+++ b/packages/explicabl/tsconfig.json
@@ -13,7 +13,7 @@
"declarationMap": true,
"sourceMap": true,
"types": ["node"],
- "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo"
+ "tsBuildInfoFile": "tsconfig.tsbuildinfo"
},
"include": ["src"]
}
diff --git a/packages/identifiabl-core/LICENSE b/packages/identifiabl-core/LICENSE
new file mode 100644
index 0000000..c29d293
--- /dev/null
+++ b/packages/identifiabl-core/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 Reducibl
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/identifiabl-core/package.json b/packages/identifiabl-core/package.json
index 75588f5..7e9acc9 100644
--- a/packages/identifiabl-core/package.json
+++ b/packages/identifiabl-core/package.json
@@ -13,7 +13,9 @@
"directory": "packages/identifiabl-core"
},
"files": [
- "dist"
+ "dist",
+ "LICENSE",
+ "README.md"
],
"scripts": {
"build": "tsc -p tsconfig.json",
@@ -21,5 +23,8 @@
},
"dependencies": {
"jose": "^6.1.0"
+ },
+ "engines": {
+ "node": ">=18"
}
}
diff --git a/packages/identifiabl-core/tsconfig.json b/packages/identifiabl-core/tsconfig.json
index 5c317ea..e4c820a 100644
--- a/packages/identifiabl-core/tsconfig.json
+++ b/packages/identifiabl-core/tsconfig.json
@@ -4,7 +4,7 @@
"composite": true,
"rootDir": "src",
"outDir": "dist",
- "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo"
+ "tsBuildInfoFile": "tsconfig.tsbuildinfo"
},
"include": ["src"],
"references": [
diff --git a/packages/identifiabl/LICENSE b/packages/identifiabl/LICENSE
new file mode 100644
index 0000000..c29d293
--- /dev/null
+++ b/packages/identifiabl/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 Reducibl
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/identifiabl/package.json b/packages/identifiabl/package.json
index a3a33f6..2a8b24c 100644
--- a/packages/identifiabl/package.json
+++ b/packages/identifiabl/package.json
@@ -15,7 +15,6 @@
"LICENSE"
],
"dependencies": {
- "jose": "^5.0.0",
"@gatewaystack/identifiabl-core": "^0.1.0",
"@gatewaystack/request-context": "^0.0.6"
},
@@ -25,5 +24,8 @@
"scripts": {
"build": "tsc -p tsconfig.json",
"prepublishOnly": "npm run build"
+ },
+ "engines": {
+ "node": ">=18"
}
}
diff --git a/packages/limitabl-core/LICENSE b/packages/limitabl-core/LICENSE
new file mode 100644
index 0000000..c29d293
--- /dev/null
+++ b/packages/limitabl-core/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 Reducibl
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/limitabl-core/package.json b/packages/limitabl-core/package.json
index 83b7fff..c60c68d 100644
--- a/packages/limitabl-core/package.json
+++ b/packages/limitabl-core/package.json
@@ -20,5 +20,8 @@
},
"devDependencies": {
"typescript": "^5.6.3"
+ },
+ "engines": {
+ "node": ">=18"
}
}
diff --git a/packages/limitabl-core/tsconfig.json b/packages/limitabl-core/tsconfig.json
index f3f2f18..6566a49 100644
--- a/packages/limitabl-core/tsconfig.json
+++ b/packages/limitabl-core/tsconfig.json
@@ -8,7 +8,7 @@
"declarationMap": true,
"sourceMap": true,
"types": ["node"],
- "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo"
+ "tsBuildInfoFile": "tsconfig.tsbuildinfo"
},
"include": ["src"]
}
diff --git a/packages/limitabl/LICENSE b/packages/limitabl/LICENSE
new file mode 100644
index 0000000..c29d293
--- /dev/null
+++ b/packages/limitabl/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 Reducibl
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/limitabl/package.json b/packages/limitabl/package.json
index 7ce7aaa..0bc2f8d 100644
--- a/packages/limitabl/package.json
+++ b/packages/limitabl/package.json
@@ -27,5 +27,8 @@
"devDependencies": {
"@types/express": "^4.17.21",
"typescript": "^5.6.3"
+ },
+ "engines": {
+ "node": ">=18"
}
}
diff --git a/packages/limitabl/tsconfig.json b/packages/limitabl/tsconfig.json
index 7208f0f..0678654 100644
--- a/packages/limitabl/tsconfig.json
+++ b/packages/limitabl/tsconfig.json
@@ -8,7 +8,7 @@
"declarationMap": true,
"sourceMap": true,
"types": ["node"],
- "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo"
+ "tsBuildInfoFile": "tsconfig.tsbuildinfo"
},
"include": ["src"],
"references": [
diff --git a/packages/proxyabl-core/LICENSE b/packages/proxyabl-core/LICENSE
new file mode 100644
index 0000000..c29d293
--- /dev/null
+++ b/packages/proxyabl-core/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 Reducibl
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/proxyabl-core/package.json b/packages/proxyabl-core/package.json
index 5cb5be1..136fc7b 100644
--- a/packages/proxyabl-core/package.json
+++ b/packages/proxyabl-core/package.json
@@ -20,5 +20,8 @@
},
"devDependencies": {
"typescript": "^5.6.3"
+ },
+ "engines": {
+ "node": ">=18"
}
}
diff --git a/packages/proxyabl-core/tsconfig.json b/packages/proxyabl-core/tsconfig.json
index f3f2f18..6566a49 100644
--- a/packages/proxyabl-core/tsconfig.json
+++ b/packages/proxyabl-core/tsconfig.json
@@ -8,7 +8,7 @@
"declarationMap": true,
"sourceMap": true,
"types": ["node"],
- "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo"
+ "tsBuildInfoFile": "tsconfig.tsbuildinfo"
},
"include": ["src"]
}
diff --git a/packages/proxyabl/LICENSE b/packages/proxyabl/LICENSE
new file mode 100644
index 0000000..c29d293
--- /dev/null
+++ b/packages/proxyabl/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 Reducibl
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/proxyabl/package.json b/packages/proxyabl/package.json
index 4235dd8..50675be 100644
--- a/packages/proxyabl/package.json
+++ b/packages/proxyabl/package.json
@@ -22,11 +22,14 @@
"express": "^4.22.0",
"express-rate-limit": "^8.2.2",
"@gatewaystack/proxyabl-core": "^0.1.0",
- "@gatewaystack/request-context": "0.0.6"
+ "@gatewaystack/request-context": "^0.0.6"
},
"devDependencies": {
"@types/express": "^4.17.21",
"typescript": "^5.6.3",
"tsx": "^4.19.2"
+ },
+ "engines": {
+ "node": ">=18"
}
}
diff --git a/packages/proxyabl/tsconfig.json b/packages/proxyabl/tsconfig.json
index ae64fc4..962c896 100644
--- a/packages/proxyabl/tsconfig.json
+++ b/packages/proxyabl/tsconfig.json
@@ -8,7 +8,7 @@
"declarationMap": true,
"sourceMap": true,
"types": ["node"],
- "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo"
+ "tsBuildInfoFile": "tsconfig.tsbuildinfo"
},
"include": ["src"],
"references": [
diff --git a/packages/request-context/LICENSE b/packages/request-context/LICENSE
new file mode 100644
index 0000000..c29d293
--- /dev/null
+++ b/packages/request-context/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 Reducibl
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/request-context/package.json b/packages/request-context/package.json
index ac22aea..3e9b840 100644
--- a/packages/request-context/package.json
+++ b/packages/request-context/package.json
@@ -15,10 +15,14 @@
"LICENSE"
],
"scripts": {
- "build": "tsc -p tsconfig.json"
+ "build": "tsc -p tsconfig.json",
+ "prepublishOnly": "npm run build"
},
"dependencies": {},
"devDependencies": {
"typescript": "^5.6.3"
+ },
+ "engines": {
+ "node": ">=18"
}
-}
\ No newline at end of file
+}
diff --git a/packages/transformabl-core/LICENSE b/packages/transformabl-core/LICENSE
new file mode 100644
index 0000000..c29d293
--- /dev/null
+++ b/packages/transformabl-core/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 Reducibl
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/transformabl-core/package.json b/packages/transformabl-core/package.json
index 5cae6eb..c7971e7 100644
--- a/packages/transformabl-core/package.json
+++ b/packages/transformabl-core/package.json
@@ -20,5 +20,8 @@
},
"devDependencies": {
"typescript": "^5.6.3"
+ },
+ "engines": {
+ "node": ">=18"
}
}
diff --git a/packages/transformabl-core/tsconfig.json b/packages/transformabl-core/tsconfig.json
index f3f2f18..6566a49 100644
--- a/packages/transformabl-core/tsconfig.json
+++ b/packages/transformabl-core/tsconfig.json
@@ -8,7 +8,7 @@
"declarationMap": true,
"sourceMap": true,
"types": ["node"],
- "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo"
+ "tsBuildInfoFile": "tsconfig.tsbuildinfo"
},
"include": ["src"]
}
diff --git a/packages/transformabl/LICENSE b/packages/transformabl/LICENSE
new file mode 100644
index 0000000..c29d293
--- /dev/null
+++ b/packages/transformabl/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 Reducibl
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/transformabl/package.json b/packages/transformabl/package.json
index e32affa..04026ae 100644
--- a/packages/transformabl/package.json
+++ b/packages/transformabl/package.json
@@ -26,5 +26,8 @@
},
"devDependencies": {
"typescript": "^5.6.3"
+ },
+ "engines": {
+ "node": ">=18"
}
}
diff --git a/packages/transformabl/tsconfig.json b/packages/transformabl/tsconfig.json
index f6426b1..1a667f2 100644
--- a/packages/transformabl/tsconfig.json
+++ b/packages/transformabl/tsconfig.json
@@ -8,7 +8,7 @@
"declarationMap": true,
"sourceMap": true,
"types": ["node"],
- "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo"
+ "tsBuildInfoFile": "tsconfig.tsbuildinfo"
},
"include": ["src"],
"references": [
diff --git a/packages/validatabl-core/LICENSE b/packages/validatabl-core/LICENSE
new file mode 100644
index 0000000..c29d293
--- /dev/null
+++ b/packages/validatabl-core/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 Reducibl
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/validatabl-core/package.json b/packages/validatabl-core/package.json
index c8dd361..072e135 100644
--- a/packages/validatabl-core/package.json
+++ b/packages/validatabl-core/package.json
@@ -20,5 +20,8 @@
},
"devDependencies": {
"typescript": "^5.6.3"
+ },
+ "engines": {
+ "node": ">=18"
}
}
diff --git a/packages/validatabl-core/tsconfig.json b/packages/validatabl-core/tsconfig.json
index f3f2f18..6566a49 100644
--- a/packages/validatabl-core/tsconfig.json
+++ b/packages/validatabl-core/tsconfig.json
@@ -8,7 +8,7 @@
"declarationMap": true,
"sourceMap": true,
"types": ["node"],
- "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo"
+ "tsBuildInfoFile": "tsconfig.tsbuildinfo"
},
"include": ["src"]
}
diff --git a/packages/validatabl/LICENSE b/packages/validatabl/LICENSE
new file mode 100644
index 0000000..c29d293
--- /dev/null
+++ b/packages/validatabl/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 Reducibl
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/validatabl/package.json b/packages/validatabl/package.json
index 3ffa24e..b214cc9 100644
--- a/packages/validatabl/package.json
+++ b/packages/validatabl/package.json
@@ -27,5 +27,8 @@
"devDependencies": {
"@types/express": "^4.17.21",
"typescript": "^5.6.3"
+ },
+ "engines": {
+ "node": ">=18"
}
}
diff --git a/packages/validatabl/tsconfig.json b/packages/validatabl/tsconfig.json
index 31b0f44..978a775 100644
--- a/packages/validatabl/tsconfig.json
+++ b/packages/validatabl/tsconfig.json
@@ -8,7 +8,7 @@
"declarationMap": true,
"sourceMap": true,
"types": ["node"],
- "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo"
+ "tsBuildInfoFile": "tsconfig.tsbuildinfo"
},
"include": ["src"],
"references": [
From 7cbc9c25edf27592bae4af83b3e6af878704fad5 Mon Sep 17 00:00:00 2001
From: David Crowe
Date: Thu, 23 Jul 2026 14:15:22 -0700
Subject: [PATCH 11/13] Correct proxyabl SSRF marketing to match what actually
runs (#43)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
proxyabl-core's assertUrlSafe/executeProxyRequest (DNS-resolving private-IP
blocking, host allowlist, redirect:"manual", size/timeout caps) are exported
and unit-tested but have zero consumers outside their own tests. The README
and docs implied the gateway proxy runs this engine; it does not.
Decision: correct the marketing (the issue's "Or" option), not wire the engine
into the router — because the router's model is genuinely different, not a bug
to paper over:
- The three user-facing router fetches (mcpHandler, restToolHandler,
proxyHandler) all forward to an operator-CONFIGURED backend (functionsBase /
proxy target). The caller controls the tool name / path, not the host, which
is pinned to the configured backend via a host+protocol equality check.
- executeProxyRequest is built for the opposite case — proxying arbitrary,
caller-influenced URLs against an allowlist. Dropping it into the router
would (a) reject configured backends that are legitimately internal
(private-IP blocking), (b) change response semantics (it buffers+caps and
throws on non-2xx instead of passing upstream status/body through), and
(c) block upstream redirects the router may rely on. That is a breaking
change to live gateway behavior to satisfy a marketing line — backwards.
- OSS has no arbitrary-URL / custom-connector path today (executeProxyRequest's
ancestor served custom HTTP tools that live only in prod), so the engine has
no natural OSS consumer yet.
Changes (docs only):
- README module table: proxyabl row now describes the router accurately
(configured-backend, path-allowlisted, identity-aware) and points to the
proxyabl-core SSRF primitive.
- README: added an "SSRF note" spelling out the two threat models.
- docs/packages.md: split the proxyabl entry into the core SSRF-safe primitive
vs the pass-through Express router, and noted the router does not route
through the engine + the convergence direction.
- proxyabl-core/README: added a scope note so a reader (or a reviewer grepping
assertUrlSafe) sees the primitive is intentional and standalone, not dead.
The proxyabl-core README's own SSRF description was already accurate for the
primitive and is unchanged except for the note. The real fix — one canonical
proxyabl-core engine consumed by both the OSS router and prod's fork — is the
convergence epic; flagged, not attempted here.
---
README.md | 4 +++-
docs/packages.md | 8 +++++++-
packages/proxyabl-core/README.md | 2 ++
3 files changed, 12 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index d4b42b4..345de5d 100644
--- a/README.md
+++ b/README.md
@@ -88,9 +88,11 @@ Each layer ships as a framework-agnostic `-core` package plus an Express middlew
| `@gatewaystack/transformabl` | [](https://www.npmjs.com/package/@gatewaystack/transformabl) | PII detection, redaction, safety classification |
| `@gatewaystack/validatabl` | [](https://www.npmjs.com/package/@gatewaystack/validatabl) | Deny-by-default policy engine, scope/permission enforcement |
| `@gatewaystack/limitabl` | [](https://www.npmjs.com/package/@gatewaystack/limitabl) | Rate limits, budget tracking, agent guard |
-| `@gatewaystack/proxyabl` | [](https://www.npmjs.com/package/@gatewaystack/proxyabl) | Auth mode routing, SSRF protection, identity-aware proxy |
+| `@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` | [](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.
diff --git a/docs/packages.md b/docs/packages.md
index c636227..3621f5e 100644
--- a/docs/packages.md
+++ b/docs/packages.md
@@ -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
diff --git a/packages/proxyabl-core/README.md b/packages/proxyabl-core/README.md
index d3e567f..d078b43 100644
--- a/packages/proxyabl-core/README.md
+++ b/packages/proxyabl-core/README.md
@@ -98,6 +98,8 @@ resolveAuth(config: AuthModeConfig, context: AuthContext): ResolvedAuth
### SSRF Protection
+> **Scope.** `assertUrlSafe` / `executeProxyRequest` are the primitive for proxying **arbitrary, caller-influenced URLs** — call them directly from your handler. The Express router in [`@gatewaystack/proxyabl`](https://www.npmjs.com/package/@gatewaystack/proxyabl) is a different thing: it forwards to an operator-**configured** backend and does not route through this engine (a configured backend may legitimately be internal, which private-IP blocking would reject). Importing the router does not give you this SSRF check automatically.
+
```ts
assertUrlSafe(opts: UrlSafetyOptions): Promise
```
From ee548e677be6c9c1740873624ccf676c0819c206 Mon Sep 17 00:00:00 2001
From: David Crowe
Date: Thu, 23 Jul 2026 15:10:27 -0700
Subject: [PATCH 12/13] Add a zero-config quickstart example: governance in 30
seconds
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The on-ramp led with `identifiabl` (JWT), which 401s every request until you
stand up an IdP — so a new developer's first run showed nothing working. This
adds a runnable, zero-config first win.
`examples/quickstart` is a ~50-line script over the pure `-core` packages that
prints three real decisions with no IdP, backend, or config:
- deny-by-default policy (validatabl) — read_file allowed, delete_database denied
- PII redaction (transformabl) — email + SSN → [EMAIL]/[SSN]
- rate limit (limitabl) — the 4th call in the window is refused
It uses only currently-published package versions, so `npm install && npm start`
works today. README now offers this as the "see it work first" path before the
IdP-backed middleware setup. CLAUDE.md's stale "tests are not yet implemented"
status is corrected (suites exist under __tests__/).
Deliberately does NOT demo the "rm -rf hardline" — that classifier lives in the
private prod engine, not OSS; showing it here would claim a capability the repo
doesn't ship.
Verified: `npm run build` + the example runs and prints the decisions.
---
CLAUDE.md | 14 +++----
README.md | 10 +++++
examples/README.md | 12 ++++++
examples/quickstart/README.md | 32 +++++++++++++++
examples/quickstart/index.ts | 69 ++++++++++++++++++++++++++++++++
examples/quickstart/package.json | 17 ++++++++
6 files changed, 147 insertions(+), 7 deletions(-)
create mode 100644 examples/README.md
create mode 100644 examples/quickstart/README.md
create mode 100644 examples/quickstart/index.ts
create mode 100644 examples/quickstart/package.json
diff --git a/CLAUDE.md b/CLAUDE.md
index 4f65c01..73bbb2b 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -100,10 +100,10 @@ packages//
- 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//__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.
diff --git a/README.md b/README.md
index d4b42b4..923b522 100644
--- a/README.md
+++ b/README.md
@@ -44,6 +44,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
diff --git a/examples/README.md b/examples/README.md
new file mode 100644
index 0000000..d9d30f0
--- /dev/null
+++ b/examples/README.md
@@ -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).
diff --git a/examples/quickstart/README.md b/examples/quickstart/README.md
new file mode 100644
index 0000000..0c43835
--- /dev/null
+++ b/examples/quickstart/README.md
@@ -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 john@acme.com 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).
diff --git a/examples/quickstart/index.ts b/examples/quickstart/index.ts
new file mode 100644
index 0000000..8568c57
--- /dev/null
+++ b/examples/quickstart/index.ts
@@ -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 john@acme.com 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();
diff --git a/examples/quickstart/package.json b/examples/quickstart/package.json
new file mode 100644
index 0000000..70abf75
--- /dev/null
+++ b/examples/quickstart/package.json
@@ -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"
+ }
+}
From 8519b7954fc08af55198266fb097664fad7820a3 Mon Sep 17 00:00:00 2001
From: David Crowe
Date: Thu, 23 Jul 2026 16:24:28 -0700
Subject: [PATCH 13/13] chore: regenerate lockfile after integrating cleanup
PRs
---
package-lock.json | 48 ++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 43 insertions(+), 5 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 62d7205..d11ca39 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6601,7 +6601,7 @@
},
"packages/explicabl": {
"name": "@gatewaystack/explicabl",
- "version": "0.0.8",
+ "version": "0.0.9",
"license": "MIT",
"dependencies": {
"express": "^4.22.0",
@@ -6611,11 +6611,17 @@
"@types/express": "^4.17.21",
"tsx": "^4.19.2",
"typescript": "^5.6.3"
+ },
+ "engines": {
+ "node": ">=18"
}
},
"packages/explicabl-core": {
"name": "@gatewaystack/observability-core",
- "version": "0.0.1"
+ "version": "0.0.1",
+ "engines": {
+ "node": ">=18"
+ }
},
"packages/identifiabl": {
"name": "@gatewaystack/identifiabl",
@@ -6623,8 +6629,10 @@
"license": "MIT",
"dependencies": {
"@gatewaystack/identifiabl-core": "^0.1.0",
- "@gatewaystack/request-context": "^0.0.6",
- "jose": "^5.0.0"
+ "@gatewaystack/request-context": "^0.0.6"
+ },
+ "engines": {
+ "node": ">=18"
},
"peerDependencies": {
"express": "^4.0.0 || ^5.0.0"
@@ -6636,6 +6644,9 @@
"license": "MIT",
"dependencies": {
"jose": "^6.1.0"
+ },
+ "engines": {
+ "node": ">=18"
}
},
"packages/identifiabl-core/node_modules/jose": {
@@ -6656,6 +6667,9 @@
"@types/express": "^4.17.21",
"typescript": "^5.6.3"
},
+ "engines": {
+ "node": ">=18"
+ },
"peerDependencies": {
"express": "^4.0.0 || ^5.0.0"
}
@@ -6666,6 +6680,9 @@
"license": "MIT",
"devDependencies": {
"typescript": "^5.6.3"
+ },
+ "engines": {
+ "node": ">=18"
}
},
"packages/proxyabl": {
@@ -6674,7 +6691,7 @@
"license": "MIT",
"dependencies": {
"@gatewaystack/proxyabl-core": "^0.1.1",
- "@gatewaystack/request-context": "0.0.6",
+ "@gatewaystack/request-context": "^0.0.6",
"express": "^4.22.0",
"express-rate-limit": "^8.2.2"
},
@@ -6683,6 +6700,9 @@
"jose": "^6.1.0",
"tsx": "^4.19.2",
"typescript": "^5.6.3"
+ },
+ "engines": {
+ "node": ">=18"
}
},
"packages/proxyabl-core": {
@@ -6694,6 +6714,9 @@
},
"devDependencies": {
"typescript": "^5.6.3"
+ },
+ "engines": {
+ "node": ">=18"
}
},
"packages/proxyabl-core/node_modules/jose": {
@@ -6721,6 +6744,9 @@
"license": "MIT",
"devDependencies": {
"typescript": "^5.6.3"
+ },
+ "engines": {
+ "node": ">=18"
}
},
"packages/transformabl": {
@@ -6733,6 +6759,9 @@
"devDependencies": {
"typescript": "^5.6.3"
},
+ "engines": {
+ "node": ">=18"
+ },
"peerDependencies": {
"express": "^4.0.0 || ^5.0.0"
}
@@ -6743,6 +6772,9 @@
"license": "MIT",
"devDependencies": {
"typescript": "^5.6.3"
+ },
+ "engines": {
+ "node": ">=18"
}
},
"packages/validatabl": {
@@ -6756,6 +6788,9 @@
"@types/express": "^4.17.21",
"typescript": "^5.6.3"
},
+ "engines": {
+ "node": ">=18"
+ },
"peerDependencies": {
"express": "^4.0.0 || ^5.0.0"
}
@@ -6766,6 +6801,9 @@
"license": "MIT",
"devDependencies": {
"typescript": "^5.6.3"
+ },
+ "engines": {
+ "node": ">=18"
}
},
"tools/echo-server": {