<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Binimise Labs</title>
    <description>The latest articles on DEV Community by Binimise Labs (@binimise_labs).</description>
    <link>https://dev.to/binimise_labs</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4138007%2Fc00248fb-857d-4499-bc91-f1da71ca0035.png</url>
      <title>DEV Community: Binimise Labs</title>
      <link>https://dev.to/binimise_labs</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/binimise_labs"/>
    <language>en</language>
    <item>
      <title>Why Most "Liveness Detection" SDKs Can Be Bypassed With an iPhone Live Photo (And How We Fixed It)</title>
      <dc:creator>Binimise Labs</dc:creator>
      <pubDate>Thu, 24 Sep 2026 16:37:55 +0000</pubDate>
      <link>https://dev.to/binimise_labs/why-most-liveness-detection-sdks-can-be-bypassed-with-an-iphone-live-photo-and-how-we-fixed-it-1gn1</link>
      <guid>https://dev.to/binimise_labs/why-most-liveness-detection-sdks-can-be-bypassed-with-an-iphone-live-photo-and-how-we-fixed-it-1gn1</guid>
      <description>&lt;p&gt;The problem nobody talks about in face verification&lt;/p&gt;

&lt;p&gt;Most face-verification flows check one thing: is there a face in the frame that matches the enrolled user? They don't seriously check is this a live human being, right now, in front of the camera?&lt;/p&gt;

&lt;p&gt;That gap is exactly what attackers exploit. Hold up an iPhone Live Photo (it has subtle motion baked in), print a 3D paper frame, or replay a screen recording on a 4K monitor — and a lot of "liveness" SDKs wave you through.&lt;/p&gt;

&lt;p&gt;We ran into this building attendance and identity-verification features for field workforce apps, and ended up writing our own on-device liveness + matching engine for Flutter. Here's the technical breakdown of how it works, and what we learned building something that has to run fully offline, in under 300ms, on a mid-range Android phone.&lt;/p&gt;

&lt;p&gt;Why offline-first changes the architecture&lt;/p&gt;

&lt;p&gt;Cloud-based liveness APIs are the default because they're easy to build. Upload a frame, get a score back. But that approach has three real costs:&lt;/p&gt;

&lt;p&gt;Latency — a round trip adds 500ms–2s depending on network, which kills conversion on check-in flows&lt;br&gt;
Privacy — you're shipping someone's face to a server, which is a GDPR/compliance headache the moment you touch EU or health-adjacent users&lt;br&gt;
Availability — field workers in low-connectivity zones (warehouses, rural sites, basements) can't verify at all&lt;/p&gt;

&lt;p&gt;So we built the entire pipeline to run on-device:&lt;/p&gt;

&lt;p&gt;Frame Capture → Edge AI Inference (TFLite) → Biometric Vector Validation&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Frame Capture — the SDK opens the front camera and streams raw frames locally inside the widget. No frame ever leaves the device.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Edge AI Inference — lightweight, encrypted TFLite models detect the face, analyze passive 3D depth landmarks, and score spoof features (texture, reflection, depth consistency) — all in the same pass.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Vector Validation — the face is converted into a 512-dimension embedding and matched 1:1 or 1:N against locally stored template hashes. Raw images are never persisted; only the encrypted vector hash is kept.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What "passive" liveness actually means&lt;/p&gt;

&lt;p&gt;Active liveness ("blink now," "turn your head") is annoying and still spoofable with enough effort. Passive liveness analyzes a single camera session for signals a photo or screen literally cannot reproduce:&lt;/p&gt;

&lt;p&gt;Depth micro-structure — real skin has depth variance a flat image or screen doesn't&lt;br&gt;
Reflection physics — screens and glossy photo paper reflect light differently than skin&lt;br&gt;
Texture entropy — printed/replayed faces have compression and moiré artifacts at the pixel level&lt;/p&gt;

&lt;p&gt;We map roughly 50,000 depth points onto the detected face per frame to make this robust against extreme angles, low light, and glasses — without asking the user to do anything.&lt;/p&gt;

&lt;p&gt;Integrating it in Flutter&lt;br&gt;
dart&lt;br&gt;
import 'package:binimise_biometric/binimise_biometric.dart';&lt;/p&gt;

&lt;p&gt;final result = await BinimiseBiometric.runLivenessCheck();&lt;/p&gt;

&lt;p&gt;if (result.isLive) {&lt;br&gt;
  final match = await BinimiseBiometric.matchFace(&lt;br&gt;
    templateId: enrolledUserId,&lt;br&gt;
  );&lt;br&gt;
  print('Match confidence: ${match.confidenceScore}');&lt;br&gt;
} else {&lt;br&gt;
  print('Spoof attempt blocked: ${result.spoofType}');&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Both liveness and face classification happen in the same camera session — no secondary prompt, no "now scan again" screen. That single decision made the biggest difference to our drop-off rate in testing.&lt;/p&gt;

&lt;p&gt;Specs, for the skeptics&lt;br&gt;
Platform    Min version Architectures&lt;br&gt;
Android API 24+ (7.0)   arm64-v8a, armeabi-v7a, x86_64&lt;br&gt;
iOS 14.0+   Physical (arm64), Simulator (arm64, x86_64)&lt;br&gt;
Liveness check: &amp;lt; 300ms&lt;br&gt;
1:1 / 1:N face match: &amp;lt; 1ms (after enrollment)&lt;br&gt;
Zero network calls at runtime&lt;br&gt;
No raw image persistence — GDPR-friendly by design&lt;br&gt;
What it stops (and what it doesn't try to)&lt;/p&gt;

&lt;p&gt;It's built specifically against the attacks that beat naive systems: iPhone Live Photos, printed 3D frames, silicone masks, and screen replays (including 4K/8K playback). It also blocks rooted/compromised devices from completing verification at all. It is not trying to be a full KYC/document-verification suite — it's a focused liveness + matching layer you drop into an existing auth or attendance flow.&lt;/p&gt;

&lt;p&gt;Try it&lt;/p&gt;

&lt;p&gt;The SDK is free to try, no credit card:&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/binimiselabs/binimise_biometric" rel="noopener noreferrer"&gt;https://github.com/binimiselabs/binimise_biometric&lt;/a&gt;&lt;br&gt;
Demo app: &lt;a href="https://play.google.com/store/apps/details?id=com.liveness.liveness_app" rel="noopener noreferrer"&gt;https://play.google.com/store/apps/details?id=com.liveness.liveness_app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you're building anything with attendance, KYC, or repeat identity checks in Flutter, I'd genuinely like feedback on where this breaks — spoof detection is an arms race and outside testing is how you find the gaps.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>mobilesecurity</category>
      <category>biometric</category>
      <category>machinelearning</category>
    </item>
  </channel>
</rss>
