Documentation

How Myelin works

Five cores over one encrypted SQLite file, an MCP surface with eighteen tools, and a CLI for everything a model is not allowed to do. This page explains the reasoning, not only the behaviour — a design you cannot argue with is one you cannot trust.

On this page
  1. How Myelin fits
  2. The memory hub
  3. The process registry
  4. The quality engine
  5. The adaptation loop
  6. Preferences
  7. The MCP contract
  8. The CLI
  9. The security model

How Myelin fits

Myelin is one binary and one SQLite file per brain. It speaks the Model Context Protocol, so an MCP-capable host — Claude Code, Claude Desktop, or anything else that implements the protocol — connects to it the same way it connects to any other server.

What it exposes is not a chat interface and not an agent. It is a set of tools, resources, and prompts that the host can reach during a session it was already having. A task begins; the host recalls what this brain knows about that kind of task; it works; it validates the result; it logs the outcome. Next time, the recall is better because the outcome was recorded.

     your editor / app                Myelin                 your disk
┌─────────────────────────┐   ┌───────────────────┐   ┌─────────────────────┐
│   MCP host + model      │──>│  18 tools         │──>│  brain.db           │
│                         │<──│  4 resources      │<──│  (AES-256-GCM)      │
│                         │   │  MCP prompts      │   │                     │
└─────────────────────────┘   └───────────────────┘   └─────────────────────┘
                                 no model calls

Five cooperating cores sit over that one file: the memory hub, the process registry, the quality engine, the adaptation loop, and preferences. Each is independently testable. None of them calls a model.

Two transports serve the identical surface:

myelin serve            # stdio, local — your key never leaves the machine
myelin serve --http     # stateless multi-tenant, behind TLS, for a server

The same tool handlers back both, so the two surfaces are identical by construction rather than by discipline. The differences that do exist are security boundaries, not feature gaps: an HTTP session cannot read or write a local_only memory through any path, and that is enforced in the engine rather than in the transport, so there is no route around it.

What “brain” means

A brain is one project’s accumulated context: its memories, its processes, its standards, its preferences, and its outcome history. Brains are separate on purpose. The conventions of one codebase are noise in another, and a single shared store degrades into exactly the vague, half-relevant recall that makes people stop trusting memory features.

You will typically have one brain per project, plus a tenant-level meta-brain for the things that are true of you rather than of any one repository.

The memory hub

Why append-only, rather than an updatable row

The obvious design for a memory store is a row per fact with a confidence column you update as you learn more. It is obvious, and it is wrong, for a reason that only shows up later.

When a fact changes, the update destroys the previous state. Six weeks on, the question you actually have is never “what do we believe now” — you can read that off the row. It is “when did this become true, what did we believe before, and was the model working from the old value when it made that decision?” An updatable column cannot answer any of those, because the evidence was overwritten by the thing you wanted to know about.

So Myelin appends. Correcting a memory writes a new one and marks the old one superseded, pointing at its replacement. The chain stays readable. Recall returns the current fact; the history is there when you need to explain something.

There is exactly one exception, purge, which hard-deletes a single memory — the row, its full-text index entry, and its edges — and leaves a tombstone recording that an id once existed, so doctor can verify the deletion was complete. It exists because “append-only” is not an acceptable answer to someone who needs a thing gone. It is CLI-and-admin only, and deliberately not an MCP tool, so a model cannot reach it.

Bi-temporal, and why that matters

Two clocks, not one. observed_at is when the fact was true in the world; the record’s own timestamp is when Myelin learned it. Decay measures from observed_at when it is known, so importing a two-year-old design note does not make it look like this morning’s decision.

Trust is computed at read time

Trust is never stored. It is derived when you read:

trust = (½ ^ (age / halfLife)) ^ (1 / reinforce)

Half-lives are per class, because different kinds of knowledge go stale at very different rates:

classhalf-lifeexample
identity3650 days“Gus works in Singapore”
convention180 days“we squash before merging”
project_state14 days“the API listens on 9090”
episode7 days“the deploy failed on Tuesday”

Tier multiplies the half-life: core ×4 with a 0.5 floor, so a promoted memory never decays out of reach; normal ×1; archive ×0.25.

Note that reinforcement is an exponent, not a bonus added to a score. That choice does real work. It keeps trust inside (0, 1] with no clamping anywhere, and it makes the function provably decreasing in age and increasing in use — properties the test suite asserts rather than assumes. A bonus term would need a cap, and a cap is a place where two very different memories start returning the same number.

