Skip to content

Security & architecture review

Prepared for: IT / Data Security Team Document version: 1.0 Date: April 2026 Prepared by: DocGuy LLC


Executive summary

JuryBinder is a browser-based Progressive Web App (PWA) designed to assist trial attorneys during voir dire (juror selection). It operates primarily within the user's local browser. All case data — juror names, questionnaire answers, notes, flags, favorability ratings, and strike decisions — remains on the user's device and is never transmitted to any external server.

The application contacts a license server for three narrowly scoped operations (activation, case registration, and token refresh). These requests transmit only the license key and a random identifier. No case data is included in any network request.

This document provides a technical overview of the application's architecture, data handling practices, bundled third-party libraries, network behavior, and instructions for independent verification.


1. Architecture overview

Deployment model

JuryBinder is served as a single HTML file from jurybinder.com/app and can be installed as a PWA on iPad or desktop. It can also be distributed as a standalone .html file for fully airgapped environments.

Property Detail
Delivery format Single .html file, served via HTTPS or opened locally
Server requirement None for case work; license server for activation and case creation only
Internet requirement One-time activation + brief connection per new case; fully offline thereafter
Installation required No — opens in browser; optional PWA install for native app experience
Operating system Any (Windows, macOS, iPadOS)
Browser requirement Chrome 86+, Edge 86+, or Safari (iOS 15.2+)

What is inside the application

The application file contains three types of content bundled together at build time:

  1. Application logic — Pre-compiled JavaScript (React UI framework) that renders the attorney's interface
  2. UI framework libraries — React 18.2.0 and ReactDOM 18.2.0 (both MIT-licensed), bundled inline
  3. Spreadsheet library — SheetJS 0.18.5 (Apache 2.0), bundled inline for reading and writing Excel files
  4. Archive library — JSZip 3.10.1 (MIT), bundled inline for reading and writing .jbinder case bundles
  5. Patch library — fast-json-patch 3.1.1 (MIT), bundled inline for RFC 6902 mutation logging
  6. Embedded public key — RSA-2048 public key for verifying license server JWT signatures

No code is downloaded at runtime. No external scripts, fonts, images, or stylesheets are fetched from the internet.

Browser sandbox

The application runs inside the browser's built-in security sandbox. This means:

  • It cannot access the file system except through explicit, user-initiated dialogs
  • It cannot communicate with other processes on the machine
  • It cannot execute native code
  • It cannot escalate privileges

2. Data handling

Where data lives

All case data (juror names, questionnaire answers, attorney notes, flags, favorability ratings, flag rules, strike decisions) is stored exclusively in two places:

  1. Browser memory — while the application is open in the browser tab
  2. Local files on the user's device — when the user explicitly opens, saves, or exports a file

A working copy is also maintained in the browser's Origin Private File System (OPFS), a sandboxed storage area managed by the browser that is inaccessible to other websites and applications. This working copy serves as a crash-recovery buffer and is overwritten each time the user opens a new case.

Neither location involves any external system.

The .jbinder file format

Case data is stored in a .jbinder file — a standard ZIP archive (similar to .docx or .xlsx) containing:

File Contents
manifest.json Case metadata: name, ID, creation date, schema version
config.json Jury size, strike limits, flag rule definitions, oral question definitions
spreadsheet.xlsx Current juror data snapshot (questionnaire answers and status only)
users/{uid}.json Per-contributor mutation log with timestamps and JSON Patch operations

The file is created, read, and written entirely on the user's device. It is never transmitted to JuryBinder's infrastructure.

Local storage usage

The application uses the browser's localStorage for a small number of non-case-data values:

Key Contents Contains case data?
jurybinder_v5_identity Device UUID, display name, email, attribution color No
jurybinder_v5_license_key License key string No
jurybinder_v5_license_token JWT session token (30-day expiry) No
jurybinder_v5_opfs_meta Working copy metadata (case name, last-saved timestamp) No — metadata only
jb_rule_library Saved flag rule templates for reuse across cases No — rule definitions only
jb_byJurorShowFlagged UI preference No
jb_onboarding_* Onboarding dismissal states No

No juror names, questionnaire answers, notes, flags, favorability ratings, or strike decisions are stored in localStorage.

What the application does NOT do

