template-sanitizer.tsβ’5 kB
import { logger } from './logger';
/**
* Configuration for template sanitization
*/
export interface SanitizerConfig {
problematicTokens: string[];
tokenPatterns: RegExp[];
replacements: Map<string, string>;
}
/**
* Default sanitizer configuration
*/
export const defaultSanitizerConfig: SanitizerConfig = {
problematicTokens: [
// Specific tokens can be added here if needed
],
tokenPatterns: [
/apify_api_[A-Za-z0-9]+/g,
/sk-[A-Za-z0-9]+/g, // OpenAI tokens
/pat[A-Za-z0-9_]{40,}/g, // Airtable Personal Access Tokens
/ghp_[A-Za-z0-9]{36,}/g, // GitHub Personal Access Tokens
/gho_[A-Za-z0-9]{36,}/g, // GitHub OAuth tokens
/Bearer\s+[A-Za-z0-9\-._~+\/]+=*/g // Generic bearer tokens
],
replacements: new Map([
['apify_api_', 'apify_api_YOUR_TOKEN_HERE'],
['sk-', 'sk-YOUR_OPENAI_KEY_HERE'],
['pat', 'patYOUR_AIRTABLE_TOKEN_HERE'],
['ghp_', 'ghp_YOUR_GITHUB_TOKEN_HERE'],
['gho_', 'gho_YOUR_GITHUB_TOKEN_HERE'],
['Bearer ', 'Bearer YOUR_TOKEN_HERE']
])
};
/**
* Template sanitizer for removing API tokens from workflow templates
*/
export class TemplateSanitizer {
constructor(private config: SanitizerConfig = defaultSanitizerConfig) {}
/**
* Add a new problematic token to sanitize
*/
addProblematicToken(token: string): void {
if (!this.config.problematicTokens.includes(token)) {
this.config.problematicTokens.push(token);
logger.info(`Added problematic token to sanitizer: ${token.substring(0, 10)}...`);
}
}
/**
* Add a new token pattern to detect
*/
addTokenPattern(pattern: RegExp, replacement: string): void {
this.config.tokenPatterns.push(pattern);
const prefix = pattern.source.match(/^([^[]+)/)?.[1] || '';
if (prefix) {
this.config.replacements.set(prefix, replacement);
}
}
/**
* Sanitize a workflow object
*/
sanitizeWorkflow(workflow: any): { sanitized: any; wasModified: boolean } {
if (!workflow) {
return { sanitized: workflow, wasModified: false };
}
const original = JSON.stringify(workflow);
let sanitized = this.sanitizeObject(workflow);
// Remove sensitive workflow data
if (sanitized && sanitized.pinData) {
delete sanitized.pinData;
}
if (sanitized && sanitized.executionId) {
delete sanitized.executionId;
}
if (sanitized && sanitized.staticData) {
delete sanitized.staticData;
}
const wasModified = JSON.stringify(sanitized) !== original;
return { sanitized, wasModified };
}
/**
* Check if a workflow needs sanitization
*/
needsSanitization(workflow: any): boolean {
const workflowStr = JSON.stringify(workflow);
// Check for known problematic tokens
for (const token of this.config.problematicTokens) {
if (workflowStr.includes(token)) {
return true;
}
}
// Check for token patterns
for (const pattern of this.config.tokenPatterns) {
pattern.lastIndex = 0; // Reset regex state
if (pattern.test(workflowStr)) {
return true;
}
}
return false;
}
/**
* Get list of detected tokens in a workflow
*/
detectTokens(workflow: any): string[] {
const workflowStr = JSON.stringify(workflow);
const detectedTokens: string[] = [];
// Check for known problematic tokens
for (const token of this.config.problematicTokens) {
if (workflowStr.includes(token)) {
detectedTokens.push(token);
}
}
// Check for token patterns
for (const pattern of this.config.tokenPatterns) {
pattern.lastIndex = 0; // Reset regex state
const matches = workflowStr.match(pattern);
if (matches) {
detectedTokens.push(...matches);
}
}
return [...new Set(detectedTokens)]; // Remove duplicates
}
private sanitizeObject(obj: any): any {
if (typeof obj === 'string') {
return this.replaceTokens(obj);
} else if (Array.isArray(obj)) {
return obj.map(item => this.sanitizeObject(item));
} else if (obj && typeof obj === 'object') {
const result: any = {};
for (const key in obj) {
result[key] = this.sanitizeObject(obj[key]);
}
return result;
}
return obj;
}
private replaceTokens(str: string): string {
let result = str;
// Replace known problematic tokens
this.config.problematicTokens.forEach(token => {
result = result.replace(new RegExp(token, 'g'), 'YOUR_API_TOKEN_HERE');
});
// Replace pattern-matched tokens
this.config.tokenPatterns.forEach(pattern => {
result = result.replace(pattern, (match) => {
// Find the best replacement based on prefix
for (const [prefix, replacement] of this.config.replacements) {
if (match.startsWith(prefix)) {
return replacement;
}
}
return 'YOUR_TOKEN_HERE';
});
});
return result;
}
}