Skip to content

10 — Security & Privacy

1. Posture

The data in this system — holdings, net worth, risk tolerance, religious practice — is among the most sensitive a person holds, and the last category is one most financial software never has to consider. The design assumption is that no third party is entitled to any of it by default, including the AI providers whose models the system can use.

Privacy here is not a settings page. It is enforced at a single architectural chokepoint (the Model Gateway), verified by tests, and made visible in the UI at the moment it applies.

2. Threat model

Adversary Capability Primary mitigation
Network attacker on the LAN Intercept traffic to the self-hosted server TLS everywhere including LAN; HSTS; optional mTLS on the home-server profile
Stolen or unlocked device Physical access to a signed-in browser Passkey bound to the device's platform authenticator, short session lifetime, re-assertion for sensitive actions, remote session revocation
Malicious script in the browser XSS or a compromised front-end dependency reading session state Strict CSP with no inline script, session in HttpOnly SameSite=Strict cookies so script cannot read it, subresource integrity, no third-party scripts at all
Compromised dependency Arbitrary code in-process Default-deny egress allowlist, least-privilege containers, lockfiles with hash pinning, SBOM and vulnerability scanning in CI
Malicious ingested content Prompt injection via news, social, or filings Quarantine boundary — §4
Curious or breached AI provider Sees whatever is sent to it Local-first default; gateway-enforced redaction; explicit opt-in for portfolio sharing
Server compromise Read database and files Volume encryption, column encryption for high-sensitivity fields, secrets outside the DB, tamper-evident audit log
The system itself Erroneous or manipulated trading action No real-money code path before Phase 5; circuit breakers; human confirmation; append-only audit

Out of scope: nation-state adversaries with physical access to the host, and supply-chain compromise of the operating system or hardware.

3. Data classification and flow control

Class Examples Rule
Secret Broker credentials (Phase 5), API keys, encryption keys OS keychain or age-encrypted store. Never in the database, never in logs, never in a prompt
Private Positions, balances, order history, risk profile, personal notes Never leaves the deployment unless explicitly enabled per-provider
Sensitive Watchlists, chosen methodology, recommendation history Local by default; aggregate form only if sharing is enabled
Public Market data, filings, news Freely transmitted

Gateway-enforced redaction. Every outbound model call passes through one component that strips or refuses Private and Secret class fields unless the corresponding setting is enabled. This is a single chokepoint by design — a rule applied at forty call sites is a rule that will be missed at the forty-first. It is verified by a test that asserts no private field can reach an external provider under default settings, run against every prompt template.

When portfolio sharing is off but the user asks a portfolio question, the assistant receives aggregate characteristics rather than positions — sector weights and risk metrics without instrument identities — and the UI states that this is what happened.

4. Prompt-injection containment

The system deliberately ingests text written by people who may want to influence it. A forum post or press release containing instructions aimed at a language model costs an attacker nothing and, in a system that can influence trades, could pay well. This is treated as an active threat, not a theoretical one.

Containment is structural, not prompt-based. Instructing a model to ignore injections is a mitigation, not a control. The controls:

  1. Capability removal. Agents that read untrusted content have an empty tool set. There is no function for injected instructions to invoke. This is the primary control and the only one that does not depend on model behaviour.
  2. Trust tiering. Content carries its trust_tier from ingestion through to generation. Tier C content is spotlighted as untrusted data in the prompt, and the system instruction states that instructions found inside content blocks are to be reported as observations, never followed.
  3. Schema-constrained output. Quarantined agents can only emit a validated Signal. There is no free-text channel through which injected content reaches another component unescaped.
  4. Influence capping. Narrative signals have a bounded maximum aggregate contribution to any score. No volume of coordinated posting can, by itself, generate a recommendation.
  5. Injection detection. A classifier flags likely injection attempts pre-analysis; detections are down-weighted and raised as data-quality events, which also surfaces coordinated campaigns as a signal in their own right.
  6. Red-team suite in CI. A maintained corpus of injection attempts — direct instruction, role-play framing, encoded payloads, injections embedded in tables and filing exhibits — runs on every change. Any instance of ingested content altering a tool call, a score, or an order fails the build.

