@olane/o-server 0.8.0 → 0.8.2

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 (40) hide show
  1. package/dist/src/interfaces/server-config.interface.d.ts +47 -1
  2. package/dist/src/interfaces/server-config.interface.d.ts.map +1 -1
  3. package/dist/src/middleware/error-handler.d.ts +9 -0
  4. package/dist/src/middleware/error-handler.d.ts.map +1 -1
  5. package/dist/src/middleware/error-handler.js +49 -3
  6. package/dist/src/middleware/jwt-auth.d.ts +54 -0
  7. package/dist/src/middleware/jwt-auth.d.ts.map +1 -0
  8. package/dist/src/middleware/jwt-auth.js +145 -0
  9. package/dist/src/o-server.d.ts.map +1 -1
  10. package/dist/src/o-server.js +53 -23
  11. package/dist/src/validation/address-validator.d.ts +13 -0
  12. package/dist/src/validation/address-validator.d.ts.map +1 -0
  13. package/dist/src/validation/address-validator.js +40 -0
  14. package/dist/src/validation/index.d.ts +17 -0
  15. package/dist/src/validation/index.d.ts.map +1 -0
  16. package/dist/src/validation/index.js +16 -0
  17. package/dist/src/validation/method-validator.d.ts +13 -0
  18. package/dist/src/validation/method-validator.d.ts.map +1 -0
  19. package/dist/src/validation/method-validator.js +36 -0
  20. package/dist/src/validation/params-sanitizer.d.ts +15 -0
  21. package/dist/src/validation/params-sanitizer.d.ts.map +1 -0
  22. package/dist/src/validation/params-sanitizer.js +51 -0
  23. package/dist/src/validation/request-validator.d.ts +53 -0
  24. package/dist/src/validation/request-validator.d.ts.map +1 -0
  25. package/dist/src/validation/request-validator.js +53 -0
  26. package/dist/src/validation/validation-error.d.ts +11 -0
  27. package/dist/src/validation/validation-error.d.ts.map +1 -0
  28. package/dist/src/validation/validation-error.js +12 -0
  29. package/dist/test/ai.spec.d.ts +0 -1
  30. package/dist/test/ai.spec.js +20 -13
  31. package/dist/test/error-security.spec.d.ts +2 -0
  32. package/dist/test/error-security.spec.d.ts.map +1 -0
  33. package/dist/test/error-security.spec.js +134 -0
  34. package/dist/test/input-validation.spec.d.ts +14 -0
  35. package/dist/test/input-validation.spec.d.ts.map +1 -0
  36. package/dist/test/input-validation.spec.js +487 -0
  37. package/dist/test/jwt-auth.spec.d.ts +2 -0
  38. package/dist/test/jwt-auth.spec.d.ts.map +1 -0
  39. package/dist/test/jwt-auth.spec.js +397 -0
  40. package/package.json +10 -4
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Recursively sanitizes parameters by removing dangerous properties
3
+ * that could lead to prototype pollution attacks
4
+ *
5
+ * Security features:
6
+ * - Removes __proto__, constructor, and prototype properties
7
+ * - Recursive traversal for nested objects
8
+ * - Array handling
9
+ * - Preserves safe nested structures
10
+ *
11
+ * @param params - The parameters to sanitize (can be any type)
12
+ * @returns Sanitized copy of the parameters
13
+ */
14
+ export declare function sanitizeParams(params: any): any;
15
+ //# sourceMappingURL=params-sanitizer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"params-sanitizer.d.ts","sourceRoot":"","sources":["../../../src/validation/params-sanitizer.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,GAAG,GAAG,GAAG,CAyC/C"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Dangerous property names that should be removed to prevent prototype pollution
3
+ */
4
+ const DANGEROUS_KEYS = ['__proto__', 'constructor', 'prototype'];
5
+ /**
6
+ * Recursively sanitizes parameters by removing dangerous properties
7
+ * that could lead to prototype pollution attacks
8
+ *
9
+ * Security features:
10
+ * - Removes __proto__, constructor, and prototype properties
11
+ * - Recursive traversal for nested objects
12
+ * - Array handling
13
+ * - Preserves safe nested structures
14
+ *
15
+ * @param params - The parameters to sanitize (can be any type)
16
+ * @returns Sanitized copy of the parameters
17
+ */
18
+ export function sanitizeParams(params) {
19
+ // Handle null and undefined
20
+ if (params === null || params === undefined) {
21
+ return params;
22
+ }
23
+ // Handle primitives (string, number, boolean)
24
+ if (typeof params !== 'object') {
25
+ return params;
26
+ }
27
+ // Handle arrays - recursively sanitize each element
28
+ if (Array.isArray(params)) {
29
+ return params.map((item) => sanitizeParams(item));
30
+ }
31
+ // Handle objects - create sanitized copy
32
+ const sanitized = {};
33
+ for (const key in params) {
34
+ // Skip if not own property
35
+ if (!Object.prototype.hasOwnProperty.call(params, key)) {
36
+ continue;
37
+ }
38
+ // Skip dangerous keys (case-sensitive check)
39
+ if (DANGEROUS_KEYS.includes(key)) {
40
+ continue;
41
+ }
42
+ // Also check case-insensitive variants to be extra safe
43
+ const lowerKey = key.toLowerCase();
44
+ if (DANGEROUS_KEYS.some((dk) => dk.toLowerCase() === lowerKey)) {
45
+ continue;
46
+ }
47
+ // Recursively sanitize the value
48
+ sanitized[key] = sanitizeParams(params[key]);
49
+ }
50
+ return sanitized;
51
+ }
@@ -0,0 +1,53 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Schema for POST /use endpoint
4
+ * Validates the main node.use() request format
5
+ */
6
+ export declare const useRequestSchema: z.ZodObject<{
7
+ address: z.ZodString;
8
+ method: z.ZodString;
9
+ params: z.ZodOptional<z.ZodAny>;
10
+ id: z.ZodOptional<z.ZodString>;
11
+ }, "strip", z.ZodTypeAny, {
12
+ address: string;
13
+ method: string;
14
+ params?: any;
15
+ id?: string | undefined;
16
+ }, {
17
+ address: string;
18
+ method: string;
19
+ params?: any;
20
+ id?: string | undefined;
21
+ }>;
22
+ /**
23
+ * Schema for POST /:address/:method endpoint
24
+ * This endpoint accepts any body as params
25
+ */
26
+ export declare const convenienceRequestSchema: z.ZodAny;
27
+ /**
28
+ * Schema for POST /use/stream endpoint
29
+ * Similar to useRequestSchema but without id
30
+ */
31
+ export declare const streamRequestSchema: z.ZodObject<{
32
+ address: z.ZodString;
33
+ method: z.ZodString;
34
+ params: z.ZodOptional<z.ZodAny>;
35
+ }, "strip", z.ZodTypeAny, {
36
+ address: string;
37
+ method: string;
38
+ params?: any;
39
+ }, {
40
+ address: string;
41
+ method: string;
42
+ params?: any;
43
+ }>;
44
+ /**
45
+ * Validates request data against a Zod schema
46
+ *
47
+ * @param data - The data to validate
48
+ * @param schema - The Zod schema to validate against
49
+ * @returns The validated and parsed data
50
+ * @throws {ValidationError} If validation fails
51
+ */
52
+ export declare function validateRequest<T>(data: unknown, schema: z.ZodSchema<T>): T;
53
+ //# sourceMappingURL=request-validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-validator.d.ts","sourceRoot":"","sources":["../../../src/validation/request-validator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;GAGG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;EAK3B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,wBAAwB,UAAU,CAAC;AAEhD;;;GAGG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;EAI9B,CAAC;AAEH;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAqB3E"}
@@ -0,0 +1,53 @@
1
+ import { z } from 'zod';
2
+ import { ValidationError } from './validation-error.js';
3
+ /**
4
+ * Schema for POST /use endpoint
5
+ * Validates the main node.use() request format
6
+ */
7
+ export const useRequestSchema = z.object({
8
+ address: z.string().min(1, 'address is required'),
9
+ method: z.string().min(1, 'method is required'),
10
+ params: z.any().optional(),
11
+ id: z.string().optional(),
12
+ });
13
+ /**
14
+ * Schema for POST /:address/:method endpoint
15
+ * This endpoint accepts any body as params
16
+ */
17
+ export const convenienceRequestSchema = z.any();
18
+ /**
19
+ * Schema for POST /use/stream endpoint
20
+ * Similar to useRequestSchema but without id
21
+ */
22
+ export const streamRequestSchema = z.object({
23
+ address: z.string().min(1, 'address is required'),
24
+ method: z.string().min(1, 'method is required'),
25
+ params: z.any().optional(),
26
+ });
27
+ /**
28
+ * Validates request data against a Zod schema
29
+ *
30
+ * @param data - The data to validate
31
+ * @param schema - The Zod schema to validate against
32
+ * @returns The validated and parsed data
33
+ * @throws {ValidationError} If validation fails
34
+ */
35
+ export function validateRequest(data, schema) {
36
+ try {
37
+ return schema.parse(data);
38
+ }
39
+ catch (error) {
40
+ if (error instanceof z.ZodError) {
41
+ // Format Zod errors into a readable message
42
+ const message = error.errors
43
+ .map((e) => {
44
+ const path = e.path.join('.');
45
+ return path ? `${path}: ${e.message}` : e.message;
46
+ })
47
+ .join(', ');
48
+ throw new ValidationError(`Invalid request: ${message}`, 'INVALID_PARAMS');
49
+ }
50
+ // Re-throw non-Zod errors
51
+ throw error;
52
+ }
53
+ }
@@ -0,0 +1,11 @@
1
+ import { OlaneError } from '../middleware/error-handler.js';
2
+ /**
3
+ * Custom error class for validation failures
4
+ * Extends Error and implements OlaneError interface for consistency
5
+ */
6
+ export declare class ValidationError extends Error implements OlaneError {
7
+ code: string;
8
+ status: number;
9
+ constructor(message: string, code: string);
10
+ }
11
+ //# sourceMappingURL=validation-error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation-error.d.ts","sourceRoot":"","sources":["../../../src/validation/validation-error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAE5D;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,KAAM,YAAW,UAAU;IAC9D,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;gBAEH,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;CAM1C"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Custom error class for validation failures
3
+ * Extends Error and implements OlaneError interface for consistency
4
+ */
5
+ export class ValidationError extends Error {
6
+ constructor(message, code) {
7
+ super(message);
8
+ this.name = 'ValidationError';
9
+ this.code = code;
10
+ this.status = 400; // All validation errors are 400 Bad Request
11
+ }
12
+ }
@@ -1,2 +1 @@
1
- export {};
2
1
  //# sourceMappingURL=ai.spec.d.ts.map
