Skip to content

fix: verify all adjacent certificate chain links in browser MDA validation#130

Open
antojoseph wants to merge 2 commits into
masterfrom
security/f006-mda-cert-chain-validation
Open

fix: verify all adjacent certificate chain links in browser MDA validation#130
antojoseph wants to merge 2 commits into
masterfrom
security/f006-mda-cert-chain-validation

Conversation

@antojoseph
Copy link
Copy Markdown

Summary

  • console-ui/src/lib/cert-verify.ts: step 3 now iterates all adjacent pairs certs[i].verify(certs[i+1]) instead of only checking leaf → certs[1]
  • Step label updated to "Verifying certificate chain links"

Security impact

The previous code only verified that the leaf was signed by certs[1], and separately that the topmost cert was signed by Apple's root. A crafted chain like [forged_leaf, attacker_CA, Apple_root] — where forged_leaf is signed by attacker_CA and attacker_CA is self-signed — would pass both checks without attacker_CA being validated against Apple_root. The fix closes this gap by verifying every link in the chain before accepting it.

This is a client-side verification displayed in the UI's "Verify Device" panel, so exploitation requires serving a forged cert chain to a user who clicks "Verify."

Test plan

  • npx tsc --noEmit — no new type errors in cert-verify.ts
  • Manual: paste a valid 3-cert Apple MDA chain and verify all steps pass
  • Manual: insert a self-signed intermediate and verify step 3 fails

…ation

Previously only verified leaf→certs[1] and topCert→AppleRoot, leaving
intermediate links unchecked. A crafted chain [forged_leaf, attacker_CA,
Apple_root] would pass both checks without attacker_CA being signed by
Apple root. Now iterates all adjacent pairs before accepting the chain.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 4, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
d-inference Ready Ready Preview May 15, 2026 8:09pm
d-inference-console-ui-dev Ready Ready Preview May 15, 2026 8:09pm
d-inference-landing Ready Ready Preview May 15, 2026 8:09pm

Request Review

Copy link
Copy Markdown
Contributor

@hankbobtheresearchoor hankbobtheresearchoor left a comment

Choose a reason for hiding this comment

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

LGTM. This closes the browser-side MDA chain gap by checking each adjacent certificate signature before the Apple root check.

@vercel
Copy link
Copy Markdown

vercel Bot commented May 15, 2026

Deployment failed with the following error:

You don't have permission to create a Preview Deployment for this Vercel project: d-inference-landing.

View Documentation: https://vercel.com/docs/accounts/team-members-and-roles

@vercel
Copy link
Copy Markdown

vercel Bot commented May 15, 2026

Deployment failed with the following error:

You don't have permission to create a Preview Deployment for this Vercel project: d-inference.

View Documentation: https://vercel.com/docs/accounts/team-members-and-roles

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 15, 2026

Benchmark Results

Runner: macos-15 (M1 Virtual) | Date: 2026-05-15 20:17 UTC

1-provider-streaming

1 providers, 1 users, 30 requests, concurrency=5, streaming=true

Model Providers RAM
mlx-community/Qwen3.5-0.8B-MLX-4bit 1 0.5 GB
Metric Value
Total Requests 30
Success 30
Errors 0
Total Duration 18.12s
Throughput 1.7 req/s

Latency Decomposition

Segment Count Mean P50 P95 Max
total_e2e 30 1.351s 13ms 5.695s 12.61s
parse 30 63µs 35µs 268µs 292µs
reserve 30 3ms 2ms 12ms 13ms
route 30 582ms 0s 1.518s 12.572s
queue_wait 8 2.184s 648ms 12.573s 12.573s
encrypt 30 0s 0s 1ms 1ms
dispatch 30 95µs 70µs 234µs 880µs
coordinator_to_provider 30 761ms 7ms 5.675s 5.675s

Assertion Report: PASS

Assertion Result Detail
parse:mean<=1ms PASS mean=63.2µs (threshold=1ms)
parse:p95<=5ms PASS p95=268µs (threshold=5ms)
reserve:mean<=50ms PASS mean=2.986733ms (threshold=50ms)
reserve:p95<=200ms PASS p95=11.68ms (threshold=200ms)
encrypt:mean<=5ms PASS mean=335.933µs (threshold=5ms)
encrypt:p95<=50ms PASS p95=643µs (threshold=50ms)
dispatch:mean<=5ms PASS mean=95.366µs (threshold=5ms)
dispatch:p95<=50ms PASS p95=234µs (threshold=50ms)

1-provider-non-streaming

1 providers, 1 users, 20 requests, concurrency=5, streaming=false

