Skip to content

Audit and fix AkFit security stability issues#28

Merged
BigAkins merged 20 commits into
mainfrom
audit/codebase-security-stability
Jun 30, 2026
Merged

Audit and fix AkFit security stability issues#28
BigAkins merged 20 commits into
mainfrom
audit/codebase-security-stability

Conversation

@BigAkins

@BigAkins BigAkins commented Jun 30, 2026

Copy link
Copy Markdown
Owner

This PR adds a comprehensive AkFit security and stability audit and implements focused fixes from the audit.

Includes:

  • Adds the codebase security/stability audit report
  • Adds the required Apple privacy manifest
  • Centralizes user-owned store resets on identity changes
  • Prevents stale async fetches from repopulating user-owned stores after sign-out/account switch
  • Fixes Search refetch storms
  • Fixes Progress data pagination for high-frequency loggers
  • Implements password reset recovery flow
  • Adds production Supabase drift checks
  • Hardens drift checks against false negatives
  • Hardens guest data decoding against schema evolution data loss
  • Improves grocery offline/failure handling
  • Fixes grocery stale merge and clear-checked races
  • Adds Sign in with Apple credential revocation during account deletion
  • Binds Apple revocation to the authenticated user's linked Apple identity
  • Pins secret-bearing GitHub Actions to commit SHAs
  • Removes tracked generated graph output and updates .gitignore
  • Sanitizes DEBUG auth error logging
  • Adds/updates Swift, Supabase, and Edge Function test coverage

Manual checks before App Store release:

  • Live Apple account deletion with real Apple/Supabase secrets
  • Live password reset recovery email/deep link
  • Run Supabase production drift workflow after GitHub secrets are configured

Greptile Summary

This PR is a comprehensive security and stability audit implementation, touching auth flows, store lifecycle, edge functions, CI hardening, and guest data resilience. The changes are well-scoped and clearly motivated by the accompanying audit document.

  • Auth/identity: Centralizes all user-owned store resets in a single RootView onChange(of: currentUserId) hook; adds a full password-recovery state machine with deep-link validation and cancel/dismiss flows; adds Apple credential revocation with identity binding before account deletion.
  • Store hardening: GroceryListStore gains a version-tracked merge strategy to prevent stale async fetches from overwriting in-flight writes; FoodLogStore adds paginated refreshWeek to avoid silent row truncation for high-frequency loggers; GuestDataStore adopts element-level lossy decoding for arrays.
  • Infrastructure: GitHub Actions steps pinned to commit SHAs; Deno edge-function tests added to CI; a new weekly production drift check workflow introduced.

Confidence Score: 5/5

Safe to merge; no findings affect correctness of the critical account-deletion or password-recovery paths.

The two flagged items are both non-blocking: the redundant setAuth call in resolveDeleteAccountSession leaves stale state on the shared functions client but does not affect the current deletion flow (the explicit Authorization header takes precedence), and the double applyAuthenticatedSession during recovery-URL handling is idempotent. All security-sensitive operations — Apple grant revocation with identity binding, session validation before deletion, stale-fetch guards after sign-out — are correctly implemented.

AkFit/AkFit/Auth/AuthManager.swift — the redundant setAuth call and the dual applyAuthenticatedSession path during password recovery are worth a second look before adding new edge functions.

Important Files Changed

