@blamejs/core 0.18.43 → 0.18.45

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.
@@ -2528,7 +2528,197 @@ function resolveProfileAndPosture(opts, cfg) {
2528
2528
  }
2529
2529
  overlay = Object.assign({}, overlay, cfg.compliancePostures[posture]);
2530
2530
  }
2531
- return Object.assign({}, cfg.defaults || {}, overlay, opts);
2531
+ // An explicitly-undefined property means the caller did not set it, not that
2532
+ // the default should be removed. `{ maxBytes: parsedEnvValue }` with the
2533
+ // variable unset is the ordinary way to write it, and a plain Object.assign
2534
+ // copies that undefined straight over the profile's value - after which the
2535
+ // cap is gone and every `measured > undefined` comparison is false. Dropping
2536
+ // undefined here restores the merge everyone already assumes: last one to
2537
+ // actually SET a key wins.
2538
+ var resolved = Object.assign({}, cfg.defaults || {}, overlay, _defined(opts));
2539
+ // The numeric caps are checked HERE, at the one point every entry point
2540
+ // passes through, rather than in the generated validate() alone. A guard's
2541
+ // hand-written entry points - guardMarkdown.render, guardEmail.sanitize,
2542
+ // guardHtml's three - resolve their own opts and then read maxBytes/maxLines
2543
+ // straight out of the result. When the check lived only in validate(), those
2544
+ // paths took `{ maxBytes: "8mb" }` at face value: every `bytes > maxBytes`
2545
+ // comparison against a string is false, so the malformed value did not fall
2546
+ // back to the default, it DISABLED the cap on untrusted input. Checking at
2547
+ // the resolver makes the bypass unreachable by construction, so a guard
2548
+ // cannot grow a new entry point that silently opts out of its own limits.
2549
+ //
2550
+ // Two lists, because they answer two different questions.
2551
+ //
2552
+ // `intOpts` is what the guard AUTHOR declared to be a cap. Zero is not a
2553
+ // setting there — `maxBytes: 0` would refuse every input — so those stay
2554
+ // strictly positive, and the guards that already refuse it keep doing so.
2555
+ //
2556
+ // `nonNegativeOpts` is DERIVED from the defaults, and derivation cannot tell
2557
+ // a cap from a tolerance. `maxRuntimeMs: 0` means "no runtime budget" and
2558
+ // `nbfFutureSlackMs: 0` means "allow no clock slack": ordinary settings that
2559
+ // requiring a positive integer took away. What derivation CAN say is that
2560
+ // the value must still be a non-negative integer, and that is the whole
2561
+ // fail-open class — a string, an Infinity, a NaN, a fraction or a negative
2562
+ // makes every `measured > cap` comparison false and removes the bound.
2563
+ //
2564
+ // An earlier version kept a hand-written list of the options where zero is a
2565
+ // value. That list was the same kind of thing that drifted in the first
2566
+ // place, and it had already missed two.
2567
+ //
2568
+ // This is also why the general resolver must not infer on its own: a consumer
2569
+ // whose defaults hold `{ retries: 3 }` is entitled to `retries: 0`, and only
2570
+ // the caller knows which of its options are limits.
2571
+ if (Array.isArray(cfg.intOpts) && cfg.intOpts.length > 0) {
2572
+ numericBounds.requireAllPositiveFiniteIntIfPresent(resolved, cfg.intOpts,
2573
+ prefix + ".resolveOpts", ErrorClass, prefix + ".bad-opt");
2574
+ }
2575
+ if (Array.isArray(cfg.nonNegativeOpts)) {
2576
+ cfg.nonNegativeOpts.forEach(function (k) {
2577
+ if (resolved[k] === undefined) return;
2578
+ if (Array.isArray(cfg.intOpts) && cfg.intOpts.indexOf(k) !== -1) return;
2579
+ numericBounds.requireNonNegativeFiniteIntIfPresent(resolved[k],
2580
+ prefix + ".resolveOpts " + k, ErrorClass, prefix + ".bad-opt");
2581
+ });
2582
+ }
2583
+ // Options whose value must come from a fixed vocabulary. Checked here, with
2584
+ // the caps, because this is the one point every entry point passes through -
2585
+ // a check on the request path would let a typo through gate construction and
2586
+ // surface it as failing requests instead of a boot error. Read leniently, a
2587
+ // misspelled policy takes whichever branch is not the strict one, so this
2588
+ // fails closed by refusing the value rather than guessing at it.
2589
+ if (cfg.enumOpts && typeof cfg.enumOpts === "object") {
2590
+ Object.keys(cfg.enumOpts).forEach(function (k) {
2591
+ var allowed = cfg.enumOpts[k];
2592
+ var v = resolved[k];
2593
+ if (v === undefined || !Array.isArray(allowed)) return;
2594
+ if (typeof v !== "string" || allowed.indexOf(v) === -1) {
2595
+ throw ErrorClass.factory(prefix + ".bad-opt",
2596
+ prefix + ": " + k + " must be one of " + allowed.join(", ") +
2597
+ "; got " + JSON.stringify(v));
2598
+ }
2599
+ });
2600
+ }
2601
+ return resolved;
2602
+ }
2603
+
2604
+
2605
+ // The own enumerable properties of `o` that were actually given a value. A key
2606
+ // present with the value `undefined` is treated as absent, so it cannot erase
2607
+ // a default it was never meant to touch.
2608
+ function _defined(o) {
2609
+ if (!o || typeof o !== "object") return o;
2610
+ var out = {};
2611
+ Object.keys(o).forEach(function (k) {
2612
+ if (o[k] !== undefined) out[k] = o[k];
2613
+ });
2614
+ return out;
2615
+ }
2616
+
2617
+ /**
2618
+ * @primitive b.gateContract.capKeysOf
2619
+ * @signature b.gateContract.capKeysOf(defaults, declared?)
2620
+ * @since 0.18.44
2621
+ * @status stable
2622
+ * @related b.gateContract.resolveProfileAndPosture, b.gateContract.defineGuard
2623
+ *
2624
+ * The guard option names that must stay non-negative integers: every default
2625
+ * whose value is a positive finite integer, plus any name passed in `declared`.
2626
+ *
2627
+ * Pass the result as `nonNegativeOpts` when binding `resolveProfileAndPosture`
2628
+ * by hand, so the resolver refuses a value given as a string, `Infinity`, a
2629
+ * `NaN`, a fraction or a negative number. Those do not fall back to the
2630
+ * default — every `measured > cap` comparison against them is false, which
2631
+ * removes the limit on untrusted input. A guard built by `defineGuard` gets
2632
+ * this applied for it.
2633
+ *
2634
+ * NON-NEGATIVE rather than positive, because derivation cannot tell a cap from
2635
+ * a tolerance: `maxRuntimeMs: 0` means "no runtime budget" and
2636
+ * `nbfFutureSlackMs: 0` means "allow no clock slack". Declare the options that
2637
+ * are genuinely caps as `intOpts` instead, where zero is refused.
2638
+ *
2639
+ * Derived rather than hand-listed on purpose: a written-out list drifts from
2640
+ * the defaults it mirrors, which is how `maxRuntimeMs` came to be unchecked
2641
+ * across the whole guard family.
2642
+ *
2643
+ * @example
2644
+ * var DEFAULTS = { maxBytes: 1024, mode: "enforce", ratio: 0.5 };
2645
+ * b.gateContract.capKeysOf(DEFAULTS);
2646
+ * // -> ["maxBytes"]
2647
+ */
2648
+ function capKeysOf(defaults, declared) {
2649
+ return _capKeys(defaults, declared);
2650
+ }
2651
+
2652
+ /**
2653
+ * @primitive b.gateContract.identitySanitize
2654
+ * @signature b.gateContract.identitySanitize(input)
2655
+ * @since 0.18.44
2656
+ * @status stable
2657
+ * @related b.gateContract.defineGuard, b.gateContract.severityDisposition
2658
+ *
2659
+ * The sanitize transform for a guard that refuses rather than repairs.
2660
+ *
2661
+ * Some inputs cannot be safely rewritten: forging a JWT `alg`, editing an OAuth
2662
+ * `state`, rewriting a caller's regular expression or dropping cookies would
2663
+ * disarm the very evidence the guard is inspecting. A guard like that has no
2664
+ * repair to perform, so its transform returns the value unchanged — reached
2665
+ * only when nothing high or critical refused it upstream.
2666
+ *
2667
+ * @example
2668
+ * module.exports = b.gateContract.defineGuard({
2669
+ * name: "auth",
2670
+ * sanitizeTransform: b.gateContract.identitySanitize,
2671
+ * });
2672
+ */
2673
+ function identitySanitize(input) {
2674
+ return input;
2675
+ }
2676
+
2677
+ /**
2678
+ * @primitive b.gateContract.ctxValueFrom
2679
+ * @signature b.gateContract.ctxValueFrom(ctx, fields)
2680
+ * @since 0.18.44
2681
+ * @status stable
2682
+ * @related b.gateContract.defineGuard, b.gateContract.buildGuardGate
2683
+ *
2684
+ * Read a gate context the way a generated gate does: the first field carrying
2685
+ * a value, or `undefined` when the context carries none of them.
2686
+ *
2687
+ * The distinction is the point. A field that is ABSENT means there is nothing
2688
+ * for this guard to look at; a field PRESENT as an empty string is a value, and
2689
+ * one most validators refuse. An `ctx.a || ctx.b || ""` chain collapses both
2690
+ * into `""`, so a gate written that way serves an empty identifier its own
2691
+ * `validate` rejects. Use this in a hand-written gate and short-circuit only on
2692
+ * `undefined`.
2693
+ *
2694
+ * @example
2695
+ * var name = b.gateContract.ctxValueFrom(ctx, ["filename", "name"]);
2696
+ * if (name === undefined) return { ok: true, action: "serve" };
2697
+ */
2698
+ function ctxValueFrom(ctx, fields) {
2699
+ return _ctxValueForKind(null, ctx, fields);
2700
+ }
2701
+
2702
+ // The cap keys of a GUARD: every default that is a positive finite integer,
2703
+ // plus anything the spec names outright. These are the caps, budgets and depth
2704
+ // bounds an operator may raise or lower but must not replace with a shape that
2705
+ // silently compares false against everything. Only defineGuard calls this -
2706
+ // the general resolver must not infer the same thing about an arbitrary
2707
+ // consumer's options.
2708
+ function _capKeys(defaults, declared) {
2709
+ var out = [];
2710
+ if (defaults && typeof defaults === "object") {
2711
+ Object.keys(defaults).forEach(function (k) {
2712
+ var v = defaults[k];
2713
+ if (typeof v === "number" && Number.isInteger(v) && v > 0 && Number.isFinite(v)) {
2714
+ out.push(k);
2715
+ }
2716
+ });
2717
+ }
2718
+ if (Array.isArray(declared)) {
2719
+ declared.forEach(function (k) { if (out.indexOf(k) === -1) out.push(k); });
2720
+ }
2721
+ return out;
2532
2722
  }
