Security model
Written for whoever has to approve this. If something below is not enough for your risk appetite, say so. We read issues.
Your credential is forwarded, never interpreted
The server is a relay. It does not hold the secret needed to validate a credential and does not attempt to. The Vulnetix API recomputes the digest and is the sole authority.
The only local parsing is splitting ApiKey <uuid>:<hex> to recover the
organisation id for cache keying. There is no session store, so the credential is
presented on every request and nothing about it is retained between them.
Everything returned lands in a transcript
This is the constraint that shapes the rest.
A tool result is fed to a model, which means it reaches your conversation history, your client’s logs, the model provider’s inference logs, and whatever you later share or export.
The device_code is encrypted, not just signed
The browser sign-in has a property worth spelling out. In the CLI, the secret
device_code never leaves your machine. An MCP server structurally breaks that,
because the code must cross the wire so your client can poll with it.
That matters because the redemption endpoint is unauthenticated: possession of
the device_code is the entire authentication. Anyone reading a leaked
transcript inside the five-minute window who polls faster than you wins the
single-use race and receives your organisation’s key.
So the code is wrapped in AES-256-GCM before it is ever handed out, under a key derived by HKDF-SHA256 and held only by the server. Your client holds ciphertext it cannot read.
Handles are also bound to a purpose, so one minted for the login flow cannot be opened by anything else, and they carry their expiry inside the sealed body where it cannot be tampered with.
The residual exposure
The final key returned by vulnetix_auth_poll must reach you through the
transcript; there is no other channel. Treat any credential obtained that way as
compromised if the session was shared, and rotate it.
Why the key is not a tool parameter
The protocol allows mirroring tool parameters into HTTP headers. We do not use it for credentials, and will not.
The mechanism requires the value to also appear in the tool’s arguments, which means it is in the advertised schema, which means the model generates it on every call. That maximises transcript and provider-log exposure, invites hallucination and cross-tenant contamination in a multi-account session, and defeats the point of a static header configured out of band.
There is no anonymous mode
Quota is metered per organisation. A shared server falling back to one built-in credential would collapse every anonymous caller into a single bucket, the one every Vulnetix CLI user already draws from, where one runaway agent loop denies service far beyond this deployment.
The server also never silently downgrades a working credential when a limit is hit: a downgrade would return different data with no signal, indistinguishable from an upstream bug.
Prompt injection
CVE descriptions, advisory prose and cached proof-of-concept source are attacker-influenced by construction. Anyone who can get text into an advisory can get text in front of the model, and the output becomes a result your agent may act on.
Four rules, enforced centrally instead of per tool:
- Upstream content is passed as data inside a delimited block, never concatenated into instructions.
- The system prompt is fixed and states the block is material to analyse, not instructions to follow.
- Output is schema-constrained JSON, so the model cannot emit free-form prose that a caller might mistake for an instruction.
- Output never influences control flow. It does not choose an endpoint, set a parameter, or select a tool.
AI-derived fields are labelled distinctly from database fact so a downstream agent can tell interpretation from data.
The escape hatch is allowlisted
vdb_request matches paths against read-only prefixes. A free-form path would
make it an SSRF primitive: the caller controls the path, the server attaches a
real credential, and the request originates inside Cloudflare’s network.
The CLI-only surface (several endpoints there mutate organisation state) and the
credential-minting surface are unreachable, as is any non-GET method.
Transport hardening
OriginandHostvalidated against an allowlist before anything else reads the request; a mismatch is403. This is DNS-rebinding protection the transport spec requires.- Non-
POSTis405; non-JSON content type is415. - Header/body mismatches are rejected
400, so an intermediary routing on headers and a server executing on the body can never disagree. - Upstream responses are size-guarded before buffering.
Rate limits are surfaced, never retried
A limit is returned as an in-band tool result. A transport-level 429 would make
clients retry the whole exchange. The text explicitly
tells the agent not to retry and carries the reset time, because an agent that
sees an error will otherwise call the same tool again immediately. The server
itself never retries a 429.
Reporting
Report security issues to the Vulnetix security contact instead of opening a
public issue.
SECURITY.md in the repository has the current details.