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:

TipoConfiançaCorrespondências
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

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_code nã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.diagnosis a uma variável local e registrar essa variável três linhas depois não produz nenhuma descoberta — há um exemplo trabalhado em test/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.