@keynv/testers 0.1.0-rc.23

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 (58) hide show
  1. package/LICENSE +21 -0
  2. package/dist/http.d.ts +36 -0
  3. package/dist/http.d.ts.map +1 -0
  4. package/dist/http.js +80 -0
  5. package/dist/http.js.map +1 -0
  6. package/dist/http.test.d.ts +2 -0
  7. package/dist/http.test.d.ts.map +1 -0
  8. package/dist/http.test.js +71 -0
  9. package/dist/http.test.js.map +1 -0
  10. package/dist/index.d.ts +14 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +20 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/mysql.d.ts +25 -0
  15. package/dist/mysql.d.ts.map +1 -0
  16. package/dist/mysql.js +60 -0
  17. package/dist/mysql.js.map +1 -0
  18. package/dist/postgres.d.ts +25 -0
  19. package/dist/postgres.d.ts.map +1 -0
  20. package/dist/postgres.js +59 -0
  21. package/dist/postgres.js.map +1 -0
  22. package/dist/redis.d.ts +25 -0
  23. package/dist/redis.d.ts.map +1 -0
  24. package/dist/redis.js +57 -0
  25. package/dist/redis.js.map +1 -0
  26. package/dist/run.d.ts +16 -0
  27. package/dist/run.d.ts.map +1 -0
  28. package/dist/run.js +50 -0
  29. package/dist/run.js.map +1 -0
  30. package/dist/run.test.d.ts +2 -0
  31. package/dist/run.test.d.ts.map +1 -0
  32. package/dist/run.test.js +69 -0
  33. package/dist/run.test.js.map +1 -0
  34. package/dist/sanitize.d.ts +14 -0
  35. package/dist/sanitize.d.ts.map +1 -0
  36. package/dist/sanitize.js +29 -0
  37. package/dist/sanitize.js.map +1 -0
  38. package/dist/sanitize.test.d.ts +2 -0
  39. package/dist/sanitize.test.d.ts.map +1 -0
  40. package/dist/sanitize.test.js +43 -0
  41. package/dist/sanitize.test.js.map +1 -0
  42. package/dist/ssh.d.ts +34 -0
  43. package/dist/ssh.d.ts.map +1 -0
  44. package/dist/ssh.js +111 -0
  45. package/dist/ssh.js.map +1 -0
  46. package/dist/ssrf.d.ts +17 -0
  47. package/dist/ssrf.d.ts.map +1 -0
  48. package/dist/ssrf.js +185 -0
  49. package/dist/ssrf.js.map +1 -0
  50. package/dist/ssrf.test.d.ts +2 -0
  51. package/dist/ssrf.test.d.ts.map +1 -0
  52. package/dist/ssrf.test.js +104 -0
  53. package/dist/ssrf.test.js.map +1 -0
  54. package/dist/types.d.ts +42 -0
  55. package/dist/types.d.ts.map +1 -0
  56. package/dist/types.js +4 -0
  57. package/dist/types.js.map +1 -0
  58. package/package.json +52 -0