Model Providers RAM
mlx-community/Qwen3.5-0.8B-MLX-4bit 1 0.5 GB
Metric Value
Total Requests 20
Success 20
Errors 0
Total Duration 9.873s
Throughput 2.0 req/s

Latency Decomposition

Segment Count Mean P50 P95 Max
total_e2e 20 2.429s 798ms 7.543s 7.543s
parse 20 38µs 33µs 161µs 161µs
reserve 20 3ms 2ms 13ms 13ms
route 20 452ms 0s 6.729s 6.729s
queue_wait 4 2.259s 789ms 6.729s 6.729s
encrypt 20 256µs 213µs 538µs 538µs
dispatch 20 61µs 45µs 174µs 174µs
coordinator_to_provider 20 1.133s 7ms 5.648s 5.648s

Assertion Report: PASS

Assertion Result Detail
parse:mean<=1ms PASS mean=37.6µs (threshold=1ms)
parse:p95<=5ms PASS p95=161µs (threshold=5ms)
reserve:mean<=50ms PASS mean=3.2282ms (threshold=50ms)
reserve:p95<=200ms PASS p95=12.678ms (threshold=200ms)
encrypt:mean<=5ms PASS mean=256.4µs (threshold=5ms)
encrypt:p95<=50ms PASS p95=538µs (threshold=50ms)
dispatch:mean<=5ms PASS mean=60.5µs (threshold=5ms)
dispatch:p95<=50ms PASS p95=174µs (threshold=50ms)

7-provider-multi-model

7 providers, 5 users, 50 requests, concurrency=10, streaming=true

Model Providers RAM
mlx-community/Qwen3.5-0.8B-MLX-4bit 4 0.5 GB
mlx-community/gemma-3-270m-4bit 3 0.2 GB
Metric Value
Total Requests 50
Success 48
Errors 2
Total Duration 1m13.835s
Throughput 0.7 req/s

Latency Decomposition

Segment Count Mean P50 P95 Max
total_e2e 48 5.565s 87ms 36.205s 36.817s
parse 48 58µs 47µs 145µs 243µs
reserve 48 13ms 5ms 51ms 85ms
route 48 909ms 0s 4.507s 20.009s
queue_wait 6 3.942s 4.442s 4.923s 4.923s
encrypt 48 0s 0s 1ms 1ms
dispatch 48 0s 0s 0s 3ms
coordinator_to_provider 48 4.632s 12ms 36.123s 36.783s

Assertion Report: PASS

Assertion Result Detail
parse:mean<=1ms PASS mean=58.437µs (threshold=1ms)
parse:p95<=5ms PASS p95=145µs (threshold=5ms)
reserve:mean<=50ms PASS mean=12.699375ms (threshold=50ms)
reserve:p95<=200ms PASS p95=50.597ms (threshold=200ms)
encrypt:mean<=5ms PASS mean=281.791µs (threshold=5ms)
encrypt:p95<=50ms PASS p95=723µs (threshold=50ms)
dispatch:mean<=5ms PASS mean=142.562µs (threshold=5ms)
dispatch:p95<=50ms PASS p95=226µs (threshold=50ms)

3-provider-high-concurrency

3 providers, 10 users, 60 requests, concurrency=20, streaming=true

Model Providers RAM
mlx-community/Qwen3.5-0.8B-MLX-4bit 3 0.5 GB
Metric Value
Total Requests 60
Success 60
Errors 0
Total Duration 15.522s
Throughput 3.9 req/s

Latency Decomposition

Segment Count Mean P50 P95 Max
total_e2e 60 3.255s 2.347s 9.141s 9.143s
parse 60 67µs 39µs 156µs 933µs
reserve 60 13ms 6ms 55ms 58ms
route 60 2.04s 854ms 8.985s 9s
queue_wait 45 2.72s 1.414s 8.985s 9.001s
encrypt 60 256µs 188µs 501µs 636µs
dispatch 60 0s 0s 1ms 1ms
coordinator_to_provider 60 1.164s 23ms 5.805s 5.856s

Assertion Report: PASS

Assertion Result Detail
parse:mean<=1ms PASS mean=67.05µs (threshold=1ms)
parse:p95<=5ms PASS p95=156µs (threshold=5ms)
reserve:mean<=50ms PASS mean=12.7208ms (threshold=50ms)
reserve:p95<=200ms PASS p95=54.816ms (threshold=200ms)
encrypt:mean<=5ms PASS mean=255.916µs (threshold=5ms)
encrypt:p95<=50ms PASS p95=501µs (threshold=50ms)
dispatch:mean<=5ms PASS mean=94.85µs (threshold=5ms)
dispatch:p95<=50ms PASS p95=832µs (threshold=50ms)

1-provider-queue-saturation