2533
2723
 
2534
2724
  // _warnUnmappedPosture — emit a one-time, grep-able audit warning that a
@@ -2810,14 +3000,34 @@ var _KIND_CTX_FIELDS = Object.freeze({
2810
3000
 
2811
3001
  // override (when given) replaces the per-KIND field table — lets a guard whose
2812
3002
  // gate is the standard chain but reads a custom ctx field take the default gate.
3003
+ // Returns the ctx value for this guard's kind, or `undefined` when the ctx
3004
+ // carries none of its fields at all.
3005
+ //
3006
+ // The distinction matters: an ABSENT field means there is nothing for this
3007
+ // guard to look at, while a field PRESENT as an empty string is a value, and
3008
+ // one most guards' validators refuse. Collapsing both to "" made the gate serve
3009
+ // `{ country: "" }` while validate("") reported it - the gate disagreeing with
3010
+ // the validator it is meant to enforce, across seventeen guards.
3011
+ //
3012
+ // A truthy field still wins, and still in declaration order, so a ctx carrying
3013
+ // a real value anywhere resolves exactly as before; the empty case only decides
3014
+ // between "" and undefined.
2813
3015
  function _ctxValueForKind(kind, ctx, override) {
2814
3016
  ctx = ctx || {};
2815
3017
  var fields = override || _KIND_CTX_FIELDS[kind];
2816
3018
  if (!fields) return extractBytesAsText(ctx); // content (default)
3019
+ var emptyPresent;
3020
+ var sawEmpty = false;
2817
3021
  for (var i = 0; i < fields.length; i += 1) {
2818
- if (ctx[fields[i]]) return ctx[fields[i]];
3022
+ var v = ctx[fields[i]];
3023
+ if (v) return v;
3024
+ if (!sawEmpty && v !== undefined && v !== null &&
3025
+ Object.prototype.hasOwnProperty.call(ctx, fields[i])) {
3026
+ emptyPresent = v;
3027
+ sawEmpty = true;
3028
+ }
2819
3029
  }
2820
- return "";
3030
+ return sawEmpty ? emptyPresent : undefined;
2821
3031
  }
2822
3032
 
2823
3033
  /**
@@ -2946,6 +3156,19 @@ function defineGuard(spec) {
2946
3156
  defaults: defaults,
2947
3157
  errorClass: ErrorClass,
2948
3158
  errCodePrefix: prefix,
3159
+ // What the author declared IS a cap, so zero is refused there.
3160
+ intOpts: Array.isArray(spec.intOpts) ? spec.intOpts : null,
3161
+ // Everything else derived from this guard's own defaults, held only to
3162
+ // "still a non-negative integer". Deriving rather than hand-listing is
3163
+ // what stops the drift that left maxRuntimeMs unchecked in all 27
3164
+ // guards, guard-csv with no list at all, and guard-sql's and guard-svg's
3165
+ // caps unnamed — while leaving `maxRuntimeMs: 0` and the clock-slack
3166
+ // options the settings they have always been. Declared here so both
3167
+ // bind on EVERY path that resolves opts, not just the generated
3168
+ // validate() below.
3169
+ nonNegativeOpts: _capKeys(defaults, spec.intOpts),
3170
+ // Options restricted to a fixed vocabulary, checked at the same funnel.
3171
+ enumOpts: spec.enumOpts || null,
2949
3172
  });
2950
3173
  };
2951
3174
  if (typeof spec.detect === "function") {
@@ -3104,17 +3327,26 @@ function defineGuard(spec) {
3104
3327
  // is idempotent over an already-resolved opts, so spec.validate's internal
3105
3328
  // resolution stays correct.
3106
3329
  function defaultGate(rawOpts) {
3107
- var opts = resolveProfileAndPosture(rawOpts || {}, {
3108
- profiles: profiles,
3109
- compliancePostures: postures,
3110
- defaults: defaults,
3111
- errorClass: ErrorClass,
3112
- errCodePrefix: prefix,
3113
- });
3330
+ // Through the guard's own resolver, not a second copy of its binding. The
3331
+ // copy that used to be here carried no cap or vocabulary list, so building
3332
+ // a gate accepted a malformed limit or a misspelled policy that validate()
3333
+ // would have refused - a misconfiguration surviving boot to fail later on
3334
+ // requests, which is the opposite of what a config-time check is for.
3335
+ var opts = _resolveGuardOpts(rawOpts || {});
3114
3336
  var perCtx = spec.defaultGateCheck || function (ctx) {
3115
3337
  var value = _ctxValueForKind(spec.kind, ctx, ctxFields);
3116
- if (!value) return { ok: true, action: "serve" };
3117
- var rv = spec.validate(value, opts);
3338
+ // Only an ABSENT field short-circuits. A field present as an empty string
3339
+ // is a value, and the validator decides what it is worth - which keeps the
3340
+ // gate's verdict and validate()'s verdict the same answer.
3341
+ if (value === undefined || value === null) return { ok: true, action: "serve" };
3342
+ var rv;
3343
+ try { rv = spec.validate(value, opts); }
3344
+ catch (_e) {
3345
+ // A validator that cannot even parse the value has not approved it.
3346
+ // Letting the throw escape would crash the request this gate exists to
3347
+ // decide, so it becomes the refusal it already means.
3348
+ return { ok: false, action: "refuse" };
3349
+ }
3118
3350
  return severityDisposition(rv.issues || []);
3119
3351
  };
3120
3352
  return buildGuardGate(
@@ -3128,6 +3360,11 @@ function defineGuard(spec) {
3128
3360
  var out = {
3129
3361
  NAME: spec.name,
3130
3362
  KIND: spec.kind,
3363
+ // The options this guard DECLARES to be caps, where zero is refused. Every
3364
+ // other numeric option is derived from the defaults and held only to
3365
+ // "non-negative integer", because derivation cannot tell a cap from a
3366
+ // tolerance. Exposed so the family tests can tell the two apart.
3367
+ INT_OPTS: Object.freeze(Array.isArray(spec.intOpts) ? spec.intOpts.slice() : []),
3131
3368
  validate: spec.validate,
3132
3369
  resolveOpts: _resolveGuardOpts,
3133
3370
  buildProfile: buildProfileFn,
@@ -3472,6 +3709,12 @@ module.exports = {
3472
3709
  workerThreadGate: workerThreadGate,
3473
3710
  buildProfile: buildProfile,
3474
3711
  resolveProfileAndPosture: resolveProfileAndPosture,
3712
+ // For a guard that binds its own resolver instead of going through
3713
+ // defineGuard: the cap keys to declare, derived from its defaults so the
3714
+ // list cannot drift away from them.
3715
+ capKeysOf: capKeysOf,
3716
+ identitySanitize: identitySanitize,
3717
+ ctxValueFrom: ctxValueFrom,
3475
3718
  runIssueValidator: runIssueValidator,
3476
3719
  buildGuardGate: buildGuardGate,
3477
3720
  severityDisposition: severityDisposition,
package/lib/guard-all.js CHANGED
@@ -74,6 +74,7 @@ var STANDALONE_GUARDS = [
74
74
  require("./guard-domain"),
75
75
  require("./guard-uuid"),
76
76
  require("./guard-cidr"),
77
+ require("./guard-country"),
77
78
  require("./guard-time"),
78
79
  require("./guard-mime"),
79
80
  require("./guard-jwt"),
@@ -183,8 +183,20 @@ var DEFAULTS = gateContract.strictDefaults(PROFILES, {
183
183
 
184
184
  var COMPLIANCE_POSTURES = gateContract.compliancePostures(PROFILES, { base: 256 });
185
185
 
186
+ // The options that are genuinely CAPS, where zero is not a setting. This guard
187
+ // builds its own exports rather than going through defineGuard, so it names
188
+ // them here. maxRuntimeMs is deliberately absent: zero there means no runtime
189
+ // budget. See guard-image for the full reasoning.
190
+ var INT_OPTS = ["maxEntries", "maxTotalBytes", "maxEntryBytes",
191
+ "maxCompressionRatio", "maxAggregateRatio"];
192
+
186
193
  // ---- Helpers ----
187
194
 
195
+ // This guard builds its own exports rather than going through defineGuard, so
196
+ // it binds its resolver here and declares its caps here too. The list is
197
+ // DERIVED from DEFAULTS rather than written out, because a hand-kept list is
198
+ // what drifted away from the defaults it was meant to mirror and left limits
199
+ // unchecked across the family.
188
200
  function _resolveOpts(opts) {
189
201
  return gateContract.resolveProfileAndPosture(opts, {
190
202
  profiles: PROFILES,
@@ -192,6 +204,8 @@ function _resolveOpts(opts) {
192
204
  defaults: DEFAULTS,
193
205
  errorClass: GuardArchiveError,
194
206
  errCodePrefix: "archive",
207
+ intOpts: INT_OPTS,
208
+ nonNegativeOpts: gateContract.capKeysOf(DEFAULTS),
195
209
  });
196
210
  }
197
211
 
package/lib/guard-auth.js CHANGED
@@ -316,9 +316,7 @@ function _detectIssues(bundle, opts) {
316
316
  // transform is identity — the bundle is returned unchanged when no high/critical
317
317
  // issue refused upstream. A non-object input refuses upstream via the high-
318
318
  // severity auth.bad-input issue _detectIssues raises.
319
- function _sanitizeTransform(input) {
320
- return input;
321
- }
319
+ var _sanitizeTransform = gateContract.identitySanitize;
322
320
 
323
321
  /**
324
322
  * @primitive b.guardAuth.gate