@@ -0,0 +1,104 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ // Deterministic, offline DNS: a couple of "malicious" names resolve to
3
+ // internal addresses, one resolves to a public address.
4
+ vi.mock('node:dns/promises', () => ({
5
+ lookup: vi.fn(async (host) => {
6
+ if (host === 'rebind.evil.test')
7
+ return [{ address: '127.0.0.1', family: 4 }];
8
+ if (host === 'metadata.evil.test')
9
+ return [{ address: '169.254.169.254', family: 4 }];
10
+ if (host === 'split.evil.test')
11
+ return [
12
+ { address: '93.184.216.34', family: 4 },
13
+ { address: '10.0.0.5', family: 4 },
14
+ ];
15
+ if (host === 'public.test')
16
+ return [{ address: '93.184.216.34', family: 4 }];
17
+ throw Object.assign(new Error('ENOTFOUND'), { code: 'ENOTFOUND' });
18
+ }),
19
+ }));
20
+ import { isBlockedHost, isBlockedHostResolved, isBlockedUrl, isBlockedUrlResolved, } from './ssrf.js';
21
+ describe('isBlockedHost — literal IPv4', () => {
22
+ it.each([
23
+ ['loopback', '127.0.0.1'],
24
+ ['loopback /8', '127.1.2.3'],
25
+ ['10/8', '10.0.0.5'],
26
+ ['172.16/12', '172.16.0.1'],
27
+ ['172.31 edge', '172.31.255.255'],
28
+ ['192.168/16', '192.168.1.1'],
29
+ ['link-local + metadata', '169.254.169.254'],
30
+ ['0.0.0.0/8', '0.0.0.0'],
31
+ ['CGNAT 100.64/10', '100.64.0.1'],
32
+ ['TEST-NET-1', '192.0.2.1'],
33
+ ['TEST-NET-2', '198.51.100.7'],
34
+ ['TEST-NET-3', '203.0.113.7'],
35
+ ['multicast', '224.0.0.1'],
36
+ ['reserved 240/4', '240.0.0.1'],
37
+ ['decimal integer form of 127.0.0.1', '2130706433'],
38
+ ['hex form of 127.0.0.1', '0x7f000001'],
39
+ ])('blocks %s', (_label, host) => {
40
+ expect(isBlockedHost(host)).toBe(true);
41
+ });
42
+ it.each([
43
+ ['public DNS', '8.8.8.8'],
44
+ ['public', '93.184.216.34'],
45
+ ['172.15 (just below private)', '172.15.0.1'],
46
+ ['172.32 (just above private)', '172.32.0.1'],
47
+ ['198.51.99 (not TEST-NET-2)', '198.51.99.7'],
48
+ ])('allows %s', (_label, host) => {
49
+ expect(isBlockedHost(host)).toBe(false);
50
+ });
51
+ });
52
+ describe('isBlockedHost — literal IPv6 + hostnames', () => {
53
+ it.each([
54
+ ['v6 loopback', '::1'],
55
+ ['v6 loopback bracketed', '[::1]'],
56
+ ['v6 unspecified', '::'],
57
+ ['v6 link-local', 'fe80::1'],
58
+ ['v6 ULA', 'fd00::1'],
59
+ ['v6 ULA fc', 'fc00::1'],
60
+ ['v6 multicast', 'ff02::1'],
61
+ ['v4-mapped loopback', '::ffff:127.0.0.1'],
62
+ ['localhost', 'localhost'],
63
+ ['gcp metadata', 'metadata.google.internal'],
64
+ ])('blocks %s', (_label, host) => {
65
+ expect(isBlockedHost(host)).toBe(true);
66
+ });
67
+ it('allows a public IPv6 address', () => {
68
+ expect(isBlockedHost('2606:4700:4700::1111')).toBe(false);
69
+ });
70
+ it('does not classify a bare hostname synchronously', () => {
71
+ expect(isBlockedHost('rebind.evil.test')).toBe(false);
72
+ });
73
+ });
74
+ describe('isBlockedHostResolved — DNS-aware (closes the rebinding bypass)', () => {
75
+ it('blocks a public hostname that resolves to loopback', async () => {
76
+ expect(await isBlockedHostResolved('rebind.evil.test')).toBe(true);
77
+ });
78
+ it('blocks a public hostname that resolves to the cloud metadata IP', async () => {
79
+ expect(await isBlockedHostResolved('metadata.evil.test')).toBe(true);
80
+ });
81
+ it('blocks when ANY resolved address is internal', async () => {
82
+ expect(await isBlockedHostResolved('split.evil.test')).toBe(true);
83
+ });
84
+ it('allows a hostname that resolves only to public addresses', async () => {
85
+ expect(await isBlockedHostResolved('public.test')).toBe(false);
86
+ });
87
+ it('does not block an unresolvable host (tester surfaces its own error)', async () => {
88
+ expect(await isBlockedHostResolved('nope.invalid.test')).toBe(false);
89
+ });
90
+ });
91
+ describe('isBlockedUrl / isBlockedUrlResolved', () => {
92
+ it('blocks non-http(s) protocols', () => {
93
+ expect(isBlockedUrl('file:///etc/passwd')).toBe(true);
94
+ expect(isBlockedUrl('gopher://x')).toBe(true);
95
+ });
96
+ it('blocks a malformed URL', () => {
97
+ expect(isBlockedUrl('not a url')).toBe(true);
98
+ });
99
+ it('resolves the URL host via DNS', async () => {
100
+ expect(await isBlockedUrlResolved('http://rebind.evil.test/path')).toBe(true);
101
+ expect(await isBlockedUrlResolved('https://public.test/path')).toBe(false);
102
+ });
103
+ });
104
+ //# sourceMappingURL=ssrf.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssrf.test.js","sourceRoot":"","sources":["../src/ssrf.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ import type { z } from 'zod';
2
+ import type { ZodType, ZodTypeDef } from 'zod';
3
+ export declare const testerEnum: z.ZodEnum<["postgres", "mysql", "redis", "ssh", "http"]>;
4
+ export type TesterType = z.infer<typeof testerEnum>;
5
+ /**
6
+ * The plaintext value resolved from an alias, plus optional auxiliary
7
+ * fields some testers need (e.g., username for SSH keypair auth).
8
+ *
9
+ * The runner constructs this once, hands it to the tester, and never
10
+ * persists or logs the value.
11
+ */
12
+ export interface ResolvedSecret {
13
+ readonly alias: string;
14
+ readonly value: string;
15
+ readonly fields?: Readonly<Record<string, string>>;
16
+ }
17
+ /**
18
+ * Per-tester target shape (database host/port/db, ssh host/user, http
19
+ * url/auth-style, etc.). Each tester ships its own zod schema.
20
+ */
21
+ export type TesterTarget = Readonly<Record<string, unknown>>;
22
+ export interface TestResult {
23
+ readonly ok: boolean;
24
+ readonly latency_ms: number;
25
+ /** Sanitized — must NEVER carry the resolved secret value. */
26
+ readonly error?: string;
27
+ /** Optional structured info (server version, identity hash, etc.). */
28
+ readonly info?: Readonly<Record<string, unknown>>;
29
+ }
30
+ export interface Tester<T extends TesterTarget = TesterTarget> {
31
+ readonly type: TesterType;
32
+ /**
33
+ * The schema's *input* type stays unknown so callers (the CLI's
34
+ * --target k=v parser, the MCP tool) can hand untyped objects in;
35
+ * zod's defaults / coercions turn them into the strongly-typed
36
+ * output the tester sees.
37
+ */
38
+ readonly schema: ZodType<T, ZodTypeDef, unknown>;
39
+ test(secret: ResolvedSecret, target: T): Promise<TestResult>;
40
+ }
41
+ export declare const DEFAULT_TIMEOUT_MS = 5000;
42
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AAE/C,eAAO,MAAM,UAAU,0DAAwB,CAAC;AAChD,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACpD;AAED;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAE7D,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,8DAA8D;IAC9D,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,sEAAsE;IACtE,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACnD;AAED,MAAM,WAAW,MAAM,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY;IAC3D,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;IACjD,IAAI,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CAC9D;AAED,eAAO,MAAM,kBAAkB,OAAQ,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,4 @@
1
+ import { validation } from '@keynv/core';
2
+ export const testerEnum = validation.testerName;
3
+ export const DEFAULT_TIMEOUT_MS = 5_000;
4
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAIzC,MAAM,CAAC,MAAM,UAAU,GAAG,UAAU,CAAC,UAAU,CAAC;AA2ChD,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,CAAC"}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@keynv/testers",
3
+ "version": "0.1.0-rc.23",
4
+ "description": "keynv pluggable connection testers (postgres, mysql, redis, ssh, http, cloud IAM).",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "engines": {
19
+ "node": ">=20.0.0"
20
+ },
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/keynv-labs/keynv.git",
27
+ "directory": "packages/testers"
28
+ },
29
+ "homepage": "https://github.com/keynv-labs/keynv#readme",
30
+ "bugs": {
31
+ "url": "https://github.com/keynv-labs/keynv/issues"
32
+ },
33
+ "dependencies": {
34
+ "ioredis": "^5.4.1",
35
+ "mysql2": "^3.11.5",
36
+ "pg": "^8.13.1",
37
+ "ssh2": "^1.16.0",
38
+ "zod": "^3.23.8",
39
+ "@keynv/core": "0.1.0-rc.23",
40
+ "@keynv/redactor": "0.1.0-rc.23"
41
+ },
42
+ "devDependencies": {
43
+ "@types/pg": "^8.11.10",
44
+ "@types/ssh2": "^1.15.1"
45
+ },
46
+ "scripts": {
47
+ "build": "tsc -p tsconfig.json",
48
+ "typecheck": "tsc -p tsconfig.json --noEmit",
49
+ "test": "vitest run",
50
+ "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\""
51
+ }
52
+ }