@exaudeus/workrail 0.0.12 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/infrastructure/storage/git-workflow-storage.d.ts +65 -0
- package/dist/infrastructure/storage/git-workflow-storage.d.ts.map +1 -0
- package/dist/infrastructure/storage/git-workflow-storage.js +284 -0
- package/dist/infrastructure/storage/git-workflow-storage.js.map +1 -0
- package/dist/infrastructure/storage/plugin-workflow-storage.d.ts +102 -0
- package/dist/infrastructure/storage/plugin-workflow-storage.d.ts.map +1 -0
- package/dist/infrastructure/storage/plugin-workflow-storage.js +319 -0
- package/dist/infrastructure/storage/plugin-workflow-storage.js.map +1 -0
- package/dist/infrastructure/storage/remote-workflow-storage.d.ts +44 -0
- package/dist/infrastructure/storage/remote-workflow-storage.d.ts.map +1 -0
- package/dist/infrastructure/storage/remote-workflow-storage.js +321 -0
- package/dist/infrastructure/storage/remote-workflow-storage.js.map +1 -0
- package/dist/tools/mcp_initialize.js +1 -1
- package/dist/utils/storage-security.d.ts +74 -0
- package/dist/utils/storage-security.d.ts.map +1 -0
- package/dist/utils/storage-security.js +134 -0
- package/dist/utils/storage-security.js.map +1 -0
- package/package.json +1 -1
- package/workflows/coding-task-workflow.json +59 -15
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CommunityWorkflowStorage = exports.RemoteWorkflowStorage = void 0;
|
|
4
|
+
const storage_security_1 = require("../../utils/storage-security");
|
|
5
|
+
const error_handler_1 = require("../../core/error-handler");
|
|
6
|
+
/**
|
|
7
|
+
* Remote workflow storage that fetches workflows from a community registry.
|
|
8
|
+
* Implements security best practices and proper error handling.
|
|
9
|
+
* Similar to npm registry but for workflows.
|
|
10
|
+
*/
|
|
11
|
+
class RemoteWorkflowStorage {
|
|
12
|
+
config;
|
|
13
|
+
securityOptions;
|
|
14
|
+
constructor(config) {
|
|
15
|
+
// Validate and secure the configuration
|
|
16
|
+
this.validateConfig(config);
|
|
17
|
+
this.securityOptions = (0, storage_security_1.validateSecurityOptions)(config);
|
|
18
|
+
this.config = {
|
|
19
|
+
...this.securityOptions,
|
|
20
|
+
baseUrl: config.baseUrl.replace(/\/$/, ''), // Remove trailing slash
|
|
21
|
+
apiKey: config.apiKey || '',
|
|
22
|
+
timeout: config.timeout || 10000,
|
|
23
|
+
retryAttempts: config.retryAttempts || 3,
|
|
24
|
+
userAgent: config.userAgent || 'workrail-mcp-server/1.0'
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
validateConfig(config) {
|
|
28
|
+
if (!config.baseUrl) {
|
|
29
|
+
throw new error_handler_1.SecurityError('baseUrl is required for remote storage', 'config-validation');
|
|
30
|
+
}
|
|
31
|
+
// Validate URL security
|
|
32
|
+
(0, storage_security_1.validateSecureUrl)(config.baseUrl);
|
|
33
|
+
// Validate timeout and retry settings (allow shorter timeouts for testing)
|
|
34
|
+
if (config.timeout && (config.timeout < 100 || config.timeout > 60000)) {
|
|
35
|
+
throw new error_handler_1.SecurityError('timeout must be between 100ms and 60000ms', 'config-validation');
|
|
36
|
+
}
|
|
37
|
+
if (config.retryAttempts && (config.retryAttempts < 0 || config.retryAttempts > 10)) {
|
|
38
|
+
throw new error_handler_1.SecurityError('retryAttempts must be between 0 and 10', 'config-validation');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async loadAllWorkflows() {
|
|
42
|
+
try {
|
|
43
|
+
const response = await this.fetchWithRetry('/workflows');
|
|
44
|
+
const data = await this.parseResponse(response);
|
|
45
|
+
// Handle different response formats
|
|
46
|
+
const workflows = data.workflows || data.data || [];
|
|
47
|
+
return this.validateWorkflows(workflows);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
if (error instanceof error_handler_1.SecurityError || error instanceof error_handler_1.StorageError) {
|
|
51
|
+
throw error; // Re-throw known errors
|
|
52
|
+
}
|
|
53
|
+
// Transform unknown errors to storage errors for graceful degradation
|
|
54
|
+
throw new error_handler_1.StorageError(`Failed to load workflows from remote registry: ${error.message}`, 'remote-fetch');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async getWorkflowById(id) {
|
|
58
|
+
try {
|
|
59
|
+
const sanitizedId = (0, storage_security_1.sanitizeId)(id);
|
|
60
|
+
const response = await this.fetchWithRetry(`/workflows/${encodeURIComponent(sanitizedId)}`);
|
|
61
|
+
if (!response.ok) {
|
|
62
|
+
if (response.status === 404) {
|
|
63
|
+
return null; // Workflow not found
|
|
64
|
+
}
|
|
65
|
+
throw new error_handler_1.StorageError(`Remote registry returned ${response.status}: ${response.statusText}`, 'remote-fetch');
|
|
66
|
+
}
|
|
67
|
+
const workflow = await this.parseResponse(response);
|
|
68
|
+
// Verify the returned workflow ID matches what we requested
|
|
69
|
+
if (workflow.id !== sanitizedId) {
|
|
70
|
+
throw new error_handler_1.InvalidWorkflowError(sanitizedId, `Registry returned workflow with mismatched ID: ${workflow.id}`);
|
|
71
|
+
}
|
|
72
|
+
return workflow;
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (error instanceof error_handler_1.SecurityError ||
|
|
76
|
+
error instanceof error_handler_1.StorageError ||
|
|
77
|
+
error instanceof error_handler_1.InvalidWorkflowError) {
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
throw new error_handler_1.StorageError(`Failed to load workflow ${id} from remote registry: ${error.message}`, 'remote-fetch');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async listWorkflowSummaries() {
|
|
84
|
+
try {
|
|
85
|
+
const response = await this.fetchWithRetry('/workflows/summaries');
|
|
86
|
+
const data = await this.parseResponse(response);
|
|
87
|
+
const summaries = data.summaries || data.data || [];
|
|
88
|
+
return this.validateSummaries(summaries);
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
if (error instanceof error_handler_1.SecurityError || error instanceof error_handler_1.StorageError) {
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
throw new error_handler_1.StorageError(`Failed to load workflow summaries from remote registry: ${error.message}`, 'remote-fetch');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async save(workflow) {
|
|
98
|
+
try {
|
|
99
|
+
const sanitizedWorkflow = this.validateWorkflowForSave(workflow);
|
|
100
|
+
const response = await this.fetchWithRetry('/workflows', {
|
|
101
|
+
method: 'POST',
|
|
102
|
+
headers: {
|
|
103
|
+
'Content-Type': 'application/json',
|
|
104
|
+
...(this.config.apiKey && { 'Authorization': `Bearer ${this.config.apiKey}` })
|
|
105
|
+
},
|
|
106
|
+
body: JSON.stringify(sanitizedWorkflow)
|
|
107
|
+
});
|
|
108
|
+
if (!response.ok) {
|
|
109
|
+
const errorData = await this.parseResponse(response);
|
|
110
|
+
throw new error_handler_1.StorageError(`Failed to publish workflow: ${errorData.message || errorData.error || 'Unknown error'}`, 'remote-save');
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
if (error instanceof error_handler_1.SecurityError ||
|
|
115
|
+
error instanceof error_handler_1.StorageError ||
|
|
116
|
+
error instanceof error_handler_1.InvalidWorkflowError) {
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
throw new error_handler_1.StorageError(`Failed to save workflow to remote registry: ${error.message}`, 'remote-save');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async fetchWithRetry(path, options) {
|
|
123
|
+
const url = `${this.config.baseUrl}${path}`;
|
|
124
|
+
let lastError = null;
|
|
125
|
+
for (let attempt = 1; attempt <= this.config.retryAttempts; attempt++) {
|
|
126
|
+
try {
|
|
127
|
+
const controller = new AbortController();
|
|
128
|
+
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
129
|
+
const response = await fetch(url, {
|
|
130
|
+
...options,
|
|
131
|
+
signal: controller.signal,
|
|
132
|
+
headers: {
|
|
133
|
+
'User-Agent': this.config.userAgent,
|
|
134
|
+
'Accept': 'application/json',
|
|
135
|
+
...options?.headers
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
clearTimeout(timeoutId);
|
|
139
|
+
return response;
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
lastError = error;
|
|
143
|
+
if (attempt === this.config.retryAttempts) {
|
|
144
|
+
break; // Don't wait after the last attempt
|
|
145
|
+
}
|
|
146
|
+
// Exponential backoff with jitter
|
|
147
|
+
const delay = Math.pow(2, attempt) * 1000 + Math.random() * 1000;
|
|
148
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
throw new error_handler_1.StorageError(`Failed to fetch ${url} after ${this.config.retryAttempts} attempts: ${lastError?.message}`, 'network-timeout');
|
|
152
|
+
}
|
|
153
|
+
async parseResponse(response) {
|
|
154
|
+
try {
|
|
155
|
+
const text = await response.text();
|
|
156
|
+
if (!text) {
|
|
157
|
+
throw new error_handler_1.StorageError('Empty response from remote registry', 'parse-error');
|
|
158
|
+
}
|
|
159
|
+
return JSON.parse(text);
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
if (error instanceof error_handler_1.StorageError) {
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
throw new error_handler_1.StorageError(`Failed to parse response from remote registry: ${error.message}`, 'parse-error');
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
validateWorkflows(workflows) {
|
|
169
|
+
if (!Array.isArray(workflows)) {
|
|
170
|
+
throw new error_handler_1.StorageError('Remote registry returned invalid workflows data', 'validation-error');
|
|
171
|
+
}
|
|
172
|
+
return workflows.filter((workflow) => {
|
|
173
|
+
try {
|
|
174
|
+
// Basic validation - detailed validation should be handled by schema validator decorator
|
|
175
|
+
if (!workflow || typeof workflow !== 'object') {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
const wf = workflow;
|
|
179
|
+
if (!wf.id || !wf.name || !wf.steps) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
// Validate the ID is safe
|
|
183
|
+
(0, storage_security_1.sanitizeId)(wf.id);
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
return false; // Skip invalid workflows
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
validateSummaries(summaries) {
|
|
192
|
+
if (!Array.isArray(summaries)) {
|
|
193
|
+
throw new error_handler_1.StorageError('Remote registry returned invalid summaries data', 'validation-error');
|
|
194
|
+
}
|
|
195
|
+
return summaries.filter((summary) => {
|
|
196
|
+
try {
|
|
197
|
+
if (!summary || typeof summary !== 'object') {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
const s = summary;
|
|
201
|
+
if (!s.id || !s.name) {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
// Validate the ID is safe
|
|
205
|
+
(0, storage_security_1.sanitizeId)(s.id);
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
validateWorkflowForSave(workflow) {
|
|
214
|
+
if (!workflow || typeof workflow !== 'object') {
|
|
215
|
+
throw new error_handler_1.InvalidWorkflowError('unknown', 'Workflow must be a valid object');
|
|
216
|
+
}
|
|
217
|
+
if (!workflow.id || !workflow.name || !workflow.steps) {
|
|
218
|
+
throw new error_handler_1.InvalidWorkflowError(workflow.id || 'unknown', 'Workflow must have id, name, and steps');
|
|
219
|
+
}
|
|
220
|
+
// Sanitize the ID
|
|
221
|
+
const sanitizedId = (0, storage_security_1.sanitizeId)(workflow.id);
|
|
222
|
+
return {
|
|
223
|
+
...workflow,
|
|
224
|
+
id: sanitizedId
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
exports.RemoteWorkflowStorage = RemoteWorkflowStorage;
|
|
229
|
+
/**
|
|
230
|
+
* Multi-source workflow storage that combines bundled, local, and remote workflows.
|
|
231
|
+
* Uses composition to cleanly separate concerns.
|
|
232
|
+
*/
|
|
233
|
+
class CommunityWorkflowStorage {
|
|
234
|
+
sources;
|
|
235
|
+
remoteStorage;
|
|
236
|
+
constructor(bundledStorage, localStorage, remoteConfig) {
|
|
237
|
+
this.remoteStorage = new RemoteWorkflowStorage(remoteConfig);
|
|
238
|
+
this.sources = [bundledStorage, localStorage, this.remoteStorage];
|
|
239
|
+
}
|
|
240
|
+
async loadAllWorkflows() {
|
|
241
|
+
const allWorkflows = [];
|
|
242
|
+
const seenIds = new Set();
|
|
243
|
+
// Load from all sources, with later sources taking precedence
|
|
244
|
+
for (const source of this.sources) {
|
|
245
|
+
try {
|
|
246
|
+
const workflows = await source.loadAllWorkflows();
|
|
247
|
+
for (const workflow of workflows) {
|
|
248
|
+
if (seenIds.has(workflow.id)) {
|
|
249
|
+
// Replace existing workflow with same ID
|
|
250
|
+
const existingIndex = allWorkflows.findIndex(wf => wf.id === workflow.id);
|
|
251
|
+
if (existingIndex >= 0) {
|
|
252
|
+
allWorkflows[existingIndex] = workflow;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
allWorkflows.push(workflow);
|
|
257
|
+
seenIds.add(workflow.id);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
// For storage sources, we want to continue even if one fails
|
|
263
|
+
// This is intentional graceful degradation behavior
|
|
264
|
+
if (error instanceof error_handler_1.StorageError) {
|
|
265
|
+
console.warn(`Storage source failed (graceful degradation):`, error.message);
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
console.warn(`Unexpected error from storage source:`, error);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return allWorkflows;
|
|
273
|
+
}
|
|
274
|
+
async getWorkflowById(id) {
|
|
275
|
+
try {
|
|
276
|
+
const sanitizedId = (0, storage_security_1.sanitizeId)(id);
|
|
277
|
+
// Search in reverse order (later sources take precedence)
|
|
278
|
+
for (let i = this.sources.length - 1; i >= 0; i--) {
|
|
279
|
+
try {
|
|
280
|
+
const workflow = await this.sources[i].getWorkflowById(sanitizedId);
|
|
281
|
+
if (workflow) {
|
|
282
|
+
return workflow;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
// Continue searching other sources if one fails
|
|
287
|
+
if (error instanceof error_handler_1.StorageError) {
|
|
288
|
+
console.warn(`Storage source failed for workflow ${sanitizedId}:`, error.message);
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
console.warn(`Unexpected error from storage source for workflow ${sanitizedId}:`, error);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
if (error instanceof error_handler_1.SecurityError || error instanceof error_handler_1.InvalidWorkflowError) {
|
|
299
|
+
throw error; // Don't continue with invalid IDs
|
|
300
|
+
}
|
|
301
|
+
throw new error_handler_1.StorageError(`Failed to retrieve workflow ${id}: ${error.message}`, 'multi-source-error');
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
async listWorkflowSummaries() {
|
|
305
|
+
// Reuse loadAllWorkflows for consistency and caching benefits
|
|
306
|
+
const workflows = await this.loadAllWorkflows();
|
|
307
|
+
return workflows.map(workflow => ({
|
|
308
|
+
id: workflow.id,
|
|
309
|
+
name: workflow.name,
|
|
310
|
+
description: workflow.description,
|
|
311
|
+
category: 'community',
|
|
312
|
+
version: workflow.version
|
|
313
|
+
}));
|
|
314
|
+
}
|
|
315
|
+
async save(workflow) {
|
|
316
|
+
// Delegate to remote storage for publishing
|
|
317
|
+
return this.remoteStorage.save(workflow);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
exports.CommunityWorkflowStorage = CommunityWorkflowStorage;
|
|
321
|
+
//# sourceMappingURL=remote-workflow-storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote-workflow-storage.js","sourceRoot":"","sources":["../../../src/infrastructure/storage/remote-workflow-storage.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage security utilities extracted from FileWorkflowStorage patterns
|
|
3
|
+
* for consistent security across all storage implementations.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Sanitize and validate workflow identifiers for security.
|
|
7
|
+
* Prevents null byte injection and enforces valid character set.
|
|
8
|
+
*
|
|
9
|
+
* @param id - The workflow identifier to validate
|
|
10
|
+
* @returns Normalized and validated identifier
|
|
11
|
+
* @throws SecurityError for null bytes
|
|
12
|
+
* @throws InvalidWorkflowError for invalid characters
|
|
13
|
+
*/
|
|
14
|
+
export declare function sanitizeId(id: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* Assert that a resolved path stays within the specified base directory.
|
|
17
|
+
* Prevents path traversal attacks by ensuring no directory escape.
|
|
18
|
+
*
|
|
19
|
+
* @param resolvedPath - The fully resolved absolute path to check
|
|
20
|
+
* @param baseDir - The base directory that should contain the path
|
|
21
|
+
* @throws SecurityError if path escapes the base directory
|
|
22
|
+
*/
|
|
23
|
+
export declare function assertWithinBase(resolvedPath: string, baseDir: string): void;
|
|
24
|
+
/**
|
|
25
|
+
* Validate file size against security limits.
|
|
26
|
+
* Prevents resource exhaustion and DoS attacks via oversized files.
|
|
27
|
+
*
|
|
28
|
+
* @param fileSize - Size of the file in bytes
|
|
29
|
+
* @param maxSize - Maximum allowed size in bytes
|
|
30
|
+
* @param context - Context for error reporting (e.g., filename)
|
|
31
|
+
* @throws SecurityError if file exceeds size limit
|
|
32
|
+
*/
|
|
33
|
+
export declare function validateFileSize(fileSize: number, maxSize: number, context?: string): void;
|
|
34
|
+
/**
|
|
35
|
+
* Sanitize and resolve a file path safely within a base directory.
|
|
36
|
+
* Combines path resolution with base directory validation.
|
|
37
|
+
*
|
|
38
|
+
* @param basePath - The base directory
|
|
39
|
+
* @param relativePath - The relative path to resolve
|
|
40
|
+
* @returns Safely resolved absolute path
|
|
41
|
+
* @throws SecurityError if the resolved path escapes the base
|
|
42
|
+
*/
|
|
43
|
+
export declare function securePathResolve(basePath: string, relativePath: string): string;
|
|
44
|
+
/**
|
|
45
|
+
* Validate URL security for remote storage implementations.
|
|
46
|
+
* Ensures URLs use safe protocols and don't target local resources.
|
|
47
|
+
*
|
|
48
|
+
* @param url - The URL to validate
|
|
49
|
+
* @throws SecurityError for unsafe URLs
|
|
50
|
+
*/
|
|
51
|
+
export declare function validateSecureUrl(url: string): void;
|
|
52
|
+
/**
|
|
53
|
+
* Common security options interface for storage implementations.
|
|
54
|
+
*/
|
|
55
|
+
export interface StorageSecurityOptions {
|
|
56
|
+
/** Maximum file size in bytes (default: 1MB) */
|
|
57
|
+
maxFileSizeBytes?: number;
|
|
58
|
+
/** Whether to allow HTTP URLs (default: false, HTTPS only) */
|
|
59
|
+
allowHttp?: boolean;
|
|
60
|
+
/** Custom allowed URL patterns (advanced use) */
|
|
61
|
+
allowedUrlPatterns?: RegExp[];
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Default security configuration following FileWorkflowStorage patterns.
|
|
65
|
+
*/
|
|
66
|
+
export declare const DEFAULT_SECURITY_OPTIONS: Required<StorageSecurityOptions>;
|
|
67
|
+
/**
|
|
68
|
+
* Validate security options and apply defaults.
|
|
69
|
+
*
|
|
70
|
+
* @param options - User-provided security options
|
|
71
|
+
* @returns Validated options with defaults applied
|
|
72
|
+
*/
|
|
73
|
+
export declare function validateSecurityOptions(options?: StorageSecurityOptions): Required<StorageSecurityOptions>;
|
|
74
|
+
//# sourceMappingURL=storage-security.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage-security.d.ts","sourceRoot":"","sources":["../../src/utils/storage-security.ts"],"names":[],"mappings":"AAGA;;;GAGG;AAEH;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAW7C;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAI5E;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAQ1F;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAIhF;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CA8BnD;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,gDAAgD;IAChD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,8DAA8D;IAC9D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,iDAAiD;IACjD,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC/B;AAED;;GAEG;AACH,eAAO,MAAM,wBAAwB,EAAE,QAAQ,CAAC,sBAAsB,CAIrE,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,GAAE,sBAA2B,GAAG,QAAQ,CAAC,sBAAsB,CAAC,CAY9G"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_SECURITY_OPTIONS = void 0;
|
|
4
|
+
exports.sanitizeId = sanitizeId;
|
|
5
|
+
exports.assertWithinBase = assertWithinBase;
|
|
6
|
+
exports.validateFileSize = validateFileSize;
|
|
7
|
+
exports.securePathResolve = securePathResolve;
|
|
8
|
+
exports.validateSecureUrl = validateSecureUrl;
|
|
9
|
+
exports.validateSecurityOptions = validateSecurityOptions;
|
|
10
|
+
const tslib_1 = require("tslib");
|
|
11
|
+
const path_1 = tslib_1.__importDefault(require("path"));
|
|
12
|
+
const error_handler_1 = require("../core/error-handler");
|
|
13
|
+
/**
|
|
14
|
+
* Storage security utilities extracted from FileWorkflowStorage patterns
|
|
15
|
+
* for consistent security across all storage implementations.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Sanitize and validate workflow identifiers for security.
|
|
19
|
+
* Prevents null byte injection and enforces valid character set.
|
|
20
|
+
*
|
|
21
|
+
* @param id - The workflow identifier to validate
|
|
22
|
+
* @returns Normalized and validated identifier
|
|
23
|
+
* @throws SecurityError for null bytes
|
|
24
|
+
* @throws InvalidWorkflowError for invalid characters
|
|
25
|
+
*/
|
|
26
|
+
function sanitizeId(id) {
|
|
27
|
+
if (id.includes('\u0000')) {
|
|
28
|
+
throw new error_handler_1.SecurityError('Null byte detected in identifier', 'sanitizeId');
|
|
29
|
+
}
|
|
30
|
+
const normalised = id.normalize('NFC');
|
|
31
|
+
const valid = /^[a-zA-Z0-9_-]+$/.test(normalised);
|
|
32
|
+
if (!valid) {
|
|
33
|
+
throw new error_handler_1.InvalidWorkflowError(id, 'Invalid characters in workflow id');
|
|
34
|
+
}
|
|
35
|
+
return normalised;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Assert that a resolved path stays within the specified base directory.
|
|
39
|
+
* Prevents path traversal attacks by ensuring no directory escape.
|
|
40
|
+
*
|
|
41
|
+
* @param resolvedPath - The fully resolved absolute path to check
|
|
42
|
+
* @param baseDir - The base directory that should contain the path
|
|
43
|
+
* @throws SecurityError if path escapes the base directory
|
|
44
|
+
*/
|
|
45
|
+
function assertWithinBase(resolvedPath, baseDir) {
|
|
46
|
+
if (!resolvedPath.startsWith(baseDir + path_1.default.sep) && resolvedPath !== baseDir) {
|
|
47
|
+
throw new error_handler_1.SecurityError('Path escapes storage sandbox', 'file-access');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Validate file size against security limits.
|
|
52
|
+
* Prevents resource exhaustion and DoS attacks via oversized files.
|
|
53
|
+
*
|
|
54
|
+
* @param fileSize - Size of the file in bytes
|
|
55
|
+
* @param maxSize - Maximum allowed size in bytes
|
|
56
|
+
* @param context - Context for error reporting (e.g., filename)
|
|
57
|
+
* @throws SecurityError if file exceeds size limit
|
|
58
|
+
*/
|
|
59
|
+
function validateFileSize(fileSize, maxSize, context) {
|
|
60
|
+
if (fileSize > maxSize) {
|
|
61
|
+
const contextStr = context ? ` (${context})` : '';
|
|
62
|
+
throw new error_handler_1.SecurityError(`File exceeds size limit of ${maxSize} bytes${contextStr}`, 'file-size');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Sanitize and resolve a file path safely within a base directory.
|
|
67
|
+
* Combines path resolution with base directory validation.
|
|
68
|
+
*
|
|
69
|
+
* @param basePath - The base directory
|
|
70
|
+
* @param relativePath - The relative path to resolve
|
|
71
|
+
* @returns Safely resolved absolute path
|
|
72
|
+
* @throws SecurityError if the resolved path escapes the base
|
|
73
|
+
*/
|
|
74
|
+
function securePathResolve(basePath, relativePath) {
|
|
75
|
+
const resolvedPath = path_1.default.resolve(basePath, relativePath);
|
|
76
|
+
assertWithinBase(resolvedPath, basePath);
|
|
77
|
+
return resolvedPath;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Validate URL security for remote storage implementations.
|
|
81
|
+
* Ensures URLs use safe protocols and don't target local resources.
|
|
82
|
+
*
|
|
83
|
+
* @param url - The URL to validate
|
|
84
|
+
* @throws SecurityError for unsafe URLs
|
|
85
|
+
*/
|
|
86
|
+
function validateSecureUrl(url) {
|
|
87
|
+
try {
|
|
88
|
+
const parsed = new URL(url);
|
|
89
|
+
// Only allow HTTPS and HTTP protocols
|
|
90
|
+
if (!['https:', 'http:'].includes(parsed.protocol)) {
|
|
91
|
+
throw new error_handler_1.SecurityError(`Unsafe protocol: ${parsed.protocol}. Only HTTP/HTTPS allowed`, 'url-validation');
|
|
92
|
+
}
|
|
93
|
+
// Prevent localhost and private IP access
|
|
94
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
95
|
+
if (hostname === 'localhost' ||
|
|
96
|
+
hostname === '127.0.0.1' ||
|
|
97
|
+
hostname.startsWith('192.168.') ||
|
|
98
|
+
hostname.startsWith('10.') ||
|
|
99
|
+
hostname.startsWith('172.')) {
|
|
100
|
+
throw new error_handler_1.SecurityError('Access to local/private networks not allowed', 'url-validation');
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
if (error instanceof error_handler_1.SecurityError) {
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
throw new error_handler_1.SecurityError(`Invalid URL format: ${url}`, 'url-validation');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Default security configuration following FileWorkflowStorage patterns.
|
|
112
|
+
*/
|
|
113
|
+
exports.DEFAULT_SECURITY_OPTIONS = {
|
|
114
|
+
maxFileSizeBytes: 1_000_000, // 1 MB
|
|
115
|
+
allowHttp: false,
|
|
116
|
+
allowedUrlPatterns: []
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* Validate security options and apply defaults.
|
|
120
|
+
*
|
|
121
|
+
* @param options - User-provided security options
|
|
122
|
+
* @returns Validated options with defaults applied
|
|
123
|
+
*/
|
|
124
|
+
function validateSecurityOptions(options = {}) {
|
|
125
|
+
const validated = { ...exports.DEFAULT_SECURITY_OPTIONS, ...options };
|
|
126
|
+
if (validated.maxFileSizeBytes <= 0) {
|
|
127
|
+
throw new error_handler_1.SecurityError('maxFileSizeBytes must be positive', 'config-validation');
|
|
128
|
+
}
|
|
129
|
+
if (validated.maxFileSizeBytes > 100_000_000) { // 100MB upper limit
|
|
130
|
+
throw new error_handler_1.SecurityError('maxFileSizeBytes exceeds reasonable limit (100MB)', 'config-validation');
|
|
131
|
+
}
|
|
132
|
+
return validated;
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=storage-security.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage-security.js","sourceRoot":"","sources":["../../src/utils/storage-security.ts"],"names":[],"mappings":"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"}
|