feat(hh): graceful shutdown, crypt default theme, neutralize branding, share-prep

- Graceful shutdown: Ctrl+C quits in chat (interrupts PTY while driving),
  RAII TermGuard + panic hook + SIGTERM/SIGHUP always restore the terminal
- Default theme is now "crypt" (neutral monochrome); theme sigil mirrored in
  chat/roster/help so the pentagram only renders under the "church" theme
- Neutralize inverted-pentagram branding across CLI, scripts, docs, and Cargo
  metadata (kept only in themes/church.toml + the render-time placeholder)
- Rewrite root README around hack-house; add bootstrap.sh, SECURITY.md,
  CODE_OF_CONDUCT.md, CHANGELOG.md, and issue/PR templates
- .gitignore cleanup; stop tracking .venv

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
leetcrypt
2026-05-31 23:23:19 -07:00
parent 5de493e895
commit ff5186a9d3
2748 changed files with 589 additions and 713161 deletions
+14 -14
View File
@@ -1,4 +1,4 @@
//! hack-house — encrypted collaborative sessions with a summoned sandbox.
//! hack-house — encrypted collaborative sessions with a summoned sandbox.
//!
//! This binary exposes the crypto-parity spike used to prove the Rust client
//! speaks the same SRP / Fernet dialect as the Python Sanic server, plus the
@@ -21,7 +21,7 @@ const STD: base64::engine::general_purpose::GeneralPurpose =
base64::engine::general_purpose::STANDARD;
#[derive(Parser)]
#[command(name = "hack-house", about = "encrypted collaborative sessions")]
#[command(name = "hack-house", about = "encrypted collaborative sessions")]
struct Cli {
#[command(subcommand)]
cmd: Cmd,
@@ -147,14 +147,14 @@ fn selftest() -> Result<()> {
v
});
let a = hex::encode(c.a_bytes());
println!("A = {}", &a[..32]);
println!("A = {}", &a[..32]);
let salt = hex::decode("0a1b2c3d")?;
let b = hex::decode("047426a55963c70bc385c6a51f6e9dc0bfe5e16b0d1fee4f566fb54b60fa77144f15ed1ee6ade007bd92f2b90846e1ee083ab4290239420606f48a1d861f759543d7856cbce21fd7fec98c9961a66610b412fea2efc5be78f35b18fd48176ac80c3a1cbefacac81e25e7da8079fac4012d01c47d85b783c2ea7340819bfe73d29cd0953d47c8fade77caa5459fb77d88fb918c073a77c495fa884859142a270cb0b1668de06131b150df4dbc931953a381710b7fdb98a953d6f77a4bba847c4c62c15cca8e514dc13f531427966a553c461aa4ab0caec9665612861fef03d48676e5f6551fc8ca4317f3118e0294c949bd2f5821e5900e7f695225dafa0ba2d2")?;
let ch = c.process_challenge(&salt, &b)?;
let want_m = "6e733ba88eb86c52e3be89207d2815a65b4dea8116f668af5de1b66ce1f047dd";
assert_eq!(hex::encode(&ch.m), want_m, "M mismatch vs pysrp");
println!("M matches pysrp golden vector ✓");
println!("selftest passed — Rust SRP ≡ Python srp");
println!("M matches pysrp golden vector ✓");
println!("selftest passed — Rust SRP ≡ Python srp");
Ok(())
}
@@ -192,7 +192,7 @@ fn handshake(
let b = STD.decode(init["B"].as_str().context("no B")?)?;
let salt = STD.decode(init["salt"].as_str().context("no salt")?)?;
let room_salt = STD.decode(init["room_salt"].as_str().context("no room_salt")?)?;
println!("/srp/init ok — user_id={}", &user_id[..8]);
println!("/srp/init ok — user_id={}", &user_id[..8]);
let ch = client.process_challenge(&salt, &b)?;
@@ -213,11 +213,11 @@ fn handshake(
let server_hamk = STD.decode(verify["H_AMK"].as_str().context("no H_AMK")?)?;
anyhow::ensure!(server_hamk == ch.h_amk, "server H_AMK mismatch — MITM?");
let ws_token = verify["ws_token"].as_str().context("no ws_token")?.to_string();
println!("/srp/verify ok — server identity proven (H_AMK ✓)");
println!("/srp/verify ok — server identity proven (H_AMK ✓)");
// Room key + encrypt a message the Python clients can read.
let fernet = crypto::room_fernet(password.as_bytes(), &room_salt)?;
let ct = fernet.encrypt(b"the house is open \xE2\x9B\xA7");
let ct = fernet.encrypt(b"the house is open");
// Connect WS and send the ciphertext.
let ws_scheme = if no_tls { "ws" } else { "wss" };
@@ -226,18 +226,18 @@ fn handshake(
);
let (mut sock, _resp) =
tungstenite::connect(&ws_url).context("ws connect (insecure wss not yet wired)")?;
println!("websocket attached to the house");
println!("websocket attached to the house");
// First frame is the `init` state snapshot.
if let Ok(msg) = sock.read() {
if let Ok(txt) = msg.into_text() {
let v: serde_json::Value = serde_json::from_str(&txt).unwrap_or_default();
println!("recv: type={}", v["type"].as_str().unwrap_or("?"));
println!("recv: type={}", v["type"].as_str().unwrap_or("?"));
}
}
sock.send(tungstenite::Message::Text(ct))?;
sock.flush()?;
println!("sent encrypted offering");
println!("sent encrypted offering");
// Read frames until we see our broadcast echo, then decrypt it to prove the
// full round-trip (Rust encrypt → server relay → Rust decrypt).
@@ -250,19 +250,19 @@ fn handshake(
match fernet.decrypt(ct) {
Ok(pt) => {
println!(
"round-trip ✓ decrypted: {:?}",
"round-trip ✓ decrypted: {:?}",
String::from_utf8_lossy(&pt)
);
break;
}
Err(_) => println!("[decrypt failed]"),
Err(_) => println!("[decrypt failed]"),
}
}
}
}
Ok(_) => {}
Err(e) => {
println!("ws read ended: {e}");
println!("ws read ended: {e}");
break;
}
}