1 providers, 10 users, 40 requests, concurrency=15, streaming=true

Model Providers RAM
mlx-community/Qwen3.5-0.8B-MLX-4bit 1 0.5 GB
Metric Value
Total Requests 40
Success 40
Errors 0
Total Duration 15.075s
Throughput 2.7 req/s

Latency Decomposition

Segment Count Mean P50 P95 Max
total_e2e 40 4.209s 2.965s 8.572s 8.573s
parse 40 89µs 46µs 436µs 573µs
reserve 40 8ms 5ms 26ms 27ms
route 40 3.648s 2.63s 8.491s 8.494s
queue_wait 35 4.17s 2.926s 8.491s 8.494s
encrypt 40 0s 0s 1ms 1ms
dispatch 40 0s 0s 0s 1ms
coordinator_to_provider 40 531ms 12ms 5.208s 5.208s

Assertion Report: PASS

Assertion Result Detail
parse:mean<=1ms PASS mean=88.925µs (threshold=1ms)
parse:p95<=5ms PASS p95=436µs (threshold=5ms)
reserve:mean<=50ms PASS mean=7.842475ms (threshold=50ms)
reserve:p95<=200ms PASS p95=26.372ms (threshold=200ms)
encrypt:mean<=5ms PASS mean=253.625µs (threshold=5ms)
encrypt:p95<=50ms PASS p95=1.134ms (threshold=50ms)
dispatch:mean<=5ms PASS mean=97.225µs (threshold=5ms)
dispatch:p95<=50ms PASS p95=402µs (threshold=50ms)

3-provider-20-users

3 providers, 20 users, 60 requests, concurrency=10, streaming=true

Model Providers RAM
mlx-community/Qwen3.5-0.8B-MLX-4bit 3 0.5 GB
Metric Value
Total Requests 60
Success 60
Errors 0
Total Duration 16.387s
Throughput 3.7 req/s

Latency Decomposition

Segment Count Mean P50 P95 Max
total_e2e 60 1.418s 335ms 7.495s 7.51s
parse 60 55µs 36µs 237µs 411µs
reserve 60 5ms 3ms 19ms 25ms
route 60 216ms 0s 767ms 925ms
queue_wait 26 499ms 527ms 925ms 925ms
encrypt 60 238µs 158µs 537µs 792µs
dispatch 60 0s 0s 3ms 7ms
coordinator_to_provider 60 1.187s 8ms 7.435s 7.449s

Assertion Report: PASS

Assertion Result Detail
parse:mean<=1ms PASS mean=55.366µs (threshold=1ms)
parse:p95<=5ms PASS p95=237µs (threshold=5ms)
reserve:mean<=50ms PASS mean=5.184233ms (threshold=50ms)
reserve:p95<=200ms PASS p95=18.612ms (threshold=200ms)
encrypt:mean<=5ms PASS mean=238.116µs (threshold=5ms)
encrypt:p95<=50ms PASS p95=537µs (threshold=50ms)
dispatch:mean<=5ms PASS mean=265.066µs (threshold=5ms)
dispatch:p95<=50ms PASS p95=2.843ms (threshold=50ms)

1-provider-scaling

1 providers, 5 users, 30 requests, concurrency=10, streaming=true

Model Providers RAM
mlx-community/Qwen3.5-0.8B-MLX-4bit 1 0.5 GB
Metric Value
Total Requests 30
Success 30
Errors 0
Total Duration 14.069s
Throughput 2.1 req/s

Latency Decomposition

Segment Count Mean P50 P95 Max
total_e2e 30 3.346s 1.951s 8.576s 8.704s
parse 30 85µs 48µs 377µs 496µs
reserve 30 9ms 7ms 24ms 24ms
route 30 2.538s 1.81s 8.524s 8.644s
queue_wait 23 3.31s 1.885s 8.524s 8.645s
encrypt 30 246µs 198µs 543µs 560µs
dispatch 30 55µs 34µs 219µs 271µs
coordinator_to_provider 30 787ms 10ms 5.791s 5.792s

Assertion Report: PASS

Assertion Result Detail
parse:mean<=1ms PASS mean=85.166µs (threshold=1ms)
parse:p95<=5ms PASS p95=377µs (threshold=5ms)
reserve:mean<=50ms PASS mean=9.3668ms (threshold=50ms)
reserve:p95<=200ms PASS p95=24.135ms (threshold=200ms)
encrypt:mean<=5ms PASS mean=246.233µs (threshold=5ms)
encrypt:p95<=50ms PASS p95=543µs (threshold=50ms)
dispatch:mean<=5ms PASS mean=55.2µs (threshold=5ms)
dispatch:p95<=50ms PASS p95=219µs (threshold=50ms)

