rulesync 16.22.0 → 16.22.1

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.
@@ -3385,6 +3385,47 @@ var RulesyncFile = class extends AiFile {
3385
3385
  }
3386
3386
  };
3387
3387
  //#endregion
3388
+ //#region src/utils/bounded-walk.ts
3389
+ const ALIAS_HINT = "(a chain of YAML aliases may be amplifying the document)";
3390
+ /**
3391
+ * Create the bookkeeping for one walk. `subject` names the document kind in
3392
+ * every error ("Frontmatter", "Shared config"); `root`, when given, is entered
3393
+ * up front so the root object counts as the first nesting level, matching the
3394
+ * +1 that `enter` applies to every container nested inside it.
3395
+ */
3396
+ function createBoundedWalk({ subject, limits, root }) {
3397
+ const ancestors = /* @__PURE__ */ new WeakSet();
3398
+ let valuesRemaining = limits.maxValues;
3399
+ let stringCharsRemaining = limits.maxStringChars;
3400
+ let depth = 0;
3401
+ const chargeChars = (chars) => {
3402
+ stringCharsRemaining -= chars;
3403
+ if (stringCharsRemaining < 0) throw new Error(`${subject}'s string values expand to more than ${limits.maxStringChars} characters; refusing to process it ${ALIAS_HINT}`);
3404
+ };
3405
+ const chargeValue = (stringChars = 0) => {
3406
+ valuesRemaining -= 1;
3407
+ if (valuesRemaining < 0) throw new Error(`${subject} expands to more than ${limits.maxValues} values; refusing to process it ${ALIAS_HINT}`);
3408
+ chargeChars(stringChars);
3409
+ };
3410
+ const enter = (container) => {
3411
+ depth += 1;
3412
+ if (depth > limits.maxDepth) throw new Error(`${subject} nests more than ${limits.maxDepth} levels deep; refusing to process it ${ALIAS_HINT}`);
3413
+ ancestors.add(container);
3414
+ };
3415
+ const leave = (container) => {
3416
+ ancestors.delete(container);
3417
+ depth -= 1;
3418
+ };
3419
+ if (root !== void 0) enter(root);
3420
+ return {
3421
+ chargeValue,
3422
+ chargeChars,
3423
+ isAncestor: (container) => ancestors.has(container),
3424
+ enter,
3425
+ leave
3426
+ };
3427
+ }
3428
+ //#endregion
3388
3429
  //#region src/utils/prototype-pollution.ts
3389
3430
  /**
3390
3431
  * Keys that, if walked into when constructing or merging objects from
@@ -3524,30 +3565,6 @@ const MAX_FRONTMATTER_STRING_CHARS = 4e6;
3524
3565
  * manifest stays well under this.
3525
3566
  */
3526
3567
  const MAX_FRONTMATTER_RAW_CHARS = 65536;
3527
- /** Charge string content (a string leaf or an object key) against the character budget. */
3528
- function chargeStringChars({ options, chars }) {
3529
- options.budget.stringCharsRemaining -= chars;
3530
- if (options.budget.stringCharsRemaining < 0) throw new Error(`Frontmatter's string values expand to more than ${MAX_FRONTMATTER_STRING_CHARS} characters; refusing to process it (a chain of YAML aliases may be amplifying the document)`);
3531
- }
3532
- function consumeBudget({ options, stringChars = 0 }) {
3533
- options.budget.remaining -= 1;
3534
- if (options.budget.remaining < 0) throw new Error(`Frontmatter expands to more than ${MAX_FRONTMATTER_VALUES} values; refusing to process it (a chain of YAML aliases may be amplifying the document)`);
3535
- chargeStringChars({
3536
- options,
3537
- chars: stringChars
3538
- });
3539
- }
3540
- /** Enter one more container level, throwing if the depth cap is exceeded. */
3541
- function enterContainer({ options, container }) {
3542
- options.depth += 1;
3543
- if (options.depth > 64) throw new Error(`Frontmatter nests more than 64 levels deep; refusing to process it (a chain of YAML aliases may be amplifying the document)`);
3544
- options.ancestors.add(container);
3545
- }
3546
- /** Leave a container level entered via {@link enterContainer}. */
3547
- function leaveContainer({ options, container }) {
3548
- options.ancestors.delete(container);
3549
- options.depth -= 1;
3550
- }
3551
3568
  /**
3552
3569
  * Estimate the serialized character cost of a leaf that is not a string (a
3553
3570
  * string leaf is charged by its own length instead).
@@ -3570,40 +3587,26 @@ function estimateLeafChars(value) {
3570
3587
  * bounded by {@link MAX_FRONTMATTER_VALUES} instead.
3571
3588
  */