Because trust is computed rather than stored, nothing in the database can be quietly stale. There is no background job that has to run for the numbers to mean anything, and no window during which the store disagrees with itself.

The recall pipeline

In order, every time:

  1. FTS5 full-text search, top 40 candidates.
  2. Score by bm25 × trust — relevance times how much this brain should currently believe it.
  3. Fit a byte budget. Context is finite; recall returns what fits rather than everything that matched.
  4. Expand one hop along edges, damped ×0.5. One hop, because two hops reliably drags in something loosely associated and dilutes the result.
  5. Flag contradictions.
  6. Touch what was returned, which is what feeds reinforcement.
  7. Prepend the core tier — up to 2KB of standing context, and not touched: core memories are offered rather than asked for, so reading them should not count as use.

Retrieval is keyword search plus graph-lite expansion. There is no embedding model, which is the point: recall is free, explainable, and instant. Vector search is a v2 question, held back until semantic retrieval earns the complexity it costs.

Contradictions are surfaced, not hunted

Two ways a contradiction is flagged: an explicit contradicts edge, or two live memories asserting the same subject differently. The second is what catches a supersede race — two people correcting the same memory at once. Both corrections land, and recall flags them as contradicting. Losing a write to avoid an awkward result would be the worse failure.

Myelin does not run a background job hunting for inconsistency across the whole brain. Contradictions surface at the moment they are relevant, which is when something recalls both sides of one. Myelin flags; you decide.

The process registry

Memory answers what is true here. The process registry answers how work of this kind gets done here — which is the part that usually lives in someone’s head, or in a CONTRIBUTING file nobody opens.

Three kinds, deliberately distinct:

Workflows — how a type of task gets done. Ordered steps, with checks and owner gates between them.

Skills — how a specific artifact gets produced. Guidance rather than sequence.

Loops — how an autonomous cycle knows when to stop. Bounded phases with an exit condition.

All three are YAML, and all three are validated when saved rather than when read. A broken process can never be retrieved, because it was never stored.

The validation rules, and why they are strict

Validation is collect-all: you get every problem at once, not the first one. Fixing a process one error per attempt is a bad experience and it hides whether the errors are related.

It rejects cross-kind fields. A skill carrying steps is refused rather than merged, because that document is a mistake — someone meant to write a workflow — and silently accepting it produces a skill that behaves in a way its author will not predict.

Loops must declare both exit_when and a bounded max_iterations. An unbounded loop is not a process, it is a bug, and the registry will not hold one. The bound is required even when exit_when looks obviously reachable, because “obviously reachable” is exactly the assumption that produces a cycle running until someone notices the bill.

Matching is deterministic

Processes are matched against a task by keyword containment on word boundaries, so a trigger of test does not fire on latest. Multi-word triggers must match as phrases, and they count double, because a phrase trigger misfires far less often than a single word.

Nothing about matching involves a model. The same task text selects the same process every time, which means when the wrong process fires you can look at the trigger list and see precisely why.

Served as MCP prompts

This is the part that changes how it feels in practice. Stored workflows are registered with the host as native MCP prompts — one prompt per workflow, plus the myelin-loop prompt — built from what the brain actually holds.

The difference is not cosmetic. A process in a file is something the model may read, may summarise, or may skip when context gets tight. A process exposed as a prompt is something the host offers deliberately and the model receives whole. It travels with you to a different model, or a different host, without being rewritten.

Versioning

Saving a new version supersedes the old one, in the same way memory does. Old versions stay retrievable. A process that changed last Tuesday and broke something on Wednesday should be diffable on Thursday.

The quality engine

A standard is one YAML document with two tables: rules, which drive validate, and risks, which drive precheck. Standards are versioned the same way processes are.

validate

validate grades an artifact against a standard deterministically. Measured on the developer machine and asserted as a test so a regression fails the build: 6.2ms per 100KB, against a budget of 10ms. There is no model in the loop, so that number is a property of the code rather than of the weather.

It composes thirteen checker primitives, and that set is closed:

require_pattern · forbid_pattern · require_all_of · require_one_of · max_line_length · max_lines · max_bytes · forbid_trailing_whitespace · max_consecutive_blank_lines · balanced_delimiters · require_prefix · require_suffix · require_unique_lines

