- Layer 4 EntropyScanner: Shannon entropy, Base32/Base64 detection, CVE-2025-55284 ping/nslookup exfil, EchoLeak markdown pattern, DNS tunneling (iodine/dnscat) - Layer 5 UnicodeScanner: ASCII Smuggling (U+E0000 Tags Block), Variant Selectors, Zero-Width steganography, CamoLeak image-ordering (CVE-2025-53773), homoglyphs, BiDi override, high-entropy URL params - 30 DNS covert channel rules (dns-001 to dns-030) - ATLASMapper: 29 techniques (ATLAS v5.4.0 Feb 2026), added AML.T0062 (Agent Tool Invocation), AML.TA0015 (C2 tactic), memory poisoning, multi-agent trust, CamoLeak, Unicode steganography mappings - Rule count: 72 → 102 - Build: tsup 316ms, zero TypeScript errors
122 lines
3.2 KiB
TypeScript
122 lines
3.2 KiB
TypeScript
/**
|
|
* ShieldX Database Migration Runner
|
|
*
|
|
* Reads DATABASE_URL from environment, connects to PostgreSQL,
|
|
* and runs all SQL migration files in order.
|
|
*
|
|
* Usage:
|
|
* npm run db:migrate # Run pending migrations
|
|
* npm run db:migrate -- --reset # Drop all shieldx_* tables, then re-run
|
|
*/
|
|
|
|
import { readFileSync, readdirSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import { Client } from 'pg';
|
|
|
|
const MIGRATIONS_DIR = join(__dirname, '..', 'src', 'learning', 'migrations');
|
|
|
|
const TABLE_DROP_ORDER = [
|
|
'shieldx_drift_reports',
|
|
'shieldx_conversation_turns',
|
|
'shieldx_conversation_state',
|
|
'shieldx_attack_edges',
|
|
'shieldx_attack_nodes',
|
|
'shieldx_embeddings',
|
|
'shieldx_feedback',
|
|
'shieldx_incidents',
|
|
'shieldx_sessions',
|
|
'shieldx_patterns',
|
|
];
|
|
|
|
function getMigrationFiles(): readonly string[] {
|
|
const files = readdirSync(MIGRATIONS_DIR)
|
|
.filter((f) => f.endsWith('.sql'))
|
|
.sort();
|
|
|
|
if (files.length === 0) {
|
|
throw new Error(`No .sql files found in ${MIGRATIONS_DIR}`);
|
|
}
|
|
|
|
return files;
|
|
}
|
|
|
|
async function createClient(): Promise<Client> {
|
|
const databaseUrl = process.env.DATABASE_URL;
|
|
|
|
if (!databaseUrl) {
|
|
throw new Error(
|
|
'DATABASE_URL environment variable is required.\n' +
|
|
'Example: DATABASE_URL=postgresql://user:pass@localhost:5432/shieldx',
|
|
);
|
|
}
|
|
|
|
const client = new Client({ connectionString: databaseUrl });
|
|
await client.connect();
|
|
return client;
|
|
}
|
|
|
|
async function dropAllTables(client: Client): Promise<void> {
|
|
console.log('\n--- RESET MODE: Dropping all shieldx_* tables ---\n');
|
|
|
|
for (const table of TABLE_DROP_ORDER) {
|
|
try {
|
|
await client.query(`DROP TABLE IF EXISTS ${table} CASCADE`);
|
|
console.log(` Dropped: ${table}`);
|
|
} catch (err) {
|
|
const message = err instanceof Error ? err.message : String(err);
|
|
console.warn(` Warning dropping ${table}: ${message}`);
|
|
}
|
|
}
|
|
|
|
console.log('\n--- All tables dropped ---\n');
|
|
}
|
|
|
|
async function runMigration(
|
|
client: Client,
|
|
filename: string,
|
|
): Promise<void> {
|
|
const filepath = join(MIGRATIONS_DIR, filename);
|
|
const sql = readFileSync(filepath, 'utf-8');
|
|
|
|
const startMs = performance.now();
|
|
await client.query(sql);
|
|
const durationMs = (performance.now() - startMs).toFixed(1);
|
|
|
|
console.log(` [OK] ${filename} (${durationMs}ms)`);
|
|
}
|
|
|
|
async function main(): Promise<void> {
|
|
const args = process.argv.slice(2);
|
|
const resetMode = args.includes('--reset');
|
|
|
|
console.log('ShieldX Database Migration Runner');
|
|
console.log('=================================\n');
|
|
|
|
const migrationFiles = getMigrationFiles();
|
|
console.log(`Found ${migrationFiles.length} migration(s) in ${MIGRATIONS_DIR}\n`);
|
|
|
|
const client = await createClient();
|
|
|
|
try {
|
|
if (resetMode) {
|
|
await dropAllTables(client);
|
|
}
|
|
|
|
console.log('Running migrations:\n');
|
|
|
|
for (const file of migrationFiles) {
|
|
await runMigration(client, file);
|
|
}
|
|
|
|
console.log('\nAll migrations completed successfully.');
|
|
} catch (err) {
|
|
const message = err instanceof Error ? err.message : String(err);
|
|
console.error(`\nMigration failed: ${message}`);
|
|
process.exitCode = 1;
|
|
} finally {
|
|
await client.end();
|
|
}
|
|
}
|
|
|
|
main();
|