Coordinated vulnerability disclosure

Every piece of software has bugs. We'd rather you found ours first.

This is diagnos's vulnerability disclosure program. We don't pay cash — right now we simply can't. We pay in public gratitude, a spot in the hall of fame, and the certainty that somebody's medical report got safer because of your contribution.

security.txt
curl https://diagnos.health/.well-known/security.txt Contact: mailto:[email protected]Contact: mailto:[email protected]Policy: https://diagnos.health/en/bounty/Preferred-Languages: pt-BR, en, es, it, frExpires: 2027-09-16T00:00:00Z

If you need more detail, the file is live: /.well-known/security.txt

Quick access

All ten sections on this page — jump straight to the one you need.

Hall of fame

Every valid vulnerability lands on this list, under whatever name you pick — your name, your handle, your @, or none of the above.

#ResearcherWhat they foundWhen
001 YOU this row is reserved whenever you like
002 — — —
003 — — —
004 — — —
005 — — —
Pixel art poster of a man in a top hat pointing straight at the reader

I WANT YOU

FOR DIAGNOS SECURITY

Yes, it's empty. The program was born along with this page, so somebody has to go first. It isn't a competitive slot yet.

Security posture

What is already in production, and the six gaps we haven't closed.

Controls already in production

  • Your password never reaches us

    Login runs on OPAQUE, a PAKE: the server never sees your password, not even during login. It only keeps a registration_record, which is useless on its own.

  • The server can't open your vault

    Keys are born on your device. The server only ever receives ciphertext — there's no line of code on the other end that opens a KEK.

  • Replaying a request doesn't work

    Every sensitive request carries a nonce + timestamp, primary key (scope_id, ts, nonce) in D1, a 120s clock tolerance, and a 300s retention window swept by cron.

  • Every relevant request becomes a record

    We write one object per relevant request to R2. The write is billed and runs off the response's critical path — auditing doesn't slow anyone down.

  • The most sensitive domain is the most locked down

    sign.diagnos.health runs a genuinely enforced default-src 'none' CSP — it blocks, not just reports — with SRI computed at build time. The device cookie is __Host-, HttpOnly, Secure, SameSite=Strict.

  • Clinical data knows where it lives

    Reports and images sit in an R2 bucket with EU jurisdiction tied directly to the binding — not a promise in a contract, a setting the infrastructure enforces.

Known gaps

  • We've never hired an independent penetration test. Nobody outside the company has audited this.
  • No SOC 2, no ISO 27001.
  • HIPAA: the technical requirements are covered, but the subprocessor contract chain (BAA) is incomplete — only two signed, the rest still in negotiation.
  • The app's CSP is still report-only: it reports violations, it doesn't block anything.
  • Automated vulnerability scanning still happens ad hoc, not as a continuous routine.
  • The rate-limit circuit breaker is a conservative guess, never calibrated against real production traffic.

The first list is what we've already built. The second is why this page exists. We're not asking you to trust us — we're asking you to distrust us, on the record.

Program scope

The authorised targets, what's out, and the rule for the internal audience.

Nine possible targets. The tag next to each one says whether it's in, out, or in-with-fine-print — read it before you point a tool at any of them.