Filename Overview
AkFit/AkFit/Auth/AuthManager.swift Major additions: password recovery state machine, Apple identity detection, centralized sign-out/guest-exit cleanup, and session validation before account deletion. The redundant setAuth call in resolveDeleteAccountSession mutates global functions client state unnecessarily; and the URL handler and auth observer both call applyAuthenticatedSession for the same recovery session.
supabase/functions/delete-account/handler.ts New Apple grant revocation flow: exchanges the authorization code, verifies the returned id_token's issuer/audience/subject against the stored Supabase identity, and revokes before deletion. JWT payload is decoded without signature verification (acceptable given TLS transit from Apple), a pre-existing concern already noted in a previous review comment.
AkFit/AkFit/AkFitApp.swift Centralized all user-owned store resets into a single onChange(of: authManager.currentUserId) hook in RootView, eliminating the previous drift where individual call sites missed stores. Password recovery routing added as the first priority in the RootView switch.
AkFit/AkFit/Logging/GroceryListStore.swift Comprehensive rewrite with stale-fetch prevention via activeFetchVersions/recentlyConfirmedWrites merge logic, optimistic toggle with revert on failure, session pre-flight before writes, and a reset() method for the centralized identity-change hook.
AkFit/AkFit/Logging/FoodLogStore.swift Added paginated refreshWeek to avoid silent row truncation at the 1,000-row PostgREST cap for high-frequency loggers, canApplyUserOwnedState guards after every async boundary, and a reset() method.
AkFit/AkFit/Guest/GuestDataStore.swift Hardened guest data decoding with loadLossyArray (element-level resilience) and loadStringDictionary (partial-dictionary recovery), protecting against schema-evolution data loss on upgrade.
AkFit/AkFit/Views/Auth/SetNewPasswordView.swift New password-reset recovery UI with four states (processingLink, ready/form, invalidLink, passwordUpdated), client-side policy validation (≥12 chars, upper/lower/digit), and cancel/dismiss flows wired to AuthManager.
.github/workflows/ci.yml Supabase setup-cli action pinned to commit SHA; Deno edge-function tests added as a dedicated step before the DB integrity checks; actions/checkout remains unpinned at @v4.
.github/workflows/supabase-production-drift.yml New weekly + push-triggered workflow that connects to the production DB and runs drift checks; checkout action pinned to commit SHA, consistent with the PR's SHA-pinning theme.
AkFit/AkFit/Views/Settings/SettingsView.swift Added Apple re-authorization sheet for account deletion, wired to authManager.requiresAppleReauthorizationForAccountDeletion; removed per-store manual reset calls (now centralized in RootView).

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant User
    participant iOS as iOS App
    participant AM as AuthManager
    participant RV as RootView
    participant SB as Supabase Auth
    participant EF as delete-account Edge Fn
    participant Apple as Apple ID

    note over User,Apple: Password Recovery Flow
    User->>iOS: Tap recovery email link
    iOS->>AM: handleIncomingURL(url)
    AM->>AM: setPasswordRecoveryPending(true)
    AM->>SB: auth.session(from: url)
    SB-->>AM: recoveredSession + fires .passwordRecovery event
    par URL handler path
        AM->>AM: applyAuthenticatedSession(recoveredSession)
        AM->>AM: "passwordRecoveryState = .ready"
    and Auth observer path
        AM->>AM: handle(.passwordRecovery, session)
        AM->>AM: applyAuthenticatedSession(session)
        AM->>AM: "passwordRecoveryState = .ready"
    end
    RV->>RV: Shows SetNewPasswordView
    User->>iOS: Submit new password
    iOS->>AM: updatePasswordAfterRecovery(password)
    AM->>SB: auth.update(user: UserAttributes)
    SB-->>AM: success
    AM->>AM: "passwordRecoveryState = .passwordUpdated"

    note over User,Apple: Account Deletion Flow (Apple user)
    User->>iOS: Confirm delete account
    iOS->>AM: deleteAccount(appleAuthorizationCode)
    AM->>AM: resolveDeleteAccountSession()
    AM->>SB: auth.session + auth.user(jwt:)
    SB-->>AM: validSession
    AM->>EF: POST /delete-account (Bearer JWT + appleAuthorizationCode)
    EF->>SB: getUser() via anon+JWT
    SB-->>EF: authenticated user
    EF->>Apple: POST /auth/token (exchange code)
    Apple-->>EF: id_token + refresh_token
    EF->>EF: verify sub matches linked Apple identity
    EF->>Apple: POST /auth/revoke (refresh_token)
    Apple-->>EF: 200 OK
    EF->>SB: admin.deleteUser(user.id)
    SB-->>EF: success
    EF-->>AM: 200 success
    AM->>SB: auth.signOut()
    SB-->>AM: .signedOut event
    AM->>RV: "userState = .signedOut"
    RV->>RV: onChange(currentUserId) → resetUserOwnedStores()
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant User
    participant iOS as iOS App
    participant AM as AuthManager
    participant RV as RootView
    participant SB as Supabase Auth
    participant EF as delete-account Edge Fn
    participant Apple as Apple ID

    note over User,Apple: Password Recovery Flow
    User->>iOS: Tap recovery email link
    iOS->>AM: handleIncomingURL(url)
    AM->>AM: setPasswordRecoveryPending(true)
    AM->>SB: auth.session(from: url)
    SB-->>AM: recoveredSession + fires .passwordRecovery event
    par URL handler path
        AM->>AM: applyAuthenticatedSession(recoveredSession)
        AM->>AM: "passwordRecoveryState = .ready"
    and Auth observer path
        AM->>AM: handle(.passwordRecovery, session)
        AM->>AM: applyAuthenticatedSession(session)
        AM->>AM: "passwordRecoveryState = .ready"
    end
    RV->>RV: Shows SetNewPasswordView
    User->>iOS: Submit new password
    iOS->>AM: updatePasswordAfterRecovery(password)
    AM->>SB: auth.update(user: UserAttributes)
    SB-->>AM: success
    AM->>AM: "passwordRecoveryState = .passwordUpdated"

    note over User,Apple: Account Deletion Flow (Apple user)
    User->>iOS: Confirm delete account
    iOS->>AM: deleteAccount(appleAuthorizationCode)
    AM->>AM: resolveDeleteAccountSession()
    AM->>SB: auth.session + auth.user(jwt:)
    SB-->>AM: validSession
    AM->>EF: POST /delete-account (Bearer JWT + appleAuthorizationCode)
    EF->>SB: getUser() via anon+JWT
    SB-->>EF: authenticated user
    EF->>Apple: POST /auth/token (exchange code)
    Apple-->>EF: id_token + refresh_token
    EF->>EF: verify sub matches linked Apple identity
    EF->>Apple: POST /auth/revoke (refresh_token)
    Apple-->>EF: 200 OK
    EF->>SB: admin.deleteUser(user.id)
    SB-->>EF: success
    EF-->>AM: 200 success
    AM->>SB: auth.signOut()
    SB-->>AM: .signedOut event
    AM->>RV: "userState = .signedOut"
    RV->>RV: onChange(currentUserId) → resetUserOwnedStores()
