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:
| Tipo | Confianza | Coincidencias |
|---|---|---|
ssn | 0.95 | 123-45-6789 |
mrn | 0.90 | MRN-12345, MRN: 12345 |
dob | 0.85 | DOB: 01/01/1980, born 3/14/75 |
name | 0.80 | Patient John Doe (captura John Doe) |
phone | 0.75 | 555-867-5309, (555) 867 5309 |
email | 0.70 | jane.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_codeno 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.diagnosisa una variable local y registrar esa variable tres líneas después no produce ningún hallazgo — hay un ejemplo trabajado entest/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.