Commit Graph
212 Commits
Author SHA1 Message Date
naturallaw777 61287dfece perf: speed up Hub service status loading 2026-08-11 13:35:59 -05:00
Arena Agentandnaturallaw777 679c7a039f Fix DDNS URL validation: replace ${IP} temporarily for validator, keep placeholder for storage 2026-08-11 11:47:32 -05:00
947c04834d Fix all 8 security hardening blockers for PR #423
Co-authored-by: naturallaw777 <99053422+naturallaw777@users.noreply.github.com>
2026-08-11 15:38:08 +00:00
a111de1ece Security hardening: fix all 8 blocking findings for PR #419
Fix 1: Update support.js to collect SSH public key and POST JSON
Fix 2: Legacy njalla.sh migration - parse safely, archive non-executable, replace cron with systemd timer
Fix 3: DDNS SSRF prevention - allowlist only njal.la, reject other hosts, disable curl redirects
Fix 4: Legacy root support-key removal migration (_remove_legacy_root_support_key)
Fix 5: Automatic support-key expiration (expires_at + _expire_support_if_stale)
Fix 6: Move security helpers to security_helpers.py, tests import production code
Fix 7: Real NIP-19/Bech32 npub validation (_bech32_decode + _validate_npub)
Fix 8: Replace journalctl sudo wildcard with restricted sovran-journal-helper.py
Also: Make _write_hub_overrides() atomic with tempfile+os.replace
94 tests passing

Co-authored-by: naturallaw777 <99053422+naturallaw777@users.noreply.github.com>
2026-08-11 12:07:18 +00:00
9b77b04741 Fix IP validation in DDNS and document journalctl sudo rule
Co-authored-by: naturallaw777 <99053422+naturallaw777@users.noreply.github.com>
2026-08-11 10:45:50 +00:00
f2ad9c1f17 Security hardening: fix DDNS injection, Nix injection, reboot auth, support key, sudo rules
Co-authored-by: naturallaw777 <99053422+naturallaw777@users.noreply.github.com>
2026-08-11 10:44:26 +00:00
naturallaw777 9df52256d4 fix: harden error handling and sanitize exception details across security endpoints (CWE-209) 2026-08-07 15:36:44 -05:00
naturallaw777 9051aed737 fix: sanitize exception handling in verify-integrity and security-reset (CWE-209) 2026-08-07 14:58:24 -05:00
naturallaw777 899f570ded fix: sanitize api_security_reset errors to prevent exception information exposure (CWE-209) 2026-08-07 14:31:38 -05:00
naturallaw777 56db634900 fix: use canonical prefix containment check for CodeQL path-injection 2026-08-07 14:22:44 -05:00
naturallaw777 3522270373 fix: pass sanitized abs_path to os.chown to resolve CodeQL path injection at 4338 2026-08-07 14:17:34 -05:00
a32b353eda fix: resolve merge conflict with origin/main in server.py
Co-authored-by: naturallaw777 <99053422+naturallaw777@users.noreply.github.com>
2026-08-07 18:50:01 +00:00
naturallaw777 4555764d87 fix: add CodeQL-recognized path sanitization for domain_name 2026-08-07 13:44:26 -05:00
naturallaw777 5130076400 fix: add CodeQL-recognized path sanitization for domain_name 2026-08-07 13:33:55 -05:00
naturallaw777 f0a640519d fix: remove domain substring check for CodeQL incomplete-url 2026-08-07 13:26:41 -05:00
naturallaw777 a14ef03d40 fix: use sentinel for njalla header check (CodeQL incomplete-url-substring) 2026-08-07 13:07:06 -05:00
naturallaw777 1ecf245a07 fix: prevent reflected XSS in lnurl-qr print endpoint 2026-08-07 12:59:08 -05:00
Contributorandnaturallaw777 8a766181de fix: hash root password instead of storing in clear text (CWE-312)
- Replace plain-text write of new_root_password in api_security_reset()
  with scrypt-hashed storage via _hash_password(), matching how the free
  password is already handled.

- Return new_root_password in the API response so the user sees it once
  before it is irreversibly hashed on disk.

- Teach _resolve_credential() to detect scrypt hashes and display a
  human-readable placeholder instead of raw hex in the Hub credentials UI.

