Skip to content

Commit 76aee0e

Browse files
SchenLongclaude
andcommitted
fix: VIS-04 + F-5 — conditional TestFlowBanner + gate setup/status post-auth
VIS-04: ModelLab TestFlowBanner now checks `models.length > 0 && !modelError` before showing "Model configured. Run adversarial attacks?" — prevents contradictory state when the main panel is in error. F-5: /api/setup/status now returns 401 to unauthenticated callers once setup is complete (users exist). Pre-setup (no users), the endpoint remains open so the setup wizard can render. Prevents minor recon of instance provisioning state. Also: model-lab.test.tsx mock updated for useModelContext import. Verified: typecheck PASS, vitest 6252/6252. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent a93ada5 commit 76aee0e

File tree

3 files changed

+28
-4
lines changed

3 files changed

+28
-4
lines changed

packages/dojolm-web/src/app/api/setup/status/route.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,18 @@
22
* File: /api/setup/status/route.ts
33
* Purpose: Returns whether initial setup is needed (no users in DB)
44
* Story: Setup Wizard
5+
*
6+
* Security (F-5, 2026-04-16): Once setup is complete (users exist), this
7+
* endpoint returns 401 to unauthenticated callers. Only the login page
8+
* and authenticated sessions may check setup status post-setup — this
9+
* prevents unauthenticated recon from learning whether the instance has
10+
* been provisioned.
511
*/
612

713
import { NextResponse } from 'next/server';
814
import { isDemoMode } from '@/lib/demo';
915

10-
export async function GET() {
16+
export async function GET(req: Request) {
1117
// Demo mode: always show setup wizard on page load
1218
if (isDemoMode()) {
1319
return NextResponse.json({ needsSetup: true });
@@ -16,7 +22,23 @@ export async function GET() {
1622
try {
1723
const { userRepo } = await import('@/lib/db/repositories/user.repository');
1824
const count = userRepo.countUsers();
19-
return NextResponse.json({ needsSetup: count === 0 });
25+
26+
// Setup incomplete (no users) — always allow so the setup wizard can render
27+
if (count === 0) {
28+
return NextResponse.json({ needsSetup: true });
29+
}
30+
31+
// Setup complete — gate behind auth to prevent unauthenticated recon (F-5)
32+
const { getSessionFromRequest } = await import('@/lib/auth/session');
33+
const session = getSessionFromRequest(req);
34+
if (!session) {
35+
return NextResponse.json(
36+
{ error: 'Authentication required' },
37+
{ status: 401 }
38+
);
39+
}
40+
41+
return NextResponse.json({ needsSetup: false });
2042
} catch {
2143
return NextResponse.json(
2244
{ error: 'Failed to check setup status' },

packages/dojolm-web/src/components/__tests__/model-lab.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ vi.mock('@/lib/contexts', () => ({
8080
LLMModelProvider: ({ children }: { children: ReactNode }) => <>{children}</>,
8181
LLMExecutionProvider: ({ children }: { children: ReactNode }) => <>{children}</>,
8282
LLMResultsProvider: ({ children }: { children: ReactNode }) => <>{children}</>,
83+
useModelContext: () => ({ models: [{ id: 'test-model' }], error: null, isLoading: false }),
8384
}))
8485

8586
vi.mock('@/components/reports/ConsolidatedReportButton', () => ({

packages/dojolm-web/src/components/llm/ModelLab.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { ModelList } from './ModelList';
1616
import { ComparisonView } from './ComparisonView';
1717
import { CustomProviderBuilder } from './CustomProviderBuilder';
1818
import { JutsuTab } from './JutsuTab';
19-
import { LLMModelProvider, LLMExecutionProvider, LLMResultsProvider } from '@/lib/contexts';
19+
import { LLMModelProvider, LLMExecutionProvider, LLMResultsProvider, useModelContext } from '@/lib/contexts';
2020
import { Brain, GitCompare, Wrench, ScrollText, Crosshair } from 'lucide-react';
2121
import { TestFlowBanner } from '@/components/ui/TestFlowBanner';
2222
import { GuardBadge } from '@/components/guard';
@@ -50,6 +50,7 @@ const VALID_TABS: readonly ModelLabTab[] = ['models', 'compare', 'jutsu', 'custo
5050
export function ModelLab({ initialTab = 'models' }: ModelLabProps) {
5151
const [activeTab, setActiveTab] = useState<ModelLabTab>(initialTab);
5252
const { setActiveTab: navigateTo } = useNavigation();
53+
const { models, error: modelError } = useModelContext();
5354

5455
return (
5556
<div className="space-y-6">
@@ -109,7 +110,7 @@ export function ModelLab({ initialTab = 'models' }: ModelLabProps) {
109110
</Tabs>
110111

111112
<TestFlowBanner
112-
show={true}
113+
show={models.length > 0 && !modelError}
113114
message="Model configured. Run adversarial attacks against it?"
114115
actionLabel="Open Atemi Lab"
115116
targetNavId="adversarial"

0 commit comments

Comments
 (0)