PHI Guard MCP
Examina o código-fonte em busca de vazamento de PHI em prompts de LLM, logs e chamadas de analytics - local-first, sem chamadas de rede.
Documentação
phi-guard-mcp
Um servidor MCP local-first que detecta PHI (informações de saúde protegidas) fluindo para prompts de LLM, declarações de log e chamadas de analytics — no seu código-fonte, antes de ser publicado.
Ele roda inteiramente na sua máquina via stdio. Nenhum código, trecho ou valor detectado é enviado a lugar algum.
Por quê
O momento de risco em uma base de código de saúde raramente é o banco de dados. É a linha
onde um registro de paciente é interpolado em um prompt, um console.log, ou um
evento de analytics. Essas linhas parecem inofensivas na revisão e nunca aparecem em
varreduras de infraestrutura, porque nada está mal configurado — o código está apenas
fazendo o que diz.
Ferramentas
redact_suggest
Recebe um trecho de texto bruto — uma linha de log, um prompt, uma mensagem de erro — detecta valores com formato de PHI e retorna uma versão editada junto com o que encontrou.
Entrada
{ "text": "Patient John Doe (MRN-12345), DOB: 01/01/1980" }
Saída
{
"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 }
]
}
Padrões e suas pontuações de confiança:
| Tipo | Confiança | Correspondências |
|---|---|---|
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 |
Os padrões começam deliberadamente estreitos. Um falso positivo que treina alguém a ignorar a ferramenta é pior do que uma correspondência perdida.
scan_code
Percorre um diretório e sinaliza linhas onde um identificador de aparência sensível
(patient, diagnosis, dob, ssn, mrn, birthdate, medicalrecord)
aparece na mesma linha que um destino arriscado (openai, anthropic, bedrock,
console.log/error/warn, logger., winston, pino, .track(, e
capture( / captureException( / captureMessage().
Comentários de linha inteira // e # são ignorados, então um arquivo que discute o tratamento de PHI
em prosa não dispara o scanner na sua própria documentação.
Dadas as linhas operacionais 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" }
Saída — trecho. O diretório completo de fixtures retorna 8 descobertas, porque também contém os fixtures positivos descritos em Testado 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);"
}
]
Escaneia .ts, .js, .tsx, .jsx, .py, .go. Ignora node_modules, dist,
build, coverage, out, .next, .turbo, e dotfiles.
Ambas as condições devem ser verdadeiras na mesma linha. É isso que o mantém silencioso: no
próprio código-fonte deste repositório — que é denso com as palavras patient, diagnosis,
mrn, e ssn dentro de suas definições de padrão — ele relata zero descobertas.
Testado contra
6 de 6 padrões reais de vazamento detectados, em 5 destinos diferentes (OpenAI,
Anthropic, Sentry, Winston, PostHog/analytics) e 2 linguagens (TypeScript,
Python) — incluindo identificadores snake_case (patient_name,
patient_diagnosis), que uma regex ingênua de limite de palavra não captura e que é a
convenção de nomenclatura dominante em Python e Go.
0 falsos positivos em 5 fixtures de código limpo, incluindo código que discute política de PHI em comentários e prosa sem nunca vazá-la, e código que lida legitimamente com registros de pacientes sem enviá-los a nenhum lugar arriscado.
1 limitação documentada: a detecção é baseada em linhas, então um valor sensível atribuído em uma linha e usado em uma chamada arriscada várias linhas depois não é atualmente capturado. Este é um limite de escopo conhecido, não um bug — veja O que isto NÃO é abaixo.
Os fixtures completos de teste estão em test/fixtures/ se você quiser
verificar qualquer um destes por conta própria em vez de aceitar por fé:
npm test
A suíte afirma ambas as direções: cada arquivo em positive/ deve produzir pelo
menos uma descoberta, e negative/ deve produzir exatamente zero. Uma falha em qualquer
lado reprova a execução.
O que isto NÃO é
- Não é um serviço hospedado. É um processo stdio local. Não há backend, nenhuma conta e nenhuma telemetria. Seu código nunca sai da sua máquina.
- Não é uma certificação HIPAA, auditoria ou atestado de conformidade. Passar em uma
execução de
scan_codenão prova nada a um regulador. É um linter para uma classe específica de erro, não evidência de conformidade. Trate um resultado limpo como "estes padrões específicos não dispararam", nunca como "esta base de código é segura para HIPAA". - Não é um concorrente do Prowler, AWS Config ou ferramentas de postura de nuvem. Esses escaneiam infraestrutura e configuração. Este lê código-fonte e encontra uma classe diferente de problema. Eles são complementares; este não substitui nenhum deles.
- Não é exaustivo. A detecção baseada em regex tem uma taxa real de falsos negativos. Não capturará PHI em uma variável que não consegue corresponder por nome, ou valores que chegam de uma chamada externa.
- Não é capaz de seguir um valor entre linhas. O identificador e o destino têm
que aparecer na mesma linha. Atribuir
patient.diagnosisa uma variável local e registrar essa variável três linhas depois não produz nenhuma descoberta — há um exemplo trabalhado emtest/fixtures/known-limitations/. Análise real de fluxo de dados está fora do escopo para v1; este é um limite deliberado, e o fixture existe para que a lacuna permaneça visível em vez de esquecida. - Não é totalmente ciente de comentários. Apenas comentários de linha inteira
//e#são ignorados. Comentários de bloco (/* ... */) e comentários finais de linha ainda são escaneados, então uma palavra-chave de destino dentro de um deles pode produzir uma descoberta mesmo que nada seja executado.
Configuração
Requer Node.js 18+.
git clone https://github.com/Abidit/phi-guard-mcp.git
cd phi-guard-mcp
npm install
npm run build
dist/ está no gitignore, então npm run build é necessário após clonar — a configuração
do MCP abaixo aponta para a saída compilada.
Claude Code
Adicione .mcp.json à raiz do seu projeto, usando o caminho absoluto para o seu clone:
{
"mcpServers": {
"phi-guard": {
"command": "node",
"args": ["/absolute/path/to/phi-guard-mcp/dist/index.js"]
}
}
}
Reinicie o Claude Code, ou execute /mcp e reconecte phi-guard. Apenas uma recompilação
não alcançará um processo stdio já em execução.
Verificação
npm test # fixture suite: positive, negative, known limitations
npm run typecheck # src/ and test/ under strict mode
npx tsx test/smoke.ts
Ou conduza-o através do Inspector oficial sem um 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)"
O servidor declara apenas a capacidade tools, então resources/list e
prompts/list retornam corretamente -32601 Method not found. A interface do Inspector
testa todos os três independentemente e mostra esses dois em vermelho — esperado, não uma falha.
Licença
MIT — veja LICENSE.