The following capabilities were scanned and confirmed absent from the application source code:

Capability API / Pattern Present in app?
Legacy HTTP requests XMLHttpRequest No
Server-based real-time transmission WebSocket No
Background analytics or telemetry navigator.sendBeacon No
Browser cookie storage document.cookie No
Session browser storage sessionStorage No
Dynamic code execution eval() No
Dynamic code execution new Function() No (appears once in JSZip library internals; not in application code)

Local sync APIs

JuryBinder uses two additional APIs for its real-time sync feature. Both are strictly peer-to-peer — no server receives or relays case data.

API Purpose Data transmitted to a server?
RTCPeerConnection / RTCDataChannel WebRTC peer-to-peer data channel for syncing mutations between devices on the same WiFi network No — data travels directly between devices. DTLS encryption is mandatory.
CoreBluetooth (via Capacitor, iOS app only) BLE GATT service for syncing mutations between devices over Bluetooth No — Bluetooth is a local radio protocol with ~30ft range. No network involved.

In both cases, mutations (JSON Patch operations) are exchanged directly between devices. The sync session exists only between connected devices and uses no intermediary infrastructure.

What the application DOES do (network)

The application uses the fetch() API for three narrowly scoped license server calls. These are documented in detail in Section 4. No case data is included in any of these requests.


3. Bundled third-party libraries

All libraries are included verbatim from their official published distributions. No modifications have been made to any library.

Library Version License Role
React 18.2.0 MIT UI framework (rendering)
ReactDOM 18.2.0 MIT UI framework (DOM rendering)
SheetJS (xlsx) 0.18.5 Apache 2.0 Excel file read/write
JSZip 3.10.1 MIT ZIP archive read/write for .jbinder format
fast-json-patch 3.1.1 MIT RFC 6902 JSON Patch for mutation logging

License compliance

  • MIT License — permits commercial use, distribution, modification, and private use. No restrictions beyond preserving the license notice.
  • Apache 2.0 License — permits commercial use, distribution, modification, and private use. Requires preservation of copyright and license notices (fulfilled by their inclusion in the bundled library source).

4. Network requests — complete inventory

JuryBinder contacts the license server in exactly three situations. The server is hosted on Google Cloud (Firebase Cloud Functions) and communicates over HTTPS.

4.1 License activation

When: User enters a license key for the first time.

Request:

POST /api/license/activate
Content-Type: application/json

{
  "license_key": "JB-XXXX-XXXX-XXXX-XXXX",
  "uid": "550e8400-e29b-41d4-a716-446655440000",
  "app_version": "5.1.0"
}

Data sent: License key, a randomly generated device UUID (no semantic content), and the app version number.

Response: A signed JWT session token valid for 30 days.

Case data sent: None.

4.2 New case registration

When: User selects New Case (consumes one trial credit).

Request:

POST /api/trial/register
Content-Type: application/json

{
  "license_key": "JB-XXXX-XXXX-XXXX-XXXX",
  "trial_id": "a7f3c8e2-1b2d-4c9f-a4b5-c6d7e8f9g0h1",
  "app_version": "5.1.0"
}

Data sent: License key, a randomly generated case UUID (no connection to case name, jurors, or content), and the app version number.

Server action: Atomically decrements the license's credit count and registers the trial UUID.

Response: An updated JWT session token with decremented credit count.

Case data sent: None.

4.3 Thirty-day token refresh

When: Session token approaches expiration (checked automatically on app load).

Request:

POST /api/license/checkin
Content-Type: application/json

{
  "license_key": "JB-XXXX-XXXX-XXXX-XXXX",
  "app_version": "5.1.0"
}

Data sent: License key and app version number.

Response: A fresh 30-day JWT session token.

Case data sent: None.

Network request summary

Request Trigger Data sent Case data?
Activate First launch License key + device UUID No
Register trial New Case button License key + random case UUID No
Check-in Approaching token expiry License key No

In all three cases, the response is a signed JWT token. No case content is ever included in any request or response.


5. Cryptographic verification

JWT session tokens

Session tokens are signed by the license server using RS256 (RSA-2048 + SHA-256) and verified client-side using an embedded public key.