@@ -1,19 +1,26 @@
1
+ "use strict";
2
+ // Disabled: o-server should not depend on o-lane
3
+ // This test was likely copied from another package
4
+ /*
1
5
  import { NodeState, oAddress } from '@olane/o-core';
2
6
  import { oLaneTool } from '@olane/o-lane';
3
7
  import { expect } from 'chai';
8
+
4
9
  describe('in-process @memory', () => {
5
- it('should be able to start a single node with no leader', async () => {
6
- const node = new oLaneTool({
7
- address: new oAddress('o://test'),
8
- leader: null,
9
- parent: null,
10
- });
11
- await node.start();
12
- expect(node.state).to.equal(NodeState.RUNNING);
13
- const transports = node.transports;
14
- // expect(transports.length).to.equal(1);
15
- // expect(transports[0].toString()).to.contain('/memory');
16
- await node.stop();
17
- expect(node.state).to.equal(NodeState.STOPPED);
10
+ it('should be able to start a single node with no leader', async () => {
11
+ const node = new oLaneTool({
12
+ address: new oAddress('o://test'),
13
+ leader: null,
14
+ parent: null,
18
15
  });
16
+
17
+ await node.start();
18
+ expect(node.state).to.equal(NodeState.RUNNING);
19
+ const transports = node.transports;
20
+ // expect(transports.length).to.equal(1);
21
+ // expect(transports[0].toString()).to.contain('/memory');
22
+ await node.stop();
23
+ expect(node.state).to.equal(NodeState.STOPPED);
24
+ });
19
25
  });
26
+ */
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=error-security.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-security.spec.d.ts","sourceRoot":"","sources":["../../test/error-security.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,134 @@
1
+ import { sanitizeErrorMessage } from '../src/middleware/error-handler.js';
2
+ import { expect } from 'aegir/chai';
3
+ // Test the sanitizeErrorMessage function directly
4
+ // This avoids the complexity of full end-to-end tests with oLaneTool
5
+ describe('Error Sanitization', () => {
6
+ let originalNodeEnv;
7
+ beforeEach(() => {
8
+ originalNodeEnv = process.env.NODE_ENV;
9
+ });
10
+ afterEach(() => {
11
+ process.env.NODE_ENV = originalNodeEnv;
12
+ });
13
+ describe('Production Mode', () => {
14
+ beforeEach(() => {
15
+ process.env.NODE_ENV = 'production';
16
+ });
17
+ it('should use generic message for NODE_NOT_FOUND in production', () => {
18
+ const result = sanitizeErrorMessage('NODE_NOT_FOUND', 'Detailed error: node at o://internal/secret not found');
19
+ expect(result).to.equal('The requested resource was not found');
20
+ expect(result).to.not.contain('internal');
21
+ expect(result).to.not.contain('secret');
22
+ });
23
+ it('should use generic message for TOOL_NOT_FOUND in production', () => {
24
+ const result = sanitizeErrorMessage('TOOL_NOT_FOUND', 'Tool implementation at /var/app/tools/secret-tool.js not found');
25
+ expect(result).to.equal('The requested tool was not found');
26
+ expect(result).to.not.contain('/var/app');
27
+ expect(result).to.not.contain('secret-tool');
28
+ });
29
+ it('should use generic message for INVALID_PARAMS in production', () => {
30
+ const result = sanitizeErrorMessage('INVALID_PARAMS', 'Invalid param: user_id must be UUID, got admin-password-123');
31
+ expect(result).to.equal('Invalid parameters provided');
32
+ expect(result).to.not.contain('admin-password');
33
+ expect(result).to.not.contain('UUID');
34
+ });
35
+ it('should use generic message for TIMEOUT in production', () => {
36
+ const result = sanitizeErrorMessage('TIMEOUT', 'Timeout connecting to database at 192.168.1.100:5432');
37
+ expect(result).to.equal('The request timed out');
38
+ expect(result).to.not.contain('192.168');
39
+ expect(result).to.not.contain('5432');
40
+ });
41
+ it('should use generic message for EXECUTION_ERROR in production', () => {
42
+ const result = sanitizeErrorMessage('EXECUTION_ERROR', 'Failed to execute: API key "sk-secret-123" is invalid');
43
+ expect(result).to.equal('An error occurred while processing your request');
44
+ expect(result).to.not.contain('API key');
45
+ expect(result).to.not.contain('sk-secret');
46
+ });
47
+ it('should use generic message for unknown error codes in production', () => {
48
+ const result = sanitizeErrorMessage('CUSTOM_ERROR', 'Custom error with sensitive data: password=secret123');
49
+ expect(result).to.equal('An internal error occurred');
50
+ expect(result).to.not.contain('password');
51
+ expect(result).to.not.contain('secret');
52
+ });
53
+ });
54
+ describe('Development Mode', () => {
55
+ beforeEach(() => {
56
+ process.env.NODE_ENV = 'development';
57
+ });
58
+ it('should preserve original message for NODE_NOT_FOUND in development', () => {
59
+ const originalMessage = 'Detailed error: node at o://internal/debug not found';
60
+ const result = sanitizeErrorMessage('NODE_NOT_FOUND', originalMessage);
61
+ expect(result).to.equal(originalMessage);
62
+ expect(result).to.contain('internal');
63
+ expect(result).to.contain('debug');
64
+ });
65
+ it('should preserve original message for TOOL_NOT_FOUND in development', () => {
66
+ const originalMessage = 'Tool implementation at /var/app/tools/test-tool.js not found';
67
+ const result = sanitizeErrorMessage('TOOL_NOT_FOUND', originalMessage);
68
+ expect(result).to.equal(originalMessage);
69
+ expect(result).to.contain('/var/app');
70
+ expect(result).to.contain('test-tool');
71
+ });
72
+ it('should preserve original message for INVALID_PARAMS in development', () => {
73
+ const originalMessage = 'Invalid param: user_id must be UUID, got test-123';
74
+ const result = sanitizeErrorMessage('INVALID_PARAMS', originalMessage);
75
+ expect(result).to.equal(originalMessage);
76
+ expect(result).to.contain('UUID');
77
+ expect(result).to.contain('test-123');
78
+ });
79
+ it('should preserve original message for TIMEOUT in development', () => {
80
+ const originalMessage = 'Timeout connecting to database at localhost:5432';
81
+ const result = sanitizeErrorMessage('TIMEOUT', originalMessage);
82
+ expect(result).to.equal(originalMessage);
83
+ expect(result).to.contain('localhost');
84
+ expect(result).to.contain('5432');
85
+ });
86
+ it('should preserve original message for EXECUTION_ERROR in development', () => {
87
+ const originalMessage = 'Failed to execute: connection pool exhausted';
88
+ const result = sanitizeErrorMessage('EXECUTION_ERROR', originalMessage);
89
+ expect(result).to.equal(originalMessage);
90
+ expect(result).to.contain('connection pool');
91
+ });
92
+ });
93
+ describe('Stack Trace Handling', () => {
94
+ it('should verify details are undefined in production mode', () => {
95
+ process.env.NODE_ENV = 'production';
96
+ // Simulate what happens in o-server.ts line 207
97
+ const error = new Error('Test error');
98
+ const stackTrace = error.stack;
99
+ const details = process.env.NODE_ENV === 'development'
100
+ ? stackTrace
101
+ : undefined;
102
+ expect(details).to.be.undefined;
103
+ });
104
+ it('should verify details include stack trace in development mode', () => {
105
+ process.env.NODE_ENV = 'development';
106
+ const error = new Error('Test error');
107
+ const stackTrace = error.stack;
108
+ const details = process.env.NODE_ENV === 'development'
109
+ ? stackTrace
110
+ : undefined;
111
+ expect(details).to.exist;
112
+ expect(details).to.contain('Error: Test error');
113
+ expect(details).to.contain('at');
114
+ });
115
+ it('should verify error.details takes precedence over error.stack', () => {
116
+ process.env.NODE_ENV = 'development';
117
+ const error = new Error('Test error');
118
+ error.details = 'Custom debug information';
119
+ const details = process.env.NODE_ENV === 'development'
120
+ ? (error.details || error.stack)
121
+ : undefined;
122
+ expect(details).to.equal('Custom debug information');
123
+ });
124
+ it('should verify both details and stack are filtered in production', () => {
125
+ process.env.NODE_ENV = 'production';
126
+ const error = new Error('Test error');
127
+ error.details = 'Sensitive information: API_KEY=sk-secret-123';
128
+ const details = process.env.NODE_ENV === 'development'
129
+ ? (error.details || error.stack)
130
+ : undefined;
131
+ expect(details).to.be.undefined;
132
+ });
133
+ });
134
+ });
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Input Validation & Sanitization Tests
3
+ *
4
+ * Comprehensive test suite covering:
5
+ * - Address validation (path traversal, format, control chars)
6
+ * - Method validation (private methods, prototype pollution)
7
+ * - Parameter sanitization (recursive dangerous property removal)
8
+ * - Request schema validation (Zod-based)
9
+ * - Integration tests (SQL injection, NoSQL injection, XSS, prototype pollution)
10
+ *
11
+ * Part of Phase 1 Security - Wave 2 (Input Validation)
12
+ */
13
+ export {};
14
+ //# sourceMappingURL=input-validation.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input-validation.spec.d.ts","sourceRoot":"","sources":["../../test/input-validation.spec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG"}