Hack Hydra 2026 · Track 01 · built on HydraDB

Derived knowledge inherits the access control of everything it was derived from.

A fact inferred from three documents is not a document. It has no ACL of its own — so document-level filtering, which is what every enterprise AI assistant does, has no answer for it. We measured the hole, and closed it.

Live · one derived fact

“What changes did the Engineering Lead suggest for the AnomalyForce product vision?”

Answering as David Miller · VP Engineering Bob Brown (Engineering Lead) is active across four product areas: AnomalyForce, CollaborateForce, InsightForce, VizForce.
Ungated graphdiscloses
Document-level ACLdiscloses
Cordondiscloses
Correct. David can read all four spaces this fact rests on, so every system discloses it. Now switch to Bob.

01 · the measurement

Document filtering is exactly correct — until it infers

1,514 questions from Salesforce’s HERB enterprise benchmark, 12 principals across the access spectrum, 18,168 trials per system. Identical retrieval, identical answer assembly. The only difference is what each is willing to disclose.

Measured over 18,168 trialsDocument-level ACLCordon
Leak rate17.4%0.0%
Answer quality (set-F1)0.0990.099
False denials00
Disclosure decided by ingest order1,008 of 7,280never
Verified againstits own model of accessGitHub’s own 404

Zero leaked at derivation depth 0. That is not a compliment we are paying the baseline — it is the finding. Document filtering does not have a bug. It has a ceiling, and the ceiling is the first inference.

Depth 0 — read from one artifact 0
Depth 1 — derived across spaces 3,626
Depth 2 — derived from derived 400
Depth 3 — derived from those 6,591

Facts leaked by document-level filtering, by derivation depth. Cordon: zero on every rung.

02 · worse than wrong

The baseline is not just wrong. It is arbitrary.

A derived fact carries one space, assigned by whichever source the writer reached first. That assignment is what a document gate reads. So we asked: would it have answered differently had the node been filed under a different one of its own sources?

Pairs to protect7,280HERB, all principals
Answer flips1,00813.8% — decided by ingest order
On real GitHub perms292 / 30994% arbitrary
Cordon0never reads the attribution
Same graph, same permissions, same person asking — opposite answer. A security decision settled by ingest order is not a security decision. docs/RESULTS.md · regenerate with npm run audit

03 · not our opinion

The ground truth is somebody else’s server

The strongest objection to any of this is that we invented the access control we then enforced. So the whole pipeline runs a second time against a system that already has permissions: 8 real GitHub repositories — 5 private, 3 public — inside a real organisation with 11 nested teams as principals. Only the loader differs. Extraction, resolution, derivation and the admissibility rule are the same code.

Principals1311 real nested teams
Doc-ACL leaks292of 1,118 pairs
Cordon leaks0same code, same rule
404 assertions26 / 26GitHub is the test oracle

For every source underneath a withheld fact, the audit issues an unauthenticated request and asserts the refusal:

# the public repository in the same organisation $ curl -so /dev/null -w '%{http_code}' https://github.com/cordon-demo/cordon-demo-handbook 200 # the private one, holding the evidence underneath the fact $ curl -so /dev/null -w '%{http_code}' https://github.com/cordon-demo/cordon-demo-fornax/issues/2 404 Same org, same request, no credentials either time. The 404 is about permission, not existence. GitHub refuses to show the document — a document-level gate hands over the fact derived from it.

The check deliberately uses github.com, not the API. The unauthenticated API allows 60 requests an hour and then answers 403 to everything — and counting a rate-limit 403 as a pass would fabricate the exact result this section exists to establish. Rate limiting is detected and reported separately from pass and fail.

04 · the attack nobody else can express

Two facts you may see, one you may not

Two facts a principal is entitled to can together determine a third they are not. Document-level filtering cannot defend against this even in principle, because the thing being aggregated is not a document. We formalised it, proved what is impossible, and mined the rest from the graph rather than inventing examples.

Theorem 1 · proved and checked

An attacker cannot climb the derivation edges. required(f) is the union of its supports’ requirements, so holding every part of a conclusion already entitles you to the conclusion.

0 counterexamples over every case in the graph.

Theorem 2 · the honest one

Cordon is closed under aggregation exactly when every claim about a space is only asserted by facts resting on that space — claim locality. So the exposure is a property of the corpus, not of the rule.

HERB: 0 of 38,600 artifacts name another space. Locality holds exactly.

What we found in our own system

So we seeded a fixture with the cross-repository references real issues are full of — and Cordon leaked 16 times out of 130 denied pairs. We are reporting that because it locates the mechanism exactly: a public issue naming a private repository.

“Ingrid Holm notes the SDK release depends on cordon-demo-borealis integration work.” public repository · asserting a claim about a private one

The fix follows from the diagnosis: widen the requirement to include the spaces a fact names, not only where its evidence sits. Leaks go to zero, and it costs 54 additional withholdings. Both rules ship with their numbers; the operator picks.

Compositional inference

Open, measured. A denied fact’s claims are partly reachable from permitted ones. Recovery falls as depth rises — Cordon degrades most gracefully where the explicit channel is most dangerous.

Refusal as an oracle

Our own defence opens it. Naming the space you lack is useful to a colleague and a perfect oracle to an attacker. Measured in bits, with an indistinguishable-abstention mode that closes it and the cost stated.

Disclosure-dependent truth

Whether you perceive a contradiction at all depends on your clearance. Two colleagues ask the same question and receive confidently opposed answers, and neither is told the other side exists.

05 · why this needs HydraDB

Admissibility is a walk, not a lookup

-- what this fact depends on: walk the support chain to evidence MATCH (f:Fact {id: $fact})-[:RESTS_ON*1..5]->(s:Source) RETURN s.space

The requirement is discovered by traversal, per asker, at query time — never read from a field. It cannot be materialised: with n principals there are 2n visibility subsets, and precomputing per principal is 30M rows that one membership change invalidates.

A level-3 fact sits four hops from any evidence and has no edge to a space at all. There is no field to read. The answer only exists as a walk.

And a vector index cannot express this even in principle. An embedding records what a fact resembles; it carries no record of what it was derived from, and a constraint that propagates along derivation has nothing to travel down. Similarity is not provenance.

Graph95,898nodes · 226,357 edges
Invariant0violations in 330,190 pairs
Entity resolution100%precision · 99% recall, no LLM
Filed upstream3issues, with reproductions

Building this, we probed what the OpenCypher subset actually supports and found queries silently truncating at 1,024 rows with no error — which for a security system means a quarter of an authorization table disappearing quietly. Our client fails closed on it. All three findings are filed upstream with reproductions: #115, #116, #117. We also publish what does not run in the engine: the transitive org closure is application-side, because variable-length traversal requires a fixed source id. A reviewer who finds that themselves reads it as a hole; measured and published, it is a capability map.

06 · how to disbelieve us

Every number opens onto the run that produced it

Cordon’s whole thesis is auditability, so nothing here is estimated, projected or illustrative. Raw artifacts carrying the git SHA and timestamp are committed alongside the code.

$ docker compose up # clean checkout to a working console $ npm run audit # the leak table + retriever sweep $ npm run audit:github # real permissions, no credentials needed $ npm run attack # the aggregation census $ npm test # incl. property-based soundness

The invariant compares requirements obtained by graph traversal against requirements recomputed independently from the corpus. That distinction is not pedantry: our first version compared a value against itself, could not have failed, and passed for hours. When it was fixed, 47 facts disagreed — and that is how a real access-control bug was found.

A security property only ever checked against the field that produced it is not being checked at all. the thing we would most want a reviewer to take away