@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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 keynv-labs and contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/http.d.ts ADDED
@@ -0,0 +1,36 @@
1
+ import { z } from 'zod';
2
+ import type { Tester } from './types.js';
3
+ declare const Target: z.ZodObject<{
4
+ url: z.ZodString;
5
+ method: z.ZodDefault<z.ZodEnum<["GET", "POST", "HEAD"]>>;
6
+ /**
7
+ * - 'basic' : secret.value is the password; target.user is the username.
8
+ * - 'bearer' : secret.value is the token.
9
+ * - 'header' : secret.value is the value for header `target.header_name`.
10
+ */
11
+ auth: z.ZodEnum<["basic", "bearer", "header"]>;
12
+ user: z.ZodOptional<z.ZodString>;
13
+ header_name: z.ZodOptional<z.ZodString>;
14
+ expect_status_min: z.ZodDefault<z.ZodNumber>;
15
+ expect_status_max: z.ZodDefault<z.ZodNumber>;
16
+ }, "strip", z.ZodTypeAny, {
17
+ url: string;
18
+ method: "GET" | "POST" | "HEAD";
19
+ auth: "basic" | "bearer" | "header";
20
+ expect_status_min: number;
21
+ expect_status_max: number;
22
+ user?: string | undefined;
23
+ header_name?: string | undefined;
24
+ }, {
25
+ url: string;
26
+ auth: "basic" | "bearer" | "header";
27
+ method?: "GET" | "POST" | "HEAD" | undefined;
28
+ user?: string | undefined;
29
+ header_name?: string | undefined;
30
+ expect_status_min?: number | undefined;
31
+ expect_status_max?: number | undefined;
32
+ }>;
33
+ type HttpTarget = z.infer<typeof Target>;
34
+ export declare const httpTester: Tester<HttpTarget>;
35
+ export {};
36
+ //# sourceMappingURL=http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAA8B,MAAM,EAAE,MAAM,YAAY,CAAC;AAErE,QAAA,MAAM,MAAM;;;IAGV;;;;OAIG;;;;;;;;;;;;;;;;;;;;;;EAMH,CAAC;AAEH,KAAK,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,MAAM,CAAC,CAAC;AAEzC,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,UAAU,CA2DzC,CAAC"}
package/dist/http.js ADDED
@@ -0,0 +1,80 @@
1
+ import { z } from 'zod';
2
+ import { isBlockedUrlResolved } from './ssrf.js';
3
+ const Target = z.object({
4
+ url: z.string().url(),
5
+ method: z.enum(['GET', 'POST', 'HEAD']).default('GET'),
6
+ /**
7
+ * - 'basic' : secret.value is the password; target.user is the username.
8
+ * - 'bearer' : secret.value is the token.
9
+ * - 'header' : secret.value is the value for header `target.header_name`.
10
+ */
11
+ auth: z.enum(['basic', 'bearer', 'header']),
12
+ user: z.string().optional(),
13
+ header_name: z.string().min(1).optional(),
14
+ expect_status_min: z.coerce.number().int().min(100).max(599).default(200),
15
+ expect_status_max: z.coerce.number().int().min(100).max(599).default(299),
16
+ });
17
+ export const httpTester = {
18
+ type: 'http',
19
+ schema: Target,
20
+ async test(secret, target) {
21
+ if (await isBlockedUrlResolved(target.url)) {
22
+ return {
23
+ ok: false,
24
+ latency_ms: 0,
25
+ error: 'Target URL is blocked (private/internal IP or metadata endpoint).',
26
+ };
27
+ }
28
+ const start = Date.now();
29
+ const headers = {};
30
+ switch (target.auth) {
31
+ case 'basic': {
32
+ if (!target.user)
33
+ return failBadTarget('basic auth requires target.user', start);
34
+ const encoded = Buffer.from(`${target.user}:${secret.value}`).toString('base64');
35
+ headers['authorization'] = `Basic ${encoded}`;
36
+ break;
37
+ }
38
+ case 'bearer':
39
+ headers['authorization'] = `Bearer ${secret.value}`;
40
+ break;
41
+ case 'header': {
42
+ if (!target.header_name) {
43
+ return failBadTarget('header auth requires target.header_name', start);
44
+ }
45
+ headers[target.header_name.toLowerCase()] = secret.value;
46
+ break;
47
+ }
48
+ }
49
+ try {
50
+ const res = await fetch(target.url, {
51
+ method: target.method,
52
+ headers,
53
+ signal: AbortSignal.timeout(5000),
54
+ // Do NOT follow redirects: the SSRF pre-check only validated the
55
+ // original URL, and following a 3xx could land on an internal
56
+ // address and leak the auth header cross-origin. A redirect is
57
+ // treated as a (non-2xx) result.
58
+ redirect: 'manual',
59
+ });
60
+ const ok = res.status >= target.expect_status_min && res.status <= target.expect_status_max;
61
+ return {
62
+ ok,
63
+ latency_ms: Date.now() - start,
64
+ info: { status: res.status },
65
+ ...(ok ? {} : { error: `HTTP ${res.status}` }),
66
+ };
67
+ }
68
+ catch (err) {
69
+ return {
70
+ ok: false,
71
+ latency_ms: Date.now() - start,
72
+ error: err instanceof Error ? err.message : String(err),
73
+ };
74
+ }
75
+ },
76
+ };
77
+ function failBadTarget(message, start) {
78
+ return { ok: false, latency_ms: Date.now() - start, error: message };
79
+ }
80
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=http.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.test.d.ts","sourceRoot":"","sources":["../src/http.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,71 @@
1
+ import { createServer } from 'node:http';
2
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
3
+ process.env['KEYNV_ALLOW_LOOPBACK_TESTERS'] = '1';
4
+ import { httpTester } from './http.js';
5
+ let server;
6
+ let url;
7
+ beforeEach(async () => {
8
+ server = createServer((req, res) => {
9
+ const auth = req.headers.authorization ?? '';
10
+ const apiKey = req.headers['x-api-key'] ?? '';
11
+ if (auth === 'Bearer good-token' || apiKey === 'good-key') {
12
+ res.statusCode = 200;
13
+ res.end('ok');
14
+ return;
15
+ }
16
+ if (auth === `Basic ${Buffer.from('alice:good-pass').toString('base64')}`) {
17
+ res.statusCode = 200;
18
+ res.end('ok');
19
+ return;
20
+ }
21
+ res.statusCode = 401;
22
+ res.end('unauthorized');
23
+ });
24
+ await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
25
+ const addr = server.address();
26
+ if (typeof addr === 'object' && addr)
27
+ url = `http://127.0.0.1:${addr.port}/`;
28
+ });
29
+ afterEach(async () => {
30
+ await new Promise((resolve) => server.close(() => resolve()));
31
+ });
32
+ describe('httpTester', () => {
33
+ it('returns ok for a valid bearer token (200 OK)', async () => {
34
+ const r = await httpTester.test({ alias: '@x.dev.api', value: 'good-token' }, { url, method: 'GET', auth: 'bearer', expect_status_min: 200, expect_status_max: 299 });
35
+ expect(r.ok).toBe(true);
36
+ expect(r.info?.['status']).toBe(200);
37
+ });
38
+ it('returns fail for a wrong bearer token (401)', async () => {
39
+ const r = await httpTester.test({ alias: '@x.dev.api', value: 'wrong-token' }, { url, method: 'GET', auth: 'bearer', expect_status_min: 200, expect_status_max: 299 });
40
+ expect(r.ok).toBe(false);
41
+ expect(r.error).toMatch(/401/);
42
+ });
43
+ it('handles basic auth', async () => {
44
+ const r = await httpTester.test({ alias: '@x.dev.api', value: 'good-pass' }, {
45
+ url,
46
+ method: 'GET',
47
+ auth: 'basic',
48
+ user: 'alice',
49
+ expect_status_min: 200,
50
+ expect_status_max: 299,
51
+ });
52
+ expect(r.ok).toBe(true);
53
+ });
54
+ it('handles custom-header auth', async () => {
55
+ const r = await httpTester.test({ alias: '@x.dev.api', value: 'good-key' }, {
56
+ url,
57
+ method: 'GET',
58
+ auth: 'header',
59
+ header_name: 'x-api-key',
60
+ expect_status_min: 200,
61
+ expect_status_max: 299,
62
+ });
63
+ expect(r.ok).toBe(true);
64
+ });
65
+ it('rejects basic auth without user', async () => {
66
+ const r = await httpTester.test({ alias: '@x.dev.api', value: 'p' }, { url, method: 'GET', auth: 'basic', expect_status_min: 200, expect_status_max: 299 });
67
+ expect(r.ok).toBe(false);
68
+ expect(r.error).toMatch(/target\.user|blocked/i);
69
+ });
70
+ });
71
+ //# sourceMappingURL=http.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.test.js","sourceRoot":"","sources":["../src/http.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import { httpTester } from './http.js';
2
+ import { mysqlTester } from './mysql.js';
3
+ import { postgresTester } from './postgres.js';
4
+ import { redisTester } from './redis.js';
5
+ import { sshTester } from './ssh.js';
6
+ import type { Tester, TesterType } from './types.js';
7
+ export type { ResolvedSecret, Tester, TesterTarget, TesterType, TestResult, } from './types.js';
8
+ export { DEFAULT_TIMEOUT_MS, testerEnum } from './types.js';
9
+ export { runTest, type RunArgs } from './run.js';
10
+ export { sanitizeResult } from './sanitize.js';
11
+ export declare const testers: ReadonlyArray<Tester>;
12
+ export declare function findTester(type: TesterType): Tester | null;
13
+ export { postgresTester, mysqlTester, redisTester, sshTester, httpTester };
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAErD,YAAY,EACV,cAAc,EACd,MAAM,EACN,YAAY,EACZ,UAAU,EACV,UAAU,GACX,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,UAAU,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,eAAO,MAAM,OAAO,EAAE,aAAa,CAAC,MAAM,CAMzC,CAAC;AAEF,wBAAgB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CAE1D;AAED,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,20 @@
1
+ import { httpTester } from './http.js';
2
+ import { mysqlTester } from './mysql.js';
3
+ import { postgresTester } from './postgres.js';
4
+ import { redisTester } from './redis.js';
5
+ import { sshTester } from './ssh.js';
6
+ export { DEFAULT_TIMEOUT_MS, testerEnum } from './types.js';
7
+ export { runTest } from './run.js';
8
+ export { sanitizeResult } from './sanitize.js';
9
+ export const testers = [
10
+ postgresTester,
11
+ mysqlTester,
12
+ redisTester,
13
+ sshTester,
14
+ httpTester,
15
+ ];
16
+ export function findTester(type) {
17
+ return testers.find((t) => t.type === type) ?? null;
18
+ }
19
+ export { postgresTester, mysqlTester, redisTester, sshTester, httpTester };
20
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAUrC,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAgB,MAAM,UAAU,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,MAAM,CAAC,MAAM,OAAO,GAA0B;IAC5C,cAAwB;IACxB,WAAqB;IACrB,WAAqB;IACrB,SAAmB;IACnB,UAAoB;CACrB,CAAC;AAEF,MAAM,UAAU,UAAU,CAAC,IAAgB;IACzC,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,IAAI,CAAC;AACtD,CAAC;AAED,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC"}
@@ -0,0 +1,25 @@
1
+ import { z } from 'zod';
2
+ import type { Tester } from './types.js';
3
+ declare const Target: z.ZodObject<{
4
+ host: z.ZodString;
5
+ port: z.ZodDefault<z.ZodNumber>;
6
+ database: z.ZodOptional<z.ZodString>;
7
+ user: z.ZodString;
8
+ ssl: z.ZodOptional<z.ZodBoolean>;
9
+ }, "strip", z.ZodTypeAny, {
10
+ user: string;
11
+ host: string;
12
+ port: number;
13
+ database?: string | undefined;
14
+ ssl?: boolean | undefined;
15
+ }, {
16
+ user: string;
17
+ host: string;
18
+ port?: number | undefined;
19
+ database?: string | undefined;
20
+ ssl?: boolean | undefined;
21
+ }>;
22
+ type MysqlTarget = z.infer<typeof Target>;
23
+ export declare const mysqlTester: Tester<MysqlTarget>;
24
+ export {};
25
+ //# sourceMappingURL=mysql.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mysql.d.ts","sourceRoot":"","sources":["../src/mysql.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAA8B,MAAM,EAAE,MAAM,YAAY,CAAC;AAErE,QAAA,MAAM,MAAM;;;;;;;;;;;;;;;;;;EAMV,CAAC;AAEH,KAAK,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,MAAM,CAAC,CAAC;AAE1C,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,WAAW,CA+C3C,CAAC"}
package/dist/mysql.js ADDED
@@ -0,0 +1,60 @@
1
+ import { z } from 'zod';
2
+ import { isBlockedHostResolved } from './ssrf.js';
3
+ const Target = z.object({
4
+ host: z.string().min(1),
5
+ port: z.coerce.number().int().min(1).max(65535).default(3306),
6
+ database: z.string().min(1).optional(),
7
+ user: z.string().min(1),
8
+ ssl: z.coerce.boolean().optional(),
9
+ });
10
+ export const mysqlTester = {
11
+ type: 'mysql',
12
+ schema: Target,
13
+ async test(secret, target) {
14
+ if (await isBlockedHostResolved(target.host)) {
15
+ return {
16
+ ok: false,
17
+ latency_ms: 0,
18
+ error: 'Target host is blocked (private/internal IP or metadata endpoint).',
19
+ };
20
+ }
21
+ const start = Date.now();
22
+ const mysql = await import('mysql2/promise');
23
+ const conn = await mysql
24
+ .createConnection({
25
+ host: target.host,
26
+ port: target.port,
27
+ ...(target.database ? { database: target.database } : {}),
28
+ user: target.user,
29
+ password: secret.value,
30
+ ...(target.ssl !== false ? { ssl: {} } : {}),
31
+ connectTimeout: 5000,
32
+ })
33
+ .catch((err) => {
34
+ return {
35
+ __error__: err instanceof Error ? err.message : String(err),
36
+ };
37
+ });
38
+ if ('__error__' in conn) {
39
+ return { ok: false, latency_ms: Date.now() - start, error: conn.__error__ };
40
+ }
41
+ try {
42
+ const [rows] = await conn.query('SELECT VERSION() AS v');
43
+ const v = rows[0]?.v;
44
+ return { ok: true, latency_ms: Date.now() - start, info: { server_version: v } };
45
+ }
46
+ catch (err) {
47
+ return {
48
+ ok: false,
49
+ latency_ms: Date.now() - start,
50
+ error: err instanceof Error ? err.message : String(err),
51
+ };
52
+ }
53
+ finally {
54
+ await conn.end().catch(() => {
55
+ /* ignore */
56
+ });
57
+ }
58
+ },
59
+ };
60
+ //# sourceMappingURL=mysql.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mysql.js","sourceRoot":"","sources":["../src/mysql.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import { z } from 'zod';
2
+ import type { Tester } from './types.js';
3
+ declare const Target: z.ZodObject<{
4
+ host: z.ZodString;
5
+ port: z.ZodDefault<z.ZodNumber>;
6
+ database: z.ZodString;
7
+ user: z.ZodString;
8
+ ssl: z.ZodOptional<z.ZodBoolean>;
9
+ }, "strip", z.ZodTypeAny, {
10
+ user: string;
11
+ host: string;
12
+ port: number;
13
+ database: string;
14
+ ssl?: boolean | undefined;
15
+ }, {
16
+ user: string;
17
+ host: string;
18
+ database: string;
19
+ port?: number | undefined;
20
+ ssl?: boolean | undefined;
21
+ }>;
22
+ type PostgresTarget = z.infer<typeof Target>;
23
+ export declare const postgresTester: Tester<PostgresTarget>;
24
+ export {};
25
+ //# sourceMappingURL=postgres.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../src/postgres.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAA8B,MAAM,EAAE,MAAM,YAAY,CAAC;AAErE,QAAA,MAAM,MAAM;;;;;;;;;;;;;;;;;;EAMV,CAAC;AAEH,KAAK,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,MAAM,CAAC,CAAC;AAE7C,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,cAAc,CA8CjD,CAAC"}
@@ -0,0 +1,59 @@
1
+ import { z } from 'zod';
2
+ import { isBlockedHostResolved } from './ssrf.js';
3
+ const Target = z.object({
4
+ host: z.string().min(1),
5
+ port: z.coerce.number().int().min(1).max(65535).default(5432),
6
+ database: z.string().min(1),
7
+ user: z.string().min(1),
8
+ ssl: z.coerce.boolean().optional(),
9
+ });
10
+ export const postgresTester = {
11
+ type: 'postgres',
12
+ schema: Target,
13
+ async test(secret, target) {
14
+ if (await isBlockedHostResolved(target.host)) {
15
+ return {
16
+ ok: false,
17
+ latency_ms: 0,
18
+ error: 'Target host is blocked (private/internal IP or metadata endpoint).',
19
+ };
20
+ }
21
+ const start = Date.now();
22
+ // Lazy-load to keep the dep optional for environments that don't
23
+ // exercise this tester (e.g., running only the http tester).
24
+ const { Client } = await import('pg');
25
+ const client = new Client({
26
+ host: target.host,
27
+ port: target.port,
28
+ database: target.database,
29
+ user: target.user,
30
+ password: secret.value,
31
+ ssl: target.ssl ?? true,
32
+ connectionTimeoutMillis: 5000,
33
+ statement_timeout: 5000,
34
+ });
35
+ try {
36
+ await client.connect();
37
+ const r = await client.query('SELECT 1 AS ok, version() AS v');
38
+ const ok = r.rows[0]?.ok === 1;
39
+ return {
40
+ ok,
41
+ latency_ms: Date.now() - start,
42
+ ...(ok ? { info: { server_version: r.rows[0]?.v } } : {}),
43
+ };
44
+ }
45
+ catch (err) {
46
+ return {
47
+ ok: false,
48
+ latency_ms: Date.now() - start,
49
+ error: err instanceof Error ? err.message : String(err),
50
+ };
51
+ }
52
+ finally {
53
+ await client.end().catch(() => {
54
+ /* ignore */
55
+ });
56
+ }
57
+ },
58
+ };
59
+ //# sourceMappingURL=postgres.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres.js","sourceRoot":"","sources":["../src/postgres.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAGlD,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;IACtB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;IAC7D,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3B,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;CACnC,CAAC,CAAC;AAIH,MAAM,CAAC,MAAM,cAAc,GAA2B;IACpD,IAAI,EAAE,UAAU;IAChB,MAAM,EAAE,MAAM;IACd,KAAK,CAAC,IAAI,CAAC,MAAsB,EAAE,MAAsB;QACvD,IAAI,MAAM,qBAAqB,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7C,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,UAAU,EAAE,CAAC;gBACb,KAAK,EAAE,oEAAoE;aAC5E,CAAC;QACJ,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACzB,iEAAiE;QACjE,6DAA6D;QAC7D,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;QACtC,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC;YACxB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,QAAQ,EAAE,MAAM,CAAC,KAAK;YACtB,GAAG,EAAE,MAAM,CAAC,GAAG,IAAI,IAAI;YACvB,uBAAuB,EAAE,IAAI;YAC7B,iBAAiB,EAAE,IAAI;SACxB,CAAC,CAAC;QACH,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;YACvB,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,KAAK,CAA4B,gCAAgC,CAAC,CAAC;YAC1F,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;YAC/B,OAAO;gBACL,EAAE;gBACF,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;gBAC9B,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,cAAc,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC1D,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;gBAC9B,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;aACxD,CAAC;QACJ,CAAC;gBAAS,CAAC;YACT,MAAM,MAAM,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE;gBAC5B,YAAY;YACd,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;CACF,CAAC"}
@@ -0,0 +1,25 @@
1
+ import { z } from 'zod';
2
+ import type { Tester } from './types.js';
3
+ declare const Target: z.ZodObject<{
4
+ host: z.ZodString;
5
+ port: z.ZodDefault<z.ZodNumber>;
6
+ db: z.ZodDefault<z.ZodNumber>;
7
+ user: z.ZodOptional<z.ZodString>;
8
+ tls: z.ZodOptional<z.ZodBoolean>;
9
+ }, "strip", z.ZodTypeAny, {
10
+ host: string;
11
+ port: number;
12
+ db: number;
13
+ user?: string | undefined;
14
+ tls?: boolean | undefined;
15
+ }, {
16
+ host: string;
17
+ user?: string | undefined;
18
+ port?: number | undefined;
19
+ db?: number | undefined;
20
+ tls?: boolean | undefined;
21
+ }>;
22
+ type RedisTarget = z.infer<typeof Target>;
23
+ export declare const redisTester: Tester<RedisTarget>;
24
+ export {};
25
+ //# sourceMappingURL=redis.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../src/redis.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAA8B,MAAM,EAAE,MAAM,YAAY,CAAC;AAErE,QAAA,MAAM,MAAM;;;;;;;;;;;;;;;;;;EAMV,CAAC;AAEH,KAAK,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,MAAM,CAAC,CAAC;AAE1C,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,WAAW,CA2C3C,CAAC"}
package/dist/redis.js ADDED
@@ -0,0 +1,57 @@
1
+ import { z } from 'zod';
2
+ import { isBlockedHostResolved } from './ssrf.js';
3
+ const Target = z.object({
4
+ host: z.string().min(1),
5
+ port: z.coerce.number().int().min(1).max(65535).default(6379),
6
+ db: z.coerce.number().int().min(0).max(15).default(0),
7
+ user: z.string().optional(),
8
+ tls: z.coerce.boolean().optional(),
9
+ });
10
+ export const redisTester = {
11
+ type: 'redis',
12
+ schema: Target,
13
+ async test(secret, target) {
14
+ if (await isBlockedHostResolved(target.host)) {
15
+ return {
16
+ ok: false,
17
+ latency_ms: 0,
18
+ error: 'Target host is blocked (private/internal IP or metadata endpoint).',
19
+ };
20
+ }
21
+ const start = Date.now();
22
+ const { default: Redis } = await import('ioredis');
23
+ const client = new Redis({
24
+ host: target.host,
25
+ port: target.port,
26
+ db: target.db,
27
+ ...(target.user ? { username: target.user } : {}),
28
+ password: secret.value,
29
+ ...(target.tls ? { tls: {} } : {}),
30
+ lazyConnect: true,
31
+ maxRetriesPerRequest: 0,
32
+ connectTimeout: 5000,
33
+ });
34
+ try {
35
+ await client.connect();
36
+ const pong = await client.ping();
37
+ const ok = pong === 'PONG';
38
+ return { ok, latency_ms: Date.now() - start };
39
+ }
40
+ catch (err) {
41
+ return {
42
+ ok: false,
43
+ latency_ms: Date.now() - start,
44
+ error: err instanceof Error ? err.message : String(err),
45
+ };
46
+ }
47
+ finally {
48
+ try {
49
+ client.disconnect();
50
+ }
51
+ catch {
52
+ /* ignore */
53
+ }
54
+ }
55
+ },
56
+ };
57
+ //# sourceMappingURL=redis.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis.js","sourceRoot":"","sources":["../src/redis.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAGlD,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;IACtB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;IAC7D,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IACrD,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC3B,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;CACnC,CAAC,CAAC;AAIH,MAAM,CAAC,MAAM,WAAW,GAAwB;IAC9C,IAAI,EAAE,OAAO;IACb,MAAM,EAAE,MAAM;IACd,KAAK,CAAC,IAAI,CAAC,MAAsB,EAAE,MAAmB;QACpD,IAAI,MAAM,qBAAqB,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7C,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,UAAU,EAAE,CAAC;gBACb,KAAK,EAAE,oEAAoE;aAC5E,CAAC;QACJ,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACzB,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC;QACnD,MAAM,MAAM,GAAG,IAAI,KAAK,CAAC;YACvB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,EAAE,EAAE,MAAM,CAAC,EAAE;YACb,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACjD,QAAQ,EAAE,MAAM,CAAC,KAAK;YACtB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAClC,WAAW,EAAE,IAAI;YACjB,oBAAoB,EAAE,CAAC;YACvB,cAAc,EAAE,IAAI;SACrB,CAAC,CAAC;QACH,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;YACvB,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;YACjC,MAAM,EAAE,GAAG,IAAI,KAAK,MAAM,CAAC;YAC3B,OAAO,EAAE,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC;QAChD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;gBAC9B,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;aACxD,CAAC;QACJ,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC;gBACH,MAAM,CAAC,UAAU,EAAE,CAAC;YACtB,CAAC;YAAC,MAAM,CAAC;gBACP,YAAY;YACd,CAAC;QACH,CAAC;IACH,CAAC;CACF,CAAC"}
package/dist/run.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ import type { ResolvedSecret, TestResult, Tester, TesterTarget } from './types.js';
2
+ export interface RunArgs {
3
+ tester: Tester;
4
+ secret: ResolvedSecret;
5
+ target: TesterTarget;
6
+ timeoutMs?: number;
7
+ }
8
+ /**
9
+ * Validates the target against the tester's schema, invokes test()
10
+ * with a hard timeout, and runs the result through the sanitizer.
11
+ *
12
+ * The runner never touches network state itself — it's a thin
13
+ * orchestration layer so testers stay focused on protocol mechanics.
14
+ */
15
+ export declare function runTest(args: RunArgs): Promise<TestResult>;
16
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAGnF,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,YAAY,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAmBD;;;;;;GAMG;AACH,wBAAsB,OAAO,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC,CAsBhE"}
package/dist/run.js ADDED
@@ -0,0 +1,50 @@
1
+ import { sanitizeResult } from './sanitize.js';
2
+ import { DEFAULT_TIMEOUT_MS } from './types.js';
3
+ function withTimeout(promise, ms) {
4
+ return new Promise((resolve, reject) => {
5
+ const t = setTimeout(() => reject(new Error(`tester timed out after ${ms}ms`)), ms);
6
+ t.unref();
7
+ promise.then((v) => {
8
+ clearTimeout(t);
9
+ resolve(v);
10
+ }, (err) => {
11
+ clearTimeout(t);
12
+ reject(err);
13
+ });
14
+ });
15
+ }
16
+ /**
17
+ * Validates the target against the tester's schema, invokes test()
18
+ * with a hard timeout, and runs the result through the sanitizer.
19
+ *
20
+ * The runner never touches network state itself — it's a thin
21
+ * orchestration layer so testers stay focused on protocol mechanics.
22
+ */
23
+ export async function runTest(args) {
24
+ const start = Date.now();
25
+ const parsed = args.tester.schema.safeParse(args.target);
26
+ if (!parsed.success) {
27
+ return {
28
+ ok: false,
29
+ latency_ms: Date.now() - start,
30
+ error: `invalid target: ${formatZodIssues(parsed.error)}`,
31
+ };
32
+ }
33
+ const timeoutMs = args.timeoutMs ?? DEFAULT_TIMEOUT_MS;
34
+ let result;
35
+ try {
36
+ result = await withTimeout(args.tester.test(args.secret, parsed.data), timeoutMs);
37
+ }
38
+ catch (err) {
39
+ result = {
40
+ ok: false,
41
+ latency_ms: Date.now() - start,
42
+ error: err instanceof Error ? err.message : String(err),
43
+ };
44
+ }
45
+ return sanitizeResult(result, args.secret);
46
+ }
47
+ function formatZodIssues(error) {
48
+ return error.issues.map((i) => `${i.path.join('.') || '<root>'}: ${i.message}`).join('; ');
49
+ }
50
+ //# sourceMappingURL=run.js.map