Skip to content

feat(evals): add Swift and Android MFA step-up evals - #196

Merged
sanchitmehtagit merged 2 commits into
mainfrom
feat/mobile-mfa-evals
Aug 18, 2026
Merged

feat(evals): add Swift and Android MFA step-up evals#196
sanchitmehtagit merged 2 commits into
mainfrom
feat/mobile-mfa-evals

Conversation

@subhankarmaiti

@subhankarmaiti subhankarmaiti commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

✏️ Changes

Adds MFA step-up evals for the two mobile SDKs: swift_mfa and android_mfa. Same shape as mfa/react — gate a Transfer Funds action behind MFA by requesting step-up through Universal Login with acr_values and max_age 0, reading amr off the ID token, and only then running the transfer. Mobile had no MFA coverage before this.

Both use a new shared scaffold (src/evals/scaffolds/{swift,android}/auth0): a login-only app with Auth0 already wired, no Transfer Funds UI, since building the feature is the task. No compile_command — there is no native toolchain in the sandbox, same as the mobile quickstarts.

The graders accept either legitimate way of doing each step, so correct solutions aren't failed on style: amr via JWTDecode / customClaims on Swift and getExtraInfo() / jwtdecode on Android, and max_age via the dedicated builder or a max_age parameter. L2 penalises the embedded MFA grant, which is wrong for a Universal Login app.

Two side fixes: the README eval table listed quickstarts only, so the existing MFA and DPoP evals are backfilled; and .build/ / .gradle/ are gitignored, since both scaffolds pick up build residue locally.

  • I described the changes on this PR.

🔮 Type of Change

  • Standard
  • Emergency
  • Significant

🔗 References

  • SDK-10701, SDK-10702 — under epic SDK-10349

  • Reference eval: apps/auth0-evals/src/evals/mfa/react/

  • I added at least one link (task, Slack thread, etc.) to explain why this change is needed.

📖 Documentation

Updated the eval table in apps/auth0-evals/README.md. No new primitives, levels or flags, so the other docs stay accurate.

  • I reflected this change in the (internal and/or user-facing) documentation, or explained why no update is needed.

🎯 Testing

npm run build && npm test && npm run lint pass. No framework logic changed, so no new package tests.

Ran the deterministic graders against the bare scaffolds and against a hand-written correct solution for each platform: everything positive fails on the scaffold, everything passes on the correct solution. That caught two graders the scaffold itself already satisfied, now scoped to the step-up request.

10/10 jobs green:

Eval baseline agent agent+skills agent+mcp agent+mcp+skills
swift_mfa 5/11 10/13 · 87.2 B 13/13 · 95.8 A 7/15 · 71.8 C 11/15 · 84.5 B
android_mfa 7/13 13/15 · 78.8 B 13/15 · 83.7 B 14/17 · 85.1 B 16/17 · 97 A

Failures are specific, not noisy: one run never used acr_values at all, two hand-rolled Base64 JWT parsing, one set withMaxAge(0) only behind a flag the MFA path passed as false, one short-circuited the amr check on a session boolean.

Ignore the baseline column — two pre-existing framework bugs make it meaningless. runBaseline sets no maxOutputTokens and doesn't disable thinking, so on Opus 5 the answer goes to reasoning tokens (swift_mfa returned 0 characters). And baseline judges always see an empty corpus, because gradeText writes to llm_response.txt and the judge excludes .txt. Follow-ups to come; neither is caused by this PR.

  • I described how I tested these changes, or explained why I did not.
  • This change has integration, unit, or performance test coverage, or I explained why it does not.

Mirrors the React MFA eval for mobile: gate a Transfer Funds action behind
step-up (acr_values + max_age 0 through Universal Login) and the amr claim.
Both ship a shared login-only scaffold on Auth0.swift 3.x / Auth0.Android 4.x
and skip the native build, like the mobile quickstarts.

Also backfills the MFA and DPoP rows missing from the eval table, and ignores
the .build / .gradle residue those scaffolds pick up locally.
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review available on request

  • 🔍 Trigger review

Reviews should be triggered manually for repositories with fewer than 10 stars. Select Trigger review above or comment @coderabbitai review to review the latest changes. For a full review, comment @coderabbitai full review.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ddfd5288-60e1-4e6b-a5e2-90b1106f12cb

📝 Walkthrough

Walkthrough

The change adds Android and Swift MFA evaluation prompts and graders, registers new evals, and introduces Auth0 authentication scaffolds for Compose and SwiftUI apps.

