@blamejs/core 0.7.50 → 0.7.52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,10 @@ upgrading across more than a few patches at a time.
8
8
 
9
9
  ## v0.7.x
10
10
 
11
+ - **0.7.52** (2026-05-05) — `b.guardGraphql` — GraphQL request-shape safety primitive (KIND="graphql-request"). Validates user-supplied GraphQL request bundles against the canonical query-shape DoS catalog before the framework hands the query to a schema-aware executor. Threat catalog: query depth bombs (N² query-shape DoS — caps at strict 8 / balanced 12 / permissive 24); alias-bomb breadth DoS (caps at 8/16/32 aliases per selection-set); introspection in production (`__schema` / `__type` schema-leak); batch query DoS (caps at 1/10/50 entries per array); persisted-query enforcement (refuse free-form queries when `persistedQueryPolicy: "require"`); operation-name allowlist drift; variable type confusion (operator declares `variableShapes: { id: "string", limit: "number" }`); oversized query / variable / total bytes; BIDI / null / control / zero-width universal refuse on the query string. Brace-counting query-shape walker handles strings + comments correctly without requiring a full GraphQL parser. Profiles: `strict` (refuse introspection + batch + shape-DoS), `balanced` (audit most, allow batch up to 10), `permissive` (universal-refuse class still refused; rest allow / audit). Postures: `hipaa` / `pci-dss` / `soc2` strict overlay; `gdpr` balanced overlay. Adaptive integration harness gains a KIND="graphql-request" dispatcher reading `ctx.graphqlRequest`. Auto-registers into `b.guardAll` as a STANDALONE_GUARD.
12
+
13
+ - **0.7.51** (2026-05-05) — `b.guardOauth` — OAuth flow-shape safety primitive (KIND="oauth-flow"). Validates user-supplied OAuth 2.x / OIDC authorization-code-flow parameter bundles before the framework's `b.auth.oauth` client exchanges them. Threat catalog: PKCE missing or non-S256 (RFC 7636 / OAuth 2.1 mandate; `plain` is the downgrade-attack class); state missing (RFC 6749 §10.12 CSRF); redirect_uri not in operator allowlist (exact-match per OAuth 2.1 — no prefix / wildcard / scheme drift); response_type allowlist drift (refuse implicit `token` deprecated in OAuth 2.1, require operator-allowed types); scope-token shape per RFC 6749 §3.3 (refuse non-printable / control / whitespace-other-than-space); issuer missing on callback (RFC 9207 IdP-mix-up defense — set `flow._isCallback = true` to enforce); authorization-code reuse via operator-supplied `seenCodeStore.hasSeen(code)` (RFC 6749 §10.5 replay class); excessive parameter / total bytes; BIDI / null / control / zero-width universal refuse. Profiles: `strict` (PKCE S256, state required, redirect_uri exact-match, RFC 9207 iss required), `balanced` (PKCE any method, state + redirect_uri allowlist required, iss audited), `permissive` (universal-refuse class still refused; rest audit). Postures: `hipaa` / `pci-dss` / `soc2` strict overlay; `gdpr` balanced overlay. Adaptive integration harness gains a KIND="oauth-flow" dispatcher reading `ctx.oauthFlow`. Auto-registers into `b.guardAll` as a STANDALONE_GUARD.
14
+
11
15
  - **0.7.50** (2026-05-05) — `b.guardJwt` — JWT identifier-safety primitive (KIND="identifier"). Validates user-supplied JWT compact-serialization strings against the canonical CVE-class refuse list before hand-off to a verifier — `b.guardJwt` never replaces signature verification, it reduces the input space the verifier sees. Threat catalog: shape malformation; `alg=none` (CVE-2015-9235 jsonwebtoken / CVE-2018-0114 java-jwt) — universally refused at every profile; alg-allowlist drift (PQC-first default: ML-DSA / SLH-DSA / EdDSA / ES* / RS* / PS*); `kid` path-traversal (operator `keyResolver` path-injection class — e.g. `kid: "../etc/passwd"` would escape a key-directory `fs.readFile(keyDir + kid)` resolver); `typ` confusion (non-JWT-shape media-type tokens coerced into the slot); oversized header / payload / signature segment defense (decompression bomb + parser DoS); `exp` / `nbf` / `iat` sanity (past `exp` = replay; far-future `nbf` / `iat` = clock-skew / attacker-shaped); required-claims enforcement (default `iss` / `exp` / `iat` at strict); unknown `crit` field refuse (RFC 7515 §4.1.11 — operator MUST refuse crit values it doesn't understand); BIDI / null / control / zero-width universal refuse. **`b.guardJwt.kidSafe(kid)`** is the documented contract for operator `keyResolver` implementations: throws on traversal indicators or control bytes, returns the validated kid on success. Profiles: `strict` (refuse everything), `balanced` (refuse alg=none / kid-traversal / unknown-crit; audit the rest), `permissive` (universal-refuse class still refused). Postures: `hipaa` / `pci-dss` / `soc2` strict overlay; `gdpr` balanced overlay. Auto-registers into `b.guardAll` as a STANDALONE_GUARD.
12
16
 
13
17
  - **0.7.49** (2026-05-05) — `b.middleware.headers(opts)` — inbound HTTP header threat-detection middleware. Sits at the top of the request lifecycle. Threat catalog: `header-name-shape` (header name not a valid RFC 9110 §5.1 token); `header-value-control-byte` (CR / LF / NUL inside any header value — header-injection defense in depth on top of Node's rejection); `header-count-cap` (default 100 inbound headers max); `header-value-cap` (default 8 KiB per value); `smuggling-cl-te` (RFC 9112 §6.1 — both Content-Length and Transfer-Encoding present, the canonical CL.TE / TE.CL request-smuggling vector); `smuggling-cl-multi` / `smuggling-te-multi` (multiple values for either header — proxy-desync class); `deprecated-trust-header` (X-Forwarded-For / -Proto / -Host / -Port / X-Real-IP present without operator-supplied `trustProxy: true` opt — operators should adopt RFC 7239 `Forwarded`). On `mode: "enforce"` + `refuseOnHigh` (default), refuses with HTTP 400 + JSON body listing detected high-severity issues; emits one audit row per issue regardless of mode. Complements the existing per-route smuggling defense in `b.middleware.bodyParser` by running the same check at the top of the chain (covers GET / HEAD requests that don't body-parse).
package/index.js CHANGED
@@ -116,6 +116,8 @@ var guardCidr = require("./lib/guard-cidr");
116
116
  var guardTime = require("./lib/guard-time");
117
117
  var guardMime = require("./lib/guard-mime");
118
118
  var guardJwt = require("./lib/guard-jwt");
119
+ var guardOauth = require("./lib/guard-oauth");
120
+ var guardGraphql = require("./lib/guard-graphql");
119
121
  var guardAll = require("./lib/guard-all");
120
122
  var ssrfGuard = require("./lib/ssrf-guard");
121
123
  var authHeader = require("./lib/auth-header");
@@ -263,6 +265,8 @@ module.exports = {
263
265
  guardTime: guardTime,
264
266
  guardMime: guardMime,
265
267
  guardJwt: guardJwt,
268
+ guardOauth: guardOauth,
269
+ guardGraphql: guardGraphql,
266
270
  guardAll: guardAll,
267
271
  ssrfGuard: ssrfGuard,
268
272
  authHeader: authHeader,
@@ -297,6 +297,23 @@ var GuardMimeError = defineClass("GuardMimeError", { alwaysPermane
297
297
  // fields (RFC 7515 §4.1.11), BIDI / null / control / zero-width
298
298
  // universal refuse. alwaysPermanent.
299
299
  var GuardJwtError = defineClass("GuardJwtError", { alwaysPermanent: true });
300
+ // GuardOauthError covers OAuth flow-shape violations: PKCE missing /
301
+ // non-S256 (downgrade-attack class), state missing (RFC 6749 §10.12
302
+ // CSRF class), redirect_uri not in operator allowlist (exact-match
303
+ // per OAuth 2.1), response_type allowlist drift, scope-token shape
304
+ // (RFC 6749 §3.3), issuer missing on callback (RFC 9207 IdP-mix-up),
305
+ // authorization-code reuse (RFC 6749 §10.5), oversized parameter,
306
+ // BIDI / null / control / zero-width universal refuse.
307
+ // alwaysPermanent.
308
+ var GuardOauthError = defineClass("GuardOauthError", { alwaysPermanent: true });
309
+ // GuardGraphqlError covers GraphQL request-shape violations: query
310
+ // depth bombs (N² query-shape DoS), alias-bomb breadth DoS,
311
+ // introspection in production, batch-query DoS, persisted-query
312
+ // enforcement, operation-name allowlist drift, variable type
313
+ // confusion, oversized query / variable / total bytes, BIDI / null /
314
+ // control / zero-width universal refuse on the query string.
315
+ // alwaysPermanent.
316
+ var GuardGraphqlError = defineClass("GuardGraphqlError", { alwaysPermanent: true });
300
317
  // DoraError covers DORA Article 17 incident-reporting workflow errors
301
318
  // (classification refusal, report-shape validation, ESA-template
302
319
  // generation, audit-chain integration). Permanent — these are
@@ -362,6 +379,8 @@ module.exports = {
362
379
  GuardTimeError: GuardTimeError,
363
380
  GuardMimeError: GuardMimeError,
364
381
  GuardJwtError: GuardJwtError,
382
+ GuardOauthError: GuardOauthError,
383
+ GuardGraphqlError: GuardGraphqlError,
365
384
  DoraError: DoraError,
366
385
  ComplianceError: ComplianceError,
367
386
  SmtpPolicyError: SmtpPolicyError,
package/lib/guard-all.js CHANGED
@@ -95,6 +95,8 @@ var STANDALONE_GUARDS = [
95
95
  require("./guard-time"),
96
96
  require("./guard-mime"),
97
97
  require("./guard-jwt"),
98
+ require("./guard-oauth"),
99
+ require("./guard-graphql"),
98
100
  ];
99
101
 
100
102
  // Framework-wide profile + posture vocabulary that every guard MUST
@@ -0,0 +1,461 @@
1
+ "use strict";
2
+ /**
3
+ * guard-graphql — GraphQL request-shape safety primitive
4
+ * (b.guardGraphql).
5
+ *
6
+ * Validates user-supplied GraphQL request bundles against the
7
+ * canonical query-shape DoS catalog before the framework hands the
8
+ * query to a schema-aware executor. KIND="graphql-request" — consumes
9
+ * `ctx.graphqlRequest` shape: { query, operationName?, variables? }.
10
+ *
11
+ * Threat catalog:
12
+ * - Query depth bombs — deeply-nested selection sets multiply N²
13
+ * against schema depth, bypassing field-level rate limits.
14
+ * - Query breadth / alias bombs — same field repeated under
15
+ * different aliases (`a:friend b:friend c:friend ...`) bypasses
16
+ * per-field limits.
17
+ * - Variable type confusion — variables passed as the wrong shape
18
+ * (string for ID expecting Int, object for scalar). Many
19
+ * executors coerce silently; the guard refuses non-shape-matching
20
+ * types when the operator declares variable shapes.
21
+ * - Introspection in production — `__schema` / `__type` queries
22
+ * leak schema details; refused unless operator opts in.
23
+ * - Batch query DoS — operators supporting [{},{}] batch arrays
24
+ * get N requests for one HTTP hit; the guard caps batch length.
25
+ * - Persisted-query opt-in — when operatorRequiresPersistedQuery,
26
+ * refuse free-form queries that don't carry a persisted-query
27
+ * hash extension.
28
+ * - Operation-name allowlist — operator may pin operationName to
29
+ * a whitelist of named operations (denylist for ad-hoc queries).
30
+ * - Excessive query / variable / total byte length — parser DoS.
31
+ * - BIDI / null / control / zero-width universal refuse on the
32
+ * query string.
33
+ *
34
+ * var rv = b.guardGraphql.validate(req, { profile: "strict" });
35
+ * var g = b.guardGraphql.gate({ profile: "strict" });
36
+ */
37
+
38
+ var codepointClass = require("./codepoint-class");
39
+ var lazyRequire = require("./lazy-require");
40
+ var gateContract = require("./gate-contract");
41
+ var C = require("./constants");
42
+ var numericBounds = require("./numeric-bounds");
43
+ var { GuardGraphqlError } = require("./framework-error");
44
+
45
+ var observability = lazyRequire(function () { return require("./observability"); });
46
+ void observability;
47
+
48
+ var _err = GuardGraphqlError.factory;
49
+
50
+ // ---- Profile presets ----
51
+
52
+ var PROFILES = Object.freeze({
53
+ "strict": {
54
+ bidiPolicy: "reject",
55
+ controlPolicy: "reject",
56
+ nullBytePolicy: "reject",
57
+ zeroWidthPolicy: "reject",
58
+ introspectionPolicy: "reject",
59
+ persistedQueryPolicy: "audit",
60
+ operationNamePolicy: "audit",
61
+ batchPolicy: "reject",
62
+ aliasBombPolicy: "reject",
63
+ depthPolicy: "reject",
64
+ variableShapePolicy: "reject",
65
+ maxDepth: 8, // allow:raw-byte-literal — selection-set depth ceiling
66
+ maxAliasesPerSelection: 8, // allow:raw-byte-literal — alias breadth ceiling
67
+ maxBatchSize: 1, // allow:raw-byte-literal — strict refuses batch
68
+ maxQueryBytes: C.BYTES.kib(8),
69
+ maxVariableBytes: C.BYTES.kib(8),
70
+ maxBytes: C.BYTES.kib(32),
71
+ maxRuntimeMs: C.TIME.seconds(2),
72
+ },
73
+ "balanced": {
74
+ bidiPolicy: "reject",
75
+ controlPolicy: "reject",
76
+ nullBytePolicy: "reject",
77
+ zeroWidthPolicy: "reject",
78
+ introspectionPolicy: "audit",
79
+ persistedQueryPolicy: "audit",
80
+ operationNamePolicy: "audit",
81
+ batchPolicy: "audit",
82
+ aliasBombPolicy: "audit",
83
+ depthPolicy: "audit",
84
+ variableShapePolicy: "audit",
85
+ maxDepth: 12, // allow:raw-byte-literal — selection-set depth ceiling
86
+ maxAliasesPerSelection: 16, // allow:raw-byte-literal — alias breadth ceiling
87
+ maxBatchSize: 10, // allow:raw-byte-literal — batch size ceiling
88
+ maxQueryBytes: C.BYTES.kib(16),
89
+ maxVariableBytes: C.BYTES.kib(16),
90
+ maxBytes: C.BYTES.kib(64),
91
+ maxRuntimeMs: C.TIME.seconds(2),
92
+ },
93
+ "permissive": {
94
+ bidiPolicy: "reject", // BIDI refused at every profile
95
+ controlPolicy: "reject", // controls refused at every profile
96
+ nullBytePolicy: "reject", // null refused at every profile
97
+ zeroWidthPolicy: "reject", // zero-width refused at every profile
98
+ introspectionPolicy: "allow",
99
+ persistedQueryPolicy: "allow",
100
+ operationNamePolicy: "allow",
101
+ batchPolicy: "allow",
102
+ aliasBombPolicy: "audit",
103
+ depthPolicy: "audit",
104
+ variableShapePolicy: "audit",
105
+ maxDepth: 24, // allow:raw-byte-literal — selection-set depth ceiling
106
+ maxAliasesPerSelection: 32, // allow:raw-byte-literal — alias breadth ceiling
107
+ maxBatchSize: 50, // allow:raw-byte-literal — batch size ceiling
108
+ maxQueryBytes: C.BYTES.kib(64),
109
+ maxVariableBytes: C.BYTES.kib(64),
110
+ maxBytes: C.BYTES.kib(256),
111
+ maxRuntimeMs: C.TIME.seconds(2),
112
+ },
113
+ });
114
+
115
+ var DEFAULTS = Object.freeze(Object.assign({}, PROFILES["strict"], {
116
+ mode: "enforce",
117
+ }));
118
+
119
+ var COMPLIANCE_POSTURES = Object.freeze({
120
+ "hipaa": Object.assign({}, PROFILES["strict"], {
121
+ forensicSnippetBytes: C.BYTES.bytes(512),
122
+ }),
123
+ "pci-dss": Object.assign({}, PROFILES["strict"], {
124
+ forensicSnippetBytes: C.BYTES.bytes(512),
125
+ }),
126
+ "gdpr": Object.assign({}, PROFILES["balanced"], {
127
+ forensicSnippetBytes: C.BYTES.bytes(256),
128
+ }),
129
+ "soc2": Object.assign({}, PROFILES["strict"], {
130
+ forensicSnippetBytes: C.BYTES.bytes(1024),
131
+ }),
132
+ });
133
+
134
+ function _resolveOpts(opts) {
135
+ return gateContract.resolveProfileAndPosture(opts, {
136
+ profiles: PROFILES,
137
+ compliancePostures: COMPLIANCE_POSTURES,
138
+ defaults: DEFAULTS,
139
+ errorClass: GuardGraphqlError,
140
+ errCodePrefix: "graphql",
141
+ });
142
+ }
143
+
144
+ // _measureQueryShape — walks the query string and computes
145
+ // brace-depth + per-selection-set alias counts using simple paren
146
+ // counting. Not a full GraphQL parser (operator runs the schema-
147
+ // aware parser downstream); the heuristic catches DoS shapes
148
+ // without a full lex/parse.
149
+ function _measureQueryShape(query) {
150
+ var maxDepth = 0;
151
+ var maxAliases = 0;
152
+ var depth = 0;
153
+ var inString = false;
154
+ var inComment = false;
155
+ var aliasCounts = [0]; // per-depth alias counter
156
+ for (var i = 0; i < query.length; i += 1) {
157
+ var c = query.charAt(i);
158
+ if (inComment) {
159
+ if (c === "\n") inComment = false;
160
+ continue;
161
+ }
162
+ if (inString) {
163
+ if (c === '"' && query.charAt(i - 1) !== "\\") inString = false;
164
+ continue;
165
+ }
166
+ if (c === '"') { inString = true; continue; }
167
+ if (c === "#") { inComment = true; continue; }
168
+ if (c === "{") {
169
+ depth += 1;
170
+ if (depth > maxDepth) maxDepth = depth;
171
+ aliasCounts.push(0);
172
+ } else if (c === "}") {
173
+ // Capture the current selection-set's alias count before popping
174
+ // — otherwise we lose the per-block max when the block closes.
175
+ var current = aliasCounts[aliasCounts.length - 1] || 0;
176
+ if (current > maxAliases) maxAliases = current;
177
+ depth -= 1;
178
+ aliasCounts.pop();
179
+ if (depth < 0) depth = 0;
180
+ } else if (c === ":") {
181
+ // Alias indicator — `alias: field`. Increment the current depth's
182
+ // counter when the char before `:` looks like an identifier.
183
+ var prev = i > 0 ? query.charAt(i - 1) : "";
184
+ if (/[A-Za-z0-9_]/.test(prev) && depth > 0) {
185
+ aliasCounts[depth] += 1;
186
+ }
187
+ }
188
+ }
189
+ // Final sweep covers any unclosed selection-sets (operator-supplied
190
+ // syntactically-invalid queries).
191
+ for (var ai = 0; ai < aliasCounts.length; ai += 1) {
192
+ if (aliasCounts[ai] > maxAliases) maxAliases = aliasCounts[ai];
193
+ }
194
+ return { maxDepth: maxDepth, maxAliases: maxAliases };
195
+ }
196
+
197
+ function _detectIssues(req, opts) {
198
+ var issues = [];
199
+ if (!req || typeof req !== "object") {
200
+ return [{ kind: "bad-input", severity: "high",
201
+ ruleId: "graphql.bad-input",
202
+ snippet: "graphql request is not an object" }];
203
+ }
204
+
205
+ // Batch handling.
206
+ if (Array.isArray(req)) {
207
+ if (opts.batchPolicy !== "allow") {
208
+ if (opts.batchPolicy === "reject" || req.length > opts.maxBatchSize) {
209
+ issues.push({
210
+ kind: "batch-size",
211
+ severity: opts.batchPolicy === "reject" ? "high" : "warn",
212
+ ruleId: "graphql.batch-size",
213
+ snippet: "GraphQL batch length " + req.length + " exceeds " +
214
+ "maxBatchSize " + opts.maxBatchSize +
215
+ (opts.batchPolicy === "reject" ?
216
+ " (strict refuses any batch)" : ""),
217
+ });
218
+ if (opts.batchPolicy === "reject") return issues;
219
+ }
220
+ }
221
+ // Apply per-request validation to each entry.
222
+ for (var bi = 0; bi < req.length; bi += 1) {
223
+ var sub = _detectIssues(req[bi], opts);
224
+ for (var si = 0; si < sub.length; si += 1) {
225
+ issues.push(Object.assign({}, sub[si], {
226
+ snippet: "[batch[" + bi + "]] " + sub[si].snippet,
227
+ }));
228
+ }
229
+ }
230
+ return issues;
231
+ }
232
+
233
+ // Total-bytes cap.
234
+ try {
235
+ var totalBytes = Buffer.byteLength(JSON.stringify(req), "utf8");
236
+ if (totalBytes > opts.maxBytes) {
237
+ return [{ kind: "request-cap", severity: "high",
238
+ ruleId: "graphql.request-cap",
239
+ snippet: "graphql request " + totalBytes + " bytes " +
240
+ "exceeds maxBytes " + opts.maxBytes }];
241
+ }
242
+ } catch (_e) { /* unstringifiable surfaces below */ }
243
+
244
+ if (typeof req.query !== "string" || req.query.length === 0) {
245
+ issues.push({
246
+ kind: "query-missing", severity: "high",
247
+ ruleId: "graphql.query-missing",
248
+ snippet: "graphql request missing `query` string",
249
+ });
250
+ return issues;
251
+ }
252
+ if (Buffer.byteLength(req.query, "utf8") > opts.maxQueryBytes) {
253
+ issues.push({
254
+ kind: "query-cap", severity: "high",
255
+ ruleId: "graphql.query-cap",
256
+ snippet: "query " + req.query.length + " bytes exceeds " +
257
+ "maxQueryBytes " + opts.maxQueryBytes,
258
+ });
259
+ return issues;
260
+ }
261
+
262
+ // Codepoint-class threats on the query.
263
+ var charThreats = codepointClass.detectCharThreats(req.query, opts, "graphql");
264
+ for (var ci = 0; ci < charThreats.length; ci += 1) issues.push(charThreats[ci]);
265
+
266
+ // Variables byte cap.
267
+ if (req.variables !== undefined) {
268
+ try {
269
+ var varBytes = Buffer.byteLength(JSON.stringify(req.variables), "utf8");
270
+ if (varBytes > opts.maxVariableBytes) {
271
+ issues.push({
272
+ kind: "variables-cap", severity: "high",
273
+ ruleId: "graphql.variables-cap",
274
+ snippet: "variables exceed maxVariableBytes " + opts.maxVariableBytes,
275
+ });
276
+ }
277
+ } catch (_e) { /* unstringifiable variables */ }
278
+ }
279
+
280
+ // Introspection.
281
+ if (opts.introspectionPolicy !== "allow") {
282
+ if (req.query.indexOf("__schema") !== -1 ||
283
+ req.query.indexOf("__type") !== -1) {
284
+ issues.push({
285
+ kind: "introspection",
286
+ severity: opts.introspectionPolicy === "reject" ? "high" : "warn",
287
+ ruleId: "graphql.introspection",
288
+ snippet: "query contains `__schema` / `__type` introspection — " +
289
+ "leaks schema details in production",
290
+ });
291
+ }
292
+ }
293
+
294
+ // Persisted-query enforcement.
295
+ if (opts.persistedQueryPolicy === "require") {
296
+ var ext = req.extensions;
297
+ var hasPersisted = ext && ext.persistedQuery &&
298
+ typeof ext.persistedQuery.sha256Hash === "string";
299
+ if (!hasPersisted) {
300
+ issues.push({
301
+ kind: "persisted-query-missing", severity: "high",
302
+ ruleId: "graphql.persisted-query-missing",
303
+ snippet: "persistedQueryPolicy is `require` but request carries " +
304
+ "no extensions.persistedQuery.sha256Hash",
305
+ });
306
+ }
307
+ }
308
+
309
+ // Operation-name allowlist.
310
+ if (Array.isArray(opts.allowedOperations) &&
311
+ opts.operationNamePolicy !== "allow") {
312
+ if (typeof req.operationName !== "string" ||
313
+ opts.allowedOperations.indexOf(req.operationName) === -1) {
314
+ issues.push({
315
+ kind: "operation-not-allowed",
316
+ severity: opts.operationNamePolicy === "reject" ? "high" : "warn",
317
+ ruleId: "graphql.operation-not-allowed",
318
+ snippet: "operationName `" + (req.operationName || "<missing>") +
319
+ "` not in operator allowlist",
320
+ });
321
+ }
322
+ }
323
+
324
+ // Query shape — depth + alias bombs.
325
+ var shape = _measureQueryShape(req.query);
326
+ if (opts.depthPolicy !== "allow" && shape.maxDepth > opts.maxDepth) {
327
+ issues.push({
328
+ kind: "depth-exceeded",
329
+ severity: opts.depthPolicy === "reject" ? "high" : "warn",
330
+ ruleId: "graphql.depth-exceeded",
331
+ snippet: "query depth " + shape.maxDepth + " exceeds maxDepth " +
332
+ opts.maxDepth + " — N² query-shape DoS class",
333
+ });
334
+ }
335
+ if (opts.aliasBombPolicy !== "allow" &&
336
+ shape.maxAliases > opts.maxAliasesPerSelection) {
337
+ issues.push({
338
+ kind: "alias-bomb",
339
+ severity: opts.aliasBombPolicy === "reject" ? "high" : "warn",
340
+ ruleId: "graphql.alias-bomb",
341
+ snippet: "selection-set alias count " + shape.maxAliases +
342
+ " exceeds maxAliasesPerSelection " +
343
+ opts.maxAliasesPerSelection +
344
+ " — alias-bomb breadth-DoS class",
345
+ });
346
+ }
347
+
348
+ // Variable shape (operator-declared via opts.variableShapes).
349
+ if (opts.variableShapePolicy !== "allow" &&
350
+ opts.variableShapes && typeof opts.variableShapes === "object" &&
351
+ req.variables && typeof req.variables === "object") {
352
+ var keys = Object.keys(opts.variableShapes);
353
+ for (var ki = 0; ki < keys.length; ki += 1) {
354
+ var k = keys[ki];
355
+ var expected = opts.variableShapes[k];
356
+ var actual = req.variables[k];
357
+ if (actual === undefined) continue;
358
+ if (typeof actual !== expected) {
359
+ issues.push({
360
+ kind: "variable-type-confusion",
361
+ severity: opts.variableShapePolicy === "reject" ? "high" : "warn",
362
+ ruleId: "graphql.variable-type-confusion",
363
+ snippet: "variable `" + k + "` is " + typeof actual +
364
+ ", expected " + expected,
365
+ });
366
+ }
367
+ }
368
+ }
369
+
370
+ return issues;
371
+ }
372
+
373
+ function validate(input, opts) {
374
+ opts = _resolveOpts(opts);
375
+ numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
376
+ ["maxBytes", "maxQueryBytes", "maxVariableBytes",
377
+ "maxDepth", "maxAliasesPerSelection", "maxBatchSize"],
378
+ "guardGraphql.validate", GuardGraphqlError, "graphql.bad-opt");
379
+ return gateContract.aggregateIssues(_detectIssues(input, opts));
380
+ }
381
+
382
+ function sanitize(input, opts) {
383
+ opts = _resolveOpts(opts);
384
+ var issues = _detectIssues(input, opts);
385
+ for (var i = 0; i < issues.length; i += 1) {
386
+ if (issues[i].severity === "critical" || issues[i].severity === "high") {
387
+ throw _err(issues[i].ruleId || "graphql.refused",
388
+ "guardGraphql.sanitize: " + issues[i].snippet);
389
+ }
390
+ }
391
+ return input;
392
+ }
393
+
394
+ function gate(opts) {
395
+ opts = _resolveOpts(opts);
396
+ return gateContract.buildGuardGate(
397
+ opts.name || "guardGraphql:" + (opts.profile || "default"),
398
+ opts,
399
+ async function (ctx) {
400
+ var req = ctx && (ctx.graphqlRequest || ctx.gql);
401
+ if (!req) return { ok: true, action: "serve" };
402
+ var rv = validate(req, opts);
403
+ if (rv.issues.length === 0) return { ok: true, action: "serve" };
404
+ var hasCritical = rv.issues.some(function (i) {
405
+ return i.severity === "critical";
406
+ });
407
+ var hasHigh = rv.issues.some(function (i) {
408
+ return i.severity === "high";
409
+ });
410
+ if (!hasCritical && !hasHigh) {
411
+ return { ok: true, action: "audit-only", issues: rv.issues };
412
+ }
413
+ return { ok: false, action: "refuse", issues: rv.issues };
414
+ });
415
+ }
416
+
417
+ var buildProfile = gateContract.makeProfileBuilder(PROFILES);
418
+
419
+ function compliancePosture(name) {
420
+ return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES,
421
+ _err, "graphql");
422
+ }
423
+
424
+ var _gqlRulePacks = gateContract.makeRulePackLoader(GuardGraphqlError, "graphql");
425
+ var loadRulePack = _gqlRulePacks.load;
426
+
427
+ module.exports = {
428
+ // ---- guard-* family registry exports ----
429
+ NAME: "graphql",
430
+ KIND: "graphql-request",
431
+ INTEGRATION_FIXTURES: Object.freeze({
432
+ kind: "graphql-request",
433
+ benignBytes: Buffer.from(JSON.stringify({
434
+ query: "query GetMe { me { id name } }",
435
+ operationName: "GetMe",
436
+ }), "utf8"),
437
+ hostileBytes: Buffer.from(JSON.stringify({
438
+ query: "query Inspect { __schema { types { name } } }",
439
+ operationName: "Inspect",
440
+ }), "utf8"),
441
+ benignGraphqlRequest: {
442
+ query: "query GetMe { me { id name } }",
443
+ operationName: "GetMe",
444
+ },
445
+ hostileGraphqlRequest: {
446
+ query: "query Inspect { __schema { types { name } } }",
447
+ operationName: "Inspect",
448
+ },
449
+ }),
450
+ // ---- primitive surface ----
451
+ validate: validate,
452
+ sanitize: sanitize,
453
+ gate: gate,
454
+ buildProfile: buildProfile,
455
+ compliancePosture: compliancePosture,
456
+ loadRulePack: loadRulePack,
457
+ PROFILES: PROFILES,
458
+ DEFAULTS: DEFAULTS,
459
+ COMPLIANCE_POSTURES: COMPLIANCE_POSTURES,
460
+ GuardGraphqlError: GuardGraphqlError,
461
+ };
@@ -0,0 +1,401 @@
1
+ "use strict";
2
+ /**
3
+ * guard-oauth — OAuth flow-shape safety primitive (b.guardOauth).
4
+ *
5
+ * Validates user-supplied OAuth 2.x / OIDC authorization-code-flow
6
+ * parameter bundles before the framework's b.auth.oauth client
7
+ * exchanges them. KIND="oauth-flow" — consumes ctx.oauthFlow.
8
+ *
9
+ * Threat catalog:
10
+ * - PKCE missing or non-S256 — RFC 7636 mandates code_verifier;
11
+ * OAuth 2.1 mandates S256 (no plain). The plaintext "plain"
12
+ * method is downgrade-attack class.
13
+ * - state missing / replayed — RFC 6749 §10.12 + §10.14; without
14
+ * state-binding the flow is open to CSRF.
15
+ * - redirect_uri not in allowlist — RFC 6749 §3.1.2 + OAuth 2.1
16
+ * mandate exact-match (no prefix / wildcard / scheme drift).
17
+ * - response_type not in allowlist — refuse "token" implicit flow
18
+ * (deprecated in OAuth 2.1) and "id_token" outside OIDC; require
19
+ * operator-allowed types.
20
+ * - scope tampering — refuse scope values containing whitespace
21
+ * other than space (RFC 6749 §3.3) or non-printable bytes.
22
+ * - issuer (iss) missing on callback — RFC 9207 mandates iss
23
+ * parameter on authorization response to defeat the IdP-mix-up
24
+ * attack.
25
+ * - code reuse — operator-supplied seenCodeStore detects
26
+ * authorization-code replay (RFC 6749 §10.5).
27
+ * - excessive parameter / value length — defense against parser
28
+ * DoS and decompression-bomb-shaped clients.
29
+ * - BIDI / null / control / zero-width universal refuse.
30
+ *
31
+ * var rv = b.guardOauth.validate({ redirect_uri, state, ... },
32
+ * { profile: "strict" });
33
+ * var g = b.guardOauth.gate({ profile: "strict" });
34
+ */
35
+
36
+ var codepointClass = require("./codepoint-class");
37
+ var lazyRequire = require("./lazy-require");
38
+ var gateContract = require("./gate-contract");
39
+ var C = require("./constants");
40
+ var numericBounds = require("./numeric-bounds");
41
+ var { GuardOauthError } = require("./framework-error");
42
+
43
+ var observability = lazyRequire(function () { return require("./observability"); });
44
+ void observability;
45
+
46
+ var _err = GuardOauthError.factory;
47
+
48
+ var SCOPE_TOKEN_RE = /^[\x21\x23-\x5b\x5d-\x7e]+$/; // allow:raw-byte-literal — RFC 6749 §3.3 scope-token charset
49
+ var DEFAULT_RESPONSE_TYPES = Object.freeze(["code"]);
50
+
51
+ // ---- Profile presets ----
52
+
53
+ var PROFILES = Object.freeze({
54
+ "strict": {
55
+ bidiPolicy: "reject",
56
+ controlPolicy: "reject",
57
+ nullBytePolicy: "reject",
58
+ zeroWidthPolicy: "reject",
59
+ pkcePolicy: "require-s256",
60
+ statePolicy: "require",
61
+ redirectUriPolicy: "require-exact-allowlist",
62
+ responseTypePolicy: "require-allowlist",
63
+ scopeTamperingPolicy: "reject",
64
+ issuerOnCallbackPolicy: "require", // RFC 9207
65
+ codeReusePolicy: "reject",
66
+ allowedResponseTypes: DEFAULT_RESPONSE_TYPES,
67
+ maxParamBytes: C.BYTES.kib(2),
68
+ maxBytes: C.BYTES.kib(8),
69
+ maxRuntimeMs: C.TIME.seconds(2),
70
+ },
71
+ "balanced": {
72
+ bidiPolicy: "reject",
73
+ controlPolicy: "reject",
74
+ nullBytePolicy: "reject",
75
+ zeroWidthPolicy: "reject",
76
+ pkcePolicy: "require-any", // S256 or plain
77
+ statePolicy: "require",
78
+ redirectUriPolicy: "require-exact-allowlist",
79
+ responseTypePolicy: "require-allowlist",
80
+ scopeTamperingPolicy: "reject",
81
+ issuerOnCallbackPolicy: "audit",
82
+ codeReusePolicy: "reject",
83
+ allowedResponseTypes: Object.freeze(["code", "code id_token"]),
84
+ maxParamBytes: C.BYTES.kib(2),
85
+ maxBytes: C.BYTES.kib(8),
86
+ maxRuntimeMs: C.TIME.seconds(2),
87
+ },
88
+ "permissive": {
89
+ bidiPolicy: "reject", // BIDI refused at every profile
90
+ controlPolicy: "reject", // controls refused at every profile
91
+ nullBytePolicy: "reject", // null refused at every profile
92
+ zeroWidthPolicy: "reject", // zero-width refused at every profile
93
+ pkcePolicy: "audit",
94
+ statePolicy: "audit",
95
+ redirectUriPolicy: "audit",
96
+ responseTypePolicy: "audit",
97
+ scopeTamperingPolicy: "reject", // scope tampering refused at every profile
98
+ issuerOnCallbackPolicy: "audit",
99
+ codeReusePolicy: "reject", // code reuse refused at every profile
100
+ allowedResponseTypes: null,
101
+ maxParamBytes: C.BYTES.kib(4),
102
+ maxBytes: C.BYTES.kib(16),
103
+ maxRuntimeMs: C.TIME.seconds(2),
104
+ },
105
+ });
106
+
107
+ var DEFAULTS = Object.freeze(Object.assign({}, PROFILES["strict"], {
108
+ mode: "enforce",
109
+ }));
110
+
111
+ var COMPLIANCE_POSTURES = Object.freeze({
112
+ "hipaa": Object.assign({}, PROFILES["strict"], {
113
+ forensicSnippetBytes: C.BYTES.bytes(256),
114
+ }),
115
+ "pci-dss": Object.assign({}, PROFILES["strict"], {
116
+ forensicSnippetBytes: C.BYTES.bytes(256),
117
+ }),
118
+ "gdpr": Object.assign({}, PROFILES["balanced"], {
119
+ forensicSnippetBytes: C.BYTES.bytes(128),
120
+ }),
121
+ "soc2": Object.assign({}, PROFILES["strict"], {
122
+ forensicSnippetBytes: C.BYTES.bytes(512),
123
+ }),
124
+ });
125
+
126
+ function _resolveOpts(opts) {
127
+ return gateContract.resolveProfileAndPosture(opts, {
128
+ profiles: PROFILES,
129
+ compliancePostures: COMPLIANCE_POSTURES,
130
+ defaults: DEFAULTS,
131
+ errorClass: GuardOauthError,
132
+ errCodePrefix: "oauth",
133
+ });
134
+ }
135
+
136
+ function _detectIssues(flow, opts) {
137
+ var issues = [];
138
+ if (!flow || typeof flow !== "object") {
139
+ return [{ kind: "bad-input", severity: "high",
140
+ ruleId: "oauth.bad-input",
141
+ snippet: "oauth flow is not an object" }];
142
+ }
143
+
144
+ // Total-bytes cap — JSON-stringify proxy for input size.
145
+ try {
146
+ var serialized = JSON.stringify(flow);
147
+ if (Buffer.byteLength(serialized, "utf8") > opts.maxBytes) {
148
+ return [{ kind: "flow-cap", severity: "high",
149
+ ruleId: "oauth.flow-cap",
150
+ snippet: "oauth flow exceeds maxBytes " + opts.maxBytes }];
151
+ }
152
+ } catch (_e) { /* unstringifiable — flagged below */ }
153
+
154
+ // Codepoint-class threats applied to every string value at the
155
+ // top-level (operator nests via `flow` so this catches the canonical
156
+ // OAuth params).
157
+ var keys = Object.keys(flow);
158
+ for (var ki = 0; ki < keys.length; ki += 1) {
159
+ var v = flow[keys[ki]];
160
+ if (typeof v !== "string") continue;
161
+ if (Buffer.byteLength(v, "utf8") > opts.maxParamBytes) {
162
+ issues.push({
163
+ kind: "param-cap", severity: "high",
164
+ ruleId: "oauth.param-cap",
165
+ snippet: "oauth param `" + keys[ki] + "` exceeds maxParamBytes " +
166
+ opts.maxParamBytes,
167
+ });
168
+ continue;
169
+ }
170
+ var charThreats = codepointClass.detectCharThreats(v, opts, "oauth");
171
+ for (var ci = 0; ci < charThreats.length; ci += 1) {
172
+ issues.push(Object.assign({}, charThreats[ci], {
173
+ snippet: "oauth.param `" + keys[ki] + "`: " + charThreats[ci].snippet,
174
+ }));
175
+ }
176
+ }
177
+
178
+ // PKCE.
179
+ if (opts.pkcePolicy !== "audit" && opts.pkcePolicy !== "allow") {
180
+ var hasVerifier = typeof flow.code_verifier === "string" && flow.code_verifier.length > 0;
181
+ var hasChallenge = typeof flow.code_challenge === "string" && flow.code_challenge.length > 0;
182
+ if (!hasVerifier && !hasChallenge) {
183
+ issues.push({
184
+ kind: "pkce-missing", severity: "high",
185
+ ruleId: "oauth.pkce-missing",
186
+ snippet: "neither code_verifier nor code_challenge present " +
187
+ "(RFC 7636 / OAuth 2.1 require PKCE for every client)",
188
+ });
189
+ }
190
+ if (hasChallenge && opts.pkcePolicy === "require-s256") {
191
+ var method = flow.code_challenge_method || "plain";
192
+ if (method !== "S256") {
193
+ issues.push({
194
+ kind: "pkce-method", severity: "high",
195
+ ruleId: "oauth.pkce-method",
196
+ snippet: "code_challenge_method `" + method + "` not S256 " +
197
+ "(OAuth 2.1 forbids `plain` — downgrade-attack class)",
198
+ });
199
+ }
200
+ }
201
+ }
202
+
203
+ // state.
204
+ if (opts.statePolicy === "require") {
205
+ if (typeof flow.state !== "string" || flow.state.length === 0) {
206
+ issues.push({
207
+ kind: "state-missing", severity: "high",
208
+ ruleId: "oauth.state-missing",
209
+ snippet: "state parameter missing — required for CSRF defense " +
210
+ "(RFC 6749 §10.12)",
211
+ });
212
+ }
213
+ }
214
+
215
+ // redirect_uri.
216
+ if (typeof flow.redirect_uri === "string" &&
217
+ opts.redirectUriPolicy === "require-exact-allowlist") {
218
+ var allowlist = opts.allowedRedirectUris;
219
+ // When the operator hasn't configured an allowlist, the gate can't
220
+ // enforce exact-match; skip the check entirely. Operator-side
221
+ // configuration warnings live in the operator's startup audit, not
222
+ // in per-request issue lists.
223
+ if (Array.isArray(allowlist) && allowlist.length > 0 &&
224
+ allowlist.indexOf(flow.redirect_uri) === -1) {
225
+ issues.push({
226
+ kind: "redirect-uri-not-allowed", severity: "high",
227
+ ruleId: "oauth.redirect-uri-not-allowed",
228
+ snippet: "redirect_uri `" + flow.redirect_uri + "` not in " +
229
+ "operator allowlist (RFC 6749 §3.1.2 / OAuth 2.1 " +
230
+ "mandate exact-match)",
231
+ });
232
+ }
233
+ }
234
+
235
+ // response_type.
236
+ if (typeof flow.response_type === "string" &&
237
+ opts.responseTypePolicy === "require-allowlist" &&
238
+ Array.isArray(opts.allowedResponseTypes)) {
239
+ if (opts.allowedResponseTypes.indexOf(flow.response_type) === -1) {
240
+ issues.push({
241
+ kind: "response-type-not-allowed", severity: "high",
242
+ ruleId: "oauth.response-type-not-allowed",
243
+ snippet: "response_type `" + flow.response_type + "` not in " +
244
+ "operator allowedResponseTypes",
245
+ });
246
+ }
247
+ }
248
+
249
+ // scope tampering.
250
+ if (typeof flow.scope === "string" &&
251
+ opts.scopeTamperingPolicy !== "allow") {
252
+ var scopes = flow.scope.split(" ");
253
+ for (var si = 0; si < scopes.length; si += 1) {
254
+ var s = scopes[si];
255
+ if (s.length === 0) continue;
256
+ if (!SCOPE_TOKEN_RE.test(s)) { // allow:regex-no-length-cap — scope value bounded by maxParamBytes
257
+ issues.push({
258
+ kind: "scope-token-shape",
259
+ severity: opts.scopeTamperingPolicy === "reject" ? "high" : "warn",
260
+ ruleId: "oauth.scope-token-shape",
261
+ snippet: "scope token `" + s + "` violates RFC 6749 §3.3 " +
262
+ "scope-token charset (whitespace / control / non-printable)",
263
+ });
264
+ }
265
+ }
266
+ }
267
+
268
+ // RFC 9207 issuer on callback.
269
+ if (opts.issuerOnCallbackPolicy === "require" &&
270
+ flow._isCallback === true) {
271
+ if (typeof flow.iss !== "string" || flow.iss.length === 0) {
272
+ issues.push({
273
+ kind: "issuer-missing", severity: "high",
274
+ ruleId: "oauth.issuer-missing",
275
+ snippet: "iss parameter missing on callback — RFC 9207 mandates " +
276
+ "issuer identification to defeat IdP-mix-up attack",
277
+ });
278
+ }
279
+ }
280
+
281
+ // code reuse — operator supplies seenCodeStore + reportSeen() / hasSeen().
282
+ if (typeof flow.code === "string" &&
283
+ opts.codeReusePolicy !== "allow" &&
284
+ opts.seenCodeStore && typeof opts.seenCodeStore.hasSeen === "function") {
285
+ try {
286
+ if (opts.seenCodeStore.hasSeen(flow.code)) {
287
+ issues.push({
288
+ kind: "code-reused", severity: "critical",
289
+ ruleId: "oauth.code-reused",
290
+ snippet: "authorization code already exchanged — replay class " +
291
+ "(RFC 6749 §10.5)",
292
+ });
293
+ }
294
+ } catch (_e) { /* drop-silent — operator-supplied store */ }
295
+ }
296
+
297
+ return issues;
298
+ }
299
+
300
+ function validate(input, opts) {
301
+ opts = _resolveOpts(opts);
302
+ numericBounds.requireAllPositiveFiniteIntIfPresent(opts,
303
+ ["maxBytes", "maxParamBytes"],
304
+ "guardOauth.validate", GuardOauthError, "oauth.bad-opt");
305
+ return gateContract.aggregateIssues(_detectIssues(input, opts));
306
+ }
307
+
308
+ function sanitize(input, opts) {
309
+ opts = _resolveOpts(opts);
310
+ // OAuth flows can't be repaired — sanitize either passes through
311
+ // valid input or throws.
312
+ var issues = _detectIssues(input, opts);
313
+ for (var i = 0; i < issues.length; i += 1) {
314
+ if (issues[i].severity === "critical" || issues[i].severity === "high") {
315
+ throw _err(issues[i].ruleId || "oauth.refused",
316
+ "guardOauth.sanitize: " + issues[i].snippet);
317
+ }
318
+ }
319
+ return input;
320
+ }
321
+
322
+ function gate(opts) {
323
+ opts = _resolveOpts(opts);
324
+ return gateContract.buildGuardGate(
325
+ opts.name || "guardOauth:" + (opts.profile || "default"),
326
+ opts,
327
+ async function (ctx) {
328
+ var flow = ctx && (ctx.oauthFlow || ctx.flow);
329
+ if (!flow) return { ok: true, action: "serve" };
330
+ var rv = validate(flow, opts);
331
+ if (rv.issues.length === 0) return { ok: true, action: "serve" };
332
+ var hasCritical = rv.issues.some(function (i) {
333
+ return i.severity === "critical";
334
+ });
335
+ var hasHigh = rv.issues.some(function (i) {
336
+ return i.severity === "high";
337
+ });
338
+ if (!hasCritical && !hasHigh) {
339
+ return { ok: true, action: "audit-only", issues: rv.issues };
340
+ }
341
+ return { ok: false, action: "refuse", issues: rv.issues };
342
+ });
343
+ }
344
+
345
+ var buildProfile = gateContract.makeProfileBuilder(PROFILES);
346
+
347
+ function compliancePosture(name) {
348
+ return gateContract.lookupCompliancePosture(name, COMPLIANCE_POSTURES,
349
+ _err, "oauth");
350
+ }
351
+
352
+ var _oauthRulePacks = gateContract.makeRulePackLoader(GuardOauthError, "oauth");
353
+ var loadRulePack = _oauthRulePacks.load;
354
+
355
+ module.exports = {
356
+ // ---- guard-* family registry exports ----
357
+ NAME: "oauth",
358
+ KIND: "oauth-flow",
359
+ INTEGRATION_FIXTURES: Object.freeze({
360
+ kind: "oauth-flow",
361
+ benignBytes: Buffer.from(JSON.stringify({
362
+ response_type: "code",
363
+ redirect_uri: "https://app.example.com/callback",
364
+ state: "csrf-rand-1",
365
+ scope: "openid profile",
366
+ code_challenge: "abc123def456ghi789jkl012mno345pqr678", // allow:raw-byte-literal — base64url-shaped fixture
367
+ code_challenge_method: "S256",
368
+ }), "utf8"),
369
+ hostileBytes: Buffer.from(JSON.stringify({
370
+ response_type: "code",
371
+ redirect_uri: "https://attacker.example/callback",
372
+ // state missing — CSRF class
373
+ scope: "openid",
374
+ }), "utf8"),
375
+ benignOauthFlow: {
376
+ response_type: "code",
377
+ redirect_uri: "https://app.example.com/callback",
378
+ state: "csrf-rand-1",
379
+ scope: "openid profile",
380
+ code_challenge: "abc123def456ghi789jkl012mno345pqr678", // allow:raw-byte-literal — base64url-shaped fixture
381
+ code_challenge_method: "S256",
382
+ },
383
+ hostileOauthFlow: {
384
+ response_type: "code",
385
+ redirect_uri: "https://attacker.example/callback",
386
+ // state missing → state-missing refuse
387
+ scope: "openid",
388
+ },
389
+ }),
390
+ // ---- primitive surface ----
391
+ validate: validate,
392
+ sanitize: sanitize,
393
+ gate: gate,
394
+ buildProfile: buildProfile,
395
+ compliancePosture: compliancePosture,
396
+ loadRulePack: loadRulePack,
397
+ PROFILES: PROFILES,
398
+ DEFAULTS: DEFAULTS,
399
+ COMPLIANCE_POSTURES: COMPLIANCE_POSTURES,
400
+ GuardOauthError: GuardOauthError,
401
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/core",
3
- "version": "0.7.50",
3
+ "version": "0.7.52",
4
4
  "description": "The Node framework that owns its stack.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "blamejs contributors",
@@ -2,10 +2,10 @@
2
2
  "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
3
3
  "bomFormat": "CycloneDX",
4
4
  "specVersion": "1.5",
5
- "serialNumber": "urn:uuid:fd92fe0b-46b7-4794-836b-2b5e010a0fd5",
5
+ "serialNumber": "urn:uuid:5f6f1fab-e230-4b2c-ad06-d5b9143d8fe2",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-05-05T22:18:17.867Z",
8
+ "timestamp": "2026-05-05T22:47:38.526Z",
9
9
  "lifecycles": [
10
10
  {
11
11
  "phase": "build"
@@ -19,14 +19,14 @@
19
19
  }
20
20
  ],
21
21
  "component": {
22
- "bom-ref": "@blamejs/core@0.7.50",
22
+ "bom-ref": "@blamejs/core@0.7.52",
23
23
  "type": "library",
24
24
  "name": "blamejs",
25
- "version": "0.7.50",
25
+ "version": "0.7.52",
26
26
  "scope": "required",
27
27
  "author": "blamejs contributors",
28
28
  "description": "The Node framework that owns its stack.",
29
- "purl": "pkg:npm/%40blamejs/core@0.7.50",
29
+ "purl": "pkg:npm/%40blamejs/core@0.7.52",
30
30
  "properties": [],
31
31
  "externalReferences": [
32
32
  {
@@ -54,7 +54,7 @@
54
54
  "components": [],
55
55
  "dependencies": [
56
56
  {
57
- "ref": "@blamejs/core@0.7.50",
57
+ "ref": "@blamejs/core@0.7.52",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]