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:
- Application logic — Pre-compiled JavaScript (React UI framework) that renders the attorney's interface
- UI framework libraries — React 18.2.0 and ReactDOM 18.2.0 (both MIT-licensed), bundled inline
- Spreadsheet library — SheetJS 0.18.5 (Apache 2.0), bundled inline for reading and writing Excel files
- Archive library — JSZip 3.10.1 (MIT), bundled inline for reading and writing
.jbindercase bundles - Patch library — fast-json-patch 3.1.1 (MIT), bundled inline for RFC 6902 mutation logging
- 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:
- Browser memory — while the application is open in the browser tab
- 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 tocrypto.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)¶
How to verify — Windows (PowerShell)¶
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.