fix cve correlation service and guid validation

This commit is contained in:
Oliver Kieselbach
2026-04-01 15:50:16 +02:00
parent cff85ab104
commit 9cee2ed9e9
7 changed files with 56 additions and 14 deletions
@@ -1 +1 @@
760
762
@@ -481,8 +481,9 @@ namespace AutopilotMonitor.Functions.Services.Vulnerability
var cveDataJson = JsonSerializer.Serialize(cacheCves, JsonOptions);
// Guard against exceeding Table Storage 64KB single-property limit
// Iteratively halve until it fits (floor: 10 CVEs)
while (cveDataJson.Length > 60_000 && cacheCves.Count > 10)
// Azure Table Storage uses UTF-16 encoding → 64KB = 32K chars max.
// Use 30_000 as threshold to leave headroom for entity overhead.
while (cveDataJson.Length > 30_000 && cacheCves.Count > 10)
{
cacheCves = cacheCves.Take(cacheCves.Count / 2).ToList();
cveDataJson = JsonSerializer.Serialize(cacheCves, JsonOptions);
@@ -491,7 +492,7 @@ namespace AutopilotMonitor.Functions.Services.Vulnerability
}
// Last resort: strip Criteria strings (biggest space consumer) and retry
if (cveDataJson.Length > 60_000)
if (cveDataJson.Length > 30_000)
{
foreach (var c in cacheCves)
foreach (var v in c.AffectedVersions)
@@ -11,6 +11,7 @@ import { useNotifications } from "../../contexts/NotificationContext";
import { api } from "@/lib/api";
import { authenticatedFetch, TokenExpiredError } from "@/lib/authenticatedFetch";
import { trackEvent } from "@/lib/appInsights";
import { asGuidOrUndefined } from "@/lib/inputValidation";
import { Session } from "./types";
import { StatsCard } from "./components/StatsCards";
import { WelcomeMessage } from "./components/WelcomeMessage";
@@ -100,9 +101,11 @@ export default function Home() {
const fetchSessions = async (loadMoreCursor?: string, globalTenantIdOverride?: string) => {
try {
// Use different endpoint based on global admin mode
const effectiveTenantFilter = globalTenantIdOverride !== undefined ? globalTenantIdOverride : tenantIdFilter.trim();
// Only pass tenantIdFilter as query param if it's a valid GUID (fuzzy search sets the GUID on selection)
const rawFilter = globalTenantIdOverride !== undefined ? globalTenantIdOverride : tenantIdFilter.trim();
const effectiveTenantFilter = asGuidOrUndefined(rawFilter);
let endpoint = globalAdminMode
? api.globalSessions.list(effectiveTenantFilter || undefined)
? api.globalSessions.list(effectiveTenantFilter)
: api.sessions.list(tenantId);
// Append cursor for "Load More" requests
@@ -3,8 +3,7 @@ import { useState, useCallback } from "react";
import { api } from "@/lib/api";
import { authenticatedFetch } from "@/lib/authenticatedFetch";
import { RuleResult } from "@/types";
const GUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
import { isGuid } from "@/lib/inputValidation";
export function useSessionAnalysis(
sessionId: string,
@@ -16,7 +15,7 @@ export function useSessionAnalysis(
const [vulnerabilityReport, setVulnerabilityReport] = useState<unknown>(null);
const fetchAnalysisResults = useCallback(async (reanalyze = false) => {
if (!sessionTenantId || !GUID_REGEX.test(sessionTenantId)) return;
if (!sessionTenantId || !isGuid(sessionTenantId)) return;
try {
setLoadingAnalysis(true);
@@ -38,7 +37,7 @@ export function useSessionAnalysis(
}, [sessionId, sessionTenantId, getAccessToken]);
const fetchVulnerabilityReport = useCallback(async (rescan = false) => {
if (!sessionTenantId || !GUID_REGEX.test(sessionTenantId)) return;
if (!sessionTenantId || !isGuid(sessionTenantId)) return;
try {
const response = await authenticatedFetch(
api.sessions.vulnerabilityReport(sessionId, sessionTenantId, rescan || undefined),
@@ -33,8 +33,7 @@ import { generateUiExport, generateCsvExport, generateSessionCsvExport, generate
import { Session, EnrollmentEvent, RuleResult } from "@/types";
import { trackEvent } from "@/lib/appInsights";
import { useAdminMode } from "@/hooks/useAdminMode";
const GUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
import { isGuid } from "@/lib/inputValidation";
export default function SessionDetailPage() {
const params = useParams();
@@ -330,7 +329,7 @@ export default function SessionDetailPage() {
return;
}
const effectiveTenantId = resolveEffectiveTenantId();
if (!effectiveTenantId || !GUID_REGEX.test(effectiveTenantId)) {
if (!effectiveTenantId || !isGuid(effectiveTenantId)) {
return;
}
fetchEventsInFlight.current = true;
@@ -0,0 +1,40 @@
/**
* Centralised input-validation helpers.
*
* Every value that reaches an API call or is interpolated into a URL must
* pass through one of these guards first. Keep validators here so pages
* don't roll their own regex and we have a single place to harden.
*/
// ---------------------------------------------------------------------------
// GUID / UUID
// ---------------------------------------------------------------------------
const GUID_RE =
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
/** Returns `true` when `value` is a well-formed GUID / UUID (any version). */
export function isGuid(value: string | null | undefined): boolean {
return !!value && GUID_RE.test(value.trim());
}
/**
* Returns the trimmed GUID if valid, otherwise `undefined`.
* Handy for optional query-parameter slots: pass the result straight to the
* URL builder and it will be omitted when invalid.
*/
export function asGuidOrUndefined(
value: string | null | undefined,
): string | undefined {
if (!value) return undefined;
const trimmed = value.trim();
return GUID_RE.test(trimmed) ? trimmed : undefined;
}
// ---------------------------------------------------------------------------
// Session ID (same shape as GUID today, but named separately for clarity)
// ---------------------------------------------------------------------------
export function isSessionId(value: string | null | undefined): boolean {
return isGuid(value);
}
File diff suppressed because one or more lines are too long