Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,30 @@ unless the subscriber is a member.
### Append-Only Audit Log

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

### Desktop Secret Storage — OS Keyring

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

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

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

### Input Validation

Expand Down
17 changes: 13 additions & 4 deletions desktop/scripts/check-file-sizes.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const overrides = new Map([
// harness-persona-sync: persona-runtime resolution threaded into the spawn
// path here. Load-bearing feature growth; queued to split in the resolver
// unify refactor followup.
["src-tauri/src/managed_agents/runtime.rs", 1966],
["src-tauri/src/managed_agents/runtime.rs", 1969],
["src-tauri/src/managed_agents/personas.rs", 1080],
["src-tauri/src/managed_agents/persona_card.rs", 1050],
// applyWorkspace reposDir parameter plus the validateReposDir binding,
Expand All @@ -53,11 +53,15 @@ const overrides = new Map([
// harness-persona-sync feature growth, queued to split in the resolver-unify
// refactor followup. discovery.rs is dominated by the new test module
// (the effective_agent_command / divergent / create-time override matrix);
// types.rs adds the persona/instance harness fields; migration_tests.rs adds
// the harness-sync migration coverage. Load-bearing, not generic debt.
// types.rs adds the persona/instance harness fields. Load-bearing, not
// generic debt.
["src-tauri/src/managed_agents/discovery.rs", 1043],
["src-tauri/src/managed_agents/types.rs", 1010],
["src-tauri/src/migration_tests.rs", 1033],
// migration_tests.rs carries the harness-sync migration coverage plus the
// patch_json_records owner-only writeback regression test (SECURITY.md:90
// crash-safe 0o600 fallback). Load-bearing security + feature coverage, not
// generic debt growth. Approved override; still queued to split.
["src-tauri/src/migration_tests.rs", 1063],
["src-tauri/src/nostr_convert.rs", 1126],
["src/shared/api/relayClientSession.ts", 1022],
["src-tauri/src/migration.rs", 1295],
Expand All @@ -70,6 +74,11 @@ const overrides = new Map([
// the Inbox nav badge — a small overage from load-bearing badge plumbing,
// not generic debt growth. Approved override; still queued to split.
["src/app/AppShell.tsx", 1008],
// PersistBackend enum + marker-on-keyring-success plumbing and its three
// fail-closed regression tests (silent identity rotation on keyring outage).
// A small overage from load-bearing security plumbing on a file already at
// 893 lines, not generic debt growth. Approved override; still queued to split.
["src-tauri/src/app_state.rs", 1012],
]);

await runFileSizeCheck({
Expand Down
1 change: 1 addition & 0 deletions desktop/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 9 additions & 1 deletion desktop/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@ name = "buzz_lib"
crate-type = ["staticlib", "cdylib", "rlib"]

[features]
default = []
default = ["system-keyring"]
mesh-llm = ["dep:mesh-llm-sdk", "dep:mesh-llm-host-runtime"]
# OS keyring backing for desktop secret storage (nsec private keys). When
# disabled, secrets fall back to 0o600 files. On by default for real builds.
system-keyring = ["dep:keyring"]

[build-dependencies]
serde = { version = "1", features = ["derive"] }
Expand All @@ -31,9 +34,14 @@ ctrlc = { version = "3", features = ["termination"] }

[target.'cfg(target_os = "macos")'.dependencies]
objc2-app-kit = { version = "0.3.2", default-features = false, features = ["NSHapticFeedback"] }
keyring = { version = "3.6.3", default-features = false, features = ["apple-native", "vendored"], optional = true }

[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.61", features = ["Win32_Storage_FileSystem", "Win32_System_JobObjects", "Win32_System_Threading", "Win32_Foundation"] }
keyring = { version = "3.6.3", default-features = false, features = ["windows-native", "vendored"], optional = true }

[target.'cfg(target_os = "linux")'.dependencies]
keyring = { version = "3.6.3", default-features = false, features = ["sync-secret-service", "vendored"], optional = true }

[dependencies]
atomic-write-file = "0.3"
Expand Down
Loading
Loading