Threat model
Nimbus8's security posture starts with a clear threat model. We defend against four primary threats: data exfiltration (your conversations, files, and generated content leaving the device without your knowledge), model tampering (a downloaded model being modified after verification to produce malicious output), prompt injection (untrusted input manipulating the model into executing unintended actions), and network eavesdropping (interception of data when the user opts into network features like custom endpoints or Hugging Face downloads).
We explicitly do not defend against physical device access by an attacker who has your passcode. If someone has your unlocked iPhone, they have access to everything in the app — the same as any other iOS app. iOS Data Protection handles encryption at rest, and we rely on it rather than implementing a second layer of app-level encryption that would degrade performance without meaningfully improving security against this threat.
The key architectural advantage is that Nimbus8 is offline by default. There is no server, no account, no sync, and no telemetry. The network is only used when you explicitly enable it — downloading models from Hugging Face, connecting to a custom endpoint, or authenticating with GitHub for Cirrus PRs. This means the attack surface for remote exploitation is essentially zero in the default configuration.
iOS sandbox
Nimbus8 runs inside the standard iOS app sandbox. All user data — conversations, generated images, model files, the search index, audit logs — lives in the app's sandboxed container. No other app can read this data, and Nimbus8 cannot read data from other apps. The only shared storage is the App Group container used for widget snapshots, which contains only the minimal JSON needed to render the Ashe widget.
Generated images in the Mirage gallery use iOS Complete File Protection (NSFileProtectionComplete), meaning they are encrypted and inaccessible when the device is locked. Conversation databases and the Haze memory index use the default protection level (NSFileProtectionCompleteUntilFirstUserAuthentication) to allow background task access after the first unlock. Model files use the same default protection — they need to be readable by background download resume operations.
Nimbus8 requests no unusual entitlements. There is no background location, no contacts access, no health data, no push notification registration. The only entitlements beyond the baseline are the App Group (for widget communication), the camera (for Stratus), the microphone (for Overture), and the photo library (for Mirage save-to-Photos). Each of these triggers a standard iOS permission prompt on first use.
Keychain usage
Sensitive credentials are stored in the iOS Keychain, not in UserDefaults, not in files, and not in the database. This includes Hugging Face access tokens (used to download gated models like Llama and Gemma), custom endpoint API keys (for users who connect to their own inference servers), and GitHub OAuth tokens (used by Cirrus for PR creation).
All Keychain items use the kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly accessibility level. This means credentials are available after the first device unlock (so background model downloads can authenticate) but are never included in device backups and never synced to iCloud Keychain. If you restore a backup to a new device, you'll need to re-enter your tokens.
Credentials are never logged, never included in crash reports (there are no crash reporters), never written to the audit log, and never passed to the model as context. The Rust core accesses Keychain items through a Swift FFI bridge that returns opaque handles — the credential value is read from Keychain, used for the HTTP request, and then dropped. It never persists in memory longer than the duration of the network call.
Network posture
The default network posture is offline. Out of the box, Nimbus8 makes zero network requests. No analytics, no crash reporting, no telemetry, no phone-home checks, no update pings. The app works entirely from local data — models you've already downloaded, conversations stored on disk, the local search index.
When you opt into network features, HTTPS is enforced for all connections except loopback addresses (127.0.0.1, ::1, localhost). This exception exists because many users run local inference servers (Ollama, vLLM, llama.cpp server) on their Mac and connect from their iPhone over the local network. Requiring TLS certificates for localhost would create unnecessary friction for this common use case. All non-loopback custom endpoints must use HTTPS — HTTP connections are rejected at the network layer.
Nimbus8 includes no third-party analytics SDKs, no crash reporting frameworks, and no advertising identifiers. The only network calls the app ever makes are: Hugging Face API requests (model search, download, gated model auth), GitHub API requests (Cirrus OAuth and PR creation), custom endpoint inference requests, and DuckDuckGo/SearXNG queries (Mist web search). Each of these follows a minimum-data principle — we send only the parameters required for the API call and nothing else.
Model integrity
Every model downloaded through the Hugging Face browser is verified using a Trust-On-First-Use (TOFU) scheme. On first download, Nimbus8 computes the SHA256 hash of the model file and records it in the persistent model registry (index.json). On every subsequent load, the hash is recomputed and compared. A mismatch is a hard error — the model will not load, and the user is shown a clear warning explaining that the file has been modified since it was first verified.
For models installed via the curated catalog, the expected SHA256 hash is included in the catalog manifest itself. This means the first-load verification checks against a known-good hash rather than trusting whatever was downloaded. If the downloaded file doesn't match the catalog hash, the download is marked as failed and the file is deleted. This protects against CDN compromise or man-in-the-middle attacks during download.
Image files processed by Stratus and Mirage go through safe image validation before being decoded with UIImage. This guards against malformed image files that could exploit image decoder vulnerabilities. The validation checks file headers, dimensions, and color space before passing the data to Core Graphics. Oversized images are downscaled before decode to prevent memory exhaustion attacks.
Prompt safety
The Ashe safety scanner runs on every piece of text that enters the system prompt — user messages, tool outputs, memory injections, skill definitions, and web search snippets. It's a multi-pattern regex sweep designed to catch the most common prompt injection techniques before they reach the model.
The scanner detects role-hijack prefixes (attempts to override the system prompt with phrases like "ignore previous instructions" or "you are now"), invisible Unicode characters (zero-width spaces, right-to-left overrides, and other characters used to hide malicious instructions in seemingly innocent text), credential exfiltration patterns (instructions that try to get the model to output Keychain contents, API keys, or file paths), and SSRF probe patterns (attempts to make the model generate requests to internal network addresses or metadata endpoints).
When the scanner flags content, the behavior depends on context. In interactive chat (Gale, Cirrus), flagged content is highlighted with a warning but still sent — the user is in control and may be testing intentionally. In Ashe agent turns, flagged content in tool outputs triggers a safety pause: the turn loop stops, the flagged content is shown in the activity feed, and the user must explicitly approve continuation. Skill definitions and memory entries are scanned before persistence — flagged items are rejected with an explanation.
Audit chain
Every action taken by the Ashe agent runtime is recorded in a Merkle hash-chained audit log. Each log entry contains a timestamp, the action type (plan, tool-call, apply, reflect), the input and output data, and a SHA256 hash that chains to the previous entry. This creates a tamper-evident record — if any entry is modified or deleted, the chain breaks and subsequent entries fail verification.
On every app launch, Ashe verifies the integrity of the audit chain by walking the hash links from the most recent entry back to the genesis entry. If a break is detected, the app flags the affected session and shows a warning in the activity feed. The corrupted session's entries are preserved but marked as unverified. This doesn't prevent the app from functioning — it's a detection mechanism, not a prevention mechanism.
Per-session integrity verification runs at the start of every agent turn. Before the turn loop begins, Ashe verifies the chain for the current session. This catches tampering that might occur between turns — for example, if a malicious tool output tried to modify previous log entries to cover its tracks. The verification is fast (O(n) in the number of entries for the session, typically under 1ms) and doesn't add perceptible latency to agent interactions.
Agent controls
Ashe's agent runtime includes three layers of resource control: fuel metering, loop guards, and tool access policies. Together, they prevent runaway agent behavior — whether caused by a confused model, a prompt injection, or a genuinely difficult task that would consume excessive resources.
Fuel metering enforces per-turn budgets on three dimensions: token count (input + output), wall-clock time, and tool-call count. When any budget is exhausted, the turn ends gracefully — the model's partial output is preserved, and the user is shown a summary of what was consumed. Background tasks (scheduled Ashe hands) get tighter budgets than foreground interactive turns, because they run without user supervision and iOS may terminate the app if it uses too many resources.
Tool access policies control which tools the agent can invoke. Each tool in the registry has a policy: Allow (the tool runs without confirmation), Deny (the tool is never available to the agent), or AllowWithScan (the tool runs, but its input and output are passed through the safety scanner). Per-tool policy overrides let you customize access — for example, you might Allow file-read tools but set file-write tools to AllowWithScan so you can review what the agent wants to write before it happens. The loop guard detects repeated identical tool calls (same tool, same arguments, same output) and breaks the loop after three repetitions to prevent infinite cycles.
Supply chain
Nimbus8 uses only open-weight models. There are no proprietary model dependencies, no models that require a license server to run, and no models that phone home for usage tracking. Every model in the catalog is available on Hugging Face under a published license (Apache 2.0, MIT, Llama Community License, Gemma Terms of Use). You can inspect the weights, the tokenizer, and the model card before you download.
The Rust core library is compiled from source as part of the app build. There are no pre-compiled binary blobs in the Rust dependency tree. The only pre-compiled binary included in the app is ONNX Runtime, which is distributed as an Apple XCFramework built from the official Microsoft repository. We pin to a specific ONNX Runtime release and verify its checksum before integration.
Third-party Swift dependencies are minimal and audited: llama.cpp (inference backend, compiled from source), mlx-swift (Apple's MLX framework binding), Highlightr (syntax highlighting, optional), and the standard Apple frameworks. The dependency tree is intentionally small to reduce the surface area for supply chain attacks. We do not use CocoaPods — all dependencies are managed through Swift Package Manager with version pinning.
Reporting vulnerabilities
If you've found a security vulnerability in Nimbus8, we want to hear about it. Please email security@trynimbus.org with a description of the issue, steps to reproduce, and your assessment of severity. We'll acknowledge receipt within 48 hours and aim to provide a substantive response within 7 business days.
We follow responsible disclosure practices. If you report a vulnerability, we ask that you give us reasonable time to investigate and fix it before publishing details. We'll coordinate with you on a disclosure timeline — typically 90 days from the initial report, or sooner if we ship a fix earlier. We credit reporters in the changelog unless you prefer to remain anonymous.
The scope of our security program covers the Nimbus8 iOS app, the Rust core library, and the landing site. It does not cover third-party models (report those to the model authors), Hugging Face infrastructure (report to Hugging Face), or Apple's iOS security (report to Apple). If you're unsure whether something is in scope, email us anyway — we'd rather triage a report that turns out to be out of scope than miss a real vulnerability.