3-provider-scaling

3 providers, 5 users, 30 requests, concurrency=10, streaming=true

Model Providers RAM
mlx-community/Qwen3.5-0.8B-MLX-4bit 3 0.5 GB
Metric Value
Total Requests 30
Success 30
Errors 0
Total Duration 16.449s
Throughput 1.8 req/s

Latency Decomposition

Segment Count Mean P50 P95 Max
total_e2e 30 3.103s 21ms 9.367s 9.368s
parse 30 51µs 43µs 99µs 195µs
reserve 30 9ms 5ms 35ms 36ms
route 30 65µs 40µs 176µs 411µs
encrypt 30 270µs 209µs 509µs 513µs
dispatch 30 64µs 57µs 130µs 251µs
coordinator_to_provider 30 3.085s 12ms 9.337s 9.343s

Assertion Report: PASS

Assertion Result Detail
parse:mean<=1ms PASS mean=50.9µs (threshold=1ms)
parse:p95<=5ms PASS p95=99µs (threshold=5ms)
reserve:mean<=50ms PASS mean=9.339666ms (threshold=50ms)
reserve:p95<=200ms PASS p95=34.717ms (threshold=200ms)
encrypt:mean<=5ms PASS mean=270.066µs (threshold=5ms)
encrypt:p95<=50ms PASS p95=509µs (threshold=50ms)
dispatch:mean<=5ms PASS mean=63.766µs (threshold=5ms)
dispatch:p95<=50ms PASS p95=130µs (threshold=50ms)

5-provider-scaling

5 providers, 5 users, 30 requests, concurrency=10, streaming=true

Model Providers RAM
mlx-community/Qwen3.5-0.8B-MLX-4bit 5 0.5 GB
Metric Value
Total Requests 30
Success 30
Errors 0
Total Duration 21.58s
Throughput 1.4 req/s

Latency Decomposition

Segment Count Mean P50 P95 Max
total_e2e 30 4.471s 11ms 13.436s 13.436s
parse 30 138µs 51µs 835µs 853µs
reserve 30 10ms 3ms 35ms 35ms
route 30 1.668s 0s 10.006s 10.006s
encrypt 30 246µs 159µs 698µs 830µs
dispatch 30 95µs 52µs 354µs 807µs
coordinator_to_provider 30 2.783s 6ms 13.379s 13.405s

Assertion Report: PASS

Assertion Result Detail
parse:mean<=1ms PASS mean=137.666µs (threshold=1ms)
parse:p95<=5ms PASS p95=835µs (threshold=5ms)
reserve:mean<=50ms PASS mean=9.5872ms (threshold=50ms)
reserve:p95<=200ms PASS p95=34.604ms (threshold=200ms)
encrypt:mean<=5ms PASS mean=246.133µs (threshold=5ms)
encrypt:p95<=50ms PASS p95=698µs (threshold=50ms)
dispatch:mean<=5ms PASS mean=94.766µs (threshold=5ms)
dispatch:p95<=50ms PASS p95=354µs (threshold=50ms)

3-provider-heavy-100conc-10kb

3 providers, 20 users, 100 requests, concurrency=100, streaming=true

Model Providers RAM
mlx-community/Qwen3.5-0.8B-MLX-4bit 3 0.5 GB
Metric Value
Total Requests 100
Success 100
Errors 0
Total Duration 21.732s
Throughput 4.6 req/s

Latency Decomposition

Segment Count Mean P50 P95 Max
total_e2e 100 13.152s 13.134s 20.684s 21.205s
parse 100 1ms 0s 4ms 47ms
reserve 100 82ms 85ms 123ms 153ms
route 100 12.296s 12.848s 20.466s 20.956s
queue_wait 88 13.973s 14.234s 20.466s 20.956s
encrypt 100 1ms 0s 2ms 4ms
dispatch 100 0s 0s 1ms 11ms
coordinator_to_provider 100 698ms 35ms 5.619s 5.691s

Assertion Report: FAIL

Assertion Result Detail
parse:mean<=1ms FAIL mean=1.24168ms (threshold=1ms)
parse:p95<=5ms PASS p95=3.943ms (threshold=5ms)
reserve:mean<=50ms FAIL mean=82.0268ms (threshold=50ms)
reserve:p95<=200ms PASS p95=122.922ms (threshold=200ms)
encrypt:mean<=5ms PASS mean=508.33µs (threshold=5ms)
encrypt:p95<=50ms PASS p95=1.646ms (threshold=50ms)
dispatch:mean<=5ms PASS mean=334.16µs (threshold=5ms)
dispatch:p95<=50ms PASS p95=1.043ms (threshold=50ms)

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.

3 participants