Changes

Mobile MFA evaluations

Layer / File(s) Summary
Evaluation catalog and grading contracts
apps/auth0-evals/README.md, apps/auth0-evals/src/evals/mfa/...
The catalog lists new MFA and DPoP evals. Android and Swift prompts define transfer-funds MFA tasks. Graders check Auth0 configuration, MFA claims, secure token handling, current APIs, and implementation structure.
Android Auth0 scaffold
.gitignore, apps/auth0-evals/src/evals/scaffolds/android/auth0/...
The scaffold adds Android project configuration, Auth0 resources, launcher metadata, authentication persistence, login/logout flows, Compose UI, and agent instructions.
Swift Auth0 scaffold
apps/auth0-evals/src/evals/scaffolds/swift/auth0/...
The scaffold adds Swift package and Auth0 configuration, authentication state and credential flows, SwiftUI views, and agent instructions.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

Suggested reviewers: sanchitmehtagit

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding Swift and Android MFA step-up evaluations.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/mobile-mfa-evals

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/auth0-evals/src/evals/mfa/android/PROMPT.md`:
- Line 10: The Android prompt at
apps/auth0-evals/src/evals/mfa/android/PROMPT.md:10-10 must require a new
Universal Login step-up request using the MFA acr_values policy URI and max_age:
0, followed by ID-token amr validation requiring "mfa" before the transfer.
Apply the same requirements to the Swift prompt at
apps/auth0-evals/src/evals/mfa/swift/PROMPT.md:10-10.

In `@apps/auth0-evals/src/evals/scaffolds/swift/auth0/Package.swift`:
- Around line 9-23: Update Package.swift so BarkbookApp is an executable product
and target owning ContentView.swift’s `@main` entry point, and declare the root
Auth0.plist as a processed resource for that target. Ensure the executable
target carries the app’s callback URL scheme configuration while preserving the
Auth0.swift dependency; do not run a Swift build.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 487296ee-0d9f-4b8b-911a-d21d651753f0

📥 Commits

Reviewing files that changed from the base of the PR and between dc076fc and f95be44.

📒 Files selected for processing (16)
  • .gitignore
  • apps/auth0-evals/README.md
  • apps/auth0-evals/src/evals/mfa/android/PROMPT.md
  • apps/auth0-evals/src/evals/mfa/android/graders.ts
  • apps/auth0-evals/src/evals/mfa/swift/PROMPT.md
  • apps/auth0-evals/src/evals/mfa/swift/graders.ts
  • apps/auth0-evals/src/evals/scaffolds/android/auth0/AGENTS.md
  • apps/auth0-evals/src/evals/scaffolds/android/auth0/app/build.gradle
  • apps/auth0-evals/src/evals/scaffolds/android/auth0/app/src/main/AndroidManifest.xml
  • apps/auth0-evals/src/evals/scaffolds/android/auth0/app/src/main/java/com/barkbook/app/MainActivity.kt
  • apps/auth0-evals/src/evals/scaffolds/android/auth0/app/src/main/res/values/strings.xml
  • apps/auth0-evals/src/evals/scaffolds/swift/auth0/AGENTS.md
  • apps/auth0-evals/src/evals/scaffolds/swift/auth0/Auth0.plist
  • apps/auth0-evals/src/evals/scaffolds/swift/auth0/Package.swift
  • apps/auth0-evals/src/evals/scaffolds/swift/auth0/Sources/BarkbookApp/AuthenticationService.swift
  • apps/auth0-evals/src/evals/scaffolds/swift/auth0/Sources/BarkbookApp/ContentView.swift

Comment thread apps/auth0-evals/src/evals/mfa/android/PROMPT.md
Comment thread apps/auth0-evals/src/evals/scaffolds/swift/auth0/Package.swift
'Uses correct multi-factor acr_values policy URI',
GraderLevel.L1,
),
contains('withParameters', 'Passes acr_values through WebAuthProvider withParameters', GraderLevel.L1),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The withParameters check at L1 creates a false-negative risk. Any correct solution that uses only withMaxAge(0) (without calling withParameters) will fail this grader despite being correct. The contains('acr_values', ...) check on line 6 already verifies the parameter is present somewhere — this method-specific check is redundant.

Suggested fix: Remove this line. If you want to verify the current builder API is used, that belongs in an L5 judge.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Checked WebAuthProvider.kt on main: the login builder has withMaxAge(Int) and withParameters(Map<String, Any?>), no singular withParameter and no acr_values builder. So acr_values can only reach the request through withParameters — anything that fails this grader also fails contains('acr_values'). Near-redundant, agreed, but it catches one thing that grader can't: an agent hand-building an /authorize URL with acr_values. Keeping it.

// Auth0.Android has no Credentials.claims property — claims come from credentials.user.
notContains('credentials.claims', 'No hallucinated Credentials.claims property', GraderLevel.L2),
notContains(
'mfaClient',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This grader checks for lowercase 'mfaClient' (variable/method form) but misses the uppercase 'MFAClient' class name. An agent that imports the class without assigning it to a mfaClient-named variable bypasses this check:

import com.auth0.android.provider.MFAClient  // 'mfaClient' never appears → grader passes
val client = MFAClient.create(...)           // same

Suggested fix: Add a second grader after this one:

notContains('MFAClient', 'No hallucinated MFAClient class import', GraderLevel.L2),

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch, and it's worse than the casing: notContains defaults to caseSensitive: true, and the needle names the wrong symbol on each platform. In Auth0.swift MFAClient is a real protocol reached via Auth0.mfa(), so the type may never appear in the code at all; in Auth0.Android the class is MfaApiClient, which mfaclient doesn't match even case-insensitively — so this grader couldn't fire on Android.

Switched both to the one thing every embedded-MFA path must carry, case-insensitive:

notContains(
  'mfaToken',
  'Does not use the embedded MFA grant (MfaApiClient) — wrong approach for a Universal Login app',
  GraderLevel.L2,
  { caseSensitive: false },
),

Went with a replacement rather than a second grader — an L2 slot that can never fail would inflate the Hallucination dimension.


// ── L2: Hallucination / wrong approach ────────────────────────────────
notContains('Auth0SDK', 'No hallucinated Auth0SDK package name (correct package is Auth0)', GraderLevel.L2),
notContains(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same issue as the Android grader — checks for lowercase 'mfaClient' but misses the uppercase 'MFAClient' class name. If an agent imports or references the class by its proper PascalCase name without using a lowercase-named variable, the hallucination goes undetected.

Suggested fix: Add a second grader after this one:

notContains('MFAClient', 'No hallucinated MFAClient class', GraderLevel.L2),

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch, and it's worse than the casing: notContains defaults to caseSensitive: true, and the needle names the wrong symbol on each platform. In Auth0.swift MFAClient is a real protocol reached via Auth0.mfa(), so the type may never appear in the code at all; in Auth0.Android the class is MfaApiClient, which mfaclient doesn't match even case-insensitively — so this grader couldn't fire on Android.

Switched both to the one thing every embedded-MFA path must carry, case-insensitive:

notContains(
  'mfaToken',
  'Does not use the embedded MFA grant (Auth0.mfa()/MFAClient) — wrong approach for a Universal Login app',
  GraderLevel.L2,
  { caseSensitive: false },
),

Went with a replacement rather than a second grader — an L2 slot that can never fail would inflate the Hallucination dimension.


// ── L5: Current API patterns ──────────────────────────────────────────
// `.parameters` / `.maxAge` are the current builders for extra authorization
// parameters. The scaffold's login call uses neither, so this only passes if

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This deterministic matches check is location-agnostic — it only verifies .parameters( or .maxAge( exists somewhere in the workspace, not that it's used in the step-up request specifically. The L4 judge already validates the step-up flow is correct; this check adds little and could fail correct solutions that pass acr_values via a different-but-valid mechanism.

Suggested fix: Either drop this in favor of relying on the L5 judge below it (line 71), or add a comment making the assumption explicit.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Location-agnostic, true, but there's no valid alternative mechanism to miss: Auth0.swift's WebAuth builder has no acr_values method, so .parameters([String: String]) is the only way to pass it, and .maxAge(Int) is the only dedicated max_age builder. The scaffold's login call uses neither, so this can only pass if the agent built the step-up request. Keeping it as the deterministic counterpart to the judge below it.

…raders

notContains is case-sensitive, and 'mfaClient' names neither SDK's symbol — Auth0.swift has an MFAClient protocol reached via Auth0.mfa() (the type is often never named), Auth0.Android has MfaApiClient. Match mfaToken case-insensitively instead, which every embedded-MFA path carries.
@sanchitmehtagit
sanchitmehtagit merged commit 98cd6b9 into main Aug 18, 2026
6 checks passed
@sanchitmehtagit
sanchitmehtagit deleted the feat/mobile-mfa-evals branch August 18, 2026 04:47
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