Loading

Comments Outside Diff (1)

  1. supabase/functions/delete-account/handler.ts, line 960-988 (link)

    P2 security Apple ID token decoded without signature verification

    appleSubjectFromToken extracts the sub claim by base64-decoding the JWT payload, but it never verifies the ES256 signature against Apple's public keys. The iss and aud checks are valuable but are also just decoded from the unverified payload — a tampered payload would still pass them.

    In practice the token arrives from https://appleid.apple.com over TLS, so exploitation requires breaking HTTPS in transit. However, Apple's own guidelines (and JWT-security best practice) require verifying the signature before trusting any claim. For an action as irreversible as account deletion and Apple-grant revocation, adding signature verification closes this gap.

    The fix involves fetching Apple's public keys from https://appleid.apple.com/auth/keys and verifying the JWT signature with the matching key (kid in the JWT header) before trusting sub. This can be done with the Web Crypto API available in Deno.

    Fix in Claude Code Fix in Codex Fix in Cursor

Reviews (4): Last reviewed commit: "Serialize password recovery tests" | Re-trigger Greptile

BigAkins and others added 17 commits June 11, 2026 11:10
8-domain verified audit (architecture, performance, tests/CI, App Store,
offline/state, Supabase scale, HealthKit, UX) with adversarial
verification of all P0/P1 findings, live-DB/advisor/log evidence.
Top finding: password reset is a complete dead end (permanent lockout).
Includes scores, top-10, exact recommended tests, and a roadmap with an
explicit not-worth-fixing list. No fixes implemented in this phase.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
App code uses UserDefaults in 7 files (GuestDataStore, NotificationService,
AuthManager, four stores) — a required-reason API Apple enforces via
ITMS-91053 at upload validation. SDK manifests (supabase-swift,
sentry-cocoa) cover SDK code only, so a missing app-level manifest could
block any future upload, including an urgent hotfix, at archive time.

