Skip to content

Commit fbb5946

Browse files
security: gate dev bypass flags on NODE_ENV, timing-safe OTP compare, rate-limit platform detect
- DEV_SKIP_LEGAL_REVIEW and DEV_SKIP_ADMIN_MFA were bare env checks; new devFlagEnabled() helper makes them inert in production builds - OTP hash comparison now uses crypto.timingSafeEqual in both DB and memory modes - /api/platforms/detect now rate limited (60/hour per IP) to prevent platform database enumeration - npm audit fix for dev-only undici advisories Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 1dc269a commit fbb5946

14 files changed

Lines changed: 54 additions & 19 deletions

File tree

asmita/package-lock.json

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

asmita/src/app/(victim)/case/[caseId]/page.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Link from "next/link";
2+
import { devFlagEnabled } from "@/lib/dev-flags";
23
import { AppShell } from "@/components/layout/AppShell";
34
import { requireSession } from "@/lib/auth/middleware";
45
import { getCaseForUser, isUserDeactivated } from "@/lib/case-ops";
@@ -48,7 +49,7 @@ export default async function CasePage({
4849
if (record && hashUploadEnabled) {
4950
hashSubmissions = await listHashesForCase(record.id);
5051
}
51-
const skipLegalGate = process.env.DEV_SKIP_LEGAL_REVIEW === "true";
52+
const skipLegalGate = devFlagEnabled("DEV_SKIP_LEGAL_REVIEW");
5253
const hasQueuedUrl = record?.urls.some(
5354
(u) => u.status === "NOTICE_QUEUED" || (skipLegalGate && u.status === "PENDING_REVIEW" && u.platformId),
5455
) ?? false;

asmita/src/app/(victim)/case/[caseId]/sign/page.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { redirect } from "next/navigation";
2+
import { devFlagEnabled } from "@/lib/dev-flags";
23
import { AppShell } from "@/components/layout/AppShell";
34
import { requireSession } from "@/lib/auth/middleware";
45
import { getCaseForUser } from "@/lib/case-ops";
@@ -38,7 +39,7 @@ export default async function SignNoticePage({
3839
});
3940
if (alreadySigned) redirect(`/case/${caseId}/confirmation`);
4041

41-
const skipLegalGate = process.env.DEV_SKIP_LEGAL_REVIEW === "true";
42+
const skipLegalGate = devFlagEnabled("DEV_SKIP_LEGAL_REVIEW");
4243

4344
const url = await db.submittedUrl.findFirst({
4445
where: {

asmita/src/app/api/admin/auth/verify/route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { NextResponse } from "next/server";
2+
import { devFlagEnabled } from "@/lib/dev-flags";
23
import { z } from "zod";
34
import { createAdminIdentity, getAdminRole, isAdminEmail } from "@/lib/auth/admin-allowlist";
45
import { verifyAdminTotp } from "@/lib/auth/admin-mfa";
@@ -28,7 +29,7 @@ export async function POST(request: Request) {
2829

2930
const admin = createAdminIdentity(parsed.data.email);
3031
const otpOk = await verifyOtp(parsed.data.email, parsed.data.otp);
31-
const skipMfa = process.env.DEV_SKIP_ADMIN_MFA === "true";
32+
const skipMfa = devFlagEnabled("DEV_SKIP_ADMIN_MFA");
3233
const mfaOk = skipMfa || verifyAdminTotp({ token: parsed.data.totp });
3334
if (!otpOk || !mfaOk) {
3435
logSecurityEvent({

asmita/src/app/api/admin/urls/[urlId]/reissue-portal/route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { NextResponse } from "next/server";
2+
import { devFlagEnabled } from "@/lib/dev-flags";
23
import { verifyCsrfRequest } from "@/lib/csrf";
34
import { requireAdminPermission } from "@/lib/auth/require-admin";
45
import { db } from "@/lib/db";
@@ -65,7 +66,7 @@ export async function POST(
6566
return NextResponse.json({ error: "email_notice_not_supported" }, { status: 409 });
6667
}
6768

68-
const skipLegalGate = process.env.DEV_SKIP_LEGAL_REVIEW === "true";
69+
const skipLegalGate = devFlagEnabled("DEV_SKIP_LEGAL_REVIEW");
6970
const template = await db.noticeTemplate.findFirst({
7071
where: {
7172
templateType: templateType as never,

asmita/src/app/api/cases/[caseId]/preview-hash-advisory/route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { NextRequest, NextResponse } from "next/server";
2+
import { devFlagEnabled } from "@/lib/dev-flags";
23
import { requireSession } from "@/lib/auth/middleware";
34
import { getCaseForUser } from "@/lib/case-ops";
45
import { db } from "@/lib/db";
@@ -42,7 +43,7 @@ export async function GET(
4243
return NextResponse.json({ error: "no_hashes" }, { status: 409 });
4344
}
4445

45-
const skipLegalGate = process.env.DEV_SKIP_LEGAL_REVIEW === "true";
46+
const skipLegalGate = devFlagEnabled("DEV_SKIP_LEGAL_REVIEW");
4647
const template = await db.noticeTemplate.findFirst({
4748
where: {
4849
templateType: "HASH_ADVISORY",

asmita/src/app/api/cases/[caseId]/preview-notice/route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { NextRequest, NextResponse } from "next/server";
2+
import { devFlagEnabled } from "@/lib/dev-flags";
23
import { requireSession } from "@/lib/auth/middleware";
34
import { getCaseForUser } from "@/lib/case-ops";
45
import { db } from "@/lib/db";
@@ -24,7 +25,7 @@ export async function GET(
2425
const record = await getCaseForUser(caseId, auth.session.sub);
2526
if (!record) return NextResponse.json({ error: "not_found" }, { status: 404 });
2627

27-
const skipLegalGate = process.env.DEV_SKIP_LEGAL_REVIEW === "true";
28+
const skipLegalGate = devFlagEnabled("DEV_SKIP_LEGAL_REVIEW");
2829

2930
const url = await db.submittedUrl.findFirst({
3031
where: {

asmita/src/app/api/cases/[caseId]/sign-hash-advisory/route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { NextResponse } from "next/server";
2+
import { devFlagEnabled } from "@/lib/dev-flags";
23
import { z } from "zod";
34
import { verifyCsrfRequest } from "@/lib/csrf";
45
import { requireSession } from "@/lib/auth/middleware";
@@ -91,7 +92,7 @@ export async function POST(
9192
return NextResponse.json({ error: "no_hashes" }, { status: 409 });
9293
}
9394

94-
const skipLegalGate = process.env.DEV_SKIP_LEGAL_REVIEW === "true";
95+
const skipLegalGate = devFlagEnabled("DEV_SKIP_LEGAL_REVIEW");
9596
const template = await db.noticeTemplate.findFirst({
9697
where: {
9798
templateType: "HASH_ADVISORY",

asmita/src/app/api/cases/[caseId]/sign-notice/route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { NextResponse } from "next/server";
2+
import { devFlagEnabled } from "@/lib/dev-flags";
23
import { z } from "zod";
34
import type { UrlStatus } from "@prisma/client";
45
import { verifyCsrfRequest } from "@/lib/csrf";
@@ -95,7 +96,7 @@ export async function POST(
9596
}
9697

9798
const { urlId, name, contact, signature } = parsed.data;
98-
const skipLegalGate = process.env.DEV_SKIP_LEGAL_REVIEW === "true";
99+
const skipLegalGate = devFlagEnabled("DEV_SKIP_LEGAL_REVIEW");
99100
const allowedStatuses: UrlStatus[] = skipLegalGate
100101
? ["NOTICE_QUEUED", "PENDING_REVIEW"]
101102
: ["NOTICE_QUEUED"];

asmita/src/app/api/notices/dispatch/route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { NextResponse } from "next/server";
2+
import { devFlagEnabled } from "@/lib/dev-flags";
23
import { z } from "zod";
34
import { verifyCsrfRequest } from "@/lib/csrf";
45
import { db } from "@/lib/db";
@@ -75,7 +76,7 @@ export async function POST(request: Request) {
7576
return NextResponse.json({ error: "email_notice_not_supported" }, { status: 409 });
7677
}
7778

78-
const skipLegalGate = process.env.DEV_SKIP_LEGAL_REVIEW === "true";
79+
const skipLegalGate = devFlagEnabled("DEV_SKIP_LEGAL_REVIEW");
7980
const template = await db.noticeTemplate.findFirst({
8081
where: {
8182
templateType,

0 commit comments

Comments
 (0)