import type { DomainProfile } from '../profiles/domain-profile.js'; export interface CrawlSafetyDecision { allowed: boolean; reason?: string; severity: 'info' | 'warn' | 'block'; } const _extraDestructive = process.env.CRAWL_EXTRA_DESTRUCTIVE_PATTERNS ? process.env.CRAWL_EXTRA_DESTRUCTIVE_PATTERNS.split(',').map((p) => p.trim()).filter(Boolean) : []; const DESTRUCTIVE_ACTION = new RegExp( `\\b(delete|remove|destroy|deactivate|disable|cancel|refund|void|transfer|withdraw|pay now|place order|submit order|confirm payment|send money|publish|approve|reject|bulk update|drop${_extraDestructive.length ? `|${_extraDestructive.join('|')}` : ''})\\b`, 'i', ); const SECRET_LIKE = /\b(api[_ -]?key|secret|token|password|authorization|cookie|set-cookie)\b/i; export function assessActionSafety(label: string, profile: DomainProfile): CrawlSafetyDecision { const riskyByProfile = profile.riskyActions.some((risk) => label.toLowerCase().includes(risk.toLowerCase())); if (DESTRUCTIVE_ACTION.test(label) || riskyByProfile) { return { allowed: false, severity: 'block', reason: `Blocked risky action "${label}" for ${profile.industry} profile.`, }; } return { allowed: true, severity: 'info' }; } export function redactSensitiveValue(value: string): string { let redacted = value .replace(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi, '[email]') .replace(/\b(?:\+?\d[\d -]{8,}\d)\b/g, '[phone-or-id]') .replace(/\b(?:\d[ -]*?){13,19}\b/g, '[card-like-number]') .replace(/\b[A-Za-z0-9_-]{24,}\b/g, '[token-like-value]'); if (SECRET_LIKE.test(redacted)) redacted = redacted.replace(/:\s*[^,\s}]+/g, ': [redacted]'); return redacted; } export function redactJson(value: unknown): unknown { if (typeof value === 'string') return redactSensitiveValue(value); if (Array.isArray(value)) return value.map(redactJson); if (value && typeof value === 'object') { const out: Record = {}; for (const [key, item] of Object.entries(value)) { if (SECRET_LIKE.test(key)) out[key] = '[redacted]'; else out[key] = redactJson(item); } return out; } return value; }