import fs from 'fs'; import path from 'path'; function walk(dir) { let results = []; const entries = fs.readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { results = results.concat(walk(fullPath)); } else if (/\.(tsx|ts|jsx|js|css|scss)$/.test(entry.name)) { results.push(fullPath); } } return results; } const files = walk('src'); console.log(`Found ${files.length} source files under src/ to inspect.`); const findings = []; // Patterns: // 1. Any shadow class except shadow-none, shadow-[none], shadow-[0px] const shadowPattern = /\bshadow(-[a-zA-Z0-9_/\[\]#.-]+)?\b/g; // 2. Any rounded class except rounded-none, rounded-[0px], rounded-0, rounded-[0] const roundedPattern = /\brounded(-[a-zA-Z0-9_/\[\]#.-]+)?\b/g; // 3. bg-gradient-* const bgGradientPattern = /\bbg-gradient(-[a-zA-Z0-9_-]+)?\b/g; // 4. from-* gradient stop const fromPattern = /\bfrom-[a-zA-Z0-9_/\[\]#.-]+\b/g; // 5. via-* gradient stop const viaPattern = /\bvia-[a-zA-Z0-9_/\[\]#.-]+\b/g; // 6. to-* gradient stop (e.g., to-black, to-white, to-slate-900, to-[#...]) const toPattern = /\bto-(black|white|slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose|transparent|current|\[#[a-fA-F0-9]+\])(-[0-9]+)?(\/[0-9]+)?\b/g; // 7. CSS box-shadow const cssBoxShadowPattern = /box-shadow\s*:\s*([^;]+)/gi; // 8. CSS border-radius const cssBorderRadiusPattern = /border-radius\s*:\s*([^;]+)/gi; for (const filePath of files) { const content = fs.readFileSync(filePath, 'utf8'); const lines = content.split('\n'); lines.forEach((line, index) => { const lineNum = index + 1; const trimmed = line.trim(); // Check CSS properties let match; while ((match = cssBoxShadowPattern.exec(line)) !== null) { const val = match[1].trim(); if (val !== 'none' && val !== '0' && val !== '0px') { findings.push({ type: 'CSS_BOX_SHADOW', file: filePath, line: lineNum, matched: match[0], raw: trimmed, }); } } while ((match = cssBorderRadiusPattern.exec(line)) !== null) { const val = match[1].trim(); if (val !== '0' && val !== '0px' && val !== '0 0 0 0') { findings.push({ type: 'CSS_BORDER_RADIUS', file: filePath, line: lineNum, matched: match[0], raw: trimmed, }); } } // Check Tailwind / Class matches // Look for string literals or className definitions // To be thorough, check any occurrence in the file let rMatch; while ((rMatch = roundedPattern.exec(line)) !== null) { const cls = rMatch[0]; if ( cls !== 'rounded-none' && cls !== 'rounded-[0px]' && cls !== 'rounded-0' && cls !== 'rounded-[0]' ) { // Exclude JS identifier words like roundedTotal, Math.round, etc. if not a class token // A class token is typically in quotes, backticks, or preceded/followed by whitespace/quotes const before = line[rMatch.index - 1] || ' '; const after = line[rMatch.index + cls.length] || ' '; if ( /['"`\s=:({[,>]/.test(before) && /['"`\s=:)}],<]/.test(after) ) { findings.push({ type: 'FORBIDDEN_ROUNDED_CLASS', file: filePath, line: lineNum, matched: cls, raw: trimmed, }); } } } let sMatch; while ((sMatch = shadowPattern.exec(line)) !== null) { const cls = sMatch[0]; if ( cls !== 'shadow-none' && cls !== 'shadow-[none]' && cls !== 'shadow-[0px]' ) { const before = line[sMatch.index - 1] || ' '; const after = line[sMatch.index + cls.length] || ' '; if ( /['"`\s=:({[,>]/.test(before) && /['"`\s=:)}],<]/.test(after) ) { findings.push({ type: 'FORBIDDEN_SHADOW_CLASS', file: filePath, line: lineNum, matched: cls, raw: trimmed, }); } } } let gMatch; while ((gMatch = bgGradientPattern.exec(line)) !== null) { findings.push({ type: 'FORBIDDEN_BG_GRADIENT_CLASS', file: filePath, line: lineNum, matched: gMatch[0], raw: trimmed, }); } let fMatch; while ((fMatch = fromPattern.exec(line)) !== null) { // Exclude JS imports: import { ... } from '...' if (!/\bimport\b|\bexport\b/.test(line)) { findings.push({ type: 'FORBIDDEN_FROM_GRADIENT_CLASS', file: filePath, line: lineNum, matched: fMatch[0], raw: trimmed, }); } } let vMatch; while ((vMatch = viaPattern.exec(line)) !== null) { findings.push({ type: 'FORBIDDEN_VIA_GRADIENT_CLASS', file: filePath, line: lineNum, matched: vMatch[0], raw: trimmed, }); } let tMatch; while ((tMatch = toPattern.exec(line)) !== null) { // Check if it's inside className or class attribute or tailwind string findings.push({ type: 'FORBIDDEN_TO_GRADIENT_CLASS', file: filePath, line: lineNum, matched: tMatch[0], raw: trimmed, }); } }); } console.log('=== ADVERSARIAL SCAN REPORT ==='); console.log(`Scanned files: ${files.length}`); console.log(`Total violations detected: ${findings.length}`); if (findings.length > 0) { console.log('\n--- VIOLATIONS LIST ---'); findings.forEach((f, idx) => { console.log(`${idx + 1}. [${f.type}] ${f.file}:${f.line}`); console.log(` Token: "${f.matched}"`); console.log(` Line: ${f.raw}`); }); process.exit(1); } else { console.log('CLEAN: No forbidden rounded, shadow, gradient, or box-shadow tokens found in src/'); process.exit(0); }