The same trust boundary applies to the assistant: content retrieved into its context is data, and the assistant cannot place orders regardless of what any retrieved text says.

5. Authentication and access

No passwords anywhere in the system. Access is via WebAuthn passkeys bound to the device's platform authenticator — Touch ID, Windows Hello, or an equivalent. WebAuthn is a browser-native standard, so the web client gets this without any of the bridging a native app would need. There is nothing to phish, nothing to reuse, and nothing to leak in a breach.

Sessions live in HttpOnly, Secure, SameSite=Strict cookies, so front-end script cannot read them even if an XSS bug existed. Sessions are short-lived, and refresh requires re-asserting the passkey. Devices are individually enumerable and revocable. High-sensitivity actions — changing the Shariah methodology, altering risk limits, or (in Phase 5) anything touching real money — require a fresh passkey assertion regardless of session age.

Browser-specific hardening: a strict Content-Security-Policy with no inline script and no external origins; the client bundle is served from the same origin as the API, so there are no third-party scripts to trust; Cross-Origin-Opener-Policy and X-Frame-Options: DENY against clickjacking of the order confirmation flow.

6. Cryptography and storage

  • In transit: TLS 1.3, automatic certificates via Caddy, including on LAN, with HSTS. Self-hosted deployments on a private domain use a locally-trusted CA rather than certificate pinning, which browsers do not expose to applications.
  • At rest: full-volume encryption for database and object storage; additional column-level encryption for Secret and the most sensitive Private fields, with keys held outside the database.
  • Keys: OS keychain on desktop; age-encrypted keyfile with a passphrase for headless deployments; documented rotation procedure.
  • Backups: encrypted before leaving the host, with a restore procedure that is tested rather than assumed. An untested backup is a hypothesis.

7. Egress control

Default-deny outbound networking with an explicit host allowlist covering the configured data providers and, if enabled, the AI provider. Rationale: this system pulls from many external sources and runs model inference; a compromised dependency or a malicious model response attempting to exfiltrate should hit a network policy, not a code review.

Every outbound request is logged with destination, purpose, and data class. The Settings section exposes this as a plain-language data-flow view: what left the device, when, to whom, and why. A privacy claim the user can verify is worth more than one they must accept.

8. Audit log

Append-only and hash-chained — each entry includes the hash of its predecessor, so deletion or modification is detectable.

Recorded: every recommendation with its full manifest; every order and fill; every compliance verdict and transition; every risk limit evaluation and breach; every configuration change with before/after; every authentication and device event; every outbound data flow; every circuit breaker activation.

The log is user-visible and exportable. Its purpose is not compliance theatre — it is the mechanism by which the user can reconstruct, months later, exactly what the system knew and did.

9. Real-money safety (Phase 5)

Defence in depth, because this is where errors become irreversible:

  • No code path exists before Phase 5. Not a disabled flag — the broker adapter, credential storage, and routing code are absent. A bug cannot enable what is not implemented.
  • Enablement ceremony: a minimum paper-trading track record; a written acknowledgement of risk; a fresh passkey assertion; a mandatory cooling-off period between enabling and first live order; broker credentials entered once and stored server-side under column encryption, never held in the browser.
  • Separate confirmation per order in modes 1 and 2, each requiring a passkey assertion, until the user explicitly graduates to automated mode.
  • Hard caps independent of the risk engine — maximum order value, maximum daily notional, maximum position count — enforced at the adapter layer so a risk-engine bug cannot bypass them.
  • Kill switch that revokes broker credentials, not merely one that stops sending orders.
  • Automated mode is opt-in per strategy, with a mandatory periodic re-confirmation so it cannot be enabled once and forgotten.

10. Privacy defaults

Off unless the user turns them on: telemetry, analytics, crash reporting, cloud model usage, portfolio data sharing with any external provider, and cloud backup.

On by default: local-only inference where hardware permits, egress allowlist, encrypted storage, and the data-flow view.

Data is exportable in full (JSON and CSV) and deletable in full, with deletion actually removing data rather than flagging it. There is no vendor to retain it — which is, in the end, the strongest privacy property in the design.