mirror of
https://github.com/Such-Software/smirk-backend-core.git
synced 2026-10-17 07:21:32 +02:00
fix(operator): harden S3/S2/S4 per adversarial review
9 confirmed findings (review wbdf3c0sd), all fixed:
S4 (capabilities):
- REGRESSION: /capabilities reported grin_relay:false for a working relay (S4
wrongly coupled it to Grin chain serviceability). The relay is a non-custodial
mailbox — wallet broadcasts locally — so it is decoupled again. Removed the now-
dead grin_relay downgrade note.
S3 (bootstrap latch):
- A DELETE of the latch row escaped Locked without Tampered detection on a
CLI-bootstrapped-but-no-users instance (adoption keyed off `users` only). Boot
now adopts on has_any_users() OR has_any_admin_keys(), so a deleted latch
re-latches Locked instead of silently dropping to uninitialized.
- bootstrap_admin's single-bootstrap guard was a check-then-insert race (two
concurrent `setup` could seed two admins). It now takes the admin-keys mutation
advisory lock (shared with add/revoke/rotate) at the top of its tx.
S2 (admin posture + landing):
- Hard bind guard matched loopback by string prefix ("localhost".starts_with) —
"localhost.evil.com" slipped past. Now resolves via to_socket_addrs and requires
EVERY resolved addr to be loopback.
- host_allowed's IPv6 loopback check was an unanchored prefix ("[::1]" matched
"[::1].evil"); now anchored to exactly "[::1]" / "[::1]:port". +regression tests.
- Anti-clickjacking headers (X-Frame-Options/CSP) are now applied on the 403
Host-reject path too (computed on both branches, headers added once).
- GET /api/v1/server-info now returns a BARE 404 when landing is off (the
{error,code} envelope itself hinted the route exists).
- Public landing `chains` now uses the SAME serviceability predicate as
/capabilities (no over-advertising an unserviceable chain).
- Removed the dead/misleading PUBLIC_EXPOSE_FEATURES toggle (never read; the
feature tuple is intentionally never emitted anonymously).
Dismissed after verification: latch chains-by-flag (dup), title length bound
(operator-owned + escaped), prices:true with empty PRICES_ASSETS (documented).
140 lib + 140 integration green, no openapi drift, scans clean.
This commit is contained in:
+16
-6
@@ -46,8 +46,11 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
SetupState::Fresh => {
|
||||
// Adopt an existing deployment as already-bootstrapped (so a live
|
||||
// upgrade never exposes a setup window); a truly empty DB begins
|
||||
// uninitialized, awaiting `smirk-admin setup`.
|
||||
let adopt = db.has_any_users().await?;
|
||||
// uninitialized, awaiting `smirk-admin setup`. Adopt on EITHER
|
||||
// wallet users OR admin keys, so deleting the latch row on a
|
||||
// CLI-bootstrapped-but-no-users instance re-latches Locked rather
|
||||
// than silently downgrading to uninitialized.
|
||||
let adopt = db.has_any_users().await? || db.has_any_admin_keys().await?;
|
||||
db.init_server_config(secret, adopt).await?;
|
||||
if adopt {
|
||||
tracing::info!("existing deployment adopted: bootstrap latched (locked)");
|
||||
@@ -172,10 +175,17 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
if state.config.admin.enabled {
|
||||
let admin_addr = state.config.admin.bind.clone();
|
||||
// Fail-closed bind guard: refuse a non-loopback admin bind unless the
|
||||
// operator explicitly opts in (confidentiality is by socket).
|
||||
let is_loopback = admin_addr.starts_with("127.")
|
||||
|| admin_addr.starts_with("[::1]")
|
||||
|| admin_addr.starts_with("localhost");
|
||||
// operator explicitly opts in (confidentiality is by socket). Resolve the
|
||||
// address (so "localhost" works) and require EVERY resolved addr to be
|
||||
// loopback — a prefix check would accept "localhost.evil.com".
|
||||
use std::net::ToSocketAddrs;
|
||||
let is_loopback = match admin_addr.to_socket_addrs() {
|
||||
Ok(addrs) => {
|
||||
let addrs: Vec<_> = addrs.collect();
|
||||
!addrs.is_empty() && addrs.iter().all(|a| a.ip().is_loopback())
|
||||
}
|
||||
Err(_) => false,
|
||||
};
|
||||
if !is_loopback && !state.config.admin.allow_public_bind {
|
||||
return Err(format!(
|
||||
"ADMIN_BIND {admin_addr} is not loopback; set ADMIN_ALLOW_PUBLIC_BIND=true to override \
|
||||
|
||||
Reference in New Issue
Block a user