A standard composes these; it cannot introduce new logic. That is the constraint that keeps validation fast and deterministic. The moment a standard can supply its own predicate, validation becomes arbitrary code with arbitrary runtime, and the guarantee that it always terminates in milliseconds is gone.

Every violation names the rule, the standard, the line, the column, and what to do about it. That last field is the one that matters for a host: given a location and an expectation, a model repairs the specific thing. Given “this does not meet the standard”, it rewrites the whole artifact and introduces two new problems.

precheck

precheck grades an action before anything expensive runs, and returns one of LOW, HIGH, or BLOCK. The highest matching risk wins.

One thing about it is worth stating precisely, because the obvious reading is wrong: LOW means nothing in the risk table matched. It is not a claim that the action is safe. Myelin reports what this brain knows, and knowing nothing about an action is not the same as knowing it is fine. A tool that returned “safe” for anything it had no rule about would be lying at exactly the moment the answer mattered.

Secret refusal

Content that looks like a credential is refused before it reaches the database: seventeen structured rules plus entropy checks. A refusal names the rule and the byte offset, and never the matched text — so refusals are safe to write to a log, which is the only way they are useful.

The honest limit: this is a guard against accident, not a guarantee against determination. Someone who wants to store a secret in Myelin can obfuscate it past the scanner. What it reliably stops is the ordinary case — pasting a block of config that happens to contain a key.

The adaptation loop

log_outcome records what happened and writes a matching episode, so the same event is both queryable as structured data and recallable as text. One write, two shapes, because the two are useful at different moments and keeping them in sync manually is the kind of thing that stops happening after a fortnight.

The reflector notices repetition

At fixed thresholds:

findingthreshold
a recipe worth capturing3 successes in one task family / 30 days
a preference worth storingthe same correction twice
drift worth surfacing5 failures or partials in one family / 14 days

These are constants in code, not settings. They live under the protected reflect. namespace specifically so that Myelin cannot lower the bar at which it starts proposing changes to itself. A system that can tune its own threshold for proposing changes has a feedback loop with nothing on the other end of it.

It proposes; it never applies

The reflector raises a proposal. Proposals wait for you. Nothing is rewritten unasked, and nothing is enabled quietly.

The asymmetry is deliberate and slightly awkward on purpose: the reflector can notice that a workflow should exist without being able to write one. So Myelin tells you what it saw, and you supply the body. That is a worse experience than autogeneration and a much better outcome, because a process you did not write is a process you will not trust, and a process you do not trust is one you will route around.

Accepting a proposal takes a snapshot first, and a failed snapshot aborts the accept. Regret should be recoverable, and it is only recoverable if the snapshot is a precondition rather than a courtesy.

Reviewing them is a CLI action, not a tool:

myelin proposals list
myelin proposals accept prp_7f2a
myelin proposals reject prp_7f2a

Accepting a proposal is deliberately not reachable over MCP. A model can see that proposals exist. It cannot act on one.

Preferences

Preferences are the fifth core. They are the standing settings that shape how everything else behaves, which is exactly why they are the part with the strictest rules about who may write them.

Protected namespaces

Six namespaces are compiled in rather than stored: gates. risk. reflect. trust. core. secrets.

Because they are compiled in, no brain and no import can widen them. A malicious or merely careless export cannot arrive carrying a new “protected” list that happens to be shorter.

The gate keys on origin. An automatic write to a protected key is refused outright. An owner write, made through the CLI, is allowed. So the settings that govern risk classification, reflection thresholds, and secret handling can never be changed by the loop that they constrain.

The explicit-durable test

Not everything you say is a preference.

“Use tabs from now on” — a preference. “Use tabs here” — not a preference.

A statement has to carry an explicit durability marker to become a stored preference. Any one-off qualifier (“just this once”) or hedge (“maybe”, a trailing question mark) vetoes it outright.

set_preference requires the owner’s words verbatim in its statement field, and applies the test to those words. The host cannot paraphrase an instruction into permanence.

This is a small rule that prevents a specific, corrosive failure: a passing remark in a frustrated moment becoming a permanent rule that quietly shapes every session afterwards, long after anyone remembers saying it. When the test refuses, the refusal says what to write instead.

Resolution order

session (host-held, never stored) → project braintenant meta-brain.

Nearest wins. A session-scoped instruction overrides the project’s stored preference for the length of that session and leaves nothing behind.

The MCP contract