Target Status What it is
vault.diagnos.health In scope The product's main server — the "vault". Session, OPAQUE unlock, the ledger, signed R2 URLs, AI agents, MCP and OAuth all run through here.
api.diagnos.health In scope The same server, under the other name the product uses for it. Both entries live side by side in the code today — both are valid.
/api/internal/v1/* In scope, with a catch A prefix inside the API itself, reserved for the official app — it requires headers a bare curl won't produce on its own. Read the note below before testing this line.
sign.diagnos.health In scope The report-signing ceremony — WebAuthn/passkey, reached by link or QR code. The product's most sensitive domain.
app.diagnos.health In scope diagnos's web app.
diagnos.health In scope This site, the one you're reading right now. It's in scope even though it's static marketing HTML — but set your expectations: the worst thing you'll find here is an open redirect.
SDK · CLI · API Apache-2.0 In scope SDK, CLI and API in Python, Apache-2.0 licensed. The code is heading to a public repository soon — reading it is welcome.
*.diagnos.health Out of scope Any *.diagnos.health not on this list is out of scope and outside the safe harbor. It's the standard GitHub made popular: we can only authorize research on systems that are ours.
cloudflare · google · modal · sentry · … Out of scope Cloudflare, Google/Firebase, Modal, Sentry and the like. Report to them, not us — unless the problem is clearly in our integration.

About the "internal" audience (and why curl alone gets you nowhere)

Required headers

The five headers the official app emits on its own.

The internal audience requires, all at once:

  • Authorization: Bearer <Firebase ID token>
  • X-AppCheck-Token
  • X-Signature-Hmac
  • X-Signature-Nonce
  • X-Signature-Timestamp

What doesn't exist

Two pieces of internal folklore this note kills.
  • There's no X-Device-Challenge. The name circulated internally, but it never existed in a single line of code.
  • There's no automatic workspace lockout. No wrong call locks an account by itself — that one also circulated, and it's also false.

What's actually there

Rate limiting, real replay protection, brute-force throttling, and human-reviewed telemetry.

What's actually there, and says the same thing:

  • Per-route rate limiting, plus a circuit breaker per scope: 600 requests per window.
  • Real replay protection: primary key (scope_id, ts, nonce) — a repeated nonce returns 409, not 401.
  • Brute-force throttling on login: 10 attempts in 15 minutes per account, best-effort.
  • Every 4xx/5xx becomes an anonymous security signal; high-severity ones open an exception a human reviews before any block happens.

Test the internal audience through the web app, with your own account — the headers get emitted naturally, and everything you do there is in scope and covered by the safe harbor. Hammering /api/internal/v1/* directly with curl proves nothing beyond a collection of 401s — and it just creates noise a human has to triage by hand.

Patient data

The one rule with no exception: stop at the first real record.

What not to do

  • Don't open the exam.
  • Don't scroll.
  • Don't take a screenshot.
  • Don't copy the ID "to check later".

What to do

  • Report it right away, with the minimum detail needed to prove the problem exists.
  • The proof of concept demonstrates access, never content — for example: "I could list records from other workspaces via endpoint X by swapping parameter Y", without pasting the report itself.
  • Delete every local copy: cache, screenshots, Burp history, proxy logs.
  • Never exfiltrate, download, share, publish, or try to monetize patient data — not even to prove impact.

This isn't bureaucracy. It's that on the other side of that JSON is a biopsy result with someone's full name on it.

Rules of engagement

What's allowed while testing, AI use, response times and disclosure.

While testing

Your own test accounts, automated tooling, and stop at the first real record.
Allowed
  • Test with your own test accounts, or accounts you created yourself.
  • Run automated tooling — scanners, fuzzers — with good judgment.
  • Stop at the first sign of real patient data and report it.
Not allowed
  • Accessing, or attempting to access, someone else's account.
  • Volumetric DoS, or pointing a scanner that fires thousands of requests per minute at production — dumping traffic isn't research, it's abuse.
  • Social engineering our team, our patients, or our partners.
  • Physical attacks.

Stopping isn't a suggestion — it's the single most important rule on this page. Full detail in Patient data.

AI use in reports

Fine to use. Every technical claim in the report is still yours.
  • Using AI to explore or write is fine — we're not going to pretend it doesn't happen. But you're responsible for every word and every technical claim in the report, AI-generated or not.
  • Explicitly say whether an AI tool helped.
  • Every report needs a proof of concept that actually works, reproduced by you. Scanner or LLM output without manual confirmation isn't a vulnerability — it's a hypothesis, and an unverified hypothesis gets closed as invalid.

A fabricated, exaggerated, or non-reproducing report gets closed without detailed feedback. Repeat offenses get you banned from the program.

curl — Jan 2026

In January 2026, the curl project shut down its entire bug bounty program after AI-generated reports came to vastly outnumber valid ones. In years of tracking it, not a single report produced solely by AI, without human verification, ever found a real vulnerability.

We'd rather not end up there.

Response timelines

Acknowledged within 5 business days, then triage, fix, and notice.
  1. Acknowledgment

    We confirm receipt of your report within 5 business days.

  2. Triage

    We assess severity and tell you where it's headed.

  3. Fix

    We work the fix with priority proportional to severity.

  4. Notice

    We let you know once the fix ships.

  5. Recognition

    We offer optional public credit — and we never retaliate against good-faith research.

The full severity-and-priority ladder is in the Severity classification.

These are service targets, not a contractual SLA — but they're the standard we hold ourselves to.

Public disclosure

We ask for 90 calendar days before any publication.

We ask for 90 calendar days counted from confirmation of the issue — not from submission, which is a date only you control — before any publication.

If the fix ships sooner, we can agree on a joint disclosure date earlier.

If the issue is being actively exploited by someone else right now, that window shrinks — tell us immediately.

And never publish patient data, real credentials, or anything that raises the risk, even after the window closes.

Legal safe harbor

Why this policy is the express authorisation Brazilian law requires.

In practice, we commit to:

  • Not pursuing or supporting any civil claim, criminal complaint, or police report against you for research done under this policy — including for any technical measure used to access a system in scope.
  • If a third party threatens or brings legal action against you over that research, stating publicly and formally that your conduct was authorized, good-faith security research.

And that has limits, just as visible as the promise:

  • It only covers the systems listed under Scope. We can't authorize research on a third party's system, even one reachable from ours.
  • It doesn't cover conduct outside this policy's scope or rules.
  • It doesn't cover taking data with you. Accessing to test is one thing; extracting is another — see Patient data and §4 of Article 154-A, which raises the penalty specifically for that.
  • It doesn't apply to research done to extort or coerce anyone, nor to anyone excluded under Recognition.

Testing us from outside Brazil? This authorization is still grounded in Brazilian law — it's the law that governs the systems you'd be touching. As a courtesy, we also treat this policy as a good-faith authorization to access under whatever computer-crime law applies where you live.

Not sure whether a specific test fits this policy? Ask us first, at [email protected].

Legal basis under Brazilian law

Brazil has no CFAA, no DMCA. Those are American statutes, and pasting that language into a Brazilian policy protects nobody here — it just gives away that the text was translated, not thought through.

What actually applies is Article 154-A of the Brazilian Penal Code (Law 12,737/2012, amended by Law 14,155/2021). It makes it a crime to break into someone else's computer system "without the express or tacit authorization" of the person who uses that system.

One detail changes everything. The 2012 wording only applied when the break-in happened by "unduly violating a security mechanism" — you had to defeat some kind of lock. Law 14,155/2021 removed that requirement. Today the crime no longer turns on breaking anything: it turns on acting without authorization. Authorization became the single decisive element.

That's why this page is the express authorization the law is talking about. And that's stronger than a promise not to sue you: with authorization, the crime never comes into existence in the first place — it isn't a defense you raise after the fact, it's the absence of an element of the offense itself. Authorized conduct never becomes a crime.

Ineligible findings

The cases we close without review, grouped by category.

It's not that we don't care — it's that without demonstrated impact, there's no way to tell it apart from noise.

Configuration and headers

Missing header, SPF/DKIM/DMARC, exposed version.
  • Missing security header (CSP, HSTS, X-Frame-Options) without a real exploitation PoC.
  • Missing or misconfigured SPF, DKIM, or DMARC record.
  • Software version disclosure in a banner, header, or public changelog, with no exploitation attached.
  • Missing certificate pinning in a mobile app.

Interface and interaction

Clickjacking with no sensitive action, self-XSS, open redirect.
  • Clickjacking on a page that performs no sensitive action.
  • Self-XSS — the victim has to paste their own payload.
  • Open redirect with no demonstrated additional impact.

Enumeration and rate limits

User enumeration, rate limiting with no brute-force proof.
  • User or email enumeration — finding out whether an account exists.
  • Missing rate limiting with no proof of a viable brute force.
  • CSV injection with no proven execution in the victim's environment.

Infrastructure and availability

Volumetric DoS and interception without a flaw of ours.
  • Volumetric denial of service, or anything that depends on generating massive traffic.
  • An attack that requires intercepting someone else's traffic, without a flaw of ours that makes it possible.

Third parties and dependencies

A flaw in a library or service we don't operate.
  • A flaw in a third-party library, without a PoC of specific exploitation in our environment.
  • A flaw in a service or infrastructure we don't operate.

Report quality

Scanner or AI output with no human verification.
  • Scanner output with no manual PoC and no confirmation of real exploitation.
  • AI-generated report with no human verification and no reproducible PoC.

Outside the threat model

Social engineering, physical attacks, outdated browsers.
  • Social engineering against our team, our partners, or our patients.
  • Physical attacks against an office, employee, or device.
  • A bug that only shows up in an outdated browser or OS no longer supported by its maker.
  • An attack that depends on the victim following the attacker's own instructions — pasting a command, installing an extension.

Think your case is the exception? Send it anyway and explain why. This list is a filter, not a wall.

Severity classification

Critical, high, medium and low, with concrete examples from the product.

Severity decides one thing: where you land in the fix queue. Nothing else.

Severity General criteria Examples at diagnos
Critical Compromises confidentiality for more than one patient, or breaks the core end-to-end guarantee — no click required from the victim.
  • Decrypting someone's report without holding the key — breaking the end-to-end model itself
  • An authentication bypass that crosses workspaces
  • Extracting key material from our backend
  • Remote code execution on the server that processes exams
  • An IDOR that reaches across different workspaces
High Compromises data within a single workspace, or grants admin privilege inside it — still no interaction from the victim.
  • An IDOR within the same workspace
  • Escalating to admin of one workspace
  • Stored XSS in an authenticated area
  • A one-off WebAuthn bypass
Medium Real but limited impact: needs a click from the victim, or exposes metadata instead of the report's content.
  • Reflected XSS that needs a victim click
  • CSRF on a non-clinical action
  • A metadata leak that confirms an exam exists without revealing its content
Low Minimal impact, needs unlikely conditions to line up, or is purely informational — no clear path to patient data.
  • A verbose error with no secret in it
  • A library version disclosed with no exploitable CVE

The final call is ours — but we explain the reasoning. Disagree, and we'll take another look.

Recognition and eligibility

There is no cash reward. What there is, and who can receive it.

Four things, in this order — the first one stings a little.

  1. No money. Zero.

    No "we're currently evaluating this." Zero, actually. We're a small company, and whatever money exists pays for servers and people. If that changes one day, this page changes with it — and you'll be among the first to know.

  2. Product credit, maybe.

    We may — at our own discretion and with zero obligation — offer credit toward the platform. No agreed value, no guarantee, not negotiable. It's not payment: it's gratitude with an invoice taped to it.

  3. A spot in the hall of fame.

    Every valid vulnerability gets listed — under whatever name you pick: your real name, a handle, a social @, or anonymous.

    See the hall of fame
  4. Gratitude from people you'll never meet.

    On the other end of this are doctors and patients who will never know your name. It's not much. We know it's not much. But it's true.

And we ask for one thing back: permission to cite your report — technical, no patient data — in our security changelog, once the fix ships.

Who can take part

Anyone can report a bug, and we fix it either way. What changes is credit — no public recognition or product credit for:

  • Anyone who works or worked at MedDeck in the last six months — contractors included.
  • A close relative of someone with privileged information about the system being tested.
  • A person or entity on a sanctions list (OFAC/SDN, EU) or in a country under comprehensive embargo.
  • Anyone who reached the flaw through a breach of contract or an internal leak, rather than independent research.

How to submit a report

The two channels, encryption, and what a report has to contain.

Most reports go through the standard box. The other one exists for when waiting would actually cost us.

The standard channel

[email protected]

Every report comes in here — it's the box we read first, always.

Send a report

Only when waiting is expensive

[email protected]

Active exploitation happening right now. Patient data already exposed publicly. A leaked credential. This box is for that — and only that.

Using this inbox to flag a missing security header is pulling the emergency brake to get off at your stop: it works, but everyone on the train is going to look at you.

This can't wait

What your report needs to include

  • Numbered reproduction steps, in the order you actually did them.
  • The real impact — what an attacker can DO, not just "I got X."
  • The environment, browser, and account you used.
  • A minimal, non-destructive proof of concept.

It doesn't need to be in English, and it doesn't need to look like a CVE. Clarity beats formality.

Keep everything on the private channel with us until we've agreed on disclosure together.

The file that does this for you. It's already live, and it's how an automated tool finds us before any human reads this: /.well-known/security.txt

Encrypted reports

We haven't published a PGP key yet. Want to encrypt anyway? Ask us by email and we'll send you a way to do it.

Frequently asked questions

Eight direct answers, from payment to the disclosure window.

Do you really pay nothing?

Nothing. Not cash, not crypto, not a t-shirt. We might offer product credit when it makes sense, with zero guarantee — that's gratitude with a limit, not a price in disguise.

Can I test with my real account?

You can, and it's the recommended way if you're part of the internal audience — see the "Scope" section. What you can never do, under any circumstance, is test with someone else's account.

I found patient data. Now what?

Stop. Don't open the exam, don't scroll, don't copy the ID "to confirm later." Report the finding without pasting the report's content — the "Patient data" section has the step by step, and it isn't red tape: it's the line between security research and a crime.

How long until someone replies?

We confirm receipt within 5 business days; after that, you get a position on severity and next steps. That's a service goal we hold ourselves to, not a contractual SLA clause.

Can I publish what I found?

Yes — 90 calendar days after the issue is confirmed, or sooner if we agree on a date together. Never with patient data inside, even after the window closes.

I used AI to find this. Is that a problem?

None, as long as you verified and reproduced the issue yourself, in a real environment. AI output without that check isn't a vulnerability, it's a hypothesis — the "Rules" section explains why.

Will you sue me?

No, if you followed this policy. The "Safe harbor" section is, in one sentence, the express authorization that Article 154-A of the Brazilian Penal Code requires for what you're about to do to not be a crime — that's not rhetoric, it's the text of the law.

I already reported and got no reply.

Resend it mentioning the date of your first email — email gets lost. If it's genuinely urgent, use the urgent inbox. It's not indifference: we're a small team, not a 24-hour security operations center.

That's it. Go ahead.

If you read this far, you already care about these patients' data more than plenty of people who handle it for a living. Thank you — and happy hunting.

Report a vulnerability