Last updated: 2026-09-07. Intended production leaf is 20
certificate hostnames on one Let's Encrypt PEM (with_single_cert): the
live 18 plus cryptoquick.com and www.cryptoquick.com. Live leaf
still has 18 names (CT). Validating A for cryptoquick apex/www
succeeds (AD true). Leftover parent DS key tag 2368 is gone. HTTPS
failure on those two names is certificate hostname mismatch, not
SERVFAIL. The old wait-for-SERVFAIL gate is closed as a live A-lookup
gate. SHA-1 parent DS digest type 1 remains standing DNSSEC quality debt
in operator-facts Monday leftover; it is not the HTTPS cause. Laptop
--issue after the tree lists the two names is operator-gated
(Namecheap custody). Do not invent leftover Namecheap clicks. Do not
MX-flip Baxter. Esplora Hosts stay off this leaf. Prior 2026-09-03. NIP-07 /login Login failed: 500 on
HTTP/3 was Axum missing ConnectInfo on POST /api/v1/auth/session
(axum-h3 does not insert it; TCP HTTP/2 was JSON 401). Tree fix: optional
peer extractor + do not ban unspecified. Leftover: inject the real QUIC
peer into ConnectInfo so HTTP/3 bans and rate-limit keys are per-client
(today H3 middleware already falls back to 0.0.0.0 when the extension is
absent). NWC is not that 500; it is the /mail wallet store after login.
Live switch still operator-owned. Flake input splora is
github:SurmountSystems/splora on the surmount branch, locked rev
9481e4cb87273aa99b0357be48503765beadb919 (previous lock
343727487988ed0a764674ff21c0750465b9a3e8). Imported
nixosModules.splora instance options now include cookieFile,
daemonRpcAddr, jsonrpcImport, daemonDir (null is remote JSON-RPC),
publicHealth, db cache default 24, and httpSocketFile default
/run/splora/${name}.http.sock. No QUIC on those Unix sockets. HTTP/2
and HTTP/3 stay on this-tree Axum. This tree keeps
surmount.sploraIndexer as the host-local single knob (one instance,
cookie path charset, sample stays off). The wrap maps those first-class
instance options. It does not mkForce ReadOnlyPaths and does not inject
--jsonrpc-import / --public-health through extraArgs. Eval of the
remote JSON-RPC wrap does not fight the imported module. Do not delete
the wrap. Indexer units stay off until private host-local sets that
option with a reachable JSON-RPC address and cookie path. grokOss is
unrelated to REST. Flake input grok-oss tracks
github:SurmountSystems/grok-oss/remote-1 at PR 51 head
(6edf5fda9507c9e3c9c9f5a67871ebb3ce6cca7d; open PR, product tip
SurmountSystems/grok-oss#51). Enable
surmount.grokOss still from private host-local only; public sample
stays default off. This slice did not bump grok-oss. Upstream crane omits
.cargo/config.toml from src and builds --offline --locked. Laptop
cargo still uses Menhera. This tree does not wrap crane. Menhera
in-sandbox fetch is already fixed upstream. The public sample host keeps
surmount.sploraProxy.enable default off, surmount.sploraIndexer.enable
default off, and does not set services.splora.enable. Private host-local
already enables surmount.sploraProxy with the five esplora Host maps.
Those Host maps and hypervisor UDP 443 are optional Axum edge, not
prerequisites of the mempool REST. Unix socket or one existing Host is
enough. Do not map REST onto the mail console Host.
surmount.managementUi.http3Enable stays default true. The live UI
process has SURMOUNT_SPLORA_PROXY=1. /run/splora exists from tmpfiles.
There are no indexer HTTP sockets and no queue.sock. Do not claim
Esplora is live in browsers. Do not claim the queue unit is live.
just grok-oss attaches as user grok (runuser) so MemoryMax can apply.
The grokOss module stays default off. Do not start Lake from this agent.
Do not invent MX / DMARC p=reject / Vaultwarden / ban / Q-AUTH-1 /
reboot.
Open leftover (operator host-local, complete sentences):
- Private host-local still needs
surmount.sploraIndexer.enable = trueplus a reachable Bitcoin Core JSON-RPC address (surmount.sploraIndexer.daemonRpcAddr) and a cookie file path (surmount.sploraIndexer.cookieFile; never cookie bytes in git) so one indexer instance can start. A local bitcoind datadir on this guest is not required. OptionalpublicHealth = truepasses--public-health. elementsd is not required unless a liquid instance is enabled. Do not start five indexers. Do not treat queue-onlyservices.splora.enableas REST. Do not set this on the public sample host. - The operator still enables
surmount.grokOssfrom private host-local, setspackagetopkgs.grok-oss, and sets a MemoryMax budget string there. The flake pin is open PR 51 head onremote-1. That is grok-oss product leftover. It is not a mempool REST gate. That does not auto-start the TUI; start grok-oss in tmux as user grok after login. Default remains off.
Highest value next (unblock): private host-local JSON-RPC address,
cookie file path, and one indexer instance. Do not treat a local
bitcoind datadir as leftover. Do not wait on five esplora Let's Encrypt
names, just check-remote as a REST gate, hypervisor UDP 443, or
grokOss for the mempool API. Menhera in-sandbox is already fixed
upstream. Do not start Lake.
Last updated: 2026-08-25 (living mailbox map stays in operator-facts;
operator bins are nix run .#....) Prior 2026-08-22 (living mailbox map:
~/.agents/surmount-server/operator-facts.md. Do not invent
MX flip / p=reject / VW. Prior 2026-08-20 (mail records on every mailbox domain, not
primary-only. Do not use p=reject. Live public vs API:
surmount.systems MX 10 mail.surmount.systems, EmailType MX, SPF
a:mail.surmount.systems -all, dual DKIM, TLS-RPT, CAA; public _dmarc
at 1.1.1.1 p=quarantine; unsigned, no DS. cryptoquick.com
child (+cd): dual DKIM, TLS-RPT, CAA, SPF a:mail.cryptoquick.com -all,
MX 10 mail.cryptoquick.com, DMARC p=quarantine (leave as-is).
That day's validating SERVFAIL was leftover parent DS 2368 alg 13 digest
type 1 SHA-1 (no child DNSKEY). Live 2026-09-07: leftover parent DS
key tag 2368 is gone. Validating A for cryptoquick apex/www succeeds
(AD true). Do not re-add 2368. The old wait-for-SERVFAIL gate is
closed. Intended leaf includes cryptoquick apex/www (20 names). Live
leaf still omits those two (18 names).
Extra MTA-STS wait. baxterartworks.com EmailType FWD; public MX
still eforward1-5 (do not claim MX flipped). _dmarc.baxterartworks.com
intended is p=quarantine. Do not restore getHosts _dmarc
to p=none. getHosts (11 records) has dual DKIM, TLS-RPT, CAA, SPF
a:mail.surmount.systems plus eforward include -all. Public 1.1.1.1
2026-08-20: DKIM / _dmarc / TLS-RPT NXDOMAIN, CAA empty, old
eforward-only SPF ~all, SOA serial 1787245654 not bumped. Public
_dmarc may stay NXDOMAIN while EmailType is FWD even when getHosts has
the TXT. Leftover _acme-challenge TXT still on getHosts. Extra domains
do not entirely lack dual DKIM / TLS-RPT / CAA. Extra-zone API
worked this slice (not still Invalid request IP). Remaining:
laptop --issue of the 20-name leaf after the tree lists cryptoquick
apex/www (Namecheap custody); SHA-1 parent DS digest type 1 as
operator-facts Monday leftover (not the HTTPS cause); Baxter Custom MX only if the operator asks
(--live set-mx fail-closed while FWD); public NS republish/lag for
Baxter getHosts vs served zone. No VW / ban / reboot. Dual-pin:
AGENTS.md, docs/DNS.md,
docs/SECURITY.md,
docs/COMPACTION-PIN.md.)
Prior same day: extra static vhost HTTPS live for six Namecheap
zones; that day's production leaf 17 certificate hostnames; contributor
self-serve mailbox password + optional NWC on /mail. Q-AUTH-1
unchanged.)
Prior 2026-08-19 (in-tree sshd hostKeys is ed25519 only when
hardening is on. Live host still advertises RSA until a deploy-host
switch. Do not wipe leftover RSA files this wave. Do not invent MX /
DMARC / VW / ban / Q-AUTH-1 / reboot / PQConnect as this change.
Prior same day: in-tree host paper trail: persistent
size-capped journald, sshd VERBOSE, structured http_request with peer
field and no Authorization/Cookie, just host-logs. Live on the
mail host (persistent /var/log/journal, SystemMaxUse 1G, unit journals
for mail/ui/sshd/fail2ban/nix-daemon). 2026-08-25 in-tree add: explicit
journald RateLimit knobs, just host-logs -- --status snapshot, Axum
WARN+ on stderr. RateLimit and stderr split wait on the next
deploy-host switch. qemu-guest-agent still host-local + that switch.)
Prior 2026-08-18 (HTTPS is durable: surmount-management-ui
ExecStart is the Nix store binary; /run 0640-bin drop-in removed.
https://services.surmount.systems/health 200; apex 200; IMAP
993 Let's Encrypt production leaf covering mail + services +
apex + www + mta-sts. TLS key is 0600 surmount-ui (owner-only).
Stalwart uses copies under secrets/mail/tls. surmount.remoteBuilder enabled from private
host-local: MemoryMax + Nice=19 on nix-daemon.service (real
rustc cgroup) as well as builder/user slices, cpuQuota = auto (not
95 percent of one CPU), maxJobs memory-safe (not a fake high advert),
niced nix-daemon --stdio, nixbuilder key-only. surmount.lake is
default off (niced + MemoryMax when host-local enables it). Do not
start Lake from an agent. SHC ticket 261
closed 2026-08-18. Agents never reboot. Do not open another ticket. Do
not invent MX / DMARC / VW / ban / Q-AUTH-1.
In-tree scaffold memoryMax = "4G" is not a published guest RAM size.
Scaffold maxJobs = 8 is not a published core count.
Host-local may set a real cap privately. Niceness is not a memory cap.
The operator laptop is local. surmount-1 is the remote builder.
Laptop user Nix machines max-jobs matches live guest
nix.settings.max-jobs (guest-sourced; not laptop cores). Laptop
system /etc/nix/machines is still absent (agent sudo -n denied;
operator TTY commands in
/home/hunter/.agents/reports/impl-untwist-inxi-machines-2026-08-18.md).
User nix.conf already points at the user file. Measure the mail host
with ssh surmount-1 inxi; do not copy laptop inxi onto
modules/remote-builder.nix. Prior
same day: box recovered after hypervisor Stop then Start; UI HTTPS
was a temporary /var/lib/surmount/surmount-management-ui-0640 plus a
/run drop-in (old store binary refused 0640; reboot would lose it).
Namecheap API stays laptop-only. Prior 2026-08-17: mail-plane TLS
apply driver shipped. 2026-08-18: point-stalwart-mail-tls resolves
Stalwart 0.16.15 query Certificate --json from certificate hostnames
plus id (still accepts filePath if present). Laptop renew is a real
command: just laptop-renew-cert -- --check|--live --directory production
(issue only if due; stage/install/restart/prove otherwise). Laptop user
timer: --install-timer --target ... (without --target is BLOCKED).
Not a VPS ACME timer. Do not put a calendar
note in place of that path.)
Nix ssh-ng builder live on the host (MemoryMax generation
switched). Lake, if run at all, is outside this flake. Onion-Location + Alt-Svc
on the Axum edge; live Arti unit surmount-arti-hidden-service active;
same v3 hostname for apex, www, services, extra static Hosts, and
MTA-STS (/_o/{host} for non-console); Tor Browser verify
BLOCKED; operator HS identity backup still open; B3 not fully
closed; live VW unit still leftover). Prior 2026-08-14
(create-mailbox portal shipped on /mail;
review 0 open; leftover-cookie / role None password is 403; NIP-98
query mismatch fail-open closed; reviewed tree switched on the
live host, nixos-rebuild 0; post-switch smoke 1, loopback /health
failed, cause unknown). Prior same day: primary hunter MailPlus copy
done; Vandelay import + JMAP export done;
TLS handshake logs journald-only; Safari
diagnosis = tls_handshake_failed while retrying the phone. Prior:
2026-08-12 earn-trust live Tracks 0/A/B/C before operator reboot;
report .agents/reports/impl-earn-trust-wave.md. Same day prior:
offline drivers + B4 live Nostr gate; LE production multi-name +
durable PEMs (laptop custody).
Live (honest): public :443 is surmount-manage not Stalwart.
This laptop 2026-08-18 after hard cycle: SSH up;
https://services.surmount.systems/health 200; apex 200;
IMAP/SMTPS Let's Encrypt YE1 including mail. SHC ticket 261
(closed 2026-08-18) said our RAM use at the tail end cannibalized the
hypervisor host and Proxmox automatically OOM-shutdown the guest as a
protection step. Console evidence 2026-08-18: Lean/Lake OOM killed
systemd and took inbound networking with it. Niceness is not a memory
cap. Agents never reboot. Do not open another ticket to re-argue
the Stopped badge. Cert is Let's Encrypt production (issuer YE2 as of later
measures; YE1 on the 2026-08-13 proof). Intended production leaf is
20 certificate hostnames on one PEM. Live leaf as of 2026-09-07
(CT) is still 18 names:
baxterartworks.com, www.baxterartworks.com, btcfur.com,
www.btcfur.com, exophiles.org, www.exophiles.org,
iantuckerstudios.com, www.iantuckerstudios.com, nostrfurs.com,
www.nostrfurs.com, yiffa.app, www.yiffa.app, surmount.systems,
www.surmount.systems, mail.surmount.systems,
services.surmount.systems, mta-sts.surmount.systems,
mail.cryptoquick.com. Extra static
vhost HTTPS for those six Namecheap zones is live (HTTPS 200,
packaged site content, not console, not COMING SOON). Missing from the
live leaf: cryptoquick.com and www.cryptoquick.com. That hostname
mismatch is why cryptoquick HTTPS fails verify. Validating A succeeds
(AD true). Leftover parent DS key tag 2368 is gone.
Issued by laptop DNS-01 (Namecheap; ClientIp = laptop egress); host
SURMOUNT_ACME_ENABLE=0; durable PEMs under
/var/lib/surmount/secrets/tls/{cert,key}.pem. Private host-local pins
acme.enable = false + those durable TLS paths + authMode=nostr +
mtaStsMode=testing. Auth (B4): anonymous services / 307 /login;
anonymous /api/v1/domains 401; /health 200. Apex/www (live 2026-08-19): packaged SurmountSystems/site
(flake input 1c84696 + pkgs.surmount-public-site; Host routing
on apex and www). https://surmount.systems/ and
https://www.surmount.systems/ 200 current GitHub tip copy (title
Surmount Systems, BIP 360 + P2MR, Grok OSS, contributors; nav/list
plus marks in styles.css). Not yellow UNDER CONSTRUCTION. COMING
SOON leftover closed. Services console stays on
services.surmount.systems (anon / 307 /login; /health
200). Report:
/home/hunter/.agents/reports/impl-deploy-surmount-site-update-2026-08-19.md. DKIM engine: both
File DkimSignature objects present on the primary domain, selectors
stalwart (Ed25519) + stalwart-rsa (RSA-4096), stage active (sign-ready;
not outbound signed mail; primary public MX is mail.surmount.systems).
Extra mailbox DNS is no longer "entirely missing": cryptoquick.com
child (+cd) has dual DKIM / TLS-RPT / CAA; baxterartworks.com
getHosts has dual DKIM / TLS-RPT / CAA; _dmarc intended
p=quarantine (public _dmarc may stay NXDOMAIN while EmailType is
FWD). Extra MTA-STS wait (not on this leaf). Extra-zone Namecheap
API worked this slice. Intended production leaf adds
cryptoquick.com / www.cryptoquick.com on the same PEM (20 names).
Live leaf still omits those two (18 names). Laptop --issue after the
tree lists them is operator-gated (Namecheap custody). MTA-STS HTTPS:
https://mta-sts.surmount.systems/.well-known/mta-sts.txt 200
mode: testing (HTTP/1.1 and HTTP/2; domain-audit-style curl --fail OK);
policy mx: is product mailHostname (primary public MX is now
mail.surmount.systems; stay testing; do not enforce). Recovery: durable Domain B
/var/lib/surmount/secrets/stalwart/recovery.env mode 600, unit
EnvironmentFile=-.../recovery.env (Q2). Report:
.agents/reports/impl-earn-trust-wave.md.
Not live VW; mail is live on the production leaf (993/465 YE1);
primary MX flipped 2026-08-20 (mail.surmount.systems; Baxter MX still parked FWD; cryptoquick public MX already this host); mailbox _dmarc is
per-apex (primary public p=quarantine, cryptoquick p=quarantine leave as-is, Baxter intended p=quarantine; do not use p=reject; do not restore Baxter getHosts to p=none);
not live Tor Browser / onion-fetch proof; not operator
HS identity backup; not live ban drop; Q-AUTH-1 still
open (durable session store, key-loss, first-operator bootstrap UX).
This wave (done on the host): deploy-host of current tree +
surmount.remoteBuilder with MemoryMax; store UI binary; /run
drop-in gone; health/apex 200; IMAP Let's Encrypt; no Lake unit.
Still open (not this module): laptop system /etc/nix/machines
plus system builders= still need a local operator sudo TTY (laptop
is the Nix client; machines slots come from live guest, not laptop
inxi).
User nix.conf already has builders = @ the user machines file. Lake stays outside this flake; do not
start it. Mail and critical units stay normal priority. Do not
invent MX / DMARC / VW / ban / Q-AUTH-1 / reboot / a new SHC ticket
to fill the queue. Law: AGENTS.md
Mention is in scope; remote builder and niceness;
docs/OPS.md Remote Nix builder.
Reports: /home/hunter/.agents/reports/impl-durable-https-remote-builder-2026-08-18.md;
prior /home/hunter/.agents/reports/impl-lake-memory-cap-2026-08-18.md;
ticket thread /home/hunter/.agents/reports/ticket-261-thread-lessons-2026-08-18.md.
Host package (btop, 2026-08-17; ET 2026-08-28): modules/btop.nix adds
pkgs.btop when surmount.enable; laptop just btop uses Eternal
Terminal (et -c btop, live tty required, nested guest runs local
btop). Not on the live box until a deploy-host switch.
Hermetic just test-btop-host. Report:
.agents/reports/impl-btop-ci-deploy-2026-08-17.md (CI/deploy leftover).
Host paper trail (2026-08-19, in-tree): surmount.logging (default on)
persistent journal + size cap, sshd VERBOSE, nft ban-drop log prefix,
Arti log_sensitive_information = false, Stalwart/VW/UI stdout ->
journald, nixbuilder in systemd-journal when remoteBuilder is on.
just host-logs follows the journal over the same SSH target as
just btop. Not on the live box until a deploy-host switch.
No public log viewer. No secrets in logs. Scaffold SystemMaxUse=1G
is not a published guest disk size.
Arti (honest 2026-08-17): daemon unit active; hostname file present;
clearnet Onion-Location + Alt-Svc proven on HTTPS 307/200 for apex, www,
services. That is not B3 closed. Status: living residual after live
free-443 + production multi-name HTTPS (incl. mta-sts) + dual-sign register
- MTA-STS testing + durable recovery + B4 + create-mailbox portal (review
0 open) + onion discovery headers + live Arti unit. Post-reboot
proof PASS (2026-08-13). Not operator acceptance of parked residual
(MX, VW, remaining Arti, ban, Q-AUTH-1). S6 values and live S7b unit remain
operator host residual. Hunter stays IMAP-only until an Administrator
pastes a real npub on
/mailGrant console login (portal attach path shipped 2026-08-20; no real npubs in tree). Named extra-person MailPlus import from DS3018xs 2026-08-20. Living map:~/.agents/surmount-server/operator-facts.md. Other unnamed MailPlus uids deferred. feat:old-site-serve deferred.
Reload SoT: docs/COMPACTION-PIN.md.
Honesty pin: local end-to-end green (nix run .#e2e / just e2e), including
self-signed certificate/key files and optional Tor with temp hidden-service
keys, is not public cutover, production surmount-arti ownership, or a live
kernel firewall drop. Host proof is nix run .#e2e-host / just e2e-host with
SURMOUNT_E2E_HOST=1 on a real deploy. This environment has no staging/VPS
host (SURMOUNT_E2E_* unset; no active UI unit; arti not on PATH; tor
present only). Agent work below does not soft-elevate host rows.
Production :80 (operator 2026-08-02): assume TCP port 80 is free on the NixOS box for the product redirect-only listener. That is not ACME HTTP-01 on product :80 (still parked; Q-EDGE). Ops day-one: docs/OPS.md; edge detail: docs/EDGE_AND_TLS.md.
| Entry | What |
|---|---|
just check |
Host CI-style bar: fmt (check-only, errors if dirty) then clippy (-D warnings) then test (all workspace cargo tests). Does not write format fixes. Does not hard-depend on Tor deep row or host e2e |
just ci / just check-ci |
Same as nix build .#checks.<system>.ci (full flake CI aggregate). GHA job display name is just ci (required-check footgun; see .github/workflows/ci.yml) |
checks.*.ci |
management-ui build + cargo fmt/clippy/test, e2e-pure-test (Rust pure helpers + host-gate contracts), one full module-eval, thin pure deploy/arti path contracts, arti-onion-package eval (features/passthru, no cargo build), nixfmt. Does not run host probes or optional Tor deep row. Workflow: .github/workflows/ci.yml (pattern from grok-oss) |
just fmt |
Format check only (cargo fmt --check + flake nixfmt --check); use just fmt-write to apply |
just test |
Host cargo test only (fast loop) |
just test-btop-host |
Hermetic crate tests for surmount-btop-host (synthetic example.test; no live SSH). Also a GHA quality step. Live just btop is operator TTY only. |
just clippy |
Host cargo clippy --all-targets -- -D warnings |
nix run .#e2e / just e2e |
Local comprehensive end-to-end (SoT flake app; Rust packages.e2e / surmount-e2e). No NixOS, no root, no secrets in git: one hermetic cargo test -p surmount-management-ui plus named critical test anchors (health/TLS self-signed PEMs, https+local cleartext dual bind, rate-limit, ban/helper + remove_ban, surface audit 404/501, Nostr auth off/gate/NIP-98 session, onion unset residual, directory list + account create auth-off/CSRF/lab-escape/503, SSR, unauthorized stub) and cargo test -p surmount-e2e --lib. Not five independent cargo filters. Optional Tor publish+fetch when service-capable arti + Tor client present (SURMOUNT_E2E_TOR=0 force-skip). Self-signed is correct for local HTTPS. |
nix run .#e2e-host / just e2e-host |
Host end-to-end (SoT flake app; Rust packages.e2e-host / surmount-e2e-host). Never a flake check. Requires SURMOUNT_E2E_HOST=1 or exits 2. Also requires SURMOUNT_E2E_BASE_URL (health FAIL if unset, not silent SKIP). Ban track requires SURMOUNT_E2E_LAB_IP membership in surmount-ban4 unless SURMOUNT_E2E_SKIP_BAN=1. Summary ban_drop=UNPROVEN (set existence / membership is not live traffic drop). Probes: HTTPS health, MemoryDenyWriteExecute (no writable+executable memory), UI no CAP_NET_ADMIN (ambient+bounding), Arti unit, ban helper + set preflight. |
just check-heavy |
Optional mail-vm (not in ci) |
Heavy (not in ci): mail-vm-test, mail-vps-eval toplevel, stalwart-mail FOD.
CI note: only module-eval-contract runs full nixosSystem. Deploy-secrets and
arti-module flake checks are pure charset/path-shape only (no triple eval).
Module-eval is not a substitute for process end-to-end.
E2E vs CI (product choice, not a law of nature): Today checks.*.ci keeps
reproducible tree checks (including e2e-pure-test lib contracts) and
leaves the full hermetic anchor harness at just e2e / nix run .#e2e. Putting
only that hermetic path (anchors + cargo tests; SURMOUNT_E2E_TOR=0 or no
Tor tools) into a flake check is a deliberate cost-vs-safety choice if we want
e2e always inside CI. Tor deep row and host e2e stay out of CI either way so
green CI never soft-elevates to "cutover done." Local temp Tor keys and
self-signed PEMs are not host ownership or public cutover.
Deploy secrets
- Fail-loud activation + oneshot when
requireDeployMaterial - Strict host path charset (
modules/lib/host-paths.nix); all shell usesescapeShellArg - Per-path kinds:
requiredHostPathsis{ path; kind = file|directory; }[](no global kind, no weakany) - Real module eval:
tests/module-eval.nix(single full stack contract)
Axum edge
- In-process rustls HTTPS (TLS 1.3 lean, aws-lc-rs; hybrid PQ KEX when
provider defaults enable it) loads host PEMs;
listenMode=httpshappy path - Fail-closed if PEMs missing/unreadable/wrong key mode; garbage PEM rejected
allowCleartextHttpsEscapeis emergency cleartext override (default false): when set it always binds cleartext under https, even if rustls is ready; bare https with PEMs (escape off) is the happy path at Nix eval- Rate limit: loopback peers trust X-Real-IP only (XFF ignored for keys); max key cap; 429 + Retry-After integration test
- Ban decision layer (
ban.rs): Allow / Whitelisted / RateLimited / Banned / BanCandidate; whitelist never banned; last-used touch on allowed whitelist traffic; memory backend + optional file state; nft command builder with hermeticRecordingNftExec(no root in CI). Enforcement default off (lean private).accessControlNix opts + nft set names when enabled. - Least-privilege ban helper (scaffold): binary
surmount-nft-ban-helper(kernel firewall helper) + JSON protocol (add_ban/remove_ban/ping); product elevation is socket-activated oneshot (surmount-nft-ban-helper.socket+@.service) with CAP_NET_ADMIN on the helper unit only. UI usesSURMOUNT_BAN_NFT_HELPER_SOCK(Unix socket); keeps NoNewPrivileges and never gets CAP_NET_ADMIN. Child setcap spawn is not the host path (NNP blocks file-cap elevation). DryRun does not mutate firewall sets; Enforce apply-then-durable (no memory write if apply fails). Re-signal after crash-window treats "File exists" / already-present as apply success so app durable can catch up.remove_bantreats absent element as success (lab unban/cleanup). Hermetic tests + mock; no root/real kernel sets in CI. Auth-failure BanCandidate stub shipped:BanGuard::signal_unauthorized/decide_ban_signal+ main request-context adapter; Off/DryRun/Enforce consistent; whitelist immune. Surface audit + ban matrix locked: routes do not auto-ban on 404/501; session exchange fail and bad presented NIP-98 may signal once; missing cookie does not. Table dual-pinned in docs/SECURITY.md. Full Q-ACL surface list still open. Not live host drop. - Nostr auth foundation (2026-08-01; polish 2026-08-02): rust-nostr NIP-98
(kind 27235) + HMAC session cookie scaffold;
SURMOUNT_AUTH_MODEoff (default) / nostr; env allowlist + optionalSURMOUNT_NOSTR_ALLOWLIST_FILE(env wins; empty fail-closed); challenge/session/logout/me +/login. Not JS NDK. UI honesty: auth-mode banners,/loginwhen nostr. Create mailbox form is on/mail(Administrator only);/accountsstill has no create form. Q-AUTH-1 residual: key-loss, durable session store, first-operator bootstrap UX (do not invent). nsec never on server. - Security headers + CSRF logout (2026-08-01): management router baseline
CSP (lean SSR; per-request nonce for
/loginNIP-07 script), nosniff, Referrer-Policy no-referrer, X-Frame-Options DENY + CSP frame-ancestors none. Double-submit CSRF cookie on session create; enforced onPOST /api/v1/auth/logout. Session cookie HttpOnly + SameSite=Lax; Secure when HTTPS listen. - TLS private key must not be group/world readable (
mode & 0o077 == 0) - Redirect helpers + host allowlist + IPv6 Host parse
- Local cleartext full API (
SURMOUNT_LOCAL_CLEARTEXT_LISTEN): loopback only; concurrent with https primary; not the redirect-only :80 listener. Nix auto-derives when https UI + Arti enable + no explicit arti backend (public https ->127.0.0.1:8090; loopback https on 8090 ->:8091). surmount.web.enabledefault false (nginx not product edge); dual-run escapeweb.enable = truestill wires nginx + ACME; module-eval asserts web-off / https-ui / dual-run-on paths (no secrets in tree)- Leptos SSR multi-page management console:
pages.rsrendersGET /,/domains,/accounts,/system,/mail,/loginvia Leptosview!+.to_html()(ssr feature only; plain<a href>nav, no WASM). Sharedbuild_routerkeeps rate-limit / ban / auth middleware. Honest accounts inventory (empty +source: unavailable; no fakeadmin@). Domains from config labeled as inventory, not Stalwart directory. Live Stalwart probe on overview/mail. Hermetic tests: SSR markers, DOGE palette, nav, no skeleton, accounts honesty,/health. No NPM. Crane: nixpkgsrustPackages_1_95(host channel nixos-26.05). DOGE theme (2026-08-01): pure 3-bit RGB eight-color palette only (data-theme="doge",color-scheme: only dark); no grays / no light media queries. Spec SurmountSystems/specs0001_DOGE.mdv1.0.0. Directory trait + hermetic mock + live Stalwart client shipped (2026-08-01 mock; 2026-08-07 live):directory.rsstrategy onAppState(UnavailableDirectorydefault;MockDirectoryvia constructor or explicitSURMOUNT_DIRECTORY=mockonly, never default-on;StalwartDirectoryviaSURMOUNT_DIRECTORY=stalwart+ host Bearer token env/file, fail-closed misconfig, list fail-closed empty on HTTP error; management JMAPx:Account/query+get). API + SSR list through directory. Mutations shipped (2026-08-07): create/update via trait +POST/PATCH /api/v1/accounts; mock +x:Account/setwire-mock; auth gate (nostr or lab escape) + CSRF on cookie POSTs; no nsec. Create mailbox form shipped (2026-08-14) on/mail(Administrator; optional npub; User vs Administrator; map at/var/lib/surmount/console/accounts.json, envSURMOUNT_CONSOLE_ACCOUNTS). Review 0 open. Leftover cookie / role None is 403 on password. NIP-98 query mismatch fail-open is closed. Map load fail-closed. Flock across load+edit+save. nsec not echoed.ok: falsewhenconsole_savedis false. Attach npub shipped (2026-08-20):/mailGrant console login pastesnpub1..., session CSRF, map write even when directory listing is unavailable. Hunter IMAP-only until an Administrator pastes a real npub./accountsstill has no create form. Q-AUTH-1 still open. MX still parked. No live VW / ban. Arti unit live; Tor verify and HS backup still residual (B3 not fully closed). Mailbox password shipped (2026-08-14):POST /api/v1/accounts/password/mailform; lookup by email; Password credential on the mailbox Account (not API-tokenAccountPassword); live client POSTsSURMOUNT_STALWART_URL/jmap(0.16.15POST /apiis HTTP 404); works when listing is unavailable if host token + loopback URL exist; password never in JSON/logs/argv. Structured request logging shipped: onion-redacted path, no secret headers. Nix:managementUi.directory/stalwartTokenPath(default unavailable).
Arti HS
- Management-publish
arti.toml:[onion_services."<nickname>"]+proxy_portsto management backend (TCP orunix:UDS);storage.state_dir=onionServiceStateDir(deploy secrets); cache understateDir - TCP backend: null
backendAddresstracks UI bind when UI is http; when UI is https (escape off) and no explicit backend/UDS, module auto-points onion at the local cleartext API (managementUi.localCleartextListenor auto-derived loopback). ExplicitbackendAddressequal to primary https TCP still warns. UDS backend suppresses auto cleartext bind. - Lean onion backend is cleartext HTTP (or UDS). No TLS-on-onion.
startDaemondefault false (enable installs config + status oneshot only)- Complete lean path:
startDaemon=truedoes not requireacceptIncompleteOnionConfig(no effect this module version) - Surmount package:
pkgs.artiOnionService/packages.*.arti-onion-serviceis Surmount-owned Arti 2.5.1 source build + cargo featureonion-service-service(distinct from stock nixpkgspkgs.arti1.4.2). Module prefers it whenpackageis null; capability viapassthru.surmountOnionServiceCapableor explicitpackageIsOnionServiceCapable(stock path still fail-closed) - Daemon unit: package required;
ConditionPathIsDirectoryon HS state; restart burst capped; HS dir must be writable bysurmount-arti(e.g. 0750 surmount-arti:surmount-arti; never auto-create identity dir) - No private keys in tree; lean defaults (Stalwart admin/JMAP publish off; those flags reserved, no stanzas yet)
- Onion IP collapse (documented limit): lean TCP loopback rproxy means
UI ConnectInfo peer is Arti (
127.0.0.1); onion clients share one rate-limit/ban key unless a future PROXY / trusted-IP path lands. Do not invent X-Real-IP from Arti. Ban of 127.0.0.1 would deny cleartext+onion. - Onion-Location + Alt-Svc (2026-08-17; every public Host 2026-08-20):
security_headers_middlewareemits both on mapped HTTPS 2xx/3xx (apex,www.{apex}, services, extra static Hosts,mta-sts.{apex}; same v3). Non-console Onion-Location uses/_o/{clearnet-host}{path}; onion-side rewrite serves that Host.http://{onion}/stays the services console. Mail unmapped. Mapping loaded once at process start from the existing onion surface plus extra static Hosts. No hot-reload; restart the unit after hostname file, map file, static vhost map, or env change. Optional env:SURMOUNT_ONION_LOCATION_ENABLED,SURMOUNT_ONION_ALT_SVC_ENABLED,SURMOUNT_ONION_LOCATION_DISABLED_HOSTS,SURMOUNT_ONION_ALT_SVC_DISABLED_HOSTS,SURMOUNT_ONION_MAP_FILE.GET /api/v1/systemdumpsonion_discovery(admin-gated when Nostr on). Local Arti cleartext bind does not emit discovery headers. Tor Browser purple pill still residual. Live extra-vhost and MTA-STS dual headers proven 2026-08-20 afterdeploy-host(same v3,/_o/{host}). Vaultwarden unit and/vaultproxy still off (no Domain A vaultwarden-admin, no host-local enable fragment). - Live host (2026-08-17, private host-local only): unit
surmount-arti-hidden-serviceactive; durable HS dir (not/run); hostname file present (v3 onion; do not paste the address in this public tree) (same v3 for apex, www, services). Host-local extras:HOME=/var/lib/surmount/arti;surmount-uiin groupsurmount-artito read hostname; keystore 0700. Public module still does not set HOME /port_info. Arti does not writehostname; host wrote it fromarti hss onion-address. Headers proven on HTTPS 307/200. Tor Browser purple pill BLOCKED. - Residual: live Tor network verification (unit active != published;
Tor Browser BLOCKED); operator offline backup of HS identity;
systemd hardening parity in the public module after real
arti proxy; Q-ARTI-*. Local e2e may publish with temp keys when arti is present; that is not operator-owned backup. Do not claim B3 fully closed.
Tooling
justfile+ flakechecks.ciaggregate- crane cargo test/clippy/fmt checks
- Deploy automation (2026-08-08, offline; host-local import 2026-08-10):
host-local contract docs/deploy-host-local.md;
operator driver
nix run .#surmount-deploy-host(+just deploy-host) with dry-run, lockout key check, refuse host-local into trackedhosts//secrets/; flake auto-import of./host-localwhen present (known names + authorized_keys keyFiles; optionaldefault.nix); rebuild usespath:REMOTE#attrso untracked host-local is visible;/host-local/gitignored; hermetic crate tests inchecks.*.ci. Dual-pins: OPS, hygiene, SECRETS, sample host comments. Not CI remote switch; not live SSH cutover (needs operator target + working SSH + real keys). - Secrets custody bridge (2026-08-09/10, S0-S4 offline review-hardened,
re-review 0 open): three-domain living contract + attribute schema +
material inventory + operator runbook in docs/SECRETS.md;
nix run .#secrets-install-host(staging primary, optional secret-tool, refuse public inject, mode 0600, no value log); S4 opt-innix run .#surmount-deploy-host -- --install-secrets(default off; refuses missing staging/host-id; dry-run plans bridge; never logs values); hermetic crate tests inchecks.*.ci+ GHA quality only. Not libsecret Rust crate (S5 parked); not live keyring/host CI. - Host cutover automation pack A0 (2026-08-10, offline): living gate
checklist docs/OPS.md + docs/host-cutover-gates.txt;
nix run .#surmount-host-material-inventory(https-only /--with-vaultwarden/--acme-path; never logs values); lab DNS-01nix run .#acme-dns-hook-lab(temp zone; not production DNS); private host-local fragment emit (HTTPS/ACME/requireDeployMaterial/restic notes + S7bsurmount.vaultwarden.enable = trueonly with VW profile); composenix run .#surmount-host-cutover(default dry-run;--live+ target for mutation;--generate-materialfor non-CA kinds; refuse in-tree staging and dash-shaped SSH targets); kindvaultwarden-adminon secrets-install + deploy allowlists; hermetic crate tests inchecks.*.ci+ just recipes + GHA quality step. Scripts S6 install path and S7b enable path after material exists. S6 values and live S7b unit remain operator host residual. Never live LE/SSH/keyring as CI green.
Security posture docs (2026-08-08)
- Ladder B0-B7 documented as operator host work with proof gates (pointing at existing modules). Sample host still does not enable requireDeployMaterial / https / Arti / ban by default.
- D1 TLS hybrid KEX offline: rustls workspace
prefer-post-quantum+ hermetic unit tests that default aws-lc-rs provider includes and prefers X25519MLKEM768; dual-pin EDGE/SECURITY/deploy-host-local/COMPACTION-PIN. - D1-host runbook + probe (offline ship 2026-08-08): operator commands in
docs/deploy-host-local.md section 6;
nix run .#surmount-tls-hybrid/just check-tls-hybrid(exit 2 BLOCKED whenSURMOUNT_E2E_BASE_URLunset; hermetic crate tests with PATH-mock classical/MLKEM). Dual-pin OPS/EDGE/SECURITY/deploy-host-local/COMPACTION-PIN. Not live host hybrid until B1 + probe green on the real box. Not flakechecks.ci. - Deploy/private-data/hybrid-probe/secrets/cutover CI: GHA quality job runs
hermetic crate tests in
checks.*.ci(in addition to--treescan). Hermetic only; never livenix run .#surmount-tls-hybrid, live keyring, live LE, or live SSH switch as green. Still not flakechecks.ciremote switch; still not live cutover. - D2 PQConnect integration prep dual-pinned (consume after human sibling commit; 26.05 rebuild; keys never git; no premature flake path input). Product wire still human/operator-gated. Track C self-host DNS, B1+ host PEMs: still operator- or human-gated (see highest-value next).
| Track | Residual |
|---|---|
| Deploy on real host | Driver + flake host-local auto-import + path: rebuild + post-switch smoke shipped offline (nix run .#surmount-deploy-host-post-switch-smoke; always after rebuild even if non-zero; hermetic fake systemctl/curl/ss self-test). Loopback /health is day-1 HTTP :8090; when the unit is public HTTPS edge (SURMOUNT_LISTEN_MODE=https and listen port 443) smoke curls https://127.0.0.1:443/health (curl -sk), not HTTP on :443. Smoke also checks TLS PEM path presence when https edge / SURMOUNT_TLS_* (durable H-PEM defaults; paths only), optional ACME enable note, and :80/:443 listen when https edge is configured (day-1 loopback skips PEM/listen). Operator still supplies target, private host-local material, working SSH, keys matching agent, and runs live switch. Activation hang residual (user@0 / "reloading user units for root") is known ops residual: prefer second SSH; verify generation + units via automatic smoke after recovery. No multi-host fleet tool. |
| Public HTTPS host cutover (B1 / live W3) | Live free-443 + production multi-name HTTPS (2026-08-11; mta-sts on leaf 2026-08-12; mail on leaf 2026-08-18): free-443 live; public :443 is surmount-manage, not Stalwart; :8080 management + mail ports unchanged. https://services.surmount.systems/health 200 without -k. Cert LE production. Intended leaf is 20 names on one PEM. Live leaf as of 2026-09-07 is 18 names: six extra static zones apex+www (baxterartworks.com, btcfur.com, exophiles.org, iantuckerstudios.com, nostrfurs.com, yiffa.app) plus surmount.systems, www.surmount.systems, mail.surmount.systems, services.surmount.systems, mta-sts.surmount.systems, plus mail.cryptoquick.com. Missing: cryptoquick.com and www.cryptoquick.com. Issue path: laptop DNS-01 (Namecheap; SettleSeconds=120); host ACME off. Durable PEMs /var/lib/surmount/secrets/tls/{cert,key}.pem. Reports: .agents/reports/impl-live-le-production-laptop.md, .agents/reports/impl-earn-trust-wave.md, .agents/reports/post-reboot-proof-2026-08-13.md. Post-reboot proof PASS (2026-08-13, real Blesta reboot): units UI + Stalwart active; MTA-STS testing; both DkimSignature objects active; recovery.env mode 600; operator-only journal. Later (2026-08-18): IMAP/SMTPS Let's Encrypt YE1 including mail. Later (2026-08-19): apex/www packaged SurmountSystems/site (flake input 1c84696; not UNDER CONSTRUCTION). Later (2026-08-20): extra static vhost HTTPS live for those six Namecheap zones (exclusive A matching this host; HTTPS 200; packaged site content; not console; not COMING SOON). Extra-vhost HTTPS is not remaining for those six. baxterartworks.com is also a local Stalwart Domain; public MX still parked. Static-site-only extras are not mail domains. Not live in browsers as trusted HTTPS (2026-09-07): cryptoquick.com and www.cryptoquick.com fail verify because the live 18-name leaf omits them. Validating A succeeds (AD true). Leftover parent DS key tag 2368 is gone. Host tree populated; insecure -k HTTP 200 titled Hunter Beast. Intended leaf is 20 names on one PEM. Still residual (operator-gated): laptop --issue after the tree lists the two missing names (Namecheap custody); SHA-1 parent DS digest type 1 in operator-facts Monday leftover (not the HTTPS cause; do not re-add 2368; do not invent leftover Namecheap clicks); firewall across switch proof; VW host enable + /vault/ only with operator OK. H4 parked. Do not claim live VW. Mail is on the production leaf. Do not invent MX / DMARC / VW / reboot |
| nginx module delete from tree | Dual-run escape still ships (web.enable = true). Unused-detection checklist: docs/OPS.md. Delete module file only after operators no longer need it and explicit operator OK |
| :80 redirect listener | Wired in tree (flag + listen + allowlist; dual-run mutex; redirect-only; same-host HTTPS upgrade including apex/www so public users are not sent to the operator console). Operator assumption (2026-08-02): production port 80 is free for that bind. Host public proof still residual. ACME HTTP-01 on product :80 parked (Q-EDGE; free :80 does not invent ACME) |
| requireDeployMaterial (B2) | Module shipped; enable on host only after PEMs (and later Arti paths) exist so activation fails loud. Sample host leaves it commented. |
| Arti live HS (B3) | Do not claim B3 fully closed. Live Tor Browser / onion-fetch verify still residual (unit active != published). Operator offline backup of HS identity still open. Q-ARTI-2/3 still open. Package currency shipped: Surmount-owned Arti 2.5.1 source + onion-service-service (artiOnionService; not nixpkgs 1.4.2 lag). Tree cleartext local backend for https+Arti auto-path shipped (loopback API + onion target). Local temp-key publish (optional just e2e row) != operator backup. Do not invent Q-ARTI-2/3 answers. Onion status product-real (2026-08-10): when artiHiddenService.enable, management-ui derives hostname file + SURMOUNT_ONION_HS_STATE_DIR from onionServiceStateDir (walk nested hostname); structured status configured / hostname_missing / not_provisioned on SSR + GET /api/v1/system; residual names surmount.artiHiddenService (not demo env copy). Lab SURMOUNT_ONION_URL override only. Onion-Location + Alt-Svc shipped (2026-08-17; every public Host 2026-08-20): security_headers_middleware on mapped HTTPS 2xx/3xx; mapping loaded once at process start (restart after hostname/env/map change; no hot-reload). Same v3 for apex, www, services, extra static Hosts, and mta-sts.{apex}. Non-console Onion-Location uses /_o/{clearnet-host}{path}. Mail unmapped. Optional env: SURMOUNT_ONION_LOCATION_ENABLED, SURMOUNT_ONION_ALT_SVC_ENABLED, SURMOUNT_ONION_LOCATION_DISABLED_HOSTS, SURMOUNT_ONION_ALT_SVC_DISABLED_HOSTS, SURMOUNT_ONION_MAP_FILE. Dump on GET /api/v1/system onion_discovery (admin-gated when Nostr on). Live host (2026-08-17, private host-local; extra/MTA-STS dual headers 2026-08-20): unit surmount-arti-hidden-service active; durable HS dir (not /run); hostname file present (v3 onion; do not paste the address in this public tree); headers proven on HTTPS 307/200 for apex, www, services (2026-08-17) and extra/MTA-STS /_o/{host} (2026-08-20). Extra live wiring (host-local only): HOME=/var/lib/surmount/arti; surmount-ui in group surmount-arti to read hostname; keystore 0700. Public module still does not set HOME / port_info. Arti does not write hostname; host wrote it from arti hss onion-address. Tor Browser purple pill BLOCKED. Do not log full onion addresses in failure tails |
| Nostr production auth (B4) | Live gated (2026-08-12): public services authMode=nostr; anonymous / 307 /login (login HTML, not dashboard); anonymous /api/v1/domains 401; /health 200. Apex/www public page is packaged SurmountSystems/site (live 2026-08-19 GitHub tip 1c84696; COMING SOON leftover closed). HTTP/2 :authority fallback already shipped. Domain B session-secret (EnvironmentFile) + nostr-allowlist installed (0600, surmount-ui; values never in git). Private host-local: authMode=nostr + paths + publicBaseUrl + ACME-off + durable TLS. Footgun guard live (public+off refuse). Report: .agents/reports/impl-auth-live-b4-switch.md. Q-AUTH-1 still open (durable session store, key-loss, first-operator bootstrap UX). Allowlisted operator browser login (NIP-07 / NIP-98) is operator-side proof, not a remaining host install. nsec never on server. |
| Merciless ban product (B5) | First path + helper scaffold shipped: Rust decide + memory/file; optional kernel firewall sync via Unix-socket helper oneshot or unsupported direct exec; Nix accessControl + sets; nftHelper requires backend=nft (fail-closed); UI no CAP_NET_ADMIN; EEXIST/already-present treated as apply ok for crash-window re-signal; remove_ban / lab unban shipped (hermetic delete-element + absent=ok). Auth-failure BanCandidate stub shipped + surface audit (404/501 do not auto-ban); bad NIP-98 / session exchange may call signal_unauthorized once (missing cookie does not). Still residual: Q-ACL-1..6 (which surfaces call the hook); live host helper + sets drop (just e2e-host); fail2ban SSH transitional (B7 after B5 solid). Not live host drop / full unauthorized auto-ban product |
| Mail earn-trust DNS/TLS (B6) | Primary surmount.systems live (2026-08-11/12, re-checked 2026-08-20): Namecheap NS; operator clicked hosted DNSSEC Status ON; live 2026-08-20 still no parent DS and no apex DNSKEY (Insecure, not SERVFAIL; waiting for Namecheap to publish); dual DKIM TXT stalwart + stalwart-rsa; DMARC p=quarantine; TLS-RPT; MTA-STS DNS + HTTPS testing; CAA Let's Encrypt. Domain B dual-sign PEMs on host. Live DkimSignature (Track A, primary Domain): both selectors stage active (sign-ready; not outbound signed mail). Primary public MX flipped 2026-08-20 (dual-sign + PTR already green; operator Custom MX click; laptop --live set-mx). EmailType MX. Public and auth NS MX 10 mail.surmount.systems. SPF v=spf1 a:mail.surmount.systems -all. DMARC p=quarantine. Laptop issue-le-prod/namecheap.env list worked (stored ClientIp accepted; file not rewritten). In-tree list prints EmailType. Leftover eforward MX gone. --live set-mx still fails closed while EmailType is FWD (not a public MX flip while Email Forwarding is on; extra domains; no EmailType-set API). domain-audit FAILs registrar eforward on claimed mailbox domains. Local Stalwart RCPT for hunter@surmount.systems is 250. Public MX still eforward/FWD on baxterartworks.com until asked. MTA-STS HTTPS live (testing, primary only): leaf covers mta-sts.surmount.systems; stay testing; do not enforce. Extra mailbox domains (operator 2026-08-20; not primary-only): this host already sends/receives locally for cryptoquick.com and baxterartworks.com. They do not entirely lack dual DKIM / TLS-RPT / CAA. cryptoquick.com: child (dig @1.1.1.1 +cd, SOA serial 1787271066) already has dual DKIM, TLS-RPT, CAA, SPF v=spf1 a:mail.cryptoquick.com -all, MX 10 mail.cryptoquick.com, DMARC p=quarantine (leave as-is). Validating resolvers SERVFAIL on leftover parent DS key tag 2368, algorithm 13, digest type 1, no child DNSKEY. Operator UI this measure: Advanced DNS DNSSEC Status off. Do not re-add 2368. Same hosted ON after that DS is gone. Extra MTA-STS wait. Do not add apex/www to the production leaf. baxterartworks.com: EmailType still FWD; public MX eforward1-5 (do not claim MX flipped). getHosts (11 records) has dual DKIM (same p= as primary), TLS-RPT, CAA Let's Encrypt, SPF v=spf1 a:mail.surmount.systems include:spf.efwd.registrar-servers.com -all. _dmarc intended p=quarantine. Do not restore getHosts _dmarc to p=none. Public recursive 1.1.1.1 2026-08-20: DKIM / _dmarc / TLS-RPT NXDOMAIN (may stay NXDOMAIN while EmailType is FWD even when getHosts has the TXT), CAA empty NOERROR, SPF still eforward-only ~all, SOA serial 1787245654 not bumped. Leftover _acme-challenge TXT still on getHosts. Extra MTA-STS not published. --live set-mx fails closed while FWD. Extra-zone API worked this slice (not still Invalid request IP). Published Namecheap API has no DNSSEC/DS commands. Do not whitelist the VPS. Still residual: leftover cryptoquick parent DS (required for validating mailers to see those records); Baxter Custom MX only if the operator asks; public NS republish/lag for Baxter getHosts vs served zone; extra MTA-STS wait; register-dkim --live --domain as a host user who can read the token; rDNS/PTR; mail-tester. No reboot. Checklist: docs/DNS.md. |
| D1 hybrid TLS proof | Offline provider config shipped (prefer-post-quantum + hermetic unit tests). Host runbook + probe script shipped offline. Live negotiation residual after B1 on the real host. |
| D2 PQConnect | Integration prep docs dual-pinned. Sibling packaging; path flake input after human commit; rebuild on 26.05; keys host-only; Q-PQC-* open. Do not soft-elevate or wire flake early. |
| Track C self-host DNS | Optional; not required for B6. |
| Admin Leptos SSR | Multi-page console + UI depth (2026-08-01): SSR /, /domains, /accounts, /system, /mail, /login with DOGE chrome, status chips (UI / Stalwart / onion configured marker), inventory cards, definition-list system page, mail probe card. No skeleton branding. Accounts honest empty (source: unavailable) by default; domains config inventory only. Directory trait + hermetic mock + live client shipped (2026-08-07): AppState.directory (unavailable default; labeled mock fixture; live stalwart management JMAP with host token, never default-on; list fail-closed empty). Account create/update mutations shipped (2026-08-07): Directory::create_account / update_account; POST /api/v1/accounts + PATCH /api/v1/accounts/{id}; mock + live x:Account/set wire-mock; fail-closed when auth_mode=off unless lab SURMOUNT_DIRECTORY_ALLOW_UNAUTHENTICATED; double-submit CSRF when session cookie present (same helper as logout). Create mailbox form shipped (2026-08-14) on /mail (Administrator only; optional npub; User vs Administrator; map at /var/lib/surmount/console/accounts.json / SURMOUNT_CONSOLE_ACCOUNTS). Review 0 open. Leftover cookie / role None is 403 on password. NIP-98 query mismatch fail-open is closed. Map load fail-closed. Flock across RMW. nsec not echoed. ok: false when console_saved is false. Hunter IMAP-only until an Administrator attaches npub. /accounts still has no create form. Mailbox password shipped (2026-08-14): /mail form + POST /api/v1/accounts/password; lookup by email; Password credential on the mailbox Account (not API-token AccountPassword); works when listing is unavailable if host token + loopback URL exist. Docs scrub: README/STACK describe real console; Stalwart admin = bootstrap fallback. Directory research: docs/research/stalwart-directory-api.md. Day-one host order in docs/OPS.md. Still residual: hydrate islands only if clear SSR gap (parked); full Q-AUTH-1 product answers; primary public MX flipped 2026-08-20 (mail.surmount.systems); Baxter MX parked FWD; JMAP proxy + webmail beyond thin 501 (501 honesty locked); host cutover / hardware deploy still open for operator; host must place Stalwart API token for live directory list and for mailbox password when listing stays unavailable |
| Nostr E2E auth | Foundation shipped (2026-08-01): SURMOUNT_AUTH_MODE off (default) / nostr; rust-nostr NIP-98 verify (kind 27235); env allowlist fail-closed when empty; HMAC session cookie scaffold; challenge/session/logout/me + /login. Not JS NDK. Security headers + CSRF logout shipped (2026-08-01): baseline CSP (nonce for login script) / nosniff / Referrer-Policy / frame denial on management router; double-submit CSRF on POST /api/v1/auth/logout (session cookie remains HttpOnly + SameSite=Lax; Secure when HTTPS). CSRF on account mutation POSTs shipped (2026-08-07) (cookie session requires X-CSRF-Token / body csrf). Structured request logging shipped (2026-08-07): method/path/status/latency; onion-redacted path; no Authorization/Cookie/bodies. Q-AUTH-1 residual (do not invent): key-loss recovery; durable server session store choice; first-operator bootstrap product UX. nsec never on server. Full webmail CSP still residual. |
| JMAP proxy + v1 webmail | Thin 501 boundary shipped / locked: POST /api/v1/jmap returns honest 501 (error: jmap_proxy_not_implemented), mail SSR documents residual, /webmail is 404 (no fake UI), surface audit no auto-ban on 501. Hermetic: jmap_proxy_returns_honest_501_body + mail_page_documents_jmap_proxy_501_residual + surface audit. Beyond 501 (real authenticated proxy + v1 webmail UI) parked offline-capable; do not invent. |
| Operator OS secret store + deploy bridge | S0-S4 + A0 + V0/V1 + ladder P0-P7 + S8 + H2/H2b + H3 + H5/H6 + H7 offline shipped (2026-08-09/11): three-domain contract (Domain C = human custody; cutover = A→B + host-local + DNS APIs); laptop is long-term custody SoT; VPS durable Domain B default /var/lib/surmount/secrets for PEMs + session/token/namecheap/VW (survives reboot); /run/surmount-secrets still allowlisted ephemeral. secrets-install-host (namecheap-api, optional --client-ip / --require-namecheap); namecheap hooks refuse non-owner-only env mode; A0 host-cutover multi-step ladder + remote free-443; packaged acme-dns-hook-namecheap code for dnsHookPath; hermetic tests. H2/H2b durable PEM + ACME path defaults and parent modes; H3 recovery unit EnvironmentFile option + strip path; H5/H6 multi-name render + no silent prod + post-switch PEM/listen smoke; H7 permanent Admin before ApiKey (compose/bootstrap). This wave (offline): recommended durable default recoveryAdminEnvFile = /var/lib/surmount/secrets/stalwart/recovery.env (mail.nix mkDefault; explicit "" opts out; t34b/t34f); named just laptop-renew-cert. Live host (2026-08-11): free-443 applied; Domain B token path used for free-443; production multi-name PEMs on durable /var/lib/surmount/secrets/tls/ (laptop DNS-01; host ACME off). Not shipped / still residual: S5 Rust/libsecret (parked); Namecheap VPS whitelist only if host ACME ever wanted (not this path); live free-443 / live LE as CI green (forbidden); durable recovery.env is live (Track C 2026-08-12); post-reboot proof PASS (2026-08-13); namecheap.env co-located under ACME ReadWritePaths parent (default .../acme/namecheap.env next to account.json; UI can rewrite env without a separate path grant; prefer sibling leaf e.g. .../secrets/dns/ under H4, not this slice); install bridge always re-chmods material root / state prefix to 0755 even when operator set tighter modes (create-only or "widen only if lacking exec" residual); H4 zero-cred-on-VPS redesign (parked). Living: docs/SECRETS.md, docs/OPS.md ladder + gates, docs/host-cutover-gates.txt. |
| Vaultwarden (domain C) | S7a shipped offline (2026-08-09): module + UI; sample enable=false; pure eval + hermetic UI tests. Do not rebuild S7a. S7b A0 offline shipped: allowlisted kind vaultwarden-admin -> durable /var/lib/surmount/secrets/vaultwarden/admin.env (S8; /run alias still inventory-OK); cutover --with-vaultwarden inventory + private surmount-vaultwarden-enable.nix + restic path note + smoke recipes. V4 offline shipped (2026-08-10): human-store runbook in docs/SECRETS.md section 5.1 (what goes in VW vs A vs B); explicit VW never feeds nixos-rebuild; optional export helper nix run .#secrets-export-bw-to-staging + hermetic crate tests (fixture + mock bw; not live VW; not activation). OPS pointer for G8-G10. Phase B offline shipped (2026-08-11): Axum reverse-proxy under /vault/ in management-ui (proxy_vaultwarden.rs); Nix knobs vaultwardenProxyEnable / Prefix / Upstream (default off); hermetic tests green; docs EDGE + SECURITY dual-pin. Report: .agents/reports/impl-phase-b-vw-axum-subpath.md. Not live host VW unit; not public same-origin vault until operator enables proxy (public HTTPS edge has production LE /health 200; do not enable VW this wave). Still residual (S7b host / live): operator token value (or --generate-material), live switch, unit active, first VW admin UI account once, optional public/onion exposure (do not invent Q-ARTI); enable vaultwardenProxyEnable only with operator OK. Export is optional residual comfort, not a cutover blocker and never a boot dependency. H0: track modules/vaultwarden.nix when human commits (agents never git add). Q-SEC-SM-* open (do not invent Bitwarden Secrets Manager). Never secrets in git. |
| Synology NAS / MailPlus + static HTML | Inventory done (2026-08-13). AFP shares MailPlus, mail, sites, surmount mounted read-only. Report: .agents/reports/nas-inventory-mail-site-2026-08-13.md. Mail is MailPlus @local/<uid>/<uid>/Maildir (maildir-nested). The mail share is empty. Uid map: living map is ~/.agents/surmount-server/operator-facts.md. Hunter stays IMAP-only until an Administrator attaches npub. Extra people are ordinary User mailboxes. Some leftover MailPlus accounts are hunter aliases. Same local-part on another domain is not an alias. Distinct MailPlus accounts are separate User mailboxes. MailPlus copy/import for a same-local-part extra-domain mailbox is still not done; do not fold it into the primary hunter uid. Do not import unnamed extra uids unless named. This GVFS AFP mount cannot readdir non-empty cur/ (I/O error). Copy path (2026-08-13): gio list + reconstruct Maildir :2, flags + rsync --files-from (open-by-name). SMB still password-blocked; NAS SSH 22/2244 refused. Durable user unit surmount-copy-1029 finished (Result=success, folders_ok=31, folders_fail=0, listed_total=535991). Dest: /var/lib/surmount/import/maildir/1029/1029/Maildir. .All Mail excluded and absent. Dest vs source gio passed (INBOX 88912, .Sent 7972, .Archive 179387, .Surmount 1685; dest not short). Only uid 1029 under import/maildir. Report: .agents/reports/copy-1029-maildir-2026-08-13.md. Import done (2026-08-14). Maildir->Vandelay archive: 32 mailboxes, 535961 emails. JMAP export success (unit surmount-import-1029 Result=success, ExecMainStatus=0; export-only 01:28:50Z-13:14:39Z). JMAP totals exact vs dest: Inbox 88912, Sent Items 7972, Archive 179387, Surmount 1685, sum 535961. No All Mail folder. Dest .All Mail still absent. Only uid 1029 under import/maildir. Temporary Http/Jmap push limits restored (authenticated rate 1000/60s; Jmap concurrent 4/4, uploadCount 1000, uploadQuota 50e6). Loopback session rewriter stopped (:18080 closed). Live stalwart-cli 1.0.12 has no import subcommand. Live PATH: vandelay 1.0.7 + surmount-mail-import-maildir. Snapshot /var/lib/surmount/backup/stalwart-mail-pre-1029-2026-08-13 (do not delete). Dest and archive kept. Primary public MX flipped 2026-08-20 (mail.surmount.systems). Report: .agents/reports/import-hunter-1029-2026-08-13.md. Old company page is surmount/Site (static HTML). sites/ is other vhosts; lunarlupine is Grav PHP (do not serve as product). DS3018xs import (2026-08-19/20): extra people as ordinary User only (no console Administrator). Primary hunter was not re-imported from DS3018xs. Operator still sets mailbox passwords on /mail. Extra-domain same-local-part User principal created 2026-08-22. MailPlus copy/import for that mailbox is not done. Do not import unnamed extra uids unless named. Living map: ~/.agents/surmount-server/operator-facts.md. Two office DiskStations: canonical host ids DS1513 (5-bay) and DS3018xs (6-bay). Discover + AFP mount + uid copy scripts shipped (just diskstation-discover, just diskstation-afp-mount -- --host DS1513, just mailplus-copy-uid -- --host DS3018xs <uid>). Copy --host matches IPv4-named GVFS via laptop-private hint (never print IPv4). AFP mount accepts operator IPv4 at runtime (--afp-host / --uri / laptop-private hint ~/.local/share/surmount/diskstation-afp-hosts, never git). --host is the Secret Service label. Nautilus/gio remember shipped: secrets-prompt stores GNOME NetworkPassword (protocol=afp) beside synology-afp; mount fills it if missing. DSM rename is optional. mDNS is optional. Last-week path is gio by operator IPv4. Do not publish office LAN IPv4 in this tree. Secret kind synology-afp is laptop-only (install-host refuses); one item per NAS. Other MailPlus uids still deferred. feat:old-site-serve (old NAS surmount/Site as product serve) deferred. Public site document root shipped; live files at /var/lib/surmount/public-site from sibling site repo (host deploy of this binary still needed). NAS stays read-only. |
| sshd ed25519-only host keys | In tree (2026-08-19): modules/hardening.nix sets services.openssh.hostKeys to one ed25519 key at /etc/ssh/ssh_host_ed25519_key. Hermetic just test-sshd-hostkeys-eval (t43). Password-off / no kbd-interactive unchanged. Live leftover: the running host still advertises RSA until a deploy-host switch. Existing RSA files on disk are not deleted by dropping rsa from hostKeys. Do not deploy this wave. Do not wipe live keys. Do not reboot. PQ honesty: this is classical SSH hygiene. Ed25519 is not PQ. OpenSSH host keys are not PQ. PQConnect is not this. |
| LUKS live / PQConnect / UDS Stalwart / search / multi-host | As before. |
Living map: ~/.agents/surmount-server/operator-facts.md. Same local-part
on another domain is not an alias. Distinct MailPlus accounts are separate
User mailboxes. Password on services /mail. Do not invent extra-domain
MX flip, DMARC p=reject, or Vaultwarden for this slice.
Laptop Namecheap custody. Not the VPS. No IPs or tokens here.
Primary surmount.systems public MX is live (10 mail.surmount.systems;
EmailType MX; SPF a:mail.surmount.systems -all; public _dmarc
p=quarantine). Extra-zone API worked this slice (Baxter getHosts
11 records). cryptoquick.com public MX is this host (Custom MX).
baxterartworks.com MX still parked FWD. No reboot.
Primary Custom MX leftover is closed. --live set-mx still is not
a public MX flip while Email Forwarding is on (EmailType FWD fails
closed). While Email Forwarding is on, Namecheap publishes eforward MX
and drops custom MX from getHosts.
- Cryptoquick HTTPS is a certificate hostname mismatch, not SERVFAIL.
Live 2026-09-07: leftover parent DS key tag 2368 is gone. Validating
A for
cryptoquick.comandwww.cryptoquick.comsucceeds (AD true). The old wait-for-SERVFAIL gate is closed. Live production leaf still has 18 names and still omits those two. Intended leaf is 20 names on one PEM (with_single_cert). After the tree lists the two names, laptop--issueis operator-gated because Namecheap credentials and ClientIp are laptop custody. SHA-1 parent DS digest type 1 remains standing DNSSEC quality debt in operator-facts Monday leftover; it is not the HTTPS cause. Do not re-add 2368. Do not invent leftover Namecheap clicks. Path: docs/DNS.md Best DNSSEC we can actually run. baxterartworks.comCustom MX only if the operator asks. EmailType is still FWD;--live set-mxfail-closed. Public MX still eforward1-5. Do not claim MX flipped.- Public NS republish/lag for Baxter getHosts vs served zone.
_dmarc.baxterartworks.comintended isp=quarantine. Do not restore getHosts_dmarctop=none. getHosts already has dual DKIM, TLS-RPT, CAA, and the new SPF.1.1.1.12026-08-20 still NXDOMAIN for DKIM/_dmarc/TLS-RPT (public_dmarcmay stay NXDOMAIN while EmailType is FWD even when getHosts has the TXT), empty CAA, old eforward-only SPF, SOA serial 1787245654. Leftover_acme-challengeTXT still on getHosts. Extra MTA-STS wait.
Shipped in tree (do not re-list as next work): in-process rustls HTTPS,
Arti HS management-publish + package overlay, https+Arti auto local
cleartext backend, Onion-Location + Alt-Svc on Axum HTTPS (2026-08-17),
nginx default-off / dual-run escape, :80 redirect-only
bind, P1 Axum product edge pin (docs + free Stalwart :443 apply template
under nix/stalwart/ / /etc/surmount/stalwart/), ban decision first path +
helper add_ban/remove_ban/ping, Leptos SSR multi-page management
console (not skeleton), surface audit (no auto-ban on 404/501), live
Stalwart directory client (explicit opt-in), JMAP thin 501 honesty
contracts, account create/update mutations (auth + CSRF), create-mailbox
portal on /mail (review 0 open; leftover-cookie 403; NIP-98 query
fail-open closed), structured request logging, hermetic e2e-host
pure helpers. Details under What shipped.
Ranking (2026-08-11) after live free-443 + production multi-name HTTPS + durable PEMs (laptop custody) + offline harden H2-H7:
Host durability bottleneck (narrowed): production LE multi-name cert
(intended 20 certificate hostnames on one PEM; live 18 as of
2026-09-07: six extra static zones apex+www plus
surmount apex/www/mail/services/mta-sts plus mail.cryptoquick.com;
extra-vhost HTTPS live for those six; cryptoquick apex/www not
on the live leaf), durable Domain B PEMs,
dual-sign engine register, MTA-STS testing
policy HTTPS, durable recovery.env, and B4 Nostr gate are live
(2026-08-12; mail on leaf later 2026-08-18; extras on leaf 2026-08-20).
Apex is packaged
SurmountSystems/site (not UNDER CONSTRUCTION). Post-reboot proof PASS
(2026-08-13). Lockdown prove PASS (2026-08-13): public 22/80/443 are
sshd + Axum; Stalwart :8080 loopback; auth nostr; secrets mode 600; no
extra public listeners. Report:
.agents/reports/vps-lockdown-away-2026-08-13.md. Host ACME stays off.
Remaining host/operator now:
Primary hunter mailbox principal exists and is no longer empty. Living
map: ~/.agents/surmount-server/operator-facts.md. Vandelay 1.0.7 is on the
live host PATH (wrapper uses vandelay import maildir then
vandelay export, not stalwart-cli import). Primary hunter copy + import
finished. Primary public MX flipped 2026-08-20 (mail.surmount.systems;
EmailType MX). Public site is packaged
pkgs.surmount-public-site from flake input github:SurmountSystems/site
(live 2026-08-19 tip 1c84696; not UNDER CONSTRUCTION). Extra people imported from DS3018xs (ordinary User; mailbox passwords still operator). Extra-domain same-local-part User principal created 2026-08-22. MailPlus copy/import for that mailbox is still not done; do not import into the primary hunter uid. Other unnamed uids deferred. Extra-domain Custom MX only if asked, leftover SHA-1 parent DS digest type 1 in operator-facts Monday leftover (not the HTTPS cause; leftover DS 2368 is gone), optional S7b + /vault/ with
operator OK. Do not claim live VW.
| Bucket | Highest-value next for parallel agents |
|---|---|
| Ship (offline) | Earn-trust offline drivers already shipped. Do not re-ship register-dkim / laptop-renew-cert / packaged SurmountSystems/site (not UNDER CONSTRUCTION) / recoveryAdminEnvFile defaults. Do not invent Q-AUTH-1 / Q-PQC / Q-HOST / Q-SEC-SM; S5 parked; do not rebuild S7a |
| Park (offline) | H4 zero-cred-on-VPS DNS-01 redesign; full JMAP proxy + v1 webmail beyond 501; hydrate islands without clear SSR gap; Track C self-host DNS; D2 PQConnect product wire until human sibling commit; inventing any Q-*; S5 Rust/libsecret; live free-443 / live LE as CI green (forbidden); DMARC p=reject; extra-domain MX flip; import over AFP GVFS |
| Host-gated (highest value now) | This wave (host done): HTTPS store binary + remoteBuilder MemoryMax on nix-daemon.service; maxJobs memory-safe; Lake unit absent. Leftover: laptop system /etc/nix/machines only if a daemon-only Nix path is used later. Public site packaged from github:SurmountSystems/site (apex/www; live 2026-08-19 tip 1c84696). Primary hunter MailPlus copy + import done. Extra people imported as ordinary User (MX not flipped). Hunter aliases imported into hunter, not separate mailboxes. Extra-domain same-local-part is not those aliases. User principal created 2026-08-22. MailPlus copy/import for that mailbox is not done. Living map: ~/.agents/surmount-server/operator-facts.md. Mailbox passwords still operator on /mail. Dual-NAS AFP scripts shipped (DS1513 / DS3018xs; --afp-host IPv4 at runtime for reach only; NetworkPassword server is host id / <id>.local, never IPv4; paste wipe after store; live Secret Service remount later; DSM rename optional). Remaining unnamed MailPlus uids deferred (not 1028/1035). Extra mail-domain DNS is open under B6 (live 2026-09-07 leftover parent DS key tag 2368 is gone; validating A for cryptoquick apex/www succeeds; intended leaf is 20 names; live leaf still omits cryptoquick apex/www; laptop --issue after the tree lists those names is operator-gated; SHA-1 parent DS digest type 1 remains operator-facts Monday leftover and is not the HTTPS cause; do not re-add 2368; do not invent leftover Namecheap clicks; Baxter getHosts already has dual DKIM / TLS-RPT / CAA; _dmarc.baxterartworks.com intended p=quarantine; public _dmarc may stay NXDOMAIN while EmailType is FWD; do not restore getHosts _dmarc to p=none; Baxter Custom MX only if asked). Extra-zone API worked this slice. domain-audit extra mailbox checks landed separately. Primary public MX is mail.surmount.systems (Custom MX leftover closed). Baxter public MX stays parked FWD. Also: rDNS when shc-api staged (O2); S7b / /vault/ only with operator OK; D1-host hybrid; B2; B3 Arti; B5. Q-AUTH-1 product answers stay open. Do not invent extra-domain MX flip / DMARC p=reject / VW / ban / Q-AUTH-1 / reboot / a new SHC ticket as extra tracks. |
| Operator handoff | Stage/commit as needed so flake just check-ci is green (agents never stage); track modules/vaultwarden.nix when committing (H0). Host just check still sees the working tree. Human owns sibling pqconnect flake commit before D2 wire-up |
Shipped offline (2026-08-08, Track D): D1-host runbook (deploy-host-local
section 6) + nix run .#surmount-tls-hybrid (BLOCKED exit 2 when BASE_URL unset)
- hermetic crate tests (PATH-mock classical exit 1 /
MLKEM exit 0) + just recipes; GHA quality step for hybrid self-test only;
dual-pin OPS/EDGE/SECURITY/deploy-host-local/COMPACTION-PIN; D2 integration
prep in
docs/research/pqconnect-local-packaging.md. Not live host hybrid, flake path input to uncommitted sibling, real switch, PEMs, Arti publish, or ban drop.
Shipped offline (2026-08-08, residual pass): D1 prefer-post-quantum +
hermetic unit tests for aws-lc-rs hybrid group include/prefer-first; GHA runs
crate tests for private-data + deploy-host; just
recipes; COMPACTION-PIN / EDGE / SECURITY / deploy-host-local dual-pins.
Not host hybrid negotiation, real switch, PEMs, Arti publish, or ban drop.
Shipped offline (2026-08-08, Track A): host-local contract doc;
nix run .#surmount-deploy-host + hermetic crate tests; dual-pins
OPS/hygiene/SECRETS/SECURITY/EDGE; sample host comment pointers; security
ladder B0-B7 + D1 honesty docs; activation hang workaround documented.
Shipped offline prior (2026-08-07): account create/update mutations
(directory trait + mock + Stalwart x:Account/set wire-mock; auth gate + lab
escape; CSRF on cookie POSTs); structured http_request logging with onion
redaction (no secret paths/tokens/nsec). 2026-08-13: TLS handshake /
accept failures emit tls_handshake_failed at warn (peer, SNI if known,
short error; no PEM). http_request also records Host and User-Agent
(truncated). Logs stay stdout -> journald for surmount-management-ui.
No HTTP log dump, no world-readable /var/log / /var/lib/surmount access
file. Safari / iPhone diagnosis: journalctl -u surmount-management-ui -g tls_handshake_failed while retrying the phone. Mutation e2e hermetic anchors in
HERMETIC_ANCHORS: account_create_auth_off_fail_closed_without_lab_escape,
account_create_cookie_auth_requires_csrf (also covers PATCH+CSRF),
account_create_lab_escape_mock_succeeds,
account_create_unavailable_directory_service_unavailable. Local green !=
public cutover / host Arti / live ban drop.
Tree defaults already support product https UI with web off. Host proof is
still residual (just e2e-host). Do not claim live public cutover or host
MemoryDenyWriteExecute (no writable+executable memory) done from eval or
local e2e alone. Do not call that hardening "unsafe" without a failed run.
Acceptance (host; automate via
SURMOUNT_E2E_HOST=1 SURMOUNT_E2E_BASE_URL=... SURMOUNT_E2E_LAB_IP=... just e2e-host,
or SKIP_BAN=1 when omitting ban track):
- Public :443 serves surmount-manage after free-443 live (2026-08-11); Stalwart not on :443. Still residual: reboot/switch without host-local ACME-off fragment; firewall durable across switch
- Free production :80 binds product redirect-only listener when enabled (operator free-:80 assumption; not ACME-on-product-:80); live :80 was management-ui HTTP redirect after free-443 (confirm durable after switch)
- Certificate and key files on host only under durable Domain B
/var/lib/surmount/secrets/tls/(cert 0640surmount-ui:surmount-tls, key 0600surmount-uiowner-only; never in git; 2026-08-11 laptop production issue + secrets-install; 2026-08-27 key 0600). Still residual: host-local ACME-off so next switch does not depend only on runtime drop-in -
systemctl show surmount-management-ui -p MemoryDenyWriteExecute=> yes - TLS health:
https://services.surmount.systems/health200 without-k; LE production. Live leaf as of 2026-09-07 is 18 names (six extra static zones apex+www plus surmount apex/www/mail/services/mta-sts plusmail.cryptoquick.com). Extra-vhost HTTPS live for those six. Intended leaf is 20 names on one PEM. Cryptoquick apex/www are not on the live leaf. HTTPS on those two names fails at certificate hostname mismatch. Validating A succeeds (AD true). Leftover parent DS key tag 2368 is gone. Reboot-safe proof still open - nginx not product edge when web off
- Cert renew laptop path:
just laptop-renew-cert -- --check|--live --directory production(issue only if due; stages tls-cert/tls-key, installs cert 0640 / key 0600, restarts UI, proves health + IMAP). Laptop user timer--install-timer --target ...(without --target is BLOCKED). Host ACME stays off. ACME-on-Axum-:80 (HTTP-01) still parked; UI still needs a restart after PEM replace (hot-reload residual) - Optional: remove
modules/web.nixonly after dual-run unused
Module + package overlay shipped. Local optional Tor row (just e2e) may
publish with temp keys when arti + client exist; that does not close
host residual.
Still residual (host):
- Live Tor network verification (unit active != published; Tor Browser purple pill BLOCKED 2026-08-17; clearnet Onion-Location / Alt-Svc is not an Alt-Svc upgrade proof)
- Durable HS dir on host, owned/writable by
surmount-arti(not/run); identity generated on first start; never in git - Operator offline backup of that HS identity
- Cleartext local backend when managementUi is https-only (tree: auto loopback cleartext API + Arti backend)
- Onion-Location + Alt-Svc on mapped HTTPS 2xx/3xx;
onion_discoveryonGET /api/v1/system; no hot-reload (restart after hostname/env/map) - systemd hardening parity in the public module after real
arti proxy(host-localHOME=/var/lib/surmount/artionly) - Q-ARTI-2 / Q-ARTI-3 not invented; admin/JMAP onion stanzas still off
- Surface onion URL to operator (hostname file + SSR + system dump + discovery headers). Do not log onion in failure tails
Tree acceptance (hermetic; covered by just e2e):
-
surmount-nft-ban-helperbinary + narrowadd_ban/remove_ban/ping - UI Unix-socket client
SURMOUNT_BAN_NFT_HELPER_SOCK; no CAP_NET_ADMIN on UI - Socket-activated oneshot unit holds CAP_NET_ADMIN/RAW (not child setcap;
NNP-safe). Mutually exclusive with
nftExec - DryRun does not mutate kernel firewall sets; Enforce apply-first fail-closed
- Hermetic tests (RecordingNftExec + fake script + Unix socket); no root in CI
- Decision layer contracts preserved (Allow/Whitelist/RateLimited/Banned)
- Auth-failure BanCandidate stub shipped (
decide_ban_signal/BanGuard::signal_unauthorized+ request-context hook; whitelist immune; Off/DryRun/Enforce). Not full Q-ACL surface list / Nostr login. - Lab
remove_ban(delete-element argv; absent element = ok); e2e-host cleanup guidance points at helper CLI
Still residual (not claimed done; host just e2e-host):
- Live host: enable
backend=nft+enforcement=enforce+nftHelper+ sets; prove ban IP lands insurmount-ban4/surmount-ban6and is dropped - Crash-window recovery: apply ok then die before durable persist; re-signal treats already-present element as success (EEXIST / "File exists"); tiny residual if other race messages appear on host
- Q-ACL-1..6 policy answers (do not invent)
- Which HTTP/mail surfaces must call
signal_unauthorized(Q-ACL-1); tree audit only: 404/501 do not auto-call the hook today - Replace transitional fail2ban sshd when Rust path covers SSH
- Multi-page SSR console: overview, domains, accounts, system, mail (DOGE; honest empty accounts; config domain inventory; live Stalwart probe)
- Directory trait + hermetic mock (
source: unavailabledefault; labeledmockfixture for tests /SURMOUNT_DIRECTORY=mockonly) - Live Stalwart directory client (2026-08-07):
SURMOUNT_DIRECTORY=stalwart+ host token (SURMOUNT_STALWART_TOKEN/TOKEN_FILE); management JMAPx:Account/query+get; hermetic wire-mock + fail-closed empty on error; NixmanagementUi.directory/stalwartTokenPath(default unavailable; no default-on fake accounts). Domains stay config inventory. - Operator UX pass (2026-08-10): Overview health-first dashboard +
compact Attention (product language; no env primary residual for
auth/directory/onion/vault on Overview). System holds technical residual.
DOGE craft pass (type scale, chip semantics ok/fail/warn/neutral).
Contract:
overview_operator_language_no_env_primary_residual. Living: docs/SEARCH_AND_UI.md Phase 2 + UX principles; follow-on backlog.agents/reports/ux-residual-follow-on.md. - Synology-style Overview package home (2026-08-10): DSM-like tiles +
health strip + compact alert chips; removed lede/Hostnames/Navigate/
residual essays from home. Hostnames + enable paths on System. Same
honesty contract (rewritten for tile labels Off/Ready/Missing, Off/Linked).
Report:
.agents/reports/impl-synology-dashboard.md. - Deeper UX residual (not this slice): design-system/token extract, a11y/ mobile, auth bootstrap product UX when Q-AUTH-1 answers land, webmail when JMAP proxy, live host cutover polish when B1/B3 green. Ranked in ux-residual-follow-on report.
- Leptos hydrate islands only where a clear SSR gap needs them (none forced this pass; DOGE SSR primary; no NPM)
- Nostr auth foundation (mode/allowlist/NIP-98/session cookie; rust-nostr); e2e hermetic anchors shipped for off/gate/NIP-98 session + surface audit
- Create-mailbox portal on
/mail(2026-08-14): optional npub; Administrator vs User; map/var/lib/surmount/console/accounts.json(SURMOUNT_CONSOLE_ACCOUNTS). Review 0 open. Leftover cookie / role None password 403; NIP-98 query mismatch fail-open closed; map load fail-closed; flock across RMW; nsec not echoed;ok: falsewhenconsole_savedis false. Attach npub on/mailGrant console login shipped 2026-08-20 (bech32, CSRF, map write without listing). Contributor self-password + NWC (2026-08-20): User PATCH own mailbox password; Administrator still any (support). User/mailis self-serve + Evolution facts, not create. Optional NWC URI at Domain B/var/lib/surmount/secrets/ui/nwc.json(save/clear only; no live pay path). Hunter IMAP-only until Admin pastes a real npub./accountsstill has no create form. - Q-AUTH-1 residual: key-loss, durable session store, first-operator bootstrap UX (do not invent)
- JMAP thin 501 honesty + hermetic route contracts (2026-08-07): body code,
no invented methodResponses, mail SSR residual copy, no
/webmailproduct - JMAP authenticated proxy + v1 webmail UI beyond 501 (parked depth)
- Hardware / host deploy next (operator; not invented here)
| Id | Why |
|---|---|
| Q-HOST-1 / Q-HOST-2 | Provider / LUKS day-one |
| Q-EDGE-1 / Q-CA-* | Shared durable Let's Encrypt PEMs for mail + web is the offline product path (File Certificate + surmount-tls). Live leaf still last proven without mail; host apply still residual. Public CA is Let's Encrypt production. |
| Q-ARTI-2 / Q-ARTI-3 | Onion surfaces (lean defaults in module) |
| Q-DEP-1 | Deploy-secrets tool long-term |
| Q-ACL-* | Full ban policy |
Local green is never cutover. Keep offline agent work and host-gated work in separate buckets. Do not soft-elevate host rows from hermetic e2e.
Shipped or shippable on the developer machine / CI. Prove with just e2e /
just check / hermetic cargo. Examples:
| Slice | Status |
|---|---|
| Nostr auth foundation + hermetic e2e anchors (off/gate/NIP-98 session) | Shipped (Q-AUTH-1 product answers still open) |
| Directory trait + hermetic mock | Shipped |
| Live Stalwart directory client (JMAP + hermetic wire-mock + Nix opts) | Shipped 2026-08-07 (default still unavailable; host token when enabling live; no cutover claim) |
Create-mailbox portal on /mail + optional npub + two roles + map |
Shipped 2026-08-14 (review 0 open; leftover-cookie / role None password 403; NIP-98 query fail-open closed; map load fail-closed; flock RMW; nsec not echoed; ok: false when console_saved false). Attach npub 2026-08-20: Grant console login binds npub1... to an existing mailbox (session CSRF; listing optional). Self-password + NWC 2026-08-20: User sets own IMAP password after login; NWC URI save/clear off git; no Lightning node. Q-AUTH-1 still open. MX still parked. Hunter IMAP-only until Admin pastes a real npub |
| :80 redirect-only wiring + dual-run mutex | Shipped (host public bind still residual) |
| Production free-:80 docs pin (redirect-only; not ACME invent) | Pinned 2026-08-02 |
| CSP / CSRF / session hardening beyond scaffold | Residual product when other agents touch auth/UI; not host-gated |
| JMAP thin 501 honesty + route contracts | Shipped / locked 2026-08-07 (not full proxy) |
| JMAP authenticated proxy + v1 webmail UI beyond 501 | Residual offline-capable (parked depth; do not invent) |
| Hydrate islands | Parked until a clear SSR gap; DOGE SSR primary; no NPM |
| Optional local Tor deep row when arti + Tor client on PATH | Local only; != host surmount-arti ownership |
| Host-local contract + deploy driver + self-test | Shipped 2026-08-08 (not host cutover; not flake remote switch) |
| GHA hermetic deploy + private-data + hybrid-probe + secrets-install self-tests | Shipped 2026-08-08 (+ secrets gate 2026-08-09; quality job; not checks.ci; not live hybrid/keyring/SSH install) |
| Security ladder B0-B7 docs | Shipped 2026-08-08 (host stages still gated) |
| D1 hybrid provider config + hermetic unit proof | Shipped 2026-08-08 (prefer-post-quantum) |
| D1-host hybrid probe script + runbook | Shipped offline 2026-08-08 (live negotiation still host residual after B1) |
| D2 PQConnect integration prep docs | Shipped offline 2026-08-08 (no flake wire; product wire still gated) |
| Q-AUTH-1 / Q-ACL / Q-ARTI / Q-EDGE / Q-PQC answers | Do not invent; park |
| Track C self-host DNS / D2 PQConnect product wire-up | Parked until operator rank + human sibling commit |
| Untracked / dirty tree stage handoff | Operator stage/commit so flake just check-ci sees intended files (agents never stage). Host just check / cargo test use the working tree |
Require real deploy + SURMOUNT_E2E_HOST=1 / operator secrets. Agents without
a host stop at docs, tree wiring, and local e2e. Ladder detail:
docs/deploy-host-local.md section 6.
- B0 + deploy driver on host: private host-local (keys non-empty),
nix run .#surmount-deploy-hostor manual rsync +nixos-rebuild switch --flake .#mail-vps; smokesystemctl is-active - B1 Public HTTPS cutover +
just e2e-host(PEMs, MemoryDenyWriteExecute, public :443; free :80 redirect-only bind when enabled) - D1-host hybrid negotiation probe (
SURMOUNT_E2E_BASE_URL=... nix run .#surmount-tls-hybrid); document negotiated group - B2
requireDeployMaterialonce PEMs (and later Arti paths) exist - B3 Arti live Tor verify + operator HS identity backup (unit active != published; discovery headers and live unit do not close B3)
- B4 production Nostr auth on host: live gated (2026-08-12)
(anon login/401 + health 200). Apex/www packaged site live 2026-08-19
(GitHub tip
1c84696). Q-AUTH-1 still open (durable session store, key-loss, first-operator bootstrap UX) - B5 Ban enforce lab (sets + membership + traffic drop proof; cleanup via
surmount-nft-ban-helper remove-ban <ip>) - B6 mail earn-trust DNS/TLS (docs/DNS.md); Track C not required
- B7 fail2ban shrink only after B5 solid
- Cert renew path for chosen CA flow (Q-EDGE-1 / Q-CA-*); ACME-on-product-:80 still parked
- Delete
web.nixonly when dual-run unused and explicit operator OK (checklist: docs/OPS.md) - D2 PQConnect only after human sibling commit + Q-PQC answers
- Nix ssh-ng remote builder on surmount-1 (host MemoryMax live;
Lake unit default off).
surmount.remoteBuilder+ niced stdio + sliceMemoryMaxare switched.nixbuilderis key-only.surmount.lakeexists in tree, enable stays false. Do not start Lake from an agent. Leftover: laptop system/etc/nix/machinesplus systembuilders=need an operator sudo TTY (agent denied). User machines file is already wired. Machines slots = live guest max-jobs, not laptop inxi. SHC ticket 261 (closed 2026-08-18). Not NixOps. Mail and builder work share the host. Dev builds max-nice; critical units stay normal priority. Agents never reboot. Do not invent MX / DMARC / VW / ban / Q-AUTH-1 / reboot / a new SHC ticket.
- Version currency audit vs network latest (Stalwart family at tip;
crane rustc 1.88 vs stable 1.97.1 noted in pre-26.05 audit; living
crane is 1.95 via
rustPackages_1_95, see docs/COMPACTION-PIN.md). Snapshot: docs/research/version-audit.md - Arti packaging currency: Surmount-owned 2.5.1 source build
(
nix/packages/arti-onion-service.nix, GitLabarti-v2.5.1) +onion-service-service; rustc vianixpkgs-rust(MSRV 1.91+); not full OS channel bump. Eval contract locks version 2.5.1 (matches nixpkgs-rustarticargoDeps; crates.io max_stable 2.5.1 as of 2026-08-27). Live Tor verify still residual - COMPACTION-PIN dual-pin: local cleartext API for https+Arti shipped;
remove_ban/ lab unban shipped; residual host Tor/ban/cutover unchanged - Dual-run nginx unused detection checklist in OPS (no
web.nixdelete) - Local validation:
nix run .#e2e/just e2e; pure helpers viacargo test -p surmount-e2e --lib; host e2e without env expected exit 2 - E2E SoT rewrite: Rust
crates/surmount-e2e+ flakeapps.e2e/apps.e2e-host; bashscripts/e2e-*.shpure lib retired - Production free-:80 pin (docs dual-pin 2026-08-02; redirect-only)
- Optional local Tor deep row: still residual until built
artion PATH + Tor client + live verify (do not fake host ownership)
Do not invent Q-* answers (Q-ACL-1..6, Q-AUTH-1 Nostr, Q-ARTI-2/3 onion surfaces, ACME-on-product-:80) or claim host rows from local e2e alone.
nix run .#e2e
# or: just e2e
# local comprehensive end-to-end (hermetic); optional Tor when tools present
just check
# exit 0 -> host fmt --check + clippy -D warnings + cargo test
just check-ci
# exit 0 -> checks.<system>.ci (may be slow; includes e2e-pure-test, not host e2e)
# host only (exit 2 if SURMOUNT_E2E_HOST unset; BASE_URL required; LAB_IP unless SKIP_BAN):
SURMOUNT_E2E_HOST=1 \
SURMOUNT_E2E_BASE_URL=https://127.0.0.1 \
SURMOUNT_E2E_LAB_IP=203.0.113.50 \
nix run .#e2e-host
# or: just e2e-host
# ban_drop=UNPROVEN in summary means membership/preflight only, not live drop
# lab cleanup: surmount-nft-ban-helper remove-ban "$SURMOUNT_E2E_LAB_IP"
cd crates && cargo test && cargo clippy --all-targets -- -D warnings
# pure e2e helpers + host-gate contracts without VPS:
cargo test -p surmount-e2e --lib
# module-eval only:
nix build ".#checks.$(nix eval --impure --raw --expr 'builtins.currentSystem').module-eval-contract"