Myelin speaks MCP over two transports with an identical surface: myelin serve (stdio) and myelin serve --http (streamable HTTP, stateless).

Tools

Eighteen, in four groups.

Memory

toolwhat it does
recallwhat this brain knows about a task, ranked by relevance × trust
rememberappend a memory (credentials refused)
supersedereplace one that is no longer true
linkrelate two memories; contradicts is meaningful
forgetarchive a memory so it decays out of recall
promote_to_corepropose standing context — raises a proposal

Note that promote_to_core proposes rather than promotes. Standing context is prepended to every recall, so a model that could write to it directly would be able to install instructions for its own future sessions.

Processmatch_process · get_workflow · get_skill · save_process · list_processes

Qualityvalidate · precheck · save_standard

Adaptation and preferenceslog_outcome · list_proposals · get_preferences · set_preference

Not tools, by design

purge · push · pull · restore · shred · key · accepting a proposal.

A model cannot reach any of them. Each is either destructive, or moves key material, or changes what the system will do in future sessions. They live in the CLI, where a person runs them.

Resources

myelin://core · myelin://preferences · myelin://processes · myelin://standards

Reading the core resource does not touch anything. Reading standing instructions is not the same as recalling something, and counting it as use would inflate the reinforcement of exactly the memories that are already privileged.

Prompts

myelin-loop, plus one prompt per stored workflow, registered from what the brain actually holds.

HTTP

POST /mcp
Authorization: Bearer myl_pat_...
X-Myelin-Brain: project-a        # or omit, with a single-brain token
X-Myelin-Key: <base64 32 bytes>  # TLS only

The pipeline, in order: auth → brain selection → key intake → quota → dispatch → audit. Every request re-authenticates and re-resolves. Nothing persists between calls except a 10-minute idle key cache.

Errors: 401 unauthenticated or wrong key · 403 scope · 404 unknown brain · 413 body too large · 426 a key sent over plaintext · 429 rate limit.

The 426 Upgrade Required is not a formality. Myelin refuses to accept a key header over a plaintext connection rather than accepting it and warning, because a key that has crossed a plaintext network is already compromised and a warning arrives too late to help.

local_only over HTTP

A memory marked sensitivity='local_only':

  • is invisible to every HTTP read path — recall, get, list, edge expansion, and the core-tier prepend;
  • cannot be written over HTTP at all;
  • has its edges dropped when it is withheld, because an edge pointing at a memory that is not there would itself reveal that something was withheld.

This is enforced in the engine rather than in the transport, so there is no layer where a future change could route around it.

Admin REST

/v1/health · /v1/brains · /v1/brains/{id}/pull|push|snapshots|shred · /v1/tokens · /v1/restore

/v1/restore only lists snapshots. The server has no key, so restoring always happens on your device.

The CLI

Global flags: --data <dir> (default .myelin, or MYELIN_DATA) and --name <brain> (default project) apply to the local commands. Flags may appear before or after positional arguments.

Local

myelin init creates a brain seeded with the starter pack, then prints both the stdio and remote-connector setup snippets.

myelin init --name project --default-sensitivity normal

--default-sensitivity local_only makes a brain whose memories never leave the machine unless you say otherwise.

myelin serve

myelin serve                                    # stdio
myelin serve --http --listen 127.0.0.1:8787     # multi-tenant

--http refuses a non-loopback address unless MYELIN_TRUST_PROXY=1 confirms TLS terminates in front.

myelin doctor [--repair] checks database integrity, FTS synchronisation, dangling supersedes and edges, tombstone consistency, required meta keys, and absolute paths. --repair fixes the fixable classes.

myelin stats — counts, revision, and default sensitivity.

myelin export [--format md|jsonl] — Markdown is meant to survive Myelin. It should be legible with no tooling at all.

myelin core add|rm <id> — promote a memory to standing context, or return it to normal.

myelin proposals list|accept|reject <id> — accepting takes a snapshot first.

myelin purge <brain> <id> --reason "..." — permanently deletes one memory. Requires a reason and a typed confirmation. This is the one thing Myelin cannot undo.

Server

myelin admin bootstrap --email you@example.com — run on the server. Creates the first tenant and prints an admin token once. It does not create a brain, because a brain’s key must be generated on your device.

myelin login <server-url> — saves the server and token to ~/.myelin/config.json (mode 0600) and verifies them immediately. Refuses plaintext URLs.

