Skip to content

iOS security hardening: Keychain, clipboard, https-only, tmp file protection - #2

Merged
mnimtz merged 1 commit into
mainfrom
claude/code-check-security-review-wcxq5h
Aug 5, 2026
Merged

iOS security hardening: Keychain, clipboard, https-only, tmp file protection#2
mnimtz merged 1 commit into
mainfrom
claude/code-check-security-review-wcxq5h

Conversation

@mnimtz

@mnimtz mnimtz commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Summary

Follow-up from the iOS/Swift security review of the nimshare app. Backend is untouched — these are client-side hardening fixes only. The app was already solid (tokens in Keychain, no WebView, no TLS bypass, Authorization stripped on redirect, ATS at secure defaults, zero secret logging); this closes the remaining at-rest / clipboard gaps.

⚠️ iOS/UIKit can't be compiled on the Linux runner (UIKit is Apple-only), so these were hand-reviewed, not compile-checked here — please verify the build in Xcode. The repo's ci.yml/container.yml only build the .NET backend, which is unchanged.

Fixes

🟠 Medium — Keychain accessibility hardened (Keychain.swift)

JWT session token was stored with kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly → decryptable while the device is locked. Now kSecAttrAccessibleWhenUnlockedThisDeviceOnly (the app only touches the token in the foreground). ThisDeviceOnly kept → no iCloud sync / backup leak.

🟠 Medium — Secrets no longer leak via the shared/Universal clipboard

API tokens (ApiTokensView) and revealed license keys (KeyStoreView, KeyStoreLicensesView) were copied via UIPasteboard.general.string → readable by any app + synced to other Apple devices. Now copied with .localOnly: true + a 60 s .expirationDate. Share-link URLs are left as-is (they're shareable links, not secrets).

🟢 Low — Server config is HTTPS-only (ServerConfigView)

The server-URL field accepted http://. ATS already blocks cleartext at runtime, but it's now rejected in the input with a clear message (defense-in-depth against a future ATS exception).

🟢 Low — Download hardening (TmpFile)

Server-provided filenames are sanitized (path separators / .. stripped — defense-in-depth; writes were already sandboxed to tmp), and the temp download dir is set to FileProtectionType.completeUnlessOpen.

Deferred (flagged, not in this PR)

  • Optional biometric (Face ID) app-lock + client-side token max-age (Medium): the persisted session has no app-level lock and no client-side expiry (bootstrap keeps the session on non-401 network errors). This is a real UX feature (settings toggle + LAContext flow + locked overlay), so it deserves its own change rather than a rushed edit — happy to do it as a focused follow-up.
  • Certificate pinning (Low, informational).

What was checked and is clean

Token in Keychain (not UserDefaults); password never persisted (AutoFill); no hardcoded secrets; no URLSessionDelegate TLS bypass; Authorization header stripped on redirect to Blob SAS; ATS secure defaults; no deep-link/URL-scheme surface; no WebView (QuickLook + AVPlayer); zero print/NSLog/os_log; TOTP secret never stored.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MjfiPG6J7tTFm1dFx5zYmh


Generated by Claude Code

…https-only server, tmp file protection

Follow-up from the iOS/Swift security review of the nimshare app (no backend changes):

- Keychain (Keychain.swift): store the JWT with
  kSecAttrAccessibleWhenUnlockedThisDeviceOnly instead of AfterFirstUnlock, so a
  live session token is not decryptable while the device is locked. ThisDeviceOnly
  is kept (no iCloud sync / backup leak).
- Clipboard: copy API tokens (ApiTokensView) and revealed license keys
  (KeyStoreView, KeyStoreLicensesView) with .localOnly + a 60 s expiry, so secrets
  don't sync via Universal Clipboard and auto-clear. Share-link URLs are unchanged
  (not secrets).
- Server config (ServerConfigView): accept https:// only; reject cleartext http://
  (which ATS already blocks at runtime) with a clear message.
- Downloads (TmpFile): sanitize the server-provided filename (strip path
  separators / traversal) and set FileProtectionType.completeUnlessOpen on the
  temp directory.

Deferred (separate follow-up): optional biometric (Face ID) app-lock and a
client-side token max-age — a UX feature rather than a surgical hardening tweak.

Note: iOS/UIKit can't be compiled on this Linux runner; changes are surgical and
were hand-reviewed — build verification happens in Xcode.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MjfiPG6J7tTFm1dFx5zYmh
@mnimtz
mnimtz marked this pull request as ready for review August 5, 2026 04:51
@mnimtz
mnimtz merged commit 11fa068 into main Aug 5, 2026
1 check passed
mnimtz added a commit that referenced this pull request Aug 7, 2026
Marcus's Test (basf.de): Willkommenstext ok, aber kein Logo + eigenes Logo weg
+ "Bereitgestellt mit NimShare" links statt mittig.

- Logo (#2): basf.de leitet auf www.basf.com um; relative Logo-URLs wurden gegen
  die URSPRUNGS-Domain aufgelöst → falscher Host → kein Logo. Jetzt gegen die
  FINALE URL nach Redirects (resp.RequestMessage.RequestUri) aufgelöst.
- Eigenes Logo (#4): ResolveThemeAsync mergt jetzt — Link-Vorlage überlagert nur
  gesetzte Felder, leere (z.B. kein Kundenlogo) fallen aufs Instanz/Personal-
  Branding zurück. Ohne Link-Vorlage byte-genau wie bisher.
- Footer (#3): "Bereitgestellt mit NimShare" auf der Folder-Landing zentriert.

Lokal mit .NET 8 gebaut: 0 Fehler. VERSION 1.12.1 → 1.12.2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants