PHI Guard MCP

Escanea el código fuente en busca de fugas de PHI en prompts de LLM, registros y llamadas de analítica: local-primero, sin llamadas de red.

Documentación

phi-guard-mcp

Un servidor MCP local-first que detecta PHI (información de salud protegida) que fluye hacia prompts de LLM, declaraciones de registro y llamadas de analítica — en tu código fuente, antes de que se publique.

Se ejecuta completamente en tu máquina a través de stdio. Ningún código, fragmento o valor detectado se envía a ningún lugar.

Por qué

El momento de riesgo en un código base de atención médica rara vez es la base de datos. Es la línea donde un registro de paciente se interpola en un prompt, un console.log, o un evento de analítica. Esas líneas parecen inofensivas en la revisión y nunca aparecen en el escaneo de infraestructura, porque nada está mal configurado — el código simplemente hace lo que dice.

Herramientas

redact_suggest

Toma un fragmento de texto sin procesar — una línea de registro, un prompt, un mensaje de error — detecta valores con forma de PHI y devuelve una versión redactada junto con lo que encontró.

Entrada

{ "text": "Patient John Doe (MRN-12345), DOB: 01/01/1980" }

Salida

{
  "original": "Patient John Doe (MRN-12345), DOB: 01/01/1980",
  "redacted": "Patient [NAME] ([MRN]), [DOB]",
  "detected": [
    { "type": "mrn",  "value": "MRN-12345",       "confidence": 0.9  },
    { "type": "dob",  "value": "DOB: 01/01/1980", "confidence": 0.85 },
    { "type": "name", "value": "John Doe",        "confidence": 0.8  }
  ]
}

Patrones y sus puntuaciones de confianza:

TipoConfianzaCoincidencias
ssn0.95123-45-6789
mrn0.90MRN-12345, MRN: 12345
dob0.85DOB: 01/01/1980, born 3/14/75
name0.80Patient John Doe (captura John Doe)
phone0.75555-867-5309, (555) 867 5309
email0.70jane.roe@example.com

Los patrones comienzan deliberadamente estrechos. Un falso positivo que entrena a alguien a ignorar la herramienta es peor que una coincidencia omitida.

scan_code