Declares only what the app actually uses: UserDefaults with reason CA92.1
(app's own data), tracking=false, no tracking domains. A repo sweep
confirmed no other required-reason APIs (file timestamps, boot time, disk
space, keyboard list) in app code. Verified present in the built app
bundle root with correct content.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign-out previously reset NO stores, and the manual per-call-site reset
lists had drifted (exit-guest missed favorites; delete-account reset six)
— so on a shared device, user A's food logs, recents, favorites, grocery
list, and daily note survived in memory into user B's session, and the
note editor could even upsert A's note text into B's account.

RootView now resets all six user-owned stores in ONE place via
.onChange(of: authManager.userState) — covering sign-out, account
deletion, guest exit, and sign-in uniformly. onChange runs before newly
inserted views' .task fetches, so fresh data loads for the new identity.
The duplicated reset lists in SettingsView are deleted; five now-unused
store environment properties removed.

Adds StoreResetTests pinning the reset() contract for FoodLogStore,
WaterStore, BodyweightStore, and FavoriteFoodStore via the existing
preview-seed initializers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SearchView's .task re-runs on every tab appearance, and despite the
"fetched once on first appear" comment it re-downloaded the full
~1,500-row type-ahead corpus plus the empty-state suggestions on every
visit — production API logs showed 6 corpus fetches in a single session
(~40-60KB each of identical static data).

Guard both static catalog fetches with isEmpty checks: they load once per
app session and self-heal (a failed fetch leaves the array empty, so the
next appearance retries). Recents/favorites/grocery keep refreshing per
appearance — they are small per-user queries that genuinely change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
refreshWeek issued a single un-limited ascending query, and PostgREST
silently caps every response at max_rows (1,000). A high-frequency logger
(11+ entries/day) exceeds that in the 90-day window, and ascending order
meant the truncated rows were the NEWEST days — the Progress chart showed
zero-calorie recent days for exactly the most engaged users, with no
error.

Fetch in explicit 1,000-row pages (hard stop at 5 pages ≈ 55 logs/day
over 90 days) with a secondary id ordering so pagination is stable across
rows sharing a logged_at timestamp. Typical users still make exactly one
request; the 7- and 30-day ranges are unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 30, 2026 15:11
@supabase

supabase Bot commented Jun 30, 2026

Copy link
Copy Markdown

Updates to Preview Branch (audit/codebase-security-stability) ↗︎

Deployments Status Updated
Database Tue, 30 Jun 2026 16:14:27 UTC
Services Tue, 30 Jun 2026 16:14:27 UTC
APIs Tue, 30 Jun 2026 16:14:27 UTC

Tasks are run on every commit but only new migration files are pushed.
Close and reopen this PR if you want to apply changes from existing seed or migration files.

Tasks Status Updated
Configurations Tue, 30 Jun 2026 16:14:28 UTC
Migrations Tue, 30 Jun 2026 16:14:28 UTC
Seeding Tue, 30 Jun 2026 16:14:28 UTC
Edge Functions Tue, 30 Jun 2026 16:14:30 UTC

View logs for this Workflow Run ↗︎.
Learn more about Supabase for Git ↗︎.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR is a broad security/stability hardening pass across the iOS app and Supabase tooling, adding stronger auth/account-deletion controls, safer client-side state handling across identity changes, improved operational checks (production drift), and expanded regression tests to lock the behaviors in.

Changes:

  • Strengthens auth/account security (password policy tightening, password recovery UI flow, and Apple reauthorization + server-side revocation before Apple-backed account deletion).
  • Prevents cross-user data bleed by centralizing user-owned store resets on identity changes and guarding stores against stale async updates.
  • Adds production Supabase drift detection (script + workflow + documentation) and removes tracked generated graphify-out artifacts.

Reviewed changes

Copilot reviewed 80 out of 97 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
supabase/functions/delete-account/index.ts Refactors Edge Function entrypoint to delegate to a dedicated handler.
supabase/config.toml Tightens auth settings (password policy, secure password change, JWT verification for delete-account).
scripts/supabase/check-production-drift.sh Adds a read-only production DB drift check runner using psql.
docs/supabase-production-drift-check.md Documents drift check scope, safe usage, and GitHub Actions setup.
.github/workflows/supabase-production-drift.yml Adds scheduled/manual workflow to run production drift checks.
.github/workflows/ci.yml Adds Deno setup + Edge Function tests for delete-account.
.github/workflows/claude.yml Hardens Claude workflow triggering/permissions and pins the action SHA.
.github/workflows/claude-code-review.yml Hardens Claude review workflow triggering/permissions and pins the action SHA.
AkFit/AkFit/AkFitApp.swift Adds URL handling for recovery links and centralizes user-owned store resets on identity transitions.
AkFit/AkFit/Views/Auth/AuthView.swift Updates password-reset UX copy and applies stricter password policy on sign-up only.
AkFit/AkFit/Views/Auth/SetNewPasswordView.swift Adds password recovery “set new password” screen + validation policy.
AkFit/AkFit/Views/Settings/SettingsView.swift Adds Apple reauth sheet for account deletion and routes deletion through Apple code when required.
AkFit/AkFit/Views/Search/SearchView.swift Reduces refetch storms and improves grocery action UX/error handling + disables interactions while busy.
AkFit/AkFit/Logging/FoodLogStore.swift Adds identity-safety guards for async refresh/write paths and implements paged history loading.
AkFit/AkFit/Logging/WaterStore.swift Adds identity-safety guards to prevent stale async writes/refreshes after sign-out/account switch.
AkFit/AkFit/Logging/BodyweightStore.swift Adds identity-safety guards to prevent stale async writes/refreshes after sign-out/account switch.
AkFit/AkFit/Logging/DailyNoteStore.swift Adds identity-safety guards to prevent stale async writes/refreshes after sign-out/account switch.
AkFit/AkFit/Logging/FavoriteFoodStore.swift Adds identity-safety guards and safer optimistic update reverts across identity changes.
AkFit/Config/AppConfig.swift Adds stricter Supabase URL validation to prevent unsafe/misconfigured endpoints.
AkFit/Services/SentryMonitoring.swift Disables network telemetry capture and scrubs URLs/headers/breadcrumb data before sending.
AkFit/AkFit/Models/FoodLog.swift Adds resilient decoding for legacy guest-cached rows missing/invalid meal_slot.
AkFit/AkFit/PrivacyInfo.xcprivacy Adds required Apple privacy manifest for app-level required-reason APIs.
AkFit/AkFitTests/StoreResetTests.swift Adds tests pinning store reset semantics across identity transitions.
AkFit/AkFitTests/SentryMonitoringTests.swift Adds tests for URL/header/data scrubbing helpers.
AkFit/AkFitTests/PasswordRecoveryTests.swift Adds tests for password recovery validation + AuthManager recovery event behavior.
AkFit/AkFitTests/GuestDataStorePersistenceTests.swift Adds tests for schema-evolution-safe guest persistence and protected storage behavior.
AkFit/AkFitTests/AuthManagerDeleteAccountTests.swift Adds tests around Apple deletion requirements and debug-log sanitization.
AkFit/AkFitTests/AppConfigTests.swift Adds tests for Supabase URL validation rules.
.gitignore Ignores graphify-out/ going forward.
graphify-out/GRAPH_REPORT.md Removes tracked generated graph report output.
graphify-out/cache/e11fa65adcaae970332a0cfff21709f944a4beeac95c8264712977c85c1a3d74.json Removes tracked generated graph cache artifact.
graphify-out/cache/d204ea1fa8d2e3260a1c59e2a864d1978ad319fa8c988348c370b51200643e27.json Removes tracked generated graph cache artifact.
graphify-out/cache/d0f479c4e1c4887a96c198f484c3ce796f922d70c8cb1bae86121c0d43220446.json Removes tracked generated graph cache artifact.
graphify-out/cache/c75ad8a6db5b094141492703a10f1ffb8667a7508466dcde7a913e99146dc1f4.json Removes tracked generated graph cache artifact.
graphify-out/cache/bffe27fb9b4a54f4777c175ecc2898dd0ef64557475f9964360f034a72e130b2.json Removes tracked generated graph cache artifact.
graphify-out/cache/bcbee6f8b09fc5c643bd4d073a69ef8996759cbd5df34b917626cc25f0921b63.json Removes tracked generated graph cache artifact.
graphify-out/cache/bbcc10bed2283efdd5db16631c2bac0801cde91bcc96302d8c08af1f8e50484f.json Removes tracked generated graph cache artifact.
graphify-out/cache/baa63c3cf5c98a3cdbbaf90a8b5568b09f3b4cf8dfcd7385e8112c0d6c0a5ff2.json Removes tracked generated graph cache artifact.
graphify-out/cache/ba8b7b4adb7d5656d2d44a577a41d3a0f6939eff04a3ed9223eadeaaf3fd6db3.json Removes tracked generated graph cache artifact.
graphify-out/cache/b81ac998449d463c29f07820998035a7319a2607e34e005679f9e94793f0f5c9.json Removes tracked generated graph cache artifact.
graphify-out/cache/ab24c29c9794dd5f12999b3d7606d10c4f53f1a4c994a40d63c1496127aefe99.json Removes tracked generated graph cache artifact.
graphify-out/cache/96fea7b4a46de6d11edd0d888e4b57bf8ba779dc7a92c6e231341dc845b51b19.json Removes tracked generated graph cache artifact.
graphify-out/cache/953ce43ded2c5fb40da203251c63f8ab2b621d82219cbc1b850ebc0027c5055c.json Removes tracked generated graph cache artifact.
graphify-out/cache/8893e5e024709e977f983965b9c1be310cbfad981e8db3e441f1358af8954c42.json Removes tracked generated graph cache artifact.
graphify-out/cache/7fe68d8ebebceece50cd78a1d15400df5ce449e9de6275d20d56a088dc09d753.json Removes tracked generated graph cache artifact.
graphify-out/cache/75a964f6d4a66d50ab637d6b373b726458dc30ebf591d0b3f63c14a839696fbb.json Removes tracked generated graph cache artifact.
graphify-out/cache/71e326c886afe22b4d709f70541f5cb12149b0d8b083a8108b4b4fb3882dd764.json Removes tracked generated graph cache artifact.
graphify-out/cache/6e3ca76195f6db34b60e487d13527ad9bb3b227798f563fe67af34b4c74cfb5e.json Removes tracked generated graph cache artifact.
graphify-out/cache/544516b1b6df806e43cdc4277c0deebd9984030687949eff01966b56c308b73c.json Removes tracked generated graph cache artifact.
graphify-out/cache/4b52614bc573fa6daadff4935cbba3729743edc202f38ce85ac3e37c0eafdbab.json Removes tracked generated graph cache artifact.
graphify-out/cache/1f6eefc480fc455029da65735b9d9d49ccf2a130cb3dee7d7228ae770ea70eff.json Removes tracked generated graph cache artifact.
graphify-out/cache/17d4b182f0b0dc232ea38bbe3158ba77d8593891aa3020953b99f995d8f6c534.json Removes tracked generated graph cache artifact.
graphify-out/cache/07f05f75c08be8c3f88566a86d62b3f7e57ceafed67c5c2d3e8c675fc7ca89a1.json Removes tracked generated graph cache artifact.
graphify-out/cache/38ef77636049c30d2f10a2a5a4da99630f5be25170e7651befd66fb9f5d5a40d.json Removes tracked generated graph cache artifact.
graphify-out/cache/367315176110df03d3c529c3b1e1d0fe86ebaea6dbf1ec0539a559ba7ecd8894.json Removes tracked generated graph cache artifact.
graphify-out/cache/3200686a9ff2436c08cdf38f4d58daba30602fcc79a705c918b479666ac889df.json Removes tracked generated graph cache artifact.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

loggedAt = try c.decode(Date.self, forKey: .loggedAt)
createdAt = try c.decode(Date.self, forKey: .createdAt)

let rawMealSlot = (try? c.decodeIfPresent(String.self, forKey: .mealSlot)) ?? nil
Comment on lines +267 to +269
let pageSize = 1_000
let maxPages = 5 // hard stop ≈ 55 logs/day over 90 days
for page in 0..<maxPages {

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

// Signing out should never trap the user in a broken authenticated
// state. Clear the local session even if the remote revoke fails.
clearAuthenticatedState()

P2 Badge Clear Supabase's stored session on sign-out failure

When auth.signOut() throws, this fallback only clears AuthManager's in-memory state; it does not clear the Supabase client's persisted session/refresh token. In the network-failure or revoke-failure path, the UI routes to signed-out state for this run, but the next auth observer/launch can restore the still-stored session and sign the user back in. Use a local sign-out/session removal path in this catch before clearing app state.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +435 to +438
try await SupabaseClientProvider.shared.auth.resetPasswordForEmail(
email,
redirectTo: PasswordRecoveryLink.redirectURL(state: state)
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add the recovery callback to Supabase redirects

This reset flow now always asks Supabase to send users to akfit://auth-callback?..., but the checked-in Supabase auth config still only allow-lists https://127.0.0.1:3000 under [auth].additional_redirect_urls. In environments managed from this config, resetPasswordForEmail will reject or strip the custom callback, so email/password users still cannot complete password recovery until the akfit://auth-callback redirect is allowed.

Useful? React with 👍 / 👎.

Comment thread AkFit/AkFit/Auth/AuthManager.swift
Comment thread .github/workflows/claude.yml
@BigAkins
BigAkins merged commit 9cf9ec0 into main Jun 30, 2026
3 checks passed
BigAkins added a commit that referenced this pull request Jul 12, 2026
* Post-merge release-safety review: fix P1 silent note-save failure + hygiene

Multi-agent review (7 dimensions, adversarially verified) of the merged
security/stability work found no P0s and two P1s, both fixed here:

- DailyNoteStore.save no longer swallows persistence failures behind a
  false success signal: it returns Bool, mutates todayContent only after
  the write is decided, logs + captures failures to Sentry with standard
  classification tags, and NoteEditorSheet keeps the sheet open with a
  retry alert (swipe-dismiss blocked mid-save so typed text can't be
  destroyed by a failing in-flight save).
- StoreResetTests now pins reset() for all six user-owned stores
  (DailyNoteStore and GroceryListStore were unpinned — the exact
  cross-user data-leak regression class PR #28 fixed).

Tiny verified P2 hardening included:
- SearchView post-log Undo surfaces delete failures (alert + Sentry)
  instead of silently leaving the entry logged.
- FoodDetailView favorite toggle captures errors to Sentry (was the only
  unobserved user-initiated Supabase write).
- .gitignore adds *.xcarchive / *.xcresult.
- claude.yml pins actions/checkout to the v4.3.1 release SHA (secret in
  scope in that job).
- ci.yml also runs on push to main so direct pushes can't bypass tests.
- config.toml: akfit://auth-callback added to the redirect allow-list
  (exact entry for OAuth + ** wildcard for query-string recovery links,
  per GoTrue glob semantics) and documents the dashboard-managed
  production auth posture (email confirmations, leaked-password
  protection).
- Stale pre-PR#28 reset() comments in four stores corrected; README and
  release checklist note that CLAUDE.md / docs/ui-reference are
  local-only.

Verified: 119 tests in 18 suites pass on iPhone 17 Pro simulator; edited
TOML/YAML parse clean; production Supabase advisors checked live (only
pre-existing WARNs: pg_trgm in public, leaked-password protection off).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X3aqVLXYVueZBeKd4U7imw

* Address PR review: clarify save() identity semantics, add auth_code tags

- DailyNoteStore.save doc: identity change BEFORE the write returns false;
  identity change AFTER a successful upsert returns true (the write
  persisted for the original account; only the local mutation is skipped,
  RootView resets the store). Code behavior unchanged — the comment was
  imprecise (Copilot review).
- Add the auth_code Sentry tag to the quick_log_undo and favorite_toggle
  captures, matching the richer grocery/daily-note tag idiom — session
  expiry is exactly the failure class these paths can hit (Greptile P2).

119/119 tests pass locally after the change.
@BigAkins
BigAkins deleted the audit/codebase-security-stability branch July 12, 2026 23:44
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