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.
- package/dist/__tests__/billing.test.d.ts +2 -0
- package/dist/__tests__/billing.test.d.ts.map +1 -0
- package/dist/__tests__/billing.test.js +31 -0
- package/dist/__tests__/billing.test.js.map +1 -0
- package/dist/__tests__/dns-pinning.test.d.ts +2 -0
- package/dist/__tests__/dns-pinning.test.d.ts.map +1 -0
- package/dist/__tests__/dns-pinning.test.js +33 -0
- package/dist/__tests__/dns-pinning.test.js.map +1 -0
- package/dist/__tests__/llm-classifier-cache-store.test.d.ts +2 -0
- package/dist/__tests__/llm-classifier-cache-store.test.d.ts.map +1 -0
- package/dist/__tests__/llm-classifier-cache-store.test.js +65 -0
- package/dist/__tests__/llm-classifier-cache-store.test.js.map +1 -0
- package/dist/__tests__/llm-classifier-cache.test.d.ts +2 -0
- package/dist/__tests__/llm-classifier-cache.test.d.ts.map +1 -0
- package/dist/__tests__/llm-classifier-cache.test.js +44 -0
- package/dist/__tests__/llm-classifier-cache.test.js.map +1 -0
- package/dist/__tests__/llm-classifier.test.d.ts +2 -0
- package/dist/__tests__/llm-classifier.test.d.ts.map +1 -0
- package/dist/__tests__/llm-classifier.test.js +167 -0
- package/dist/__tests__/llm-classifier.test.js.map +1 -0
- package/dist/__tests__/plans-classifier-limits.test.d.ts +2 -0
- package/dist/__tests__/plans-classifier-limits.test.d.ts.map +1 -0
- package/dist/__tests__/plans-classifier-limits.test.js +22 -0
- package/dist/__tests__/plans-classifier-limits.test.js.map +1 -0
- package/dist/__tests__/policy-category-floor.test.d.ts +2 -0
- package/dist/__tests__/policy-category-floor.test.d.ts.map +1 -0
- package/dist/__tests__/policy-category-floor.test.js +46 -0
- package/dist/__tests__/policy-category-floor.test.js.map +1 -0
- package/dist/__tests__/policy-claude-bash.test.d.ts +2 -0
- package/dist/__tests__/policy-claude-bash.test.d.ts.map +1 -0
- package/dist/__tests__/policy-claude-bash.test.js +401 -0
- package/dist/__tests__/policy-claude-bash.test.js.map +1 -0
- package/dist/__tests__/policy-llm-floor.test.d.ts +2 -0
- package/dist/__tests__/policy-llm-floor.test.d.ts.map +1 -0
- package/dist/__tests__/policy-llm-floor.test.js +107 -0
- package/dist/__tests__/policy-llm-floor.test.js.map +1 -0
- package/dist/__tests__/policy-ssh-e2e.test.d.ts +2 -0
- package/dist/__tests__/policy-ssh-e2e.test.d.ts.map +1 -0
- package/dist/__tests__/policy-ssh-e2e.test.js +89 -0
- package/dist/__tests__/policy-ssh-e2e.test.js.map +1 -0
- package/dist/__tests__/policy-ssh-sessions.test.d.ts +2 -0
- package/dist/__tests__/policy-ssh-sessions.test.d.ts.map +1 -0
- package/dist/__tests__/policy-ssh-sessions.test.js +139 -0
- package/dist/__tests__/policy-ssh-sessions.test.js.map +1 -0
- package/dist/__tests__/policy-ssh.test.d.ts +2 -0
- package/dist/__tests__/policy-ssh.test.d.ts.map +1 -0
- package/dist/__tests__/policy-ssh.test.js +180 -0
- package/dist/__tests__/policy-ssh.test.js.map +1 -0
- package/dist/__tests__/policy.test.js +400 -2
- package/dist/__tests__/policy.test.js.map +1 -1
- package/dist/__tests__/redact.test.js +76 -0
- package/dist/__tests__/redact.test.js.map +1 -1
- package/dist/__tests__/signing.test.js +89 -0
- package/dist/__tests__/signing.test.js.map +1 -1
- package/dist/__tests__/ssh-fingerprint.test.d.ts +2 -0
- package/dist/__tests__/ssh-fingerprint.test.d.ts.map +1 -0
- package/dist/__tests__/ssh-fingerprint.test.js +19 -0
- package/dist/__tests__/ssh-fingerprint.test.js.map +1 -0
- package/dist/__tests__/vpn-route.test.d.ts +2 -0
- package/dist/__tests__/vpn-route.test.d.ts.map +1 -0
- package/dist/__tests__/vpn-route.test.js +72 -0
- package/dist/__tests__/vpn-route.test.js.map +1 -0
- package/dist/__tests__/wireguard.test.d.ts +2 -0
- package/dist/__tests__/wireguard.test.d.ts.map +1 -0
- package/dist/__tests__/wireguard.test.js +114 -0
- package/dist/__tests__/wireguard.test.js.map +1 -0
- package/dist/billing.d.ts +12 -0
- package/dist/billing.d.ts.map +1 -0
- package/dist/billing.js +41 -0
- package/dist/billing.js.map +1 -0
- package/dist/crypto.d.ts +5 -0
- package/dist/crypto.d.ts.map +1 -1
- package/dist/crypto.js +80 -23
- package/dist/crypto.js.map +1 -1
- package/dist/dns-pinning.d.ts +28 -0
- package/dist/dns-pinning.d.ts.map +1 -0
- package/dist/dns-pinning.js +113 -0
- package/dist/dns-pinning.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/llm-classifier-cache-store.d.ts +49 -0
- package/dist/llm-classifier-cache-store.d.ts.map +1 -0
- package/dist/llm-classifier-cache-store.js +63 -0
- package/dist/llm-classifier-cache-store.js.map +1 -0
- package/dist/llm-classifier-cache.d.ts +6 -0
- package/dist/llm-classifier-cache.d.ts.map +1 -0
- package/dist/llm-classifier-cache.js +52 -0
- package/dist/llm-classifier-cache.js.map +1 -0
- package/dist/llm-classifier.d.ts +29 -0
- package/dist/llm-classifier.d.ts.map +1 -0
- package/dist/llm-classifier.js +191 -0
- package/dist/llm-classifier.js.map +1 -0
- package/dist/observability.d.ts +36 -0
- package/dist/observability.d.ts.map +1 -0
- package/dist/observability.js +75 -0
- package/dist/observability.js.map +1 -0
- package/dist/plans.d.ts +17 -0
- package/dist/plans.d.ts.map +1 -1
- package/dist/plans.js +36 -14
- package/dist/plans.js.map +1 -1
- package/dist/policy.d.ts +173 -3
- package/dist/policy.d.ts.map +1 -1
- package/dist/policy.js +910 -42
- package/dist/policy.js.map +1 -1
- package/dist/redact.d.ts.map +1 -1
- package/dist/redact.js +83 -3
- package/dist/redact.js.map +1 -1
- package/dist/regex-safety.d.ts +21 -0
- package/dist/regex-safety.d.ts.map +1 -0
- package/dist/regex-safety.js +49 -0
- package/dist/regex-safety.js.map +1 -0
- package/dist/sanitize.d.ts +31 -0
- package/dist/sanitize.d.ts.map +1 -0
- package/dist/sanitize.js +54 -0
- package/dist/sanitize.js.map +1 -0
- package/dist/schemas.d.ts +202 -10
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +91 -1
- package/dist/schemas.js.map +1 -1
- package/dist/signing.d.ts +15 -0
- package/dist/signing.d.ts.map +1 -1
- package/dist/signing.js +53 -4
- package/dist/signing.js.map +1 -1
- package/dist/ssh-fingerprint.d.ts +10 -0
- package/dist/ssh-fingerprint.d.ts.map +1 -0
- package/dist/ssh-fingerprint.js +52 -0
- package/dist/ssh-fingerprint.js.map +1 -0
- package/dist/ssrf.d.ts +36 -0
- package/dist/ssrf.d.ts.map +1 -0
- package/dist/ssrf.js +140 -0
- package/dist/ssrf.js.map +1 -0
- package/dist/types.d.ts +130 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/wireguard.d.ts +63 -0
- package/dist/wireguard.d.ts.map +1 -0
- package/dist/wireguard.js +226 -0
- package/dist/wireguard.js.map +1 -0
- package/package.json +42 -29
- package/.turbo/turbo-build.log +0 -4
- package/.turbo/turbo-test.log +0 -76
- package/dist/__tests__/content-crypto.test.d.ts +0 -2
- package/dist/__tests__/content-crypto.test.d.ts.map +0 -1
- package/dist/__tests__/content-crypto.test.js +0 -117
- package/dist/__tests__/content-crypto.test.js.map +0 -1
- package/dist/__tests__/signing.test (# Edit conflict 2026-04-01 z3etfmC #).js +0 -51
- package/dist/__tests__/signing.test.js (# Edit conflict 2026-04-01 4rndy9C #).map +0 -1
- package/dist/content-crypto.d.ts +0 -24
- package/dist/content-crypto.d.ts.map +0 -1
- package/dist/content-crypto.js +0 -58
- package/dist/content-crypto.js.map +0 -1
- package/src/__tests__/crypto.test.ts +0 -169
- package/src/__tests__/messaging.test.ts +0 -83
- package/src/__tests__/policy.test.ts +0 -222
- package/src/__tests__/redact.test.ts +0 -41
- package/src/__tests__/signing.test.ts +0 -55
- package/src/crypto.ts +0 -235
- package/src/index.ts +0 -8
- package/src/mcp-catalog.ts +0 -181
- package/src/plans.ts +0 -116
- package/src/policy.ts +0 -216
- package/src/redact.ts +0 -131
- package/src/schemas.ts +0 -121
- package/src/signing.ts +0 -120
- package/src/types.ts +0 -213
- package/test-gateway.mjs +0 -47
- package/tsconfig.json +0 -10
- 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":"
|
|
1
|
+
{"version":3,"file":"signing.test.js","sourceRoot":"","sources":["../../src/__tests__/signing.test.ts"],"names":[],"mappings":";;;;;AAAA,mCAA8C;AAC9C,0DAA6B;AAC7B,mDAAwE;AACxE,8CAAgG;AAEhG;;;;GAIG;AACH,SAAS,iBAAiB,CAAC,IAA6B,EAAE,aAAqB;IAC7E,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IACrC,MAAM,KAAK,GAAG,IAAA,6BAAY,EAAC,mBAAI,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC;IACjD,MAAM,SAAS,GAAG,IAAA,+BAAkB,EAAC,IAAI,CAAC,CAAC;IAC3C,MAAM,OAAO,GAAG,IAAA,2BAAU,EAAC,GAAG,SAAS,IAAI,SAAS,IAAI,KAAK,EAAE,CAAC,CAAC;IACjE,MAAM,SAAS,GAAG,mBAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,IAAA,6BAAY,EAAC,aAAa,CAAC,CAAC,CAAC;IAC3E,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,IAAA,6BAAY,EAAC,SAAS,CAAC,EAAE,CAAC;AAClE,CAAC;AAED,IAAA,iBAAQ,EAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,IAAA,WAAE,EAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,MAAM,EAAE,GAAG,IAAA,4BAAe,GAAE,CAAC;QAC7B,IAAA,eAAM,EAAC,EAAE,CAAC,SAAS,CAAC,CAAC,UAAU,EAAE,CAAC;QAClC,IAAA,eAAM,EAAC,EAAE,CAAC,UAAU,CAAC,CAAC,UAAU,EAAE,CAAC;QACnC,IAAA,eAAM,EAAC,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,MAAM,EAAE,GAAG,IAAA,4BAAe,GAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,CAAC;QAC/E,MAAM,OAAO,GAAG,IAAA,wBAAW,EAAC,IAAI,EAAE,WAAW,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC;QAC9D,IAAA,eAAM,EAAC,GAAG,EAAE,CAAC,IAAA,0BAAa,EAAC,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;IACzE,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,EAAE,GAAG,IAAA,4BAAe,GAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,CAAC;QAC/E,MAAM,OAAO,GAAG,IAAA,wBAAW,EAAC,IAAI,EAAE,WAAW,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC;QAC9D,MAAM,QAAQ,GAAG,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;QAC5C,IAAA,eAAM,EAAC,GAAG,EAAE,CAAC,IAAA,0BAAa,EAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC5F,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,6CAA6C,EAAE,GAAG,EAAE;QACrD,MAAM,EAAE,GAAG,IAAA,4BAAe,GAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC;QAC5F,MAAM,OAAO,GAAG,IAAA,wBAAW,EAAC,IAAI,EAAE,WAAW,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC;QAC9D,MAAM,QAAQ,GAAG,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC;QACxE,IAAA,eAAM,EAAC,GAAG,EAAE,CAAC,IAAA,0BAAa,EAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC5F,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,uEAAuE,EAAE,GAAG,EAAE;QAC/E,MAAM,GAAG,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;QAC7D,MAAM,MAAM,GAAG,IAAA,+BAAkB,EAAC,GAAG,CAAC,CAAC;QACvC,0CAA0C;QAC1C,IAAA,eAAM,EAAC,MAAM,CAAC,CAAC,IAAI,CAAC,+CAA+C,CAAC,CAAC;IACvE,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,MAAM,EAAE,GAAG,IAAA,4BAAe,GAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;QACjE,IAAA,eAAM,EAAC,GAAG,EAAE,CAAC,IAAA,0BAAa,EAAC,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,oCAAoC,CAAC,CAAC;IACpG,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,MAAM,EAAE,GAAG,IAAA,4BAAe,GAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;QACjE,MAAM,OAAO,GAAG,IAAA,wBAAW,EAAC,IAAI,EAAE,WAAW,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC;QAC9D,OAAO,CAAC,aAAa,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAC7D,IAAA,eAAM,EAAC,GAAG,EAAE,CAAC,IAAA,0BAAa,EAAC,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACrF,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,yEAAyE;QACzE,wEAAwE;QACxE,qEAAqE;QACrE,MAAM,EAAE,GAAG,IAAA,4BAAe,GAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;QACjE,MAAM,OAAO,GAAG,IAAA,wBAAW,EAAC,IAAI,EAAE,WAAW,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC;QAC9D,OAAO,CAAC,aAAa,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAC7D,IAAA,eAAM,EAAC,GAAG,EAAE,CAAC,IAAA,0BAAa,EAAC,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACrF,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,kEAAkE;QAClE,qEAAqE;QACrE,wEAAwE;QACxE,MAAM,EAAE,GAAG,IAAA,4BAAe,GAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;QAChE,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;QACjC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;YAC7B,MAAM,CAAC,GAAG,IAAA,wBAAW,EAAC,IAAI,EAAE,WAAW,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,CAAC;YACnE,IAAI,CAAC;gBAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACvB,CAAC;QACD,IAAA,eAAM,EAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,qEAAqE,EAAE,GAAG,EAAE;QAC7E,oEAAoE;QACpE,uEAAuE;QACvE,MAAM,EAAE,GAAG,IAAA,4BAAe,GAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;QAChE,MAAM,OAAO,GAAG,IAAA,wBAAW,EAAC,IAAI,EAAE,WAAW,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC;QAC9D,OAAO,CAAC,SAAS,CAAC,GAAG,0BAA0B,CAAC,CAAC,iBAAiB;QAClE,IAAA,eAAM,EAAC,GAAG,EAAE,CAAC,IAAA,0BAAa,EAAC,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACxF,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,0FAA0F,EAAE,GAAG,EAAE;QAClG,qEAAqE;QACrE,kEAAkE;QAClE,gDAAgD;QAChD,MAAM,EAAE,GAAG,IAAA,4BAAe,GAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;QAChE,MAAM,QAAQ,GAAG,IAAA,wBAAW,EAAC,IAAI,EAAE,WAAW,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC;QAC/D,MAAM,QAAQ,GAAG,IAAA,wBAAW,EAAC,IAAI,EAAE,WAAW,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC;QAC/D,IAAA,eAAM,EAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC;QACzC,IAAA,eAAM,EAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;QAC1D,IAAA,eAAM,EAAC,GAAG,EAAE,CAAC,IAAA,0BAAa,EAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QACxE,IAAA,eAAM,EAAC,GAAG,EAAE,CAAC,IAAA,0BAAa,EAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;IAC1E,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,4EAA4E,EAAE,GAAG,EAAE;QACpF,sEAAsE;QACtE,wEAAwE;QACxE,gEAAgE;QAChE,MAAM,EAAE,GAAG,IAAA,4BAAe,GAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;QAChE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,iBAAiB,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC;QAC/E,IAAA,eAAM,EAAC,GAAG,EAAE,CACV,IAAA,0BAAa,EACX,IAAI,EACJ,EAAE,YAAY,EAAE,GAAG,EAAE,aAAa,EAAE,SAAS,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,EAC3F,EAAE,CAAC,SAAS,CACb,CACF,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,IAAA,WAAE,EAAC,iFAAiF,EAAE,GAAG,EAAE;QACzF,0EAA0E;QAC1E,2EAA2E;QAC3E,wEAAwE;QACxE,+CAA+C;QAC/C,MAAM,EAAE,GAAG,IAAA,4BAAe,GAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;QAChE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,iBAAiB,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC;QAC/E,IAAA,eAAM,EAAC,GAAG,EAAE,CACV,IAAA,0BAAa,EACX,IAAI,EACJ;YACE,YAAY,EAAE,GAAG;YACjB,aAAa,EAAE,SAAS;YACxB,aAAa,EAAE,SAAS;YACxB,SAAS,EAAE,KAAK;YAChB,qBAAqB,EAAE,GAAG;SAC3B,EACD,EAAE,CAAC,SAAS,CACb,CACF,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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":"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"}
|
|
@@ -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"}
|
package/dist/billing.js
ADDED
|
@@ -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;
|
package/dist/crypto.d.ts.map
CHANGED
|
@@ -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,
|
|
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
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
-
if (
|
|
74
|
-
return new TextDecoder().decode(
|
|
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 = (
|
|
192
|
+
_cachedMasterKey = validateMasterKey(key, 'MASTER_KEY');
|
|
176
193
|
return _cachedMasterKey.slice();
|
|
177
194
|
}
|
|
178
|
-
// Support MASTER_KEY rotation
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
189
|
-
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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());
|