All files / scripts/mcp-client/config auth.ts

100% Statements 22/22
100% Branches 14/14
100% Functions 2/2
100% Lines 18/18

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82                                                                            40x 39x   35x 40x 40x 9x   9x 9x 9x   3x 3x 9x 9x 9x         28x             40x                       39x 39x    
/**
 * @module mcp-client/config/auth
 * @description Single sink for MCP auth-token **value** resolution.
 *
 * All token-bearing env vars and config-file token values are read here and
 * nowhere else. Per `.github/skills/Authentication-and-Credentials-for-
 * Agentic-Workflows`, this keeps the secret-value surface auditable: a new
 * credential source is a one-file change, and a security review only needs
 * to read this module to enumerate every place the client could pull a
 * token *value* from.
 *
 * Scope note: non-secret routing keys from the same MCP config file
 * (`gateway.port`, `gateway.domain`, and presence-only checks for
 * `gateway.apiKey` / `headers.Authorization`) are read in
 * `config/gateway-resolver.ts`. Those reads never return a token value to
 * the caller — they only steer URL selection — so the "single sink" rule
 * here covers every place a secret token can leak into the rest of the
 * client.
 *
 * @author Hack23 AB
 * @license Apache-2.0
 */
 
import fs from 'fs';
 
/**
 * Resolve the default MCP auth token.
 * Priority (the first non-empty value wins; every returned value has any
 * legacy `Bearer ` prefix stripped because the MCP gateway expects a raw
 * API key):
 *   1. MCP_AUTH_TOKEN env var (Bearer prefix stripped)
 *   2. MCP_GATEWAY_API_KEY env var (raw API key)
 *   3. gateway.apiKey from MCP config file (Bearer prefix stripped)
 *   4. mcpServers['riksdag-regering'].headers.Authorization from MCP config
 *      file (Bearer prefix stripped — the `Authorization` header is the
 *      legacy storage location for a raw API key in pre-v0.69 mcp-config.json)
 */
export function getDefaultAuthToken(): string {
  if (process.env['MCP_AUTH_TOKEN']) return process.env['MCP_AUTH_TOKEN'].replace(/^Bearer\s+/i, '');
  if (process.env['MCP_GATEWAY_API_KEY']) return process.env['MCP_GATEWAY_API_KEY'];
 
  const configPath = process.env['GH_AW_MCP_CONFIG'] ?? '/home/runner/.copilot/mcp-config.json';
  try {
    if (fs.existsSync(configPath)) {
      const raw = JSON.parse(fs.readFileSync(configPath, 'utf8')) as Record<string, unknown>;
 
      const gateway = raw['gateway'] as Record<string, unknown> | undefined;
      const apiKey = gateway?.['apiKey'] as string | undefined;
      if (apiKey) return apiKey.replace(/^Bearer\s+/i, '');
 
      const mcpServers = raw['mcpServers'] as Record<string, unknown> | undefined;
      const rrServer = mcpServers?.['riksdag-regering'] as Record<string, unknown> | undefined;
      const headers = rrServer?.['headers'] as Record<string, unknown> | undefined;
      const authHeader = headers?.['Authorization'] as string | undefined;
      if (authHeader) return authHeader.replace(/^Bearer\s+/i, '');
    }
  } catch {
    // Config file read is best-effort — fall through to empty token
  }
  return '';
}
 
/**
 * Token snapshot resolved once at module load. Mirrors the legacy
 * `DEFAULT_MCP_AUTH_TOKEN` constant from the monolithic client.
 */
export const DEFAULT_MCP_AUTH_TOKEN: string = getDefaultAuthToken();
 
/**
 * Non-secret presence check for the `MCP_GATEWAY_API_KEY` env var.
 *
 * Returns only a boolean — the token value never leaves this module. Routing
 * logic in `config/gateway-resolver.ts` calls this helper instead of reading
 * the env var directly so that every secret-bearing env access remains
 * auditable in a single file (per the
 * `Authentication-and-Credentials-for-Agentic-Workflows` skill).
 */
export function hasMcpGatewayApiKey(): boolean {
  const value = process.env['MCP_GATEWAY_API_KEY'];
  return typeof value === 'string' && value.length > 0;
}