3572
3589
  function deepCleanValue(value, options) {
3573
- consumeBudget({
3574
- options,
3575
- stringChars: typeof value === "string" ? value.length : estimateLeafChars(value)
3576
- });
3590
+ const leafChars = typeof value === "string" ? value.length : estimateLeafChars(value);
3591
+ options.walk.chargeValue(leafChars);
3577
3592
  if (value === null || value === void 0) return;
3578
3593
  if (typeof value === "string") return options.transformString ? options.transformString(value) : value;
3579
3594
  if (Array.isArray(value)) {
3580
- if (options.ancestors.has(value)) return;
3581
- enterContainer({
3582
- options,
3583
- container: value
3584
- });
3595
+ if (options.walk.isAncestor(value)) return;
3596
+ options.walk.enter(value);
3585
3597
  const cleanedArray = [];
3586
3598
  for (const item of value) {
3587
3599
  const cleaned = deepCleanValue(item, options);
3588
3600
  if (cleaned !== void 0) cleanedArray.push(cleaned);
3589
3601
  }
3590
- leaveContainer({
3591
- options,
3592
- container: value
3593
- });
3602
+ options.walk.leave(value);
3594
3603
  return cleanedArray;
3595
3604
  }
3596
3605
  if (isPlainObject$1(value)) {
3597
- if (options.ancestors.has(value)) return;
3598
- enterContainer({
3599
- options,
3600
- container: value
3601
- });
3606
+ if (options.walk.isAncestor(value)) return;
3607
+ options.walk.enter(value);
3602
3608
  const result = cleanOwnEntries(value, options);
3603
- leaveContainer({
3604
- options,
3605
- container: value
3606
- });
3609
+ options.walk.leave(value);
3607
3610
  return result;
3608
3611
  }
3609
3612
  return value;
@@ -3622,10 +3625,7 @@ function deepCleanValue(value, options) {
3622
3625
  function cleanOwnEntries(obj, options) {
3623
3626
  const result = {};
3624
3627
  for (const [key, val] of Object.entries(obj)) {
3625
- chargeStringChars({
3626
- options,
3627
- chars: key.length
3628
- });
3628
+ options.walk.chargeChars(key.length);
3629
3629
  const cleaned = deepCleanValue(val, options);
3630
3630
  if (isPrototypePollutionKey(key)) continue;
3631
3631
  if (cleaned !== void 0) result[key] = cleaned;
@@ -3636,12 +3636,15 @@ function deepCleanObject(obj, options) {
3636
3636
  if (!obj || typeof obj !== "object") return {};
3637
3637
  return cleanOwnEntries(obj, {
3638
3638
  ...options,
3639
- ancestors: new WeakSet([obj]),
3640
- budget: {
3641
- remaining: MAX_FRONTMATTER_VALUES,
3642
- stringCharsRemaining: MAX_FRONTMATTER_STRING_CHARS
3643
- },
3644
- depth: 1
3639
+ walk: createBoundedWalk({
3640
+ subject: "Frontmatter",
3641
+ limits: {
3642
+ maxValues: MAX_FRONTMATTER_VALUES,
3643
+ maxStringChars: MAX_FRONTMATTER_STRING_CHARS,
3644
+ maxDepth: 64
3645
+ },
3646
+ root: obj
3647
+ })
3645
3648
  });
3646
3649
  }
3647
3650
  /** Drop null and undefined values, recursively. */
@@ -11483,6 +11486,21 @@ function stripStrings(_key, value) {
11483
11486
  //#endregion
11484
11487
  //#region src/features/shared/shared-config-gateway.ts
11485
11488
  /**
11489
+ * Upper bound on the number of values a shared config document may expand
11490
+ * to once every YAML alias is written out. Real config files hold a few
11491
+ * hundred values at most; even a large MCP server catalog stays orders of
11492
+ * magnitude below the limit.
11493
+ */
11494
+ const MAX_SHARED_CONFIG_VALUES = 1e5;
11495
+ /**
11496
+ * Upper bound on the total character count of the string leaves and keys a
11497
+ * shared config document may expand to. The value budget bounds how many
11498
+ * values are visited, but one long string aliased thousands of times fits
11499
+ * that budget while the duplicated output balloons; charging every visited
11500
+ * string's length separately bounds the output regardless of alias count.
11501
+ */
11502
+ const MAX_SHARED_CONFIG_STRING_CHARS = 4e6;
11503
+ /**
11486
11504
  * Rebuild a parsed document without its prototype-pollution keys.
11487
11505
  *
11488
11506
  * Every object is rebuilt, not just the ones that are already plain: a literal
@@ -11497,15 +11515,61 @@ function stripStrings(_key, value) {
11497
11515
  *
11498
11516
  * Dates are the one object the YAML and TOML parsers produce that is not a
11499
11517
  * mapping, so they are passed through rather than flattened into `{}`.
11518
+ *
11519
+ * The rebuild is bounded, because a YAML alias makes one parsed container
11520
+ * reachable from many keys and every alias is copied out independently (the
11521
+ * writers dump with `noRefs: true`, so memoizing here would only move the
11522
+ * blowup into serialization). A small "alias bomb" of nested anchors would
11523
+ * otherwise cost exponential time and memory, and a self-referencing anchor
11524
+ * would recurse until the stack overflowed — both reachable from a config
11525
+ * file committed to a cloned repository. The walk therefore charges every
11526
+ * value against {@link MAX_SHARED_CONFIG_VALUES}, every string and key
11527
+ * against {@link MAX_SHARED_CONFIG_STRING_CHARS}, caps nesting at
11528
+ * {@link MAX_SHARED_CONFIG_DEPTH}, and refuses a reference back to an
11529
+ * ancestor outright, each with a clear error instead of a hang or a crash.
11500
11530
  */
11501
11531
  function sanitizeSharedConfigValue(value) {
11502
- if (Array.isArray(value)) return value.map(sanitizeSharedConfigValue);
11532
+ return sanitizeSharedConfigValueBounded(value, createBoundedWalk({
11533
+ subject: "Shared config",
11534
+ limits: {
11535
+ maxValues: MAX_SHARED_CONFIG_VALUES,
11536
+ maxStringChars: MAX_SHARED_CONFIG_STRING_CHARS,
11537
+ maxDepth: 64
11538
+ }
11539
+ }));
11540
+ }
11541
+ /**
11542
+ * Refuse a container that is already on the descent path. Unlike the
11543
+ * frontmatter cleaner, which drops such a cycle and keeps the rest of the
11544
+ * document, a shared config file is refused outright: silently dropping part
11545
+ * of a user's settings file would let a later write-back persist the loss.
11546
+ */
11547
+ function enterSharedConfigContainer(walk, container) {
11548
+ if (walk.isAncestor(container)) throw new Error("Shared config contains a value that refers back to itself (a circular YAML alias); refusing to process it");
11549
+ walk.enter(container);
11550
+ }
11551
+ function sanitizeSharedConfigValueBounded(value, walk) {
11552
+ if (typeof value === "string") {
11553
+ walk.chargeValue(value.length);
11554
+ return value;
11555
+ }
11556
+ walk.chargeValue();
11557
+ if (Array.isArray(value)) {
11558
+ enterSharedConfigContainer(walk, value);
11559
+ const items = value.map((item) => sanitizeSharedConfigValueBounded(item, walk));
11560
+ walk.leave(value);
11561
+ return items;
11562
+ }
11503
11563
  if (value === null || typeof value !== "object" || value instanceof Date) return value;
11564
+ enterSharedConfigContainer(walk, value);
11504
11565
  const result = {};
11505
11566
  for (const [key, nested] of Object.entries(value)) {
11567
+ walk.chargeChars(key.length);
11568
+ const sanitized = sanitizeSharedConfigValueBounded(nested, walk);
11506
11569
  if (isPrototypePollutionKey(key)) continue;
11507
- result[key] = sanitizeSharedConfigValue(nested);
11570
+ result[key] = sanitized;
11508
11571
  }
11572
+ walk.leave(value);
11509
11573
  return result;
11510
11574
  }
11511
11575
  /**
@@ -11534,7 +11598,12 @@ function parseSharedConfig({ format, fileContent, filePath, invalidRootPolicy =
11534
11598
  throw new Error(`Failed to parse shared config${at}: ${formatError(error)}`, { cause: error });
11535
11599
  }
11536
11600
  if (parsed === void 0 || parsed === null) return {};
11537
- const sanitized = sanitizeSharedConfigValue(parsed);
11601
+ let sanitized;
11602
+ try {
11603
+ sanitized = sanitizeSharedConfigValue(parsed);
11604
+ } catch (error) {
11605
+ throw new Error(`Failed to parse shared config${at}: ${formatError(error)}`, { cause: error });
11606
+ }
11538
11607
  if (!isPlainObject$1(sanitized)) {
11539
11608
  if (invalidRootPolicy === "error") throw new Error(`Failed to parse shared config${at}: expected a mapping at the root`);
11540
11609
  return {};
@@ -3360,6 +3360,47 @@ var RulesyncFile = class extends AiFile {
3360
3360
  }
3361
3361
  };
3362
3362
  //#endregion
3363
+ //#region src/utils/bounded-walk.ts
3364
+ const ALIAS_HINT = "(a chain of YAML aliases may be amplifying the document)";
3365
+ /**
3366
+ * Create the bookkeeping for one walk. `subject` names the document kind in
3367
+ * every error ("Frontmatter", "Shared config"); `root`, when given, is entered
3368
+ * up front so the root object counts as the first nesting level, matching the
3369
+ * +1 that `enter` applies to every container nested inside it.
3370
+ */
3371
+ function createBoundedWalk({ subject, limits, root }) {
3372
+ const ancestors = /* @__PURE__ */ new WeakSet();
3373
+ let valuesRemaining = limits.maxValues;
3374
+ let stringCharsRemaining = limits.maxStringChars;
3375
+ let depth = 0;
3376
+ const chargeChars = (chars) => {
3377
+ stringCharsRemaining -= chars;
3378
+ if (stringCharsRemaining < 0) throw new Error(`${subject}'s string values expand to more than ${limits.maxStringChars} characters; refusing to process it ${ALIAS_HINT}`);
3379
+ };
3380
+ const chargeValue = (stringChars = 0) => {
3381
+ valuesRemaining -= 1;
3382
+ if (valuesRemaining < 0) throw new Error(`${subject} expands to more than ${limits.maxValues} values; refusing to process it ${ALIAS_HINT}`);
3383
+ chargeChars(stringChars);
3384
+ };
3385
+ const enter = (container) => {
3386
+ depth += 1;
3387
+ if (depth > limits.maxDepth) throw new Error(`${subject} nests more than ${limits.maxDepth} levels deep; refusing to process it ${ALIAS_HINT}`);
3388
+ ancestors.add(container);
3389
+ };
3390
+ const leave = (container) => {
3391
+ ancestors.delete(container);
3392
+ depth -= 1;
3393
+ };
3394
+ if (root !== void 0) enter(root);
3395
+ return {
3396
+ chargeValue,
3397
+ chargeChars,
3398
+ isAncestor: (container) => ancestors.has(container),
3399
+ enter,
3400
+ leave
3401
+ };
3402
+ }
3403
+ //#endregion
3363
3404
  //#region src/utils/prototype-pollution.ts
3364
3405
  /**
3365
3406
  * Keys that, if walked into when constructing or merging objects from
@@ -3499,30 +3540,6 @@ const MAX_FRONTMATTER_STRING_CHARS = 4e6;
3499
3540
  * manifest stays well under this.
3500
3541
  */
3501
3542
  const MAX_FRONTMATTER_RAW_CHARS = 65536;
3502
- /** Charge string content (a string leaf or an object key) against the character budget. */
3503
- function chargeStringChars({ options, chars }) {
3504
- options.budget.stringCharsRemaining -= chars;
3505
- if (options.budget.stringCharsRemaining < 0) throw new Error(`Frontmatter's string values expand to more than ${MAX_FRONTMATTER_STRING_CHARS} characters; refusing to process it (a chain of YAML aliases may be amplifying the document)`);
3506
- }
3507
- function consumeBudget({ options, stringChars = 0 }) {
3508
- options.budget.remaining -= 1;
3509
- if (options.budget.remaining < 0) throw new Error(`Frontmatter expands to more than ${MAX_FRONTMATTER_VALUES} values; refusing to process it (a chain of YAML aliases may be amplifying the document)`);
3510
- chargeStringChars({
3511
- options,
3512
- chars: stringChars
3513
- });
3514
- }
3515
- /** Enter one more container level, throwing if the depth cap is exceeded. */
3516
- function enterContainer({ options, container }) {
3517
- options.depth += 1;
3518
- if (options.depth > 64) throw new Error(`Frontmatter nests more than 64 levels deep; refusing to process it (a chain of YAML aliases may be amplifying the document)`);
3519
- options.ancestors.add(container);
3520
- }
3521
- /** Leave a container level entered via {@link enterContainer}. */
3522
- function leaveContainer({ options, container }) {
3523
- options.ancestors.delete(container);
3524
- options.depth -= 1;
3525
- }
3526
3543
  /**
3527
3544
  * Estimate the serialized character cost of a leaf that is not a string (a
3528
3545
  * string leaf is charged by its own length instead).
@@ -3545,40 +3562,26 @@ function estimateLeafChars(value) {
3545
3562
  * bounded by {@link MAX_FRONTMATTER_VALUES} instead.
3546
3563
  */
3547
3564
  function deepCleanValue(value, options) {
3548
- consumeBudget({
3549
- options,
3550
- stringChars: typeof value === "string" ? value.length : estimateLeafChars(value)
3551
- });
3565
+ const leafChars = typeof value === "string" ? value.length : estimateLeafChars(value);
3566
+ options.walk.chargeValue(leafChars);
3552
3567
  if (value === null || value === void 0) return;
3553
3568
  if (typeof value === "string") return options.transformString ? options.transformString(value) : value;
3554
3569
  if (Array.isArray(value)) {
3555
- if (options.ancestors.has(value)) return;
3556
- enterContainer({
3557
- options,
3558
- container: value
3559
- });
3570
+ if (options.walk.isAncestor(value)) return;
3571
+ options.walk.enter(value);
3560
3572
  const cleanedArray = [];
3561
3573
  for (const item of value) {
3562
3574
  const cleaned = deepCleanValue(item, options);
3563
3575
  if (cleaned !== void 0) cleanedArray.push(cleaned);
3564
3576
  }
3565
- leaveContainer({
3566
- options,
3567
- container: value
3568
- });
3577
+ options.walk.leave(value);
3569
3578
  return cleanedArray;
3570
3579
  }
3571
3580
  if (isPlainObject$1(value)) {
3572
- if (options.ancestors.has(value)) return;
3573
- enterContainer({
3574
- options,
3575
- container: value
3576
- });
3581
+ if (options.walk.isAncestor(value)) return;
3582
+ options.walk.enter(value);
3577
3583
  const result = cleanOwnEntries(value, options);
3578
- leaveContainer({
3579
- options,
3580
- container: value
3581
- });
3584
+ options.walk.leave(value);
3582
3585
  return result;
3583
3586
  }
3584
3587
  return value;
@@ -3597,10 +3600,7 @@ function deepCleanValue(value, options) {
3597
3600
  function cleanOwnEntries(obj, options) {
3598
3601
  const result = {};
3599
3602
  for (const [key, val] of Object.entries(obj)) {
3600
- chargeStringChars({
3601
- options,
3602
- chars: key.length
3603
- });
3603
+ options.walk.chargeChars(key.length);
3604
3604
  const cleaned = deepCleanValue(val, options);
3605
3605
  if (isPrototypePollutionKey(key)) continue;
3606
3606
  if (cleaned !== void 0) result[key] = cleaned;
@@ -3611,12 +3611,15 @@ function deepCleanObject(obj, options) {
3611
3611
  if (!obj || typeof obj !== "object") return {};
3612
3612
  return cleanOwnEntries(obj, {
3613
3613
  ...options,
3614
- ancestors: new WeakSet([obj]),
3615
- budget: {
3616
- remaining: MAX_FRONTMATTER_VALUES,
3617
- stringCharsRemaining: MAX_FRONTMATTER_STRING_CHARS
3618
- },
3619
- depth: 1
3614
+ walk: createBoundedWalk({
3615
+ subject: "Frontmatter",
3616
+ limits: {
3617
+ maxValues: MAX_FRONTMATTER_VALUES,
3618
+ maxStringChars: MAX_FRONTMATTER_STRING_CHARS,
3619
+ maxDepth: 64
3620
+ },
3621
+ root: obj
3622
+ })
3620
3623
  });
3621
3624
  }
3622
3625
  /** Drop null and undefined values, recursively. */
@@ -11458,6 +11461,21 @@ function stripStrings(_key, value) {
11458
11461
  //#endregion
11459
11462
  //#region src/features/shared/shared-config-gateway.ts
11460
11463
  /**
11464
+ * Upper bound on the number of values a shared config document may expand
11465
+ * to once every YAML alias is written out. Real config files hold a few
11466
+ * hundred values at most; even a large MCP server catalog stays orders of
11467
+ * magnitude below the limit.
11468
+ */
11469
+ const MAX_SHARED_CONFIG_VALUES = 1e5;
11470
+ /**
11471
+ * Upper bound on the total character count of the string leaves and keys a
11472
+ * shared config document may expand to. The value budget bounds how many
11473
+ * values are visited, but one long string aliased thousands of times fits
11474
+ * that budget while the duplicated output balloons; charging every visited
11475
+ * string's length separately bounds the output regardless of alias count.
11476
+ */
11477
+ const MAX_SHARED_CONFIG_STRING_CHARS = 4e6;
11478
+ /**
11461
11479
  * Rebuild a parsed document without its prototype-pollution keys.
11462
11480
  *
11463
11481
  * Every object is rebuilt, not just the ones that are already plain: a literal
@@ -11472,15 +11490,61 @@ function stripStrings(_key, value) {
11472
11490
  *
11473
11491
  * Dates are the one object the YAML and TOML parsers produce that is not a
11474
11492
  * mapping, so they are passed through rather than flattened into `{}`.
11493
+ *
11494
+ * The rebuild is bounded, because a YAML alias makes one parsed container
11495
+ * reachable from many keys and every alias is copied out independently (the
11496
+ * writers dump with `noRefs: true`, so memoizing here would only move the
11497
+ * blowup into serialization). A small "alias bomb" of nested anchors would
11498
+ * otherwise cost exponential time and memory, and a self-referencing anchor
11499
+ * would recurse until the stack overflowed — both reachable from a config
11500
+ * file committed to a cloned repository. The walk therefore charges every
11501
+ * value against {@link MAX_SHARED_CONFIG_VALUES}, every string and key
11502
+ * against {@link MAX_SHARED_CONFIG_STRING_CHARS}, caps nesting at
11503
+ * {@link MAX_SHARED_CONFIG_DEPTH}, and refuses a reference back to an
11504
+ * ancestor outright, each with a clear error instead of a hang or a crash.
11475
11505
  */
11476
11506
  function sanitizeSharedConfigValue(value) {
11477
- if (Array.isArray(value)) return value.map(sanitizeSharedConfigValue);
11507
+ return sanitizeSharedConfigValueBounded(value, createBoundedWalk({
11508
+ subject: "Shared config",
11509
+ limits: {
11510
+ maxValues: MAX_SHARED_CONFIG_VALUES,
11511
+ maxStringChars: MAX_SHARED_CONFIG_STRING_CHARS,
11512
+ maxDepth: 64
11513
+ }
11514
+ }));
11515
+ }
11516
+ /**
11517
+ * Refuse a container that is already on the descent path. Unlike the
11518
+ * frontmatter cleaner, which drops such a cycle and keeps the rest of the
11519
+ * document, a shared config file is refused outright: silently dropping part
11520
+ * of a user's settings file would let a later write-back persist the loss.
11521
+ */
11522
+ function enterSharedConfigContainer(walk, container) {
11523
+ if (walk.isAncestor(container)) throw new Error("Shared config contains a value that refers back to itself (a circular YAML alias); refusing to process it");
11524
+ walk.enter(container);
11525
+ }
11526
+ function sanitizeSharedConfigValueBounded(value, walk) {
11527
+ if (typeof value === "string") {
11528
+ walk.chargeValue(value.length);
11529
+ return value;
11530
+ }
11531
+ walk.chargeValue();
11532
+ if (Array.isArray(value)) {
11533
+ enterSharedConfigContainer(walk, value);
11534
+ const items = value.map((item) => sanitizeSharedConfigValueBounded(item, walk));
11535
+ walk.leave(value);
11536
+ return items;
11537
+ }
11478
11538
  if (value === null || typeof value !== "object" || value instanceof Date) return value;
11539
+ enterSharedConfigContainer(walk, value);
11479
11540
  const result = {};
11480
11541
  for (const [key, nested] of Object.entries(value)) {
11542
+ walk.chargeChars(key.length);
11543
+ const sanitized = sanitizeSharedConfigValueBounded(nested, walk);
11481
11544
  if (isPrototypePollutionKey(key)) continue;
11482
- result[key] = sanitizeSharedConfigValue(nested);
11545
+ result[key] = sanitized;
11483
11546
  }
11547
+ walk.leave(value);
11484
11548
  return result;
11485
11549
  }
11486
11550
  /**
@@ -11509,7 +11573,12 @@ function parseSharedConfig({ format, fileContent, filePath, invalidRootPolicy =
11509
11573
  throw new Error(`Failed to parse shared config${at}: ${formatError(error)}`, { cause: error });
11510
11574
  }
11511
11575
  if (parsed === void 0 || parsed === null) return {};
11512
- const sanitized = sanitizeSharedConfigValue(parsed);
11576
+ let sanitized;
11577
+ try {
11578
+ sanitized = sanitizeSharedConfigValue(parsed);
11579
+ } catch (error) {
11580
+ throw new Error(`Failed to parse shared config${at}: ${formatError(error)}`, { cause: error });
11581
+ }
11513
11582
  if (!isPlainObject$1(sanitized)) {
11514
11583
  if (invalidRootPolicy === "error") throw new Error(`Failed to parse shared config${at}: expected a mapping at the root`);
11515
11584
  return {};
@@ -68635,4 +68704,4 @@ async function importChecksCore(params) {
68635
68704
  //#endregion
68636
68705
  export { RulesyncCheckFrontmatterSchema as $, ALL_TOOL_TARGETS_WITH_WILDCARD as $t, FACTORYDROID_DIR as A, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as An, fileExists as At, RulesyncSkillFrontmatterSchema as B, stripControlCharacters as Bn, readFileContent as Bt, CODEXCLI_BASH_RULES_FILE_NAME as C, RULESYNC_PERMISSIONS_LEGACY_FILE_NAME as Cn, assertDirectoryIfExists as Ct, CLAUDECODE_MEMORIES_DIR_NAME as D, RULESYNC_RULES_RELATIVE_DIR_PATH as Dn, createTempDirectory as Dt, CLAUDECODE_LOCAL_RULE_FILE_NAME as E, RULESYNC_RELATIVE_DIR_PATH as En, checkPathTraversal as Et, AUGMENTCODE_SETTINGS_LOCAL_FILE_NAME as F, formatError as Fn, isSymlink as Ft, RulesyncIgnore as G, removeFileStrict as Gt, RulesyncRuleFrontmatterSchema as H, stripHiddenCharacters as Hn, removeDirectory as Ht, getLocalSkillDirNames as I, truncateText as In, listDirectoryEntryNames as It, resolveRulesyncSourceWritePath as J, runWithDirectoryRollback as Jt, RulesyncHooks as K, removeTempDirectory as Kt, RulesyncSubagent as L, hasDeceptiveHiddenCharacters as Ln, listFilePathsRecursively as Lt, caseFoldIdentity as M, ALL_FEATURES as Mn, getHomeDirectory as Mt, groupSpellingsByCaseFoldedIdentity as N, ALL_FEATURES_WITH_WILDCARD as Nn, isFileNotFoundError as Nt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as O, RULESYNC_SKILLS_RELATIVE_DIR_PATH as On, directoryExists as Ot, AUGMENTCODE_DIR as P, DEPRECATED_FEATURE_REPLACEMENTS as Pn, isFileSystemError as Pt, RulesyncCheck as Q, ALL_TOOL_TARGETS as Qt, RulesyncSubagentFrontmatterSchema as R, hasEnclosingMarkOutsideKeycap as Rn, listSubdirectoryNames as Rt, ChecksProcessor as S, RULESYNC_PERMISSIONS_FILE_NAME as Sn, applyFileMode as St, CLAUDECODE_DIR as T, RULESYNC_PERMISSIONS_SCHEMA_URL as Tn, assertWritablePathInsideRoot as Tt, RulesyncPermissions as U, removeDirectoryStrict as Ut, RulesyncRule as V, stripControlCharactersKeepingLineFeeds as Vn, readFileContentOrNull as Vt, RulesyncMcp as W, removeFile as Wt, RulesyncCommand as X, writeFileBuffer as Xt, parseJsonc as Y, toPosixPath as Yt, RulesyncCommandFrontmatterSchema as Z, writeFileContent as Zt, IgnoreProcessor as _, RULESYNC_MCP_FILE_NAME as _n, withFallbackLoggerTarget as _t, getProcessorRegistryEntry as a, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as an, mergeInputRootConfigs as at, QWENCODE_DIR as b, RULESYNC_MCP_SCHEMA_URL as bn, CLIError as bt, RulesProcessor as c, RULESYNC_CONFIG_RELATIVE_FILE_PATH as cn, ConfigFileSchema as ct, CODEBUDDY_DIR as d, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as dn, findControlCharacter as dt, PACKAGING_TOOL_TARGETS as en, stringifyFrontmatter as et, CODEBUDDY_LOCAL_RULE_FILE_NAME as f, RULESYNC_HOOKS_FILE_NAME as fn, ConsoleLogger as ft, McpProcessor as g, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as gn, warnOnConflictingFlags as gt, shortenToWidth as h, RULESYNC_IGNORE_RELATIVE_FILE_PATH as hn, fallbackLogger as ht, inspectInputRoots as i, RULESYNC_AIIGNORE_FILE_NAME as in, ConfigResolver as it, FACTORYDROID_SETTINGS_LOCAL_FILE_NAME as j, parseCommaSeparatedList as jn, getFileSize as jt, CLAUDECODE_SKILLS_DIR_PATH as k, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as kn, ensureDir as kt, SubagentsProcessor as l, RULESYNC_CONFIG_SCHEMA_URL as ln, GITIGNORE_DESTINATION_KEY as lt, displayWidthOf as m, RULESYNC_HOOKS_RELATIVE_FILE_PATH as mn, WarningCollectingLogger as mt, formatSourceLoadFailure as n, CURATED_RULES_FEATURE_SUBDIR as nn, SHARED_USER_MANAGED_CONFIG_PATHS as nt, convertFromTool as o, RULESYNC_CHECKS_RELATIVE_DIR_PATH as on, resolveEffectiveInputRoots as ot, ELLIPSIS_WIDTH as p, RULESYNC_HOOKS_LEGACY_FILE_NAME as pn, JsonLogger as pt, getRulesyncSourceCandidates as q, resolvePath as qt, generate as r, MAX_FILE_SIZE as rn, SKILL_FILE_NAME as rt, isPackagingToolTarget as s, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as sn, CONFLICTING_TARGET_PAIRS as st, importFromTool as t, ToolTargetSchema as tn, loadYaml as tt, SkillsProcessor as u, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as un, SourceEntrySchema as ut, HooksProcessor as v, RULESYNC_MCP_LEGACY_FILE_NAME as vn, resetRunWarningState as vt, CODEXCLI_DIR as w, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as wn, assertTreeContainsNoSymlinks as wt, QWENCODE_LOCAL_RULE_FILE_NAME as x, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as xn, ErrorCodes as xt, CommandsProcessor as y, RULESYNC_MCP_RELATIVE_FILE_PATH as yn, withWarnOnceScope as yt, RulesyncSkill as z, quoteForLog as zn, pathEscapesRoot as zt };
68637
68706
 
68638
- //# sourceMappingURL=import-BKqbq4Ut.js.map
68707
+ //# sourceMappingURL=import-DUE1P1zV.js.map