Property Value
Algorithm RSASSA-PKCS1-v1_5 with SHA-256
Key size 2048 bits
Token lifetime 30 days
Issuer claim jurybinder.com

The application imports the public key via the Web Crypto API (crypto.subtle.importKey) and verifies every token signature before trusting its claims. The private key is stored in Google Cloud Secret Manager and is never present in client-side code.

Random value generation

  • Device UUID: Generated via crypto.randomUUID() (falls back to crypto.getRandomValues() in HTTP contexts)
  • Case UUID: Generated via crypto.randomUUID()
  • License keys (server-side): Generated via Node.js crypto.randomBytes() using a base32 alphabet that excludes ambiguous characters

6. Service worker and offline operation

JuryBinder registers a service worker scoped to /app/ that caches the application shell for offline use.

Behavior Detail
App shell caching Cache-first with background revalidation
License server requests Network-only (never cached)
Cross-origin requests Ignored entirely (never intercepted)
Case data Never cached by service worker
Cache versioning Bumped on every release; old caches deleted on activation

The service worker enables the application to load and function without any internet connection after the initial visit. It caches only the application code, never case data.


7. File integrity verification

To confirm that a distributed copy of JuryBinder has not been modified, verify its SHA-256 hash against the published value.

How to verify — macOS / Linux (Terminal)

shasum -a 256 /path/to/JuryBinder_v5.html

How to verify — Windows (PowerShell)

Get-FileHash "C:\Path\To\JuryBinder_v5.html" -Algorithm SHA256

Compare the output to the hash published at jurybinder.com/verify. If the hashes match, the file is byte-for-byte identical to the reviewed version. If they do not match, the file should not be used until the discrepancy is investigated.


8. Independent security scan

To independently confirm no undocumented network or storage APIs are present, the following commands scan the application file directly.

macOS / Linux (Terminal)

FILE="JuryBinder_v5.html"
for pattern in "XMLHttpRequest" "WebSocket" "sendBeacon" \
               "document.cookie" "sessionStorage" \
               "eval(" "new Function("; do
    count=$(grep -c "$pattern" "$FILE" 2>/dev/null || echo 0)
    echo "$pattern -> $count occurrences"
done
echo "---"
echo "fetch( -> $(grep -c 'fetch(' "$FILE") occurrences (expected: 3, license server only)"
echo "localStorage -> $(grep -c 'localStorage' "$FILE") occurrences (expected: identity/license/prefs only)"

Expected output

All patterns except fetch( and localStorage should show zero occurrences (or one occurrence of new Function( inside the JSZip library). The fetch( count should be 3, corresponding to the three license server calls documented in Section 4.


9. Payment processing

License purchases are processed entirely by Stripe via Embedded Checkout. JuryBinder's server:

  • Never receives or stores credit card numbers, CVVs, or bank account information
  • Receives only the customer email and selected plan from Stripe's webhook
  • Generates and emails the license key after Stripe confirms payment
  • Verifies webhook signatures using Stripe's signing secret

The customer's payment information exists only within Stripe's PCI-compliant infrastructure.


10. Security summary

Concern Assessment
Case data transmitted over the network No — zero case data in any network request. Local sync uses Bluetooth or direct WiFi (peer-to-peer, no server)
Case data stored in the cloud No — data lives only on the user's device
Application requires internet during trial No — fully offline after initial activation
Application installs native software No — runs in browser; optional PWA install
Application accesses the file system broadly No — only files explicitly selected by the user via OS dialog
Third-party code loaded at runtime No — all libraries bundled at build time
Analytics or telemetry collected No — no tracking of any kind
Dynamic code execution (eval) No — not present in application code
Cookies used No
Third-party libraries modified No — all used verbatim from official distributions
Network calls documented and auditable Yes — three calls, all to license server, all HTTPS
JWT tokens cryptographically verified Yes — RS256 with embedded public key
Application independently auditable Yes — file integrity via SHA-256; code scan via grep
Open-source components used Yes — React (MIT), ReactDOM (MIT), SheetJS (Apache 2.0), JSZip (MIT), fast-json-patch (MIT)

11. Questions and contact

Questions about this document or the application's security architecture may be directed to support@jurybinder.com. The application source code is available for review upon request.


This document was prepared to support IT security review processes. It does not constitute a formal security certification.