- Harden root-password-setup systemd service: if the secrets file already
  contains a hash, skip chpasswd so a manual restart never sets the hash
  as the literal login password.
2026-08-07 12:34:13 -05:00
naturallaw777 592f2bd12f fix: separate web auth hash from system password file
- Add FREE_PASSWORD_FILE_WEB for scrypt hashes
- Legacy fallback + auto-migrate in _check_password
- chpasswd sync in api_change_password and security reset endpoint
2026-08-07 12:04:07 -05:00
Sovran Systems 3010557064 Fix Matrix Hub admin API credentials 2026-08-07 16:09:13 +00:00
942da64332 Fix CWE-78: replace subprocess call with Synapse Admin API in create-user endpoint
Co-authored-by: naturallaw777 <99053422+naturallaw777@users.noreply.github.com>
2026-08-07 15:09:36 +00:00
naturallaw777andarena-agent 3f74388359 Clarify Zeus NWC wallet setup
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
2026-07-30 22:36:16 +00:00
naturallaw777andarena-agent 2015a3ecd4 Reload Caddy immediately when a domain (or ACME email) is saved
The Caddyfile is generated at runtime by caddy-generate-config.service
from /var/lib/domains/*, but the generator only re-runs when caddy.service
starts fresh. Saving a domain while Caddy is already running therefore
never seats the new virtual host — no proxying and no ACME cert — and the
Hub's reachability check shows a misleading 'ports 80/443' router error
until the next reboot or rebuild.

api_domains_set and api_domains_set_email now restart the generator and
reload Caddy (ExecReload: caddy reload --force, no dropped connections)
right after saving. Entirely skipped when Caddy is inactive — e.g. Node
role before its first domain-based service is enabled — because the
rebuild that enables the service starts caddy.service for the first time,
runs the generator first (requiredBy), and seats the already-saved domain
on its own. Best-effort throughout: a domain save never fails because of
a Caddy reload issue.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
2026-07-30 16:50:32 +00:00
naturallaw777andarena-agent a8ff366992 fix(hub): resolve 'vdev' version badge and align it under the Hub title
- The Hub header badge could render 'vdev' because the runtime
  /etc/nixos/VERSION lookup fell back to the literal string 'dev' when
  the file was missing (e.g. dev/test environments, or before the
  Nix-generated config carried a version at all).
- modules/core/sovran-hub.nix now reads the repo's VERSION file at
  Nix eval time and bakes a real semantic version (sovran_version)
  into the generated config.json and a VERSION file shipped with the
  package, so the Hub always has a solid value to display.
- server.py's _get_sovran_version() now reads that baked-in
  sovran_version first, and explicitly rejects a literal 'dev' value
  from any of its file-based fallbacks so the badge never shows
  'vdev' again.
- templates/index.html + header.css: wrapped the title and the
  version badge in a '.title-group' column so the version badge sits
  directly underneath 'Sovran_SystemsOS Hub', left-aligned with it.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
2026-07-29 16:06:49 +00:00
naturallaw777andarena-agent c54823ff68 feat: automated stable releases, versioned ISOs, and Hub version badge
- Add release-stable.sh script with automatic tagging, CHANGELOG updates,
  GitHub/Gitea release creation, and VERSION file management
- Update iso/common.nix to include version from VERSION file in ISO filename
- Add VERSION file (current: 1.0.3)
- Polish OS version badge in Sovran Hub header (top-right)
- Update README.md download link to versioned ISO
- Update CHANGELOG.md with existing tags (v1.0.0 – v1.0.3)

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
2026-07-29 15:11:59 +00:00
naturallaw777andarena-agent 199bde0bc6 Show deployed PHP app versions in service titles
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
2026-07-29 14:39:21 +00:00
naturallaw777andarena-agent dec0aebc95 Introduce build-time version reference file to make versioning instantaneous and solid
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
2026-07-29 14:10:55 +00:00
naturallaw777andarena-agent 4210ba899f Improve service version details and naming
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
2026-07-29 12:18:33 +00:00
Sovran SystemsandGitHub 037a3a00af Merge pull request #354 from naturallaw777/arena/019fabb1-sovran-systemsos
security: harden Lightning Wallet Connections (NWC)
2026-07-28 21:54:23 -05:00
naturallaw777andarena-agent 2e2a9b2d44 security: harden Lightning Wallet Connections (NWC)
- Add rate limiting to public LNURL endpoints (30 req/min per IP)
- Add audit logging for wallet lifecycle events (create, drain, delete, rotate)
- Add Unix socket support for Python ↔ Alby Hub communication
- Add LND macaroon permission documentation/warning
- Add pairing secret rotation API endpoint + CLI command
- Make Nostr relay configurable; auto-use Haven relay when enabled
- Strengthen domain validation (FQDN only, reject localhost/IP)
- Add structured audit log at /var/log/sovran-nwc-audit.log

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
2026-07-29 02:52:45 +00:00
naturallaw777andarena-agent 43764e8c8d feat: display version numbers on all Hub service tiles
Previously only bitcoind.service showed a version on the tile.
Now every service with a parsable Nix store path gets its version
displayed (electrs, btcpayserver, lnd, rtl, mempool, vaultwarden,
matrix-synapse, caddy, tor, etc.).

The existing _get_service_version() helper reads systemctl show
<unit> --property=ExecStart and extracts the version from the Nix
store path. Results are cached for 5 minutes per unit.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
2026-07-29 02:44:03 +00:00
naturallaw777andarena-agent aa4ffaa803 Rename NWC feature to Lightning Wallet Connections and split modal into tabs
Naming: user-facing 'Wallet Connections' -> 'Lightning Wallet Connections'
across the Hub, feature registry, tile, and NixOS modules. Internal ids
(nwc-wallets, albyhub.service, /api/nwc/*) are unchanged.

UX: the service-detail modal put status, domain diagnostics, router ports,
the enable/disable toggle, restart, the liquidity guide and the whole wallet
manager in one cramped scrolling column. For this feature the modal is now
980px wide and split into two tabs:

  - Wallets: wallet grid, create/share/verify flows, collapsible liquidity guide
  - Service & Setup: description, status, domain checklist, ports, enable, restart

A status dot and domain chip sit in the tab bar so state is visible from both
tabs, and the modal opens on Setup when the service is off or the Lightning
Address domain is unconfigured. Wallet cards gain a balance chip, pending
badge, a prominent address row, and separated destructive actions.

Non-NWC services keep the original single-column layout and width.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
2026-07-29 01:59:28 +00:00
naturallaw777andarena-agent 5e3432aa89 nwc: add downloadable/printable LNURL QR for wallet connections
Each NWC wallet connection gets its own shareable LNURL QR so the owner
can receive payments from anyone without creating invoices (the core
LNURL value proposition: a static QR that never expires).

Hub UI (service detail modal):
- New 'Share QR' button on every wallet connection card
- Dedicated share view: large QR, Lightning Address + copy, raw bech32
  LNURL + copy
- Download PNG (websites/social), download SVG (vector, print-sharp at
  any size), and a Print button that opens a print-ready payment card
  which auto-invokes the browser print dialog

Backend (FastAPI, session-authenticated):
- GET /api/nwc/wallets/{id}/lnurl -> alias, lightning_address, LNURL
- GET /api/nwc/wallets/{id}/lnurl-qr.png (?download=1, ?scale=)
- GET /api/nwc/wallets/{id}/lnurl-qr.svg (?download=1)
- GET /api/nwc/wallets/{id}/lnurl-qr/print (print-ready HTML card)
- Stdlib bech32 encoder (BIP-173, verified against the official LUD-01
  golden vector); LNURL is uppercased for QR alphanumeric-mode density
  per LUD-01 guidance; QR rendered with the existing qrencode package
- Resolves wallets by app id or pubkey; 404 unknown wallet, 503 when no
  Lightning domain is configured; all routes return 401 unauthenticated

No new system dependencies.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
2026-07-28 23:47:37 +00:00
naturallaw777andarena-agent 419680a847 Port-forward UX: drop tile Step 4 and misleading local 'ready' status
The Element Call tile still appended a synthetic 'Step 4: Router Setup
Needed' to the domain diagnostic checklist, and every port table carried a
'Sovran_SystemsOS Status' column with Ready / Not ready yet verdicts.

Both were misleading: port forwarding happens on the router, which this
computer cannot inspect. A local ss/firewall probe can neither prove nor
disprove that forwarding works — and the LiveKit TURN relay range binds on
demand, so it reported 'Not ready yet' even on a perfectly working system.

- server.py: add ROUTER_FORWARD_ONLY_UNITS ({livekit.service}); skip the
  local probe for those units, drop the step-4 append, replace extra_ports
  with router_ports (no status field), and exclude router-only ports from
  both tile health and /api/ports/health so they can't raise false alarms.
- helpers.js: new shared renderPortForwardGuideHtml() — one intro naming the
  internal IP, explicit instructions (same internal/external port, match the
  protocol, use port-range fields for 30000-40000), a colour-coded
  TCP / UDP / TCP+UDP badge per row, and a closing note that the only real
  test is loading the service from a phone on mobile data.
- features.js: enable-time modal uses the shared guide and now always lists
  every port to forward (the old local pre-filter hid ports the user still
  had to open).
- service-detail.js: tile port section uses the same guide; the SSH/non-domain
  branch keeps a small 'not open on this computer yet' hint, which is a real
  local fact, separate from router forwarding.
- onboarding.js: step 3 router note reworded to match (same number for
  internal/external, notes that Element Call adds UDP ports).
- domain-setup.css: styles for the protocol badges and instruction list.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
2026-07-28 22:34:39 +00:00
naturallaw777andarena-agent a65d977489 Simplify port-forward UX (drop onboarding step 4) + run Njal.la DDNS on feature enable
- Onboarding: remove the redundant/error-prone 'Router Setup' step (5 steps -> 4).
  A compact 80/443 (+22 SSH) note now lives inside Domain Configuration,
  and the Element Call ports are only shown at the moment they matter:
  when enabling the feature, and afterwards on the service tile.
- Onboarding step 3: fix domain prefill bug (API returns {domains: {...}}),
  make /api/network fetch best-effort so it can never block the step.
- Enable-time port modal: streamline copy (one intro + table + pointer to
  the tile's live status view).
- Element Call tile detail: replace 5 repetitive prose blocks with 2 compact
  notes around the live-status port table.
- Njal.la DDNS: run njalla.sh immediately when a DDNS-backed feature is
  enabled (previously only ran on domain save or the 15-min cron tick).
- Harden njalla.sh handling: create the base script (shebang + IP lookup)
  if missing before appending curl lines; invoke via bash explicitly.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
2026-07-28 21:38:07 +00:00
copilot-swe-agent[bot]andGitHub dc98a355e8 Fix redundant backslash escape in hostname regex character class 2026-07-27 11:41:44 +00:00
copilot-swe-agent[bot]andGitHub 154988bbeb Address review feedback: fix hostname regex, use CSS class for NWC warning banner, clarify test comment 2026-07-27 11:39:40 +00:00
copilot-swe-agent[bot]andGitHub 768f26027e Require unique hostname for Wallet Connections; add conflict validation and UI guidance 2026-07-27 11:36:02 +00:00
copilot-swe-agent[bot]andGitHub dcc6d9fc1d Fix critical Authorization header bug and CodeQL stack-trace exposure
- nwc_hub_manager.py: Fix Authorization header to use real ****** (was hardcoded to literal asterisks due to display redaction)
- server.py: Use exc.args[0] instead of str(exc) in NWC error handlers to prevent CodeQL stack-trace taint flow to HTTP responses; update albyhub.service description key
2026-07-27 03:13:28 +00:00
copilot-swe-agent[bot]andGitHub ccff377607 Replace Wallet Connections scaffolding with real Alby Hub/LND implementation
- Add nwc_hub_manager.py: AlbyHubManager with real Alby Hub API (setup, auth, CRUD, drain, delete, invoice)
- Add nwc_lnurl_service.py: dedicated loopback LNURL service on port 8181
- server.py: remove JSON scaffolding (state.json, fake invoice generator, fake NWC URI, LNURL routes); replace with real manager calls; update service maps to albyhub.service; remove LNURL auth-exempt paths
- nwc_wallet_cli.py: rewrite to use real AlbyHubManager instead of JSON state
- modules/nwc-wallets.nix: replace with albyhub user/service, nwc-lnurl service, LND macaroon, unlock-password generation
- modules/core/caddy.nix: proxy LNURL routes to port 8181 (dedicated service) instead of 8937 (Hub)
- modules/core/sovran-hub.nix: service tile points to albyhub.service
- docs/wallet-connections.md: document real architecture, Alby Hub pin/patches, backup sensitivity
- test_wallet_connections.py: replace scaffolding tests with 54 real manager tests using mocked Alby Hub
2026-07-27 03:10:13 +00:00
copilot-swe-agent[bot]andGitHub 0e94ae58fc Implement wallet connections service-detail modal CRUD flow 2026-07-27 02:22:45 +00:00
copilot-swe-agent[bot]andGitHub 9673f6733a feat: add hub-managed Wallet Connections scaffolding 2026-07-27 02:10:11 +00:00
copilot-swe-agent[bot]andGitHub 1a3c014183 feat: replace tar+DB+LND backup with ext4+rsync workflow
- Rewrite sovran-hub-backup.sh: rsync-based mirror to stable current/
  path, ext4 validation, no tar/pg_dump/mariadb-dump/LND orchestration,
  exit-24 nonfatal for /home, INCOMPLETE/BACKUP_COMPLETE markers, flock
- Update server.py: _is_supported_backup_fstype accepts only ext4
- Update support.js: require ext4, explain database limitations, update
  failure message from exFAT to ext4
- Update sovran-hub.nix: replace pkgs.gnutar with pkgs.rsync + pkgs.acl
- Rewrite test_manual_backup_workflow.py: 40 new tests covering rsync
  options, ext4 acceptance, exit-24 handling, no-delete, stable current/
  path, INCOMPLETE markers, behavioral rsync tests, and regressions
2026-07-20 22:45:04 +00:00
copilot-swe-agent[bot]andGitHub 9f917d69da Address code review: compute stderr_text only when chunks are present 2026-07-18 16:30:38 +00:00
copilot-swe-agent[bot]andGitHub 98f63fde73 Fix Manual Backup exit-code-127: add bash+gawk to service PATH, harden launcher 2026-07-18 16:29:10 +00:00
copilot-swe-agent[bot]andGitHub 994a5ca1cd Refine backup validation and manifest details 2026-07-17 17:00:32 +00:00
copilot-swe-agent[bot]andGitHub 9a76e71dd0 Address validation feedback for backup workflow 2026-07-17 16:58:34 +00:00
copilot-swe-agent[bot]andGitHub 08f650cc35 Implement reliable exFAT manual backup workflow 2026-07-17 16:56:08 +00:00
copilot-swe-agent[bot]andGitHub dcd699d31e fix: address code review feedback
- Use set -eu (not set -euf) in sovran-hosts-update.sh
- Add sync note for domain validation regex between shell and Python
- Rename `seen` to `unique_addresses` in _resolve_all_addresses
- Improve loopback override UI message with external DNS check guidance
- Add docstring note about first-address display in _resolve_all_addresses"
2026-07-15 15:26:43 +00:00
copilot-swe-agent[bot]andGitHub 5b3a6c4be8 feat: server-side loopback overrides and Hub diagnostic fixes
- Add modules/core/local-domain-loopback.nix: systemd service and
  activation script that write configured service domains to a
  Sovran-managed block in /etc/hosts (127.0.0.1 / ::1) so requests
  originating on this computer reach Caddy without NAT loopback.

- Import local-domain-loopback.nix in modules/modules.nix.

- server.py: add _validate_domain_value, _is_loopback_address,
  _resolve_all_addresses, _trigger_hosts_update helpers.

- server.py: update _check_domain_reachable to use --resolve so
  reachability is checked locally via Caddy, not via NAT loopback.

- server.py: update _evaluate_domain_checklist, api_services inline DNS
  check, and api_domains_check to recognise loopback resolution as an
  intentional local override rather than a DNS mismatch.

- server.py: call _trigger_hosts_update from api_domains_set after
  saving a service domain so the /etc/hosts entry is applied immediately.

- Add app/tests/test_loopback_diagnostics.py with 47 tests covering
  domain validation, loopback detection, diagnostic checklist logic,
  composite health, and api_domains_check."
2026-07-15 15:24:16 +00:00