Skip to content

Security: Receipts-Health/downcoding-defense

Security

SECURITY.md

Security Policy

downcoding-defense is the open detection + appeal-letter engine behind Receipts Health. It runs fully self-hosted: a local 835 file in, a PDF out. We take the security of both the code and the people who run it seriously.

Reporting a vulnerability

Please do not open a public issue for security vulnerabilities.

Report privately to security@receiptshealth.com. Include:

  • a description of the issue and its impact,
  • the affected version or commit,
  • steps to reproduce (a minimal proof of concept if you have one), and
  • any suggested remediation.

You can expect an acknowledgement within 3 business days and a substantive response within 10 business days. We practice coordinated disclosure: we'll work with you on a fix and a disclosure timeline, and we're happy to credit you in the release notes unless you'd rather stay anonymous. Please give us a reasonable window to ship a fix before any public disclosure.

If you prefer encrypted email, say so in a first message and we'll exchange keys.

Scope

In scope — anything in this repository: the 835 parser, de-identification boundary, detection rules, the LLM classifier and letter generator, the standards index, PDF generation, and their handling of untrusted input (a malformed or malicious 835, a crafted CSV, prompt-injection via file contents, citation-token validation, path handling, dependency risk).

Out of scope — the hosted Receipts Health service. Authentication, secrets, Cloud SQL, the operational PHI boundary, and job orchestration live in a separate, private deployment plane and are not part of this repo. Report issues in the hosted product to the same address, but note that its code is not published here.

What is not in this repository, by design

Open code is not an open system. This repo is public on purpose — auditability and "many eyes" are a security asset, and the AGPL forces hosted forks to publish their changes — but the sensitive material stays out:

  • No credentials or secrets. Every key is read from the environment at run time (see .env.example for the variable names; the values are yours). None are committed.
  • No PHI. The engine de-identifies at the parse boundary before any LLM call; identifiers are templated back into letters locally, after the model call. The committed 835 test corpus is synthetic — a no-PHI rule is enforced by policy in tests/fixtures/corpus/README.md and by tests/test_deidentify_corpus.py. If you ever find real patient data in this repo or its history, treat it as a security incident and report it privately.

Running it safely

  • Treat every input 835/CSV as untrusted; run in an environment you control.
  • Set MOCK_EXTERNAL=true to exercise the full flow offline with no network and no LLM call.
  • Keep dependencies current (uv sync); we monitor advisories for the pinned set.

There aren't any published security advisories