dynos_sync is subjected to a 140-test security audit across 13 categories before every release. This document describes what is tested and why.
Test file: test/dynos_sync_total_audit_test.dart
| Category | Tests | Severity coverage |
|---|---|---|
| 1. HIPAA & Health Data Compliance | 1--12 | 6 CRITICAL, 3 HIGH, 2 MEDIUM, 1 LOW |
| 2. Data Leak Testing | 13--25 | 2 CRITICAL, 4 HIGH, 4 MEDIUM, 3 LOW |
| 3. Injection & Input Validation | 26--37 | 2 CRITICAL, 4 HIGH, 5 MEDIUM, 1 LOW |
| 4. Authentication & Authorization | 38--47 | 4 CRITICAL, 2 HIGH, 2 MEDIUM, 2 LOW |
| 5. Conflict Resolution Integrity | 48--56 | 3 CRITICAL, 3 HIGH, 2 MEDIUM, 1 LOW |
| 6. Flood Testing & Performance | 57--75 | 3 CRITICAL, 5 HIGH, 7 MEDIUM, 4 LOW |
| 7. Cryptographic Validation | 76--83 | Structural (engine delegates crypto) |
| 8. Race Conditions & Concurrency | 84--91 | 2 CRITICAL, 3 HIGH, 2 MEDIUM, 1 LOW |
| 9. Chaos Engineering & Edge Cases | 92--107 | 4 CRITICAL, 4 HIGH, 6 MEDIUM, 2 LOW |
| 10. Denial of Service & Abuse | 108--114 | 1 CRITICAL, 3 HIGH, 2 MEDIUM, 1 LOW |
| 11. OWASP Mobile Top 10 | 115--124 | Maps to OWASP M1--M10 |
| 12. GDPR & Data Sovereignty | 125--130 | 2 CRITICAL, 2 HIGH, 1 MEDIUM, 1 LOW |
| 13. Patch Operation | 131--140 | 4 CRITICAL, 3 HIGH, 2 MEDIUM, 1 LOW |
Total: 140 tests, 0 failures required for release.
Tests that the engine can safely handle protected health information:
- PII masking --
sensitiveFieldsare replaced with[REDACTED]before any data reaches the queue, local store, remote, or error logs - Audit trail -- the event stream emits typed events for every push, pull, conflict, and deletion
- Data retention --
logout()destroys all user data across queue, local store, and timestamps - Session timeout --
AuthExpiredExceptionsurfaces aSyncAuthRequiredevent for re-auth - De-identification -- up to 18 HIPAA Safe Harbor identifiers can be masked via
sensitiveFields - Structural -- engine has no filesystem access, no UI widgets, no temp files (delegates to stores)
Tests every channel through which data could escape:
- Cross-user isolation -- after logout, zero records from the previous user exist in queue, local store, or timestamps. Drain after logout pushes zero records.
- Payload inspection -- pushed payloads contain only user-provided fields, no device metadata injected
- Error log scrubbing --
onErrorcallbacks receive masked payloads, never raw sensitive values - Event masking --
SyncPoisonPillandSyncErrorevents contain masked payloads whensensitiveFieldsis configured - Structural -- engine uses no SharedPreferences, no MethodChannel, no clipboard, no Realtime subscriptions
Tests that malicious input cannot exploit the engine:
- SQL injection -- 7 injection vectors (
'; DROP TABLE,UNION SELECT,OR '1'='1', etc.) stored as literal strings - NoSQL injection --
$gt,$ne,$regex,$whereoperator patterns stored as literal map keys - XSS --
<script>,<img onerror>,<svg onload>,javascript:URIs stored verbatim (engine doesn't render) - Path traversal --
../../../etc/passwd, UNC paths, URL-encoded traversals stored as literals - Oversized payloads -- 50MB payload triggers
PayloadTooLargeException - Null bytes -- survive round-trip through queue and local store
- Unicode -- emoji, RTL, ZWJ sequences, combining marks, surrogate pairs survive JSON round-trip
- Integer overflow -- max/min int64 values stored correctly (Dart uses 64-bit integers)
- Deep nesting -- 100 levels of nested JSON handled without stack overflow
- Malformed server response --
FormatExceptionemitsSyncError, doesn't crash
Tests auth boundaries:
- Token expiry mid-drain --
AuthExpiredExceptionstops drain, emitsSyncAuthRequired, preserves remaining entries - RLS enforcement -- write with mismatched
user_id/owner_idthrows[RLS_Bypass]exception - RLS on pull -- pulled rows with wrong
user_idare skipped withRlsViolationException - Drain lock -- prevents concurrent drain calls (acts as rate limiter)
- No infinite retry -- auth failures stop immediately, don't loop
- Post-logout purge -- queue, local data, and timestamps all wiped
- Privilege escalation --
sensitiveFieldscan mask fields likeisAdmin,role,permissions
Tests that conflicts never silently destroy data:
- LWW correctness -- both directions tested (remote newer wins, local newer wins)
- DELETE vs UPDATE -- local delete always wins over remote update
- Server-wins / Client-wins -- explicitly tested strategies
- Custom resolver --
onConflictcallback receives both versions, returned value is used - Rapid-fire conflicts -- 100 conflicting updates to the same record resolved deterministically
- Conflict events --
SyncConflictevent carrieslocalVersion,remoteVersion,resolvedVersion,strategyUsed - No-timestamp fallback -- when
updated_atis missing, falls back toserverWins
Stress tests at 10x-1000x expected load:
- 10K write throughput -- completes in < 5 seconds
- 100K write throughput -- completes without OOM
- Drain throughput -- 1K/10K/50K queue drain completes
- Memory bounded -- 100 write-drain cycles don't accumulate unbounded queue entries
- Drain lock under pressure -- 100+ concurrent drain calls, lock prevents overlap
- Concurrent writes during sync -- no data corruption when writing while draining
- Exponential backoff -- delays verified to increase (2s, 4s, 8s...) with cap
- Timeout handling -- remote timeouts handled gracefully, no hang
Structural tests confirming that cryptographic concerns are delegated:
- Engine has no encryption implementation -- delegates to
LocalStore(SQLCipher),RemoteStore(HTTPS) - No API keys, secrets, or credentials in engine code
- No token comparison (no timing attack surface)
- TLS enforcement delegated to HTTP client in
RemoteStore
- Double-drain prevention -- drain lock is a boolean flag, second call returns immediately
- Atomic write ordering --
_enqueue()runs beforelocal.upsert() - Event stream safety -- broadcast stream supports multiple listeners
- Subscription cleanup --
dispose()closes the stream controller - No deadlock -- drain lock is a simple boolean, not a mutex
Production failure scenarios:
- App killed mid-sync -- queue entry survives remote failure
- Empty server response -- engine handles gracefully, emits
SyncPullCompletewithrowCount: 0 - Disk full --
ThrowingLocalStoresurfaces error, queue entry preserved - UTC timestamps -- all
SyncEntry.createdAttimestamps are UTC - 30-day offline -- 1500 queued records all drain when connectivity returns
- Extra/missing remote fields -- engine upserts whatever the remote returns
- Partial batch failure -- batch fails, fallback to individual push, some succeed
- Year 2038 -- Dart
DateTimeis 64-bit, no overflow - Leap second -- engine doesn't crash on edge-case timestamps
- No sync loops --
pullAll()doesn't enqueue, so pull-push cycles can't loop - Queue bomb -- drain processes only
batchSizeper call, not the entire queue - Rapid login/logout -- 1000 cycles, queue clears each time, no resource leak
- Stale workers --
dispose()closes the event stream, no further events - Payload limits --
maxPayloadBytesenforced before any write
Explicit mapping to OWASP Mobile Top 10 (2024):
| OWASP | Engine posture |
|---|---|
| M1 -- Credential Storage | Engine stores no credentials. Delegates to secure storage. |
| M2 -- Supply Chain | 4 dependencies: uuid, meta, drift, supabase |
| M3 -- Authentication | No passwords stored. Uses opaque userId. |
| M4 -- Input Validation | Payload passed through as Map<String,dynamic>, validated by jsonEncode |
| M5 -- Communication | Delegated to RemoteStore (Supabase uses HTTPS) |
| M6 -- Privacy | sensitiveFields provides selective masking |
| M7 -- Binary Protections | No secrets in source code |
| M8 -- Security Defaults | Default config: empty sensitiveFields, 1MB payload limit, 3 retries |
| M9 -- Data Storage | Delegated to LocalStore, no direct filesystem access |
| M10 -- Cryptography | Delegated to stores and HTTP client |
- Right to Erasure (Art. 17) --
logout()destroys all data across queue, local, timestamps - Data portability (Art. 20) -- data readable via
LocalStoreandQueueStoreinterfaces - Data minimization --
sensitiveFieldsmasks unnecessary fields before sync - Consent tracking --
addTable()enables progressive table registration as consent is granted - Data residency --
RemoteStoreendpoint is configurable per region - Transfer logging -- event stream provides audit trail for all sync operations
Tests that the new SyncOperation.patch (partial update) is safe across all vectors:
- Partial payload integrity -- engine sends exactly the provided fields, no extras injected
- Queue persistence --
SyncEntrypreserves thepatchoperation type through the queue - Drain forwarding -- drain correctly forwards patch operations to
RemoteStore - Mixed batch -- upsert + patch + delete all processed correctly in a single drain cycle
- Payload size limits --
maxPayloadBytesenforced on patch payloads - RLS enforcement -- mismatched
user_idthrows[RLS_Bypass]on patch, same as upsert - PII masking --
sensitiveFieldsmasks values in patch payloads viawrite() - Poison pill -- patch entries dropped after
maxRetrieswithSyncPoisonPillevent - Auth expiry --
AuthExpiredExceptionduring patch drain stops and preserves entry - SQL injection -- injection strings in patch payloads stored as literals, not executed
Every release must:
- Pass all 140 tests in
test/dynos_sync_total_audit_test.dart - Pass all existing tests across other test files
- Zero
dart analyzeerrors or warnings - Verified compatibility with Drift and Supabase adapters
Secured by dynos.fit