fix: app/server hardening follow-ups (4 of 5) #12

Merged
agent-int merged 1 commit from fix/issue-6-hardening into main 2026-06-29 16:55:03 -07:00
Collaborator

Refs #6. Four of the five hardening follow-ups. Item D is intentionally not included -- see below.

Done

A. Report currency + prior net worth threaded over gRPC. They were dropped on the gRPC path, so the dashboard's currency scope and the change-since-last-session delta were lost. Added currency + prior_net_worth to GetReportRequest and threaded them through LedgerClient.report, the getReport handler, and the app's loadReport.

B. Live-push reconnect resumes from the last seq + debounces. On a flap the consume loop reset lastSeq to 0 and replayed the whole change_log (one reload() per historical entry). changes() now takes a fromSeq (the stream yields seq strictly greater than it); the loop resubscribes from the last processed seq and debounces reconnects with capped exponential backoff (0.25s -> 10s, reset on a healthy stream).

C. Bearer token moved to the Keychain. It lived in UserDefaults (a world-readable plist). Now stored in the Keychain, migrating any legacy UserDefaults value once. The COMMONS_GRPC_TOKEN env var still wins for dev.

E. Plaintext non-loopback guard. The app's only transport is plaintext h2c (TLS is terminated at the edge). It now refuses a plaintext connection to a non-loopback host (GRPCEndpoint.isLoopback guard), so a bearer token is never sent in the clear over the network.

Deferred

D. Namespace idempotency keys by principal -- not changed. The current first-writer-wins behavior is a deliberate, documented semantic, flagged in WriterExecutor.postTransaction as pending a human decision (it only matters once clients choose their own keys; today keys are server-generated per proposal). Changing money-path dedup semantics shouldn't be done unilaterally, so #6 stays open for that item.

Tests

  • Client round-trip: report threads currency (a no-match filter zeroes the artifact) and prior net worth (delta = current - prior).
  • GRPCEndpoint.isLoopback unit tests (loopback vs non-loopback hosts).
  • Data-source changes(fromSeq:) signature update across both sources + the controller.

Full gate green: 188 SwiftPM tests + the Xcode app oracle (** TEST SUCCEEDED **). buf lint / buf format / buf build clean on the proto change.

Refs #6. Four of the five hardening follow-ups. **Item D is intentionally not included** -- see below. ## Done **A. Report currency + prior net worth threaded over gRPC.** They were dropped on the gRPC path, so the dashboard's currency scope and the change-since-last-session delta were lost. Added `currency` + `prior_net_worth` to `GetReportRequest` and threaded them through `LedgerClient.report`, the `getReport` handler, and the app's `loadReport`. **B. Live-push reconnect resumes from the last seq + debounces.** On a flap the consume loop reset `lastSeq` to 0 and replayed the whole `change_log` (one `reload()` per historical entry). `changes()` now takes a `fromSeq` (the stream yields seq strictly greater than it); the loop resubscribes from the last processed seq and debounces reconnects with capped exponential backoff (0.25s -> 10s, reset on a healthy stream). **C. Bearer token moved to the Keychain.** It lived in UserDefaults (a world-readable plist). Now stored in the Keychain, migrating any legacy UserDefaults value once. The `COMMONS_GRPC_TOKEN` env var still wins for dev. **E. Plaintext non-loopback guard.** The app's only transport is plaintext h2c (TLS is terminated at the edge). It now refuses a plaintext connection to a non-loopback host (`GRPCEndpoint.isLoopback` guard), so a bearer token is never sent in the clear over the network. ## Deferred **D. Namespace idempotency keys by principal** -- not changed. The current first-writer-wins behavior is a *deliberate, documented* semantic, flagged in `WriterExecutor.postTransaction` as pending a human decision (it only matters once clients choose their own keys; today keys are server-generated per proposal). Changing money-path dedup semantics shouldn't be done unilaterally, so #6 stays open for that item. ## Tests - Client round-trip: report threads currency (a no-match filter zeroes the artifact) and prior net worth (delta = current - prior). - `GRPCEndpoint.isLoopback` unit tests (loopback vs non-loopback hosts). - Data-source `changes(fromSeq:)` signature update across both sources + the controller. Full gate green: **188 SwiftPM tests** + the **Xcode app oracle** (`** TEST SUCCEEDED **`). `buf lint` / `buf format` / `buf build` clean on the proto change.
fix: app/server hardening follow-ups (#6)
Some checks failed
ci / macos (pull_request) Successful in 4m2s
ci / linux (pull_request) Has been cancelled
ci / proto (pull_request) Has been cancelled
ci / purity (pull_request) Has been cancelled
fce1252dd3
Four of the five #6 follow-ups. Item D (namespace idempotency keys by
principal) is left out: it is flagged in-code as a deliberate semantic
pending a human decision, so it is not changed here.

A. Report currency + prior net worth were dropped on the gRPC path, so the
   dashboard currency scope and change-since-last-session delta were lost.
   Add currency + prior_net_worth to GetReportRequest and thread them
   through LedgerClient.report, the getReport handler, and the app's
   loadReport.
B. Live-push reconnect replayed the whole change_log on every flap (it reset
   lastSeq to 0). changes() now takes a fromSeq; the consume loop resubscribes
   from the last processed seq and debounces reconnects with capped
   exponential backoff (0.25s -> 10s, reset on a healthy stream).
C. The gRPC bearer token moved from UserDefaults (a world-readable plist) to
   the Keychain, migrating any legacy UserDefaults value once. The env var
   still wins for dev.
E. Plaintext gRPC is now refused to a non-loopback host (GRPCEndpoint.isLoopback
   guard), so a bearer token is never sent in the clear over the network.

Tests: client round-trip for the report currency/prior-net-worth threading,
GRPCEndpoint.isLoopback unit tests, and the data-source signature update.
Full gate green: 188 SwiftPM tests + the Xcode app oracle (TEST SUCCEEDED).

Refs #6
agent-int deleted branch fix/issue-6-hardening 2026-06-29 16:55:03 -07:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
nic/commons!12
No description provided.