Brings the working codebase (Next.js app, auth system, Stripe billing, Docker/deploy config, tests, docs) into version control on top of the placeholder initial commit, and adds account self-deletion (Danger Zone in Settings, password + typed-email confirmation, cascading DB cleanup, Stripe cancellation) per GDPR right-to-erasure. Excludes local build caches, node_modules, and internal agent scratch files; .gitignore hardened to keep those out going forward. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
198 lines
5.9 KiB
JavaScript
198 lines
5.9 KiB
JavaScript
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);
|
|
}
|