From b91001d0293a6c671007bed59d8101723ee736c1 Mon Sep 17 00:00:00 2001 From: Naman Singh Date: Fri, 17 Jul 2026 12:55:32 +0530 Subject: [PATCH 1/2] fix(security): enforce API key auth on saved views router (#2035) The saved_views_router was registered in main.py without the require_api_key dependency, allowing unauthenticated access to all CRUD endpoints for saved views. This is an authentication bypass that exposes filter configurations and enables data tampering. Add Depends(require_api_key) to the router definition, matching the pattern used by the main API router in routes.py. --- backend/secuscan/saved_views.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/backend/secuscan/saved_views.py b/backend/secuscan/saved_views.py index 889ecc406..8f05e07d0 100644 --- a/backend/secuscan/saved_views.py +++ b/backend/secuscan/saved_views.py @@ -4,12 +4,17 @@ import uuid from typing import Any, Dict, List, Optional -from fastapi import APIRouter, HTTPException +from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel, Field, field_validator +from .auth import require_api_key from .database import get_db -saved_views_router = APIRouter(prefix="/api/v1/saved-views", tags=["saved-views"]) +saved_views_router = APIRouter( + prefix="/api/v1/saved-views", + tags=["saved-views"], + dependencies=[Depends(require_api_key)], +) _VALID_SORT_MODES = {"severity", "newest", "oldest", "target"} _VALID_SEVERITIES = {"all", "critical", "high", "medium", "low", "info"} From 1ae532c39b288c19f7d011ee9619408d471db4de Mon Sep 17 00:00:00 2001 From: Naman Singh Date: Fri, 17 Jul 2026 15:47:14 +0530 Subject: [PATCH 2/2] fix(frontend): route saved-view requests through authenticated client Export getApiKey from api.ts and update useSavedViews apiFetch to include X-Api-Key header and credentials:'include', matching the authenticated request pattern used by the rest of the API client. Without this, saved-view requests silently fail with 401 in deployments where API_BASE is absolute, causing the feature to fall back to localStorage even after login. --- frontend/src/api.ts | 2 +- frontend/src/hooks/useSavedViews.ts | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/frontend/src/api.ts b/frontend/src/api.ts index 390f51d8c..29401db05 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -390,7 +390,7 @@ export async function logoutSession(): Promise { _apiKey = null } -function getApiKey(): string | null { +export function getApiKey(): string | null { return _apiKey } diff --git a/frontend/src/hooks/useSavedViews.ts b/frontend/src/hooks/useSavedViews.ts index 1e2eddec1..f4e418614 100644 --- a/frontend/src/hooks/useSavedViews.ts +++ b/frontend/src/hooks/useSavedViews.ts @@ -1,5 +1,5 @@ import { useCallback, useEffect, useRef, useState } from 'react' -import { API_BASE } from '../api' +import { API_BASE, getApiKey } from '../api' export interface FilterPreset { severity: string @@ -126,8 +126,15 @@ async function apiFetch( init?: RequestInit, ): Promise { try { + const apiKey = getApiKey() + const authHeaders: Record = apiKey ? { 'X-Api-Key': apiKey } : {} const res = await fetch(`${API_BASE}${path}`, { ...init, + headers: { + ...authHeaders, + ...(init?.headers as Record | undefined), + }, + credentials: 'include', signal: AbortSignal.timeout(8000), }) if (!res.ok) return null