Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
kryphocron
Privacy-first ATProto substrate primitives. Type architecture, audit vocabulary, inter-service auth, and at-rest content encoding for substrates that need to commit threat-model invariants structurally rather than as policy.
What it is
kryphocron is the foundational primitives crate for the Kryphocron
substrate — an ATProto-compatible identity and publishing substrate built
around the discipline that the wrong path is harder to write than the right
path. Misuse of a primitive should be a compile error wherever possible, a
runtime error otherwise, and a silent success never.
The crate ships:
- Tier-aware envelope types (
Tier,Tiered<T>,HasNsid) that make Public / Private classification a structural property of the type system. A function emitting to a public surface cannot, by type, accept a private-tier value. - Capability proofs — sealed, unforgeable in safe Rust via
PhantomData<sealed::Token>markers. AuthContext— the in-process authentication context type with attribution-chain rehydration from upstream delegation.- Audit pipeline — composite-rollback machinery, a fallback-sink contract, and a 30+ variant audit-event vocabulary covering user, channel, substrate, and moderation classes.
- Inter-service auth — JWT verification, a capability-claim wire format with monotonicity invariants (replay / time-rewind guards), trust declarations, DID resolution, and the three-message sync handshake.
- At-rest content codec — private-tier record content is encoded at rest
by default via the
ContentCodecseam, with the friction-encodinglaqunacodec shipped as the built-inDefaultAtRestHooksbaseline. Operators may substitute a strengthening codec (authenticated encryption, HSM-backed). See "Privacy posture". - Audit-encryption hook surface — the operator-pluggable
AuditEncryptionResolvertrait shape; the crate ships the surface, operator plug-ins fill in algorithm variants.
What it is NOT
kryphocron deliberately does NOT ship:
- Confidentiality-grade encryption. The default at-rest codec (laquna) is
friction-encoding, explicitly not encryption (see Privacy posture); the
ContentCodecseam accepts an operator-supplied authenticated-encryption codec, but the crate ships no cipher of its own. - A PDS, AppView, or any other operator substrate component — those are downstream crates that build on kryphocron primitives.
- HTTP transport, TLS, or wire I/O. The crate produces and consumes byte arrays; operators wire up their own transports.
- Key management, KMS integrations, or multi-process rotation coordination.
The crate ships a single-process
DefaultRotationOracle; coordinated (DB/KMS-backed) rotation is operator-supplied, and theDidResolverand audit-encryption resolver receive key material from operator code. - ATProto record validation beyond what
kryphocron-lexiconscommits.
The discipline is door-open, not door-ajar: where an implementation choice is operator policy, the crate commits a trait shape and leaves the implementation to operators. The at-rest content codec is the one principled exception — encoding-at-default, not door-open (see Privacy posture below).
Privacy posture
kryphocron stores private-tier records encoded at rest by default, using a friction-encoding codec (laquna) shipped as part of the substrate. From laquna's README:
Laquna is not encryption. The decoder is in this repository, the slug travels inline in every artifact, and the seed is typically derived from public metadata, so any party with this code and the seed can recover the plaintext. Laquna provides no confidentiality, no authentication, and no resistance to a motivated adversary. Its only value is friction against opportunistic, at-scale content extraction.
Laquna provides friction against opportunistic and determined human adversaries. It does not defend against LLM-assisted adversaries that can call the public
decode()API directly; consumers requiring resistance to LLM-assisted bulk extraction must compose Laquna with additional access controls in the consuming system.
Operators requiring stronger at-rest guarantees install an alternative
ContentCodec via custom AtRestHooks. The substrate's encode/decode
seams accept any codec implementation that satisfies the ContentCodec
trait — including authenticated encryption codecs that deliver
confidentiality and integrity. Substitution is a strengthening path;
configurations that opt out of encoding-at-rest (identity codecs, no-op
encoders) are not supported. Kryphocron's identity is
encoding-at-default — deployments configured otherwise are not
kryphocron deployments.
Note on layered privacy: the substrate's audience-oracle wiring is the authorization layer — it gates who can read a record. Friction-encoding is the at-rest layer — it raises the cost of unauthorized observation of repository bytes. The two layers compose: audience-enforced read authorization plus friction-encoded at-rest storage. Neither alone is sufficient for strong privacy; together they form kryphocron's defense-in-depth posture.
Deployment shape and the default rotation oracle
The substrate ships DefaultRotationOracle as part of
DefaultAtRestHooks — a single-process rotation oracle. It uses a
file-backed generation state at <data_dir>/kryphocron/rotation.state
and is correct under deployments where exactly one kryphocron process
touches the data dir at a time.
Multi-process deployments — including any deployment behind a load
balancer with multiple workers, deployments with separate writer and
reader processes, deployments with maintenance workers (compactors,
re-encoding jobs, audit scrubbers) alongside the main service, and
deployments using process supervisors that may briefly run two copies
during restart — install a coordinated RotationOracle (DB-backed,
KMS-backed, or otherwise process-coordinated) from day one, not "as
they scale." This applies regardless of user count.
Records are encoded under both single- and multi-process configurations
— encoding-at-default holds either way. What DefaultRotationOracle
delivers under single-process deployment is the
rotation-cadence-correct-over-time property; multi-process deployments
running DefaultRotationOracle get encoded records but with
process-divergent rotation state, which silently breaks the
rewrite-on-rotate mechanism the substrate ships to refresh friction.
Substituting a coordinated oracle restores correctness.
Quick start
[]
= "0.3"
A minimal substrate-side integration sketch:
use ;
// At substrate startup, install audit sinks, oracles, the DID
// resolver, the deployment correlation key, and the inspection-
// notification queue.
let inspection_queue = NoInspectionNotifications; // no-op default.
// Private-tier record content is encoded at rest by default. Construct the
// baseline at-rest hooks once at startup; the
// `at_rest::{encode,decode}_record_content` seams drive the installed
// `ContentCodec` (laquna by default). See "Privacy posture" above.
let at_rest_hooks = for_data_dir?;
// Per-request: verify a JWT, build an AuthContext.
let trace_id = from_bytes;
let verified = verify_jwt.await?;
let ctx = from_xrpc_request;
// Issue a capability proof against the requester's context, then
// bind it against the target. `bind` and `reborrow` are async —
// the bind pipeline runs inside `composite_audit`, which is async,
// and timing-channel equalization sleeps via tokio.
let proof = ?;
let bound = proof.bind.await?;
// `bound` grants access to the subject; the audit pipeline has
// already fired the terminal CapabilityBound event structurally.
let subject = bound.subject;
The substrate's value isn't in the API ergonomics — it's in the threat-model invariants the type system enforces.
Bind API asymmetry
Three of the four *Proof::bind methods take (self, ctx, target).
ModerationProof::bind takes a fourth argument: rationale: ModeratorRationale (a length-bounded operator-declared string, mandatory for
every moderation action). Operators writing generic bind-dispatch code must
handle this asymmetry — the rationale is bind-time input, not issuance-time,
matching workflows where the moderator commits a rationale at the moment of
action.
Design discipline
Five organizing principles:
- The wrong path is harder to write than the right path. Misuse of a primitive is a compile error wherever possible, a runtime error otherwise, and a silent success never.
- Capabilities are unforgeable in safe code. Code outside the crate's
authoritymodule cannot construct authorization proofs — sealed traits and a private token carried inPhantomDataon every proof type enforce this. - Tier is not a label, it's a structural property. A function emitting to a public surface cannot accept a private-tier value, by type, not by runtime check.
- Audit reflects action, not intent. Audit events fire on the binding of a capability proof (success or failure), not on its issuance.
- Door-open, not door-ajar. Where an implementation choice is operator
policy, the crate ships the trait surface and leaves the implementation
pluggable. Defaults are explicit (e.g.
NoInspectionNotifications), not implicit. The at-rest content codec is the one principled exception — encoding-at-default, not door-open (see Privacy posture).
Status
0.3.0 ships the substrate's authority discipline end-to-end: tier-aware
envelopes, sealed capability proofs, the AuthContext derivation surface, the
audit pipeline with composite-rollback machinery, JWT-based inter-service auth
with the three-message sync handshake, and the at-rest content-codec seam with
the built-in laquna default. 390+ tests pin behavior across all four
capability classes plus the timing-channel equalization surface; the crate
forbids unsafe and denies todo!() / unimplemented!().
Audiences: ATProto-adjacent operators evaluating whether the substrate's design fits their threat model; downstream integrators (PDS, AppView, Graph substrates) consuming kryphocron as a dependency; and adversarial reviewers stress-testing the threat-model commitments. Wire-format and audit-event shapes are committed within 0.3.x; consumer-facing API ergonomics may still evolve.
Known limitations
None of these are security bugs — they're places where the current surface is narrower than the full design commitment, with the gap closed in future enrichment passes.
Coarse timing equalization. equalize_timing equalizes to a
deployment-configured target via a sleep primitive; it is not a
constant-time discipline. Granularity is bounded by the host scheduler
(ms-scale jitter on Windows/WSL, tighter on Linux), and a network adversary
with high-resolution latency measurement can see through it for short
operations. Treat it as a coarse first defense and layer constant-time crypto
primitives + a hardened timing primitive at the perimeter for strong
timing-channel threat models.
Placeholder audit-event payloads. A few audit-event fields ship with
placeholder data pending sealed per-class extraction traits: channel-class
peer / session_id (ChannelBound / ChannelReborrowFailed),
substrate-class scope_repr (ScopeBound), and moderation-class case id
(ModeratorInspected / ModeratorTookDown / ModeratorRestored). These
variants carry a payload_completeness field set to PartialV01; a future
release flips it to Full. Consumers branch on it to avoid rendering
placeholder data as real values.
Deferred enrichments. User-class bind consults the block-vs-resource-owner
query and (for audience-gated capabilities) the audience oracle; a generalized
per-capability multi-query oracle-results-builder is future work.
tier::visible_to is tier-only — an audience-aware overload is future work
(bind itself consults the audience oracle, so the gap is limited to the
standalone predicate). Moderation-class reborrow miss is silent at the audit
layer (no fitting variant yet); a future release adds it.
Wire-format-touching changes are reserved for a future minor cycle or the v1.0 cycle. v0.3.x patches are non-breaking only.
Closed-namespace lexicon registry
kryphocron's tier classification is closed-namespace: only NSIDs in the
tools.kryphocron.* namespace are known to the registry. Tier::from_nsid
consults KRYPHOCRON_LEXICON_REGISTRY and returns
Err(UnknownNsid::NotRegistered) for NSIDs outside it — including
ATProto-ecosystem NSIDs like app.bsky.feed.post. There is no
default-to-Tier::Public; unknown NSIDs are a hard error, not a permissive
fallback.
Operators running kryphocron alongside other ATProto lexicons (e.g. a PDS
handling both app.bsky.* and tools.kryphocron.* records) must handle
non-kryphocron records via their own classification or routing — kryphocron's
tier discipline applies only to its own namespace. Cross-namespace
classification is reserved for a future release if operators surface demand.
Feedback and contributing
Three channels, sorted by what you have:
- Integration pain or substrate-side bug → open a GitHub issue at https://github.com/skydeval/kryphocron/issues. Include a minimal reproducer (a failing test, a code excerpt).
- Security issue → see SECURITY.md. Please don't open a public issue for vulnerabilities; the policy describes the disclosure path.
- Design feedback or threat-model questions → GitHub Discussions on the
skydeval/kryphocronrepo.
The companion lexicon JSON lives in
kryphocron-lexicons;
lexicon-vocabulary suggestions belong there.
License
MPL-2.0 (Mozilla Public License 2.0). See LICENSE.
The MPL is file-level copyleft: modifications to MPL-2.0-licensed source files
must be released under MPL-2.0; downstream crates linking to kryphocron are
unconstrained and may ship under permissive licenses (MIT OR Apache-2.0
recommended for operator substrate components like PDS, AppView, etc.).
The companion kryphocron-lexicons crate ships its lexicon JSON under CC0-1.0
(public domain dedication); the wire vocabulary is universal and unencumbered.
Related
kryphocron-lexicons— companion crate shipping the lexicon JSON + Rust codegen wrappers. Required for consumers using lexicon-validated record types.