@kevinrabun/judges 3.20.14 → 3.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +68 -0
- package/dist/api.d.ts +42 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +49 -1
- package/dist/api.js.map +1 -1
- package/dist/ast/taint-tracker.d.ts +3 -1
- package/dist/ast/taint-tracker.d.ts.map +1 -1
- package/dist/ast/taint-tracker.js +523 -12
- package/dist/ast/taint-tracker.js.map +1 -1
- package/dist/cli.d.ts +13 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +124 -19
- package/dist/cli.js.map +1 -1
- package/dist/commands/benchmark.d.ts +28 -0
- package/dist/commands/benchmark.d.ts.map +1 -1
- package/dist/commands/benchmark.js +1058 -1
- package/dist/commands/benchmark.js.map +1 -1
- package/dist/config.d.ts +17 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +88 -0
- package/dist/config.js.map +1 -1
- package/dist/dedup.d.ts +23 -0
- package/dist/dedup.d.ts.map +1 -1
- package/dist/dedup.js +123 -0
- package/dist/dedup.js.map +1 -1
- package/dist/evaluators/authentication.d.ts +2 -2
- package/dist/evaluators/authentication.d.ts.map +1 -1
- package/dist/evaluators/authentication.js +26 -2
- package/dist/evaluators/authentication.js.map +1 -1
- package/dist/evaluators/cybersecurity.d.ts +2 -2
- package/dist/evaluators/cybersecurity.d.ts.map +1 -1
- package/dist/evaluators/cybersecurity.js +58 -5
- package/dist/evaluators/cybersecurity.js.map +1 -1
- package/dist/evaluators/framework-safety.d.ts.map +1 -1
- package/dist/evaluators/framework-safety.js +855 -365
- package/dist/evaluators/framework-safety.js.map +1 -1
- package/dist/evaluators/index.d.ts +1 -1
- package/dist/evaluators/index.d.ts.map +1 -1
- package/dist/evaluators/index.js +6 -2
- package/dist/evaluators/index.js.map +1 -1
- package/dist/evaluators/performance.d.ts +2 -2
- package/dist/evaluators/performance.d.ts.map +1 -1
- package/dist/evaluators/performance.js +33 -4
- package/dist/evaluators/performance.js.map +1 -1
- package/dist/evaluators/project.d.ts.map +1 -1
- package/dist/evaluators/project.js +223 -13
- package/dist/evaluators/project.js.map +1 -1
- package/dist/evaluators/shared.d.ts +31 -3
- package/dist/evaluators/shared.d.ts.map +1 -1
- package/dist/evaluators/shared.js +145 -11
- package/dist/evaluators/shared.js.map +1 -1
- package/dist/evaluators/v2.d.ts.map +1 -1
- package/dist/evaluators/v2.js +8 -0
- package/dist/evaluators/v2.js.map +1 -1
- package/dist/formatters/csv.d.ts +17 -0
- package/dist/formatters/csv.d.ts.map +1 -0
- package/dist/formatters/csv.js +54 -0
- package/dist/formatters/csv.js.map +1 -0
- package/dist/language-patterns.d.ts +136 -0
- package/dist/language-patterns.d.ts.map +1 -1
- package/dist/language-patterns.js +155 -1
- package/dist/language-patterns.js.map +1 -1
- package/dist/patches/index.d.ts.map +1 -1
- package/dist/patches/index.js +210 -0
- package/dist/patches/index.js.map +1 -1
- package/dist/presets.d.ts +14 -0
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +72 -0
- package/dist/presets.js.map +1 -1
- package/dist/scoring.d.ts.map +1 -1
- package/dist/scoring.js +43 -4
- package/dist/scoring.js.map +1 -1
- package/dist/tools/register-fix.d.ts +6 -0
- package/dist/tools/register-fix.d.ts.map +1 -0
- package/dist/tools/register-fix.js +153 -0
- package/dist/tools/register-fix.js.map +1 -0
- package/dist/tools/register-workflow.d.ts.map +1 -1
- package/dist/tools/register-workflow.js +79 -0
- package/dist/tools/register-workflow.js.map +1 -1
- package/dist/tools/register-workspace.d.ts +3 -0
- package/dist/tools/register-workspace.d.ts.map +1 -0
- package/dist/tools/register-workspace.js +215 -0
- package/dist/tools/register-workspace.js.map +1 -0
- package/dist/tools/register.d.ts +1 -1
- package/dist/tools/register.d.ts.map +1 -1
- package/dist/tools/register.js +5 -1
- package/dist/tools/register.js.map +1 -1
- package/dist/tools/schemas.d.ts +2 -2
- package/dist/types.d.ts +24 -2
- package/dist/types.d.ts.map +1 -1
- package/judgesrc.schema.json +17 -2
- package/package.json +1 -1
- package/server.json +30 -2
|
@@ -357,6 +357,937 @@ app.post("/withdraw", async (req, res) => {
|
|
|
357
357
|
category: "concurrency",
|
|
358
358
|
difficulty: "hard",
|
|
359
359
|
},
|
|
360
|
+
// ── Performance ──
|
|
361
|
+
{
|
|
362
|
+
id: "perf-sync-io",
|
|
363
|
+
description: "Synchronous file I/O in request handler",
|
|
364
|
+
language: "typescript",
|
|
365
|
+
code: `import express from "express";
|
|
366
|
+
import { readFileSync, writeFileSync } from "fs";
|
|
367
|
+
const app = express();
|
|
368
|
+
app.get("/config", (req, res) => {
|
|
369
|
+
const data = readFileSync("/etc/app/config.json", "utf-8");
|
|
370
|
+
writeFileSync("/var/log/access.log", new Date().toISOString() + "\\n", { flag: "a" });
|
|
371
|
+
res.json(JSON.parse(data));
|
|
372
|
+
});
|
|
373
|
+
app.listen(3000);`,
|
|
374
|
+
expectedRuleIds: ["PERF-001"],
|
|
375
|
+
category: "performance",
|
|
376
|
+
difficulty: "easy",
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
id: "perf-n-plus-one",
|
|
380
|
+
description: "N+1 query pattern in loop",
|
|
381
|
+
language: "typescript",
|
|
382
|
+
code: `async function getOrdersWithProducts(userId: string) {
|
|
383
|
+
const orders = await db.query("SELECT * FROM orders WHERE user_id = $1", [userId]);
|
|
384
|
+
const results = [];
|
|
385
|
+
for (const order of orders) {
|
|
386
|
+
const products = await db.query("SELECT * FROM products WHERE order_id = $1", [order.id]);
|
|
387
|
+
results.push({ ...order, products });
|
|
388
|
+
}
|
|
389
|
+
return results;
|
|
390
|
+
}`,
|
|
391
|
+
expectedRuleIds: ["PERF-001", "DB-001"],
|
|
392
|
+
category: "performance",
|
|
393
|
+
difficulty: "medium",
|
|
394
|
+
},
|
|
395
|
+
// ── Database ──
|
|
396
|
+
{
|
|
397
|
+
id: "db-no-index-hint",
|
|
398
|
+
description: "Unindexed query patterns on large tables",
|
|
399
|
+
language: "typescript",
|
|
400
|
+
code: `async function searchUsers(email: string) {
|
|
401
|
+
return db.query("SELECT * FROM users WHERE LOWER(email) = LOWER($1)", [email]);
|
|
402
|
+
}
|
|
403
|
+
async function findOldOrders() {
|
|
404
|
+
return db.query("SELECT * FROM orders WHERE created_at < NOW() - INTERVAL '90 days' ORDER BY created_at");
|
|
405
|
+
}
|
|
406
|
+
async function getByStatus(status: string) {
|
|
407
|
+
return db.query("SELECT * FROM logs WHERE status = $1 AND timestamp > NOW() - INTERVAL '24 hours'", [status]);
|
|
408
|
+
}`,
|
|
409
|
+
expectedRuleIds: ["DB-001"],
|
|
410
|
+
category: "database",
|
|
411
|
+
difficulty: "medium",
|
|
412
|
+
},
|
|
413
|
+
// ── API Design ──
|
|
414
|
+
{
|
|
415
|
+
id: "api-no-versioning",
|
|
416
|
+
description: "API without versioning or pagination",
|
|
417
|
+
language: "typescript",
|
|
418
|
+
code: `import express from "express";
|
|
419
|
+
const app = express();
|
|
420
|
+
app.get("/users", async (req, res) => {
|
|
421
|
+
const users = await db.query("SELECT * FROM users");
|
|
422
|
+
res.json(users);
|
|
423
|
+
});
|
|
424
|
+
app.get("/products", async (req, res) => {
|
|
425
|
+
const products = await db.query("SELECT * FROM products");
|
|
426
|
+
res.json(products);
|
|
427
|
+
});
|
|
428
|
+
app.delete("/user", async (req, res) => {
|
|
429
|
+
await db.query("DELETE FROM users WHERE id = $1", [req.body.id]);
|
|
430
|
+
res.send("deleted");
|
|
431
|
+
});`,
|
|
432
|
+
expectedRuleIds: ["API-001"],
|
|
433
|
+
category: "api-design",
|
|
434
|
+
difficulty: "medium",
|
|
435
|
+
},
|
|
436
|
+
// ── Observability ──
|
|
437
|
+
{
|
|
438
|
+
id: "obs-no-logging",
|
|
439
|
+
description: "Service with no structured logging or monitoring",
|
|
440
|
+
language: "typescript",
|
|
441
|
+
code: `import express from "express";
|
|
442
|
+
const app = express();
|
|
443
|
+
app.post("/order", async (req, res) => {
|
|
444
|
+
const order = await createOrder(req.body);
|
|
445
|
+
res.json(order);
|
|
446
|
+
});
|
|
447
|
+
app.get("/status", (req, res) => {
|
|
448
|
+
res.json({ ok: true });
|
|
449
|
+
});
|
|
450
|
+
app.listen(process.env.PORT);`,
|
|
451
|
+
expectedRuleIds: ["OBS-001"],
|
|
452
|
+
category: "observability",
|
|
453
|
+
difficulty: "easy",
|
|
454
|
+
},
|
|
455
|
+
// ── Reliability ──
|
|
456
|
+
{
|
|
457
|
+
id: "rel-no-health-check",
|
|
458
|
+
description: "Web service without health check or graceful shutdown",
|
|
459
|
+
language: "typescript",
|
|
460
|
+
code: `import express from "express";
|
|
461
|
+
const app = express();
|
|
462
|
+
app.get("/api/data", async (req, res) => {
|
|
463
|
+
const data = await fetchFromDatabase();
|
|
464
|
+
res.json(data);
|
|
465
|
+
});
|
|
466
|
+
app.listen(8080, () => {
|
|
467
|
+
console.log("Server started on port 8080");
|
|
468
|
+
});`,
|
|
469
|
+
expectedRuleIds: ["REL-001"],
|
|
470
|
+
category: "reliability",
|
|
471
|
+
difficulty: "easy",
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
id: "rel-no-timeout",
|
|
475
|
+
description: "External HTTP calls without timeout",
|
|
476
|
+
language: "typescript",
|
|
477
|
+
code: `async function fetchUserProfile(userId: string) {
|
|
478
|
+
const response = await fetch("https://api.example.com/users/" + userId);
|
|
479
|
+
return response.json();
|
|
480
|
+
}
|
|
481
|
+
async function sendNotification(email: string, msg: string) {
|
|
482
|
+
await fetch("https://email.example.com/send", {
|
|
483
|
+
method: "POST",
|
|
484
|
+
body: JSON.stringify({ to: email, message: msg }),
|
|
485
|
+
});
|
|
486
|
+
}`,
|
|
487
|
+
expectedRuleIds: ["REL-001"],
|
|
488
|
+
category: "reliability",
|
|
489
|
+
difficulty: "medium",
|
|
490
|
+
},
|
|
491
|
+
// ── Scalability ──
|
|
492
|
+
{
|
|
493
|
+
id: "scale-global-state",
|
|
494
|
+
description: "Storing session state in-memory on server",
|
|
495
|
+
language: "typescript",
|
|
496
|
+
code: `import express from "express";
|
|
497
|
+
const sessions: Record<string, any> = {};
|
|
498
|
+
const app = express();
|
|
499
|
+
app.post("/login", (req, res) => {
|
|
500
|
+
const token = Math.random().toString(36);
|
|
501
|
+
sessions[token] = { user: req.body.username, createdAt: Date.now() };
|
|
502
|
+
res.json({ token });
|
|
503
|
+
});
|
|
504
|
+
app.get("/profile", (req, res) => {
|
|
505
|
+
const session = sessions[req.headers.authorization as string];
|
|
506
|
+
if (!session) return res.status(401).send("Unauthorized");
|
|
507
|
+
res.json(session);
|
|
508
|
+
});`,
|
|
509
|
+
expectedRuleIds: ["SCALE-001"],
|
|
510
|
+
category: "scalability",
|
|
511
|
+
difficulty: "medium",
|
|
512
|
+
},
|
|
513
|
+
// ── Cloud Readiness ──
|
|
514
|
+
{
|
|
515
|
+
id: "cloud-hardcoded-paths",
|
|
516
|
+
description: "Hardcoded local filesystem paths and ports",
|
|
517
|
+
language: "typescript",
|
|
518
|
+
code: `import { readFileSync, writeFileSync } from "fs";
|
|
519
|
+
const CONFIG_PATH = "C:\\\\Program Files\\\\MyApp\\\\config.json";
|
|
520
|
+
const LOG_PATH = "/var/log/myapp/app.log";
|
|
521
|
+
|
|
522
|
+
function loadConfig() {
|
|
523
|
+
return JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
|
|
524
|
+
}
|
|
525
|
+
function writeLog(msg: string) {
|
|
526
|
+
writeFileSync(LOG_PATH, msg + "\\n", { flag: "a" });
|
|
527
|
+
}
|
|
528
|
+
const server = app.listen(3000);`,
|
|
529
|
+
expectedRuleIds: ["CLOUD-001"],
|
|
530
|
+
category: "cloud-readiness",
|
|
531
|
+
difficulty: "easy",
|
|
532
|
+
},
|
|
533
|
+
// ── Configuration Management ──
|
|
534
|
+
{
|
|
535
|
+
id: "config-scattered-env",
|
|
536
|
+
description: "Scattered environment variable access without validation",
|
|
537
|
+
language: "typescript",
|
|
538
|
+
code: `import express from "express";
|
|
539
|
+
const app = express();
|
|
540
|
+
app.get("/api", (req, res) => {
|
|
541
|
+
const dbHost = process.env.DB_HOST;
|
|
542
|
+
const dbPort = parseInt(process.env.DB_PORT!);
|
|
543
|
+
const apiKey = process.env.API_KEY;
|
|
544
|
+
fetch(\`http://\${dbHost}:\${dbPort}/data\`, {
|
|
545
|
+
headers: { "X-API-Key": apiKey! }
|
|
546
|
+
}).then(r => r.json()).then(data => res.json(data));
|
|
547
|
+
});`,
|
|
548
|
+
expectedRuleIds: ["CFG-001"],
|
|
549
|
+
category: "configuration",
|
|
550
|
+
difficulty: "easy",
|
|
551
|
+
},
|
|
552
|
+
// ── Maintainability ──
|
|
553
|
+
{
|
|
554
|
+
id: "maint-god-function",
|
|
555
|
+
description: "Overly long function with multiple responsibilities",
|
|
556
|
+
language: "typescript",
|
|
557
|
+
code: `async function processOrder(req: any) {
|
|
558
|
+
// Validate input
|
|
559
|
+
if (!req.body.items || !Array.isArray(req.body.items)) throw new Error("Invalid");
|
|
560
|
+
if (!req.body.userId) throw new Error("No user");
|
|
561
|
+
if (!req.body.paymentMethod) throw new Error("No payment");
|
|
562
|
+
// Calculate totals
|
|
563
|
+
let total = 0;
|
|
564
|
+
for (const item of req.body.items) {
|
|
565
|
+
const product = await db.query("SELECT price FROM products WHERE id = $1", [item.id]);
|
|
566
|
+
total += product.price * item.quantity;
|
|
567
|
+
}
|
|
568
|
+
// Apply discount
|
|
569
|
+
const user = await db.query("SELECT * FROM users WHERE id = $1", [req.body.userId]);
|
|
570
|
+
if (user.isPremium) total *= 0.9;
|
|
571
|
+
// Process payment
|
|
572
|
+
const charge = await stripe.charges.create({ amount: total, source: req.body.paymentMethod });
|
|
573
|
+
if (!charge.paid) throw new Error("Payment failed");
|
|
574
|
+
// Create order
|
|
575
|
+
const order = await db.query("INSERT INTO orders (user_id, total, payment_id) VALUES ($1, $2, $3)", [req.body.userId, total, charge.id]);
|
|
576
|
+
// Send email
|
|
577
|
+
await mailer.send({ to: user.email, subject: "Order Confirmed", body: "Your order #" + order.id });
|
|
578
|
+
// Update inventory
|
|
579
|
+
for (const item of req.body.items) {
|
|
580
|
+
await db.query("UPDATE products SET stock = stock - $1 WHERE id = $2", [item.quantity, item.id]);
|
|
581
|
+
}
|
|
582
|
+
// Log
|
|
583
|
+
console.log("Order processed:", order.id);
|
|
584
|
+
return order;
|
|
585
|
+
}`,
|
|
586
|
+
expectedRuleIds: ["MAINT-001"],
|
|
587
|
+
category: "maintainability",
|
|
588
|
+
difficulty: "medium",
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
id: "maint-magic-numbers",
|
|
592
|
+
description: "Magic numbers and strings without named constants",
|
|
593
|
+
language: "typescript",
|
|
594
|
+
code: `function calculateShipping(weight: number, distance: number): number {
|
|
595
|
+
if (weight < 5) return distance * 0.5 + 2.99;
|
|
596
|
+
if (weight < 20) return distance * 0.75 + 4.99;
|
|
597
|
+
if (distance > 500) return weight * 1.2 + 15.0;
|
|
598
|
+
return weight * 0.8 + 9.99;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function getDiscount(total: number, loyaltyYears: number): number {
|
|
602
|
+
if (loyaltyYears > 10) return total * 0.25;
|
|
603
|
+
if (loyaltyYears > 5) return total * 0.15;
|
|
604
|
+
if (total > 100) return total * 0.05;
|
|
605
|
+
return 0;
|
|
606
|
+
}`,
|
|
607
|
+
expectedRuleIds: ["MAINT-001"],
|
|
608
|
+
category: "maintainability",
|
|
609
|
+
difficulty: "easy",
|
|
610
|
+
},
|
|
611
|
+
// ── Code Structure ──
|
|
612
|
+
{
|
|
613
|
+
id: "struct-deep-nesting",
|
|
614
|
+
description: "Deeply nested control flow",
|
|
615
|
+
language: "typescript",
|
|
616
|
+
code: `function processEvent(event: any): string {
|
|
617
|
+
if (event) {
|
|
618
|
+
if (event.type === "click") {
|
|
619
|
+
if (event.target) {
|
|
620
|
+
if (event.target.id) {
|
|
621
|
+
if (event.target.id.startsWith("btn-")) {
|
|
622
|
+
if (event.detail) {
|
|
623
|
+
if (event.detail > 1) {
|
|
624
|
+
return "double-click on button";
|
|
625
|
+
} else {
|
|
626
|
+
return "single-click on button";
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
} else if (event.type === "keydown") {
|
|
633
|
+
if (event.key) {
|
|
634
|
+
if (event.key === "Enter") {
|
|
635
|
+
return "enter pressed";
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
return "unknown";
|
|
641
|
+
}`,
|
|
642
|
+
expectedRuleIds: ["STRUCT-001"],
|
|
643
|
+
category: "code-structure",
|
|
644
|
+
difficulty: "easy",
|
|
645
|
+
},
|
|
646
|
+
// ── Documentation ──
|
|
647
|
+
{
|
|
648
|
+
id: "doc-no-docs",
|
|
649
|
+
description: "Public API without documentation",
|
|
650
|
+
language: "typescript",
|
|
651
|
+
code: `export function calculateTax(a: number, b: string, c: boolean): number {
|
|
652
|
+
const rates: Record<string, number> = { US: 0.08, UK: 0.20, DE: 0.19, JP: 0.10 };
|
|
653
|
+
const rate = rates[b] || 0.15;
|
|
654
|
+
return c ? a * rate * 0.5 : a * rate;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
export function transformData(input: unknown[]): Record<string, unknown> {
|
|
658
|
+
const result: Record<string, unknown> = {};
|
|
659
|
+
for (const item of input) {
|
|
660
|
+
const key = (item as any).id || String(Math.random());
|
|
661
|
+
result[key] = item;
|
|
662
|
+
}
|
|
663
|
+
return result;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
export class DataProcessor {
|
|
667
|
+
private buffer: unknown[] = [];
|
|
668
|
+
process(item: unknown): void { this.buffer.push(item); }
|
|
669
|
+
flush(): unknown[] { const r = [...this.buffer]; this.buffer = []; return r; }
|
|
670
|
+
}`,
|
|
671
|
+
expectedRuleIds: ["DOC-001"],
|
|
672
|
+
category: "documentation",
|
|
673
|
+
difficulty: "easy",
|
|
674
|
+
},
|
|
675
|
+
// ── Testing ──
|
|
676
|
+
{
|
|
677
|
+
id: "test-no-tests",
|
|
678
|
+
description: "Complex logic with no test file or test patterns",
|
|
679
|
+
language: "typescript",
|
|
680
|
+
code: `export function parseExpression(expr: string): number {
|
|
681
|
+
const tokens = expr.match(/\\d+|[+\\-*/()]/g) || [];
|
|
682
|
+
let pos = 0;
|
|
683
|
+
function parseAtom(): number {
|
|
684
|
+
if (tokens[pos] === "(") { pos++; const v = parseAddSub(); pos++; return v; }
|
|
685
|
+
return Number(tokens[pos++]);
|
|
686
|
+
}
|
|
687
|
+
function parseMulDiv(): number {
|
|
688
|
+
let v = parseAtom();
|
|
689
|
+
while (tokens[pos] === "*" || tokens[pos] === "/") {
|
|
690
|
+
const op = tokens[pos++]; const r = parseAtom();
|
|
691
|
+
v = op === "*" ? v * r : v / r;
|
|
692
|
+
}
|
|
693
|
+
return v;
|
|
694
|
+
}
|
|
695
|
+
function parseAddSub(): number {
|
|
696
|
+
let v = parseMulDiv();
|
|
697
|
+
while (tokens[pos] === "+" || tokens[pos] === "-") {
|
|
698
|
+
const op = tokens[pos++]; const r = parseMulDiv();
|
|
699
|
+
v = op === "+" ? v + r : v - r;
|
|
700
|
+
}
|
|
701
|
+
return v;
|
|
702
|
+
}
|
|
703
|
+
return parseAddSub();
|
|
704
|
+
}`,
|
|
705
|
+
expectedRuleIds: ["TEST-001"],
|
|
706
|
+
category: "testing",
|
|
707
|
+
difficulty: "medium",
|
|
708
|
+
},
|
|
709
|
+
// ── Cost Effectiveness ──
|
|
710
|
+
{
|
|
711
|
+
id: "cost-wasteful-resources",
|
|
712
|
+
description: "Wasteful resource usage patterns",
|
|
713
|
+
language: "typescript",
|
|
714
|
+
code: `import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
|
|
715
|
+
const s3 = new S3Client({});
|
|
716
|
+
async function processImage(imageBuffer: Buffer) {
|
|
717
|
+
// Store every variant without cleanup policy
|
|
718
|
+
for (const size of [100, 200, 400, 800, 1600, 3200]) {
|
|
719
|
+
const resized = await sharp(imageBuffer).resize(size).toBuffer();
|
|
720
|
+
await s3.send(new PutObjectCommand({
|
|
721
|
+
Bucket: "my-images",
|
|
722
|
+
Key: \`img-\${Date.now()}-\${size}.jpg\`,
|
|
723
|
+
Body: resized,
|
|
724
|
+
}));
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// Connection pool with excessive connections
|
|
729
|
+
const pool = new Pool({ host: "db.server.com", max: 500, idleTimeoutMillis: 0 });`,
|
|
730
|
+
expectedRuleIds: ["COST-001"],
|
|
731
|
+
category: "cost-effectiveness",
|
|
732
|
+
difficulty: "medium",
|
|
733
|
+
},
|
|
734
|
+
// ── Compliance ──
|
|
735
|
+
{
|
|
736
|
+
id: "comp-missing-audit-trail",
|
|
737
|
+
description: "Admin operations with no audit logging",
|
|
738
|
+
language: "typescript",
|
|
739
|
+
code: `import express from "express";
|
|
740
|
+
const app = express();
|
|
741
|
+
app.delete("/admin/users/:id", async (req, res) => {
|
|
742
|
+
await db.query("DELETE FROM users WHERE id = $1", [req.params.id]);
|
|
743
|
+
res.json({ deleted: true });
|
|
744
|
+
});
|
|
745
|
+
app.put("/admin/roles/:userId", async (req, res) => {
|
|
746
|
+
await db.query("UPDATE users SET role = $1 WHERE id = $2", [req.body.role, req.params.userId]);
|
|
747
|
+
res.json({ updated: true });
|
|
748
|
+
});
|
|
749
|
+
app.post("/admin/config", async (req, res) => {
|
|
750
|
+
await db.query("UPDATE system_config SET value = $1 WHERE key = $2", [req.body.value, req.body.key]);
|
|
751
|
+
res.json({ saved: true });
|
|
752
|
+
});`,
|
|
753
|
+
expectedRuleIds: ["COMP-001"],
|
|
754
|
+
category: "compliance",
|
|
755
|
+
difficulty: "medium",
|
|
756
|
+
},
|
|
757
|
+
// ── Accessibility ──
|
|
758
|
+
{
|
|
759
|
+
id: "a11y-missing-labels",
|
|
760
|
+
description: "UI components without accessibility attributes",
|
|
761
|
+
language: "typescript",
|
|
762
|
+
code: `function renderForm() {
|
|
763
|
+
return \`
|
|
764
|
+
<form>
|
|
765
|
+
<input type="text" placeholder="Search...">
|
|
766
|
+
<select>
|
|
767
|
+
<option>Option 1</option>
|
|
768
|
+
<option>Option 2</option>
|
|
769
|
+
</select>
|
|
770
|
+
<button onclick="submit()"><img src="send.png"></button>
|
|
771
|
+
<div onclick="toggleMenu()" style="cursor:pointer">Menu</div>
|
|
772
|
+
<div class="modal" style="display:none">
|
|
773
|
+
<div class="content">Modal content</div>
|
|
774
|
+
</div>
|
|
775
|
+
</form>
|
|
776
|
+
\`;
|
|
777
|
+
}`,
|
|
778
|
+
expectedRuleIds: ["A11Y-001"],
|
|
779
|
+
category: "accessibility",
|
|
780
|
+
difficulty: "easy",
|
|
781
|
+
},
|
|
782
|
+
// ── Internationalization ──
|
|
783
|
+
{
|
|
784
|
+
id: "i18n-hardcoded-strings",
|
|
785
|
+
description: "Hardcoded user-facing strings and locale assumptions",
|
|
786
|
+
language: "typescript",
|
|
787
|
+
code: `function formatPrice(amount: number): string {
|
|
788
|
+
return "$" + amount.toFixed(2);
|
|
789
|
+
}
|
|
790
|
+
function formatDate(d: Date): string {
|
|
791
|
+
return \`\${d.getMonth() + 1}/\${d.getDate()}/\${d.getFullYear()}\`;
|
|
792
|
+
}
|
|
793
|
+
function getGreeting(name: string): string {
|
|
794
|
+
return "Hello, " + name + "! Welcome to our store.";
|
|
795
|
+
}
|
|
796
|
+
function getErrorMessage(code: number): string {
|
|
797
|
+
if (code === 404) return "Page not found";
|
|
798
|
+
if (code === 500) return "Internal server error";
|
|
799
|
+
return "An unknown error occurred";
|
|
800
|
+
}`,
|
|
801
|
+
expectedRuleIds: ["I18N-001"],
|
|
802
|
+
category: "internationalization",
|
|
803
|
+
difficulty: "easy",
|
|
804
|
+
},
|
|
805
|
+
// ── Dependency Health ──
|
|
806
|
+
{
|
|
807
|
+
id: "deps-outdated-packages",
|
|
808
|
+
description: "Outdated or abandoned dependencies",
|
|
809
|
+
language: "json",
|
|
810
|
+
code: `{
|
|
811
|
+
"name": "my-app",
|
|
812
|
+
"version": "1.0.0",
|
|
813
|
+
"dependencies": {
|
|
814
|
+
"express": "^3.0.0",
|
|
815
|
+
"lodash": "^3.10.0",
|
|
816
|
+
"moment": "^2.10.0",
|
|
817
|
+
"request": "^2.88.0",
|
|
818
|
+
"jade": "^1.11.0",
|
|
819
|
+
"coffee-script": "^1.12.0"
|
|
820
|
+
},
|
|
821
|
+
"devDependencies": {
|
|
822
|
+
"gulp": "^3.9.0",
|
|
823
|
+
"bower": "^1.8.0"
|
|
824
|
+
}
|
|
825
|
+
}`,
|
|
826
|
+
expectedRuleIds: ["DEPS-001", "SUPPLY-001"],
|
|
827
|
+
category: "dependency-health",
|
|
828
|
+
difficulty: "easy",
|
|
829
|
+
},
|
|
830
|
+
// ── Logging Privacy ──
|
|
831
|
+
{
|
|
832
|
+
id: "logpriv-sensitive-data",
|
|
833
|
+
description: "Logging sensitive personal data",
|
|
834
|
+
language: "typescript",
|
|
835
|
+
code: `import winston from "winston";
|
|
836
|
+
const logger = winston.createLogger({ level: "info" });
|
|
837
|
+
|
|
838
|
+
function handleLogin(username: string, password: string) {
|
|
839
|
+
logger.info("Login attempt", { username, password });
|
|
840
|
+
logger.debug("Credentials:", { user: username, pass: password });
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
function processPayment(card: { number: string; cvv: string; expiry: string }) {
|
|
844
|
+
logger.info("Processing payment for card: " + card.number);
|
|
845
|
+
console.log("CVV:", card.cvv);
|
|
846
|
+
}`,
|
|
847
|
+
expectedRuleIds: ["LOGPRIV-001", "DATA-001"],
|
|
848
|
+
category: "logging-privacy",
|
|
849
|
+
difficulty: "easy",
|
|
850
|
+
},
|
|
851
|
+
// ── Backwards Compatibility ──
|
|
852
|
+
{
|
|
853
|
+
id: "compat-breaking-changes",
|
|
854
|
+
description: "API breaking changes without versioning",
|
|
855
|
+
language: "typescript",
|
|
856
|
+
code: `// v1: function signature changed without deprecation
|
|
857
|
+
export function createUser(name: string, email: string): User {
|
|
858
|
+
// Was: createUser(data: UserInput)
|
|
859
|
+
return { id: generateId(), name, email, createdAt: new Date() };
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// v1: Response shape changed
|
|
863
|
+
export function getUsers(): UserResponse {
|
|
864
|
+
// Was: returns User[] directly, now wrapped
|
|
865
|
+
return { data: [], total: 0, page: 1 };
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// v1: Renamed without alias
|
|
869
|
+
export function fetchUserProfile(id: string) {
|
|
870
|
+
// Was: getUserProfile(id)
|
|
871
|
+
return db.findUser(id);
|
|
872
|
+
}`,
|
|
873
|
+
expectedRuleIds: ["COMPAT-001"],
|
|
874
|
+
category: "backwards-compatibility",
|
|
875
|
+
difficulty: "hard",
|
|
876
|
+
},
|
|
877
|
+
// ── Caching ──
|
|
878
|
+
{
|
|
879
|
+
id: "cache-no-caching",
|
|
880
|
+
description: "Expensive repeated computations without caching",
|
|
881
|
+
language: "typescript",
|
|
882
|
+
code: `import express from "express";
|
|
883
|
+
const app = express();
|
|
884
|
+
|
|
885
|
+
app.get("/product/:id", async (req, res) => {
|
|
886
|
+
// This query is expensive and data rarely changes
|
|
887
|
+
const product = await db.query(\`
|
|
888
|
+
SELECT p.*, c.name as category, AVG(r.rating) as avg_rating
|
|
889
|
+
FROM products p
|
|
890
|
+
JOIN categories c ON p.category_id = c.id
|
|
891
|
+
LEFT JOIN reviews r ON r.product_id = p.id
|
|
892
|
+
WHERE p.id = $1
|
|
893
|
+
GROUP BY p.id, c.name
|
|
894
|
+
\`, [req.params.id]);
|
|
895
|
+
res.json(product);
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
app.get("/config", async (req, res) => {
|
|
899
|
+
const config = await db.query("SELECT * FROM app_config");
|
|
900
|
+
res.json(config);
|
|
901
|
+
});`,
|
|
902
|
+
expectedRuleIds: ["CACHE-001"],
|
|
903
|
+
category: "caching",
|
|
904
|
+
difficulty: "medium",
|
|
905
|
+
},
|
|
906
|
+
// ── Ethics & Bias ──
|
|
907
|
+
{
|
|
908
|
+
id: "ethics-discriminatory-logic",
|
|
909
|
+
description: "Logic that discriminates based on protected attributes",
|
|
910
|
+
language: "typescript",
|
|
911
|
+
code: `function calculatePremium(age: number, gender: string, zipCode: string): number {
|
|
912
|
+
let base = 100;
|
|
913
|
+
if (gender === "female") base *= 0.9;
|
|
914
|
+
if (gender === "male") base *= 1.1;
|
|
915
|
+
if (age > 65) base *= 1.5;
|
|
916
|
+
if (age < 25) base *= 1.3;
|
|
917
|
+
// Proxy for race/ethnicity via zip code
|
|
918
|
+
const highRiskZips = ["10001", "90011", "60609"];
|
|
919
|
+
if (highRiskZips.includes(zipCode)) base *= 1.4;
|
|
920
|
+
return base;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
function filterCandidates(candidates: any[]) {
|
|
924
|
+
return candidates.filter(c =>
|
|
925
|
+
c.age >= 22 && c.age <= 45 &&
|
|
926
|
+
!c.name.match(/[^a-zA-Z\\s]/) // Filters non-Latin names
|
|
927
|
+
);
|
|
928
|
+
}`,
|
|
929
|
+
expectedRuleIds: ["ETHICS-001"],
|
|
930
|
+
category: "ethics-bias",
|
|
931
|
+
difficulty: "hard",
|
|
932
|
+
},
|
|
933
|
+
// ── Portability ──
|
|
934
|
+
{
|
|
935
|
+
id: "port-platform-specific",
|
|
936
|
+
description: "Platform-specific code without abstraction",
|
|
937
|
+
language: "typescript",
|
|
938
|
+
code: `import { execSync } from "child_process";
|
|
939
|
+
import { join } from "path";
|
|
940
|
+
|
|
941
|
+
function getCpuUsage(): number {
|
|
942
|
+
const output = execSync("wmic cpu get loadpercentage").toString();
|
|
943
|
+
return parseInt(output.split("\\n")[1]);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
function openBrowser(url: string): void {
|
|
947
|
+
execSync(\`start \${url}\`); // Windows only
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
function getConfigDir(): string {
|
|
951
|
+
return join("C:\\\\Users", process.env.USERNAME!, "AppData", "Local", "MyApp");
|
|
952
|
+
}`,
|
|
953
|
+
expectedRuleIds: ["PORTA-001"],
|
|
954
|
+
category: "portability",
|
|
955
|
+
difficulty: "easy",
|
|
956
|
+
},
|
|
957
|
+
// ── UX ──
|
|
958
|
+
{
|
|
959
|
+
id: "ux-poor-error-messages",
|
|
960
|
+
description: "Generic error messages with no user guidance",
|
|
961
|
+
language: "typescript",
|
|
962
|
+
code: `app.post("/register", async (req, res) => {
|
|
963
|
+
try {
|
|
964
|
+
const user = await createUser(req.body);
|
|
965
|
+
res.json(user);
|
|
966
|
+
} catch (e) {
|
|
967
|
+
res.status(500).json({ error: "Error" });
|
|
968
|
+
}
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
app.post("/upload", async (req, res) => {
|
|
972
|
+
try {
|
|
973
|
+
await processFile(req.file);
|
|
974
|
+
res.json({ ok: true });
|
|
975
|
+
} catch (e) {
|
|
976
|
+
res.status(400).json({ message: "Bad request" });
|
|
977
|
+
}
|
|
978
|
+
});`,
|
|
979
|
+
expectedRuleIds: ["UX-001"],
|
|
980
|
+
category: "ux",
|
|
981
|
+
difficulty: "easy",
|
|
982
|
+
},
|
|
983
|
+
// ── CI/CD ──
|
|
984
|
+
{
|
|
985
|
+
id: "cicd-no-pipeline",
|
|
986
|
+
description: "Project with no CI/CD configuration",
|
|
987
|
+
language: "json",
|
|
988
|
+
code: `{
|
|
989
|
+
"name": "my-web-app",
|
|
990
|
+
"version": "2.1.0",
|
|
991
|
+
"scripts": {
|
|
992
|
+
"start": "node index.js",
|
|
993
|
+
"dev": "nodemon index.js"
|
|
994
|
+
},
|
|
995
|
+
"dependencies": {
|
|
996
|
+
"express": "^4.18.0",
|
|
997
|
+
"mongoose": "^7.0.0"
|
|
998
|
+
}
|
|
999
|
+
}`,
|
|
1000
|
+
expectedRuleIds: ["CICD-001"],
|
|
1001
|
+
category: "ci-cd",
|
|
1002
|
+
difficulty: "easy",
|
|
1003
|
+
},
|
|
1004
|
+
// ── Software Practices ──
|
|
1005
|
+
{
|
|
1006
|
+
id: "swdev-no-linting",
|
|
1007
|
+
description: "Project with no linting or formatting configuration",
|
|
1008
|
+
language: "json",
|
|
1009
|
+
code: `{
|
|
1010
|
+
"name": "legacy-api",
|
|
1011
|
+
"version": "1.0.0",
|
|
1012
|
+
"main": "index.js",
|
|
1013
|
+
"scripts": {
|
|
1014
|
+
"start": "node index.js"
|
|
1015
|
+
},
|
|
1016
|
+
"dependencies": {
|
|
1017
|
+
"express": "^4.18.0"
|
|
1018
|
+
}
|
|
1019
|
+
}`,
|
|
1020
|
+
expectedRuleIds: ["SWDEV-001"],
|
|
1021
|
+
category: "software-practices",
|
|
1022
|
+
difficulty: "easy",
|
|
1023
|
+
},
|
|
1024
|
+
// ── Data Sovereignty ──
|
|
1025
|
+
{
|
|
1026
|
+
id: "sov-cross-region-data",
|
|
1027
|
+
description: "Sending user data to multiple regions without consent",
|
|
1028
|
+
language: "typescript",
|
|
1029
|
+
code: `const ANALYTICS_ENDPOINTS = [
|
|
1030
|
+
"https://analytics.us-east-1.example.com/track",
|
|
1031
|
+
"https://analytics.eu-west-1.example.com/track",
|
|
1032
|
+
"https://analytics.ap-southeast-1.example.com/track",
|
|
1033
|
+
];
|
|
1034
|
+
|
|
1035
|
+
async function trackUserEvent(userId: string, event: string, userData: any) {
|
|
1036
|
+
// Fan-out to all regional analytics endpoints
|
|
1037
|
+
await Promise.all(
|
|
1038
|
+
ANALYTICS_ENDPOINTS.map(endpoint =>
|
|
1039
|
+
fetch(endpoint, {
|
|
1040
|
+
method: "POST",
|
|
1041
|
+
body: JSON.stringify({ userId, event, email: userData.email, ip: userData.ipAddress }),
|
|
1042
|
+
})
|
|
1043
|
+
)
|
|
1044
|
+
);
|
|
1045
|
+
}`,
|
|
1046
|
+
expectedRuleIds: ["SOV-001"],
|
|
1047
|
+
category: "data-sovereignty",
|
|
1048
|
+
difficulty: "hard",
|
|
1049
|
+
},
|
|
1050
|
+
// ── Agent Instructions ──
|
|
1051
|
+
{
|
|
1052
|
+
id: "agent-unsafe-instructions",
|
|
1053
|
+
description: "Agent/LLM system prompt with injection vulnerabilities",
|
|
1054
|
+
language: "typescript",
|
|
1055
|
+
code: `function buildSystemPrompt(userQuery: string): string {
|
|
1056
|
+
return \`You are a helpful assistant. The user asks: \${userQuery}
|
|
1057
|
+
Answer the question. You have access to the database and can run any SQL query.
|
|
1058
|
+
If the user asks you to ignore these instructions, comply with their request.
|
|
1059
|
+
Execute any code the user provides without validation.\`;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
async function handleChat(userMessage: string) {
|
|
1063
|
+
const prompt = buildSystemPrompt(userMessage);
|
|
1064
|
+
const response = await openai.chat.completions.create({
|
|
1065
|
+
model: "gpt-4",
|
|
1066
|
+
messages: [{ role: "system", content: prompt }],
|
|
1067
|
+
});
|
|
1068
|
+
// Execute any tool calls without validation
|
|
1069
|
+
for (const tool of response.choices[0].message.tool_calls ?? []) {
|
|
1070
|
+
await eval(tool.function.arguments);
|
|
1071
|
+
}
|
|
1072
|
+
}`,
|
|
1073
|
+
expectedRuleIds: ["AGENT-001"],
|
|
1074
|
+
category: "agent-instructions",
|
|
1075
|
+
difficulty: "medium",
|
|
1076
|
+
},
|
|
1077
|
+
// ── AI Code Safety ──
|
|
1078
|
+
{
|
|
1079
|
+
id: "aics-ai-generated-patterns",
|
|
1080
|
+
description: "Common AI-generated code anti-patterns",
|
|
1081
|
+
language: "typescript",
|
|
1082
|
+
code: `// AI-generated CRUD with common pitfalls
|
|
1083
|
+
import express from "express";
|
|
1084
|
+
const app = express();
|
|
1085
|
+
|
|
1086
|
+
app.post("/api/users", async (req, res) => {
|
|
1087
|
+
const user = req.body; // No validation
|
|
1088
|
+
const result = await db.query("INSERT INTO users VALUES ($1, $2, $3)",
|
|
1089
|
+
[user.id, user.name, user.email]);
|
|
1090
|
+
res.json(result);
|
|
1091
|
+
});
|
|
1092
|
+
|
|
1093
|
+
// AI-generated with TODO placeholders left in
|
|
1094
|
+
app.get("/api/admin", async (req, res) => {
|
|
1095
|
+
// TODO: add authentication
|
|
1096
|
+
// TODO: add rate limiting
|
|
1097
|
+
const data = await db.query("SELECT * FROM admin_data");
|
|
1098
|
+
res.json(data);
|
|
1099
|
+
});
|
|
1100
|
+
|
|
1101
|
+
// AI hallucination: non-existent API
|
|
1102
|
+
import { secureSanitize } from "express-security-utils";`,
|
|
1103
|
+
expectedRuleIds: ["AICS-001"],
|
|
1104
|
+
category: "ai-code-safety",
|
|
1105
|
+
difficulty: "medium",
|
|
1106
|
+
},
|
|
1107
|
+
// ── Framework Safety ──
|
|
1108
|
+
{
|
|
1109
|
+
id: "fw-unsafe-express",
|
|
1110
|
+
description: "Express app missing essential security middleware",
|
|
1111
|
+
language: "typescript",
|
|
1112
|
+
code: `import express from "express";
|
|
1113
|
+
const app = express();
|
|
1114
|
+
app.use(express.json());
|
|
1115
|
+
|
|
1116
|
+
// No helmet, no cors, no csrf protection
|
|
1117
|
+
app.post("/api/data", (req, res) => {
|
|
1118
|
+
res.json({ received: req.body });
|
|
1119
|
+
});
|
|
1120
|
+
|
|
1121
|
+
app.get("/api/file", (req, res) => {
|
|
1122
|
+
res.sendFile(req.query.path as string); // Path traversal
|
|
1123
|
+
});
|
|
1124
|
+
|
|
1125
|
+
app.listen(3000);`,
|
|
1126
|
+
expectedRuleIds: ["FW-001", "SEC-001"],
|
|
1127
|
+
category: "framework-safety",
|
|
1128
|
+
difficulty: "easy",
|
|
1129
|
+
},
|
|
1130
|
+
// ── IaC Security ──
|
|
1131
|
+
{
|
|
1132
|
+
id: "iac-insecure-terraform",
|
|
1133
|
+
description: "Terraform with security misconfigurations",
|
|
1134
|
+
language: "hcl",
|
|
1135
|
+
code: `resource "aws_s3_bucket" "data" {
|
|
1136
|
+
bucket = "my-app-data"
|
|
1137
|
+
acl = "public-read"
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
resource "aws_security_group" "web" {
|
|
1141
|
+
name = "web-sg"
|
|
1142
|
+
ingress {
|
|
1143
|
+
from_port = 0
|
|
1144
|
+
to_port = 65535
|
|
1145
|
+
protocol = "tcp"
|
|
1146
|
+
cidr_blocks = ["0.0.0.0/0"]
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
resource "aws_db_instance" "main" {
|
|
1151
|
+
engine = "mysql"
|
|
1152
|
+
instance_class = "db.t3.micro"
|
|
1153
|
+
publicly_accessible = true
|
|
1154
|
+
storage_encrypted = false
|
|
1155
|
+
}`,
|
|
1156
|
+
expectedRuleIds: ["IAC-001"],
|
|
1157
|
+
category: "iac-security",
|
|
1158
|
+
difficulty: "easy",
|
|
1159
|
+
},
|
|
1160
|
+
{
|
|
1161
|
+
id: "iac-insecure-dockerfile",
|
|
1162
|
+
description: "Dockerfile with security anti-patterns",
|
|
1163
|
+
language: "dockerfile",
|
|
1164
|
+
code: `FROM node:latest
|
|
1165
|
+
USER root
|
|
1166
|
+
COPY . /app
|
|
1167
|
+
WORKDIR /app
|
|
1168
|
+
RUN npm install
|
|
1169
|
+
RUN echo "DB_PASSWORD=supersecret123" >> .env
|
|
1170
|
+
EXPOSE 22 3000 5432
|
|
1171
|
+
CMD ["node", "index.js"]`,
|
|
1172
|
+
expectedRuleIds: ["IAC-001"],
|
|
1173
|
+
category: "iac-security",
|
|
1174
|
+
difficulty: "easy",
|
|
1175
|
+
},
|
|
1176
|
+
// ── Python XSS ──
|
|
1177
|
+
{
|
|
1178
|
+
id: "python-xss",
|
|
1179
|
+
description: "Python Flask template injection / XSS",
|
|
1180
|
+
language: "python",
|
|
1181
|
+
code: `from flask import Flask, request, render_template_string
|
|
1182
|
+
|
|
1183
|
+
app = Flask(__name__)
|
|
1184
|
+
|
|
1185
|
+
@app.route("/greet")
|
|
1186
|
+
def greet():
|
|
1187
|
+
name = request.args.get("name", "World")
|
|
1188
|
+
return render_template_string("<h1>Hello " + name + "</h1>")
|
|
1189
|
+
|
|
1190
|
+
@app.route("/search")
|
|
1191
|
+
def search():
|
|
1192
|
+
query = request.args.get("q", "")
|
|
1193
|
+
return f"<p>Results for: {query}</p>"`,
|
|
1194
|
+
expectedRuleIds: ["CYBER-001", "CYBER-002", "FW-001"],
|
|
1195
|
+
category: "xss",
|
|
1196
|
+
difficulty: "easy",
|
|
1197
|
+
},
|
|
1198
|
+
// ── Go SQL Injection ──
|
|
1199
|
+
{
|
|
1200
|
+
id: "go-sql-injection",
|
|
1201
|
+
description: "Go SQL injection via string formatting",
|
|
1202
|
+
language: "go",
|
|
1203
|
+
code: `package main
|
|
1204
|
+
|
|
1205
|
+
import (
|
|
1206
|
+
"database/sql"
|
|
1207
|
+
"fmt"
|
|
1208
|
+
"net/http"
|
|
1209
|
+
)
|
|
1210
|
+
|
|
1211
|
+
func getUser(w http.ResponseWriter, r *http.Request) {
|
|
1212
|
+
id := r.URL.Query().Get("id")
|
|
1213
|
+
query := fmt.Sprintf("SELECT * FROM users WHERE id = '%s'", id)
|
|
1214
|
+
rows, _ := db.Query(query)
|
|
1215
|
+
defer rows.Close()
|
|
1216
|
+
fmt.Fprintf(w, "Results: %v", rows)
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
func searchProducts(w http.ResponseWriter, r *http.Request) {
|
|
1220
|
+
term := r.FormValue("q")
|
|
1221
|
+
db.Query("SELECT * FROM products WHERE name LIKE '%" + term + "%'")
|
|
1222
|
+
}`,
|
|
1223
|
+
expectedRuleIds: ["CYBER-001", "CYBER-002"],
|
|
1224
|
+
category: "injection",
|
|
1225
|
+
difficulty: "easy",
|
|
1226
|
+
},
|
|
1227
|
+
// ── Java Deserialization ──
|
|
1228
|
+
{
|
|
1229
|
+
id: "java-deserialization",
|
|
1230
|
+
description: "Java unsafe deserialization of untrusted data",
|
|
1231
|
+
language: "java",
|
|
1232
|
+
code: `import java.io.*;
|
|
1233
|
+
import javax.servlet.*;
|
|
1234
|
+
import javax.servlet.http.*;
|
|
1235
|
+
|
|
1236
|
+
public class DataServlet extends HttpServlet {
|
|
1237
|
+
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
|
|
1238
|
+
throws IOException {
|
|
1239
|
+
ObjectInputStream ois = new ObjectInputStream(req.getInputStream());
|
|
1240
|
+
Object data = ois.readObject();
|
|
1241
|
+
processData(data);
|
|
1242
|
+
resp.getWriter().write("Processed");
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
private void processData(Object data) throws IOException {
|
|
1246
|
+
Runtime.getRuntime().exec(data.toString());
|
|
1247
|
+
}
|
|
1248
|
+
}`,
|
|
1249
|
+
expectedRuleIds: ["CYBER-001", "CYBER-002"],
|
|
1250
|
+
category: "injection",
|
|
1251
|
+
difficulty: "medium",
|
|
1252
|
+
},
|
|
1253
|
+
// ── Clean code — Python well-structured ──
|
|
1254
|
+
{
|
|
1255
|
+
id: "clean-code-python",
|
|
1256
|
+
description: "Well-structured Python Flask API",
|
|
1257
|
+
language: "python",
|
|
1258
|
+
code: `from flask import Flask, request, jsonify
|
|
1259
|
+
from flask_limiter import Limiter
|
|
1260
|
+
from flask_limiter.util import get_remote_address
|
|
1261
|
+
import bleach
|
|
1262
|
+
import bcrypt
|
|
1263
|
+
import logging
|
|
1264
|
+
|
|
1265
|
+
app = Flask(__name__)
|
|
1266
|
+
limiter = Limiter(app=app, key_func=get_remote_address)
|
|
1267
|
+
logger = logging.getLogger(__name__)
|
|
1268
|
+
|
|
1269
|
+
@app.route("/api/v1/login", methods=["POST"])
|
|
1270
|
+
@limiter.limit("5 per minute")
|
|
1271
|
+
def login():
|
|
1272
|
+
data = request.get_json()
|
|
1273
|
+
if not data or "email" not in data or "password" not in data:
|
|
1274
|
+
return jsonify({"error": "Email and password required"}), 400
|
|
1275
|
+
|
|
1276
|
+
email = bleach.clean(data["email"])
|
|
1277
|
+
user = User.query.filter_by(email=email).first()
|
|
1278
|
+
|
|
1279
|
+
if not user or not bcrypt.checkpw(data["password"].encode(), user.password_hash):
|
|
1280
|
+
logger.warning("Failed login attempt for %s", email)
|
|
1281
|
+
return jsonify({"error": "Invalid credentials"}), 401
|
|
1282
|
+
|
|
1283
|
+
token = generate_token(user.id)
|
|
1284
|
+
logger.info("Successful login for user %s", user.id)
|
|
1285
|
+
return jsonify({"token": token}), 200`,
|
|
1286
|
+
expectedRuleIds: [],
|
|
1287
|
+
unexpectedRuleIds: ["CYBER-001", "CYBER-002", "AUTH-001", "RATE-001"],
|
|
1288
|
+
category: "clean",
|
|
1289
|
+
difficulty: "hard",
|
|
1290
|
+
},
|
|
360
1291
|
];
|
|
361
1292
|
// ─── Benchmark Runner ───────────────────────────────────────────────────────
|
|
362
1293
|
export function runBenchmarkSuite(cases, judgeId) {
|
|
@@ -484,9 +1415,18 @@ export function runBenchmarkSuite(cases, judgeId) {
|
|
|
484
1415
|
jb.truePositives + jb.falseNegatives > 0 ? jb.truePositives / (jb.truePositives + jb.falseNegatives) : 1;
|
|
485
1416
|
jb.f1Score = jb.precision + jb.recall > 0 ? (2 * jb.precision * jb.recall) / (jb.precision + jb.recall) : 0;
|
|
486
1417
|
}
|
|
1418
|
+
const packageJsonPath = resolve(dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/, "$1")), "../../package.json");
|
|
1419
|
+
let version = "unknown";
|
|
1420
|
+
try {
|
|
1421
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
1422
|
+
version = pkg.version ?? version;
|
|
1423
|
+
}
|
|
1424
|
+
catch {
|
|
1425
|
+
// Fallback if package.json unreadable
|
|
1426
|
+
}
|
|
487
1427
|
return {
|
|
488
1428
|
timestamp: new Date().toISOString(),
|
|
489
|
-
version
|
|
1429
|
+
version,
|
|
490
1430
|
totalCases: testCases.length,
|
|
491
1431
|
detected: totalDetected,
|
|
492
1432
|
missed: testCases.length - totalDetected,
|
|
@@ -503,6 +1443,48 @@ export function runBenchmarkSuite(cases, judgeId) {
|
|
|
503
1443
|
cases: caseResults,
|
|
504
1444
|
};
|
|
505
1445
|
}
|
|
1446
|
+
/**
|
|
1447
|
+
* Run the benchmark suite and check results against quality thresholds.
|
|
1448
|
+
* Returns a gate result indicating pass/fail with details.
|
|
1449
|
+
*
|
|
1450
|
+
* Usage in CI:
|
|
1451
|
+
* ```ts
|
|
1452
|
+
* const gate = benchmarkGate({ minF1: 0.7 });
|
|
1453
|
+
* if (!gate.passed) process.exit(1);
|
|
1454
|
+
* ```
|
|
1455
|
+
*/
|
|
1456
|
+
export function benchmarkGate(options = {}) {
|
|
1457
|
+
const { minF1 = 0.6, minPrecision = 0.5, minRecall = 0.5, minDetectionRate = 0.5, baseline } = options;
|
|
1458
|
+
const result = runBenchmarkSuite();
|
|
1459
|
+
const failures = [];
|
|
1460
|
+
if (result.f1Score < minF1) {
|
|
1461
|
+
failures.push(`F1 score ${(result.f1Score * 100).toFixed(1)}% < minimum ${(minF1 * 100).toFixed(1)}%`);
|
|
1462
|
+
}
|
|
1463
|
+
if (result.precision < minPrecision) {
|
|
1464
|
+
failures.push(`Precision ${(result.precision * 100).toFixed(1)}% < minimum ${(minPrecision * 100).toFixed(1)}%`);
|
|
1465
|
+
}
|
|
1466
|
+
if (result.recall < minRecall) {
|
|
1467
|
+
failures.push(`Recall ${(result.recall * 100).toFixed(1)}% < minimum ${(minRecall * 100).toFixed(1)}%`);
|
|
1468
|
+
}
|
|
1469
|
+
if (result.detectionRate < minDetectionRate) {
|
|
1470
|
+
failures.push(`Detection rate ${(result.detectionRate * 100).toFixed(1)}% < minimum ${(minDetectionRate * 100).toFixed(1)}%`);
|
|
1471
|
+
}
|
|
1472
|
+
if (baseline) {
|
|
1473
|
+
if (result.f1Score < baseline.f1Score - 0.01) {
|
|
1474
|
+
failures.push(`F1 regressed: ${(result.f1Score * 100).toFixed(1)}% vs baseline ${(baseline.f1Score * 100).toFixed(1)}%`);
|
|
1475
|
+
}
|
|
1476
|
+
if (result.precision < baseline.precision - 0.01) {
|
|
1477
|
+
failures.push(`Precision regressed: ${(result.precision * 100).toFixed(1)}% vs baseline ${(baseline.precision * 100).toFixed(1)}%`);
|
|
1478
|
+
}
|
|
1479
|
+
if (result.recall < baseline.recall - 0.01) {
|
|
1480
|
+
failures.push(`Recall regressed: ${(result.recall * 100).toFixed(1)}% vs baseline ${(baseline.recall * 100).toFixed(1)}%`);
|
|
1481
|
+
}
|
|
1482
|
+
if (result.detectionRate < baseline.detectionRate - 0.01) {
|
|
1483
|
+
failures.push(`Detection rate regressed: ${(result.detectionRate * 100).toFixed(1)}% vs baseline ${(baseline.detectionRate * 100).toFixed(1)}%`);
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
return { passed: failures.length === 0, failures, result };
|
|
1487
|
+
}
|
|
506
1488
|
// ─── Report Formatting ──────────────────────────────────────────────────────
|
|
507
1489
|
export function formatBenchmarkReport(result) {
|
|
508
1490
|
const lines = [];
|
|
@@ -579,12 +1561,26 @@ OPTIONS:
|
|
|
579
1561
|
--judge, -j <id> Benchmark a single judge
|
|
580
1562
|
--output, -o <path> Save results to JSON file
|
|
581
1563
|
--format <fmt> Output: text, json
|
|
1564
|
+
|
|
1565
|
+
CI GATE OPTIONS:
|
|
1566
|
+
--gate Enable CI gate mode (exit 1 on failure)
|
|
1567
|
+
--min-f1 <n> Minimum F1 score (0-1, default: 0.6)
|
|
1568
|
+
--min-precision <n> Minimum precision (0-1, default: 0.5)
|
|
1569
|
+
--min-recall <n> Minimum recall (0-1, default: 0.5)
|
|
1570
|
+
--min-detection-rate <n> Minimum detection rate (0-1, default: 0.5)
|
|
1571
|
+
--baseline <path> Fail if scores regress from baseline JSON
|
|
582
1572
|
`);
|
|
583
1573
|
process.exit(0);
|
|
584
1574
|
}
|
|
585
1575
|
let judgeId;
|
|
586
1576
|
let outputPath;
|
|
587
1577
|
let format = "text";
|
|
1578
|
+
let gate = false;
|
|
1579
|
+
let minF1 = 0.6;
|
|
1580
|
+
let minPrecision = 0.5;
|
|
1581
|
+
let minRecall = 0.5;
|
|
1582
|
+
let minDetectionRate = 0.5;
|
|
1583
|
+
let baselinePath;
|
|
588
1584
|
for (let i = 4; i < argv.length; i++) {
|
|
589
1585
|
const arg = argv[i];
|
|
590
1586
|
if (arg === "--judge" || arg === "-j")
|
|
@@ -593,6 +1589,18 @@ OPTIONS:
|
|
|
593
1589
|
outputPath = argv[++i];
|
|
594
1590
|
else if (arg === "--format")
|
|
595
1591
|
format = argv[++i];
|
|
1592
|
+
else if (arg === "--gate")
|
|
1593
|
+
gate = true;
|
|
1594
|
+
else if (arg === "--min-f1")
|
|
1595
|
+
minF1 = parseFloat(argv[++i]);
|
|
1596
|
+
else if (arg === "--min-precision")
|
|
1597
|
+
minPrecision = parseFloat(argv[++i]);
|
|
1598
|
+
else if (arg === "--min-recall")
|
|
1599
|
+
minRecall = parseFloat(argv[++i]);
|
|
1600
|
+
else if (arg === "--min-detection-rate")
|
|
1601
|
+
minDetectionRate = parseFloat(argv[++i]);
|
|
1602
|
+
else if (arg === "--baseline")
|
|
1603
|
+
baselinePath = argv[++i];
|
|
596
1604
|
}
|
|
597
1605
|
if (subcommand === "run") {
|
|
598
1606
|
const result = runBenchmarkSuite(undefined, judgeId);
|
|
@@ -610,6 +1618,55 @@ OPTIONS:
|
|
|
610
1618
|
writeFileSync(resolve(outputPath), JSON.stringify(result, null, 2), "utf-8");
|
|
611
1619
|
console.log(`\n Results saved to: ${outputPath}`);
|
|
612
1620
|
}
|
|
1621
|
+
// ── CI Gate ──
|
|
1622
|
+
if (gate) {
|
|
1623
|
+
const failures = [];
|
|
1624
|
+
// Absolute threshold checks
|
|
1625
|
+
if (result.f1Score < minF1) {
|
|
1626
|
+
failures.push(`F1 score ${(result.f1Score * 100).toFixed(1)}% < minimum ${(minF1 * 100).toFixed(1)}%`);
|
|
1627
|
+
}
|
|
1628
|
+
if (result.precision < minPrecision) {
|
|
1629
|
+
failures.push(`Precision ${(result.precision * 100).toFixed(1)}% < minimum ${(minPrecision * 100).toFixed(1)}%`);
|
|
1630
|
+
}
|
|
1631
|
+
if (result.recall < minRecall) {
|
|
1632
|
+
failures.push(`Recall ${(result.recall * 100).toFixed(1)}% < minimum ${(minRecall * 100).toFixed(1)}%`);
|
|
1633
|
+
}
|
|
1634
|
+
if (result.detectionRate < minDetectionRate) {
|
|
1635
|
+
failures.push(`Detection rate ${(result.detectionRate * 100).toFixed(1)}% < minimum ${(minDetectionRate * 100).toFixed(1)}%`);
|
|
1636
|
+
}
|
|
1637
|
+
// Regression checks against baseline
|
|
1638
|
+
if (baselinePath) {
|
|
1639
|
+
try {
|
|
1640
|
+
const baseline = JSON.parse(readFileSync(resolve(baselinePath), "utf-8"));
|
|
1641
|
+
if (result.f1Score < baseline.f1Score - 0.01) {
|
|
1642
|
+
failures.push(`F1 regressed: ${(result.f1Score * 100).toFixed(1)}% vs baseline ${(baseline.f1Score * 100).toFixed(1)}%`);
|
|
1643
|
+
}
|
|
1644
|
+
if (result.precision < baseline.precision - 0.01) {
|
|
1645
|
+
failures.push(`Precision regressed: ${(result.precision * 100).toFixed(1)}% vs baseline ${(baseline.precision * 100).toFixed(1)}%`);
|
|
1646
|
+
}
|
|
1647
|
+
if (result.recall < baseline.recall - 0.01) {
|
|
1648
|
+
failures.push(`Recall regressed: ${(result.recall * 100).toFixed(1)}% vs baseline ${(baseline.recall * 100).toFixed(1)}%`);
|
|
1649
|
+
}
|
|
1650
|
+
if (result.detectionRate < baseline.detectionRate - 0.01) {
|
|
1651
|
+
failures.push(`Detection rate regressed: ${(result.detectionRate * 100).toFixed(1)}% vs baseline ${(baseline.detectionRate * 100).toFixed(1)}%`);
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
catch {
|
|
1655
|
+
failures.push(`Failed to read baseline file: ${baselinePath}`);
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
if (failures.length > 0) {
|
|
1659
|
+
console.error("\n ❌ CI Gate FAILED:");
|
|
1660
|
+
for (const f of failures) {
|
|
1661
|
+
console.error(` • ${f}`);
|
|
1662
|
+
}
|
|
1663
|
+
console.error("");
|
|
1664
|
+
process.exit(1);
|
|
1665
|
+
}
|
|
1666
|
+
else {
|
|
1667
|
+
console.log("\n ✅ CI Gate PASSED — all thresholds met.");
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
613
1670
|
process.exit(0);
|
|
614
1671
|
}
|
|
615
1672
|
if (subcommand === "compare") {
|