From the 2026-07-01 OSS security review. HIGH (ReDoS in transformabl-core email regex) fixed in #30. Remaining:
proxyabl-core/src/auth.ts:57 — verifyAccessToken doesn't pin algorithms (identifiabl-core correctly pins RS256) and audience is optional → alg-confusion surface + cross-service token replay when audience unset. Pin algorithms:['RS256']; require/warn on audience. Same optional-audience in tool-gateway.ts:394/759.
proxyabl-core/src/security.ts:150 → execute.ts:57 — SSRF DNS-rebinding TOCTOU: assertUrlSafe validates resolved IPs, then fetch re-resolves (IP not pinned to socket). Pin validated IP via custom undici dispatcher. Also add 100.64/10, 192.0.0/24, 198.18/15 to isPrivateIpv4.
validatabl-core/src/scopes.ts:30 — hasScope interpolates scope into new RegExp unescaped → regex injection/ReDoS + . matches any char (user.read ~ userXread). Use exact split(' ').includes(scope).
identifiabl-core & proxyabl-core — tokens with no exp accepted forever. Require exp (jose requiredClaims:['exp']).
limitabl-core budgetTracker — pre-flight check doesn't reserve estimated cost → concurrent requests all pass, spend exceeds maxSpend; also all limiters in-memory (per-instance) so effective limit = N×configured on multi-instance. Reserve-on-preflight + document/provide shared-store adapter.
proxyabl/src/middleware.ts:10 — req.path.split('/').pop() → '' on trailing slash → scope check skipped (fail-open). Derive tool robustly, deny when undetermined.
proxyabl/src/tool-gateway.ts — shipped default secrets ('dev-only-change-me') + non-constant-time bearer compare + X-Forwarded-For rate-limit key. Delete dead tool-gateway.ts from published build or gut defaults; timingSafeEqual; key limiter on req.ip.
transformabl-core/normalize.ts — incomplete invisible-char set (misses U+00AD soft-hyphen, bidi marks) + no NFKC → redaction bypass. Expand set + NFKC.
validatabl-core/decision.ts:65 — decision({}) fail-open (documented); policy.ts:140 resolveField walks arbitrary dotted paths incl. prototype.
Supply-chain: align jose version skew (identifiabl ^5 vs identifiabl-core ^6).
Done well (trust page): identifiabl-core JWT verification (RS256-pinned, aud mandatory), proxyabl-core SSRF guard (resolves all A/AAAA, IPv4-mapped-v6, redirect:manual), sanitizeCost + single-line JSON audit logging, clean supply chain (no install scripts, files:['dist']).
From the 2026-07-01 OSS security review. HIGH (ReDoS in transformabl-core email regex) fixed in #30. Remaining:
proxyabl-core/src/auth.ts:57 —
verifyAccessTokendoesn't pinalgorithms(identifiabl-core correctly pins RS256) and audience is optional → alg-confusion surface + cross-service token replay when audience unset. Pinalgorithms:['RS256']; require/warn on audience. Same optional-audience in tool-gateway.ts:394/759.proxyabl-core/src/security.ts:150 → execute.ts:57 — SSRF DNS-rebinding TOCTOU:
assertUrlSafevalidates resolved IPs, thenfetchre-resolves (IP not pinned to socket). Pin validated IP via custom undici dispatcher. Also add 100.64/10, 192.0.0/24, 198.18/15 to isPrivateIpv4.validatabl-core/src/scopes.ts:30 —
hasScopeinterpolates scope intonew RegExpunescaped → regex injection/ReDoS +.matches any char (user.read ~ userXread). Use exactsplit(' ').includes(scope).identifiabl-core & proxyabl-core — tokens with no
expaccepted forever. Require exp (joserequiredClaims:['exp']).limitabl-core budgetTracker — pre-flight check doesn't reserve estimated cost → concurrent requests all pass, spend exceeds maxSpend; also all limiters in-memory (per-instance) so effective limit = N×configured on multi-instance. Reserve-on-preflight + document/provide shared-store adapter.
proxyabl/src/middleware.ts:10 —
req.path.split('/').pop()→ '' on trailing slash → scope check skipped (fail-open). Derive tool robustly, deny when undetermined.proxyabl/src/tool-gateway.ts — shipped default secrets ('dev-only-change-me') + non-constant-time bearer compare + X-Forwarded-For rate-limit key. Delete dead tool-gateway.ts from published build or gut defaults; timingSafeEqual; key limiter on req.ip.
transformabl-core/normalize.ts — incomplete invisible-char set (misses U+00AD soft-hyphen, bidi marks) + no NFKC → redaction bypass. Expand set + NFKC.
validatabl-core/decision.ts:65 — decision({}) fail-open (documented); policy.ts:140 resolveField walks arbitrary dotted paths incl. prototype.
Supply-chain: align jose version skew (identifiabl ^5 vs identifiabl-core ^6).
Done well (trust page): identifiabl-core JWT verification (RS256-pinned, aud mandatory), proxyabl-core SSRF guard (resolves all A/AAAA, IPv4-mapped-v6, redirect:manual), sanitizeCost + single-line JSON audit logging, clean supply chain (no install scripts, files:['dist']).