myelin brains create|list

myelin brains create meta          # the tenant meta-brain
myelin brains create project-a

The key is generated here, on your machine, stored in your keychain, and the 24-word recovery phrase is printed once. Write it down before running anything else.

myelin pull <brain> / myelin push <brain> — pull downloads ciphertext plus snapshots and decrypts locally. Push refuses if the server moved on since your pull; there is no merge engine, and it will tell you to pull first. A brain holding local_only memories is pushed as a filtered export, never a file copy.

myelin restore <brain> --at <snapshot|timestamp> [--to <path>] — nearest snapshot plus the archived changes after it. It always writes to a fresh path: a restore that destroys what you were recovering from is not a restore.

myelin token create|list|revoke

myelin token create --name claude-web --scopes write:brn_abc123

Prefer brain-scoped tokens for connectors. Revocation takes effect on the next request and evicts any key cached under that token.

myelin key export|import <brain>export prints the recovery phrase; --base64 prints the raw key for a connector header. import restores a key from a phrase.

myelin shred <brain> — destroys the server copy, offers a final pull first, and asks separately about destroying your local key. That second question is the irreversible half.

Environment

variableeffect
MYELIN_DATAdefault data directory
MYELIN_CONFIGconfig file location
MYELIN_KEYRING=fileuse the 0600 file key store instead of the OS keychain
MYELIN_TRUST_PROXY=1confirm TLS terminates in front, permitting a public bind

Measured performance

Asserted as tests, so a regression fails the build rather than being noticed later:

  • recall p95 8ms over 5,000 memories, against a 30ms budget
  • validate 6.2ms per 100KB, against a 10ms budget

Both measured on the developer machine. They are a floor for what the design allows, not a promise about your hardware.

The security model

What Myelin protects, what it does not, and where the edges are. Where there is a residual risk it is named plainly rather than described in a way that sounds better than it is.

The core claim

No Myelin server holds any key material at rest — including the hosted one.

Brains, snapshots, and archived changes are all AES-256-GCM ciphertext under a key that is generated on your device, kept in your OS keychain, and never stored on the server in any form.

The server holds a 16-byte verifier — HKDF(DEK, "key-check") — so it can reject a wrong key quickly instead of failing halfway through a decrypt. That verifier is one-way. It tells you nothing about the key and nothing about the brain.

A key reaches the server only as a request header, only over TLS, and lives only in process memory for a 10-minute idle window before being zeroed. This is why creating a brain requires the CLI on your own machine even when you are using the hosted instance: there is nothing on the server that could generate a key for you, by construction.

What that seals. Every at-rest window: disk theft, a stolen backup, a snapshot of the volume, an idle server, a decommissioned disk, a hosting provider with block-storage access. In all of those, an attacker gets ciphertext and nothing else.

And pull is always available. myelin pull brings the entire brain back to a local machine — ciphertext plus snapshots, decrypted on your device. The cloud is a location, never a cage. Nothing about a server copy is load-bearing for your ability to keep using what you have.

The residual risk, stated plainly

A root attacker on the server, during an active session, can read that session’s brain out of RAM.

While a brain is open it is decrypted, and its key is in the server process’s memory. Anyone who is root on that machine at that moment can read both. This is the honest consequence of not using a confidential VM, and it is accepted rather than argued away.

What narrows it:

  • Brains close after 10 minutes idle. Plaintext and key are wiped on close.
  • Plaintext lives in /dev/shm — RAM, never disk. PrivateTmp=yes gives the service its own, invisible to other processes and gone on stop.
  • LimitCORE=0, because a core dump would contain decrypted brains and live keys.
  • The systemd unit drops all capabilities, forbids new privileges, blocks write-and-execute memory, and permits only loopback networking.

What does not narrow it: nothing here defends against an attacker who is already root while you are actively using the service. If that is unacceptable for your data, the answer is not a configuration change — it is not putting that data on a shared machine.

Zeroing, and Go’s garbage collector

Myelin overwrites key bytes when it is done with them. Go’s garbage collector may have copied those bytes elsewhere during their lifetime, and Go gives no way to find or wipe the copies.

Zeroing narrows the window. It does not close it. Anyone claiming otherwise about a garbage-collected language is overselling.

Key loss is final

Your brain’s key exists in exactly two places: your OS keychain, and the 24-word recovery phrase printed when the brain was created.

Lose both and the brain is gone. Permanently. By anyone.

