Skip to content

Security: DendraNetwork/dendra-network

Security

SECURITY.md

Security Policy

Status: public research testnet, dendra-testnet. Dendra is experimental software. Testnet DNDR is never sold, and the chain can be reset. Final Testnet Season rewards count as points that convert one for one: each testnet DNDR the season paid becomes one mainnet DNDR, credited in the mainnet genesis to the address that received it, at most 3 000 mainnet DNDR in total. Do not put real value at risk.

What the public endpoint runs. The chain is dendra-testnet, started from a fresh genesis. The previous chain, dendra, was abandoned without a snapshot: no balance, registration, job or history was carried over, and nothing on this chain is promised to survive a future reset. A reset erases balances; the published rankings of the Final Testnet Season are archived before any reset. The network's public endpoints are testnet-api.dendranetwork.com and testnet-proof.dendranetwork.com (the site, dendranetwork.com, publishes the season's rules and rankings); no other host serves the chain.

dendra-testnet launched from a genesis with a single validator, run by the project on one host, which at launch alone orders and finalises every block. A validator set in which one validator holds a third or more of the voting power cannot survive losing that validator: if that validator stops, the chain stops; at launch the network is neither decentralized nor fault-tolerant (ADR-046). Read the current set with curl -s http://testnet-api.dendranetwork.com:26657/validators rather than trusting this page. The genesis sets committee_min_vrf_contributors to 1, and while it is 1, one validator's VRF output carrying at least two thirds of the committing power is enough to seed a draw, so at launch the project's validator alone produces the randomness of every work assignment and audit draw: draws are as fair as that operator is honest, until independent validators join and a governance vote raises the floor. A work assignment or a sampled audit draw runs only on a block whose VRF seed clears both bars: latest_contributors at or above committee_min_vrf_contributors, and contributor_power_bps at or above two thirds of the commit (decentralized_seed.go::MinVrfContributorPowerBps). dendrad query jobs committee-seed-health -o json prints both side by side (a field missing from that JSON is zero: the chain omits zero values). On a block without such a seed those draws are deferred, never taken on a seed the proposer could choose. The re-adjudication jury of a dispute is the exception: without such a seed it is drawn from the hash of the block that opens the dispute (redo_committee.go::anchorRedoCommittee), which that block's proposer composes.

The rule that gates a verdict. An audit's jury is drawn from the miners of the job's frozen pool that are eligible and present, excluding the miner under audit, and a verdict needs votes the same way at the jury bar, the larger of audit_min_quorum and two thirds of the anchored seats, rounded up (antievasion.go::auditRelativeBar). A miner is present when it proved its availability in the current or previous window, or registered in one of them (presence.go::minerPresentAt). audit_sample_bps sets the base share of settled jobs drawn for re-audit (audit_sampling.go::effectiveAuditBps adds the probation and fee-adaptive terms). Read both with dendrad query jobs params -o json or /dendra/jobs/v1/params rather than trusting a figure on this page; the other queries that decide are in the README.

What happens to a fee. Under a full hold (hold_bps = 10000, the genesis setting) the miner's share and the protocol's cut are retained at settlement. A job the draw does not select is released at finality. A selected job is paid on an upheld verdict; on a conviction the miner is slashed and the client refunded; and if no jury concludes within audit_unwind_blocks, the fee and the cut return to the client. An audit deferred for want of a seed unwinds the same way, and a job that is never served returns its escrow to the client after job_expiry.go::jobExpiryBlocks (a job whose miner cannot be drawn yet, for want of a seed that clears both bars, waits for one first). Because a stranded audit unwinds to the client, a job may open with fewer than audit_min_quorum + 1 present eligible miners when the chain runs in optimistic mode with audit_unwind_blocks above zero and a full hold (pool_freeze.go::enoughEligibleMinersToVerify); an audited job whose drawn jury does not reach the jury bar within audit_unwind_blocks ends in that refund.

A report about the verification path is therefore a report about the code: which side of a floor the network stands on is a reading, shown by the queries above, and does not need reporting. A refund to the client after audit_unwind_blocks is the designed outcome, as is the release at finality of a job the draw did not select. A way to make an audit conclude without its drawn jury, to draw a work committee or a sampled audit jury on a seed below either bar, to pay a miner for a selected job without an upheld verdict, or to keep a retention held for ever (neither paid, refunded nor slashed) is very much in scope.

Reporting a vulnerability

Please report security issues privately. Do not open a public GitHub issue for a vulnerability.

  • Email: security@dendranetwork.com
  • Include: a description, reproduction steps, affected component (chain module, gateway, miner, relay, judge, Final Testnet Season service), and impact.
  • We aim to acknowledge within a few days. Coordinated disclosure is appreciated; we will credit reporters who wish to be credited.

