@forgezero/agent 0.1.39 → 0.1.40

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/dist/fz.js CHANGED
@@ -1766,7 +1766,7 @@ function eddsa(Point, cHash, eddsaOpts = {}) {
1766
1766
  const msg = concatBytes2(...msgs);
1767
1767
  return modN_LE(hash(domain(msg, abytes2(context, undefined, "context"), !!prehash)));
1768
1768
  }
1769
- function sign2(msg, secretKey, options = {}) {
1769
+ function sign(msg, secretKey, options = {}) {
1770
1770
  validateObject(options, {}, {}, "options");
1771
1771
  msg = abytes2(msg, undefined, "message");
1772
1772
  if (prehash)
@@ -1855,7 +1855,7 @@ function eddsa(Point, cHash, eddsaOpts = {}) {
1855
1855
  return Object.freeze({
1856
1856
  keygen: createKeygen(randomSecretKey, getPublicKey),
1857
1857
  getPublicKey,
1858
- sign: sign2,
1858
+ sign,
1859
1859
  verify,
1860
1860
  utils,
1861
1861
  Point,
@@ -1875,168 +1875,14 @@ var init_edwards = __esm(() => {
1875
1875
  _8n2 = /* @__PURE__ */ BigInt(8);
1876
1876
  });
1877
1877
 
1878
- // ../node_modules/.bun/@noble+curves@2.3.0/node_modules/@noble/curves/abstract/montgomery.js
1879
- function cmask(P, swap) {
1880
- return P + swap - (swap >> _1n5 << _1n5);
1881
- }
1882
- function cswap(P) {
1883
- const offset = BigInt(6) * P;
1884
- return (mask, x_2, x_3) => {
1885
- const sum = x_2 + x_3;
1886
- const d = offset + x_3 - x_2;
1887
- const a = (d * mask + x_2) % P;
1888
- return { x_2: a, x_3: sum - a };
1889
- };
1890
- }
1891
- function validateOpts(curve) {
1892
- validateObject(curve, {
1893
- P: "bigint",
1894
- type: "string",
1895
- adjustScalarBytes: "function",
1896
- powPminus2: "function"
1897
- }, {
1898
- randomBytes: "function",
1899
- scalarMultBase: "function"
1900
- });
1901
- return Object.freeze({ ...curve });
1902
- }
1903
- function montgomery(curveDef) {
1904
- const CURVE = validateOpts(curveDef);
1905
- const { P, type, adjustScalarBytes, powPminus2, randomBytes: rand } = CURVE;
1906
- const mulBaseHook = CURVE.scalarMultBase;
1907
- const is25519 = type === "x25519";
1908
- if (!is25519 && type !== "x448")
1909
- throw new Error("invalid type");
1910
- const randomBytes_ = rand === undefined ? randomBytes2 : rand;
1911
- const montgomeryBits = is25519 ? 255 : 448;
1912
- const swap = cswap(P);
1913
- const fieldLen = is25519 ? 32 : 56;
1914
- const Gu = is25519 ? BigInt(9) : BigInt(5);
1915
- const a24 = is25519 ? BigInt(121665) : BigInt(39081);
1916
- const minScalar = is25519 ? _2n3 ** BigInt(254) : _2n3 ** BigInt(447);
1917
- const maxAdded = is25519 ? BigInt(8) * (_2n3 ** BigInt(251) - _1n5) : BigInt(4) * (_2n3 ** BigInt(445) - _1n5);
1918
- const maxScalar = minScalar + maxAdded + _1n5;
1919
- const modP = (n) => mod(n, P);
1920
- const GuBytes = encodeU(Gu);
1921
- function encodeU(u) {
1922
- return numberToBytesLE(modP(u), fieldLen);
1923
- }
1924
- function decodeU(u) {
1925
- const _u = copyBytes(abytes2(u, fieldLen, "uCoordinate"));
1926
- if (is25519)
1927
- _u[31] &= 127;
1928
- return modP(bytesToNumberLE(_u));
1929
- }
1930
- function decodeScalar(scalar) {
1931
- return bytesToNumberLE(adjustScalarBytes(copyBytes(abytes2(scalar, fieldLen, "scalar"))));
1932
- }
1933
- const lowOrderU = new Set(is25519 ? [
1934
- _0n5,
1935
- _1n5,
1936
- P - _1n5,
1937
- BigInt("325606250916557431795983626356110631294008115727848805560023387167927233504"),
1938
- BigInt("39382357235489614581723060781553021112529911719440698176882885853963445705823")
1939
- ] : [_0n5, _1n5, P - _1n5]);
1940
- function scalarMult(scalar, u) {
1941
- const pointU = decodeU(u);
1942
- if (lowOrderU.has(pointU))
1943
- throw new Error("invalid private or public key received");
1944
- const pu = montgomeryLadder(pointU, decodeScalar(scalar));
1945
- if (pu === _0n5)
1946
- throw new Error("invalid private or public key received");
1947
- return encodeU(pu);
1948
- }
1949
- function scalarMultBase(scalar) {
1950
- if (mulBaseHook === undefined)
1951
- return scalarMult(scalar, GuBytes);
1952
- const k = decodeScalar(scalar);
1953
- aInRange("scalar", k, minScalar, maxScalar);
1954
- const pu = modP(mulBaseHook(k));
1955
- if (pu === _0n5)
1956
- throw new Error("invalid private or public key received");
1957
- return encodeU(pu);
1958
- }
1959
- const getPublicKey = scalarMultBase;
1960
- const getSharedSecret = scalarMult;
1961
- function montgomeryLadder(u, scalar) {
1962
- aInRange("u", u, _0n5, P);
1963
- aInRange("scalar", scalar, minScalar, maxScalar);
1964
- const k = scalar;
1965
- const x_1 = u;
1966
- let x_2 = _1n5;
1967
- let z_2 = _0n5;
1968
- let x_3 = u;
1969
- let z_3 = _1n5;
1970
- const kx = k ^ k >> _1n5;
1971
- for (let t = BigInt(montgomeryBits - 1);t >= _0n5; t--) {
1972
- const mask2 = cmask(P, kx >> t);
1973
- ({ x_2, x_3 } = swap(mask2, x_2, x_3));
1974
- ({ x_2: z_2, x_3: z_3 } = swap(mask2, z_2, z_3));
1975
- const A = x_2 + z_2;
1976
- const AA = modP(A * A);
1977
- const B = x_2 - z_2;
1978
- const BB = modP(B * B);
1979
- const E = AA - BB;
1980
- const C = x_3 + z_3;
1981
- const D = x_3 - z_3;
1982
- const DA = modP(D * A);
1983
- const CB = modP(C * B);
1984
- const dacb = DA + CB;
1985
- const da_cb = DA - CB;
1986
- x_3 = modP(dacb * dacb);
1987
- z_3 = modP(x_1 * modP(da_cb * da_cb));
1988
- x_2 = modP(AA * BB);
1989
- z_2 = modP(E * (AA + modP(a24 * E)));
1990
- }
1991
- const mask = cmask(P, k);
1992
- ({ x_2, x_3 } = swap(mask, x_2, x_3));
1993
- ({ x_2: z_2, x_3: z_3 } = swap(mask, z_2, z_3));
1994
- const z2 = powPminus2(z_2);
1995
- return modP(x_2 * z2);
1996
- }
1997
- const lengths = {
1998
- secretKey: fieldLen,
1999
- publicKey: fieldLen,
2000
- seed: fieldLen
2001
- };
2002
- const randomSecretKey = (seed) => {
2003
- seed = seed === undefined ? randomBytes_(fieldLen) : seed;
2004
- abytes2(seed, lengths.seed, "seed");
2005
- return seed;
2006
- };
2007
- const utils = { randomSecretKey };
2008
- Object.freeze(lengths);
2009
- Object.freeze(utils);
2010
- return Object.freeze({
2011
- keygen: createKeygen(randomSecretKey, getPublicKey),
2012
- getSharedSecret,
2013
- getPublicKey,
2014
- scalarMult,
2015
- scalarMultBase,
2016
- utils,
2017
- GuBytes: GuBytes.slice(),
2018
- lengths
2019
- });
2020
- }
2021
- var _0n5, _1n5, _2n3;
2022
- var init_montgomery = __esm(() => {
2023
- init_utils2();
2024
- init_curve();
2025
- init_modular();
2026
- /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
2027
- _0n5 = /* @__PURE__ */ BigInt(0);
2028
- _1n5 = /* @__PURE__ */ BigInt(1);
2029
- _2n3 = /* @__PURE__ */ BigInt(2);
2030
- });
2031
-
2032
1878
  // ../node_modules/.bun/@noble+curves@2.3.0/node_modules/@noble/curves/ed25519.js
2033
1879
  function ed25519_pow_2_252_3(x) {
2034
1880
  const _10n = BigInt(10), _20n = BigInt(20), _40n = BigInt(40), _80n = BigInt(80);
2035
1881
  const P = ed25519_CURVE_p;
2036
1882
  const x2 = x * x % P;
2037
1883
  const b2 = x2 * x % P;
2038
- const b4 = pow2(b2, _2n4, P) * b2 % P;
2039
- const b5 = pow2(b4, _1n6, P) * x % P;
1884
+ const b4 = pow2(b2, _2n3, P) * b2 % P;
1885
+ const b5 = pow2(b4, _1n5, P) * x % P;
2040
1886
  const b10 = pow2(b5, _5n2, P) * b5 % P;
2041
1887
  const b20 = pow2(b10, _10n, P) * b10 % P;
2042
1888
  const b40 = pow2(b20, _20n, P) * b20 % P;
@@ -2044,7 +1890,7 @@ function ed25519_pow_2_252_3(x) {
2044
1890
  const b160 = pow2(b80, _80n, P) * b80 % P;
2045
1891
  const b240 = pow2(b160, _80n, P) * b80 % P;
2046
1892
  const b250 = pow2(b240, _10n, P) * b10 % P;
2047
- const pow_p_5_8 = pow2(b250, _2n4, P) * x % P;
1893
+ const pow_p_5_8 = pow2(b250, _2n3, P) * x % P;
2048
1894
  return { pow_p_5_8, b2 };
2049
1895
  }
2050
1896
  function adjustScalarBytes(bytes) {
@@ -2075,7 +1921,7 @@ function uvRatio(u, v) {
2075
1921
  }
2076
1922
  function toMontgomery(point) {
2077
1923
  const { y } = point;
2078
- return Fp.toBytes(Fp.div(_1n6 + y, _1n6 - y));
1924
+ return Fp.toBytes(Fp.div(_1n5 + y, _1n5 - y));
2079
1925
  }
2080
1926
  function toMontgomerySecret(secretKey) {
2081
1927
  const size = ed25519_Point.Fp.BYTES;
@@ -2085,18 +1931,15 @@ function toMontgomerySecret(secretKey) {
2085
1931
  function ed(opts) {
2086
1932
  return eddsa(ed25519_Point, sha512, Object.assign({ adjustScalarBytes, toMontgomery, toMontgomerySecret, zip215: true }, opts));
2087
1933
  }
2088
- var _0n6, _1n6, _2n4, _3n2, _5n2, _8n3, ed25519_CURVE_p, ed25519_CURVE, ED25519_SQRT_M1, ed25519_Point, Fp, ed25519, x25519;
1934
+ var _1n5, _2n3, _5n2, _8n3, ed25519_CURVE_p, ed25519_CURVE, ED25519_SQRT_M1, ed25519_Point, Fp, ed25519;
2089
1935
  var init_ed25519 = __esm(() => {
2090
1936
  init_sha2();
2091
1937
  init_utils();
2092
1938
  init_edwards();
2093
1939
  init_modular();
2094
- init_montgomery();
2095
1940
  /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
2096
- _0n6 = /* @__PURE__ */ BigInt(0);
2097
- _1n6 = /* @__PURE__ */ BigInt(1);
2098
- _2n4 = /* @__PURE__ */ BigInt(2);
2099
- _3n2 = /* @__PURE__ */ BigInt(3);
1941
+ _1n5 = /* @__PURE__ */ BigInt(1);
1942
+ _2n3 = /* @__PURE__ */ BigInt(2);
2100
1943
  _5n2 = /* @__PURE__ */ BigInt(5);
2101
1944
  _8n3 = /* @__PURE__ */ BigInt(8);
2102
1945
  ed25519_CURVE_p = /* @__PURE__ */ BigInt("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed");
@@ -2113,26 +1956,6 @@ var init_ed25519 = __esm(() => {
2113
1956
  ed25519_Point = /* @__PURE__ */ edwards(ed25519_CURVE, { uvRatio });
2114
1957
  Fp = /* @__PURE__ */ (() => ed25519_Point.Fp)();
2115
1958
  ed25519 = /* @__PURE__ */ ed({});
2116
- x25519 = /* @__PURE__ */ (() => {
2117
- const P = ed25519_CURVE_p;
2118
- const powPminus2 = (x) => {
2119
- const { pow_p_5_8, b2 } = ed25519_pow_2_252_3(x);
2120
- return mod(pow2(pow_p_5_8, _3n2, P) * b2, P);
2121
- };
2122
- return montgomery({
2123
- P,
2124
- type: "x25519",
2125
- powPminus2,
2126
- adjustScalarBytes,
2127
- scalarMultBase: (k) => {
2128
- const kn = mod(k, ed25519_Point.Fn.ORDER);
2129
- if (kn === _0n6)
2130
- return _0n6;
2131
- const p = ed25519_Point.BASE.multiply(kn);
2132
- return mod((p.Z + p.Y) * powPminus2(mod(p.Z - p.Y, P)), P);
2133
- }
2134
- });
2135
- })();
2136
1959
  });
2137
1960
 
2138
1961
  // ../node_modules/.bun/@noble+hashes@2.2.0/node_modules/@noble/hashes/utils.js
@@ -2347,7 +2170,7 @@ function asafenumber2(value, title = "") {
2347
2170
  function hexToNumber2(hex) {
2348
2171
  if (typeof hex !== "string")
2349
2172
  throw new TypeError("hex string expected, got " + typeof hex);
2350
- return hex === "" ? _0n7 : BigInt("0x" + hex);
2173
+ return hex === "" ? _0n5 : BigInt("0x" + hex);
2351
2174
  }
2352
2175
  function bytesToNumberBE2(bytes) {
2353
2176
  return hexToNumber2(bytesToHex3(bytes));
@@ -2400,10 +2223,10 @@ function aInRange2(title, n, min, max) {
2400
2223
  throw new RangeError("expected valid " + title + ": " + min + " <= n < " + max + ", got " + n);
2401
2224
  }
2402
2225
  function bitLen2(n) {
2403
- if (n < _0n7)
2226
+ if (n < _0n5)
2404
2227
  throw new Error("expected non-negative bigint, got " + n);
2405
2228
  let len;
2406
- for (len = 0;n > _0n7; n >>= _1n7, len += 1)
2229
+ for (len = 0;n > _0n5; n >>= _1n6, len += 1)
2407
2230
  ;
2408
2231
  return len;
2409
2232
  }
@@ -2424,7 +2247,7 @@ function validateObject2(object, fields = {}, optFields = {}) {
2424
2247
  iter(fields, false);
2425
2248
  iter(optFields, true);
2426
2249
  }
2427
- var abytes4 = (value, length, title) => abytes3(value, length, title), anumber4, bytesToHex4, concatBytes4 = (...arrays) => concatBytes3(...arrays), hexToBytes4 = (hex) => hexToBytes3(hex), isBytes4, randomBytes4 = (bytesLength) => randomBytes3(bytesLength), _0n7, _1n7, isPosBig2 = (n) => typeof n === "bigint" && _0n7 <= n, bitMask2 = (n) => (_1n7 << BigInt(n)) - _1n7, notImplemented2 = () => {
2250
+ var abytes4 = (value, length, title) => abytes3(value, length, title), anumber4, bytesToHex4, concatBytes4 = (...arrays) => concatBytes3(...arrays), hexToBytes4 = (hex) => hexToBytes3(hex), isBytes4, randomBytes4 = (bytesLength) => randomBytes3(bytesLength), _0n5, _1n6, isPosBig2 = (n) => typeof n === "bigint" && _0n5 <= n, bitMask2 = (n) => (_1n6 << BigInt(n)) - _1n6, notImplemented2 = () => {
2428
2251
  throw new Error("not implemented");
2429
2252
  };
2430
2253
  var init_utils4 = __esm(() => {
@@ -2433,8 +2256,8 @@ var init_utils4 = __esm(() => {
2433
2256
  anumber4 = anumber3;
2434
2257
  bytesToHex4 = bytesToHex3;
2435
2258
  isBytes4 = isBytes3;
2436
- _0n7 = /* @__PURE__ */ BigInt(0);
2437
- _1n7 = /* @__PURE__ */ BigInt(1);
2259
+ _0n5 = /* @__PURE__ */ BigInt(0);
2260
+ _1n6 = /* @__PURE__ */ BigInt(1);
2438
2261
  });
2439
2262
 
2440
2263
  // ../node_modules/.bun/@noble+hashes@2.2.0/node_modules/@noble/hashes/_u64.js
@@ -2634,28 +2457,28 @@ class Keccak {
2634
2457
  return to;
2635
2458
  }
2636
2459
  }
2637
- var _0n8, _1n8, _2n5, _7n2, _256n, _0x71n, SHA3_PI, SHA3_ROTL, _SHA3_IOTA, IOTAS, SHA3_IOTA_H, SHA3_IOTA_L, rotlH = (h, l, s) => s > 32 ? rotlBH(h, l, s) : rotlSH(h, l, s), rotlL = (h, l, s) => s > 32 ? rotlBL(h, l, s) : rotlSL(h, l, s), genKeccak = (suffix, blockLen, outputLen, info = {}) => createHasher2(() => new Keccak(blockLen, suffix, outputLen), info), sha3_256, sha3_512, genShake = (suffix, blockLen, outputLen, info = {}) => createHasher2((opts = {}) => new Keccak(blockLen, suffix, opts.dkLen === undefined ? outputLen : opts.dkLen, true), info), shake128, shake256;
2460
+ var _0n6, _1n7, _2n4, _7n2, _256n, _0x71n, SHA3_PI, SHA3_ROTL, _SHA3_IOTA, IOTAS, SHA3_IOTA_H, SHA3_IOTA_L, rotlH = (h, l, s) => s > 32 ? rotlBH(h, l, s) : rotlSH(h, l, s), rotlL = (h, l, s) => s > 32 ? rotlBL(h, l, s) : rotlSL(h, l, s), genKeccak = (suffix, blockLen, outputLen, info = {}) => createHasher2(() => new Keccak(blockLen, suffix, outputLen), info), sha3_256, sha3_512, genShake = (suffix, blockLen, outputLen, info = {}) => createHasher2((opts = {}) => new Keccak(blockLen, suffix, opts.dkLen === undefined ? outputLen : opts.dkLen, true), info), shake128, shake256;
2638
2461
  var init_sha3 = __esm(() => {
2639
2462
  init__u642();
2640
2463
  init_utils3();
2641
- _0n8 = BigInt(0);
2642
- _1n8 = BigInt(1);
2643
- _2n5 = BigInt(2);
2464
+ _0n6 = BigInt(0);
2465
+ _1n7 = BigInt(1);
2466
+ _2n4 = BigInt(2);
2644
2467
  _7n2 = BigInt(7);
2645
2468
  _256n = BigInt(256);
2646
2469
  _0x71n = BigInt(113);
2647
2470
  SHA3_PI = [];
2648
2471
  SHA3_ROTL = [];
2649
2472
  _SHA3_IOTA = [];
2650
- for (let round = 0, R = _1n8, x = 1, y = 0;round < 24; round++) {
2473
+ for (let round = 0, R = _1n7, x = 1, y = 0;round < 24; round++) {
2651
2474
  [x, y] = [y, (2 * x + 3 * y) % 5];
2652
2475
  SHA3_PI.push(2 * (5 * y + x));
2653
2476
  SHA3_ROTL.push((round + 1) * (round + 2) / 2 % 64);
2654
- let t = _0n8;
2477
+ let t = _0n6;
2655
2478
  for (let j = 0;j < 7; j++) {
2656
- R = (R << _1n8 ^ (R >> _7n2) * _0x71n) % _256n;
2657
- if (R & _2n5)
2658
- t ^= _1n8 << (_1n8 << BigInt(j)) - _1n8;
2479
+ R = (R << _1n7 ^ (R >> _7n2) * _0x71n) % _256n;
2480
+ if (R & _2n4)
2481
+ t ^= _1n7 << (_1n7 << BigInt(j)) - _1n7;
2659
2482
  }
2660
2483
  _SHA3_IOTA.push(t);
2661
2484
  }
@@ -2765,12 +2588,12 @@ function equalBytes2(a, b) {
2765
2588
  function copyBytes3(bytes) {
2766
2589
  return Uint8Array.from(abytes3(bytes));
2767
2590
  }
2768
- function validateOpts2(opts) {
2591
+ function validateOpts(opts) {
2769
2592
  if (Object.prototype.toString.call(opts) !== "[object Object]")
2770
2593
  throw new TypeError("expected valid options object");
2771
2594
  }
2772
2595
  function validateVerOpts(opts) {
2773
- validateOpts2(opts);
2596
+ validateOpts(opts);
2774
2597
  if (opts.context !== undefined)
2775
2598
  abytes3(opts.context, undefined, "opts.context");
2776
2599
  }
@@ -3011,7 +2834,7 @@ var init__crystals = __esm(() => {
3011
2834
 
3012
2835
  // ../node_modules/.bun/@noble+post-quantum@0.6.1/node_modules/@noble/post-quantum/ml-dsa.js
3013
2836
  function validateInternalOpts(opts) {
3014
- validateOpts2(opts);
2837
+ validateOpts(opts);
3015
2838
  if (opts.externalMu !== undefined)
3016
2839
  abool2(opts.externalMu, "opts.externalMu");
3017
2840
  }
@@ -3520,30 +3343,30 @@ var init_ml_dsa = __esm(() => {
3520
3343
 
3521
3344
  // ../node_modules/.bun/@noble+curves@2.2.0/node_modules/@noble/curves/abstract/modular.js
3522
3345
  function mod2(a, b) {
3523
- if (b <= _0n9)
3346
+ if (b <= _0n7)
3524
3347
  throw new Error("mod: expected positive modulus, got " + b);
3525
3348
  const result = a % b;
3526
- return result >= _0n9 ? result : b + result;
3349
+ return result >= _0n7 ? result : b + result;
3527
3350
  }
3528
3351
  function pow22(x, power, modulo) {
3529
- if (power < _0n9)
3352
+ if (power < _0n7)
3530
3353
  throw new Error("pow2: expected non-negative exponent, got " + power);
3531
3354
  let res = x;
3532
- while (power-- > _0n9) {
3355
+ while (power-- > _0n7) {
3533
3356
  res *= res;
3534
3357
  res %= modulo;
3535
3358
  }
3536
3359
  return res;
3537
3360
  }
3538
3361
  function invert2(number, modulo) {
3539
- if (number === _0n9)
3362
+ if (number === _0n7)
3540
3363
  throw new Error("invert: expected non-zero number");
3541
- if (modulo <= _0n9)
3364
+ if (modulo <= _0n7)
3542
3365
  throw new Error("invert: expected positive modulus, got " + modulo);
3543
3366
  let a = mod2(number, modulo);
3544
3367
  let b = modulo;
3545
- let x = _0n9, y = _1n9, u = _1n9, v = _0n9;
3546
- while (a !== _0n9) {
3368
+ let x = _0n7, y = _1n8, u = _1n8, v = _0n7;
3369
+ while (a !== _0n7) {
3547
3370
  const q = b / a;
3548
3371
  const r = b - a * q;
3549
3372
  const m = x - u * q;
@@ -3551,7 +3374,7 @@ function invert2(number, modulo) {
3551
3374
  b = a, a = r, x = u, y = v, u = m, v = n;
3552
3375
  }
3553
3376
  const gcd = b;
3554
- if (gcd !== _1n9)
3377
+ if (gcd !== _1n8)
3555
3378
  throw new Error("invert: does not exist");
3556
3379
  return mod2(x, modulo);
3557
3380
  }
@@ -3562,7 +3385,7 @@ function assertIsSquare2(Fp2, root, n) {
3562
3385
  }
3563
3386
  function sqrt3mod42(Fp2, n) {
3564
3387
  const F2 = Fp2;
3565
- const p1div4 = (F2.ORDER + _1n9) / _4n4;
3388
+ const p1div4 = (F2.ORDER + _1n8) / _4n4;
3566
3389
  const root = F2.pow(n, p1div4);
3567
3390
  assertIsSquare2(F2, root, n);
3568
3391
  return root;
@@ -3570,10 +3393,10 @@ function sqrt3mod42(Fp2, n) {
3570
3393
  function sqrt5mod82(Fp2, n) {
3571
3394
  const F2 = Fp2;
3572
3395
  const p5div8 = (F2.ORDER - _5n3) / _8n4;
3573
- const n2 = F2.mul(n, _2n6);
3396
+ const n2 = F2.mul(n, _2n5);
3574
3397
  const v = F2.pow(n2, p5div8);
3575
3398
  const nv = F2.mul(n, v);
3576
- const i = F2.mul(F2.mul(nv, _2n6), v);
3399
+ const i = F2.mul(F2.mul(nv, _2n5), v);
3577
3400
  const root = F2.mul(nv, F2.sub(i, F2.ONE));
3578
3401
  assertIsSquare2(F2, root, n);
3579
3402
  return root;
@@ -3602,15 +3425,15 @@ function sqrt9mod162(P) {
3602
3425
  };
3603
3426
  }
3604
3427
  function tonelliShanks2(P) {
3605
- if (P < _3n3)
3428
+ if (P < _3n2)
3606
3429
  throw new Error("sqrt is not defined for small field");
3607
- let Q2 = P - _1n9;
3430
+ let Q2 = P - _1n8;
3608
3431
  let S = 0;
3609
- while (Q2 % _2n6 === _0n9) {
3610
- Q2 /= _2n6;
3432
+ while (Q2 % _2n5 === _0n7) {
3433
+ Q2 /= _2n5;
3611
3434
  S++;
3612
3435
  }
3613
- let Z = _2n6;
3436
+ let Z = _2n5;
3614
3437
  const _Fp = Field2(P);
3615
3438
  while (FpLegendre2(_Fp, Z) === 1) {
3616
3439
  if (Z++ > 1000)
@@ -3619,7 +3442,7 @@ function tonelliShanks2(P) {
3619
3442
  if (S === 1)
3620
3443
  return sqrt3mod42;
3621
3444
  let cc = _Fp.pow(Z, Q2);
3622
- const Q1div2 = (Q2 + _1n9) / _2n6;
3445
+ const Q1div2 = (Q2 + _1n8) / _2n5;
3623
3446
  return function tonelliSlow(Fp2, n) {
3624
3447
  const F2 = Fp2;
3625
3448
  if (F2.is0(n))
@@ -3641,7 +3464,7 @@ function tonelliShanks2(P) {
3641
3464
  if (i === M)
3642
3465
  throw new Error("Cannot find square root");
3643
3466
  }
3644
- const exponent = _1n9 << BigInt(M - i - 1);
3467
+ const exponent = _1n8 << BigInt(M - i - 1);
3645
3468
  const b = F2.pow(c, exponent);
3646
3469
  M = i;
3647
3470
  c = F2.sqr(b);
@@ -3652,7 +3475,7 @@ function tonelliShanks2(P) {
3652
3475
  };
3653
3476
  }
3654
3477
  function FpSqrt2(P) {
3655
- if (P % _4n4 === _3n3)
3478
+ if (P % _4n4 === _3n2)
3656
3479
  return sqrt3mod42;
3657
3480
  if (P % _8n4 === _5n3)
3658
3481
  return sqrt5mod82;
@@ -3675,25 +3498,25 @@ function validateField2(field) {
3675
3498
  asafenumber2(field.BITS, "BITS");
3676
3499
  if (field.BYTES < 1 || field.BITS < 1)
3677
3500
  throw new Error("invalid field: expected BYTES/BITS > 0");
3678
- if (field.ORDER <= _1n9)
3501
+ if (field.ORDER <= _1n8)
3679
3502
  throw new Error("invalid field: expected ORDER > 1, got " + field.ORDER);
3680
3503
  return field;
3681
3504
  }
3682
3505
  function FpPow(Fp2, num, power) {
3683
3506
  const F2 = Fp2;
3684
- if (power < _0n9)
3507
+ if (power < _0n7)
3685
3508
  throw new Error("invalid exponent, negatives unsupported");
3686
- if (power === _0n9)
3509
+ if (power === _0n7)
3687
3510
  return F2.ONE;
3688
- if (power === _1n9)
3511
+ if (power === _1n8)
3689
3512
  return num;
3690
3513
  let p = F2.ONE;
3691
3514
  let d = num;
3692
- while (power > _0n9) {
3693
- if (power & _1n9)
3515
+ while (power > _0n7) {
3516
+ if (power & _1n8)
3694
3517
  p = F2.mul(p, d);
3695
3518
  d = F2.sqr(d);
3696
- power >>= _1n9;
3519
+ power >>= _1n8;
3697
3520
  }
3698
3521
  return p;
3699
3522
  }
@@ -3717,7 +3540,7 @@ function FpInvertBatch2(Fp2, nums, passZero = false) {
3717
3540
  }
3718
3541
  function FpLegendre2(Fp2, n) {
3719
3542
  const F2 = Fp2;
3720
- const p1mod2 = (F2.ORDER - _1n9) / _2n6;
3543
+ const p1mod2 = (F2.ORDER - _1n8) / _2n5;
3721
3544
  const powered = F2.pow(n, p1mod2);
3722
3545
  const yes = F2.eql(powered, F2.ONE);
3723
3546
  const zero = F2.eql(powered, F2.ZERO);
@@ -3729,7 +3552,7 @@ function FpLegendre2(Fp2, n) {
3729
3552
  function nLength2(n, nBitLength) {
3730
3553
  if (nBitLength !== undefined)
3731
3554
  anumber4(nBitLength);
3732
- if (n <= _0n9)
3555
+ if (n <= _0n7)
3733
3556
  throw new Error("invalid n length: expected positive n, got " + n);
3734
3557
  if (nBitLength !== undefined && nBitLength < 1)
3735
3558
  throw new Error("invalid n length: expected positive bit length, got " + nBitLength);
@@ -3746,12 +3569,12 @@ class _Field2 {
3746
3569
  BITS;
3747
3570
  BYTES;
3748
3571
  isLE;
3749
- ZERO = _0n9;
3750
- ONE = _1n9;
3572
+ ZERO = _0n7;
3573
+ ONE = _1n8;
3751
3574
  _lengths;
3752
3575
  _mod;
3753
3576
  constructor(ORDER, opts = {}) {
3754
- if (ORDER <= _1n9)
3577
+ if (ORDER <= _1n8)
3755
3578
  throw new Error("invalid field: expected ORDER > 1, got " + ORDER);
3756
3579
  let _nbitLength = undefined;
3757
3580
  this.isLE = false;
@@ -3781,16 +3604,16 @@ class _Field2 {
3781
3604
  isValid(num) {
3782
3605
  if (typeof num !== "bigint")
3783
3606
  throw new TypeError("invalid field element: expected bigint, got " + typeof num);
3784
- return _0n9 <= num && num < this.ORDER;
3607
+ return _0n7 <= num && num < this.ORDER;
3785
3608
  }
3786
3609
  is0(num) {
3787
- return num === _0n9;
3610
+ return num === _0n7;
3788
3611
  }
3789
3612
  isValidNot0(num) {
3790
3613
  return !this.is0(num) && this.isValid(num);
3791
3614
  }
3792
3615
  isOdd(num) {
3793
- return (num & _1n9) === _1n9;
3616
+ return (num & _1n8) === _1n8;
3794
3617
  }
3795
3618
  neg(num) {
3796
3619
  return mod2(-num, this.ORDER);
@@ -3873,14 +3696,14 @@ class _Field2 {
3873
3696
  function Field2(ORDER, opts = {}) {
3874
3697
  return new _Field2(ORDER, opts);
3875
3698
  }
3876
- var _0n9, _1n9, _2n6, _3n3, _4n4, _5n3, _7n3, _8n4, _9n2, _16n2, isNegativeLE2 = (num, modulo) => (mod2(num, modulo) & _1n9) === _1n9, FIELD_FIELDS2, FIELD_SQRT2;
3699
+ var _0n7, _1n8, _2n5, _3n2, _4n4, _5n3, _7n3, _8n4, _9n2, _16n2, isNegativeLE2 = (num, modulo) => (mod2(num, modulo) & _1n8) === _1n8, FIELD_FIELDS2, FIELD_SQRT2;
3877
3700
  var init_modular2 = __esm(() => {
3878
3701
  init_utils4();
3879
3702
  /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
3880
- _0n9 = /* @__PURE__ */ BigInt(0);
3881
- _1n9 = /* @__PURE__ */ BigInt(1);
3882
- _2n6 = /* @__PURE__ */ BigInt(2);
3883
- _3n3 = /* @__PURE__ */ BigInt(3);
3703
+ _0n7 = /* @__PURE__ */ BigInt(0);
3704
+ _1n8 = /* @__PURE__ */ BigInt(1);
3705
+ _2n5 = /* @__PURE__ */ BigInt(2);
3706
+ _3n2 = /* @__PURE__ */ BigInt(3);
3884
3707
  _4n4 = /* @__PURE__ */ BigInt(4);
3885
3708
  _5n3 = /* @__PURE__ */ BigInt(5);
3886
3709
  _7n3 = /* @__PURE__ */ BigInt(7);
@@ -3938,7 +3761,7 @@ function calcOffsets(n, window, wOpts) {
3938
3761
  let nextN = n >> shiftBy;
3939
3762
  if (wbits > windowSize) {
3940
3763
  wbits -= maxNumber;
3941
- nextN += _1n10;
3764
+ nextN += _1n9;
3942
3765
  }
3943
3766
  const offsetStart = window * windowSize;
3944
3767
  const offset = offsetStart + Math.abs(wbits) - 1;
@@ -3952,7 +3775,7 @@ function getW(P) {
3952
3775
  return pointWindowSizes2.get(P) || 1;
3953
3776
  }
3954
3777
  function assert0(n) {
3955
- if (n !== _0n10)
3778
+ if (n !== _0n8)
3956
3779
  throw new Error("invalid wNAF");
3957
3780
  }
3958
3781
 
@@ -3969,11 +3792,11 @@ class wNAF {
3969
3792
  }
3970
3793
  _unsafeLadder(elm, n, p = this.ZERO) {
3971
3794
  let d = elm;
3972
- while (n > _0n10) {
3973
- if (n & _1n10)
3795
+ while (n > _0n8) {
3796
+ if (n & _1n9)
3974
3797
  p = p.add(d);
3975
3798
  d = d.double();
3976
- n >>= _1n10;
3799
+ n >>= _1n9;
3977
3800
  }
3978
3801
  return p;
3979
3802
  }
@@ -4014,7 +3837,7 @@ class wNAF {
4014
3837
  wNAFUnsafe(W, precomputes, n, acc = this.ZERO) {
4015
3838
  const wo = calcWOpts(W, this.bits);
4016
3839
  for (let window = 0;window < wo.windows; window++) {
4017
- if (n === _0n10)
3840
+ if (n === _0n8)
4018
3841
  break;
4019
3842
  const { nextN, offset, isZero, isNeg } = calcOffsets(n, window, wo);
4020
3843
  n = nextN;
@@ -4076,7 +3899,7 @@ function createCurveFields2(type, CURVE, curveOpts = {}, FpFnLE) {
4076
3899
  throw new Error(`expected valid ${type} CURVE object`);
4077
3900
  for (const p of ["p", "n", "h"]) {
4078
3901
  const val = CURVE[p];
4079
- if (!(typeof val === "bigint" && val > _0n10))
3902
+ if (!(typeof val === "bigint" && val > _0n8))
4080
3903
  throw new Error(`CURVE.${p} must be positive bigint`);
4081
3904
  }
4082
3905
  const Fp2 = createField2(CURVE.p, curveOpts.Fp, FpFnLE);
@@ -4096,13 +3919,13 @@ function createKeygen2(randomSecretKey, getPublicKey) {
4096
3919
  return { secretKey, publicKey: getPublicKey(secretKey) };
4097
3920
  };
4098
3921
  }
4099
- var _0n10, _1n10, pointPrecomputes, pointWindowSizes2;
3922
+ var _0n8, _1n9, pointPrecomputes, pointWindowSizes2;
4100
3923
  var init_curve2 = __esm(() => {
4101
3924
  init_utils4();
4102
3925
  init_modular2();
4103
3926
  /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
4104
- _0n10 = /* @__PURE__ */ BigInt(0);
4105
- _1n10 = /* @__PURE__ */ BigInt(1);
3927
+ _0n8 = /* @__PURE__ */ BigInt(0);
3928
+ _1n9 = /* @__PURE__ */ BigInt(1);
4106
3929
  pointPrecomputes = new WeakMap;
4107
3930
  pointWindowSizes2 = new WeakMap;
4108
3931
  });
@@ -4122,19 +3945,19 @@ function edwards2(params, extraOpts = {}) {
4122
3945
  let CURVE = validated.CURVE;
4123
3946
  const { h: cofactor } = CURVE;
4124
3947
  validateObject2(opts, {}, { uvRatio: "function" });
4125
- const MASK = _2n7 << BigInt(Fn.BYTES * 8) - _1n11;
3948
+ const MASK = _2n6 << BigInt(Fn.BYTES * 8) - _1n10;
4126
3949
  const modP = (n) => Fp2.create(n);
4127
3950
  const uvRatio2 = opts.uvRatio === undefined ? (u, v) => {
4128
3951
  try {
4129
3952
  return { isValid: true, value: Fp2.sqrt(Fp2.div(u, v)) };
4130
3953
  } catch (e) {
4131
- return { isValid: false, value: _0n11 };
3954
+ return { isValid: false, value: _0n9 };
4132
3955
  }
4133
3956
  } : opts.uvRatio;
4134
3957
  if (!isEdValidXY2(Fp2, CURVE, CURVE.Gx, CURVE.Gy))
4135
3958
  throw new Error("bad curve params: generator point");
4136
3959
  function acoord(title, n, banZero = false) {
4137
- const min = banZero ? _1n11 : _0n11;
3960
+ const min = banZero ? _1n10 : _0n9;
4138
3961
  aInRange2("coordinate " + title, n, min, MASK);
4139
3962
  return n;
4140
3963
  }
@@ -4144,8 +3967,8 @@ function edwards2(params, extraOpts = {}) {
4144
3967
  }
4145
3968
 
4146
3969
  class Point {
4147
- static BASE = new Point(CURVE.Gx, CURVE.Gy, _1n11, modP(CURVE.Gx * CURVE.Gy));
4148
- static ZERO = new Point(_0n11, _1n11, _1n11, _0n11);
3970
+ static BASE = new Point(CURVE.Gx, CURVE.Gy, _1n10, modP(CURVE.Gx * CURVE.Gy));
3971
+ static ZERO = new Point(_0n9, _1n10, _1n10, _0n9);
4149
3972
  static Fp = Fp2;
4150
3973
  static Fn = Fn;
4151
3974
  X;
@@ -4168,7 +3991,7 @@ function edwards2(params, extraOpts = {}) {
4168
3991
  const { x, y } = p || {};
4169
3992
  acoord("x", x);
4170
3993
  acoord("y", y);
4171
- return new Point(x, y, _1n11, modP(x * y));
3994
+ return new Point(x, y, _1n10, modP(x * y));
4172
3995
  }
4173
3996
  static fromBytes(bytes, zip215 = false) {
4174
3997
  const len = Fp2.BYTES;
@@ -4180,16 +4003,16 @@ function edwards2(params, extraOpts = {}) {
4180
4003
  normed[len - 1] = lastByte & ~128;
4181
4004
  const y = bytesToNumberLE2(normed);
4182
4005
  const max = zip215 ? MASK : Fp2.ORDER;
4183
- aInRange2("point.y", y, _0n11, max);
4006
+ aInRange2("point.y", y, _0n9, max);
4184
4007
  const y2 = modP(y * y);
4185
- const u = modP(y2 - _1n11);
4008
+ const u = modP(y2 - _1n10);
4186
4009
  const v = modP(d * y2 - a);
4187
4010
  let { isValid, value: x } = uvRatio2(u, v);
4188
4011
  if (!isValid)
4189
4012
  throw new Error("bad point: invalid y coordinate");
4190
- const isXOdd = (x & _1n11) === _1n11;
4013
+ const isXOdd = (x & _1n10) === _1n10;
4191
4014
  const isLastByteOdd = (lastByte & 128) !== 0;
4192
- if (!zip215 && x === _0n11 && isLastByteOdd)
4015
+ if (!zip215 && x === _0n9 && isLastByteOdd)
4193
4016
  throw new Error("bad point: x=0 and x_0=1");
4194
4017
  if (isLastByteOdd !== isXOdd)
4195
4018
  x = modP(-x);
@@ -4207,7 +4030,7 @@ function edwards2(params, extraOpts = {}) {
4207
4030
  precompute(windowSize = 8, isLazy = true) {
4208
4031
  wnaf.createCache(this, windowSize);
4209
4032
  if (!isLazy)
4210
- this.multiply(_2n7);
4033
+ this.multiply(_2n6);
4211
4034
  return this;
4212
4035
  }
4213
4036
  assertValidity() {
@@ -4251,7 +4074,7 @@ function edwards2(params, extraOpts = {}) {
4251
4074
  const { X: X1, Y: Y1, Z: Z1 } = this;
4252
4075
  const A = modP(X1 * X1);
4253
4076
  const B = modP(Y1 * Y1);
4254
- const C = modP(_2n7 * modP(Z1 * Z1));
4077
+ const C = modP(_2n6 * modP(Z1 * Z1));
4255
4078
  const D2 = modP(a * A);
4256
4079
  const x1y1 = X1 + Y1;
4257
4080
  const E = modP(modP(x1y1 * x1y1) - A - B);
@@ -4296,9 +4119,9 @@ function edwards2(params, extraOpts = {}) {
4296
4119
  multiplyUnsafe(scalar) {
4297
4120
  if (!Fn.isValid(scalar))
4298
4121
  throw new RangeError("invalid scalar: expected 0 <= sc < curve.n");
4299
- if (scalar === _0n11)
4122
+ if (scalar === _0n9)
4300
4123
  return Point.ZERO;
4301
- if (this.is0() || scalar === _1n11)
4124
+ if (this.is0() || scalar === _1n10)
4302
4125
  return this;
4303
4126
  return wnaf.unsafe(this, scalar, (p) => normalizeZ2(Point, p));
4304
4127
  }
@@ -4319,20 +4142,20 @@ function edwards2(params, extraOpts = {}) {
4319
4142
  const y = modP(Y * iz);
4320
4143
  const zz = Fp2.mul(Z, iz);
4321
4144
  if (is0)
4322
- return { x: _0n11, y: _1n11 };
4323
- if (zz !== _1n11)
4145
+ return { x: _0n9, y: _1n10 };
4146
+ if (zz !== _1n10)
4324
4147
  throw new Error("invZ was invalid");
4325
4148
  return { x, y };
4326
4149
  }
4327
4150
  clearCofactor() {
4328
- if (cofactor === _1n11)
4151
+ if (cofactor === _1n10)
4329
4152
  return this;
4330
4153
  return this.multiplyUnsafe(cofactor);
4331
4154
  }
4332
4155
  toBytes() {
4333
4156
  const { x, y } = this.toAffine();
4334
4157
  const bytes = Fp2.toBytes(y);
4335
- bytes[bytes.length - 1] |= x & _1n11 ? 128 : 0;
4158
+ bytes[bytes.length - 1] |= x & _1n10 ? 128 : 0;
4336
4159
  return bytes;
4337
4160
  }
4338
4161
  toHex() {
@@ -4417,19 +4240,19 @@ class PrimeEdwardsPoint2 {
4417
4240
  return this;
4418
4241
  }
4419
4242
  }
4420
- var _0n11, _1n11, _2n7, _8n5;
4243
+ var _0n9, _1n10, _2n6, _8n5;
4421
4244
  var init_edwards2 = __esm(() => {
4422
4245
  init_utils4();
4423
4246
  init_curve2();
4424
4247
  /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
4425
- _0n11 = /* @__PURE__ */ BigInt(0);
4426
- _1n11 = /* @__PURE__ */ BigInt(1);
4427
- _2n7 = /* @__PURE__ */ BigInt(2);
4248
+ _0n9 = /* @__PURE__ */ BigInt(0);
4249
+ _1n10 = /* @__PURE__ */ BigInt(1);
4250
+ _2n6 = /* @__PURE__ */ BigInt(2);
4428
4251
  _8n5 = /* @__PURE__ */ BigInt(8);
4429
4252
  });
4430
4253
 
4431
4254
  // ../node_modules/.bun/@noble+curves@2.2.0/node_modules/@noble/curves/abstract/montgomery.js
4432
- function validateOpts3(curve) {
4255
+ function validateOpts2(curve) {
4433
4256
  validateObject2(curve, {
4434
4257
  P: "bigint",
4435
4258
  type: "string",
@@ -4440,8 +4263,8 @@ function validateOpts3(curve) {
4440
4263
  });
4441
4264
  return Object.freeze({ ...curve });
4442
4265
  }
4443
- function montgomery2(curveDef) {
4444
- const CURVE = validateOpts3(curveDef);
4266
+ function montgomery(curveDef) {
4267
+ const CURVE = validateOpts2(curveDef);
4445
4268
  const { P, type, adjustScalarBytes: adjustScalarBytes2, powPminus2, randomBytes: rand } = CURVE;
4446
4269
  const is25519 = type === "x25519";
4447
4270
  if (!is25519 && type !== "x448")
@@ -4451,9 +4274,9 @@ function montgomery2(curveDef) {
4451
4274
  const fieldLen = is25519 ? 32 : 56;
4452
4275
  const Gu = is25519 ? BigInt(9) : BigInt(5);
4453
4276
  const a24 = is25519 ? BigInt(121665) : BigInt(39081);
4454
- const minScalar = is25519 ? _2n8 ** BigInt(254) : _2n8 ** BigInt(447);
4455
- const maxAdded = is25519 ? BigInt(8) * _2n8 ** BigInt(251) - _1n12 : BigInt(4) * _2n8 ** BigInt(445) - _1n12;
4456
- const maxScalar = minScalar + maxAdded + _1n12;
4277
+ const minScalar = is25519 ? _2n7 ** BigInt(254) : _2n7 ** BigInt(447);
4278
+ const maxAdded = is25519 ? BigInt(8) * _2n7 ** BigInt(251) - _1n11 : BigInt(4) * _2n7 ** BigInt(445) - _1n11;
4279
+ const maxScalar = minScalar + maxAdded + _1n11;
4457
4280
  const modP = (n) => mod2(n, P);
4458
4281
  const GuBytes = encodeU(Gu);
4459
4282
  function encodeU(u) {
@@ -4470,7 +4293,7 @@ function montgomery2(curveDef) {
4470
4293
  }
4471
4294
  function scalarMult(scalar, u) {
4472
4295
  const pu = montgomeryLadder(decodeU(u), decodeScalar(scalar));
4473
- if (pu === _0n12)
4296
+ if (pu === _0n10)
4474
4297
  throw new Error("invalid private or public key received");
4475
4298
  return encodeU(pu);
4476
4299
  }
@@ -4479,27 +4302,27 @@ function montgomery2(curveDef) {
4479
4302
  }
4480
4303
  const getPublicKey = scalarMultBase;
4481
4304
  const getSharedSecret = scalarMult;
4482
- function cswap2(swap, x_2, x_3) {
4305
+ function cswap(swap, x_2, x_3) {
4483
4306
  const dummy = modP(swap * (x_2 - x_3));
4484
4307
  x_2 = modP(x_2 - dummy);
4485
4308
  x_3 = modP(x_3 + dummy);
4486
4309
  return { x_2, x_3 };
4487
4310
  }
4488
4311
  function montgomeryLadder(u, scalar) {
4489
- aInRange2("u", u, _0n12, P);
4312
+ aInRange2("u", u, _0n10, P);
4490
4313
  aInRange2("scalar", scalar, minScalar, maxScalar);
4491
4314
  const k = scalar;
4492
4315
  const x_1 = u;
4493
- let x_2 = _1n12;
4494
- let z_2 = _0n12;
4316
+ let x_2 = _1n11;
4317
+ let z_2 = _0n10;
4495
4318
  let x_3 = u;
4496
- let z_3 = _1n12;
4497
- let swap = _0n12;
4498
- for (let t = BigInt(montgomeryBits - 1);t >= _0n12; t--) {
4499
- const k_t = k >> t & _1n12;
4319
+ let z_3 = _1n11;
4320
+ let swap = _0n10;
4321
+ for (let t = BigInt(montgomeryBits - 1);t >= _0n10; t--) {
4322
+ const k_t = k >> t & _1n11;
4500
4323
  swap ^= k_t;
4501
- ({ x_2, x_3 } = cswap2(swap, x_2, x_3));
4502
- ({ x_2: z_2, x_3: z_3 } = cswap2(swap, z_2, z_3));
4324
+ ({ x_2, x_3 } = cswap(swap, x_2, x_3));
4325
+ ({ x_2: z_2, x_3: z_3 } = cswap(swap, z_2, z_3));
4503
4326
  swap = k_t;
4504
4327
  const A = x_2 + z_2;
4505
4328
  const AA = modP(A * A);
@@ -4517,8 +4340,8 @@ function montgomery2(curveDef) {
4517
4340
  x_2 = modP(AA * BB);
4518
4341
  z_2 = modP(E * (AA + modP(a24 * E)));
4519
4342
  }
4520
- ({ x_2, x_3 } = cswap2(swap, x_2, x_3));
4521
- ({ x_2: z_2, x_3: z_3 } = cswap2(swap, z_2, z_3));
4343
+ ({ x_2, x_3 } = cswap(swap, x_2, x_3));
4344
+ ({ x_2: z_2, x_3: z_3 } = cswap(swap, z_2, z_3));
4522
4345
  const z2 = powPminus2(z_2);
4523
4346
  return modP(x_2 * z2);
4524
4347
  }
@@ -4546,15 +4369,15 @@ function montgomery2(curveDef) {
4546
4369
  lengths
4547
4370
  });
4548
4371
  }
4549
- var _0n12, _1n12, _2n8;
4550
- var init_montgomery2 = __esm(() => {
4372
+ var _0n10, _1n11, _2n7;
4373
+ var init_montgomery = __esm(() => {
4551
4374
  init_utils4();
4552
4375
  init_curve2();
4553
4376
  init_modular2();
4554
4377
  /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
4555
- _0n12 = BigInt(0);
4556
- _1n12 = BigInt(1);
4557
- _2n8 = BigInt(2);
4378
+ _0n10 = BigInt(0);
4379
+ _1n11 = BigInt(1);
4380
+ _2n7 = BigInt(2);
4558
4381
  });
4559
4382
 
4560
4383
  // ../node_modules/.bun/@noble+hashes@2.2.0/node_modules/@noble/hashes/hmac.js
@@ -5330,8 +5153,8 @@ function ed25519_pow_2_252_32(x) {
5330
5153
  const P = ed25519_CURVE_p2;
5331
5154
  const x2 = x * x % P;
5332
5155
  const b2 = x2 * x % P;
5333
- const b4 = pow22(b2, _2n9, P) * b2 % P;
5334
- const b5 = pow22(b4, _1n13, P) * x % P;
5156
+ const b4 = pow22(b2, _2n8, P) * b2 % P;
5157
+ const b5 = pow22(b4, _1n12, P) * x % P;
5335
5158
  const b10 = pow22(b5, _5n4, P) * b5 % P;
5336
5159
  const b20 = pow22(b10, _10n, P) * b10 % P;
5337
5160
  const b40 = pow22(b20, _20n, P) * b20 % P;
@@ -5339,7 +5162,7 @@ function ed25519_pow_2_252_32(x) {
5339
5162
  const b160 = pow22(b80, _80n, P) * b80 % P;
5340
5163
  const b240 = pow22(b160, _80n, P) * b80 % P;
5341
5164
  const b250 = pow22(b240, _10n, P) * b10 % P;
5342
- const pow_p_5_8 = pow22(b250, _2n9, P) * x % P;
5165
+ const pow_p_5_8 = pow22(b250, _2n8, P) * x % P;
5343
5166
  return { pow_p_5_8, b2 };
5344
5167
  }
5345
5168
  function adjustScalarBytes2(bytes) {
@@ -5373,7 +5196,7 @@ function calcElligatorRistrettoMap(r0) {
5373
5196
  const P = ed25519_CURVE_p2;
5374
5197
  const mod3 = (n) => Fp2.create(n);
5375
5198
  const r = mod3(SQRT_M1 * r0 * r0);
5376
- const Ns = mod3((r + _1n13) * ONE_MINUS_D_SQ);
5199
+ const Ns = mod3((r + _1n12) * ONE_MINUS_D_SQ);
5377
5200
  let c = BigInt(-1);
5378
5201
  const D2 = mod3((c - d * r) * mod3(r + d));
5379
5202
  let { isValid: Ns_D_is_sq, value: s } = uvRatio2(Ns, D2);
@@ -5384,28 +5207,28 @@ function calcElligatorRistrettoMap(r0) {
5384
5207
  s = s_;
5385
5208
  if (!Ns_D_is_sq)
5386
5209
  c = r;
5387
- const Nt = mod3(c * (r - _1n13) * D_MINUS_ONE_SQ - D2);
5210
+ const Nt = mod3(c * (r - _1n12) * D_MINUS_ONE_SQ - D2);
5388
5211
  const s2 = s * s;
5389
5212
  const W0 = mod3((s + s) * D2);
5390
5213
  const W1 = mod3(Nt * SQRT_AD_MINUS_ONE);
5391
- const W2 = mod3(_1n13 - s2);
5392
- const W3 = mod3(_1n13 + s2);
5214
+ const W2 = mod3(_1n12 - s2);
5215
+ const W3 = mod3(_1n12 + s2);
5393
5216
  return new ed25519_Point2(mod3(W0 * W3), mod3(W2 * W1), mod3(W1 * W3), mod3(W0 * W2));
5394
5217
  }
5395
- var _0n13, _1n13, _2n9, _3n4, _5n4, _8n6, ed25519_CURVE_p2, ed25519_CURVE2, ED25519_SQRT_M12, ed25519_Point2, Fp2, Fn, x255192, SQRT_M1, SQRT_AD_MINUS_ONE, INVSQRT_A_MINUS_D, ONE_MINUS_D_SQ, D_MINUS_ONE_SQ, invertSqrt = (number) => uvRatio2(_1n13, number), MAX_255B, bytes255ToNumberLE = (bytes) => Fp2.create(bytesToNumberLE2(bytes) & MAX_255B), _RistrettoPoint, ristretto255_hasher;
5218
+ var _0n11, _1n12, _2n8, _3n3, _5n4, _8n6, ed25519_CURVE_p2, ed25519_CURVE2, ED25519_SQRT_M12, ed25519_Point2, Fp2, Fn, x25519, SQRT_M1, SQRT_AD_MINUS_ONE, INVSQRT_A_MINUS_D, ONE_MINUS_D_SQ, D_MINUS_ONE_SQ, invertSqrt = (number) => uvRatio2(_1n12, number), MAX_255B, bytes255ToNumberLE = (bytes) => Fp2.create(bytesToNumberLE2(bytes) & MAX_255B), _RistrettoPoint, ristretto255_hasher;
5396
5219
  var init_ed255192 = __esm(() => {
5397
5220
  init_sha22();
5398
5221
  init_utils3();
5399
5222
  init_edwards2();
5400
5223
  init_hash_to_curve();
5401
5224
  init_modular2();
5402
- init_montgomery2();
5225
+ init_montgomery();
5403
5226
  init_utils4();
5404
5227
  /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
5405
- _0n13 = /* @__PURE__ */ BigInt(0);
5406
- _1n13 = /* @__PURE__ */ BigInt(1);
5407
- _2n9 = /* @__PURE__ */ BigInt(2);
5408
- _3n4 = /* @__PURE__ */ BigInt(3);
5228
+ _0n11 = /* @__PURE__ */ BigInt(0);
5229
+ _1n12 = /* @__PURE__ */ BigInt(1);
5230
+ _2n8 = /* @__PURE__ */ BigInt(2);
5231
+ _3n3 = /* @__PURE__ */ BigInt(3);
5409
5232
  _5n4 = /* @__PURE__ */ BigInt(5);
5410
5233
  _8n6 = /* @__PURE__ */ BigInt(8);
5411
5234
  ed25519_CURVE_p2 = /* @__PURE__ */ BigInt("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed");
@@ -5422,14 +5245,14 @@ var init_ed255192 = __esm(() => {
5422
5245
  ed25519_Point2 = /* @__PURE__ */ edwards2(ed25519_CURVE2, { uvRatio: uvRatio2 });
5423
5246
  Fp2 = /* @__PURE__ */ (() => ed25519_Point2.Fp)();
5424
5247
  Fn = /* @__PURE__ */ (() => ed25519_Point2.Fn)();
5425
- x255192 = /* @__PURE__ */ (() => {
5248
+ x25519 = /* @__PURE__ */ (() => {
5426
5249
  const P = ed25519_CURVE_p2;
5427
- return montgomery2({
5250
+ return montgomery({
5428
5251
  P,
5429
5252
  type: "x25519",
5430
5253
  powPminus2: (x) => {
5431
5254
  const { pow_p_5_8, b2 } = ed25519_pow_2_252_32(x);
5432
- return mod2(pow22(pow_p_5_8, _3n4, P) * b2, P);
5255
+ return mod2(pow22(pow_p_5_8, _3n3, P) * b2, P);
5433
5256
  },
5434
5257
  adjustScalarBytes: adjustScalarBytes2
5435
5258
  });
@@ -5467,8 +5290,8 @@ var init_ed255192 = __esm(() => {
5467
5290
  if (!equalBytes(Fp2.toBytes(s), bytes) || isNegativeLE2(s, P))
5468
5291
  throw new Error("invalid ristretto255 encoding 1");
5469
5292
  const s2 = mod3(s * s);
5470
- const u1 = mod3(_1n13 + a * s2);
5471
- const u2 = mod3(_1n13 - a * s2);
5293
+ const u1 = mod3(_1n12 + a * s2);
5294
+ const u2 = mod3(_1n12 - a * s2);
5472
5295
  const u1_2 = mod3(u1 * u1);
5473
5296
  const u2_2 = mod3(u2 * u2);
5474
5297
  const v = mod3(a * d * u1_2 - u2_2);
@@ -5480,9 +5303,9 @@ var init_ed255192 = __esm(() => {
5480
5303
  x = mod3(-x);
5481
5304
  const y = mod3(u1 * Dy);
5482
5305
  const t = mod3(x * y);
5483
- if (!isValid || isNegativeLE2(t, P) || y === _0n13)
5306
+ if (!isValid || isNegativeLE2(t, P) || y === _0n11)
5484
5307
  throw new Error("invalid ristretto255 encoding 2");
5485
- return new _RistrettoPoint(new ed25519_Point2(x, y, _1n13, t));
5308
+ return new _RistrettoPoint(new ed25519_Point2(x, y, _1n12, t));
5486
5309
  }
5487
5310
  static fromHex(hex) {
5488
5311
  return _RistrettoPoint.fromBytes(hexToBytes3(hex));
@@ -6052,7 +5875,7 @@ var init_hybrid = __esm(() => {
6052
5875
  init_ml_kem();
6053
5876
  init_utils5();
6054
5877
  /*! noble-post-quantum - MIT License (c) 2024 Paul Miller (paulmillr.com) */
6055
- x25519kem = /* @__PURE__ */ ecdhKem(x255192);
5878
+ x25519kem = /* @__PURE__ */ ecdhKem(x25519);
6056
5879
  ml_kem768_x25519 = /* @__PURE__ */ (() => combineKEMS(32, 32, expandSeedXof(shake256), (pk, ct, ss) => sha3_256(concatBytes4(ss[0], ss[1], ct[1], pk[1], asciiToBytes("\\.//^\\"))), ml_kem768, x25519kem))();
6057
5880
  });
6058
5881
 
@@ -6066,29 +5889,68 @@ function deriveKeysFromSeed(seed) {
6066
5889
  const edPublic = ed25519.getPublicKey(edSecret);
6067
5890
  const mlKeys = ml_dsa65.keygen(mlSeed);
6068
5891
  mlSeed.fill(0);
6069
- return {
5892
+ const result = {
6070
5893
  ed25519: { publicKey: b64(edPublic), secretKey: b64(edSecret) },
6071
5894
  mlDsa: { publicKey: b64(mlKeys.publicKey), secretKey: b64(mlKeys.secretKey) }
6072
5895
  };
5896
+ edSecret.fill(0);
5897
+ mlKeys.secretKey.fill(0);
5898
+ return result;
5899
+ }
5900
+ function exactBytes(value, bytes) {
5901
+ if (typeof value !== "string" || value.length !== encodedLength(bytes) || !BASE64URL.test(value))
5902
+ return null;
5903
+ try {
5904
+ const decoded = un64(value);
5905
+ return decoded.length === bytes && b64(decoded) === value ? decoded : null;
5906
+ } catch {
5907
+ return null;
5908
+ }
5909
+ }
5910
+ function validIdentity(value) {
5911
+ return typeof value === "string" && value.length >= 1 && value.length <= 128 && !/[^A-Za-z0-9_.:@/-]/.test(value) && !/[\0\r\n]/.test(value);
5912
+ }
5913
+ function validResponsePublicKey(value) {
5914
+ return exactBytes(value, RESPONSE_PUBLIC_BYTES) !== null;
6073
5915
  }
6074
5916
  function generateResponseRecipient() {
6075
5917
  const pair = ml_kem768_x25519.keygen();
6076
5918
  return { publicKey: b64(pair.publicKey), secretKey: b64(pair.secretKey) };
6077
5919
  }
6078
5920
  async function openResponse(recipientSecretKey, requestBinding, envelope) {
6079
- if (envelope?.version !== 1)
5921
+ if (!requestBinding || requestBinding.length > 16384 || /[\0\r\n]/.test(requestBinding)) {
5922
+ throw new Error("response: malformed request binding");
5923
+ }
5924
+ if (envelope?.version !== 1 || envelope.suite !== RESPONSE_SEALING_SUITE) {
6080
5925
  throw new Error("response: unsupported sealed response");
6081
- const sharedSecret = ml_kem768_x25519.decapsulate(un64(envelope.kemCiphertext), un64(recipientSecretKey));
5926
+ }
5927
+ const kemCiphertext = exactBytes(envelope.kemCiphertext, RESPONSE_CIPHERTEXT_BYTES);
5928
+ const nonce = exactBytes(envelope.nonce, 12);
5929
+ if (!kemCiphertext || !nonce || typeof envelope.ciphertext !== "string" || envelope.ciphertext.length < encodedLength(16) || envelope.ciphertext.length > encodedLength(16 * 1024 * 1024) || !BASE64URL.test(envelope.ciphertext)) {
5930
+ throw new Error("response: malformed sealed response");
5931
+ }
5932
+ let encodedCiphertext;
5933
+ try {
5934
+ encodedCiphertext = un64(envelope.ciphertext);
5935
+ } catch {
5936
+ throw new Error("response: malformed sealed response");
5937
+ }
5938
+ if (encodedCiphertext.length < 16 || encodedCiphertext.length > 16 * 1024 * 1024 || b64(encodedCiphertext) !== envelope.ciphertext) {
5939
+ throw new Error("response: malformed sealed response");
5940
+ }
5941
+ const sharedSecret = ml_kem768_x25519.decapsulate(kemCiphertext, exactBytes(recipientSecretKey, RESPONSE_SECRET_BYTES) ?? (() => {
5942
+ throw new Error("response: malformed recipient secret key");
5943
+ })());
6082
5944
  const rawKey = responseKey(sharedSecret);
6083
5945
  sharedSecret.fill(0);
6084
5946
  const key = await crypto.subtle.importKey("raw", new Uint8Array(rawKey), "AES-GCM", false, ["decrypt"]);
6085
5947
  rawKey.fill(0);
6086
5948
  const decrypted = new Uint8Array(await crypto.subtle.decrypt({
6087
5949
  name: "AES-GCM",
6088
- iv: new Uint8Array(un64(envelope.nonce)),
5950
+ iv: new Uint8Array(nonce),
6089
5951
  additionalData: new Uint8Array(ENCODER.encode(requestBinding)),
6090
5952
  tagLength: 128
6091
- }, key, new Uint8Array(un64(envelope.ciphertext))));
5953
+ }, key, new Uint8Array(encodedCiphertext)));
6092
5954
  try {
6093
5955
  return JSON.parse(new TextDecoder().decode(decrypted));
6094
5956
  } finally {
@@ -6099,9 +5961,15 @@ function encodeSignatureHeader(envelope) {
6099
5961
  return b64(ENCODER.encode(JSON.stringify(SIGNATURE_FIELDS(envelope))));
6100
5962
  }
6101
5963
  function canonicalString(args) {
5964
+ if (!validIdentity(args.identity) || !/^[A-Za-z][A-Za-z0-9-]{0,31}$/.test(args.method) || /[\0\r\n]/.test(args.path) || /[\0\r\n]/.test(args.query) || !exactBytes(args.nonce, 16) || args.responseKey !== undefined && !validResponsePublicKey(args.responseKey)) {
5965
+ throw new Error("identity: malformed canonical request");
5966
+ }
6102
5967
  const body = typeof args.body === "string" ? ENCODER.encode(args.body) : args.body;
6103
5968
  const digest = Array.from(sha256(body), (byte) => byte.toString(16).padStart(2, "0")).join("");
6104
5969
  const fields = [
5970
+ "forgezero/request-signature/v1",
5971
+ REQUEST_SIGNATURE_SUITE,
5972
+ args.identity,
6105
5973
  args.method.toUpperCase(),
6106
5974
  args.path,
6107
5975
  args.query ?? "",
@@ -6109,16 +5977,17 @@ function canonicalString(args) {
6109
5977
  args.nonce,
6110
5978
  digest
6111
5979
  ];
6112
- if (args.responseKey)
6113
- fields.push(args.responseKey);
5980
+ fields.push(args.responseKey ?? "-");
6114
5981
  return fields.join(`
6115
5982
  `);
6116
5983
  }
6117
5984
  function signRequest(keys, nodeKey, args) {
6118
5985
  const timestamp = Math.floor(Date.now() / 1000);
6119
5986
  const nonce = b64(randomBytes6(16));
6120
- const message = ENCODER.encode(canonicalString({ ...args, query: args.query ?? "", timestamp, nonce }));
5987
+ const message = ENCODER.encode(canonicalString({ ...args, identity: nodeKey, query: args.query ?? "", timestamp, nonce }));
6121
5988
  return {
5989
+ version: 1,
5990
+ suite: REQUEST_SIGNATURE_SUITE,
6122
5991
  nodeKey,
6123
5992
  timestamp,
6124
5993
  nonce,
@@ -6126,7 +5995,13 @@ function signRequest(keys, nodeKey, args) {
6126
5995
  mlDsaSignature: b64(ml_dsa65.sign(message, un64(keys.mlDsa.secretKey)))
6127
5996
  };
6128
5997
  }
6129
- var ENCODER, b64, un64, randomBytes6 = (length) => crypto.getRandomValues(new Uint8Array(length)), RESPONSE_KEY_HEADER = "x-fz-response-key", responseKey = (sharedSecret) => hkdf(sha256, sharedSecret, undefined, ENCODER.encode("forgezero/response/ml-kem-768+x25519/v1"), 32), SIGNATURE_FIELDS = (envelope) => ({
5998
+ var ENCODER, b64, un64, randomBytes6 = (length) => crypto.getRandomValues(new Uint8Array(length)), RESPONSE_KEY_HEADER = "x-fz-response-key", REQUEST_SIGNATURE_SUITE = "ed25519+ml-dsa-65", RESPONSE_SEALING_SUITE = "ml-kem-768+x25519/aes-256-gcm", BASE64URL, encodedLength = (bytes) => Math.ceil(bytes * 4 / 3), length = (value, name) => {
5999
+ if (!Number.isSafeInteger(value) || value <= 0)
6000
+ throw new Error(`identity: ${name} length unavailable`);
6001
+ return value;
6002
+ }, ED_PUBLIC_BYTES, ED_SIGNATURE_BYTES, ML_PUBLIC_BYTES, ML_SIGNATURE_BYTES, RESPONSE_PUBLIC_BYTES, RESPONSE_SECRET_BYTES, RESPONSE_CIPHERTEXT_BYTES, responseKey = (sharedSecret) => hkdf(sha256, sharedSecret, undefined, ENCODER.encode("forgezero/response/ml-kem-768+x25519/v1"), 32), SIGNATURE_FIELDS = (envelope) => ({
6003
+ version: envelope.version,
6004
+ suite: envelope.suite,
6130
6005
  timestamp: envelope.timestamp,
6131
6006
  nonce: envelope.nonce,
6132
6007
  edSignature: envelope.edSignature,
@@ -6142,6 +6017,14 @@ var init_identity = __esm(() => {
6142
6017
  ENCODER = new TextEncoder;
6143
6018
  b64 = toBase64Url;
6144
6019
  un64 = fromBase64Url;
6020
+ BASE64URL = /^[A-Za-z0-9_-]+$/;
6021
+ ED_PUBLIC_BYTES = length(ed25519.lengths.publicKey, "Ed25519 public key");
6022
+ ED_SIGNATURE_BYTES = length(ed25519.lengths.signature, "Ed25519 signature");
6023
+ ML_PUBLIC_BYTES = length(ml_dsa65.lengths.publicKey, "ML-DSA-65 public key");
6024
+ ML_SIGNATURE_BYTES = length(ml_dsa65.lengths.signature, "ML-DSA-65 signature");
6025
+ RESPONSE_PUBLIC_BYTES = length(ml_kem768_x25519.lengths.publicKey, "hybrid response public key");
6026
+ RESPONSE_SECRET_BYTES = length(ml_kem768_x25519.lengths.secretKey, "hybrid response secret key");
6027
+ RESPONSE_CIPHERTEXT_BYTES = length(ml_kem768_x25519.lengths.cipherText, "hybrid response ciphertext");
6145
6028
  });
6146
6029
 
6147
6030
  // ../vault/dist/index.js
@@ -6189,8 +6072,8 @@ function signerFromApiKey(secret) {
6189
6072
  seed.fill(0);
6190
6073
  return {
6191
6074
  keyId,
6192
- async sign(request2) {
6193
- return encodeSignatureHeader(signRequest(keys, keyId, request2));
6075
+ async sign(request) {
6076
+ return encodeSignatureHeader(signRequest(keys, keyId, request));
6194
6077
  }
6195
6078
  };
6196
6079
  }
@@ -6459,13 +6342,13 @@ var VaultError, runtimeEnvironment = () => typeof process !== "undefined" && pro
6459
6342
  throw new VaultError("API_KEY_MALFORMED", "FORGEZERO_API_KEY is malformed.");
6460
6343
  }
6461
6344
  return Uint8Array.from(binary, (character) => character.charCodeAt(0));
6462
- }, requestAgentOnce = (socketPath, request2) => new Promise((resolveRequest, reject) => {
6345
+ }, requestAgentOnce = (socketPath, request) => new Promise((resolveRequest, reject) => {
6463
6346
  if (typeof process === "undefined" || typeof process.getBuiltinModule !== "function") {
6464
6347
  reject(new VaultError("MANAGED_RUNTIME_UNAVAILABLE", "The managed agent socket needs a Node or Bun server runtime."));
6465
6348
  return;
6466
6349
  }
6467
6350
  const net = process.getBuiltinModule("node:net");
6468
- const socket = net.connect(socketPath, () => socket.write(`${JSON.stringify(request2)}
6351
+ const socket = net.connect(socketPath, () => socket.write(`${JSON.stringify(request)}
6469
6352
  `));
6470
6353
  let buffer = "";
6471
6354
  socket.setTimeout(15000, () => {
@@ -6492,11 +6375,11 @@ var VaultError, runtimeEnvironment = () => typeof process !== "undefined" && pro
6492
6375
  });
6493
6376
  socket.on("end", () => reject(new VaultError("AGENT_UNAVAILABLE", "The local ForgeZero agent closed during a supervised handover.")));
6494
6377
  socket.on("error", (cause) => reject(new VaultError("AGENT_UNAVAILABLE", cause.message)));
6495
- }), managedAgentTransport = async (socketPath, request2) => {
6378
+ }), managedAgentTransport = async (socketPath, request) => {
6496
6379
  let last;
6497
6380
  for (let attempt = 0;attempt < 5; attempt += 1) {
6498
6381
  try {
6499
- return await requestAgentOnce(socketPath, request2);
6382
+ return await requestAgentOnce(socketPath, request);
6500
6383
  } catch (cause) {
6501
6384
  last = cause;
6502
6385
  if (cause?.code !== "AGENT_UNAVAILABLE" || attempt === 4)
@@ -6582,128 +6465,6 @@ function thresholdMode(id) {
6582
6465
  return THRESHOLD_MODES.find((mode) => mode.id === id);
6583
6466
  }
6584
6467
 
6585
- // ../runtime/dist/ssh-agent.js
6586
- import { Socket } from "net";
6587
- import { createHash, hkdfSync } from "crypto";
6588
- class SshAgentError extends Error {
6589
- }
6590
- var SSH_AGENTC_REQUEST_IDENTITIES = 11;
6591
- var SSH_AGENT_IDENTITIES_ANSWER = 12;
6592
- var SSH_AGENTC_SIGN_REQUEST = 13;
6593
- var SSH_AGENT_SIGN_RESPONSE = 14;
6594
- var CUSTODY_CHALLENGE = Buffer.from("forgezero/custody/ssh-agent/v1", "utf8");
6595
- function readString(buffer, offset) {
6596
- const length = buffer.readUInt32BE(offset);
6597
- const start = offset + 4;
6598
- return [buffer.subarray(start, start + length), start + length];
6599
- }
6600
- function writeString(value) {
6601
- const length = Buffer.alloc(4);
6602
- length.writeUInt32BE(value.length);
6603
- return Buffer.concat([length, value]);
6604
- }
6605
- function frame(payload) {
6606
- const length = Buffer.alloc(4);
6607
- length.writeUInt32BE(payload.length);
6608
- return Buffer.concat([length, payload]);
6609
- }
6610
- var AGENT_TIMEOUT_MS = 3000;
6611
- async function request(socketPath, payload) {
6612
- return new Promise((resolve, reject) => {
6613
- const socket = new Socket;
6614
- const chunks = [];
6615
- let expected = null;
6616
- let settled = false;
6617
- const fail = (message) => {
6618
- if (settled)
6619
- return;
6620
- settled = true;
6621
- socket.destroy();
6622
- reject(new SshAgentError(message));
6623
- };
6624
- socket.setTimeout(AGENT_TIMEOUT_MS, () => fail("SSH_AGENT_TIMEOUT"));
6625
- socket.on("error", () => fail("SSH_AGENT_UNREACHABLE"));
6626
- socket.on("connect", () => socket.write(frame(payload)));
6627
- socket.on("data", (chunk) => {
6628
- chunks.push(chunk);
6629
- const all = Buffer.concat(chunks);
6630
- if (expected === null && all.length >= 4)
6631
- expected = all.readUInt32BE(0);
6632
- if (expected !== null && all.length >= expected + 4) {
6633
- if (settled)
6634
- return;
6635
- settled = true;
6636
- socket.end();
6637
- resolve(all.subarray(4, expected + 4));
6638
- }
6639
- });
6640
- socket.on("close", () => {
6641
- if (expected === null)
6642
- fail("SSH_AGENT_CLOSED_EARLY");
6643
- });
6644
- socket.connect(socketPath);
6645
- });
6646
- }
6647
- function agentSocket(explicit) {
6648
- const path = explicit ?? process.env.SSH_AUTH_SOCK;
6649
- if (!path)
6650
- throw new SshAgentError("SSH_AUTH_SOCK_NOT_SET");
6651
- return path;
6652
- }
6653
- async function listIdentities(socketPath) {
6654
- const response = await request(agentSocket(socketPath), Buffer.from([SSH_AGENTC_REQUEST_IDENTITIES]));
6655
- if (response[0] !== SSH_AGENT_IDENTITIES_ANSWER) {
6656
- throw new SshAgentError("SSH_AGENT_BAD_RESPONSE");
6657
- }
6658
- const count = response.readUInt32BE(1);
6659
- const identities = [];
6660
- let offset = 5;
6661
- for (let index = 0;index < count; index += 1) {
6662
- const [blob, afterBlob] = readString(response, offset);
6663
- const [comment, afterComment] = readString(response, afterBlob);
6664
- offset = afterComment;
6665
- const [type] = readString(blob, 0);
6666
- identities.push({
6667
- blob,
6668
- comment: comment.toString("utf8"),
6669
- type: type.toString("utf8"),
6670
- fingerprint: `SHA256:${createHash("sha256").update(blob).digest("base64").replace(/=+$/, "")}`
6671
- });
6672
- }
6673
- return identities;
6674
- }
6675
- async function listCustodyIdentities(socketPath) {
6676
- return (await listIdentities(socketPath)).filter((id) => id.type === "ssh-ed25519");
6677
- }
6678
- async function signWithIdentity(identity, data, socketPath) {
6679
- const wrapped = await sign(identity.blob, Buffer.from(data), socketPath);
6680
- const [raw] = readString(wrapped, readString(wrapped, 0)[1]);
6681
- return new Uint8Array(raw);
6682
- }
6683
- async function sign(blob, data, socketPath) {
6684
- const payload = Buffer.concat([
6685
- Buffer.from([SSH_AGENTC_SIGN_REQUEST]),
6686
- writeString(blob),
6687
- writeString(data),
6688
- Buffer.alloc(4)
6689
- ]);
6690
- const response = await request(agentSocket(socketPath), payload);
6691
- if (response[0] !== SSH_AGENT_SIGN_RESPONSE) {
6692
- throw new SshAgentError("SSH_AGENT_SIGN_REFUSED");
6693
- }
6694
- const [signature] = readString(response, 1);
6695
- return signature;
6696
- }
6697
- async function deriveCustodyKey(identity, socketPath) {
6698
- if (identity.type !== "ssh-ed25519") {
6699
- throw new SshAgentError("SSH_KEY_TYPE_UNSUPPORTED");
6700
- }
6701
- const signature = await sign(identity.blob, CUSTODY_CHALLENGE, socketPath);
6702
- if (signature.length < 32)
6703
- throw new SshAgentError("SSH_AGENT_SIGNATURE_TOO_SHORT");
6704
- return new Uint8Array(hkdfSync("sha256", signature, identity.blob, Buffer.from("forgezero/custody/ssh-key/v1", "utf8"), 32));
6705
- }
6706
-
6707
6468
  // src/cli/run.ts
6708
6469
  class RunError extends Error {
6709
6470
  code;
@@ -6818,7 +6579,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
6818
6579
  var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
6819
6580
 
6820
6581
  // src/version.ts
6821
- var VERSION2 = "0.1.39";
6582
+ var VERSION2 = "0.1.40";
6822
6583
 
6823
6584
  // src/software.ts
6824
6585
  var BUN_INSTALLER_SHA256 = "bab8acfb046aac8c72407bdcce903957665d655d7acaa3e11c7c4616beae68dd";
@@ -6848,8 +6609,8 @@ var UBUNTU_2604_X64 = [
6848
6609
  },
6849
6610
  {
6850
6611
  requirement: { id: "arangodb", version: "3.11.14" },
6851
- check: `arangod --version 2>/dev/null | head -1 | grep -q '3.11.14'`,
6852
- install: `tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT; ` + `curl -fsSL 'https://download.arangodb.com/arangodb311/DEBIAN/amd64/arangodb3_3.11.14-1_amd64.deb' -o "$tmp/arangodb.deb"; ` + `echo "${ARANGO_SHA256} $tmp/arangodb.deb" | sha256sum -c -; ` + `DEBIAN_FRONTEND=noninteractive dpkg -i "$tmp/arangodb.deb" >/dev/null 2>&1 || ` + `DEBIAN_FRONTEND=noninteractive apt-get -y -f install`
6612
+ check: `arangod --version 2>/dev/null | head -1 | grep -q '3.11.14' && ` + `! systemctl is-active --quiet arangodb3.service && ` + `! systemctl is-enabled --quiet arangodb3.service`,
6613
+ install: `tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT; ` + `curl -fsSL 'https://download.arangodb.com/arangodb311/DEBIAN/amd64/arangodb3_3.11.14-1_amd64.deb' -o "$tmp/arangodb.deb"; ` + `echo "${ARANGO_SHA256} $tmp/arangodb.deb" | sha256sum -c -; ` + `DEBIAN_FRONTEND=noninteractive dpkg -i "$tmp/arangodb.deb" >/dev/null 2>&1 || ` + `DEBIAN_FRONTEND=noninteractive apt-get -y -f install; ` + `systemctl disable --now arangodb3.service`
6853
6614
  },
6854
6615
  {
6855
6616
  requirement: { id: "cloudflared", version: "2026.7.3" },
@@ -7931,27 +7692,6 @@ function renderPlan(plan) {
7931
7692
  `);
7932
7693
  }
7933
7694
 
7934
- // src/cli/custody.ts
7935
- var PROBE_TIMEOUT_MS = 2000;
7936
- function withTimeout(work, ms, label) {
7937
- return Promise.race([
7938
- work,
7939
- new Promise((_, reject) => setTimeout(() => reject(new SshAgentError(`SSH_AGENT_TIMEOUT:${label}`)), ms))
7940
- ]);
7941
- }
7942
- async function usableIdentities(socketPath) {
7943
- const candidates = await listCustodyIdentities(socketPath);
7944
- const usable = [];
7945
- for (const identity of candidates) {
7946
- const started = Date.now();
7947
- try {
7948
- await withTimeout(deriveCustodyKey(identity, socketPath), PROBE_TIMEOUT_MS, identity.fingerprint);
7949
- usable.push({ ...identity, responseMs: Date.now() - started });
7950
- } catch {}
7951
- }
7952
- return usable;
7953
- }
7954
-
7955
7695
  // ../node_modules/.bun/@noble+ciphers@2.2.0/node_modules/@noble/ciphers/utils.js
7956
7696
  /*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) */
7957
7697
  function isBytes5(a) {
@@ -7967,13 +7707,13 @@ function anumber5(n) {
7967
7707
  if (!Number.isSafeInteger(n) || n < 0)
7968
7708
  throw new RangeError("positive integer expected, got " + n);
7969
7709
  }
7970
- function abytes5(value, length, title = "") {
7710
+ function abytes5(value, length2, title = "") {
7971
7711
  const bytes = isBytes5(value);
7972
7712
  const len = value?.length;
7973
- const needsLen = length !== undefined;
7974
- if (!bytes || needsLen && len !== length) {
7713
+ const needsLen = length2 !== undefined;
7714
+ if (!bytes || needsLen && len !== length2) {
7975
7715
  const prefix = title && `"${title}" `;
7976
- const ofLen = needsLen ? ` of length ${length}` : "";
7716
+ const ofLen = needsLen ? ` of length ${length2}` : "";
7977
7717
  const got = bytes ? `length=${len}` : `type=${typeof value}`;
7978
7718
  const message = prefix + "expected Uint8Array" + ofLen + ", got " + got;
7979
7719
  if (!bytes)
@@ -8463,9 +8203,9 @@ var gcm = /* @__PURE__ */ wrapCipher({ blockSize: 16, nonceLength: 12, tagLength
8463
8203
  });
8464
8204
 
8465
8205
  // ../runtime/dist/custody-share.js
8466
- init_ed25519();
8467
8206
  init_hkdf();
8468
8207
  init_sha22();
8208
+ init_hybrid();
8469
8209
 
8470
8210
  // ../node_modules/.bun/@noble+hashes@2.2.0/node_modules/@noble/hashes/pbkdf2.js
8471
8211
  init_hmac();
@@ -11090,8 +10830,8 @@ var fromBase64 = (value) => {
11090
10830
  return bytes;
11091
10831
  };
11092
10832
  var utf8 = (value) => new TextEncoder().encode(value);
11093
- function deriveKey(secret, salt, info, length = KEY_BYTES) {
11094
- return hkdf(sha256, secret, salt, utf8(info), length);
10833
+ function deriveKey(secret, salt, info, length2 = KEY_BYTES) {
10834
+ return hkdf(sha256, secret, salt, utf8(info), length2);
11095
10835
  }
11096
10836
  function openWithKey(key, box, aad) {
11097
10837
  if (key.length !== KEY_BYTES)
@@ -11100,28 +10840,43 @@ function openWithKey(key, box, aad) {
11100
10840
  throw new Error(`custody: unknown algorithm ${box.alg}`);
11101
10841
  return gcm(key, fromBase64(box.nonce), utf8(aad)).decrypt(fromBase64(box.ciphertext));
11102
10842
  }
11103
- var WRAP_INFO = "forgezero:custody:wrap:v1";
11104
- var wrapKey = (shared, ephemeral, recipient) => hkdf(sha256, shared, concatBytes6(ephemeral, recipient), utf8(WRAP_INFO), 32);
11105
- function concatBytes6(left, right) {
11106
- const out = new Uint8Array(left.length + right.length);
11107
- out.set(left, 0);
11108
- out.set(right, left.length);
11109
- return out;
11110
- }
10843
+ var WRAP_INFO = "forgezero:custody:wrap:ml-kem-768+x25519:v2";
10844
+ var WRAP_SEED_SALT = utf8("forgezero:custody:wrapkey:ml-kem-768+x25519:v2");
10845
+ var wrapKey = (shared) => hkdf(sha256, shared, undefined, utf8(WRAP_INFO), 32);
11111
10846
  function openFromKey(recipientSecretKey, box, aad) {
11112
- const ephemeralPublic = fromBase64(box.ephemeral);
11113
- const recipientPublic = x25519.getPublicKey(recipientSecretKey);
11114
- const shared = x25519.getSharedSecret(recipientSecretKey, ephemeralPublic);
11115
- const key = wrapKey(shared, ephemeralPublic, recipientPublic);
10847
+ if (box?.version !== 2 || typeof box.kemCiphertext !== "string") {
10848
+ throw new Error("custody: unsupported sealed-to-key envelope");
10849
+ }
10850
+ if (recipientSecretKey.length !== ml_kem768_x25519.lengths.secretKey) {
10851
+ throw new Error("custody: invalid hybrid recipient secret key");
10852
+ }
10853
+ const ciphertext = fromBase64(box.kemCiphertext);
10854
+ if (ciphertext.length !== ml_kem768_x25519.lengths.cipherText) {
10855
+ throw new Error("custody: invalid hybrid KEM ciphertext");
10856
+ }
10857
+ const shared = Uint8Array.from(ml_kem768_x25519.decapsulate(ciphertext, recipientSecretKey));
10858
+ const key = wrapKey(shared);
11116
10859
  try {
11117
10860
  return openWithKey(key, box, aad);
11118
10861
  } finally {
11119
10862
  key.fill(0);
10863
+ shared.fill(0);
11120
10864
  }
11121
10865
  }
11122
10866
  function wrappingKeyPair(factorMaterial, info) {
11123
- const secretKey = hkdf(sha256, factorMaterial, utf8("forgezero:custody:wrapkey:v1"), utf8(info), 32);
11124
- return { secretKey, publicKey: x25519.getPublicKey(secretKey) };
10867
+ if (factorMaterial.length < 32) {
10868
+ throw new Error("custody: wrapping factor material must be at least 32 bytes");
10869
+ }
10870
+ const seed = hkdf(sha256, factorMaterial, WRAP_SEED_SALT, utf8(info), 32);
10871
+ try {
10872
+ const pair = ml_kem768_x25519.keygen(seed);
10873
+ return {
10874
+ secretKey: Uint8Array.from(pair.secretKey),
10875
+ publicKey: Uint8Array.from(pair.publicKey)
10876
+ };
10877
+ } finally {
10878
+ seed.fill(0);
10879
+ }
11125
10880
  }
11126
10881
  var ENCODER2 = new TextEncoder;
11127
10882
  var PHRASE_WORDS = 24;
@@ -11366,7 +11121,7 @@ function defaultProjectContext(root = process.cwd()) {
11366
11121
  "Update a truth source instead of copying architecture or progress into another document.",
11367
11122
  "Never report a feature as complete without running its declared verification."
11368
11123
  ],
11369
- nonAuthoritative: ["audit/"]
11124
+ nonAuthoritative: ["docs/audit/"]
11370
11125
  };
11371
11126
  }
11372
11127
  function renderProjectContext(manifest) {
@@ -11516,7 +11271,7 @@ function checkProjectContext(rootInput) {
11516
11271
  }
11517
11272
 
11518
11273
  // src/deploy-file.ts
11519
- import { createHash as createHash2 } from "crypto";
11274
+ import { createHash } from "crypto";
11520
11275
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
11521
11276
  import { basename, join as join3 } from "path";
11522
11277
 
@@ -11766,7 +11521,7 @@ var stable = (value) => {
11766
11521
  return JSON.stringify(value);
11767
11522
  };
11768
11523
  function deployDefinitionDigest(definition) {
11769
- return `sha256:${createHash2("sha256").update(stable(definition)).digest("hex")}`;
11524
+ return `sha256:${createHash("sha256").update(stable(definition)).digest("hex")}`;
11770
11525
  }
11771
11526
  var safeName = (value) => {
11772
11527
  const normalized = value.toLowerCase().replace(/^@[^/]+\//, "").replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 63);
@@ -12061,7 +11816,7 @@ function removeSession(api, realm, path = defaultSessionPath()) {
12061
11816
  }
12062
11817
 
12063
11818
  // src/bootstrap.ts
12064
- import { createHash as createHash3, createHmac, randomBytes as randomBytes7 } from "crypto";
11819
+ import { createHash as createHash2, createHmac, randomBytes as randomBytes7 } from "crypto";
12065
11820
  import {
12066
11821
  chmodSync as chmodSync2,
12067
11822
  existsSync as existsSync4,
@@ -12295,6 +12050,8 @@ function renderPlatformApiUnits(input) {
12295
12050
  throw new Error("Blue and green ports must differ.");
12296
12051
  const credentials = input.credentials.map((credential) => `LoadCredentialEncrypted=${credential.name}:${credential.encryptedPath}`).join(`
12297
12052
  `);
12053
+ const capacityEnvironment = input.capacityEnvironmentFile ? `EnvironmentFile=-${input.capacityEnvironmentFile}
12054
+ ` : "";
12298
12055
  const template = `[Unit]
12299
12056
  Description=ForgeZero (%i slot)
12300
12057
  After=network-online.target ${input.collectorUnit}
@@ -12307,7 +12064,7 @@ WorkingDirectory=${input.slotsDirectory}/%i
12307
12064
  Environment=NODE_ENV=production
12308
12065
  Environment=FZ_SLOT=%i
12309
12066
  EnvironmentFile=${input.sharedEnvironmentFile}
12310
- ${credentials}
12067
+ ${capacityEnvironment}${credentials}
12311
12068
  ExecStart=/usr/local/bin/bun run ${input.slotsDirectory}/%i/src/index.ts
12312
12069
  Restart=always
12313
12070
  RestartSec=2
@@ -12343,16 +12100,24 @@ Environment=PORT=${input.greenPort}
12343
12100
  function renderPlatformNginx(input) {
12344
12101
  boundedInteger("publicPort", input.publicPort, 1024, 65535);
12345
12102
  boundedInteger("initialSlotPort", input.initialSlotPort, 1024, 65535);
12103
+ const concurrencyLimit = boundedInteger("concurrencyLimit", input.concurrencyLimit ?? 256, 1, 1e6);
12104
+ boundedInteger("workerDrainSeconds", input.workerDrainSeconds ?? 35, 1, 300);
12346
12105
  if (input.publicPort === input.initialSlotPort)
12347
12106
  throw new Error("Edge and slot ports must differ.");
12348
12107
  return {
12349
12108
  upstream: `upstream forgezero { server 127.0.0.1:${input.initialSlotPort}; }
12350
12109
  `,
12351
- site: `server {
12110
+ site: `limit_conn_zone $server_name zone=forgezero_admission:10m;
12111
+ map $http_upgrade $forgezero_connection { default upgrade; '' close; }
12112
+ map $limit_conn_status $forgezero_retry_after { default ''; REJECTED 1; REJECTED_DRY_RUN 1; }
12113
+ server {
12352
12114
  listen 127.0.0.1:${input.publicPort};
12353
12115
  server_name _;
12354
- location ^~ /api/ { proxy_pass http://forgezero; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto https; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }
12355
- location ^~ /v1/ { proxy_pass http://forgezero; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto https; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }
12116
+ limit_conn forgezero_admission ${concurrencyLimit};
12117
+ limit_conn_status 503;
12118
+ add_header Retry-After $forgezero_retry_after always;
12119
+ location ^~ /api/ { proxy_pass http://forgezero; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto https; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $forgezero_connection; proxy_read_timeout 3600s; }
12120
+ location ^~ /v1/ { proxy_pass http://forgezero; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto https; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $forgezero_connection; proxy_read_timeout 3600s; }
12356
12121
  location / { return 404; }
12357
12122
  }
12358
12123
  `
@@ -12368,6 +12133,7 @@ function renderPlatformActivationFiles(input) {
12368
12133
  if (input.bluePort === input.greenPort)
12369
12134
  throw new Error("Activation slot ports must differ.");
12370
12135
  boundedInteger("keepReleases", input.keepReleases, 2, 100);
12136
+ const drainDeadlineMs = boundedInteger("drainDeadlineMs", input.drainDeadlineMs ?? 35000, 1000, 300000);
12371
12137
  if (!/^\/[A-Za-z0-9/_-]{1,128}$/.test(input.healthPath) || input.healthPath.includes("..")) {
12372
12138
  throw new Error("Activation health path is malformed.");
12373
12139
  }
@@ -12377,7 +12143,8 @@ function renderPlatformActivationFiles(input) {
12377
12143
  `FZ_BLUE_PORT=${input.bluePort}`,
12378
12144
  `FZ_GREEN_PORT=${input.greenPort}`,
12379
12145
  `FZ_HEALTH_PATH=${input.healthPath}`,
12380
- `FZ_KEEP_RELEASES=${input.keepReleases}`
12146
+ `FZ_KEEP_RELEASES=${input.keepReleases}`,
12147
+ `FZ_DRAIN_DEADLINE_MS=${drainDeadlineMs}`
12381
12148
  ].join(`
12382
12149
  `) + `
12383
12150
  `;
@@ -12399,7 +12166,14 @@ if (( ! healthy )); then systemctl stop "forgezero@\${target}.service" || true;
12399
12166
  upstream=/etc/nginx/conf.d/forgezero-upstream.conf; backup="$(mktemp -p /run forgezero-upstream.XXXXXX)"; [[ -f "$upstream" ]] && cp "$upstream" "$backup" || : >"$backup"
12400
12167
  printf 'upstream forgezero { server 127.0.0.1:%s; }\\n' "$port" >"$upstream"
12401
12168
  if ! nginx -t || ! nginx -s reload; then [[ -s "$backup" ]] && cp "$backup" "$upstream" || rm -f "$upstream"; rm -f "$backup"; systemctl stop "forgezero@\${target}.service" || true; [[ -n "$previous_target_link" && -d "$previous_target_link" ]] && ln -sfn "$previous_target_link" "$target_link" || rm -f "$target_link"; nginx -t >/dev/null 2>&1 && nginx -s reload || true; exit 1; fi
12402
- rm -f "$backup"; printf '%s\\n' "$target" >"$slot_file"; [[ -n "$previous_slot" && "$previous_slot" != "$target" ]] && systemctl stop "forgezero@\${previous_slot}.service" || true
12169
+ rm -f "$backup"; printf '%s\\n' "$target" >"$slot_file"
12170
+ # New nginx workers select the new slot after reload. Keep the old slot alive
12171
+ # while old workers drain in-flight requests and upgraded connections.
12172
+ if [[ -n "$previous_slot" && "$previous_slot" != "$target" ]]; then
12173
+ sleep_seconds="$(( (FZ_DRAIN_DEADLINE_MS + 999) / 1000 ))"
12174
+ sleep "$sleep_seconds"
12175
+ systemctl stop "forgezero@\${previous_slot}.service" || true
12176
+ fi
12403
12177
  mapfile -t old < <(find "$releases" -mindepth 1 -maxdepth 1 -type d -printf '%T@ %p\\n' | sort -rn | tail -n "+$((FZ_KEEP_RELEASES + 1))" | cut -d' ' -f2-)
12404
12178
  for path in "\${old[@]}"; do [[ "$path" == "$release" ]] || rm -rf -- "$path"; done
12405
12179
  printf 'promoted %s on %s\\n' "$release" "$target"
@@ -12450,34 +12224,11 @@ function planLocalOtlpProof(endpoint, collectorUnit) {
12450
12224
 
12451
12225
  // src/cloudflare-bootstrap.ts
12452
12226
  import { constants } from "fs";
12453
- import { chmod, lstat, mkdir, mkdtemp, open, rename, rm, stat, unlink } from "fs/promises";
12454
- import { dirname as dirname3, join as join5, resolve as resolve2 } from "path";
12455
- import { tmpdir } from "os";
12456
12227
  import { randomUUID } from "crypto";
12457
- import { isIP as isIP3 } from "net";
12228
+ import { chmod, lstat, mkdir, open, rename, stat, unlink } from "fs/promises";
12229
+ import { dirname as dirname3, join as join5, resolve as resolve2 } from "path";
12458
12230
 
12459
12231
  // src/cloudflare-edge.ts
12460
- import { isIP as isIP2 } from "net";
12461
- function isPrivateDatabaseAddress(value) {
12462
- const address = value.trim().toLowerCase();
12463
- const family = isIP2(address);
12464
- if (family === 4) {
12465
- const [a, b] = address.split(".").map(Number);
12466
- return a === 10 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168;
12467
- }
12468
- if (family === 6) {
12469
- const first = Number.parseInt(address.split(":", 1)[0], 16);
12470
- return Number.isFinite(first) && (first & 65024) === 64512;
12471
- }
12472
- return false;
12473
- }
12474
- function privateDatabaseHostRoute(value) {
12475
- const address = value.trim().toLowerCase();
12476
- if (!isPrivateDatabaseAddress(address)) {
12477
- throw new Error("database address must be an RFC 1918 IPv4 or unique-local IPv6 address");
12478
- }
12479
- return `${address}/${isIP2(address) === 4 ? 32 : 128}`;
12480
- }
12481
12232
  var endpoint = "https://api.cloudflare.com/client/v4";
12482
12233
  async function cf(config, path, init = {}, fetcher = fetch) {
12483
12234
  const response = await fetcher(`${endpoint}${path}`, {
@@ -12494,76 +12245,36 @@ async function cf(config, path, init = {}, fetcher = fetch) {
12494
12245
  }
12495
12246
  return body.result;
12496
12247
  }
12497
- async function ensureCloudflarePrivateRoute(config, fetcher = fetch) {
12498
- const [address, prefixText, ...extra] = config.network.split("/");
12499
- const family = isIP2(address ?? "");
12500
- const prefix = Number(prefixText);
12501
- if (extra.length > 0 || !family || !Number.isInteger(prefix) || prefix < 0 || prefix > (family === 4 ? 32 : 128)) {
12502
- throw new Error("Cloudflare private route must be an explicit IPv4 or IPv6 CIDR");
12503
- }
12504
- const path = `/accounts/${config.accountId}/teamnet/routes`;
12505
- const routes = await cf(config, path, {}, fetcher);
12506
- const current = routes.find((route2) => !route2.deleted_at && route2.network === config.network && (route2.virtual_network_id ?? "") === (config.virtualNetworkId ?? ""));
12507
- if (current) {
12508
- if (current.tunnel_id !== config.tunnelId) {
12509
- throw new Error(`private route ${config.network} already belongs to another Tunnel`);
12510
- }
12511
- return { route: current, created: false };
12512
- }
12513
- const route = await cf(config, path, {
12514
- method: "POST",
12515
- body: JSON.stringify({
12516
- network: config.network,
12517
- tunnel_id: config.tunnelId,
12518
- comment: config.comment.slice(0, 100),
12519
- ...config.virtualNetworkId ? { virtual_network_id: config.virtualNetworkId } : {}
12520
- })
12521
- }, fetcher);
12522
- return { route, created: true };
12523
- }
12524
- async function ensureCloudflarePrivateDatabaseRoute(config, fetcher = fetch) {
12525
- return ensureCloudflarePrivateRoute({
12526
- ...config,
12527
- network: privateDatabaseHostRoute(config.privateAddress)
12528
- }, fetcher);
12529
- }
12530
- async function ensureCloudflareWarpDatabaseInclude(config, fetcher = fetch) {
12531
- if (config.policyId && !/^[A-Za-z0-9-]{1,64}$/.test(config.policyId))
12532
- throw new Error("Cloudflare WARP policy id is invalid");
12533
- const route = privateDatabaseHostRoute(config.privateAddress);
12534
- const policy = config.policyId ? `/${config.policyId}` : "";
12535
- const path = `/accounts/${config.accountId}/devices/policy${policy}/include`;
12536
- const entries = await cf(config, path, {}, fetcher);
12537
- if (entries.some((entry) => entry.address === route))
12538
- return { entries, created: false };
12539
- const next = [...entries, { address: route, description: config.description.slice(0, 100) }];
12540
- const updated = await cf(config, path, {
12541
- method: "PUT",
12542
- body: JSON.stringify(next)
12543
- }, fetcher);
12544
- return { entries: updated, created: true };
12545
- }
12546
12248
  async function configureCloudflareEdge(config, fetcher = fetch) {
12547
12249
  const tunnelAuth = { apiToken: config.tunnelApiToken?.trim() || config.apiToken };
12548
12250
  const dnsAuth = { apiToken: config.dnsApiToken?.trim() || config.apiToken };
12549
12251
  const tunnelPath = `/accounts/${config.accountId}/cfd_tunnel/${config.tunnelId}/configurations`;
12550
- const current = await cf(tunnelAuth, tunnelPath, {}, fetcher);
12551
- const existing = current.config?.ingress ?? [];
12552
- const catchAll = existing.filter((rule) => !("hostname" in rule));
12553
- const otherHosts = existing.filter((rule) => ("hostname" in rule) && rule.hostname !== config.hostname);
12554
- await cf(tunnelAuth, tunnelPath, {
12555
- method: "PUT",
12556
- body: JSON.stringify({ config: { ingress: [
12557
- { hostname: config.hostname, service: config.service },
12558
- ...otherHosts,
12559
- ...catchAll.length > 0 ? catchAll : [{ service: "http_status:404" }]
12560
- ] } })
12561
- }, fetcher);
12562
12252
  const dnsPath = `/zones/${config.zoneId}/dns_records`;
12563
- const records = await cf(dnsAuth, `${dnsPath}?type=CNAME&name=${encodeURIComponent(config.hostname)}&per_page=1000`, {}, fetcher);
12253
+ const [current, records] = await Promise.all([
12254
+ cf(tunnelAuth, tunnelPath, {}, fetcher),
12255
+ cf(dnsAuth, `${dnsPath}?name=${encodeURIComponent(config.hostname)}&per_page=1000`, {}, fetcher)
12256
+ ]);
12564
12257
  if (records.length > 1) {
12565
12258
  throw new Error(`Cloudflare DNS record for ${config.hostname} is ambiguous`);
12566
12259
  }
12260
+ const existingRecord = records[0];
12261
+ if (existingRecord && (existingRecord.type !== "CNAME" || existingRecord.name && existingRecord.name.toLowerCase() !== config.hostname.toLowerCase())) {
12262
+ throw new Error(`Cloudflare DNS hostname ${config.hostname} is already owned by an incompatible ${existingRecord.type ?? "unknown"} record`);
12263
+ }
12264
+ const existing = current.config?.ingress ?? [];
12265
+ const catchAll = existing.filter((rule) => !("hostname" in rule));
12266
+ const otherHosts = existing.filter((rule) => ("hostname" in rule) && rule.hostname !== config.hostname);
12267
+ const desiredIngress = [
12268
+ { hostname: config.hostname, service: config.service },
12269
+ ...otherHosts,
12270
+ ...catchAll.length > 0 ? catchAll : [{ service: "http_status:404" }]
12271
+ ];
12272
+ if (JSON.stringify(existing) !== JSON.stringify(desiredIngress)) {
12273
+ await cf(tunnelAuth, tunnelPath, {
12274
+ method: "PUT",
12275
+ body: JSON.stringify({ config: { ingress: desiredIngress } })
12276
+ }, fetcher);
12277
+ }
12567
12278
  const record2 = {
12568
12279
  type: "CNAME",
12569
12280
  name: config.hostname,
@@ -12571,233 +12282,13 @@ async function configureCloudflareEdge(config, fetcher = fetch) {
12571
12282
  proxied: true,
12572
12283
  ttl: 1
12573
12284
  };
12574
- await cf(dnsAuth, records[0] ? `${dnsPath}/${records[0].id}` : dnsPath, {
12575
- method: records[0] ? "PUT" : "POST",
12576
- body: JSON.stringify(record2)
12577
- }, fetcher);
12578
- }
12579
- var exactAccountPermissionGroup = async (config, name, fetcher) => {
12580
- const groups = await cf(config, `/accounts/${config.accountId}/tokens/permission_groups?name=${encodeURIComponent(name)}` + "&scope=com.cloudflare.api.account", {}, fetcher);
12581
- const matches = groups.filter((group) => group.name === name && group.scopes?.includes("com.cloudflare.api.account") && Boolean(group.id && /^[a-f0-9]{32}$/i.test(group.id)));
12582
- if (matches.length !== 1) {
12583
- throw new Error(`Cloudflare account token permission group ${name} is ${matches.length === 0 ? "missing" : "ambiguous"}`);
12584
- }
12585
- return { id: matches[0].id, name };
12586
- };
12587
- async function createCloudflareAccountRuntimeToken(config, fetcher = fetch) {
12588
- if (!/^[a-f0-9]{32}$/i.test(config.accountId))
12589
- throw new Error("Cloudflare account id is invalid");
12590
- const name = config.name.trim();
12591
- if (!/^[A-Za-z0-9][A-Za-z0-9 ._-]{0,119}$/.test(name)) {
12592
- throw new Error("Cloudflare account runtime-token name is invalid");
12593
- }
12594
- const permissionNames = [...new Set(config.permissionNames)];
12595
- if (permissionNames.length === 0)
12596
- throw new Error("Cloudflare account runtime token needs a permission group");
12597
- const permissionGroups = await Promise.all(permissionNames.map((permissionName) => exactAccountPermissionGroup(config, permissionName, fetcher)));
12598
- const body = {
12599
- name,
12600
- policies: [{
12601
- effect: "allow",
12602
- permission_groups: permissionGroups.map(({ id: id2 }) => ({ id: id2 })),
12603
- resources: { [`com.cloudflare.api.account.${config.accountId}`]: "*" }
12604
- }]
12605
- };
12606
- const created = await cf(config, `/accounts/${config.accountId}/tokens`, { method: "POST", body: JSON.stringify(body) }, fetcher);
12607
- if (!created.id || !/^[a-f0-9]{32}$/i.test(created.id) || !created.value || !/^[A-Za-z0-9._-]{40,80}$/.test(created.value)) {
12608
- throw new Error("Cloudflare did not return the one-time account runtime-token id and value");
12609
- }
12610
- return { id: created.id, value: created.value, name, permissionNames };
12611
- }
12612
- async function ensureCloudflareAccessServiceToken(config, fetcher = fetch) {
12613
- const name = config.name.trim();
12614
- if (!/^[A-Za-z0-9][A-Za-z0-9 ._-]{0,99}$/.test(name)) {
12615
- throw new Error("Cloudflare Access service-token name is invalid");
12616
- }
12617
- const path = `/accounts/${config.accountId}/access/service_tokens`;
12618
- const tokens = await cf(config, `${path}?per_page=1000`, {}, fetcher);
12619
- const matches = tokens.filter((token) => token.name === name);
12620
- if (matches.length > 1)
12621
- throw new Error(`Cloudflare Access service token ${name} is ambiguous`);
12622
- if (matches[0]) {
12623
- if (!config.existing || config.existing.tokenId !== matches[0].id || config.existing.clientId !== matches[0].client_id || !config.existing.clientSecret) {
12624
- throw new Error(`Cloudflare Access service token ${name} exists but its one-time client secret was not supplied`);
12625
- }
12626
- return { credentials: config.existing, created: false };
12627
- }
12628
- const created = await cf(config, path, {
12629
- method: "POST",
12630
- body: JSON.stringify({ name, duration: config.duration ?? "8760h" })
12631
- }, fetcher);
12632
- if (!created.id || !created.client_id || !created.client_secret) {
12633
- throw new Error("Cloudflare did not return the new Access service-token secret");
12634
- }
12635
- return {
12636
- credentials: {
12637
- tokenId: created.id,
12638
- clientId: created.client_id,
12639
- clientSecret: created.client_secret
12640
- },
12641
- created: true
12642
- };
12643
- }
12644
- async function ensureCloudflareAccessPolicy(config, fetcher = fetch) {
12645
- const path = `/accounts/${config.accountId}/access/policies`;
12646
- const policies = await cf(config, `${path}?per_page=1000`, {}, fetcher);
12647
- const matches = policies.filter((policy2) => policy2.name === config.name);
12648
- if (matches.length > 1)
12649
- throw new Error(`Cloudflare Access policy ${config.name} is ambiguous`);
12650
- const desired = {
12651
- name: config.name,
12652
- decision: "non_identity",
12653
- include: [{ service_token: { token_id: config.serviceTokenId } }]
12654
- };
12655
- const policy = await cf(config, matches[0] ? `${path}/${encodeURIComponent(matches[0].id)}` : path, {
12656
- method: matches[0] ? "PUT" : "POST",
12657
- body: JSON.stringify(desired)
12658
- }, fetcher);
12659
- return { policy, created: !matches[0] };
12660
- }
12661
- async function ensureCloudflareAccessApplication(config, fetcher = fetch) {
12662
- const path = `/accounts/${config.accountId}/access/apps`;
12663
- const applications = await cf(config, `${path}?per_page=1000`, {}, fetcher);
12664
- const matches = applications.filter((application2) => application2.domain === config.hostname || application2.self_hosted_domains?.includes(config.hostname));
12665
- if (matches.length > 1)
12666
- throw new Error(`Cloudflare Access application for ${config.hostname} is ambiguous`);
12667
- const desired = {
12668
- name: config.name,
12669
- type: "self_hosted",
12670
- domain: config.hostname,
12671
- session_duration: "24h",
12672
- service_auth_401_redirect: true,
12673
- policies: [{ id: config.policyId, precedence: 1 }]
12674
- };
12675
- const application = await cf(config, matches[0] ? `${path}/${encodeURIComponent(matches[0].id)}` : path, { method: matches[0] ? "PUT" : "POST", body: JSON.stringify(desired) }, fetcher);
12676
- return { application, created: !matches[0] };
12677
- }
12678
- async function ensureCloudflareWarpEnrollmentApplication(config, fetcher = fetch) {
12679
- const name = config.name.trim();
12680
- if (!/^[A-Za-z0-9][A-Za-z0-9 ._-]{0,99}$/.test(name)) {
12681
- throw new Error("Cloudflare WARP enrollment application name is invalid");
12682
- }
12683
- const path = `/accounts/${config.accountId}/access/apps`;
12684
- const applications = await cf(config, `${path}?per_page=1000`, {}, fetcher);
12685
- const matches = applications.filter((application2) => application2.type === "warp" || application2.name === name);
12686
- if (matches.length > 1)
12687
- throw new Error(`Cloudflare WARP enrollment application ${name} is ambiguous`);
12688
- if (matches[0] && (matches[0].type !== "warp" || matches[0].name !== name)) {
12689
- throw new Error(`Cloudflare Access application ${name} is not the owned WARP enrollment application`);
12690
- }
12691
- const desired = {
12692
- name,
12693
- type: "warp",
12694
- policies: [{ id: config.policyId, precedence: 1 }]
12695
- };
12696
- const application = await cf(config, matches[0] ? `${path}/${encodeURIComponent(matches[0].id)}` : path, { method: matches[0] ? "PUT" : "POST", body: JSON.stringify(desired) }, fetcher);
12697
- if (!application.id || application.type && application.type !== "warp") {
12698
- throw new Error("Cloudflare did not return the WARP enrollment application");
12699
- }
12700
- return { application, created: !matches[0] };
12701
- }
12702
- async function ensureCloudflareVirtualNetwork(config, fetcher = fetch) {
12703
- const name = config.name.trim();
12704
- if (!/^[A-Za-z0-9][A-Za-z0-9 ._-]{0,99}$/.test(name))
12705
- throw new Error("Cloudflare VNET name is invalid");
12706
- const path = `/accounts/${config.accountId}/teamnet/virtual_networks`;
12707
- const networks = await cf(config, `${path}?per_page=1000`, {}, fetcher);
12708
- const matches = networks.filter((network) => !network.deleted_at && network.name === name);
12709
- if (matches.length > 1)
12710
- throw new Error(`Cloudflare VNET ${name} is ambiguous`);
12711
- if (matches[0])
12712
- return { virtualNetwork: matches[0], created: false };
12713
- const virtualNetwork = await cf(config, path, {
12714
- method: "POST",
12715
- body: JSON.stringify({ name, comment: config.comment.slice(0, 256), is_default_network: false })
12716
- }, fetcher);
12717
- if (!virtualNetwork.id || !/^[0-9a-f-]{36}$/i.test(virtualNetwork.id)) {
12718
- throw new Error("Cloudflare did not return the VNET id");
12719
- }
12720
- return { virtualNetwork, created: true };
12721
- }
12722
- async function ensureCloudflareWarpDevicePolicy(config, fetcher = fetch) {
12723
- const name = config.name.trim();
12724
- if (!/^[A-Za-z0-9][A-Za-z0-9 ._-]{0,99}$/.test(name))
12725
- throw new Error("Cloudflare WARP device profile name is invalid");
12726
- if (!/^[A-Za-z0-9_-]{1,128}$/.test(config.serviceTokenId))
12727
- throw new Error("Cloudflare service-token id is invalid");
12728
- if (!/^[0-9a-f-]{36}$/i.test(config.virtualNetworkId))
12729
- throw new Error("Cloudflare VNET id is invalid");
12730
- const precedence = config.precedence ?? 100;
12731
- if (!Number.isInteger(precedence) || precedence < 1 || precedence > 999999) {
12732
- throw new Error("Cloudflare WARP device profile precedence is invalid");
12733
- }
12734
- const match = `identity.service_token_uuid == "${config.serviceTokenId}"`;
12735
- const listPath = `/accounts/${config.accountId}/devices/policies`;
12736
- const path = `/accounts/${config.accountId}/devices/policy`;
12737
- const policies = await cf(config, `${listPath}?per_page=1000`, {}, fetcher);
12738
- const matches = policies.filter((policy2) => policy2.name === name);
12739
- if (matches.length > 1)
12740
- throw new Error(`Cloudflare WARP device profile ${name} is ambiguous`);
12741
- if (matches[0]?.match && matches[0].match !== match) {
12742
- throw new Error(`Cloudflare WARP device profile ${name} belongs to another enrollment identity`);
12743
- }
12744
- const desired = {
12745
- name,
12746
- match,
12747
- precedence,
12748
- description: "ForgeZero non-interactive compute enrollment",
12749
- enabled: true,
12750
- allow_mode_switch: false,
12751
- allowed_to_leave: false,
12752
- auto_connect: 0,
12753
- switch_locked: true,
12754
- service_mode_v2: { mode: "warp" },
12755
- virtual_networks: { allowed: [config.virtualNetworkId], default: config.virtualNetworkId }
12756
- };
12757
- const policy = await cf(config, matches[0] ? `${path}/${encodeURIComponent(matches[0].id)}` : path, { method: matches[0] ? "PATCH" : "POST", body: JSON.stringify(desired) }, fetcher);
12758
- if (!policy.id)
12759
- throw new Error("Cloudflare did not return the WARP device profile id");
12760
- return { policy, created: !matches[0] };
12761
- }
12762
- async function configureCloudflareWorkerAccessSecrets(config, fetcher = fetch) {
12763
- if (!/^[a-z][a-z0-9-]{0,62}$/.test(config.scriptName)) {
12764
- throw new Error("Cloudflare Worker script name is invalid");
12765
- }
12766
- await cf(config, `/accounts/${config.accountId}/workers/scripts/${config.scriptName}/secrets-bulk`, {
12767
- method: "PATCH",
12768
- body: JSON.stringify({
12769
- secrets: {
12770
- CF_ACCESS_CLIENT_ID: {
12771
- name: "CF_ACCESS_CLIENT_ID",
12772
- type: "secret_text",
12773
- text: config.credentials.clientId
12774
- },
12775
- CF_ACCESS_CLIENT_SECRET: {
12776
- name: "CF_ACCESS_CLIENT_SECRET",
12777
- type: "secret_text",
12778
- text: config.credentials.clientSecret
12779
- }
12780
- }
12781
- })
12782
- }, fetcher);
12783
- }
12784
- async function ensureCloudflareKvNamespace(config, fetcher = fetch) {
12785
- const title = config.title.trim();
12786
- if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(title)) {
12787
- throw new Error("Cloudflare KV namespace title is invalid");
12285
+ const dnsAlreadyCorrect = existingRecord?.type === record2.type && existingRecord.name?.toLowerCase() === record2.name.toLowerCase() && existingRecord.content?.toLowerCase() === record2.content.toLowerCase() && existingRecord.proxied === true && existingRecord.ttl === 1;
12286
+ if (!dnsAlreadyCorrect) {
12287
+ await cf(dnsAuth, existingRecord ? `${dnsPath}/${encodeURIComponent(existingRecord.id)}` : dnsPath, {
12288
+ method: existingRecord ? "PUT" : "POST",
12289
+ body: JSON.stringify(record2)
12290
+ }, fetcher);
12788
12291
  }
12789
- const path = `/accounts/${config.accountId}/storage/kv/namespaces`;
12790
- const namespaces = await cf(config, `${path}?per_page=1000`, {}, fetcher);
12791
- const matches = namespaces.filter((namespace2) => namespace2.title === title);
12792
- if (matches.length > 1)
12793
- throw new Error(`Cloudflare KV namespace ${title} is ambiguous`);
12794
- if (matches[0])
12795
- return { namespace: matches[0], created: false };
12796
- const namespace = await cf(config, path, {
12797
- method: "POST",
12798
- body: JSON.stringify({ title })
12799
- }, fetcher);
12800
- return { namespace, created: true };
12801
12292
  }
12802
12293
  async function ensureCloudflareTunnel(config, fetcher = fetch) {
12803
12294
  const name = config.name.trim();
@@ -12822,22 +12313,10 @@ async function ensureCloudflareTunnel(config, fetcher = fetch) {
12822
12313
  }
12823
12314
 
12824
12315
  // src/cloudflare-bootstrap.ts
12825
- var acceptanceFetch = async (url, label, fetcher, headers) => {
12826
- let response;
12827
- try {
12828
- response = await fetcher(url, {
12829
- method: "GET",
12830
- headers,
12831
- redirect: "manual",
12832
- signal: AbortSignal.timeout(5000)
12833
- });
12834
- } catch {
12835
- throw new Error(`${label} is unreachable`);
12836
- }
12837
- if (!response.ok)
12838
- throw new Error(`${label} returned HTTP ${response.status}`);
12839
- return response.status;
12840
- };
12316
+ var TOKEN = /^[A-Za-z0-9._-]{40,80}$/;
12317
+ var CONNECTOR_TOKEN = /^[A-Za-z0-9._-]{40,16384}$/;
12318
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
12319
+ var HOSTNAME = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/;
12841
12320
  var ownerUid = () => typeof process.getuid === "function" ? process.getuid() : undefined;
12842
12321
  async function assertOwnerOnlyHandle(path, handle, maximumBytes) {
12843
12322
  const metadata = await handle.stat();
@@ -12846,8 +12325,9 @@ async function assertOwnerOnlyHandle(path, handle, maximumBytes) {
12846
12325
  if (metadata.nlink !== 1)
12847
12326
  throw new Error(`${path} must not have multiple hard links`);
12848
12327
  const uid = ownerUid();
12849
- if (uid !== undefined && uid !== 0 && metadata.uid !== uid)
12328
+ if (uid !== undefined && uid !== 0 && metadata.uid !== uid) {
12850
12329
  throw new Error(`${path} must be owned by the current operator`);
12330
+ }
12851
12331
  if ((metadata.mode & 63) !== 0)
12852
12332
  throw new Error(`${path} must not be accessible by group or other users`);
12853
12333
  if ((metadata.mode & 256) === 0)
@@ -12872,25 +12352,36 @@ async function readOwnerOnlyFile(path, maximumBytes) {
12872
12352
  }
12873
12353
  async function readOwnerApiToken(path) {
12874
12354
  const token = (await readOwnerOnlyFile(path, 4096)).trim();
12875
- if (!/^[A-Za-z0-9._-]{40,80}$/.test(token)) {
12355
+ if (!TOKEN.test(token))
12876
12356
  throw new Error(`${resolve2(path)} must contain exactly one Cloudflare API token`);
12877
- }
12878
12357
  return token;
12879
12358
  }
12880
12359
  async function readCloudflareBootstrapTokens(files) {
12881
12360
  const entries = await Promise.all([
12882
12361
  ["apiToken", files.apiTokenFile],
12362
+ ["managementApiToken", files.managementApiTokenFile],
12363
+ ["runtimeApiToken", files.runtimeApiTokenFile],
12883
12364
  ["tunnelApiToken", files.tunnelApiTokenFile],
12884
12365
  ["dnsApiToken", files.dnsApiTokenFile],
12885
- ["kvApiToken", files.kvApiTokenFile],
12886
- ["accessApiToken", files.accessApiTokenFile],
12887
- ["workerApiToken", files.workerApiTokenFile]
12366
+ ["kvApiToken", files.kvApiTokenFile]
12888
12367
  ].map(async ([key, path]) => [key, path ? await readOwnerApiToken(path) : undefined]));
12889
- const tokens = Object.fromEntries(entries.filter(([, value]) => value !== undefined));
12890
- const unified = tokens.apiToken;
12891
- for (const key of ["tunnelApiToken", "dnsApiToken", "kvApiToken", "accessApiToken", "workerApiToken"]) {
12892
- if (!tokens[key] && !unified)
12893
- throw new Error(`Cloudflare ${key} file is required when --token-file is omitted`);
12368
+ const supplied = Object.fromEntries(entries.filter(([, value]) => value !== undefined));
12369
+ const tokens = {
12370
+ ...supplied.apiToken ? { apiToken: supplied.apiToken } : {},
12371
+ ...supplied.tunnelApiToken || supplied.managementApiToken ? {
12372
+ tunnelApiToken: supplied.tunnelApiToken ?? supplied.managementApiToken
12373
+ } : {},
12374
+ ...supplied.dnsApiToken || supplied.managementApiToken ? {
12375
+ dnsApiToken: supplied.dnsApiToken ?? supplied.managementApiToken
12376
+ } : {},
12377
+ ...supplied.kvApiToken || supplied.runtimeApiToken ? {
12378
+ kvApiToken: supplied.kvApiToken ?? supplied.runtimeApiToken
12379
+ } : {}
12380
+ };
12381
+ for (const key of ["tunnelApiToken", "dnsApiToken", "kvApiToken"]) {
12382
+ if (!tokens[key] && !tokens.apiToken) {
12383
+ throw new Error(`Cloudflare ${key} file is required when apiTokenFile is omitted`);
12384
+ }
12894
12385
  }
12895
12386
  return tokens;
12896
12387
  }
@@ -12900,36 +12391,30 @@ var validateId = (value, label) => {
12900
12391
  throw new Error(`${label} must be a 32-character hexadecimal id`);
12901
12392
  return normalized;
12902
12393
  };
12903
- var validateName = (value, label, maximum, allowSpaces = true) => {
12394
+ var validateName = (value, label) => {
12904
12395
  const normalized = value.trim();
12905
- const pattern = allowSpaces ? /^[A-Za-z0-9][A-Za-z0-9 ._-]*$/ : /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
12906
- if (!normalized || normalized.length > maximum || !pattern.test(normalized)) {
12396
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/.test(normalized))
12907
12397
  throw new Error(`${label} is invalid`);
12908
- }
12909
12398
  return normalized;
12910
12399
  };
12911
- var privateAddress = (value) => {
12912
- const address = value.trim().toLowerCase();
12913
- const family = isIP3(address);
12914
- if (family === 4) {
12915
- const [a, b] = address.split(".").map(Number);
12916
- if (a === 10 || a === 192 && b === 168 || a === 172 && b >= 16 && b <= 31)
12917
- return address;
12918
- }
12919
- if (family === 6) {
12920
- const first = Number.parseInt(address.split(":", 1)[0], 16);
12921
- if (Number.isFinite(first) && (first & 65024) === 64512)
12922
- return address;
12923
- }
12924
- throw new Error("Cloudflare private database address must be RFC 1918 IPv4 or unique-local IPv6");
12400
+ var normalizeService = (value) => {
12401
+ let service;
12402
+ try {
12403
+ service = new URL(value);
12404
+ } catch {
12405
+ throw new Error("Cloudflare Tunnel service must be an explicit loopback HTTP port");
12406
+ }
12407
+ if (service.protocol !== "http:" || !["localhost", "127.0.0.1", "[::1]"].includes(service.hostname) || !service.port || service.pathname !== "/" || service.username || service.password || service.search || service.hash) {
12408
+ throw new Error("Cloudflare Tunnel service must be an explicit loopback HTTP port");
12409
+ }
12410
+ return service.toString().replace(/\/$/, "");
12925
12411
  };
12926
12412
  function validateCloudflareBootstrapCoordinates(input) {
12927
12413
  const nodeInputs = input.nodes?.length ? input.nodes : [{
12928
12414
  nodeName: input.tunnelName,
12929
12415
  hostname: input.hostname,
12930
12416
  service: input.service,
12931
- tunnelName: input.tunnelName,
12932
- applicationName: input.applicationName
12417
+ tunnelName: input.tunnelName
12933
12418
  }];
12934
12419
  if (nodeInputs.length < 1 || nodeInputs.length > 32) {
12935
12420
  throw new Error("Cloudflare bootstrap requires between 1 and 32 explicit nodes");
@@ -12939,88 +12424,32 @@ function validateCloudflareBootstrapCoordinates(input) {
12939
12424
  if (!/^[a-z0-9][a-z0-9-]{0,62}$/.test(nodeName))
12940
12425
  throw new Error("Cloudflare node name is invalid");
12941
12426
  const hostname = node.hostname.trim().toLowerCase();
12942
- if (!/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/.test(hostname)) {
12427
+ if (!HOSTNAME.test(hostname))
12943
12428
  throw new Error("Cloudflare public node hostname is invalid");
12944
- }
12945
- const serviceUrl = new URL(node.service);
12946
- if (serviceUrl.protocol !== "http:" || !["localhost", "127.0.0.1", "[::1]"].includes(serviceUrl.hostname) || !serviceUrl.port || serviceUrl.pathname !== "/" || serviceUrl.search || serviceUrl.hash) {
12947
- throw new Error("Cloudflare Tunnel service must be an explicit loopback HTTP port");
12948
- }
12949
12429
  return {
12950
12430
  nodeName,
12951
12431
  hostname,
12952
- service: serviceUrl.toString().replace(/\/$/, ""),
12953
- tunnelName: validateName(node.tunnelName, "Cloudflare Tunnel name", 100, false),
12954
- applicationName: validateName(node.applicationName, "Cloudflare Access application name", 100),
12955
- ...node.privateAddress ? { privateAddress: privateAddress(node.privateAddress) } : {}
12432
+ service: normalizeService(node.service),
12433
+ tunnelName: validateName(node.tunnelName, "Cloudflare Tunnel name")
12956
12434
  };
12957
12435
  });
12958
12436
  for (const [label, values] of [
12959
12437
  ["node name", nodes.map(({ nodeName }) => nodeName)],
12960
12438
  ["hostname", nodes.map(({ hostname }) => hostname)],
12961
- ["Tunnel name", nodes.map(({ tunnelName }) => tunnelName)],
12962
- ["Access application name", nodes.map(({ applicationName }) => applicationName)]
12439
+ ["Tunnel name", nodes.map(({ tunnelName }) => tunnelName)]
12963
12440
  ]) {
12964
12441
  if (new Set(values).size !== values.length)
12965
12442
  throw new Error(`Cloudflare fleet ${label} must be unique`);
12966
12443
  }
12967
12444
  const first = nodes[0];
12968
- const workerScriptName = input.workerScriptName.trim();
12969
- if (!/^[a-z][a-z0-9-]{0,62}$/.test(workerScriptName))
12970
- throw new Error("Cloudflare Worker script name is invalid");
12971
- const workerCompatibilityDate = input.workerCompatibilityDate.trim();
12972
- if (!/^20\d{2}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])$/.test(workerCompatibilityDate)) {
12973
- throw new Error("Cloudflare Worker compatibility date is invalid");
12974
- }
12975
- const publicDomains = [...new Set(input.publicDomains.map((domain) => domain.trim().toLowerCase()))];
12976
- if (publicDomains.length < 1 || publicDomains.length > 10 || publicDomains.some((domain) => !/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/.test(domain) || nodes.some(({ hostname }) => hostname === domain))) {
12977
- throw new Error("Cloudflare Worker public domains are invalid or include the private origin hostname");
12978
- }
12979
- const workerDirectory = resolve2(input.workerDirectory);
12980
- const workerMain = input.workerMain.trim();
12981
- if (!workerMain || workerMain.startsWith("/") || workerMain.split(/[\\/]/).includes("..")) {
12982
- throw new Error("Cloudflare Worker main must be a project-relative path");
12983
- }
12984
- const runtimeTokenNamePrefix = input.runtimeTokenNamePrefix.trim();
12985
- if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/.test(runtimeTokenNamePrefix)) {
12986
- throw new Error("Cloudflare runtime-token name prefix is invalid");
12987
- }
12988
- if (input.createPrivateNetworkRuntimeToken && !input.createRuntimeTokens) {
12989
- throw new Error("private-network runtime token requires runtime-token creation");
12990
- }
12991
- const privateNetwork = input.privateNetwork ? {
12992
- warpOrganization: validateName(input.privateNetwork.warpOrganization, "Cloudflare WARP organization", 63, false).toLowerCase(),
12993
- virtualNetworkName: validateName(input.privateNetwork.virtualNetworkName, "Cloudflare VNET name", 100),
12994
- deviceProfileName: validateName(input.privateNetwork.deviceProfileName, "Cloudflare WARP device profile name", 100),
12995
- enrollmentApplicationName: validateName(input.privateNetwork.enrollmentApplicationName, "Cloudflare WARP enrollment application name", 100),
12996
- ...input.privateNetwork.deviceProfilePrecedence !== undefined ? { deviceProfilePrecedence: input.privateNetwork.deviceProfilePrecedence } : {}
12997
- } : undefined;
12998
- if (privateNetwork && (!input.createPrivateNetworkRuntimeToken || nodes.every((node) => !node.privateAddress))) {
12999
- throw new Error("Cloudflare private network requires its runtime token and at least one DB node private address");
13000
- }
13001
- if (!privateNetwork && nodes.some((node) => node.privateAddress)) {
13002
- throw new Error("Cloudflare node private addresses require privateNetwork coordinates");
13003
- }
13004
12445
  return {
13005
12446
  accountId: validateId(input.accountId, "Cloudflare account id"),
13006
12447
  zoneId: validateId(input.zoneId, "Cloudflare zone id"),
13007
12448
  hostname: first.hostname,
13008
12449
  service: first.service,
13009
12450
  tunnelName: first.tunnelName,
13010
- kvNamespaceTitle: validateName(input.kvNamespaceTitle, "Cloudflare KV namespace title", 128, false),
13011
- workerScriptName,
13012
- serviceTokenName: validateName(input.serviceTokenName, "Cloudflare Access service-token name", 100),
13013
- policyName: validateName(input.policyName, "Cloudflare Access policy name", 100),
13014
- applicationName: first.applicationName,
13015
- workerDirectory,
13016
- workerMain,
13017
- workerCompatibilityDate,
13018
- publicDomains,
13019
- createRuntimeTokens: input.createRuntimeTokens,
13020
- createPrivateNetworkRuntimeToken: input.createPrivateNetworkRuntimeToken,
13021
- runtimeTokenNamePrefix,
13022
- nodes,
13023
- ...privateNetwork ? { privateNetwork } : {}
12451
+ kvNamespaceId: validateId(input.kvNamespaceId, "Cloudflare KV namespace id"),
12452
+ nodes
13024
12453
  };
13025
12454
  }
13026
12455
  function planCloudflareBootstrap(input, outputPath) {
@@ -13032,120 +12461,18 @@ function planCloudflareBootstrap(input, outputPath) {
13032
12461
  outputFile: resolve2(outputPath),
13033
12462
  coordinates,
13034
12463
  operations: [
13035
- "create or reuse one Workers KV namespace",
13036
12464
  "create or reuse one remotely-managed Tunnel per node and checkpoint every connector token",
13037
- ...coordinates.createRuntimeTokens ? [
13038
- "create exact-account least-privilege runtime tokens and checkpoint their one-time values"
13039
- ] : [],
13040
- "deploy the shared Worker once with the created NODES binding and stable custom domains",
13041
- "create or reuse one shared Access service token/policy and one self-hosted application per node",
13042
- ...coordinates.privateNetwork ? [
13043
- "create or reuse the VNET, WARP enrollment application, locked service-token device profile and exact DB host routes"
13044
- ] : [],
13045
- "write the Access client id and secret to the existing Worker as encrypted secrets",
13046
- "reconcile each node ingress rule and proxied CNAME only after all Access applications are ready"
12465
+ "preflight each exact DNS hostname, refuse ambiguous or incompatible records, and update its existing CNAME or create it only when absent",
12466
+ "reconcile each Tunnel public-hostname ingress rule to the declared loopback API service",
12467
+ "write one node-specific handoff containing the connector token and owner-supplied KV-write token"
13047
12468
  ],
13048
12469
  secrets: [
13049
- "API tokens are read only from owner-only files and are never written to output",
13050
- "the output contains connector, Access and requested runtime credentials and is atomically written with mode 0600",
13051
- "the normal API process does not receive or import the management token files"
12470
+ "API tokens are read only from owner-only files and are never placed in argv or stdout",
12471
+ "the management token is never persisted; connector and KV runtime capabilities are atomically checkpointed with mode 0600",
12472
+ "the normal API process receives only its KV-write token and never Tunnel or DNS management authority"
13052
12473
  ]
13053
12474
  };
13054
12475
  }
13055
- var defaultWorkerCommandRunner = async ({ command, cwd, env }) => {
13056
- const child = Bun.spawn([...command], {
13057
- cwd,
13058
- env: { ...env },
13059
- stdin: "ignore",
13060
- stdout: "pipe",
13061
- stderr: "pipe"
13062
- });
13063
- const [exitCode, stdout, stderr] = await Promise.all([
13064
- child.exited,
13065
- new Response(child.stdout).text(),
13066
- new Response(child.stderr).text()
13067
- ]);
13068
- return { exitCode, stdout, stderr };
13069
- };
13070
- var inheritedWorkerEnvironment = () => {
13071
- const allowed = [
13072
- "PATH",
13073
- "HOME",
13074
- "TMPDIR",
13075
- "XDG_CONFIG_HOME",
13076
- "XDG_CACHE_HOME",
13077
- "SSL_CERT_FILE",
13078
- "SSL_CERT_DIR",
13079
- "NODE_EXTRA_CA_CERTS",
13080
- "HTTPS_PROXY",
13081
- "HTTP_PROXY",
13082
- "NO_PROXY"
13083
- ];
13084
- return Object.fromEntries(allowed.flatMap((key) => process.env[key] ? [[key, process.env[key]]] : []));
13085
- };
13086
- var redact = (text3, secrets) => {
13087
- let safe = text3.slice(0, 4096);
13088
- for (const secret of secrets)
13089
- if (secret)
13090
- safe = safe.split(secret).join("[REDACTED]");
13091
- return safe.trim();
13092
- };
13093
- async function deployCloudflareWorker(coordinates, kvNamespaceId, apiToken, runner = defaultWorkerCommandRunner) {
13094
- const validated = validateCloudflareBootstrapCoordinates(coordinates);
13095
- const workerDirectoryMetadata = await stat(validated.workerDirectory);
13096
- if (!workerDirectoryMetadata.isDirectory())
13097
- throw new Error("Cloudflare Worker directory is not a directory");
13098
- const workerMain = resolve2(validated.workerDirectory, validated.workerMain);
13099
- const workerMainMetadata = await stat(workerMain);
13100
- if (!workerMainMetadata.isFile())
13101
- throw new Error("Cloudflare Worker main is not a regular file");
13102
- const wrangler = resolve2(validated.workerDirectory, "node_modules/.bin/wrangler");
13103
- const wranglerMetadata = await stat(wrangler);
13104
- if (!wranglerMetadata.isFile())
13105
- throw new Error("Cloudflare Wrangler is not installed in the Worker project");
13106
- if (!/^[a-f0-9]{32}$/.test(kvNamespaceId))
13107
- throw new Error("Cloudflare KV namespace id is invalid");
13108
- const temporaryDirectory = await mkdtemp(join5(tmpdir(), "fz-wrangler-"));
13109
- await chmod(temporaryDirectory, 448);
13110
- const configurationPath = join5(temporaryDirectory, "wrangler.json");
13111
- try {
13112
- const handle = await open(configurationPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 384);
13113
- try {
13114
- await handle.writeFile(`${JSON.stringify({
13115
- name: validated.workerScriptName,
13116
- main: workerMain,
13117
- compatibility_date: validated.workerCompatibilityDate,
13118
- workers_dev: false,
13119
- routes: validated.publicDomains.map((pattern) => ({ pattern, custom_domain: true })),
13120
- observability: { enabled: true },
13121
- kv_namespaces: [{ binding: "NODES", id: kvNamespaceId }]
13122
- }, null, 2)}
13123
- `);
13124
- await handle.sync();
13125
- } finally {
13126
- await handle.close();
13127
- }
13128
- const result = await runner({
13129
- command: [wrangler, "deploy", "--config", configurationPath],
13130
- cwd: validated.workerDirectory,
13131
- env: {
13132
- ...inheritedWorkerEnvironment(),
13133
- XDG_CONFIG_HOME: temporaryDirectory,
13134
- XDG_CACHE_HOME: temporaryDirectory,
13135
- WRANGLER_LOG_PATH: join5(temporaryDirectory, "wrangler.log"),
13136
- CLOUDFLARE_ACCOUNT_ID: validated.accountId,
13137
- CLOUDFLARE_API_TOKEN: apiToken,
13138
- WRANGLER_SEND_METRICS: "false"
13139
- }
13140
- });
13141
- if (result.exitCode !== 0) {
13142
- const detail = redact(result.stderr || result.stdout || "no Wrangler diagnostic", [apiToken]);
13143
- throw new Error(`Cloudflare Worker deployment failed with exit ${result.exitCode}: ${detail}`);
13144
- }
13145
- } finally {
13146
- await rm(temporaryDirectory, { recursive: true, force: true });
13147
- }
13148
- }
13149
12476
  async function readExistingOutput(path) {
13150
12477
  try {
13151
12478
  await lstat(path);
@@ -13154,13 +12481,15 @@ async function readExistingOutput(path) {
13154
12481
  return;
13155
12482
  throw cause;
13156
12483
  }
13157
- const text3 = await readOwnerOnlyFile(path, 1048576);
13158
- let output;
12484
+ let parsed;
13159
12485
  try {
13160
- output = JSON.parse(text3);
13161
- } catch {
13162
- throw new Error(`${resolve2(path)} is not valid bootstrap JSON`);
12486
+ parsed = JSON.parse(await readOwnerOnlyFile(path, 1048576));
12487
+ } catch (cause) {
12488
+ if (cause instanceof SyntaxError)
12489
+ throw new Error(`${resolve2(path)} is not valid bootstrap JSON`);
12490
+ throw cause;
13163
12491
  }
12492
+ const output = parsed;
13164
12493
  if (output.format !== 1 || output.kind !== "forgezero-cloudflare-bootstrap" || !output.resources) {
13165
12494
  throw new Error(`${resolve2(path)} is not a ForgeZero Cloudflare bootstrap output`);
13166
12495
  }
@@ -13201,44 +12530,29 @@ async function readCloudflareHostHandoff(handoffPath, nodeName) {
13201
12530
  ].includes(key));
13202
12531
  if (unknown.length)
13203
12532
  throw new Error(`Cloudflare host handoff contains unsupported field ${unknown[0]}`);
13204
- if (output.warp) {
13205
- const unknownWarp = Object.keys(output.warp).filter((key) => ![
13206
- "organization",
13207
- "clientId",
13208
- "clientSecret",
13209
- "virtualNetworkId",
13210
- "deviceProfileId"
13211
- ].includes(key));
13212
- if (unknownWarp.length)
13213
- throw new Error(`Cloudflare host handoff WARP contains unsupported field ${unknownWarp[0]}`);
13214
- }
13215
12533
  const normalizedNodeName = nodeName.trim().toLowerCase();
13216
12534
  let service;
13217
12535
  try {
13218
- service = new URL(output.service ?? "");
12536
+ service = normalizeService(output.service ?? "");
13219
12537
  } catch {}
13220
- if (output.format !== 1 || output.kind !== "forgezero-cloudflare-host-handoff" || output.nodeName !== normalizedNodeName || !/^[a-f0-9]{32}$/i.test(output.accountId ?? "") || !/^[a-f0-9]{32}$/i.test(output.zoneId ?? "") || !/^[a-f0-9]{32}$/i.test(output.kvNamespaceId ?? "") || !/^[A-Za-z0-9._-]{40,80}$/.test(output.kvRuntimeToken ?? "") || !output.hostname || !/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/.test(output.hostname) || !service || service.protocol !== "http:" || service.hostname !== "127.0.0.1" || !service.port || service.pathname !== "/" || service.username || service.password || service.search || service.hash || !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(output.tunnelId ?? "") || !/^[A-Za-z0-9._-]{40,16384}$/.test(output.connectorToken ?? "")) {
12538
+ if (output.format !== 1 || output.kind !== "forgezero-cloudflare-host-handoff" || output.nodeName !== normalizedNodeName || !HOSTNAME.test(output.hostname ?? "") || !service || !UUID.test(output.tunnelId ?? "") || !CONNECTOR_TOKEN.test(output.connectorToken ?? "") || !TOKEN.test(output.kvRuntimeToken ?? "") || !/^[a-f0-9]{32}$/i.test(output.accountId ?? "") || !/^[a-f0-9]{32}$/i.test(output.zoneId ?? "") || !/^[a-f0-9]{32}$/i.test(output.kvNamespaceId ?? "")) {
13221
12539
  throw new Error("Cloudflare host handoff is malformed or belongs to another node");
13222
12540
  }
13223
- const network = output.privateNetworkRuntimeToken;
13224
- if (network !== undefined && !/^[A-Za-z0-9._-]{40,80}$/.test(network)) {
12541
+ if (output.privateNetworkRuntimeToken !== undefined && !TOKEN.test(output.privateNetworkRuntimeToken)) {
13225
12542
  throw new Error("Cloudflare host handoff private-network capability is malformed");
13226
12543
  }
13227
- if (Boolean(output.warp) !== Boolean(network)) {
12544
+ if (Boolean(output.warp) !== Boolean(output.privateNetworkRuntimeToken)) {
13228
12545
  throw new Error("Cloudflare host handoff private-network resources and capability disagree");
13229
12546
  }
13230
- if (output.warp && (!output.warp.clientId || !output.warp.clientSecret || !/^[A-Za-z0-9][A-Za-z0-9-]{0,62}$/.test(output.warp.organization) || !/^[0-9a-f-]{36}$/i.test(output.warp.virtualNetworkId) || !output.warp.deviceProfileId)) {
13231
- throw new Error("Cloudflare host handoff WARP enrollment is malformed");
13232
- }
13233
12547
  const { format: _format, kind: _kind, ...handoff } = output;
13234
12548
  return handoff;
13235
12549
  }
13236
12550
  async function prepareOwnerOutputDirectory(absolutePath) {
13237
12551
  const directory = dirname3(absolutePath);
13238
12552
  await mkdir(directory, { recursive: true, mode: 448 });
13239
- const directoryMetadata = await stat(directory);
12553
+ const metadata = await stat(directory);
13240
12554
  const uid = ownerUid();
13241
- if (!directoryMetadata.isDirectory() || uid !== undefined && directoryMetadata.uid !== uid || (directoryMetadata.mode & 18) !== 0) {
12555
+ if (!metadata.isDirectory() || uid !== undefined && metadata.uid !== uid || (metadata.mode & 18) !== 0) {
13242
12556
  throw new Error(`bootstrap output directory ${directory} must be operator-owned and not group/other writable`);
13243
12557
  }
13244
12558
  return directory;
@@ -13275,13 +12589,6 @@ async function writeOwnerBootstrapOutput(path, output) {
13275
12589
  await writeOwnerJson(path, output);
13276
12590
  }
13277
12591
  async function writeCloudflareHostHandoffs(checkpointPath, output) {
13278
- const kv = output.resources.runtimeTokens?.kv?.value;
13279
- if (!kv || !/^[A-Za-z0-9._-]{40,80}$/.test(kv)) {
13280
- return;
13281
- }
13282
- const network = output.resources.runtimeTokens?.privateNetwork?.value;
13283
- const privateNetwork = output.resources.privateNetwork;
13284
- const access = output.resources.access;
13285
12592
  for (const node of output.resources.nodes) {
13286
12593
  const handoff = {
13287
12594
  format: 1,
@@ -13294,34 +12601,19 @@ async function writeCloudflareHostHandoffs(checkpointPath, output) {
13294
12601
  accountId: output.coordinates.accountId,
13295
12602
  zoneId: output.coordinates.zoneId,
13296
12603
  kvNamespaceId: output.resources.kvNamespaceId,
13297
- kvRuntimeToken: kv,
13298
- ...network ? { privateNetworkRuntimeToken: network } : {},
13299
- ...privateNetwork && access ? { warp: {
13300
- organization: privateNetwork.warpOrganization,
13301
- clientId: access.clientId,
13302
- clientSecret: access.clientSecret,
13303
- virtualNetworkId: privateNetwork.virtualNetworkId,
13304
- deviceProfileId: privateNetwork.deviceProfileId
13305
- } } : {}
12604
+ kvRuntimeToken: output.resources.kvRuntimeToken
13306
12605
  };
13307
12606
  await writeOwnerJson(cloudflareHostHandoffPath(checkpointPath, node.nodeName), handoff);
13308
12607
  }
13309
12608
  }
13310
12609
  var tokenFor = (tokens, key) => {
13311
12610
  const token = tokens[key]?.trim() || tokens.apiToken?.trim();
13312
- if (!token)
12611
+ if (!token || !TOKEN.test(token))
13313
12612
  throw new Error(`Cloudflare ${key} is not configured`);
13314
12613
  return token;
13315
12614
  };
13316
- var initialManagementToken = (tokens) => {
13317
- const token = tokens.apiToken?.trim();
13318
- if (!token) {
13319
- throw new Error("initial Cloudflare --token-file is required to create account-owned runtime tokens");
13320
- }
13321
- return token;
13322
- };
13323
12615
  var sameCoordinates = (left, right) => JSON.stringify(left) === JSON.stringify(right);
13324
- async function applyCloudflareBootstrap(input, tokens, outputPath, fetcher = fetch, workerRunner = defaultWorkerCommandRunner) {
12616
+ async function applyCloudflareBootstrap(input, tokens, outputPath, fetcher = fetch) {
13325
12617
  const coordinates = validateCloudflareBootstrapCoordinates(input);
13326
12618
  const absoluteOutput = resolve2(outputPath);
13327
12619
  const existing = await readExistingOutput(absoluteOutput);
@@ -13329,19 +12621,18 @@ async function applyCloudflareBootstrap(input, tokens, outputPath, fetcher = fet
13329
12621
  throw new Error("bootstrap output belongs to different Cloudflare coordinates; choose a different output file");
13330
12622
  }
13331
12623
  await prepareOwnerOutputDirectory(absoluteOutput);
13332
- const namespace = await ensureCloudflareKvNamespace({
13333
- accountId: coordinates.accountId,
13334
- title: coordinates.kvNamespaceTitle,
13335
- apiToken: tokenFor(tokens, "kvApiToken")
13336
- }, fetcher);
12624
+ const kvRuntimeToken = tokenFor(tokens, "kvApiToken");
13337
12625
  const nodeResources = [];
13338
12626
  const createdNodes = [];
13339
- let resources;
13340
12627
  for (const node of coordinates.nodes) {
13341
12628
  const checkpointed = existing?.resources.nodes?.find(({ nodeName }) => nodeName === node.nodeName);
12629
+ let resource;
13342
12630
  let created = false;
13343
12631
  if (checkpointed) {
13344
- nodeResources.push(checkpointed);
12632
+ if (checkpointed.hostname !== node.hostname || checkpointed.service !== node.service || checkpointed.tunnelName !== node.tunnelName || !UUID.test(checkpointed.tunnelId) || !CONNECTOR_TOKEN.test(checkpointed.connectorToken)) {
12633
+ throw new Error(`checkpointed Cloudflare node ${node.nodeName} is malformed`);
12634
+ }
12635
+ resource = checkpointed;
13345
12636
  } else {
13346
12637
  const tunnel = await ensureCloudflareTunnel({
13347
12638
  accountId: coordinates.accountId,
@@ -13349,234 +12640,29 @@ async function applyCloudflareBootstrap(input, tokens, outputPath, fetcher = fet
13349
12640
  apiToken: tokenFor(tokens, "tunnelApiToken")
13350
12641
  }, fetcher);
13351
12642
  created = tunnel.created;
13352
- nodeResources.push({
13353
- nodeName: node.nodeName,
13354
- hostname: node.hostname,
13355
- service: node.service,
13356
- tunnelName: node.tunnelName,
13357
- tunnelId: tunnel.tunnel.id,
13358
- connectorToken: tunnel.connectorToken
13359
- });
12643
+ resource = { ...node, tunnelId: tunnel.tunnel.id, connectorToken: tunnel.connectorToken };
13360
12644
  }
13361
- createdNodes.push({ nodeName: node.nodeName, tunnel: created, application: false });
13362
- const firstNode = nodeResources[0];
13363
- resources = {
13364
- tunnelId: firstNode.tunnelId,
13365
- kvNamespaceId: namespace.namespace.id,
13366
- hostname: firstNode.hostname,
13367
- service: firstNode.service,
13368
- connectorToken: firstNode.connectorToken,
13369
- nodes: [...nodeResources],
13370
- ...existing?.resources.access ? { access: existing.resources.access } : {},
13371
- ...existing?.resources.runtimeTokens ? { runtimeTokens: existing.resources.runtimeTokens } : {},
13372
- ...existing?.resources.worker ? { worker: existing.resources.worker } : {},
13373
- ...existing?.resources.privateNetwork ? { privateNetwork: existing.resources.privateNetwork } : {}
13374
- };
13375
- await writeOwnerBootstrapOutput(absoluteOutput, {
13376
- format: 1,
13377
- kind: "forgezero-cloudflare-bootstrap",
13378
- phase: resources.access ? "access-token-provisioned" : "edge-resources-provisioned",
13379
- updatedAt: new Date().toISOString(),
13380
- coordinates,
13381
- resources
13382
- });
13383
- }
13384
- if (!resources)
13385
- throw new Error("Cloudflare fleet has no nodes");
13386
- if (coordinates.createRuntimeTokens && !resources.runtimeTokens) {
13387
- resources = {
13388
- ...resources,
13389
- runtimeTokens: { kv: await createCloudflareAccountRuntimeToken({
13390
- accountId: coordinates.accountId,
13391
- name: `${coordinates.runtimeTokenNamePrefix}-kv-runtime`,
13392
- permissionNames: ["Workers KV Storage Write"],
13393
- apiToken: initialManagementToken(tokens)
13394
- }, fetcher) }
13395
- };
13396
- await writeOwnerBootstrapOutput(absoluteOutput, {
13397
- format: 1,
13398
- kind: "forgezero-cloudflare-bootstrap",
13399
- phase: "runtime-tokens-created",
13400
- updatedAt: new Date().toISOString(),
13401
- coordinates,
13402
- resources
13403
- });
13404
- }
13405
- if (coordinates.createPrivateNetworkRuntimeToken && resources.runtimeTokens && !resources.runtimeTokens.privateNetwork) {
13406
- resources = {
13407
- ...resources,
13408
- runtimeTokens: {
13409
- ...resources.runtimeTokens,
13410
- privateNetwork: await createCloudflareAccountRuntimeToken({
13411
- accountId: coordinates.accountId,
13412
- name: `${coordinates.runtimeTokenNamePrefix}-private-network-runtime`,
13413
- permissionNames: ["Cloudflare One Networks Write", "Zero Trust Write"],
13414
- apiToken: initialManagementToken(tokens)
13415
- }, fetcher)
13416
- }
13417
- };
12645
+ nodeResources.push(resource);
12646
+ createdNodes.push({ nodeName: node.nodeName, tunnel: created });
12647
+ const first2 = nodeResources[0];
13418
12648
  await writeOwnerBootstrapOutput(absoluteOutput, {
13419
12649
  format: 1,
13420
12650
  kind: "forgezero-cloudflare-bootstrap",
13421
- phase: "runtime-tokens-created",
12651
+ phase: "edge-resources-provisioned",
13422
12652
  updatedAt: new Date().toISOString(),
13423
12653
  coordinates,
13424
- resources
13425
- });
13426
- }
13427
- if (!resources.worker) {
13428
- await deployCloudflareWorker(coordinates, namespace.namespace.id, tokenFor(tokens, "workerApiToken"), workerRunner);
13429
- resources = {
13430
- ...resources,
13431
- worker: {
13432
- scriptName: coordinates.workerScriptName,
13433
- publicDomains: coordinates.publicDomains,
13434
- deployed: true
12654
+ resources: {
12655
+ tunnelId: first2.tunnelId,
12656
+ kvNamespaceId: coordinates.kvNamespaceId,
12657
+ hostname: first2.hostname,
12658
+ service: first2.service,
12659
+ connectorToken: first2.connectorToken,
12660
+ kvRuntimeToken,
12661
+ nodes: [...nodeResources]
13435
12662
  }
13436
- };
13437
- await writeOwnerBootstrapOutput(absoluteOutput, {
13438
- format: 1,
13439
- kind: "forgezero-cloudflare-bootstrap",
13440
- phase: "worker-deployed",
13441
- updatedAt: new Date().toISOString(),
13442
- coordinates,
13443
- resources
13444
12663
  });
13445
12664
  }
13446
- const serviceToken = await ensureCloudflareAccessServiceToken({
13447
- accountId: coordinates.accountId,
13448
- name: coordinates.serviceTokenName,
13449
- apiToken: tokenFor(tokens, "accessApiToken"),
13450
- existing: resources.access
13451
- }, fetcher);
13452
- resources = { ...resources, access: serviceToken.credentials };
13453
- await writeOwnerBootstrapOutput(absoluteOutput, {
13454
- format: 1,
13455
- kind: "forgezero-cloudflare-bootstrap",
13456
- phase: "access-token-provisioned",
13457
- updatedAt: new Date().toISOString(),
13458
- coordinates,
13459
- resources
13460
- });
13461
- const policy = await ensureCloudflareAccessPolicy({
13462
- accountId: coordinates.accountId,
13463
- name: coordinates.policyName,
13464
- serviceTokenId: serviceToken.credentials.tokenId,
13465
- apiToken: tokenFor(tokens, "accessApiToken")
13466
- }, fetcher);
13467
- if (!policy.policy.id)
13468
- throw new Error("Cloudflare did not return the Access policy id");
13469
- resources = {
13470
- ...resources,
13471
- access: { ...serviceToken.credentials, policyId: policy.policy.id }
13472
- };
13473
- await writeOwnerBootstrapOutput(absoluteOutput, {
13474
- format: 1,
13475
- kind: "forgezero-cloudflare-bootstrap",
13476
- phase: "access-token-provisioned",
13477
- updatedAt: new Date().toISOString(),
13478
- coordinates,
13479
- resources
13480
- });
13481
- let privateNetworkCreated = false;
13482
- if (coordinates.privateNetwork && !resources.privateNetwork) {
13483
- const managementToken = initialManagementToken(tokens);
13484
- const virtualNetwork = await ensureCloudflareVirtualNetwork({
13485
- accountId: coordinates.accountId,
13486
- name: coordinates.privateNetwork.virtualNetworkName,
13487
- comment: "ForgeZero private database network",
13488
- apiToken: managementToken
13489
- }, fetcher);
13490
- const enrollment = await ensureCloudflareWarpEnrollmentApplication({
13491
- accountId: coordinates.accountId,
13492
- name: coordinates.privateNetwork.enrollmentApplicationName,
13493
- policyId: policy.policy.id,
13494
- apiToken: tokenFor(tokens, "accessApiToken")
13495
- }, fetcher);
13496
- const deviceProfile = await ensureCloudflareWarpDevicePolicy({
13497
- accountId: coordinates.accountId,
13498
- name: coordinates.privateNetwork.deviceProfileName,
13499
- serviceTokenId: serviceToken.credentials.tokenId,
13500
- virtualNetworkId: virtualNetwork.virtualNetwork.id,
13501
- precedence: coordinates.privateNetwork.deviceProfilePrecedence,
13502
- apiToken: managementToken
13503
- }, fetcher);
13504
- const routes = [];
13505
- for (const node of coordinates.nodes.filter((item) => item.privateAddress)) {
13506
- const resource = resources.nodes.find((item) => item.nodeName === node.nodeName);
13507
- const route = await ensureCloudflarePrivateDatabaseRoute({
13508
- accountId: coordinates.accountId,
13509
- tunnelId: resource.tunnelId,
13510
- privateAddress: node.privateAddress,
13511
- virtualNetworkId: virtualNetwork.virtualNetwork.id,
13512
- comment: `ForgeZero ${node.nodeName} database`,
13513
- apiToken: managementToken
13514
- }, fetcher);
13515
- await ensureCloudflareWarpDatabaseInclude({
13516
- accountId: coordinates.accountId,
13517
- policyId: deviceProfile.policy.id,
13518
- privateAddress: node.privateAddress,
13519
- description: `ForgeZero ${node.nodeName} database`,
13520
- apiToken: managementToken
13521
- }, fetcher);
13522
- routes.push({ nodeName: node.nodeName, routeId: route.route.id, privateAddress: node.privateAddress });
13523
- }
13524
- resources = {
13525
- ...resources,
13526
- privateNetwork: {
13527
- warpOrganization: coordinates.privateNetwork.warpOrganization,
13528
- virtualNetworkId: virtualNetwork.virtualNetwork.id,
13529
- deviceProfileId: deviceProfile.policy.id,
13530
- enrollmentApplicationId: enrollment.application.id,
13531
- routes
13532
- }
13533
- };
13534
- privateNetworkCreated = virtualNetwork.created || enrollment.created || deviceProfile.created || routes.length > 0;
13535
- await writeOwnerBootstrapOutput(absoluteOutput, {
13536
- format: 1,
13537
- kind: "forgezero-cloudflare-bootstrap",
13538
- phase: "access-token-provisioned",
13539
- updatedAt: new Date().toISOString(),
13540
- coordinates,
13541
- resources
13542
- });
13543
- }
13544
- for (const node of coordinates.nodes) {
13545
- const application = await ensureCloudflareAccessApplication({
13546
- accountId: coordinates.accountId,
13547
- name: node.applicationName,
13548
- hostname: node.hostname,
13549
- policyId: policy.policy.id,
13550
- apiToken: tokenFor(tokens, "accessApiToken")
13551
- }, fetcher);
13552
- if (!application.application.id)
13553
- throw new Error(`Cloudflare did not return the Access application id for ${node.nodeName}`);
13554
- resources = {
13555
- ...resources,
13556
- nodes: resources.nodes.map((resource) => resource.nodeName === node.nodeName ? { ...resource, applicationId: application.application.id } : resource),
13557
- access: {
13558
- ...resources.access,
13559
- ...node.nodeName === coordinates.nodes[0].nodeName ? { applicationId: application.application.id } : {}
13560
- }
13561
- };
13562
- const createdNode = createdNodes.find(({ nodeName }) => nodeName === node.nodeName);
13563
- createdNode.application = application.created;
13564
- await writeOwnerBootstrapOutput(absoluteOutput, {
13565
- format: 1,
13566
- kind: "forgezero-cloudflare-bootstrap",
13567
- phase: "access-token-provisioned",
13568
- updatedAt: new Date().toISOString(),
13569
- coordinates,
13570
- resources
13571
- });
13572
- }
13573
- await configureCloudflareWorkerAccessSecrets({
13574
- accountId: coordinates.accountId,
13575
- scriptName: coordinates.workerScriptName,
13576
- credentials: serviceToken.credentials,
13577
- apiToken: tokenFor(tokens, "workerApiToken")
13578
- }, fetcher);
13579
- for (const node of resources.nodes) {
12665
+ for (const node of nodeResources) {
13580
12666
  await configureCloudflareEdge({
13581
12667
  accountId: coordinates.accountId,
13582
12668
  zoneId: coordinates.zoneId,
@@ -13588,21 +12674,24 @@ async function applyCloudflareBootstrap(input, tokens, outputPath, fetcher = fet
13588
12674
  dnsApiToken: tokenFor(tokens, "dnsApiToken")
13589
12675
  }, fetcher);
13590
12676
  }
12677
+ const first = nodeResources[0];
13591
12678
  const output = {
13592
12679
  format: 1,
13593
12680
  kind: "forgezero-cloudflare-bootstrap",
13594
12681
  phase: "complete",
13595
12682
  updatedAt: new Date().toISOString(),
13596
12683
  coordinates,
13597
- resources,
12684
+ resources: {
12685
+ tunnelId: first.tunnelId,
12686
+ kvNamespaceId: coordinates.kvNamespaceId,
12687
+ hostname: first.hostname,
12688
+ service: first.service,
12689
+ connectorToken: first.connectorToken,
12690
+ kvRuntimeToken,
12691
+ nodes: nodeResources
12692
+ },
13598
12693
  created: {
13599
12694
  tunnel: createdNodes.some(({ tunnel }) => tunnel),
13600
- kvNamespace: namespace.created,
13601
- serviceToken: serviceToken.created,
13602
- policy: policy.created,
13603
- application: createdNodes.some(({ application }) => application),
13604
- privateNetwork: privateNetworkCreated,
13605
- workerDeployed: true,
13606
12695
  nodes: createdNodes
13607
12696
  }
13608
12697
  };
@@ -13610,99 +12699,73 @@ async function applyCloudflareBootstrap(input, tokens, outputPath, fetcher = fet
13610
12699
  await writeCloudflareHostHandoffs(absoluteOutput, output);
13611
12700
  return output;
13612
12701
  }
13613
- async function runAttendedCloudflareBootstrap(request2, dependencies = {}) {
13614
- const plan = planCloudflareBootstrap(request2.coordinates, request2.checkpointPath);
13615
- if (request2.mode === "plan") {
12702
+ async function runAttendedCloudflareBootstrap(request, dependencies = {}) {
12703
+ const plan = planCloudflareBootstrap(request.coordinates, request.checkpointPath);
12704
+ if (request.mode === "plan") {
13616
12705
  return {
13617
12706
  format: 1,
13618
12707
  kind: "forgezero-cloudflare-bootstrap-evidence",
13619
12708
  phase: "planned",
13620
12709
  checkpointFile: plan.outputFile,
13621
- workerScriptName: plan.coordinates.workerScriptName,
13622
- publicDomains: plan.coordinates.publicDomains,
13623
12710
  nodes: plan.coordinates.nodes.map(({ nodeName, hostname }) => ({ nodeName, hostname }))
13624
12711
  };
13625
12712
  }
13626
- if (!request2.tokenFiles || !Object.values(request2.tokenFiles).some(Boolean)) {
12713
+ if (!request.tokenFiles || !Object.values(request.tokenFiles).some(Boolean)) {
13627
12714
  throw new Error("Cloudflare apply requires owner-only management token file paths");
13628
12715
  }
13629
- if (plan.coordinates.createRuntimeTokens && !request2.tokenFiles.apiTokenFile) {
13630
- throw new Error("Cloudflare runtime-token creation requires the initial management token file");
13631
- }
13632
- const tokens = await readCloudflareBootstrapTokens(request2.tokenFiles);
13633
- const output = await applyCloudflareBootstrap(plan.coordinates, tokens, plan.outputFile, dependencies.fetcher ?? fetch, dependencies.workerRunner);
12716
+ const tokens = await readCloudflareBootstrapTokens(request.tokenFiles);
12717
+ const output = await applyCloudflareBootstrap(plan.coordinates, tokens, plan.outputFile, dependencies.fetcher ?? fetch);
13634
12718
  return {
13635
12719
  format: 1,
13636
12720
  kind: "forgezero-cloudflare-bootstrap-evidence",
13637
12721
  phase: "complete",
13638
12722
  checkpointFile: plan.outputFile,
13639
12723
  kvNamespaceId: output.resources.kvNamespaceId,
13640
- workerScriptName: output.coordinates.workerScriptName,
13641
- publicDomains: output.resources.worker?.publicDomains ?? output.coordinates.publicDomains,
13642
- runtimeTokenIds: {
13643
- kv: output.resources.runtimeTokens?.kv.id,
13644
- privateNetwork: output.resources.runtimeTokens?.privateNetwork?.id
13645
- },
13646
- nodes: output.resources.nodes.map(({ nodeName, hostname, tunnelId, applicationId }) => ({
12724
+ nodes: output.resources.nodes.map(({ nodeName, hostname, tunnelId }) => ({
13647
12725
  nodeName,
13648
12726
  hostname,
13649
- ...output.resources.runtimeTokens?.kv ? {
13650
- handoffFile: cloudflareHostHandoffPath(plan.outputFile, nodeName)
13651
- } : {},
13652
- tunnelId,
13653
- applicationId
12727
+ handoffFile: cloudflareHostHandoffPath(plan.outputFile, nodeName),
12728
+ tunnelId
13654
12729
  }))
13655
12730
  };
13656
12731
  }
12732
+ var acceptanceFetch = async (url, label, fetcher) => {
12733
+ let response;
12734
+ try {
12735
+ response = await fetcher(url, { method: "GET", redirect: "manual", signal: AbortSignal.timeout(5000) });
12736
+ } catch {
12737
+ throw new Error(`${label} is unreachable`);
12738
+ }
12739
+ if (!response.ok)
12740
+ throw new Error(`${label} returned HTTP ${response.status}`);
12741
+ return response.status;
12742
+ };
13657
12743
  async function verifyCloudflareBootstrapAcceptance(checkpointPath, fetcher = fetch) {
13658
12744
  const absolute = resolve2(checkpointPath);
13659
12745
  const output = await readExistingOutput(absolute);
13660
12746
  if (!output || output.phase !== "complete")
13661
12747
  throw new Error("Cloudflare acceptance requires a completed owner checkpoint");
13662
12748
  const coordinates = validateCloudflareBootstrapCoordinates(output.coordinates);
13663
- const access = output.resources.access;
13664
- if (!access?.clientId?.trim() || !access.clientSecret?.trim()) {
13665
- throw new Error("Cloudflare acceptance checkpoint is missing the Access service credential");
13666
- }
13667
- if (!output.resources.worker?.deployed || output.resources.worker.scriptName !== coordinates.workerScriptName || JSON.stringify(output.resources.worker.publicDomains) !== JSON.stringify(coordinates.publicDomains)) {
13668
- throw new Error("Cloudflare acceptance checkpoint does not prove the expected Worker deployment");
13669
- }
13670
12749
  if (output.resources.nodes.length !== coordinates.nodes.length) {
13671
12750
  throw new Error("Cloudflare acceptance checkpoint does not cover the declared node fleet");
13672
12751
  }
13673
- const nodeNames = new Set;
13674
- const hostnames = new Set;
13675
12752
  for (const node of output.resources.nodes) {
13676
12753
  const expected = coordinates.nodes.find((candidate) => candidate.nodeName === node.nodeName);
13677
- if (!expected || node.hostname !== expected.hostname || node.service !== expected.service || node.tunnelName !== expected.tunnelName || !/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(node.tunnelId)) {
12754
+ if (!expected || node.hostname !== expected.hostname || node.service !== expected.service || node.tunnelName !== expected.tunnelName || !UUID.test(node.tunnelId)) {
13678
12755
  throw new Error("Cloudflare acceptance checkpoint has an unbound node resource");
13679
12756
  }
13680
- if (nodeNames.has(node.nodeName) || hostnames.has(node.hostname)) {
13681
- throw new Error("Cloudflare acceptance checkpoint has duplicate node coordinates");
13682
- }
13683
- nodeNames.add(node.nodeName);
13684
- hostnames.add(node.hostname);
13685
12757
  }
13686
- const accessHeaders = {
13687
- "CF-Access-Client-Id": access.clientId,
13688
- "CF-Access-Client-Secret": access.clientSecret
13689
- };
13690
12758
  const nodes = await Promise.all(output.resources.nodes.map(async ({ nodeName, hostname }) => ({
13691
12759
  nodeName,
13692
12760
  hostname,
13693
- status: await acceptanceFetch(`https://${hostname}/api/health`, `Cloudflare origin ${nodeName}`, fetcher, accessHeaders)
13694
- })));
13695
- const publicDomains = await Promise.all(output.resources.worker.publicDomains.map(async (hostname) => ({
13696
- hostname,
13697
- status: await acceptanceFetch(`https://${hostname}/api/health`, `Cloudflare public domain ${hostname}`, fetcher)
12761
+ status: await acceptanceFetch(`https://${hostname}/api/health`, `Cloudflare origin ${nodeName}`, fetcher)
13698
12762
  })));
13699
12763
  return {
13700
12764
  format: 1,
13701
12765
  kind: "forgezero-cloudflare-bootstrap-acceptance",
13702
12766
  checkpointFile: absolute,
13703
12767
  verifiedAt: new Date().toISOString(),
13704
- nodes,
13705
- publicDomains
12768
+ nodes
13706
12769
  };
13707
12770
  }
13708
12771
 
@@ -13788,6 +12851,18 @@ function validateBootstrapConfig(value) {
13788
12851
  if (telemetry.protocol !== "https:" || telemetry.port && telemetry.port !== "443" || telemetry.username || telemetry.password || telemetry.search || telemetry.hash) {
13789
12852
  throw new Error("telemetry endpoint must be public HTTPS on port 443 without credentials, query or fragment");
13790
12853
  }
12854
+ const deploymentCredentials = value.deploymentCredentials ?? {};
12855
+ if (!deploymentCredentials || typeof deploymentCredentials !== "object" || Array.isArray(deploymentCredentials) || Object.keys(deploymentCredentials).length > 64) {
12856
+ throw new Error("deployment credentials must be a bounded name-to-path object");
12857
+ }
12858
+ for (const [name, path] of Object.entries(deploymentCredentials)) {
12859
+ if (!/^[A-Z_][A-Z0-9_]*$/.test(name) || typeof path !== "string" || !path.startsWith("/") || /[\r\n:]/.test(path) || !path.endsWith(".cred")) {
12860
+ throw new Error(`deployment credential ${name} must map to an absolute encrypted .cred path`);
12861
+ }
12862
+ }
12863
+ if (value.cloudflareHandoff && (!value.installCloudflared || !value.cloudflareHandoff.handoffFile || !/^[a-z0-9][a-z0-9-]{0,62}$/.test(value.cloudflareHandoff.nodeName))) {
12864
+ throw new Error("Cloudflare handoff requires cloudflared installation, a checkpoint and a valid node name");
12865
+ }
13791
12866
  if (value.kind === "tenant") {
13792
12867
  if (!/^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/.test(value.realm))
13793
12868
  throw new Error("tenant realm is malformed");
@@ -13867,9 +12942,6 @@ function validateBootstrapConfig(value) {
13867
12942
  if (!["genesis-derived", "api-token"].includes(value.enrolment.source) || value.enrolment.source === "api-token" && !value.enrolment.tokenFile || value.enrolment.source === "genesis-derived" && value.enrolment.tokenFile) {
13868
12943
  throw new Error("platform enrolment source and token file disagree");
13869
12944
  }
13870
- if (value.cloudflareHandoff && (!value.installCloudflared || !value.cloudflareHandoff.handoffFile || !/^[a-z0-9][a-z0-9-]{0,62}$/.test(value.cloudflareHandoff.nodeName))) {
13871
- throw new Error("Cloudflare handoff requires cloudflared installation, a checkpoint and a valid node name");
13872
- }
13873
12945
  value.runtime.environment = runtime;
13874
12946
  return value;
13875
12947
  }
@@ -14121,7 +13193,7 @@ function bootstrapIdentity(config) {
14121
13193
  };
14122
13194
  }
14123
13195
  function bootstrapIdentityDigest(config) {
14124
- return createHash3("sha256").update(JSON.stringify(bootstrapIdentity(config))).digest("hex");
13196
+ return createHash2("sha256").update(JSON.stringify(bootstrapIdentity(config))).digest("hex");
14125
13197
  }
14126
13198
  function parseStoredState(raw) {
14127
13199
  let value;
@@ -14199,7 +13271,7 @@ async function preparePlatformBootstrap(input, host = localBootstrapHost()) {
14199
13271
  next: "register this read-only deploy key, then run --apply concurrently on all three genesis Agency members"
14200
13272
  };
14201
13273
  }
14202
- function stateFor(config) {
13274
+ function stateFor(config, cloudflare, previousCloudflareTunnelId) {
14203
13275
  return `${JSON.stringify({
14204
13276
  format: 2,
14205
13277
  kind: config.kind,
@@ -14220,7 +13292,11 @@ function stateFor(config) {
14220
13292
  healthPath: config.runtime.healthPath,
14221
13293
  cloudflare: config.runtime.environment.cloudflare,
14222
13294
  cloudflared: Boolean(config.cloudflareHandoff)
14223
- } : { realm: config.realm }
13295
+ } : {
13296
+ realm: config.realm,
13297
+ cloudflared: Boolean(config.cloudflareHandoff),
13298
+ cloudflareTunnelId: cloudflare?.tunnelId ?? previousCloudflareTunnelId
13299
+ }
14224
13300
  }, null, 2)}
14225
13301
  `;
14226
13302
  }
@@ -14238,7 +13314,7 @@ async function bootstrapStatus(host = localBootstrapHost()) {
14238
13314
  units.push("nginx.service");
14239
13315
  if (state.kind === "platform" && state.collectorUnit)
14240
13316
  units.push(state.collectorUnit);
14241
- if (state.kind === "platform" && state.cloudflared)
13317
+ if (state.cloudflared)
14242
13318
  units.push("cloudflared.service");
14243
13319
  if (state.kind === "platform" && state.databaseRole !== "none")
14244
13320
  units.push("forgezero-db.service", "forgezero-db-verify.service");
@@ -14255,13 +13331,25 @@ async function bootstrapStatus(host = localBootstrapHost()) {
14255
13331
  if (!services[socket])
14256
13332
  problems.push(`${socket} is missing`);
14257
13333
  }
13334
+ if (state.cloudflared) {
13335
+ for (const credential of [TUNNEL_CREDENTIAL, `${CREDS}/cloudflare-kv-token.cred`]) {
13336
+ services[credential] = host.exists(credential);
13337
+ if (!services[credential])
13338
+ problems.push(`${credential} is missing`);
13339
+ }
13340
+ const tunnelId = state.cloudflare?.tunnelId ?? state.cloudflareTunnelId;
13341
+ if (!tunnelId) {
13342
+ services["cloudflared-tunnel"] = false;
13343
+ problems.push("Cloudflare tunnel identity is missing from bootstrap state");
13344
+ } else {
13345
+ const evidence = await inspectCloudflaredTunnel(host, tunnelId);
13346
+ services["cloudflared-tunnel"] = evidence.healthy;
13347
+ if (!evidence.healthy)
13348
+ problems.push(evidence.problem);
13349
+ }
13350
+ }
14258
13351
  if (state.kind === "platform") {
14259
13352
  if (state.cloudflared) {
14260
- for (const credential of [TUNNEL_CREDENTIAL, `${CREDS}/cloudflare-kv-token.cred`]) {
14261
- services[credential] = host.exists(credential);
14262
- if (!services[credential])
14263
- problems.push(`${credential} is missing`);
14264
- }
14265
13353
  if (state.cloudflare?.warp) {
14266
13354
  for (const credential of [
14267
13355
  `${CREDS}/cloudflare-network-token.cred`,
@@ -14273,15 +13361,6 @@ async function bootstrapStatus(host = localBootstrapHost()) {
14273
13361
  problems.push(`${credential} is missing`);
14274
13362
  }
14275
13363
  }
14276
- if (!state.cloudflare?.tunnelId) {
14277
- services["cloudflared-tunnel"] = false;
14278
- problems.push("Cloudflare tunnel identity is missing from bootstrap state");
14279
- } else {
14280
- const evidence = await inspectCloudflaredTunnel(host, state.cloudflare.tunnelId);
14281
- services["cloudflared-tunnel"] = evidence.healthy;
14282
- if (!evidence.healthy)
14283
- problems.push(evidence.problem);
14284
- }
14285
13364
  }
14286
13365
  const [blue, green] = await Promise.all([
14287
13366
  host.exec(["systemctl", "is-active", "--quiet", "forgezero@blue.service"]),
@@ -14350,20 +13429,21 @@ async function applyBootstrap(input, host = localBootstrapHost()) {
14350
13429
  if (host.exists(STATE_PATH))
14351
13430
  installed = parseStoredState(host.read(STATE_PATH));
14352
13431
  let cloudflare;
14353
- if (config.kind === "platform" && config.cloudflareHandoff) {
13432
+ if (config.cloudflareHandoff) {
14354
13433
  if (host.exists(config.cloudflareHandoff.handoffFile)) {
14355
13434
  cloudflare = await readCloudflareHostHandoff(config.cloudflareHandoff.handoffFile, config.cloudflareHandoff.nodeName);
14356
- } else if (installed?.cloudflare) {
13435
+ } else if (config.kind === "platform" && installed?.cloudflare) {
14357
13436
  config.runtime.environment.cloudflare = installed.cloudflare;
14358
13437
  config.runtime.environment = validatePlatformSharedEnvironment(config.runtime.environment);
14359
- } else {
13438
+ } else if (!(config.kind === "tenant" && installed?.cloudflareTunnelId)) {
14360
13439
  throw new Error("node-specific Cloudflare host handoff is missing before credential sealing");
14361
13440
  }
14362
13441
  }
13442
+ if (cloudflare && cloudflare.hostname !== config.nodeHostname) {
13443
+ throw new Error("Cloudflare handoff hostname disagrees with node hostname");
13444
+ }
14363
13445
  if (cloudflare && config.kind === "platform") {
14364
13446
  const expected = config.runtime.environment.cloudflare;
14365
- if (cloudflare.hostname !== config.nodeHostname)
14366
- throw new Error("Cloudflare handoff hostname disagrees with platform node hostname");
14367
13447
  if (expected && (cloudflare.service !== expected.tunnelService || cloudflare.accountId !== expected.accountId || cloudflare.zoneId !== expected.zoneId || cloudflare.kvNamespaceId !== expected.kvNamespaceId || cloudflare.tunnelId !== expected.tunnelId)) {
14368
13448
  throw new Error("Cloudflare handoff disagrees with immutable platform runtime coordinates");
14369
13449
  }
@@ -14405,6 +13485,9 @@ async function applyBootstrap(input, host = localBootstrapHost()) {
14405
13485
  await seal(host, "warp-auth-client-id", WARP_CLIENT_ID_CREDENTIAL, cloudflare.warp.clientId);
14406
13486
  await seal(host, "warp-auth-client-secret", WARP_CLIENT_SECRET_CREDENTIAL, cloudflare.warp.clientSecret);
14407
13487
  }
13488
+ if (cloudflare && !host.exists(`${CREDS}/cloudflare-kv-token.cred`)) {
13489
+ await seal(host, "cloudflare-kv-token", `${CREDS}/cloudflare-kv-token.cred`, cloudflare.kvRuntimeToken);
13490
+ }
14408
13491
  await host.installAgent(config);
14409
13492
  if (config.kind === "platform" && config.firewall.enabled) {
14410
13493
  await host.ensureSoftware(plan.software.filter(({ id: id2 }) => id2 === "ufw"));
@@ -14444,7 +13527,6 @@ async function applyBootstrap(input, host = localBootstrapHost()) {
14444
13527
  }
14445
13528
  }
14446
13529
  if (cloudflare) {
14447
- await seal(host, "cloudflare-kv-token", `${CREDS}/cloudflare-kv-token.cred`, cloudflare.kvRuntimeToken);
14448
13530
  if (cloudflare.privateNetworkRuntimeToken)
14449
13531
  await seal(host, "cloudflare-network-token", `${CREDS}/cloudflare-network-token.cred`, cloudflare.privateNetworkRuntimeToken);
14450
13532
  }
@@ -14465,16 +13547,23 @@ async function applyBootstrap(input, host = localBootstrapHost()) {
14465
13547
  bluePort: runtime.bluePort,
14466
13548
  greenPort: runtime.greenPort,
14467
13549
  collectorUnit: runtime.environment.otlpCollectorUnit,
14468
- credentials
13550
+ credentials,
13551
+ capacityEnvironmentFile: "/etc/forgezero/capacity.env"
13552
+ });
13553
+ const edge = renderPlatformNginx({
13554
+ publicPort: runtime.environment.publicApiPort,
13555
+ initialSlotPort: runtime.bluePort,
13556
+ concurrencyLimit: runtime.environment.concurrencyLimit,
13557
+ workerDrainSeconds: Math.ceil(runtime.environment.drainDeadlineMs / 1000)
14469
13558
  });
14470
- const edge = renderPlatformNginx({ publicPort: runtime.environment.publicApiPort, initialSlotPort: runtime.bluePort });
14471
13559
  const activation = renderPlatformActivationFiles({
14472
13560
  root: config.deployRoot ?? "/opt/forgezero",
14473
13561
  serviceUser: runtime.serviceUser,
14474
13562
  bluePort: runtime.bluePort,
14475
13563
  greenPort: runtime.greenPort,
14476
13564
  healthPath: runtime.healthPath,
14477
- keepReleases: runtime.keepReleases
13565
+ keepReleases: runtime.keepReleases,
13566
+ drainDeadlineMs: runtime.environment.drainDeadlineMs
14478
13567
  });
14479
13568
  await checked(host, ["useradd", "--system", "--no-create-home", "--shell", "/usr/sbin/nologin", runtime.serviceUser], "API service account").catch(async () => {
14480
13569
  await checked(host, ["id", runtime.serviceUser], "existing API service account");
@@ -14482,6 +13571,10 @@ async function applyBootstrap(input, host = localBootstrapHost()) {
14482
13571
  host.mkdir(runtime.environment.sharedDirectory, 488);
14483
13572
  host.mkdir(runtime.slotsDirectory, 493);
14484
13573
  host.write(envPath, renderPlatformSharedEnvironment(runtime.environment), 416);
13574
+ if (!host.exists("/etc/forgezero/capacity.env")) {
13575
+ host.write("/etc/forgezero/capacity.env", `FZ_CONCURRENCY_LIMIT=${runtime.environment.concurrencyLimit}
13576
+ `, 420);
13577
+ }
14485
13578
  await checked(host, ["chown", `root:${runtime.serviceUser}`, runtime.environment.sharedDirectory, envPath], "runtime ownership");
14486
13579
  host.write("/etc/systemd/system/forgezero@.service", units.template, 420);
14487
13580
  host.write("/etc/systemd/system/forgezero@blue.service.d/port.conf", units.dropIns.blue, 420);
@@ -14547,7 +13640,7 @@ async function applyBootstrap(input, host = localBootstrapHost()) {
14547
13640
  host.remove(config.enrolment.tokenFile);
14548
13641
  }
14549
13642
  }
14550
- if (config.kind === "platform" && config.cloudflareHandoff) {
13643
+ if (config.cloudflareHandoff) {
14551
13644
  if (!host.exists(TUNNEL_CREDENTIAL) && cloudflare) {
14552
13645
  await seal(host, "cloudflared-token", TUNNEL_CREDENTIAL, cloudflare.connectorToken);
14553
13646
  }
@@ -14556,17 +13649,17 @@ async function applyBootstrap(input, host = localBootstrapHost()) {
14556
13649
  host.write("/etc/systemd/system/cloudflared.service", tunnelUnit(), 420);
14557
13650
  await checked(host, ["systemctl", "daemon-reload"], "cloudflared unit reload");
14558
13651
  await checked(host, ["systemctl", "enable", "--now", "cloudflared.service"], "cloudflared connector supervision");
14559
- const tunnelId = config.runtime.environment.cloudflare?.tunnelId;
13652
+ const tunnelId = cloudflare?.tunnelId ?? installed?.cloudflareTunnelId ?? (config.kind === "platform" ? config.runtime.environment.cloudflare?.tunnelId : undefined);
14560
13653
  if (!tunnelId)
14561
13654
  throw new Error("Cloudflare tunnel identity is missing after handoff validation");
14562
13655
  await waitForCloudflaredTunnel(host, tunnelId);
14563
13656
  }
14564
- host.write(STATE_PATH, stateFor(config), 384);
13657
+ host.write(STATE_PATH, stateFor(config, cloudflare, installed?.cloudflareTunnelId), 384);
14565
13658
  const status = await bootstrapStatus(host);
14566
13659
  if (!status.initialized)
14567
13660
  throw new Error(`bootstrap verification failed: ${status.problems.join("; ")}`);
14568
13661
  host.remove(INTENT_PATH);
14569
- if (cloudflare && config.kind === "platform" && config.cloudflareHandoff) {
13662
+ if (cloudflare && config.cloudflareHandoff) {
14570
13663
  host.remove(config.cloudflareHandoff.handoffFile);
14571
13664
  }
14572
13665
  let launch;
@@ -14614,6 +13707,7 @@ function strictBootstrapDocument(value) {
14614
13707
  "realm",
14615
13708
  "enrolTokenFile",
14616
13709
  "software",
13710
+ "deploymentCredentials",
14617
13711
  "bootstrapRunner"
14618
13712
  ], "bootstrap config");
14619
13713
  if (root.kind === "platform") {
@@ -14680,9 +13774,11 @@ function strictBootstrapDocument(value) {
14680
13774
  exactKeys2(environment.cloudflare.warp, ["organization", "virtualNetworkId", "deviceProfileId"], "Cloudflare WARP runtime config");
14681
13775
  }
14682
13776
  } else if (root.kind === "tenant") {
13777
+ if (root.cloudflareHandoff !== undefined)
13778
+ exactKeys2(root.cloudflareHandoff, ["handoffFile", "nodeName"], "Cloudflare handoff");
14683
13779
  if (root.bootstrapRunner !== undefined)
14684
13780
  exactKeys2(root.bootstrapRunner, ["sshPrivateKeyFile", "targetTelemetryEndpoint"], "bootstrap runner config");
14685
- for (const key of ["environment", "profile", "computeReference", "database", "enrolment", "runtime", "cloudflareHandoff"]) {
13781
+ for (const key of ["environment", "profile", "computeReference", "database", "enrolment", "runtime"]) {
14686
13782
  if (root[key] !== undefined && key !== "profile")
14687
13783
  throw new Error(`tenant bootstrap cannot contain ${key}`);
14688
13784
  }
@@ -14771,6 +13867,7 @@ function localBootstrapHost() {
14771
13867
  branch: config.branch,
14772
13868
  profile: config.kind === "platform" ? config.profile : config.profile,
14773
13869
  deployRoot,
13870
+ deploymentCredentials: config.deploymentCredentials,
14774
13871
  publicApiUrl: config.apiUrl,
14775
13872
  gitCredentialPath: "/etc/forgezero/creds/git-deploy-key.cred",
14776
13873
  gitPublicKeyPath: "/etc/forgezero/git/deploy.pub",
@@ -14867,20 +13964,8 @@ function readCloudflareBootstrapCommandConfig(path, mode) {
14867
13964
  "hostname",
14868
13965
  "service",
14869
13966
  "tunnelName",
14870
- "kvNamespaceTitle",
14871
- "workerScriptName",
14872
- "serviceTokenName",
14873
- "policyName",
14874
- "applicationName",
14875
- "workerDirectory",
14876
- "workerMain",
14877
- "workerCompatibilityDate",
14878
- "publicDomains",
14879
- "createRuntimeTokens",
14880
- "createPrivateNetworkRuntimeToken",
14881
- "runtimeTokenNamePrefix",
14882
- "nodes",
14883
- "privateNetwork"
13967
+ "kvNamespaceId",
13968
+ "nodes"
14884
13969
  ], "Cloudflare bootstrap coordinates");
14885
13970
  if (coordinateSource.nodes !== undefined) {
14886
13971
  if (!Array.isArray(coordinateSource.nodes))
@@ -14890,38 +13975,21 @@ function readCloudflareBootstrapCommandConfig(path, mode) {
14890
13975
  "nodeName",
14891
13976
  "hostname",
14892
13977
  "service",
14893
- "tunnelName",
14894
- "applicationName",
14895
- "privateAddress"
13978
+ "tunnelName"
14896
13979
  ], "Cloudflare bootstrap node");
14897
13980
  }
14898
13981
  }
14899
- if (coordinateSource.privateNetwork !== undefined) {
14900
- exactKeys3(record2(coordinateSource.privateNetwork, "Cloudflare bootstrap privateNetwork"), [
14901
- "warpOrganization",
14902
- "virtualNetworkName",
14903
- "deviceProfileName",
14904
- "enrollmentApplicationName",
14905
- "deviceProfilePrecedence"
14906
- ], "Cloudflare bootstrap privateNetwork");
14907
- }
14908
- if (typeof coordinateSource.workerDirectory !== "string" || !coordinateSource.workerDirectory.trim()) {
14909
- throw new Error("Cloudflare bootstrap coordinates.workerDirectory is required");
14910
- }
14911
- const coordinates = validateCloudflareBootstrapCoordinates({
14912
- ...coordinateSource,
14913
- workerDirectory: resolve3(baseDirectory, coordinateSource.workerDirectory)
14914
- });
13982
+ const coordinates = validateCloudflareBootstrapCoordinates(coordinateSource);
14915
13983
  let tokenFiles;
14916
13984
  if (input.tokenFiles !== undefined) {
14917
13985
  const source = record2(input.tokenFiles, "Cloudflare bootstrap tokenFiles");
14918
13986
  const keys = [
14919
13987
  "apiTokenFile",
13988
+ "managementApiTokenFile",
13989
+ "runtimeApiTokenFile",
14920
13990
  "tunnelApiTokenFile",
14921
13991
  "dnsApiTokenFile",
14922
- "kvApiTokenFile",
14923
- "accessApiTokenFile",
14924
- "workerApiTokenFile"
13992
+ "kvApiTokenFile"
14925
13993
  ];
14926
13994
  exactKeys3(source, keys, "Cloudflare bootstrap tokenFiles");
14927
13995
  for (const key of keys) {
@@ -14942,8 +14010,8 @@ function readCloudflareBootstrapCommandConfig(path, mode) {
14942
14010
  };
14943
14011
  }
14944
14012
  async function runCloudflareBootstrapCommand(configPath, apply, dependencies = {}) {
14945
- const request2 = readCloudflareBootstrapCommandConfig(configPath, apply ? "apply" : "plan");
14946
- const evidence = await (dependencies.run ?? runAttendedCloudflareBootstrap)(request2);
14013
+ const request = readCloudflareBootstrapCommandConfig(configPath, apply ? "apply" : "plan");
14014
+ const evidence = await (dependencies.run ?? runAttendedCloudflareBootstrap)(request);
14947
14015
  (dependencies.write ?? ((text3) => process.stdout.write(text3)))(`${JSON.stringify(evidence, null, 2)}
14948
14016
  `);
14949
14017
  return evidence;
@@ -14956,7 +14024,7 @@ async function runCloudflareBootstrapVerificationCommand(checkpointPath, depende
14956
14024
  }
14957
14025
 
14958
14026
  // src/metal-bootstrap.ts
14959
- import { createHash as createHash4, randomBytes as randomBytes8 } from "crypto";
14027
+ import { createHash as createHash3, randomBytes as randomBytes8 } from "crypto";
14960
14028
  import {
14961
14029
  chmodSync as chmodSync3,
14962
14030
  chownSync,
@@ -14973,7 +14041,7 @@ import {
14973
14041
  writeFileSync as writeFileSync6
14974
14042
  } from "fs";
14975
14043
  import { dirname as dirname7, isAbsolute as isAbsolute2, join as join8, resolve as resolve4 } from "path";
14976
- import { isIP as isIP5 } from "net";
14044
+ import { isIP as isIP3 } from "net";
14977
14045
 
14978
14046
  // src/metal-isolation.ts
14979
14047
  import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
@@ -14981,7 +14049,7 @@ import { join as join7 } from "path";
14981
14049
 
14982
14050
  // src/metal-provision.ts
14983
14051
  import { dirname as dirname6, isAbsolute, join as join6 } from "path";
14984
- import { isIP as isIP4 } from "net";
14052
+ import { isIP as isIP2 } from "net";
14985
14053
 
14986
14054
  // src/ubuntu.ts
14987
14055
  var SUPPORTED_GUEST_IMAGE = Object.freeze({
@@ -15039,7 +14107,7 @@ function validateMetalProfile(profile) {
15039
14107
  } catch {
15040
14108
  throw new MetalProvisionError("Agent telemetry endpoint must be an absolute public HTTPS URL");
15041
14109
  }
15042
- if (telemetryEndpoint.protocol !== "https:" || telemetryEndpoint.username || telemetryEndpoint.password || telemetryEndpoint.search || telemetryEndpoint.hash || isIP4(telemetryEndpoint.hostname) !== 0 || !telemetryEndpoint.hostname.includes(".") || telemetryEndpoint.hostname === "localhost" || telemetryEndpoint.hostname.endsWith(".local"))
14110
+ if (telemetryEndpoint.protocol !== "https:" || telemetryEndpoint.username || telemetryEndpoint.password || telemetryEndpoint.search || telemetryEndpoint.hash || isIP2(telemetryEndpoint.hostname) !== 0 || !telemetryEndpoint.hostname.includes(".") || telemetryEndpoint.hostname === "localhost" || telemetryEndpoint.hostname.endsWith(".local"))
15043
14111
  throw new MetalProvisionError("Agent telemetry endpoint must be a public HTTPS DNS coordinate without credentials, query or fragment");
15044
14112
  const imageKeys = Object.keys(profile.images);
15045
14113
  if (imageKeys.length !== 1 || imageKeys[0] !== SUPPORTED_GUEST_IMAGE.key || profile.images[SUPPORTED_GUEST_IMAGE.key]?.sha256 !== SUPPORTED_GUEST_IMAGE.sha256) {
@@ -15291,13 +14359,13 @@ function validateMetalBootstrapConfig(config) {
15291
14359
  } catch {
15292
14360
  throw new MetalBootstrapError("metal API must be a public HTTPS origin");
15293
14361
  }
15294
- if (api.protocol !== "https:" || api.username || api.password || api.pathname !== "/" || api.search || api.hash || isIP5(api.hostname) !== 0 || !api.hostname.includes(".") || api.hostname.endsWith(".local")) {
14362
+ if (api.protocol !== "https:" || api.username || api.password || api.pathname !== "/" || api.search || api.hash || isIP3(api.hostname) !== 0 || !api.hostname.includes(".") || api.hostname.endsWith(".local")) {
15295
14363
  throw new MetalBootstrapError("metal API must be a credential-free public HTTPS origin");
15296
14364
  }
15297
- if (config.profile.nameservers?.some((address) => isIP5(address) === 0)) {
14365
+ if (config.profile.nameservers?.some((address) => isIP3(address) === 0)) {
15298
14366
  throw new MetalBootstrapError("metal nameservers must be literal IP addresses");
15299
14367
  }
15300
- if (isIP5(`${config.profile.subnetPrefix}.1`) !== 4 || isIP5(config.profile.gateway) !== 4 || !config.profile.gateway.startsWith(`${config.profile.subnetPrefix}.`)) {
14368
+ if (isIP3(`${config.profile.subnetPrefix}.1`) !== 4 || isIP3(config.profile.gateway) !== 4 || !config.profile.gateway.startsWith(`${config.profile.subnetPrefix}.`)) {
15301
14369
  throw new MetalBootstrapError("metal gateway must be an IPv4 address in the reviewed subnet");
15302
14370
  }
15303
14371
  if (config.profile.confidential && (!Number.isSafeInteger(config.profile.confidential.cbitpos) || config.profile.confidential.cbitpos < 1 || config.profile.confidential.cbitpos > 63 || !Number.isSafeInteger(config.profile.confidential.reducedPhysBits) || config.profile.confidential.reducedPhysBits < 0 || config.profile.confidential.reducedPhysBits > 63 || !/^0x[0-9a-fA-F]{1,16}$/.test(config.profile.confidential.policy)))
@@ -15714,7 +14782,7 @@ async function applyMetalBootstrap(config, options) {
15714
14782
  initializedAt: new Date().toISOString(),
15715
14783
  role: "metal",
15716
14784
  metalHostname: config.metalHostname,
15717
- profileSha256: createHash4("sha256").update(JSON.stringify(config.profile)).digest("hex")
14785
+ profileSha256: createHash3("sha256").update(JSON.stringify(config.profile)).digest("hex")
15718
14786
  };
15719
14787
  atomicWrite2(STATE_PATH2, `${JSON.stringify(state, null, 2)}
15720
14788
  `, 384);
@@ -15741,7 +14809,7 @@ async function metalBootstrapStatus(exec = defaultExec2) {
15741
14809
  const { metalHostname: profileHostname, hostTelemetryEndpoint, hostTelemetryUnit, ...profile } = persisted;
15742
14810
  validateMetalProfile(profile);
15743
14811
  profileValid = true;
15744
- profileSha256 = createHash4("sha256").update(JSON.stringify(profile)).digest("hex");
14812
+ profileSha256 = createHash3("sha256").update(JSON.stringify(profile)).digest("hex");
15745
14813
  if (!/^[A-Za-z0-9][A-Za-z0-9.-]{1,252}$/.test(profileHostname) || hostTelemetryEndpoint !== "http://127.0.0.1:4318") {
15746
14814
  problems.push("persisted metal host coordinates are invalid");
15747
14815
  }
@@ -16020,27 +15088,6 @@ async function api(options, path, init) {
16020
15088
  body = null;
16021
15089
  }
16022
15090
  const challenge = body?.security;
16023
- if (response.status === 428 && challenge?.scope === "action" && challenge.requestKey && custodyIdentity && !path.startsWith("/security/step-up/")) {
16024
- const proved = await proveWithAgent(options, challenge.requestKey);
16025
- if (proved) {
16026
- const replay = await fetch(`${options.api}${base}${path}`, {
16027
- method: init?.method ?? "GET",
16028
- headers: {
16029
- ...requestHeaders(options.api, sessionCookie),
16030
- "x-security-request-key": challenge.requestKey
16031
- },
16032
- body: init?.body === undefined ? undefined : JSON.stringify(init.body)
16033
- });
16034
- captureSession(replay);
16035
- let replayed = null;
16036
- try {
16037
- replayed = await replay.json();
16038
- } catch {
16039
- replayed = null;
16040
- }
16041
- return { status: replay.status, body: replayed };
16042
- }
16043
- }
16044
15091
  if (response.status === 428 && challenge?.scope === "action" && challenge.requestKey && storedSession && !path.startsWith("/security/step-up/")) {
16045
15092
  const proved = await proveWithBrowser(options, challenge.requestKey, base);
16046
15093
  if (proved) {
@@ -16124,27 +15171,6 @@ async function proveWithBrowser(options, requestKey, base) {
16124
15171
  }
16125
15172
  return false;
16126
15173
  }
16127
- var custodyIdentity = null;
16128
- function useCustodyIdentity(identity, socketPath) {
16129
- custodyIdentity = { identity, socketPath };
16130
- }
16131
- async function proveWithAgent(options, requestKey) {
16132
- if (!custodyIdentity)
16133
- return false;
16134
- const begun = await api(options, "/security/step-up/ssh/begin", {
16135
- method: "POST",
16136
- body: { requestKey }
16137
- });
16138
- const nonce = begun.body?.nonce;
16139
- if (begun.status !== 200 || !nonce)
16140
- return false;
16141
- const signature = await signWithIdentity(custodyIdentity.identity, new Uint8Array(Buffer.from(nonce, "base64")), custodyIdentity.socketPath);
16142
- const proved = await api(options, "/security/step-up/ssh/prove", {
16143
- method: "POST",
16144
- body: { requestKey, signature: Buffer.from(signature).toString("base64") }
16145
- });
16146
- return proved.status === 200;
16147
- }
16148
15174
  function hydrateSession(options) {
16149
15175
  const active = activeSession();
16150
15176
  if (!options.apiExplicit && active)
@@ -16373,37 +15399,6 @@ async function cmdApi(options, args) {
16373
15399
  return 1;
16374
15400
  }
16375
15401
  }
16376
- async function cmdKeys(options) {
16377
- const identities = await usableIdentities(options.socket);
16378
- if (options.json) {
16379
- out.line(JSON.stringify(identities.map(({ fingerprint, comment, responseMs }) => ({
16380
- fingerprint,
16381
- comment,
16382
- responseMs
16383
- })), null, 2));
16384
- return 0;
16385
- }
16386
- if (identities.length === 0) {
16387
- out.fail("No usable Ed25519 keys in the agent.");
16388
- out.line();
16389
- out.step("Custody needs a key that signs DETERMINISTICALLY, so Ed25519 only.");
16390
- out.step("Add one with: ssh-add ~/.ssh/id_ed25519");
16391
- out.line();
16392
- out.step("Note that keys which hang \u2014 forwarded agents whose upstream is");
16393
- out.step("gone, or confirm-on-use keys with nobody at the terminal \u2014 are");
16394
- out.step("skipped here rather than listed, so this can be shorter than");
16395
- out.step("`ssh-add -l`.");
16396
- return 1;
16397
- }
16398
- out.line("Usable custody keys:");
16399
- out.line();
16400
- identities.forEach((identity, index) => {
16401
- out.line(` ${index + 1}. ${identity.fingerprint}`);
16402
- out.line(` ${identity.comment || "(no comment)"} ${identity.responseMs}ms`);
16403
- });
16404
- out.line();
16405
- return 0;
16406
- }
16407
15402
  async function cmdStatus(options) {
16408
15403
  try {
16409
15404
  const health = await api(options, "/health");
@@ -16704,7 +15699,7 @@ function interactiveBootstrap(kind) {
16704
15699
  async function cmdBootstrap(options, args) {
16705
15700
  try {
16706
15701
  const operation = args[0] ?? "status";
16707
- if (operation === "platform" && args[1] === "cloudflare") {
15702
+ if ((operation === "platform" || operation === "tenant") && args[1] === "cloudflare") {
16708
15703
  if (!options.bootstrapConfigPath) {
16709
15704
  throw new Error("Cloudflare bootstrap requires --bootstrap-config <owner-only-cloudflare-json>");
16710
15705
  }
@@ -16781,7 +15776,7 @@ async function cmdBootstrap(options, args) {
16781
15776
  return 0;
16782
15777
  }
16783
15778
  if (!["platform", "tenant", "repair"].includes(operation)) {
16784
- throw new Error("Usage: fz bootstrap platform [prepare|cloudflare [verify]]|tenant|metal|status|repair [--bootstrap-config <path>] [--apply]");
15779
+ throw new Error("Usage: fz bootstrap platform [prepare|cloudflare [verify]]|tenant [cloudflare [verify]]|metal|status|repair [--bootstrap-config <path>] [--apply]");
16785
15780
  }
16786
15781
  const config = options.bootstrapConfigPath ? readBootstrapConfig(options.bootstrapConfigPath) : operation === "repair" ? (() => {
16787
15782
  throw new Error("repair requires --bootstrap-config so immutable coordinates are revalidated");
@@ -17186,7 +16181,6 @@ function usage() {
17186
16181
  fz api <method> <path> Use any authorized realm API from a project or AI agent
17187
16182
  fz ui routes [search] Discover the current account's authorized UI actions
17188
16183
  fz ui <method> <path> Run an authorized UI action without visiting the site
17189
- fz keys List agent keys usable for custody
17190
16184
  fz status Platform, ceremony and vault state
17191
16185
  fz genesis Open first-founder onboarding, link this CLI, then
17192
16186
  continue the custodian ceremony in the browser
@@ -17203,12 +16197,14 @@ function usage() {
17203
16197
  Generate the machine deploy key before private Git deployment
17204
16198
  fz bootstrap platform Install/repair a typed elastic platform compute
17205
16199
  fz bootstrap platform cloudflare
17206
- Plan/apply token-file-only KV, Worker, Access, DNS
17207
- and one Tunnel per explicit platform API node
16200
+ Plan/apply attended Tunnel and DNS reconciliation
16201
+ using supplied management and KV runtime token files
17208
16202
  fz bootstrap platform cloudflare verify
17209
- From the operator laptop, prove every Access origin
17210
- and stable Worker /api/health using the 0600 checkpoint
16203
+ From the operator laptop, prove every ordinary public
16204
+ node /api/health using the 0600 checkpoint
17211
16205
  fz bootstrap tenant Install/enrol reusable tenant compute tooling
16206
+ fz bootstrap tenant cloudflare
16207
+ Use the same attended Tunnel/DNS resource phase for tenant origins
17212
16208
  fz bootstrap metal Plan/install an identity-only physical provisioner
17213
16209
  fz bootstrap status Verify persisted profile and supervised units
17214
16210
  fz bootstrap repair Reapply an explicitly supplied reviewed config
@@ -17287,8 +16283,8 @@ function usage() {
17287
16283
  fz unlock opens the signed-in custodian's own phrase envelope. Supply the
17288
16284
  24-word BIP-39 phrase by owner-only file or stdin; it is never accepted in
17289
16285
  argv. Each custodian runs it in their own authenticated session; no process
17290
- collects several custodians' phrases. SSH identities can prove fresh CLI
17291
- actions, but cannot open the distinct WebAuthn-PRF custody envelope.
16286
+ collects several custodians' phrases. Fresh privileged actions use the
16287
+ browser passkey/TOTP flow; SSH is transport and Git interoperability only.
17292
16288
  `);
17293
16289
  }
17294
16290
  if (import.meta.main) {
@@ -17320,9 +16316,6 @@ async function runCli() {
17320
16316
  case "ui":
17321
16317
  code = await cmdApi(options, args);
17322
16318
  break;
17323
- case "keys":
17324
- code = await cmdKeys(options);
17325
- break;
17326
16319
  case "status":
17327
16320
  code = await cmdStatus(options);
17328
16321
  break;
@@ -17367,7 +16360,6 @@ async function runCli() {
17367
16360
  process.exit(code);
17368
16361
  }
17369
16362
  export {
17370
- useCustodyIdentity,
17371
16363
  targetCoordinate,
17372
16364
  safeApiPath,
17373
16365
  requestHeaders