agentlock-shared 0.2.0 → 0.3.0

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.
Files changed (169) hide show
  1. package/dist/__tests__/billing.test.d.ts +2 -0
  2. package/dist/__tests__/billing.test.d.ts.map +1 -0
  3. package/dist/__tests__/billing.test.js +31 -0
  4. package/dist/__tests__/billing.test.js.map +1 -0
  5. package/dist/__tests__/dns-pinning.test.d.ts +2 -0
  6. package/dist/__tests__/dns-pinning.test.d.ts.map +1 -0
  7. package/dist/__tests__/dns-pinning.test.js +33 -0
  8. package/dist/__tests__/dns-pinning.test.js.map +1 -0
  9. package/dist/__tests__/llm-classifier-cache-store.test.d.ts +2 -0
  10. package/dist/__tests__/llm-classifier-cache-store.test.d.ts.map +1 -0
  11. package/dist/__tests__/llm-classifier-cache-store.test.js +65 -0
  12. package/dist/__tests__/llm-classifier-cache-store.test.js.map +1 -0
  13. package/dist/__tests__/llm-classifier-cache.test.d.ts +2 -0
  14. package/dist/__tests__/llm-classifier-cache.test.d.ts.map +1 -0
  15. package/dist/__tests__/llm-classifier-cache.test.js +44 -0
  16. package/dist/__tests__/llm-classifier-cache.test.js.map +1 -0
  17. package/dist/__tests__/llm-classifier.test.d.ts +2 -0
  18. package/dist/__tests__/llm-classifier.test.d.ts.map +1 -0
  19. package/dist/__tests__/llm-classifier.test.js +167 -0
  20. package/dist/__tests__/llm-classifier.test.js.map +1 -0
  21. package/dist/__tests__/plans-classifier-limits.test.d.ts +2 -0
  22. package/dist/__tests__/plans-classifier-limits.test.d.ts.map +1 -0
  23. package/dist/__tests__/plans-classifier-limits.test.js +22 -0
  24. package/dist/__tests__/plans-classifier-limits.test.js.map +1 -0
  25. package/dist/__tests__/policy-category-floor.test.d.ts +2 -0
  26. package/dist/__tests__/policy-category-floor.test.d.ts.map +1 -0
  27. package/dist/__tests__/policy-category-floor.test.js +46 -0
  28. package/dist/__tests__/policy-category-floor.test.js.map +1 -0
  29. package/dist/__tests__/policy-claude-bash.test.d.ts +2 -0
  30. package/dist/__tests__/policy-claude-bash.test.d.ts.map +1 -0
  31. package/dist/__tests__/policy-claude-bash.test.js +401 -0
  32. package/dist/__tests__/policy-claude-bash.test.js.map +1 -0
  33. package/dist/__tests__/policy-llm-floor.test.d.ts +2 -0
  34. package/dist/__tests__/policy-llm-floor.test.d.ts.map +1 -0
  35. package/dist/__tests__/policy-llm-floor.test.js +107 -0
  36. package/dist/__tests__/policy-llm-floor.test.js.map +1 -0
  37. package/dist/__tests__/policy-ssh-e2e.test.d.ts +2 -0
  38. package/dist/__tests__/policy-ssh-e2e.test.d.ts.map +1 -0
  39. package/dist/__tests__/policy-ssh-e2e.test.js +89 -0
  40. package/dist/__tests__/policy-ssh-e2e.test.js.map +1 -0
  41. package/dist/__tests__/policy-ssh-sessions.test.d.ts +2 -0
  42. package/dist/__tests__/policy-ssh-sessions.test.d.ts.map +1 -0
  43. package/dist/__tests__/policy-ssh-sessions.test.js +139 -0
  44. package/dist/__tests__/policy-ssh-sessions.test.js.map +1 -0
  45. package/dist/__tests__/policy-ssh.test.d.ts +2 -0
  46. package/dist/__tests__/policy-ssh.test.d.ts.map +1 -0
  47. package/dist/__tests__/policy-ssh.test.js +180 -0
  48. package/dist/__tests__/policy-ssh.test.js.map +1 -0
  49. package/dist/__tests__/policy.test.js +400 -2
  50. package/dist/__tests__/policy.test.js.map +1 -1
  51. package/dist/__tests__/redact.test.js +76 -0
  52. package/dist/__tests__/redact.test.js.map +1 -1
  53. package/dist/__tests__/signing.test.js +89 -0
  54. package/dist/__tests__/signing.test.js.map +1 -1
  55. package/dist/__tests__/ssh-fingerprint.test.d.ts +2 -0
  56. package/dist/__tests__/ssh-fingerprint.test.d.ts.map +1 -0
  57. package/dist/__tests__/ssh-fingerprint.test.js +19 -0
  58. package/dist/__tests__/ssh-fingerprint.test.js.map +1 -0
  59. package/dist/__tests__/vpn-route.test.d.ts +2 -0
  60. package/dist/__tests__/vpn-route.test.d.ts.map +1 -0
  61. package/dist/__tests__/vpn-route.test.js +72 -0
  62. package/dist/__tests__/vpn-route.test.js.map +1 -0
  63. package/dist/__tests__/wireguard.test.d.ts +2 -0
  64. package/dist/__tests__/wireguard.test.d.ts.map +1 -0
  65. package/dist/__tests__/wireguard.test.js +114 -0
  66. package/dist/__tests__/wireguard.test.js.map +1 -0
  67. package/dist/billing.d.ts +12 -0
  68. package/dist/billing.d.ts.map +1 -0
  69. package/dist/billing.js +41 -0
  70. package/dist/billing.js.map +1 -0
  71. package/dist/crypto.d.ts +5 -0
  72. package/dist/crypto.d.ts.map +1 -1
  73. package/dist/crypto.js +80 -23
  74. package/dist/crypto.js.map +1 -1
  75. package/dist/dns-pinning.d.ts +28 -0
  76. package/dist/dns-pinning.d.ts.map +1 -0
  77. package/dist/dns-pinning.js +113 -0
  78. package/dist/dns-pinning.js.map +1 -0
  79. package/dist/index.d.ts +6 -0
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +9 -0
  82. package/dist/index.js.map +1 -1
  83. package/dist/llm-classifier-cache-store.d.ts +49 -0
  84. package/dist/llm-classifier-cache-store.d.ts.map +1 -0
  85. package/dist/llm-classifier-cache-store.js +63 -0
  86. package/dist/llm-classifier-cache-store.js.map +1 -0
  87. package/dist/llm-classifier-cache.d.ts +6 -0
  88. package/dist/llm-classifier-cache.d.ts.map +1 -0
  89. package/dist/llm-classifier-cache.js +52 -0
  90. package/dist/llm-classifier-cache.js.map +1 -0
  91. package/dist/llm-classifier.d.ts +29 -0
  92. package/dist/llm-classifier.d.ts.map +1 -0
  93. package/dist/llm-classifier.js +191 -0
  94. package/dist/llm-classifier.js.map +1 -0
  95. package/dist/observability.d.ts +36 -0
  96. package/dist/observability.d.ts.map +1 -0
  97. package/dist/observability.js +75 -0
  98. package/dist/observability.js.map +1 -0
  99. package/dist/plans.d.ts +17 -0
  100. package/dist/plans.d.ts.map +1 -1
  101. package/dist/plans.js +36 -14
  102. package/dist/plans.js.map +1 -1
  103. package/dist/policy.d.ts +173 -3
  104. package/dist/policy.d.ts.map +1 -1
  105. package/dist/policy.js +910 -42
  106. package/dist/policy.js.map +1 -1
  107. package/dist/redact.d.ts.map +1 -1
  108. package/dist/redact.js +83 -3
  109. package/dist/redact.js.map +1 -1
  110. package/dist/regex-safety.d.ts +21 -0
  111. package/dist/regex-safety.d.ts.map +1 -0
  112. package/dist/regex-safety.js +49 -0
  113. package/dist/regex-safety.js.map +1 -0
  114. package/dist/sanitize.d.ts +31 -0
  115. package/dist/sanitize.d.ts.map +1 -0
  116. package/dist/sanitize.js +54 -0
  117. package/dist/sanitize.js.map +1 -0
  118. package/dist/schemas.d.ts +202 -10
  119. package/dist/schemas.d.ts.map +1 -1
  120. package/dist/schemas.js +91 -1
  121. package/dist/schemas.js.map +1 -1
  122. package/dist/signing.d.ts +15 -0
  123. package/dist/signing.d.ts.map +1 -1
  124. package/dist/signing.js +53 -4
  125. package/dist/signing.js.map +1 -1
  126. package/dist/ssh-fingerprint.d.ts +10 -0
  127. package/dist/ssh-fingerprint.d.ts.map +1 -0
  128. package/dist/ssh-fingerprint.js +52 -0
  129. package/dist/ssh-fingerprint.js.map +1 -0
  130. package/dist/ssrf.d.ts +36 -0
  131. package/dist/ssrf.d.ts.map +1 -0
  132. package/dist/ssrf.js +140 -0
  133. package/dist/ssrf.js.map +1 -0
  134. package/dist/types.d.ts +130 -0
  135. package/dist/types.d.ts.map +1 -1
  136. package/dist/wireguard.d.ts +63 -0
  137. package/dist/wireguard.d.ts.map +1 -0
  138. package/dist/wireguard.js +226 -0
  139. package/dist/wireguard.js.map +1 -0
  140. package/package.json +42 -29
  141. package/.turbo/turbo-build.log +0 -4
  142. package/.turbo/turbo-test.log +0 -76
  143. package/dist/__tests__/content-crypto.test.d.ts +0 -2
  144. package/dist/__tests__/content-crypto.test.d.ts.map +0 -1
  145. package/dist/__tests__/content-crypto.test.js +0 -117
  146. package/dist/__tests__/content-crypto.test.js.map +0 -1
  147. package/dist/__tests__/signing.test (# Edit conflict 2026-04-01 z3etfmC #).js +0 -51
  148. package/dist/__tests__/signing.test.js (# Edit conflict 2026-04-01 4rndy9C #).map +0 -1
  149. package/dist/content-crypto.d.ts +0 -24
  150. package/dist/content-crypto.d.ts.map +0 -1
  151. package/dist/content-crypto.js +0 -58
  152. package/dist/content-crypto.js.map +0 -1
  153. package/src/__tests__/crypto.test.ts +0 -169
  154. package/src/__tests__/messaging.test.ts +0 -83
  155. package/src/__tests__/policy.test.ts +0 -222
  156. package/src/__tests__/redact.test.ts +0 -41
  157. package/src/__tests__/signing.test.ts +0 -55
  158. package/src/crypto.ts +0 -235
  159. package/src/index.ts +0 -8
  160. package/src/mcp-catalog.ts +0 -181
  161. package/src/plans.ts +0 -116
  162. package/src/policy.ts +0 -216
  163. package/src/redact.ts +0 -131
  164. package/src/schemas.ts +0 -121
  165. package/src/signing.ts +0 -120
  166. package/src/types.ts +0 -213
  167. package/test-gateway.mjs +0 -47
  168. package/tsconfig.json +0 -10
  169. package/vitest.config.ts +0 -8
@@ -1,7 +1,25 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  const vitest_1 = require("vitest");
7
+ const tweetnacl_1 = __importDefault(require("tweetnacl"));
8
+ const tweetnacl_util_1 = require("tweetnacl-util");
4
9
  const signing_js_1 = require("../signing.js");
10
+ /**
11
+ * Sign the pre-version-binding (legacy) pre-image: `canonical:timestamp:nonce`
12
+ * with NO `:version` suffix and no version header. Models an agent built
13
+ * before the version was bound into the signature.
14
+ */
15
+ function signLegacyUnbound(body, privateKeyB64) {
16
+ const timestamp = String(Date.now());
17
+ const nonce = (0, tweetnacl_util_1.encodeBase64)(tweetnacl_1.default.randomBytes(16));
18
+ const canonical = (0, signing_js_1.canonicalStringify)(body);
19
+ const message = (0, tweetnacl_util_1.decodeUTF8)(`${canonical}:${timestamp}:${nonce}`);
20
+ const signature = tweetnacl_1.default.sign.detached(message, (0, tweetnacl_util_1.decodeBase64)(privateKeyB64));
21
+ return { timestamp, nonce, signature: (0, tweetnacl_util_1.encodeBase64)(signature) };
22
+ }
5
23
  (0, vitest_1.describe)('Ed25519 Signing', () => {
6
24
  (0, vitest_1.it)('should generate valid keypair', () => {
7
25
  const kp = (0, signing_js_1.generateKeypair)();
@@ -47,5 +65,76 @@ const signing_js_1 = require("../signing.js");
47
65
  headers['x-timestamp'] = String(Date.now() - 10 * 60 * 1000);
48
66
  (0, vitest_1.expect)(() => (0, signing_js_1.verifyRequest)(body, headers, kp.publicKey)).toThrow('Timestamp skew');
49
67
  });
68
+ (0, vitest_1.it)('should reject timestamp from the future', () => {
69
+ // A permissive check would only look at now - ts. Replay would then work
70
+ // forever if you set the timestamp 2h in the future. Math.abs() catches
71
+ // both directions; guard against a future refactor dropping the abs.
72
+ const kp = (0, signing_js_1.generateKeypair)();
73
+ const body = { action_type: 'write', tool: 'demo', payload: {} };
74
+ const headers = (0, signing_js_1.signRequest)(body, 'agent-123', kp.privateKey);
75
+ headers['x-timestamp'] = String(Date.now() + 10 * 60 * 1000);
76
+ (0, vitest_1.expect)(() => (0, signing_js_1.verifyRequest)(body, headers, kp.publicKey)).toThrow('Timestamp skew');
77
+ });
78
+ (0, vitest_1.it)('should produce unique nonces across many signs', () => {
79
+ // If nonces collide, the gateway's replay-protection unique-index
80
+ // rejects the second insert and the agent sees a 409. 200 iterations
81
+ // is enough to shake out a non-random generator without making CI slow.
82
+ const kp = (0, signing_js_1.generateKeypair)();
83
+ const body = { action_type: 'read', tool: 'demo', payload: {} };
84
+ const nonces = new Set();
85
+ for (let i = 0; i < 200; i++) {
86
+ const n = (0, signing_js_1.signRequest)(body, 'agent-123', kp.privateKey)['x-nonce'];
87
+ if (n)
88
+ nonces.add(n);
89
+ }
90
+ (0, vitest_1.expect)(nonces.size).toBe(200);
91
+ });
92
+ (0, vitest_1.it)('should reject a replayed request that only changes the nonce header', () => {
93
+ // The signature is bound to (body, timestamp, nonce). Just rotating
94
+ // the nonce without re-signing must not produce a verifying signature.
95
+ const kp = (0, signing_js_1.generateKeypair)();
96
+ const body = { action_type: 'read', tool: 'demo', payload: {} };
97
+ const headers = (0, signing_js_1.signRequest)(body, 'agent-123', kp.privateKey);
98
+ headers['x-nonce'] = 'AAAAAAAAAAAAAAAAAAAAAAAA'; // 24-char base64
99
+ (0, vitest_1.expect)(() => (0, signing_js_1.verifyRequest)(body, headers, kp.publicKey)).toThrow('Invalid signature');
100
+ });
101
+ (0, vitest_1.it)('should verify identical payloads signed concurrently with different nonces (race-safety)', () => {
102
+ // Simulate two concurrent signers of the same body. Both must verify
103
+ // independently; only the DB unique-constraint on nonce stops the
104
+ // second from actually executing in production.
105
+ const kp = (0, signing_js_1.generateKeypair)();
106
+ const body = { action_type: 'read', tool: 'demo', payload: {} };
107
+ const headersA = (0, signing_js_1.signRequest)(body, 'agent-123', kp.privateKey);
108
+ const headersB = (0, signing_js_1.signRequest)(body, 'agent-123', kp.privateKey);
109
+ (0, vitest_1.expect)(headersA['x-nonce']).toBeTruthy();
110
+ (0, vitest_1.expect)(headersA['x-nonce']).not.toBe(headersB['x-nonce']);
111
+ (0, vitest_1.expect)(() => (0, signing_js_1.verifyRequest)(body, headersA, kp.publicKey)).not.toThrow();
112
+ (0, vitest_1.expect)(() => (0, signing_js_1.verifyRequest)(body, headersB, kp.publicKey)).not.toThrow();
113
+ });
114
+ (0, vitest_1.it)('accepts a legacy unbound-preimage signature when NO version header is sent', () => {
115
+ // Transitional fallback: agents built before version-binding sign the
116
+ // unbound pre-image and send no header. They must keep verifying so the
117
+ // rollout doesn't force a simultaneous redeploy of every agent.
118
+ const kp = (0, signing_js_1.generateKeypair)();
119
+ const body = { action_type: 'read', tool: 'demo', payload: {} };
120
+ const { timestamp, nonce, signature } = signLegacyUnbound(body, kp.privateKey);
121
+ (0, vitest_1.expect)(() => (0, signing_js_1.verifyRequest)(body, { 'x-agent-id': 'a', 'x-timestamp': timestamp, 'x-signature': signature, 'x-nonce': nonce }, kp.publicKey)).not.toThrow();
122
+ });
123
+ (0, vitest_1.it)('rejects a legacy unbound-preimage signature once x-signature-version is present', () => {
124
+ // This is the property the gateway routes must preserve by FORWARDING the
125
+ // header: when a client declares its version, the unbound legacy pre-image
126
+ // is no longer an accepted downgrade. If a route drops the header, this
127
+ // downgrade path stays open for every request.
128
+ const kp = (0, signing_js_1.generateKeypair)();
129
+ const body = { action_type: 'read', tool: 'demo', payload: {} };
130
+ const { timestamp, nonce, signature } = signLegacyUnbound(body, kp.privateKey);
131
+ (0, vitest_1.expect)(() => (0, signing_js_1.verifyRequest)(body, {
132
+ 'x-agent-id': 'a',
133
+ 'x-timestamp': timestamp,
134
+ 'x-signature': signature,
135
+ 'x-nonce': nonce,
136
+ 'x-signature-version': '1',
137
+ }, kp.publicKey)).toThrow('Invalid signature');
138
+ });
50
139
  });
51
140
  //# sourceMappingURL=signing.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"signing.test.js","sourceRoot":"","sources":["../../src/__tests__/signing.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"signing.test.js","sourceRoot":"","sources":["../../src/__tests__/signing.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ssh-fingerprint.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssh-fingerprint.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/ssh-fingerprint.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const ssh_fingerprint_js_1 = require("../ssh-fingerprint.js");
5
+ (0, vitest_1.describe)('normalizeSshHostKeyFingerprint', () => {
6
+ (0, vitest_1.it)('accepts canonical hex and lowercases it', () => {
7
+ (0, vitest_1.expect)((0, ssh_fingerprint_js_1.normalizeSshHostKeyFingerprint)('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA')).toBe('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
8
+ });
9
+ (0, vitest_1.it)('accepts OpenSSH-style SHA256 base64 and converts it to hex', () => {
10
+ (0, vitest_1.expect)((0, ssh_fingerprint_js_1.normalizeSshHostKeyFingerprint)('SHA256:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA')).toBe('0000000000000000000000000000000000000000000000000000000000000000');
11
+ });
12
+ (0, vitest_1.it)('accepts url-safe base64 without the SHA256 prefix', () => {
13
+ (0, vitest_1.expect)((0, ssh_fingerprint_js_1.normalizeSshHostKeyFingerprint)('___________________________________________')).toBe('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff');
14
+ });
15
+ (0, vitest_1.it)('rejects malformed fingerprints', () => {
16
+ (0, vitest_1.expect)(() => (0, ssh_fingerprint_js_1.normalizeSshHostKeyFingerprint)('not-a-fingerprint')).toThrow(/Invalid SHA-256 host key fingerprint/);
17
+ });
18
+ });
19
+ //# sourceMappingURL=ssh-fingerprint.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssh-fingerprint.test.js","sourceRoot":"","sources":["../../src/__tests__/ssh-fingerprint.test.ts"],"names":[],"mappings":";;AAAA,mCAA8C;AAC9C,8DAAuE;AAEvE,IAAA,iBAAQ,EAAC,gCAAgC,EAAE,GAAG,EAAE;IAC9C,IAAA,WAAE,EAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,IAAA,eAAM,EACJ,IAAA,mDAA8B,EAC5B,kEAAkE,CACnE,CACF,CAAC,IAAI,CAAC,kEAAkE,CAAC,CAAC;IAC7E,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,4DAA4D,EAAE,GAAG,EAAE;QACpE,IAAA,eAAM,EACJ,IAAA,mDAA8B,EAAC,oDAAoD,CAAC,CACrF,CAAC,IAAI,CAAC,kEAAkE,CAAC,CAAC;IAC7E,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,IAAA,eAAM,EACJ,IAAA,mDAA8B,EAAC,6CAA6C,CAAC,CAC9E,CAAC,IAAI,CAAC,kEAAkE,CAAC,CAAC;IAC7E,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,gCAAgC,EAAE,GAAG,EAAE;QACxC,IAAA,eAAM,EAAC,GAAG,EAAE,CAAC,IAAA,mDAA8B,EAAC,mBAAmB,CAAC,CAAC,CAAC,OAAO,CACvE,sCAAsC,CACvC,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=vpn-route.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vpn-route.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/vpn-route.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const policy_js_1 = require("../policy.js");
5
+ const UUID_A = '11111111-1111-4111-8111-111111111111';
6
+ const UUID_B = '22222222-2222-4222-8222-222222222222';
7
+ const UUID_C = '33333333-3333-4333-8333-333333333333';
8
+ (0, vitest_1.describe)('resolveVpnRoute', () => {
9
+ (0, vitest_1.it)('returns undefined when host or routes are missing/empty', () => {
10
+ (0, vitest_1.expect)((0, policy_js_1.resolveVpnRoute)(undefined, undefined)).toBeUndefined();
11
+ (0, vitest_1.expect)((0, policy_js_1.resolveVpnRoute)('example.com', undefined)).toBeUndefined();
12
+ (0, vitest_1.expect)((0, policy_js_1.resolveVpnRoute)('example.com', [])).toBeUndefined();
13
+ (0, vitest_1.expect)((0, policy_js_1.resolveVpnRoute)('', [{ domainPattern: 'example.com', vpnCredentialId: UUID_A }])).toBeUndefined();
14
+ });
15
+ (0, vitest_1.it)('matches exact hostname case-insensitively', () => {
16
+ const routes = [
17
+ { domainPattern: 'Internal.Corp.Example', vpnCredentialId: UUID_A },
18
+ ];
19
+ (0, vitest_1.expect)((0, policy_js_1.resolveVpnRoute)('internal.corp.example', routes)).toBe(UUID_A);
20
+ (0, vitest_1.expect)((0, policy_js_1.resolveVpnRoute)('INTERNAL.CORP.EXAMPLE', routes)).toBe(UUID_A);
21
+ // Trailing dot in FQDN form is canonicalized away.
22
+ (0, vitest_1.expect)((0, policy_js_1.resolveVpnRoute)('internal.corp.example.', routes)).toBe(UUID_A);
23
+ // Unrelated host does not match.
24
+ (0, vitest_1.expect)((0, policy_js_1.resolveVpnRoute)('other.example', routes)).toBeUndefined();
25
+ });
26
+ (0, vitest_1.it)('wildcard "*.corp.example" matches subdomains but not the bare suffix', () => {
27
+ const routes = [
28
+ { domainPattern: '*.corp.example', vpnCredentialId: UUID_A },
29
+ ];
30
+ (0, vitest_1.expect)((0, policy_js_1.resolveVpnRoute)('www.corp.example', routes)).toBe(UUID_A);
31
+ (0, vitest_1.expect)((0, policy_js_1.resolveVpnRoute)('a.b.corp.example', routes)).toBe(UUID_A);
32
+ // Bare suffix must NOT match — users who want both add a second exact entry.
33
+ (0, vitest_1.expect)((0, policy_js_1.resolveVpnRoute)('corp.example', routes)).toBeUndefined();
34
+ // Different tail.
35
+ (0, vitest_1.expect)((0, policy_js_1.resolveVpnRoute)('www.corp.other', routes)).toBeUndefined();
36
+ });
37
+ (0, vitest_1.it)('first match wins when multiple routes could apply', () => {
38
+ const routes = [
39
+ { domainPattern: 'db.corp.example', vpnCredentialId: UUID_A },
40
+ { domainPattern: '*.corp.example', vpnCredentialId: UUID_B },
41
+ { domainPattern: 'corp.example', vpnCredentialId: UUID_C },
42
+ ];
43
+ (0, vitest_1.expect)((0, policy_js_1.resolveVpnRoute)('db.corp.example', routes)).toBe(UUID_A); // exact match wins over wildcard
44
+ (0, vitest_1.expect)((0, policy_js_1.resolveVpnRoute)('web.corp.example', routes)).toBe(UUID_B); // wildcard hits here
45
+ (0, vitest_1.expect)((0, policy_js_1.resolveVpnRoute)('corp.example', routes)).toBe(UUID_C); // bare suffix exact
46
+ });
47
+ (0, vitest_1.it)('silently skips malformed pattern entries instead of throwing', () => {
48
+ // Zod rejects these at save time; this is a belt-and-suspenders guard.
49
+ const routes = [
50
+ { domainPattern: '', vpnCredentialId: UUID_A },
51
+ { domainPattern: ' ', vpnCredentialId: UUID_A },
52
+ { domainPattern: '*.', vpnCredentialId: UUID_A },
53
+ { domainPattern: 'good.example', vpnCredentialId: UUID_B },
54
+ ];
55
+ (0, vitest_1.expect)((0, policy_js_1.resolveVpnRoute)('good.example', routes)).toBe(UUID_B);
56
+ // And the malformed entries don't accidentally match:
57
+ (0, vitest_1.expect)((0, policy_js_1.resolveVpnRoute)('anything.com', routes)).toBeUndefined();
58
+ });
59
+ });
60
+ (0, vitest_1.describe)('hostnameFromUrl', () => {
61
+ (0, vitest_1.it)('extracts the lowercased hostname from http(s) URLs', () => {
62
+ (0, vitest_1.expect)((0, policy_js_1.hostnameFromUrl)('https://Api.Example.COM/path?q=1')).toBe('api.example.com');
63
+ (0, vitest_1.expect)((0, policy_js_1.hostnameFromUrl)('http://127.0.0.1:8080/x')).toBe('127.0.0.1');
64
+ });
65
+ (0, vitest_1.it)('returns undefined for non-URL inputs', () => {
66
+ (0, vitest_1.expect)((0, policy_js_1.hostnameFromUrl)(undefined)).toBeUndefined();
67
+ (0, vitest_1.expect)((0, policy_js_1.hostnameFromUrl)(null)).toBeUndefined();
68
+ (0, vitest_1.expect)((0, policy_js_1.hostnameFromUrl)('')).toBeUndefined();
69
+ (0, vitest_1.expect)((0, policy_js_1.hostnameFromUrl)('not a url')).toBeUndefined();
70
+ });
71
+ });
72
+ //# sourceMappingURL=vpn-route.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vpn-route.test.js","sourceRoot":"","sources":["../../src/__tests__/vpn-route.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=wireguard.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wireguard.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/wireguard.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const wireguard_js_1 = require("../wireguard.js");
5
+ const validConfig = `
6
+ [Interface]
7
+ PrivateKey = yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=
8
+ Address = 10.0.0.5/32
9
+ DNS = 10.0.0.1
10
+ MTU = 1420
11
+
12
+ [Peer]
13
+ PublicKey = xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=
14
+ PresharedKey = FpCyhws9cxwWoV4xELtfJvjJN+zQVRPISllRWgeopVE=
15
+ Endpoint = vpn.example.com:51820
16
+ AllowedIPs = 10.0.0.0/24, 192.168.1.0/24
17
+ PersistentKeepalive = 25
18
+ `;
19
+ (0, vitest_1.describe)('parseWireGuardConfig', () => {
20
+ (0, vitest_1.it)('parses a complete valid config', () => {
21
+ const cfg = (0, wireguard_js_1.parseWireGuardConfig)(validConfig);
22
+ (0, vitest_1.expect)(cfg.privateKey).toBe('yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=');
23
+ (0, vitest_1.expect)(cfg.address).toBe('10.0.0.5/32');
24
+ (0, vitest_1.expect)(cfg.mtu).toBe(1420);
25
+ (0, vitest_1.expect)(cfg.dns).toEqual(['10.0.0.1']);
26
+ (0, vitest_1.expect)(cfg.peer.publicKey).toBe('xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=');
27
+ (0, vitest_1.expect)(cfg.peer.endpoint).toBe('vpn.example.com:51820');
28
+ (0, vitest_1.expect)(cfg.peer.allowedIPs).toEqual(['10.0.0.0/24', '192.168.1.0/24']);
29
+ (0, vitest_1.expect)(cfg.peer.persistentKeepalive).toBe(25);
30
+ });
31
+ (0, vitest_1.it)('accepts minimal config (no DNS, MTU, PSK, keepalive)', () => {
32
+ const minimal = `
33
+ [Interface]
34
+ PrivateKey = yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=
35
+ Address = 10.0.0.5/32
36
+
37
+ [Peer]
38
+ PublicKey = xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=
39
+ Endpoint = vpn.example.com:51820
40
+ AllowedIPs = 10.0.0.0/24
41
+ `;
42
+ (0, vitest_1.expect)(() => (0, wireguard_js_1.parseWireGuardConfig)(minimal)).not.toThrow();
43
+ });
44
+ (0, vitest_1.it)('rejects config missing [Interface]', () => {
45
+ (0, vitest_1.expect)(() => (0, wireguard_js_1.parseWireGuardConfig)('[Peer]\nPublicKey=x\nEndpoint=e:1\nAllowedIPs=0.0.0.0/0')).toThrow(/Interface/i);
46
+ });
47
+ (0, vitest_1.it)('rejects config with multiple [Peer] blocks', () => {
48
+ const dual = validConfig + '\n[Peer]\nPublicKey = x\nEndpoint = y:1\nAllowedIPs = 0.0.0.0/0\n';
49
+ (0, vitest_1.expect)(() => (0, wireguard_js_1.parseWireGuardConfig)(dual)).toThrow(/exactly one \[Peer\]/i);
50
+ });
51
+ (0, vitest_1.it)('rejects malformed base64 key', () => {
52
+ const bad = validConfig.replace('yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=', 'not-base64!');
53
+ (0, vitest_1.expect)(() => (0, wireguard_js_1.parseWireGuardConfig)(bad)).toThrow(/private key/i);
54
+ });
55
+ (0, vitest_1.it)('rejects invalid endpoint format', () => {
56
+ const bad = validConfig.replace('vpn.example.com:51820', 'no-port-here');
57
+ (0, vitest_1.expect)(() => (0, wireguard_js_1.parseWireGuardConfig)(bad)).toThrow(/endpoint/i);
58
+ });
59
+ (0, vitest_1.it)('Zod schema rejects extra fields', () => {
60
+ const withExtra = {
61
+ privateKey: 'yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=',
62
+ address: '10.0.0.5/32',
63
+ extraField: 'nope',
64
+ peer: {
65
+ publicKey: 'xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=',
66
+ endpoint: 'vpn.example.com:51820',
67
+ allowedIPs: ['10.0.0.0/24'],
68
+ },
69
+ };
70
+ (0, vitest_1.expect)(() => wireguard_js_1.WireGuardConfigSchema.parse(withExtra)).toThrow();
71
+ });
72
+ (0, vitest_1.it)('rejects endpoint with port > 65535', () => {
73
+ const bad = validConfig.replace('vpn.example.com:51820', 'vpn.example.com:99999');
74
+ (0, vitest_1.expect)(() => (0, wireguard_js_1.parseWireGuardConfig)(bad)).toThrow(/port/i);
75
+ });
76
+ (0, vitest_1.it)('rejects CIDR with octet > 255', () => {
77
+ const bad = validConfig.replace('10.0.0.5/32', '999.0.0.0/32');
78
+ (0, vitest_1.expect)(() => (0, wireguard_js_1.parseWireGuardConfig)(bad)).toThrow(/ipv4 cidr/i);
79
+ });
80
+ (0, vitest_1.it)('rejects CIDR with prefix > 32', () => {
81
+ const bad = validConfig.replace('10.0.0.0/24', '10.0.0.0/99');
82
+ (0, vitest_1.expect)(() => (0, wireguard_js_1.parseWireGuardConfig)(bad)).toThrow(/ipv4 cidr/i);
83
+ });
84
+ (0, vitest_1.it)('rejects duplicate [Interface] section', () => {
85
+ const dup = validConfig + '\n[Interface]\nPrivateKey = yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=\nAddress = 10.0.0.6/32\n';
86
+ (0, vitest_1.expect)(() => (0, wireguard_js_1.parseWireGuardConfig)(dup)).toThrow(/duplicate \[interface\]/i);
87
+ });
88
+ (0, vitest_1.it)('rejects duplicate key within a section', () => {
89
+ const dup = validConfig.replace('Address = 10.0.0.5/32', 'Address = 10.0.0.5/32\nAddress = 10.0.0.6/32');
90
+ (0, vitest_1.expect)(() => (0, wireguard_js_1.parseWireGuardConfig)(dup)).toThrow(/duplicate key: address/i);
91
+ });
92
+ (0, vitest_1.it)('rejects AllowedIPs routing to cloud-metadata or loopback (SSRF-exemption pivot)', () => {
93
+ const meta = validConfig.replace('10.0.0.0/24, 192.168.1.0/24', '169.254.169.254/32');
94
+ (0, vitest_1.expect)(() => (0, wireguard_js_1.parseWireGuardConfig)(meta)).toThrow();
95
+ const loop = validConfig.replace('10.0.0.0/24, 192.168.1.0/24', '127.0.0.0/8');
96
+ (0, vitest_1.expect)(() => (0, wireguard_js_1.parseWireGuardConfig)(loop)).toThrow();
97
+ });
98
+ (0, vitest_1.it)('still allows AllowedIPs in legitimate private (RFC1918) ranges', () => {
99
+ const priv = validConfig.replace('10.0.0.0/24, 192.168.1.0/24', '172.16.0.0/12');
100
+ (0, vitest_1.expect)(() => (0, wireguard_js_1.parseWireGuardConfig)(priv)).not.toThrow();
101
+ });
102
+ (0, vitest_1.it)('rejects a DNS server pointing at the metadata address', () => {
103
+ const badDns = validConfig.replace('DNS = 10.0.0.1', 'DNS = 169.254.169.254');
104
+ (0, vitest_1.expect)(() => (0, wireguard_js_1.parseWireGuardConfig)(badDns)).toThrow();
105
+ });
106
+ });
107
+ (0, vitest_1.describe)('VPN_LIMITS_BY_PLAN', () => {
108
+ (0, vitest_1.it)('enforces free=0, pro=3, team=10', () => {
109
+ (0, vitest_1.expect)(wireguard_js_1.VPN_LIMITS_BY_PLAN.free).toBe(0);
110
+ (0, vitest_1.expect)(wireguard_js_1.VPN_LIMITS_BY_PLAN.pro).toBe(3);
111
+ (0, vitest_1.expect)(wireguard_js_1.VPN_LIMITS_BY_PLAN.team).toBe(10);
112
+ });
113
+ });
114
+ //# sourceMappingURL=wireguard.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wireguard.test.js","sourceRoot":"","sources":["../../src/__tests__/wireguard.test.ts"],"names":[],"mappings":";;AAAA,mCAA8C;AAC9C,kDAAkG;AAElG,MAAM,WAAW,GAAG;;;;;;;;;;;;;CAanB,CAAC;AAEF,IAAA,iBAAQ,EAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,IAAA,WAAE,EAAC,gCAAgC,EAAE,GAAG,EAAE;QACxC,MAAM,GAAG,GAAG,IAAA,mCAAoB,EAAC,WAAW,CAAC,CAAC;QAC9C,IAAA,eAAM,EAAC,GAAG,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,8CAA8C,CAAC,CAAC;QAC5E,IAAA,eAAM,EAAC,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACxC,IAAA,eAAM,EAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3B,IAAA,eAAM,EAAC,GAAG,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;QACtC,IAAA,eAAM,EAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,8CAA8C,CAAC,CAAC;QAChF,IAAA,eAAM,EAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;QACxD,IAAA,eAAM,EAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,CAAC,aAAa,EAAE,gBAAgB,CAAC,CAAC,CAAC;QACvE,IAAA,eAAM,EAAC,GAAG,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,sDAAsD,EAAE,GAAG,EAAE;QAC9D,MAAM,OAAO,GAAG;;;;;;;;;CASnB,CAAC;QACE,IAAA,eAAM,EAAC,GAAG,EAAE,CAAC,IAAA,mCAAoB,EAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;IAC5D,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,IAAA,eAAM,EAAC,GAAG,EAAE,CAAC,IAAA,mCAAoB,EAAC,yDAAyD,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IACtH,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,IAAI,GAAG,WAAW,GAAG,mEAAmE,CAAC;QAC/F,IAAA,eAAM,EAAC,GAAG,EAAE,CAAC,IAAA,mCAAoB,EAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAC5E,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,8BAA8B,EAAE,GAAG,EAAE;QACtC,MAAM,GAAG,GAAG,WAAW,CAAC,OAAO,CAAC,8CAA8C,EAAE,aAAa,CAAC,CAAC;QAC/F,IAAA,eAAM,EAAC,GAAG,EAAE,CAAC,IAAA,mCAAoB,EAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;IAClE,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,GAAG,GAAG,WAAW,CAAC,OAAO,CAAC,uBAAuB,EAAE,cAAc,CAAC,CAAC;QACzE,IAAA,eAAM,EAAC,GAAG,EAAE,CAAC,IAAA,mCAAoB,EAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,SAAS,GAAG;YAChB,UAAU,EAAE,8CAA8C;YAC1D,OAAO,EAAE,aAAa;YACtB,UAAU,EAAE,MAAM;YAClB,IAAI,EAAE;gBACJ,SAAS,EAAE,8CAA8C;gBACzD,QAAQ,EAAE,uBAAuB;gBACjC,UAAU,EAAE,CAAC,aAAa,CAAC;aAC5B;SACF,CAAC;QACF,IAAA,eAAM,EAAC,GAAG,EAAE,CAAC,oCAAqB,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;IACjE,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,GAAG,GAAG,WAAW,CAAC,OAAO,CAAC,uBAAuB,EAAE,uBAAuB,CAAC,CAAC;QAClF,IAAA,eAAM,EAAC,GAAG,EAAE,CAAC,IAAA,mCAAoB,EAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,MAAM,GAAG,GAAG,WAAW,CAAC,OAAO,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;QAC/D,IAAA,eAAM,EAAC,GAAG,EAAE,CAAC,IAAA,mCAAoB,EAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,MAAM,GAAG,GAAG,WAAW,CAAC,OAAO,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;QAC9D,IAAA,eAAM,EAAC,GAAG,EAAE,CAAC,IAAA,mCAAoB,EAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,GAAG,GAAG,WAAW,GAAG,mGAAmG,CAAC;QAC9H,IAAA,eAAM,EAAC,GAAG,EAAE,CAAC,IAAA,mCAAoB,EAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,0BAA0B,CAAC,CAAC;IAC9E,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD,MAAM,GAAG,GAAG,WAAW,CAAC,OAAO,CAC7B,uBAAuB,EACvB,8CAA8C,CAC/C,CAAC;QACF,IAAA,eAAM,EAAC,GAAG,EAAE,CAAC,IAAA,mCAAoB,EAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,yBAAyB,CAAC,CAAC;IAC7E,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,iFAAiF,EAAE,GAAG,EAAE;QACzF,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,6BAA6B,EAAE,oBAAoB,CAAC,CAAC;QACtF,IAAA,eAAM,EAAC,GAAG,EAAE,CAAC,IAAA,mCAAoB,EAAC,IAAI,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;QACnD,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,6BAA6B,EAAE,aAAa,CAAC,CAAC;QAC/E,IAAA,eAAM,EAAC,GAAG,EAAE,CAAC,IAAA,mCAAoB,EAAC,IAAI,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;IACrD,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,gEAAgE,EAAE,GAAG,EAAE;QACxE,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,6BAA6B,EAAE,eAAe,CAAC,CAAC;QACjF,IAAA,eAAM,EAAC,GAAG,EAAE,CAAC,IAAA,mCAAoB,EAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,gBAAgB,EAAE,uBAAuB,CAAC,CAAC;QAC9E,IAAA,eAAM,EAAC,GAAG,EAAE,CAAC,IAAA,mCAAoB,EAAC,MAAM,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;IACvD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,IAAA,iBAAQ,EAAC,oBAAoB,EAAE,GAAG,EAAE;IAClC,IAAA,WAAE,EAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,IAAA,eAAM,EAAC,iCAAkB,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACxC,IAAA,eAAM,EAAC,iCAAkB,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACvC,IAAA,eAAM,EAAC,iCAAkB,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Stripe subscription-status helpers shared by the billing routes.
3
+ *
4
+ * Keep these rules small and explicit: checkout/self-heal and webhook
5
+ * downgrade paths must make the same decision about whether an existing
6
+ * subscription can still be recovered or should be treated as terminal.
7
+ */
8
+ export declare function isEntitledStripeSubscriptionStatus(status: string): boolean;
9
+ export declare function isRecoverableStripeSubscriptionStatus(status: string): boolean;
10
+ export declare function isTerminalStripeSubscriptionStatus(status: string): boolean;
11
+ export declare function canStartNewCheckoutForStripeSubscriptionStatus(status: string): boolean;
12
+ //# sourceMappingURL=billing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"billing.d.ts","sourceRoot":"","sources":["../src/billing.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAoBH,wBAAgB,kCAAkC,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAE1E;AAED,wBAAgB,qCAAqC,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAE7E;AAED,wBAAgB,kCAAkC,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAE1E;AAED,wBAAgB,8CAA8C,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEtF"}
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ /**
3
+ * Stripe subscription-status helpers shared by the billing routes.
4
+ *
5
+ * Keep these rules small and explicit: checkout/self-heal and webhook
6
+ * downgrade paths must make the same decision about whether an existing
7
+ * subscription can still be recovered or should be treated as terminal.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.isEntitledStripeSubscriptionStatus = isEntitledStripeSubscriptionStatus;
11
+ exports.isRecoverableStripeSubscriptionStatus = isRecoverableStripeSubscriptionStatus;
12
+ exports.isTerminalStripeSubscriptionStatus = isTerminalStripeSubscriptionStatus;
13
+ exports.canStartNewCheckoutForStripeSubscriptionStatus = canStartNewCheckoutForStripeSubscriptionStatus;
14
+ const ENTITLED_STATUSES = new Set([
15
+ 'active',
16
+ 'trialing',
17
+ ]);
18
+ const RECOVERABLE_STATUSES = new Set([
19
+ ...ENTITLED_STATUSES,
20
+ 'past_due',
21
+ 'unpaid',
22
+ 'incomplete',
23
+ 'paused',
24
+ ]);
25
+ const TERMINAL_STATUSES = new Set([
26
+ 'canceled',
27
+ 'incomplete_expired',
28
+ ]);
29
+ function isEntitledStripeSubscriptionStatus(status) {
30
+ return ENTITLED_STATUSES.has(status);
31
+ }
32
+ function isRecoverableStripeSubscriptionStatus(status) {
33
+ return RECOVERABLE_STATUSES.has(status);
34
+ }
35
+ function isTerminalStripeSubscriptionStatus(status) {
36
+ return TERMINAL_STATUSES.has(status);
37
+ }
38
+ function canStartNewCheckoutForStripeSubscriptionStatus(status) {
39
+ return isTerminalStripeSubscriptionStatus(status);
40
+ }
41
+ //# sourceMappingURL=billing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"billing.js","sourceRoot":"","sources":["../src/billing.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;AAoBH,gFAEC;AAED,sFAEC;AAED,gFAEC;AAED,wGAEC;AAhCD,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC;IAChC,QAAQ;IACR,UAAU;CACX,CAAC,CAAC;AAEH,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAC;IACnC,GAAG,iBAAiB;IACpB,UAAU;IACV,QAAQ;IACR,YAAY;IACZ,QAAQ;CACT,CAAC,CAAC;AAEH,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC;IAChC,UAAU;IACV,oBAAoB;CACrB,CAAC,CAAC;AAEH,SAAgB,kCAAkC,CAAC,MAAc;IAC/D,OAAO,iBAAiB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AACvC,CAAC;AAED,SAAgB,qCAAqC,CAAC,MAAc;IAClE,OAAO,oBAAoB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AAC1C,CAAC;AAED,SAAgB,kCAAkC,CAAC,MAAc;IAC/D,OAAO,iBAAiB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AACvC,CAAC;AAED,SAAgB,8CAA8C,CAAC,MAAc;IAC3E,OAAO,kCAAkC,CAAC,MAAM,CAAC,CAAC;AACpD,CAAC"}
package/dist/crypto.d.ts CHANGED
@@ -37,6 +37,11 @@ export declare function envelopeDecrypt(encryptedData: string, masterKey: Uint8A
37
37
  export declare function encryptDEK(dek: Uint8Array, masterKey: Uint8Array): string;
38
38
  export declare function decryptDEK(encryptedDEK: string, masterKey: Uint8Array): Uint8Array;
39
39
  export declare function getMasterKey(): Uint8Array;
40
+ /** Return every retired key in rotation order (most-recent first). Used by
41
+ * the credential-decryption fallback path to try each until one succeeds.
42
+ */
43
+ export declare function getPreviousMasterKeys(): Uint8Array[];
44
+ /** Back-compat: first previous key only. Prefer `getPreviousMasterKeys()`. */
40
45
  export declare function getPreviousMasterKey(): Uint8Array | null;
41
46
  /** Zero and release cached master keys. Call on graceful shutdown. */
42
47
  export declare function clearCachedKeys(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAYA,wBAAgB,WAAW,IAAI,UAAU,CAExC;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,MAAM,CAU7D;AAED;;;;;;;;;GASG;AACH,wBAAgB,OAAO,CAAC,aAAa,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,MAAM,CA0BtE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,GAAG,MAAM,CAU3E;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,GAAG,MAAM,CAKpF;AAkDD,wBAAgB,UAAU,CAAC,GAAG,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,GAAG,MAAM,CAEzE;AAED,wBAAgB,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,GAAG,UAAU,CAGlF;AAMD,wBAAgB,YAAY,IAAI,UAAU,CAMzC;AAMD,wBAAgB,oBAAoB,IAAI,UAAU,GAAG,IAAI,CAOxD;AAED,sEAAsE;AACtE,wBAAgB,eAAe,IAAI,IAAI,CAUtC;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,SAAS,EAAE,UAAU,GACpB;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAA;CAAE,CAUpD;AAED,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,EACpB,gBAAgB,EAAE,MAAM,EACxB,SAAS,EAAE,UAAU,GACpB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAUzB"}
1
+ {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAYA,wBAAgB,WAAW,IAAI,UAAU,CAExC;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,MAAM,CAU7D;AAED;;;;;;;;;GASG;AACH,wBAAgB,OAAO,CAAC,aAAa,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,MAAM,CAwBtE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,GAAG,MAAM,CAU3E;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,GAAG,MAAM,CAKpF;AAkDD,wBAAgB,UAAU,CAAC,GAAG,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,GAAG,MAAM,CAEzE;AAED,wBAAgB,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,GAAG,UAAU,CAGlF;AA0BD,wBAAgB,YAAY,IAAI,UAAU,CAMzC;AAuCD;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,UAAU,EAAE,CAOpD;AAED,8EAA8E;AAC9E,wBAAgB,oBAAoB,IAAI,UAAU,GAAG,IAAI,CAExD;AAED,sEAAsE;AACtE,wBAAgB,eAAe,IAAI,IAAI,CAUtC;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,SAAS,EAAE,UAAU,GACpB;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAA;CAAE,CAUpD;AAED,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,EACpB,gBAAgB,EAAE,MAAM,EACxB,SAAS,EAAE,UAAU,GACpB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAUzB"}
package/dist/crypto.js CHANGED
@@ -11,6 +11,7 @@ exports.envelopeDecrypt = envelopeDecrypt;
11
11
  exports.encryptDEK = encryptDEK;
12
12
  exports.decryptDEK = decryptDEK;
13
13
  exports.getMasterKey = getMasterKey;
14
+ exports.getPreviousMasterKeys = getPreviousMasterKeys;
14
15
  exports.getPreviousMasterKey = getPreviousMasterKey;
15
16
  exports.clearCachedKeys = clearCachedKeys;
16
17
  exports.generateMasterKey = generateMasterKey;
@@ -66,13 +67,12 @@ function decrypt(encryptedData, key) {
66
67
  if (message) {
67
68
  return new TextDecoder().decode(message);
68
69
  }
69
- // Try previous master key for rotation support
70
- const prevKey = getPreviousMasterKey();
71
- if (prevKey) {
72
- const message2 = tweetnacl_1.default.secretbox.open(box, nonce, prevKey);
73
- if (message2) {
74
- return new TextDecoder().decode(message2);
75
- }
70
+ // Try every retired master key (most-recent first). See
71
+ // `getPreviousMasterKeys()` for the rotation model.
72
+ for (const prevKey of getPreviousMasterKeys()) {
73
+ const attempt = tweetnacl_1.default.secretbox.open(box, nonce, prevKey);
74
+ if (attempt)
75
+ return new TextDecoder().decode(attempt);
76
76
  }
77
77
  throw new Error('Decryption failed: invalid key or corrupted data');
78
78
  }
@@ -166,27 +166,83 @@ function decryptDEK(encryptedDEK, masterKey) {
166
166
  // Cache the decoded master key to avoid creating multiple copies in memory.
167
167
  // A single copy is preferable to many short-lived copies scattered on the heap.
168
168
  let _cachedMasterKey = null;
169
+ /** nacl.secretbox requires a 32-byte key. A shorter key silently breaks
170
+ * crypto with a confusing error deep in the stack; a longer key indicates
171
+ * misconfiguration. Fail loudly at first access instead. */
172
+ const SECRETBOX_KEY_LENGTH = 32;
173
+ function validateMasterKey(raw, name) {
174
+ let decoded;
175
+ try {
176
+ decoded = (0, tweetnacl_util_1.decodeBase64)(raw);
177
+ }
178
+ catch {
179
+ throw new Error(`${name} is not valid base64`);
180
+ }
181
+ if (decoded.length !== SECRETBOX_KEY_LENGTH) {
182
+ throw new Error(`${name} must decode to exactly ${SECRETBOX_KEY_LENGTH} bytes (got ${decoded.length})`);
183
+ }
184
+ return decoded;
185
+ }
169
186
  function getMasterKey() {
170
187
  if (_cachedMasterKey)
171
188
  return _cachedMasterKey.slice();
172
189
  const key = process.env.MASTER_KEY;
173
190
  if (!key)
174
191
  throw new Error('MASTER_KEY environment variable not set');
175
- _cachedMasterKey = (0, tweetnacl_util_1.decodeBase64)(key);
192
+ _cachedMasterKey = validateMasterKey(key, 'MASTER_KEY');
176
193
  return _cachedMasterKey.slice();
177
194
  }
178
- // Support MASTER_KEY rotation: try previous key when current key fails
179
- let _cachedPrevKey = null;
180
- let _prevKeyChecked = false;
181
- function getPreviousMasterKey() {
182
- if (_prevKeyChecked)
183
- return _cachedPrevKey ? _cachedPrevKey.slice() : null;
184
- _prevKeyChecked = true;
185
- const key = process.env.MASTER_KEY_PREVIOUS;
186
- if (!key)
195
+ // Support MASTER_KEY rotation with up to 5 previous keys. Operator sets
196
+ // MASTER_KEY = current key
197
+ // MASTER_KEY_PREVIOUS = most recent retired key (used during rotation #1)
198
+ // MASTER_KEY_PREVIOUS_2 = one-before-that (rotation #2)
199
+ // ...
200
+ // MASTER_KEY_PREVIOUS_5 = oldest retained key (rotation #5)
201
+ //
202
+ // Decryption walks the list top-down until one key succeeds; that lets an
203
+ // operator roll the key repeatedly (e.g. quarterly) without a big-bang
204
+ // re-encryption job in between. After all rows are re-encrypted with the
205
+ // new current key, unset the oldest MASTER_KEY_PREVIOUS_* env var.
206
+ //
207
+ // Each env var is validated + cached at first-use. The cache is cleared on
208
+ // shutdown so keys don't linger in the heap post-SIGTERM.
209
+ const MAX_PREVIOUS_KEYS = 5;
210
+ const _prevKeyCache = new Array(MAX_PREVIOUS_KEYS).fill(null);
211
+ function previousKeyEnvName(slot) {
212
+ // slot 0 → MASTER_KEY_PREVIOUS, slot 1 → MASTER_KEY_PREVIOUS_2, etc.
213
+ return slot === 0 ? 'MASTER_KEY_PREVIOUS' : `MASTER_KEY_PREVIOUS_${slot + 1}`;
214
+ }
215
+ function loadPreviousKeySlot(slot) {
216
+ const cached = _prevKeyCache[slot];
217
+ if (cached === 'unset')
218
+ return null;
219
+ if (cached instanceof Uint8Array)
220
+ return cached.slice();
221
+ const name = previousKeyEnvName(slot);
222
+ const raw = process.env[name];
223
+ if (!raw) {
224
+ _prevKeyCache[slot] = 'unset';
187
225
  return null;
188
- _cachedPrevKey = (0, tweetnacl_util_1.decodeBase64)(key);
189
- return _cachedPrevKey.slice();
226
+ }
227
+ const key = validateMasterKey(raw, name);
228
+ _prevKeyCache[slot] = key;
229
+ return key.slice();
230
+ }
231
+ /** Return every retired key in rotation order (most-recent first). Used by
232
+ * the credential-decryption fallback path to try each until one succeeds.
233
+ */
234
+ function getPreviousMasterKeys() {
235
+ const out = [];
236
+ for (let i = 0; i < MAX_PREVIOUS_KEYS; i++) {
237
+ const k = loadPreviousKeySlot(i);
238
+ if (k)
239
+ out.push(k);
240
+ }
241
+ return out;
242
+ }
243
+ /** Back-compat: first previous key only. Prefer `getPreviousMasterKeys()`. */
244
+ function getPreviousMasterKey() {
245
+ return loadPreviousKeySlot(0);
190
246
  }
191
247
  /** Zero and release cached master keys. Call on graceful shutdown. */
192
248
  function clearCachedKeys() {
@@ -194,11 +250,12 @@ function clearCachedKeys() {
194
250
  _cachedMasterKey.fill(0);
195
251
  _cachedMasterKey = null;
196
252
  }
197
- if (_cachedPrevKey) {
198
- _cachedPrevKey.fill(0);
199
- _cachedPrevKey = null;
253
+ for (let i = 0; i < MAX_PREVIOUS_KEYS; i++) {
254
+ const k = _prevKeyCache[i];
255
+ if (k instanceof Uint8Array)
256
+ k.fill(0);
257
+ _prevKeyCache[i] = null;
200
258
  }
201
- _prevKeyChecked = false;
202
259
  }
203
260
  function generateMasterKey() {
204
261
  return (0, tweetnacl_util_1.encodeBase64)(generateKey());