Scope

In scope: the chain (chain/), the off-chain reference stack (services/) including the Final Testnet Season programme services (services/final_season_*.py), the deployment kits, desktop application and installer (deploy/, docker/).

Particularly valuable reports:

  • Ways to mint supply, or to destroy it outside the protocol's own burns (the invariant is zero mint: the supply starts at 10,000,000 DNDR and never rises).
  • Double-settlement, escrow imbalance, or paying a job twice.
  • Slashing an honest miner (false positive), or letting a prover escape a deserved slash.
  • Leaking plaintext on-chain, at the relay, or in logs: the chain should hold only a salted hash of the prompt, an embedding of the answer, verdicts and counters, and the relay only ciphertext. That the Dendra gateway, the serving miner and, for a job drawn for audit, its jurors read the prompt and the answer is documented in the README, not a finding. From kit v0.2.1, the miner seals the reveal of an audited job for the jurors the chain anchored for that audit and for no other miner; a miner running a kit of an earlier release seals it for every other registered miner with an encryption key (ADR-057, decision 4). Each copy is sealed to the encryption key the juror registered on chain; a juror that registered none gets a copy sealed to the key the relay serves for it, which a relay that replaces that key can open (services/reveal_helpers.py::committee_pubs). A reveal from a v0.2.1 miner that anyone outside its anchored jury can open by another path is in scope.
  • Bypassing the regex floor by defeating the mechanism itself: making a request skip the filter stage, or disabling it remotely. Content the filter misses is not a finding: it misses a great deal by construction, and this project makes no claim that illegal content is filtered out.
  • Faucet drain / Sybil beyond the documented rate limits.
  • Final Testnet Season (rules, ADR-047): making the programme pay an identity its published rules would not pay: past the programme's caps, per day or per season, for a request refunded or not past its audit (a programme request whose audit unwound is paid only as decision 18 of ADR-047 states, from the season day the season service names in unwound_audit_work_from_day), for a verdict from a juror the chain did not draw or on a job the programme did not send, for an availability window the chain did not accept, or for presence on a day without a verified request; paying one reward twice; forging or replaying another identity's payout declaration; making a work answer enter the evidence, or a grade count, without the service's internal or grader token; or a published day that final_season_rank.py does not recompute from its evidence.

Out of scope

  • Testnet DNDR being never sold, and the chain being resettable: a reset erases balances by design; the season's published rankings are archived before any reset.
  • The project's validator, the launch genesis's only one, ordering every block and alone producing the randomness of every draw at launch (documented; lifted only by independent validators and a governance vote). A seed accepted below either bar (committee_min_vrf_contributors, or two thirds of the committing power) stays in scope.
  • A client refund after audit_unwind_blocks, and the release at finality of a job the draw did not select (both designed outcomes).
  • Limits the Final Testnet Season rules publish themselves (ADR-047, Consequences): for instance, that a presence proof proves an online operator key, not a model; that one card can serve the programme work of many identities; or that a stake split into many identities collects more presence rewards (one per identity with a verified request that day) and can hold more seats on a programme audit's jury (one per identity).
  • A cheat whose answer stays coherent not being convicted by the judges of kit v0.2.1 or later while their divergence guard holds (DENDRA_JUDGE_DIVERGENCE_SLASH unset or 0; ADR-057, decision 3): its retained fee unwinds to the client, unless the jury upholds the answer. A kit judge votes valid when its reference answers agree with the answer, or when it proves the request ambiguous (services/judge_worker.py::abstain_to_vote), and an upheld answer is paid. A kit judge that votes invalid on a divergence alone while that guard holds stays in scope.
  • A reveal that differs from the delivered answer in a detail its embedding does not see (a figure, a name) passing the judge's anchor check: a cosine does not prove that two texts are the same, and binding a reveal to the delivered answer needs a field the chain does not have (ADR-057, What the anchor check does not prove).
  • The consumer-GPU tier providing hardened deterrence, not a cryptographic guarantee (this is by design and documented).
  • Denial of service against a single self-hosted node.

What we do not claim

We do not claim a cryptographic confidentiality guarantee on consumer GPUs, nor that a prompt is hidden from the gateway, the serving miner or a drawn jury (nor, for a miner on a kit before v0.2.1, from the other registered miners its reveal is sealed for; nor, for a juror that registered no encryption key, from a relay that replaces the key it serves for that juror), nor that Dendra outperforms frontier models, nor any fault tolerance on dendra-testnet (its launch configuration is stated at the top of this page). Reports premised on a guarantee we never made are not vulnerabilities; reports showing we fail a guarantee we did make are very welcome.

There aren't any published security advisories