diff --git a/src/Agent/AutopilotMonitor.Agent/buildcounter.txt b/src/Agent/AutopilotMonitor.Agent/buildcounter.txt index 8766d8a9..aec11d8f 100644 --- a/src/Agent/AutopilotMonitor.Agent/buildcounter.txt +++ b/src/Agent/AutopilotMonitor.Agent/buildcounter.txt @@ -1 +1 @@ -732 +733 diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Ingest/ReportDistressFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Ingest/ReportDistressFunction.cs index 6a8cd30a..6679dae4 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Ingest/ReportDistressFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Ingest/ReportDistressFunction.cs @@ -192,10 +192,25 @@ namespace AutopilotMonitor.Functions.Functions.Ingest { // X-Forwarded-For can contain "client, proxy1, proxy2" — take the first var ip = fwd.Split(',')[0].Trim(); - // Strip port if present (e.g., "1.2.3.4:12345") + + // Handle bracketed IPv6 with port: [::1]:12345 + if (ip.StartsWith('[')) + { + var closeBracket = ip.IndexOf(']'); + if (closeBracket > 0) + ip = ip.Substring(1, closeBracket - 1); + return ip; + } + + // Bare IPv6 (contains multiple colons): return as-is + if (ip.IndexOf(':') != ip.LastIndexOf(':')) + return ip; + + // IPv4 with optional port: strip port (e.g., "1.2.3.4:12345") var colonIdx = ip.LastIndexOf(':'); - if (colonIdx > 0 && !ip.Contains(']')) // avoid stripping IPv6 + if (colonIdx > 0) ip = ip.Substring(0, colonIdx); + return ip; } } diff --git a/src/McpServer/autopilot-monitor-mcp/src/access-guard.ts b/src/McpServer/autopilot-monitor-mcp/src/access-guard.ts index 3055bc0d..2b48dd04 100644 --- a/src/McpServer/autopilot-monitor-mcp/src/access-guard.ts +++ b/src/McpServer/autopilot-monitor-mcp/src/access-guard.ts @@ -9,7 +9,7 @@ */ import type { Request, Response, NextFunction } from 'express'; import { extractTokenClaims, isTokenExpired } from './auth.js'; -import { setCurrentToken, runWithToken } from './client.js'; +import { runWithToken } from './client.js'; const BASE_URL = process.env.AUTOPILOT_API_URL ?? 'https://autopilotmonitor-api.azurewebsites.net'; @@ -148,11 +148,8 @@ export function accessGuard(req: Request, res: Response, next: NextFunction): vo return; } - // Set token for pass-through to backend API. - // runWithToken scopes the token to this async context so concurrent - // sessions cannot overwrite each other's tokens on the event loop. - // setCurrentToken is kept as fallback during the transition. - setCurrentToken(token); + // Scope the token to this async context so concurrent sessions + // cannot overwrite each other's tokens on the event loop. runWithToken(token, () => next()); }) .catch(() => { diff --git a/src/McpServer/autopilot-monitor-mcp/src/client.ts b/src/McpServer/autopilot-monitor-mcp/src/client.ts index ab0c313f..60c06294 100644 --- a/src/McpServer/autopilot-monitor-mcp/src/client.ts +++ b/src/McpServer/autopilot-monitor-mcp/src/client.ts @@ -23,19 +23,8 @@ export function runWithToken(token: string, fn: () => T): T { return tokenStore.run(token, fn); } -/** - * @deprecated Use `runWithToken` instead. Kept for backward compatibility - * during the transition — sets a fallback global token for code paths that - * haven't migrated to `runWithToken` yet. - */ -let _fallbackToken: string | undefined; - -export function setCurrentToken(token: string | undefined): void { - _fallbackToken = token; -} - export function getCurrentToken(): string | undefined { - return tokenStore.getStore() ?? _fallbackToken; + return tokenStore.getStore(); } async function apiFetch(path: string, options: RequestInit = {}): Promise { diff --git a/src/McpServer/autopilot-monitor-mcp/src/tools/admin.ts b/src/McpServer/autopilot-monitor-mcp/src/tools/admin.ts index ced6da83..3ddf9e54 100644 --- a/src/McpServer/autopilot-monitor-mcp/src/tools/admin.ts +++ b/src/McpServer/autopilot-monitor-mcp/src/tools/admin.ts @@ -270,6 +270,10 @@ export function registerAdminTools(server: McpServer): void { ); // ── Admin Diagnostic Tools ──────────────────────────────────────────── + // Security note: query_table and query_backend_logs accept arbitrary OData/KQL + // expressions. This is by design — these are trusted-admin-only diagnostic tools + // gated by GlobalAdmin RBAC on the backend. No client-side filter allowlist is + // needed because the backend enforces the same permission boundary. // Tool 20: list_tables server.tool(