There is no reset link, no support path, and no vendor override. The server has never held the key and cannot recover what it cannot read. This is not a policy that could be relaxed for a sympathetic case; it is a property of the construction, and it is the same property that makes the paragraph above true.

Write the recovery phrase on paper. Not in the brain. Not in a note synced to the same laptop. Not in the password manager whose master key lives on the same device.

The connector trade-off

claude mcp add --transport http with an X-Myelin-Key header lets you reach a brain from a browser or a phone. It also means that platform now holds your key, in whatever way it stores connector headers.

This is a real reduction in the guarantee above, and it is offered anyway because browser access is genuinely useful and the alternative is not having it. Decide deliberately:

  • On a laptop, prefer stdio (myelin serve). The key never leaves the machine and there is no reason to accept the trade.
  • For remote access, consider a brain scoped to what you would not mind a platform holding, and keep genuinely sensitive material in a local_only brain that never leaves your machine at all.

local_only

A memory with sensitivity='local_only':

  • is excluded from every HTTP read path — recall, get, list, edge expansion, and the core-tier prepend;
  • cannot be written over HTTP at all;
  • is never pushed to the server. A brain that mixes sensitivities is uploaded as a filtered export-import rather than a file copy, because deleting rows after copying would leave them recoverable in SQLite’s free list;
  • has its edges dropped when it is withheld. An edge pointing at a memory that is not there would itself reveal that something was withheld.

Secret refusal

Content that looks like a credential is refused before it reaches the database: seventeen structured rules plus entropy checks. Refusals name the rule and the byte offset, never the matched text, so a refusal is safe to log.

This is a guard against accident, not a guarantee against determination. Someone who wants to store a secret in Myelin can obfuscate it past the scanner. What it stops is the ordinary case: pasting a block of config that happens to contain a key.

Audit

Audit rows carry a timestamp, tenant, token, brain, endpoint, outcome class, and latency. They carry no memory content, no query text, no payloads, and no key material — the table has nowhere to put them, and a test asserts the live schema against the permitted field set, so a future column fails the build rather than leaking quietly.

purge and shred

purge hard-deletes one memory: the row, its full-text index entry, and its edges. It leaves a tombstone recording that an id existed, so doctor can verify the deletion was complete. It is the only exception to append-only memory, it is CLI-and-admin only, and it is deliberately not an MCP tool.

shred destroys a brain on the server: ciphertext, every snapshot, and the archived changes. Combined with destroying your key, nothing is recoverable from any copy anywhere — including copies an attacker took earlier. That is what makes it a crypto-shred rather than a deletion.

Note the ordering. Shredding the server copy while keeping your key means you can still read anything you pulled. Destroying the key is the irreversible half, and the CLI asks about it separately for exactly that reason.

Authentication

  • PATs are the workhorse: 256-bit secrets stored as argon2id hashes, scoped over actions (read ⊂ write ⊂ admin) and optionally over a single brain. Revocation takes effect on the next request — nothing caches the lookup — and revoking a token also evicts any key cached under it.
  • JWTs, when a host requires OAuth: HS256 only, 15 minutes, nothing else accepted. alg: none is rejected.
  • OAuth 2.1 PKCE: S256 only. plain offers no protection against an intercepted authorisation code, which is the entire point of PKCE.
  • Magic links: single-use, 15 minutes, held in memory only.

Transport

TLS is mandatory for anything carrying a key. Myelin refuses to bind a non-loopback address unless MYELIN_TRUST_PROXY=1 says a TLS terminator is in front, and it returns 426 Upgrade Required rather than accepting a key header over a plaintext connection.

What Myelin does not defend against

Stated so nobody has to infer it:

  • A compromised client device. If your laptop is owned, your keychain is owned.
  • A malicious or compromised MCP host. Whatever the host can ask for, it gets.
  • Root on the server during an active session, as above. On the hosted instance that means the project owner, who has root on the box. If that is not acceptable for your data, run it yourself — the runbook is published for exactly that reason.
  • Traffic analysis. The sizes and timings of requests are visible to anything on the path, even though contents are not.
  • A user determined to store a secret in a memory.
  • Denial of service beyond simple rate and size limits. Quota bounds consumption; it does not stop a determined flood.

Reporting

Open a GitHub issue for anything low-risk, or contact the owner directly for anything that should not be public first.

Next

Get it running — the setup lives on the front page, four steps.