R2D2-MERIDIAN/SECURITY.md
Joshua Belke c5c733d3ef fix(rebrand): complete the conversion the rg-based checks missed
A `git grep` audit (which, unlike `rg`, does not skip dotfiles) found the sweep
was incomplete in ways four green CI runs could not see.

**Dotfiles were never in the rewrite lists.** The `/api/codebase` and
`codebase.design` fixes built their file list from `rg -l`, so `.env.example`
and `deploy/compose/.env.example` kept the old control-plane path and an
escaped legacy host regex. `bin/git` was separately reverted to its
Codebase-era error text by the `git checkout -- bin/` that repaired the
clobbered Hermit symlinks.

**The N-1 hop was dropped in two more places**, the same defect as
codebaseChat-b8x, one generation on:

- `e2e_git.rs` published `buzz-channel` + `codebase-chat-channel` on its
  kind:30617 fixtures, per the expand-window procedure in the schema-rollout
  runbook. The sweep rewrote the second rather than adding a third, so the
  fixture stopped exercising the generation that is actually N-1 here. All
  three are now emitted. The relay still reads only its native tag, which is
  the documented design: writers emit every live generation, readers read one.
- The chart alert pack kept `buzz_*` fallbacks beside the then-current
  `codebase_chat_*` metrics. Renaming the current set to `meridian_*` left
  `buzz_*` — two generations back — as the only fallback, so a rolling upgrade
  would have matched neither on pods still emitting `codebase_chat_*`: exactly
  the observability blackout the fallback exists to prevent. The fallback now
  tracks the immediately-preceding generation, which is what "one retention
  window" in the chart README always meant. promtool passes.

**Sprout-era runtime config**, which is operator-facing rather than persisted,
now resolves through one `config::legacy_env_u64`/`_i64` helper that prefers
`MERIDIAN_*` and falls back to `SPROUT_*`, so existing environments keep
working. A single resolver is deliberate: `MAX_NOT_BEFORE_DELTA` is both
enforced in ingest and advertised in NIP-11, and those drifting apart is the
second-copy defect this rebrand keeps producing.

Also: workspace/persona `repository` metadata off `block/sprout`; the "I work
at Block" README section replaced with the honest no-packaged-builds note; the
Block corporate-CA keychain export in the local deploy script replaced with a
bring-your-own-bundle hatch; iOS `CFBundleURLName` and the theme/test locals
that my bead-ID revert had left spelled `codebaseChat`.

SECURITY.md carried **meridian@block.xyz**, which would have routed a report
about this deployment to a third party. Flagged in-file with a TODO rather than
silently pointed somewhere plausible.

Apache-2.0 copyright and attribution to Block, Inc. are retained deliberately
in LICENSE, ARCHITECTURE.md, the README footer, and the Goose logo credits —
the license requires it and a rename does not transfer authorship.

Verified: just ci exit 0; promtool SUCCESS; relay clippy -D warnings clean.
Signed-off-by: Joshua Belke <joshua@innovationhub-act.org>
2026-08-04 23:31:26 -04:00

5.6 KiB

Security Policy

Reporting a Vulnerability

Please do not report security vulnerabilities through public GitHub issues.

⚠ The reporting address below is not yet configured for this deployment. It previously routed to Block, which no longer operates this fork — sending a report there would disclose a vulnerability in your deployment to a third party. Set a real contact before publishing this repo anywhere.

If you discover a security vulnerability in Meridian, please report it by emailing security@meridian.r2d2.office.ilab.zone (TODO: confirm this mailbox exists and is monitored). Include as much detail as possible:

  • A description of the vulnerability and its potential impact
  • Steps to reproduce or a proof-of-concept (if available)
  • The affected version(s) or commit range
  • Any suggested mitigations you've identified

You will receive an acknowledgment within 48 hours. We aim to provide a full response — including a timeline for a fix — within 7 days of initial contact. We'll keep you informed as we work toward a resolution.

We ask that you:

  • Give us reasonable time to address the issue before any public disclosure
  • Avoid accessing or modifying data that does not belong to you
  • Not perform denial-of-service attacks or disrupt production systems

We will credit reporters in release notes unless you prefer to remain anonymous.


Supported Versions

Version Supported
main (latest) ✅ Active
Previous releases ⚠️ Best-effort; upgrade recommended

Meridian is pre-1.0. We do not maintain long-term support branches at this stage. All security fixes land on main first.


Security Design Principles

Authentication — NIP-42

Every connection to the relay must authenticate via NIP-42 challenge/response before writing events. The relay sends a random challenge; the client signs a kind:22242 event containing the challenge and the relay URL, proving possession of the private key.

REST endpoints authenticate via NIP-98 HTTP Auth — the client signs a kind:27235 event containing the request URL and method. The relay verifies the Schnorr signature and extracts the pubkey.

Authorization — Channel Membership as the Gate

Channel membership is the only access control mechanism. There are no separate ACL lists or capability taxonomies. If a principal (human or agent) is a member of a channel, they can read and write to it. If they are not a member, the relay rejects their requests — even if they are authenticated.

Private channels are invisible to non-members: they do not appear in channel listings, and subscription filters for private channel events return nothing unless the subscriber is a member.

Append-Only Audit Log

All events are written to a tamper-evident audit log (meridian-audit). Each log entry is chained to the previous one via a SHA-256 hash chain. Because the chain is keyless, it is tamper-evident but not tamper-resistant: it detects accidental corruption or single-row edits, but an attacker with database write access can recompute the entire chain after editing. The audit log is designed for SOX-grade compliance and eDiscovery.

Desktop Secret Storage — OS Keyring

The Meridian desktop app stores nsec private keys in the operating system keyring rather than in plaintext files: macOS Keychain, Windows Credential Manager, or the Linux Secret Service (gnome-keyring / kwallet via D-Bus). This covers both the human identity key and every managed-agent key.

On first launch after upgrading, existing plaintext keys are migrated into the keyring: the key is imported, read back to verify the round-trip, and only then is the plaintext deleted. Migration runs only when the keyring is reachable — if the backend is unavailable that session, the app keeps reading from the plaintext file and does not migrate, so a transient outage cannot resurrect a rotated key from a leftover file.

When no keyring backend is available (headless Linux with no Secret Service, for example), keys fall back to a 0o600 owner-only file. The MERIDIAN_PRIVATE_KEY environment variable, when set, always takes precedence over both stores — this is how harnessed agents and CI receive their identity.

Input Validation

  • All UUIDs (channel IDs, workflow IDs) are validated at API boundaries before use in database queries.
  • Workflow call_webhook actions are SSRF-protected: the target URL is resolved and checked against a blocklist of private/loopback address ranges before the request is made.
  • Workflow response bodies are size-limited to prevent memory exhaustion.
  • evalexpr condition evaluation is sandboxed and timeout-bounded.
  • Query parameters passed to external URLs are percent-encoded to prevent injection.

Transport Security

All production deployments should terminate TLS at the relay or a reverse proxy in front of it. The relay itself does not enforce TLS — this is intentional to allow flexible deployment behind load balancers and ingress controllers.

Dependency Management

We use cargo audit in CI to scan for known vulnerabilities in dependencies. #![deny(unsafe_code)] is enforced across all crates — no unsafe Rust.


Disclosure Policy

We follow coordinated disclosure. Once a fix is ready and released, we will publish a security advisory on GitHub describing the vulnerability, its impact, and the fix. Reporters will be credited unless they request anonymity.