Recorre un directorio y marca líneas donde un identificador de apariencia sensible (patient, diagnosis, dob, ssn, mrn, birthdate, medicalrecord) aparece en la misma línea que un sumidero de riesgo (openai, anthropic, bedrock, console.log/error/warn, logger., winston, pino, .track(, y capture( / captureException( / captureMessage().

Los comentarios de línea completa // y # se omiten, por lo que un archivo que discute el manejo de PHI en prosa no activa el escáner por su propia documentación.

Dadas las líneas operativas de test/fixtures/leaky-example.ts:

const prompt = await openai.responses.create({ input: `Patient: ${patient.name}, diagnosis: ${patient.diagnosis}` });
console.log("Sending patient prompt to LLM:", prompt);

Entrada

{ "path": "/abs/path/to/repo/test/fixtures" }

Salida — extracto. El directorio completo de fixtures devuelve 8 hallazgos, porque también contiene los fixtures positivos descritos en Probado contra.

[
  {
    "file": "test/fixtures/leaky-example.ts",
    "line": 7,
    "severity": "high",
    "issue": "Sensitive-looking identifier passed to a risky sink (LLM call, logger, or analytics)",
    "snippet": "const prompt = await openai.responses.create({ input: `Patient: ${patient.name}, diagnosis: ${patient.diagnosis}` });"
  },
  {
    "file": "test/fixtures/leaky-example.ts",
    "line": 8,
    "severity": "high",
    "issue": "Sensitive-looking identifier passed to a risky sink (LLM call, logger, or analytics)",
    "snippet": "console.log(\"Sending patient prompt to LLM:\", prompt);"
  }
]

Escanea .ts, .js, .tsx, .jsx, .py, .go. Omite node_modules, dist, build, coverage, out, .next, .turbo, y archivos de puntos.

Ambas condiciones deben cumplirse en la misma línea. Eso es lo que lo mantiene silencioso: en el propio código fuente de este repositorio — que está denso con las palabras patient, diagnosis, mrn, y ssn dentro de sus definiciones de patrones — reporta cero hallazgos.

Probado contra

6 de 6 patrones de fuga reales detectados, en 5 sumideros diferentes (OpenAI, Anthropic, Sentry, Winston, PostHog/analítica) y 2 lenguajes (TypeScript, Python) — incluyendo identificadores en snake_case (patient_name, patient_diagnosis), que una regex ingenua de límite de palabra omite y que es la convención de nomenclatura dominante en Python y Go.

0 falsos positivos en 5 fixtures de código limpio, incluyendo código que discute políticas de PHI en comentarios y prosa sin filtrarla nunca, y código que maneja legítimamente registros de pacientes sin enviarlos a ningún lugar riesgoso.

1 limitación documentada: la detección se basa en líneas, por lo que un valor sensible asignado en una línea y usado en una llamada riesgosa varias líneas después no se detecta actualmente. Este es un límite de alcance conocido, no un error — ver Lo que esto NO es abajo.

Los fixtures de prueba completos viven en test/fixtures/ si quieres verificar cualquiera de esto por ti mismo en lugar de tomarlo por fe:

npm test

La suite afirma ambas direcciones: cada archivo bajo positive/ debe producir al menos un hallazgo, y negative/ debe producir exactamente cero. Una omisión en cualquiera de los lados falla la ejecución.

Lo que esto NO es

  • No es un servicio alojado. Es un proceso local de stdio. No hay backend, no hay cuenta, y no hay telemetría. Tu código nunca sale de tu máquina.
  • No es una certificación HIPAA, auditoría o atestación de cumplimiento. Pasar una ejecución de scan_code no prueba nada a un regulador. Es un linter para una clase específica de error, no evidencia de cumplimiento. Trata un resultado limpio como "estos patrones particulares no se activaron", nunca como "este código base es seguro para HIPAA".
  • No es un competidor de Prowler, AWS Config o herramientas de postura en la nube. Esos escanean infraestructura y configuración. Esto lee código fuente y encuentra una clase diferente de problema. Son complementarios; esto no reemplaza a ninguno.
  • No es exhaustivo. La detección basada en regex tiene una tasa real de falsos negativos. No detectará PHI en una variable que no puede coincidir por nombre, o valores que llegan desde una llamada externa.
  • No puede seguir un valor a través de líneas. El identificador y el sumidero deben aparecer en la misma línea. Asignar patient.diagnosis a una variable local y registrar esa variable tres líneas después no produce ningún hallazgo — hay un ejemplo trabajado en test/fixtures/known-limitations/. El análisis real de flujo de datos está fuera del alcance para v1; este es un límite deliberado, y el fixture existe para que la brecha permanezca visible en lugar de olvidada.
  • No es completamente consciente de comentarios. Solo los comentarios de línea completa // y # se omiten. Los comentarios de bloque (/* ... */) y los comentarios finales de línea todavía se escanean, por lo que una palabra clave de sumidero dentro de uno de esos puede producir un hallazgo aunque nada se ejecute.

Configuración

Requiere Node.js 18+.

git clone https://github.com/Abidit/phi-guard-mcp.git
cd phi-guard-mcp
npm install
npm run build

dist/ está en gitignore, por lo que npm run build es necesario después de clonar — la configuración de MCP a continuación apunta a la salida compilada.

Claude Code

Agrega .mcp.json a la raíz de tu proyecto, usando la ruta absoluta a tu clon:

{
  "mcpServers": {
    "phi-guard": {
      "command": "node",
      "args": ["/absolute/path/to/phi-guard-mcp/dist/index.js"]
    }
  }
}

Reinicia Claude Code, o ejecuta /mcp y reconecta phi-guard. Una reconstrucción sola no alcanzará un proceso stdio ya en ejecución.

Verificación

npm test          # fixture suite: positive, negative, known limitations
npm run typecheck # src/ and test/ under strict mode
npx tsx test/smoke.ts

O conduce a través del Inspector oficial sin un navegador:

npx @modelcontextprotocol/inspector --cli node dist/index.js --method tools/list
npx @modelcontextprotocol/inspector --cli node dist/index.js \
  --method tools/call --tool-name redact_suggest \
  --tool-arg text="Patient John Doe (MRN-12345)"

El servidor declara solo la capacidad tools, por lo que resources/list y prompts/list devuelven correctamente -32601 Method not found. La interfaz del Inspector prueba los tres independientemente y muestra esos dos en rojo — esperado, no un fallo.

Licencia

MIT — ver LICENCIA.