@riotprompt/riotprompt 0.0.20 → 1.0.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/CHANGELOG.md +74 -0
- package/MIGRATION.md +235 -0
- package/README.md +2 -0
- package/SECURITY.md +132 -0
- package/dist/builder.js +6 -0
- package/dist/builder.js.map +1 -1
- package/dist/{cli.cjs → cli.js} +658 -216
- package/dist/context-manager.js +1 -1
- package/dist/conversation-logger.d.ts +17 -1
- package/dist/conversation-logger.js +21 -17
- package/dist/conversation-logger.js.map +1 -1
- package/dist/conversation.js +1 -1
- package/dist/error-handling.d.ts +52 -0
- package/dist/error-handling.js +132 -0
- package/dist/error-handling.js.map +1 -0
- package/dist/formatter.js +1 -1
- package/dist/iteration-strategy.js +1 -1
- package/dist/loader.js +60 -12
- package/dist/loader.js.map +1 -1
- package/dist/logger.d.ts +52 -0
- package/dist/logger.js +114 -14
- package/dist/logger.js.map +1 -1
- package/dist/logging-config.d.ts +84 -0
- package/dist/logging-config.js +116 -0
- package/dist/logging-config.js.map +1 -0
- package/dist/message-builder.js +1 -1
- package/dist/model-config.js +1 -1
- package/dist/override.js +10 -4
- package/dist/override.js.map +1 -1
- package/dist/recipes.js +6 -0
- package/dist/recipes.js.map +1 -1
- package/dist/reflection.js +1 -1
- package/dist/riotprompt.d.ts +9 -0
- package/dist/riotprompt.js +8 -0
- package/dist/riotprompt.js.map +1 -1
- package/dist/security/audit-logger.d.ts +61 -0
- package/dist/security/audit-logger.js +281 -0
- package/dist/security/audit-logger.js.map +1 -0
- package/dist/security/cli-security.d.ts +143 -0
- package/dist/security/cli-security.js +302 -0
- package/dist/security/cli-security.js.map +1 -0
- package/dist/security/defaults.d.ts +31 -0
- package/dist/security/defaults.js +72 -0
- package/dist/security/defaults.js.map +1 -0
- package/dist/security/events.d.ts +8 -0
- package/dist/security/index.d.ts +27 -0
- package/dist/security/index.js +22 -0
- package/dist/security/index.js.map +1 -0
- package/dist/security/path-guard.d.ts +161 -0
- package/dist/security/path-guard.js +327 -0
- package/dist/security/path-guard.js.map +1 -0
- package/dist/security/rate-limiter.d.ts +117 -0
- package/dist/security/rate-limiter.js +165 -0
- package/dist/security/rate-limiter.js.map +1 -0
- package/dist/security/serialization-schemas.d.ts +183 -0
- package/dist/security/serialization-schemas.js +174 -0
- package/dist/security/serialization-schemas.js.map +1 -0
- package/dist/security/timeout-guard.d.ts +123 -0
- package/dist/security/timeout-guard.js +223 -0
- package/dist/security/timeout-guard.js.map +1 -0
- package/dist/security/types.d.ts +86 -0
- package/dist/security/types.js +80 -0
- package/dist/security/types.js.map +1 -0
- package/dist/token-budget.js +1 -1
- package/dist/tools.js +1 -1
- package/dist/util/storage.js.map +1 -1
- package/guide/index.md +2 -0
- package/guide/integration.md +1109 -0
- package/guide/security.md +237 -0
- package/package.json +23 -17
- package/vite.config.cli.ts +9 -18
- package/dist/riotprompt.cjs +0 -6169
- package/dist/riotprompt.cjs.map +0 -1
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { PathSecurityConfig } from './types';
|
|
2
|
+
import { SecurityAuditLogger } from './audit-logger';
|
|
3
|
+
export type { PathSecurityConfig };
|
|
4
|
+
/**
|
|
5
|
+
* Result of path validation
|
|
6
|
+
*/
|
|
7
|
+
export interface PathValidationResult {
|
|
8
|
+
valid: boolean;
|
|
9
|
+
normalizedPath?: string;
|
|
10
|
+
error?: string;
|
|
11
|
+
violation?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* PathGuard provides security validation for file paths.
|
|
15
|
+
*
|
|
16
|
+
* Features:
|
|
17
|
+
* - Directory traversal prevention
|
|
18
|
+
* - Base path restriction
|
|
19
|
+
* - Pattern-based blocking
|
|
20
|
+
* - Null byte detection
|
|
21
|
+
* - Audit logging
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* const guard = new PathGuard({ basePaths: ['/app/data'] });
|
|
26
|
+
*
|
|
27
|
+
* const result = guard.validate('../../../etc/passwd');
|
|
28
|
+
* // { valid: false, error: 'Path contains forbidden pattern' }
|
|
29
|
+
*
|
|
30
|
+
* const safePath = guard.validateOrThrow('subdir/file.txt');
|
|
31
|
+
* // '/app/data/subdir/file.txt'
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare class PathGuard {
|
|
35
|
+
private config;
|
|
36
|
+
private auditLogger;
|
|
37
|
+
private basePaths;
|
|
38
|
+
constructor(config?: Partial<PathSecurityConfig>, auditLogger?: SecurityAuditLogger);
|
|
39
|
+
/**
|
|
40
|
+
* Validate and normalize a file path
|
|
41
|
+
*
|
|
42
|
+
* @param inputPath - The path to validate
|
|
43
|
+
* @param operation - The operation being performed (for audit logging)
|
|
44
|
+
* @returns Validation result with normalized path if valid
|
|
45
|
+
*/
|
|
46
|
+
validate(inputPath: string, _operation?: string): PathValidationResult;
|
|
47
|
+
/**
|
|
48
|
+
* Validate and return the path, throwing on failure
|
|
49
|
+
*
|
|
50
|
+
* @param inputPath - The path to validate
|
|
51
|
+
* @param operation - The operation being performed
|
|
52
|
+
* @returns The normalized path
|
|
53
|
+
* @throws Error if validation fails
|
|
54
|
+
*/
|
|
55
|
+
validateOrThrow(inputPath: string, operation?: string): string;
|
|
56
|
+
/**
|
|
57
|
+
* Add a base path at runtime
|
|
58
|
+
*
|
|
59
|
+
* @param basePath - The base path to add
|
|
60
|
+
*/
|
|
61
|
+
addBasePath(basePath: string): void;
|
|
62
|
+
/**
|
|
63
|
+
* Remove a base path
|
|
64
|
+
*
|
|
65
|
+
* @param basePath - The base path to remove
|
|
66
|
+
*/
|
|
67
|
+
removeBasePath(basePath: string): void;
|
|
68
|
+
/**
|
|
69
|
+
* Check if a path is within allowed directories
|
|
70
|
+
*
|
|
71
|
+
* @param testPath - The path to check
|
|
72
|
+
* @returns True if the path is within allowed directories
|
|
73
|
+
*/
|
|
74
|
+
isWithinAllowed(testPath: string): boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Get the first base path (for relative resolution)
|
|
77
|
+
*
|
|
78
|
+
* @returns The first base path or undefined
|
|
79
|
+
*/
|
|
80
|
+
getBasePath(): string | undefined;
|
|
81
|
+
/**
|
|
82
|
+
* Get all configured base paths
|
|
83
|
+
*
|
|
84
|
+
* @returns Array of base paths
|
|
85
|
+
*/
|
|
86
|
+
getBasePaths(): string[];
|
|
87
|
+
/**
|
|
88
|
+
* Check if path validation is enabled
|
|
89
|
+
*
|
|
90
|
+
* @returns True if enabled
|
|
91
|
+
*/
|
|
92
|
+
isEnabled(): boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Enable or disable path validation
|
|
95
|
+
*
|
|
96
|
+
* @param enabled - Whether to enable validation
|
|
97
|
+
*/
|
|
98
|
+
setEnabled(enabled: boolean): void;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Get the global PathGuard instance
|
|
102
|
+
*
|
|
103
|
+
* @returns The global PathGuard
|
|
104
|
+
*/
|
|
105
|
+
export declare function getPathGuard(): PathGuard;
|
|
106
|
+
/**
|
|
107
|
+
* Configure the global PathGuard
|
|
108
|
+
*
|
|
109
|
+
* @param config - Configuration options
|
|
110
|
+
*/
|
|
111
|
+
export declare function configurePathGuard(config: Partial<PathSecurityConfig>): void;
|
|
112
|
+
/**
|
|
113
|
+
* Reset the global PathGuard to default
|
|
114
|
+
*/
|
|
115
|
+
export declare function resetPathGuard(): void;
|
|
116
|
+
/**
|
|
117
|
+
* Sanitize a glob pattern by removing potentially dangerous sequences
|
|
118
|
+
*
|
|
119
|
+
* @param pattern - The glob pattern to sanitize
|
|
120
|
+
* @returns Sanitized glob pattern
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```typescript
|
|
124
|
+
* sanitizeGlobPattern('../../../etc/*') // Returns 'etc/*'
|
|
125
|
+
* sanitizeGlobPattern('/absolute/path/*') // Returns 'absolute/path/*'
|
|
126
|
+
* sanitizeGlobPattern('~/secrets/*') // Returns 'secrets/*'
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
export declare function sanitizeGlobPattern(pattern: string): string;
|
|
130
|
+
/**
|
|
131
|
+
* Check if a glob pattern is safe to use
|
|
132
|
+
*
|
|
133
|
+
* @param pattern - The glob pattern to check
|
|
134
|
+
* @returns true if safe, false if potentially dangerous
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```typescript
|
|
138
|
+
* isGlobSafe('src/**\/*.ts') // true
|
|
139
|
+
* isGlobSafe('../../../etc/passwd') // false
|
|
140
|
+
* isGlobSafe('/etc/*') // false
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
export declare function isGlobSafe(pattern: string): boolean;
|
|
144
|
+
/**
|
|
145
|
+
* Result of glob pattern validation
|
|
146
|
+
*/
|
|
147
|
+
export interface GlobValidationResult {
|
|
148
|
+
safe: boolean;
|
|
149
|
+
sanitized?: string;
|
|
150
|
+
warnings: string[];
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Validate and optionally sanitize a glob pattern
|
|
154
|
+
*
|
|
155
|
+
* @param pattern - The glob pattern to validate
|
|
156
|
+
* @param options - Validation options
|
|
157
|
+
* @returns Validation result with sanitized pattern if requested
|
|
158
|
+
*/
|
|
159
|
+
export declare function validateGlobPattern(pattern: string, options?: {
|
|
160
|
+
sanitize?: boolean;
|
|
161
|
+
}): GlobValidationResult;
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import path__default from 'path';
|
|
2
|
+
import { getAuditLogger } from './audit-logger.js';
|
|
3
|
+
|
|
4
|
+
function _define_property(obj, key, value) {
|
|
5
|
+
if (key in obj) {
|
|
6
|
+
Object.defineProperty(obj, key, {
|
|
7
|
+
value: value,
|
|
8
|
+
enumerable: true,
|
|
9
|
+
configurable: true,
|
|
10
|
+
writable: true
|
|
11
|
+
});
|
|
12
|
+
} else {
|
|
13
|
+
obj[key] = value;
|
|
14
|
+
}
|
|
15
|
+
return obj;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* PathGuard provides security validation for file paths.
|
|
19
|
+
*
|
|
20
|
+
* Features:
|
|
21
|
+
* - Directory traversal prevention
|
|
22
|
+
* - Base path restriction
|
|
23
|
+
* - Pattern-based blocking
|
|
24
|
+
* - Null byte detection
|
|
25
|
+
* - Audit logging
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* const guard = new PathGuard({ basePaths: ['/app/data'] });
|
|
30
|
+
*
|
|
31
|
+
* const result = guard.validate('../../../etc/passwd');
|
|
32
|
+
* // { valid: false, error: 'Path contains forbidden pattern' }
|
|
33
|
+
*
|
|
34
|
+
* const safePath = guard.validateOrThrow('subdir/file.txt');
|
|
35
|
+
* // '/app/data/subdir/file.txt'
|
|
36
|
+
* ```
|
|
37
|
+
*/ class PathGuard {
|
|
38
|
+
/**
|
|
39
|
+
* Validate and normalize a file path
|
|
40
|
+
*
|
|
41
|
+
* @param inputPath - The path to validate
|
|
42
|
+
* @param operation - The operation being performed (for audit logging)
|
|
43
|
+
* @returns Validation result with normalized path if valid
|
|
44
|
+
*/ validate(inputPath, _operation = 'access') {
|
|
45
|
+
if (!this.config.enabled) {
|
|
46
|
+
return {
|
|
47
|
+
valid: true,
|
|
48
|
+
normalizedPath: inputPath
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
// Check for null bytes (path truncation attack)
|
|
52
|
+
if (inputPath.includes('\0')) {
|
|
53
|
+
this.auditLogger.pathTraversalBlocked(inputPath, 'Null byte detected');
|
|
54
|
+
return {
|
|
55
|
+
valid: false,
|
|
56
|
+
error: 'Path contains invalid characters',
|
|
57
|
+
violation: 'null_byte'
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
// Check for denied patterns
|
|
61
|
+
for (const pattern of this.config.denyPatterns){
|
|
62
|
+
try {
|
|
63
|
+
const regex = new RegExp(pattern, 'i');
|
|
64
|
+
if (regex.test(inputPath)) {
|
|
65
|
+
this.auditLogger.pathTraversalBlocked(inputPath, `Matched deny pattern: ${pattern}`);
|
|
66
|
+
return {
|
|
67
|
+
valid: false,
|
|
68
|
+
error: 'Path contains forbidden pattern',
|
|
69
|
+
violation: pattern
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
} catch {
|
|
73
|
+
// Invalid regex pattern, skip
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Check absolute path handling
|
|
77
|
+
const isAbsolute = path__default.isAbsolute(inputPath);
|
|
78
|
+
if (isAbsolute && !this.config.allowAbsolute) {
|
|
79
|
+
this.auditLogger.pathTraversalBlocked(inputPath, 'Absolute paths not allowed');
|
|
80
|
+
return {
|
|
81
|
+
valid: false,
|
|
82
|
+
error: 'Absolute paths are not allowed',
|
|
83
|
+
violation: 'absolute_path'
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
// Normalize the path
|
|
87
|
+
let normalizedPath;
|
|
88
|
+
try {
|
|
89
|
+
if (isAbsolute) {
|
|
90
|
+
normalizedPath = path__default.normalize(inputPath);
|
|
91
|
+
} else if (this.basePaths.length > 0) {
|
|
92
|
+
// Resolve relative to first base path
|
|
93
|
+
normalizedPath = path__default.resolve(this.basePaths[0], inputPath);
|
|
94
|
+
} else {
|
|
95
|
+
normalizedPath = path__default.resolve(inputPath);
|
|
96
|
+
}
|
|
97
|
+
} catch {
|
|
98
|
+
return {
|
|
99
|
+
valid: false,
|
|
100
|
+
error: 'Invalid path format'
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
// Verify path is within allowed base paths
|
|
104
|
+
if (this.basePaths.length > 0) {
|
|
105
|
+
const isWithinBase = this.basePaths.some((basePath)=>normalizedPath.startsWith(basePath + path__default.sep) || normalizedPath === basePath);
|
|
106
|
+
if (!isWithinBase) {
|
|
107
|
+
this.auditLogger.pathTraversalBlocked(inputPath, 'Path escapes allowed directories');
|
|
108
|
+
return {
|
|
109
|
+
valid: false,
|
|
110
|
+
error: 'Path is outside allowed directories',
|
|
111
|
+
violation: 'directory_escape'
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Check for path traversal after normalization
|
|
116
|
+
// Only check if the path is NOT within any allowed base path
|
|
117
|
+
// (the isWithinBase check above already handles this for absolute paths)
|
|
118
|
+
return {
|
|
119
|
+
valid: true,
|
|
120
|
+
normalizedPath
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Validate and return the path, throwing on failure
|
|
125
|
+
*
|
|
126
|
+
* @param inputPath - The path to validate
|
|
127
|
+
* @param operation - The operation being performed
|
|
128
|
+
* @returns The normalized path
|
|
129
|
+
* @throws Error if validation fails
|
|
130
|
+
*/ validateOrThrow(inputPath, operation = 'access') {
|
|
131
|
+
const result = this.validate(inputPath, operation);
|
|
132
|
+
if (!result.valid) {
|
|
133
|
+
throw new Error(`Path validation failed: ${result.error}`);
|
|
134
|
+
}
|
|
135
|
+
return result.normalizedPath;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Add a base path at runtime
|
|
139
|
+
*
|
|
140
|
+
* @param basePath - The base path to add
|
|
141
|
+
*/ addBasePath(basePath) {
|
|
142
|
+
const normalized = path__default.resolve(basePath);
|
|
143
|
+
if (!this.basePaths.includes(normalized)) {
|
|
144
|
+
this.basePaths.push(normalized);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Remove a base path
|
|
149
|
+
*
|
|
150
|
+
* @param basePath - The base path to remove
|
|
151
|
+
*/ removeBasePath(basePath) {
|
|
152
|
+
const normalized = path__default.resolve(basePath);
|
|
153
|
+
const index = this.basePaths.indexOf(normalized);
|
|
154
|
+
if (index !== -1) {
|
|
155
|
+
this.basePaths.splice(index, 1);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Check if a path is within allowed directories
|
|
160
|
+
*
|
|
161
|
+
* @param testPath - The path to check
|
|
162
|
+
* @returns True if the path is within allowed directories
|
|
163
|
+
*/ isWithinAllowed(testPath) {
|
|
164
|
+
if (this.basePaths.length === 0) return true;
|
|
165
|
+
const normalizedTest = path__default.resolve(testPath);
|
|
166
|
+
return this.basePaths.some((basePath)=>normalizedTest.startsWith(basePath + path__default.sep) || normalizedTest === basePath);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Get the first base path (for relative resolution)
|
|
170
|
+
*
|
|
171
|
+
* @returns The first base path or undefined
|
|
172
|
+
*/ getBasePath() {
|
|
173
|
+
return this.basePaths[0];
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Get all configured base paths
|
|
177
|
+
*
|
|
178
|
+
* @returns Array of base paths
|
|
179
|
+
*/ getBasePaths() {
|
|
180
|
+
return [
|
|
181
|
+
...this.basePaths
|
|
182
|
+
];
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Check if path validation is enabled
|
|
186
|
+
*
|
|
187
|
+
* @returns True if enabled
|
|
188
|
+
*/ isEnabled() {
|
|
189
|
+
return this.config.enabled;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Enable or disable path validation
|
|
193
|
+
*
|
|
194
|
+
* @param enabled - Whether to enable validation
|
|
195
|
+
*/ setEnabled(enabled) {
|
|
196
|
+
this.config.enabled = enabled;
|
|
197
|
+
}
|
|
198
|
+
constructor(config = {}, auditLogger){
|
|
199
|
+
_define_property(this, "config", void 0);
|
|
200
|
+
_define_property(this, "auditLogger", void 0);
|
|
201
|
+
_define_property(this, "basePaths", void 0);
|
|
202
|
+
this.config = {
|
|
203
|
+
enabled: true,
|
|
204
|
+
basePaths: [],
|
|
205
|
+
allowAbsolute: false,
|
|
206
|
+
allowSymlinks: false,
|
|
207
|
+
denyPatterns: [
|
|
208
|
+
'\\.\\.',
|
|
209
|
+
'~',
|
|
210
|
+
'\\$\\{',
|
|
211
|
+
'\\$\\('
|
|
212
|
+
],
|
|
213
|
+
...config
|
|
214
|
+
};
|
|
215
|
+
// Normalize base paths
|
|
216
|
+
this.basePaths = this.config.basePaths.map((p)=>path__default.resolve(p));
|
|
217
|
+
this.auditLogger = auditLogger || getAuditLogger();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Global instance
|
|
221
|
+
let globalPathGuard = null;
|
|
222
|
+
/**
|
|
223
|
+
* Get the global PathGuard instance
|
|
224
|
+
*
|
|
225
|
+
* @returns The global PathGuard
|
|
226
|
+
*/ function getPathGuard() {
|
|
227
|
+
if (!globalPathGuard) {
|
|
228
|
+
globalPathGuard = new PathGuard();
|
|
229
|
+
}
|
|
230
|
+
return globalPathGuard;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Configure the global PathGuard
|
|
234
|
+
*
|
|
235
|
+
* @param config - Configuration options
|
|
236
|
+
*/ function configurePathGuard(config) {
|
|
237
|
+
globalPathGuard = new PathGuard(config);
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Reset the global PathGuard to default
|
|
241
|
+
*/ function resetPathGuard() {
|
|
242
|
+
globalPathGuard = null;
|
|
243
|
+
}
|
|
244
|
+
// ===== GLOB PATTERN SANITIZATION =====
|
|
245
|
+
/**
|
|
246
|
+
* Dangerous patterns that should not appear in glob patterns
|
|
247
|
+
*/ const DANGEROUS_GLOB_PATTERNS = [
|
|
248
|
+
/\.\.\//,
|
|
249
|
+
/\.\.\\/,
|
|
250
|
+
/^\//,
|
|
251
|
+
/^[a-zA-Z]:/,
|
|
252
|
+
/^~/,
|
|
253
|
+
/\$\{/,
|
|
254
|
+
/\$\(/,
|
|
255
|
+
/`/
|
|
256
|
+
];
|
|
257
|
+
/**
|
|
258
|
+
* Sanitize a glob pattern by removing potentially dangerous sequences
|
|
259
|
+
*
|
|
260
|
+
* @param pattern - The glob pattern to sanitize
|
|
261
|
+
* @returns Sanitized glob pattern
|
|
262
|
+
*
|
|
263
|
+
* @example
|
|
264
|
+
* ```typescript
|
|
265
|
+
* sanitizeGlobPattern('../../../etc/*') // Returns 'etc/*'
|
|
266
|
+
* sanitizeGlobPattern('/absolute/path/*') // Returns 'absolute/path/*'
|
|
267
|
+
* sanitizeGlobPattern('~/secrets/*') // Returns 'secrets/*'
|
|
268
|
+
* ```
|
|
269
|
+
*/ function sanitizeGlobPattern(pattern) {
|
|
270
|
+
let safe = pattern// Remove parent directory references
|
|
271
|
+
.replace(/\.\.\//g, '').replace(/\.\.\\/g, '')// Remove absolute path starters
|
|
272
|
+
.replace(/^\/+/, '').replace(/^[a-zA-Z]:[\\/]?/, '')// Remove home directory references
|
|
273
|
+
.replace(/^~[\\/]?/, '')// Remove variable expansion
|
|
274
|
+
.replace(/\$\{[^}]*\}/g, '')// Remove command substitution
|
|
275
|
+
.replace(/\$\([^)]*\)/g, '').replace(/`[^`]*`/g, '');
|
|
276
|
+
// Remove any remaining dangerous characters at the start
|
|
277
|
+
while(safe.startsWith('/') || safe.startsWith('\\')){
|
|
278
|
+
safe = safe.substring(1);
|
|
279
|
+
}
|
|
280
|
+
return safe;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Check if a glob pattern is safe to use
|
|
284
|
+
*
|
|
285
|
+
* @param pattern - The glob pattern to check
|
|
286
|
+
* @returns true if safe, false if potentially dangerous
|
|
287
|
+
*
|
|
288
|
+
* @example
|
|
289
|
+
* ```typescript
|
|
290
|
+
* isGlobSafe('src/**\/*.ts') // true
|
|
291
|
+
* isGlobSafe('../../../etc/passwd') // false
|
|
292
|
+
* isGlobSafe('/etc/*') // false
|
|
293
|
+
* ```
|
|
294
|
+
*/ function isGlobSafe(pattern) {
|
|
295
|
+
return !DANGEROUS_GLOB_PATTERNS.some((re)=>re.test(pattern));
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Validate and optionally sanitize a glob pattern
|
|
299
|
+
*
|
|
300
|
+
* @param pattern - The glob pattern to validate
|
|
301
|
+
* @param options - Validation options
|
|
302
|
+
* @returns Validation result with sanitized pattern if requested
|
|
303
|
+
*/ function validateGlobPattern(pattern, options = {}) {
|
|
304
|
+
const warnings = [];
|
|
305
|
+
// Check for dangerous patterns
|
|
306
|
+
for (const dangerousPattern of DANGEROUS_GLOB_PATTERNS){
|
|
307
|
+
if (dangerousPattern.test(pattern)) {
|
|
308
|
+
warnings.push(`Pattern contains potentially dangerous sequence: ${dangerousPattern.source}`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
const safe = warnings.length === 0;
|
|
312
|
+
if (options.sanitize && !safe) {
|
|
313
|
+
return {
|
|
314
|
+
safe: false,
|
|
315
|
+
sanitized: sanitizeGlobPattern(pattern),
|
|
316
|
+
warnings
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
return {
|
|
320
|
+
safe,
|
|
321
|
+
sanitized: safe ? pattern : undefined,
|
|
322
|
+
warnings
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export { PathGuard, configurePathGuard, getPathGuard, isGlobSafe, resetPathGuard, sanitizeGlobPattern, validateGlobPattern };
|
|
327
|
+
//# sourceMappingURL=path-guard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-guard.js","sources":["../../src/security/path-guard.ts"],"sourcesContent":["/**\n * RiotPrompt - Path Security Guard\n *\n * Implements path validation to prevent directory traversal attacks\n * in file operations.\n */\n\nimport path from 'path';\nimport { PathSecurityConfig } from './types';\nimport { getAuditLogger, SecurityAuditLogger } from './audit-logger';\n\n// Re-export PathSecurityConfig for external use\nexport type { PathSecurityConfig };\n\n/**\n * Result of path validation\n */\nexport interface PathValidationResult {\n valid: boolean;\n normalizedPath?: string;\n error?: string;\n violation?: string;\n}\n\n/**\n * PathGuard provides security validation for file paths.\n *\n * Features:\n * - Directory traversal prevention\n * - Base path restriction\n * - Pattern-based blocking\n * - Null byte detection\n * - Audit logging\n *\n * @example\n * ```typescript\n * const guard = new PathGuard({ basePaths: ['/app/data'] });\n *\n * const result = guard.validate('../../../etc/passwd');\n * // { valid: false, error: 'Path contains forbidden pattern' }\n *\n * const safePath = guard.validateOrThrow('subdir/file.txt');\n * // '/app/data/subdir/file.txt'\n * ```\n */\nexport class PathGuard {\n private config: PathSecurityConfig;\n private auditLogger: SecurityAuditLogger;\n private basePaths: string[];\n\n constructor(config: Partial<PathSecurityConfig> = {}, auditLogger?: SecurityAuditLogger) {\n this.config = {\n enabled: true,\n basePaths: [],\n allowAbsolute: false,\n allowSymlinks: false,\n denyPatterns: [\n '\\\\.\\\\.', // Parent directory\n '~', // Home directory expansion\n '\\\\$\\\\{', // Variable expansion\n '\\\\$\\\\(', // Command substitution\n ],\n ...config,\n };\n\n // Normalize base paths\n this.basePaths = this.config.basePaths.map(p => path.resolve(p));\n this.auditLogger = auditLogger || getAuditLogger();\n }\n\n /**\n * Validate and normalize a file path\n *\n * @param inputPath - The path to validate\n * @param operation - The operation being performed (for audit logging)\n * @returns Validation result with normalized path if valid\n */\n validate(inputPath: string, _operation: string = 'access'): PathValidationResult {\n if (!this.config.enabled) {\n return { valid: true, normalizedPath: inputPath };\n }\n\n // Check for null bytes (path truncation attack)\n if (inputPath.includes('\\0')) {\n this.auditLogger.pathTraversalBlocked(inputPath, 'Null byte detected');\n return {\n valid: false,\n error: 'Path contains invalid characters',\n violation: 'null_byte',\n };\n }\n\n // Check for denied patterns\n for (const pattern of this.config.denyPatterns) {\n try {\n const regex = new RegExp(pattern, 'i');\n if (regex.test(inputPath)) {\n this.auditLogger.pathTraversalBlocked(inputPath, `Matched deny pattern: ${pattern}`);\n return {\n valid: false,\n error: 'Path contains forbidden pattern',\n violation: pattern,\n };\n }\n } catch {\n // Invalid regex pattern, skip\n }\n }\n\n // Check absolute path handling\n const isAbsolute = path.isAbsolute(inputPath);\n if (isAbsolute && !this.config.allowAbsolute) {\n this.auditLogger.pathTraversalBlocked(inputPath, 'Absolute paths not allowed');\n return {\n valid: false,\n error: 'Absolute paths are not allowed',\n violation: 'absolute_path',\n };\n }\n\n // Normalize the path\n let normalizedPath: string;\n try {\n if (isAbsolute) {\n normalizedPath = path.normalize(inputPath);\n } else if (this.basePaths.length > 0) {\n // Resolve relative to first base path\n normalizedPath = path.resolve(this.basePaths[0], inputPath);\n } else {\n normalizedPath = path.resolve(inputPath);\n }\n } catch {\n return {\n valid: false,\n error: 'Invalid path format',\n };\n }\n\n // Verify path is within allowed base paths\n if (this.basePaths.length > 0) {\n const isWithinBase = this.basePaths.some(basePath =>\n normalizedPath.startsWith(basePath + path.sep) || normalizedPath === basePath\n );\n\n if (!isWithinBase) {\n this.auditLogger.pathTraversalBlocked(inputPath, 'Path escapes allowed directories');\n return {\n valid: false,\n error: 'Path is outside allowed directories',\n violation: 'directory_escape',\n };\n }\n }\n\n // Check for path traversal after normalization\n // Only check if the path is NOT within any allowed base path\n // (the isWithinBase check above already handles this for absolute paths)\n\n return {\n valid: true,\n normalizedPath,\n };\n }\n\n /**\n * Validate and return the path, throwing on failure\n *\n * @param inputPath - The path to validate\n * @param operation - The operation being performed\n * @returns The normalized path\n * @throws Error if validation fails\n */\n validateOrThrow(inputPath: string, operation: string = 'access'): string {\n const result = this.validate(inputPath, operation);\n if (!result.valid) {\n throw new Error(`Path validation failed: ${result.error}`);\n }\n return result.normalizedPath!;\n }\n\n /**\n * Add a base path at runtime\n *\n * @param basePath - The base path to add\n */\n addBasePath(basePath: string): void {\n const normalized = path.resolve(basePath);\n if (!this.basePaths.includes(normalized)) {\n this.basePaths.push(normalized);\n }\n }\n\n /**\n * Remove a base path\n *\n * @param basePath - The base path to remove\n */\n removeBasePath(basePath: string): void {\n const normalized = path.resolve(basePath);\n const index = this.basePaths.indexOf(normalized);\n if (index !== -1) {\n this.basePaths.splice(index, 1);\n }\n }\n\n /**\n * Check if a path is within allowed directories\n *\n * @param testPath - The path to check\n * @returns True if the path is within allowed directories\n */\n isWithinAllowed(testPath: string): boolean {\n if (this.basePaths.length === 0) return true;\n\n const normalizedTest = path.resolve(testPath);\n return this.basePaths.some(basePath =>\n normalizedTest.startsWith(basePath + path.sep) || normalizedTest === basePath\n );\n }\n\n /**\n * Get the first base path (for relative resolution)\n *\n * @returns The first base path or undefined\n */\n getBasePath(): string | undefined {\n return this.basePaths[0];\n }\n\n /**\n * Get all configured base paths\n *\n * @returns Array of base paths\n */\n getBasePaths(): string[] {\n return [...this.basePaths];\n }\n\n /**\n * Check if path validation is enabled\n *\n * @returns True if enabled\n */\n isEnabled(): boolean {\n return this.config.enabled;\n }\n\n /**\n * Enable or disable path validation\n *\n * @param enabled - Whether to enable validation\n */\n setEnabled(enabled: boolean): void {\n this.config.enabled = enabled;\n }\n}\n\n// Global instance\nlet globalPathGuard: PathGuard | null = null;\n\n/**\n * Get the global PathGuard instance\n *\n * @returns The global PathGuard\n */\nexport function getPathGuard(): PathGuard {\n if (!globalPathGuard) {\n globalPathGuard = new PathGuard();\n }\n return globalPathGuard;\n}\n\n/**\n * Configure the global PathGuard\n *\n * @param config - Configuration options\n */\nexport function configurePathGuard(config: Partial<PathSecurityConfig>): void {\n globalPathGuard = new PathGuard(config);\n}\n\n/**\n * Reset the global PathGuard to default\n */\nexport function resetPathGuard(): void {\n globalPathGuard = null;\n}\n\n// ===== GLOB PATTERN SANITIZATION =====\n\n/**\n * Dangerous patterns that should not appear in glob patterns\n */\nconst DANGEROUS_GLOB_PATTERNS = [\n /\\.\\.\\//, // Parent directory traversal\n /\\.\\.\\\\/, // Windows parent directory\n /^\\//, // Absolute path (Unix)\n /^[a-zA-Z]:/, // Absolute path (Windows)\n /^~/, // Home directory\n /\\$\\{/, // Variable expansion\n /\\$\\(/, // Command substitution\n /`/, // Backtick command substitution\n];\n\n/**\n * Sanitize a glob pattern by removing potentially dangerous sequences\n * \n * @param pattern - The glob pattern to sanitize\n * @returns Sanitized glob pattern\n * \n * @example\n * ```typescript\n * sanitizeGlobPattern('../../../etc/*') // Returns 'etc/*'\n * sanitizeGlobPattern('/absolute/path/*') // Returns 'absolute/path/*'\n * sanitizeGlobPattern('~/secrets/*') // Returns 'secrets/*'\n * ```\n */\nexport function sanitizeGlobPattern(pattern: string): string {\n let safe = pattern\n // Remove parent directory references\n .replace(/\\.\\.\\//g, '')\n .replace(/\\.\\.\\\\/g, '')\n // Remove absolute path starters\n .replace(/^\\/+/, '')\n .replace(/^[a-zA-Z]:[\\\\/]?/, '')\n // Remove home directory references\n .replace(/^~[\\\\/]?/, '')\n // Remove variable expansion\n .replace(/\\$\\{[^}]*\\}/g, '')\n // Remove command substitution\n .replace(/\\$\\([^)]*\\)/g, '')\n .replace(/`[^`]*`/g, '');\n\n // Remove any remaining dangerous characters at the start\n while (safe.startsWith('/') || safe.startsWith('\\\\')) {\n safe = safe.substring(1);\n }\n\n return safe;\n}\n\n/**\n * Check if a glob pattern is safe to use\n * \n * @param pattern - The glob pattern to check\n * @returns true if safe, false if potentially dangerous\n * \n * @example\n * ```typescript\n * isGlobSafe('src/**\\/*.ts') // true\n * isGlobSafe('../../../etc/passwd') // false\n * isGlobSafe('/etc/*') // false\n * ```\n */\nexport function isGlobSafe(pattern: string): boolean {\n return !DANGEROUS_GLOB_PATTERNS.some(re => re.test(pattern));\n}\n\n/**\n * Result of glob pattern validation\n */\nexport interface GlobValidationResult {\n safe: boolean;\n sanitized?: string;\n warnings: string[];\n}\n\n/**\n * Validate and optionally sanitize a glob pattern\n * \n * @param pattern - The glob pattern to validate\n * @param options - Validation options\n * @returns Validation result with sanitized pattern if requested\n */\nexport function validateGlobPattern(\n pattern: string,\n options: { sanitize?: boolean } = {}\n): GlobValidationResult {\n const warnings: string[] = [];\n\n // Check for dangerous patterns\n for (const dangerousPattern of DANGEROUS_GLOB_PATTERNS) {\n if (dangerousPattern.test(pattern)) {\n warnings.push(`Pattern contains potentially dangerous sequence: ${dangerousPattern.source}`);\n }\n }\n\n const safe = warnings.length === 0;\n\n if (options.sanitize && !safe) {\n return {\n safe: false,\n sanitized: sanitizeGlobPattern(pattern),\n warnings,\n };\n }\n\n return {\n safe,\n sanitized: safe ? pattern : undefined,\n warnings,\n };\n}\n\n"],"names":["PathGuard","validate","inputPath","_operation","config","enabled","valid","normalizedPath","includes","auditLogger","pathTraversalBlocked","error","violation","pattern","denyPatterns","regex","RegExp","test","isAbsolute","path","allowAbsolute","normalize","basePaths","length","resolve","isWithinBase","some","basePath","startsWith","sep","validateOrThrow","operation","result","Error","addBasePath","normalized","push","removeBasePath","index","indexOf","splice","isWithinAllowed","testPath","normalizedTest","getBasePath","getBasePaths","isEnabled","setEnabled","allowSymlinks","map","p","getAuditLogger","globalPathGuard","getPathGuard","configurePathGuard","resetPathGuard","DANGEROUS_GLOB_PATTERNS","sanitizeGlobPattern","safe","replace","substring","isGlobSafe","re","validateGlobPattern","options","warnings","dangerousPattern","source","sanitize","sanitized","undefined"],"mappings":"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"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate Limiter Interfaces
|
|
3
|
+
*
|
|
4
|
+
* Provides interfaces and basic implementations for rate limiting.
|
|
5
|
+
* Production applications should use dedicated rate limiting libraries
|
|
6
|
+
* (e.g., rate-limiter-flexible, express-rate-limit) for distributed systems.
|
|
7
|
+
*
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Rate limiter interface
|
|
12
|
+
* Implementation is left to users (use your preferred library)
|
|
13
|
+
*/
|
|
14
|
+
export interface RateLimiter {
|
|
15
|
+
/**
|
|
16
|
+
* Check if request is allowed
|
|
17
|
+
* @param key - Unique identifier for the rate limit bucket (e.g., user ID, IP)
|
|
18
|
+
* @returns true if allowed, false if rate limited
|
|
19
|
+
*/
|
|
20
|
+
check(key: string): Promise<boolean>;
|
|
21
|
+
/**
|
|
22
|
+
* Get remaining requests in the current window
|
|
23
|
+
* @param key - Unique identifier for the rate limit bucket
|
|
24
|
+
* @returns Number of remaining requests
|
|
25
|
+
*/
|
|
26
|
+
remaining(key: string): Promise<number>;
|
|
27
|
+
/**
|
|
28
|
+
* Reset rate limit for a key
|
|
29
|
+
* @param key - Unique identifier for the rate limit bucket
|
|
30
|
+
*/
|
|
31
|
+
reset(key: string): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Rate limiter configuration
|
|
35
|
+
*/
|
|
36
|
+
export interface RateLimiterConfig {
|
|
37
|
+
/** Time window in milliseconds */
|
|
38
|
+
windowMs: number;
|
|
39
|
+
/** Maximum requests allowed in the window */
|
|
40
|
+
maxRequests: number;
|
|
41
|
+
/** Optional function to generate keys from context */
|
|
42
|
+
keyGenerator?: (context: unknown) => string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* No-op rate limiter that always allows requests
|
|
46
|
+
* Use this as a default when rate limiting is not needed
|
|
47
|
+
*/
|
|
48
|
+
export declare class NoOpRateLimiter implements RateLimiter {
|
|
49
|
+
check(_key: string): Promise<boolean>;
|
|
50
|
+
remaining(_key: string): Promise<number>;
|
|
51
|
+
reset(_key: string): Promise<void>;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Simple in-memory rate limiter
|
|
55
|
+
*
|
|
56
|
+
* **Warning**: This implementation is NOT suitable for production use in
|
|
57
|
+
* distributed systems. Use a Redis-backed solution for production.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```typescript
|
|
61
|
+
* const limiter = new MemoryRateLimiter({
|
|
62
|
+
* windowMs: 60000, // 1 minute
|
|
63
|
+
* maxRequests: 100, // 100 requests per minute
|
|
64
|
+
* });
|
|
65
|
+
*
|
|
66
|
+
* if (await limiter.check('user-123')) {
|
|
67
|
+
* // Process request
|
|
68
|
+
* } else {
|
|
69
|
+
* // Rate limited
|
|
70
|
+
* }
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export declare class MemoryRateLimiter implements RateLimiter {
|
|
74
|
+
private requests;
|
|
75
|
+
private config;
|
|
76
|
+
private cleanupInterval;
|
|
77
|
+
constructor(config: RateLimiterConfig);
|
|
78
|
+
check(key: string): Promise<boolean>;
|
|
79
|
+
remaining(key: string): Promise<number>;
|
|
80
|
+
reset(key: string): Promise<void>;
|
|
81
|
+
/**
|
|
82
|
+
* Clean up expired entries to prevent memory leaks
|
|
83
|
+
*/
|
|
84
|
+
private cleanup;
|
|
85
|
+
/**
|
|
86
|
+
* Stop the cleanup interval (call when done with the limiter)
|
|
87
|
+
*/
|
|
88
|
+
destroy(): void;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Create a rate limiter with the given configuration
|
|
92
|
+
*
|
|
93
|
+
* @param config - Rate limiter configuration
|
|
94
|
+
* @returns Rate limiter instance
|
|
95
|
+
*/
|
|
96
|
+
export declare function createRateLimiter(config: RateLimiterConfig): RateLimiter;
|
|
97
|
+
/**
|
|
98
|
+
* Create a no-op rate limiter that always allows requests
|
|
99
|
+
*
|
|
100
|
+
* @returns No-op rate limiter instance
|
|
101
|
+
*/
|
|
102
|
+
export declare function createNoOpRateLimiter(): RateLimiter;
|
|
103
|
+
/**
|
|
104
|
+
* Get the global rate limiter instance
|
|
105
|
+
* Returns a no-op limiter if not configured
|
|
106
|
+
*/
|
|
107
|
+
export declare function getRateLimiter(): RateLimiter;
|
|
108
|
+
/**
|
|
109
|
+
* Configure the global rate limiter
|
|
110
|
+
*
|
|
111
|
+
* @param config - Rate limiter configuration
|
|
112
|
+
*/
|
|
113
|
+
export declare function configureRateLimiter(config: RateLimiterConfig): void;
|
|
114
|
+
/**
|
|
115
|
+
* Reset the global rate limiter to the default no-op implementation
|
|
116
|
+
*/
|
|
117
|
+
export declare function resetRateLimiter(): void;
|