Security Reality Audit
Trust Architecture Certification — 9/12/2026
Overall Trust Architecture Score
Feature Certification
Protection Score / Trust Engine
identityTrustEngine computes from 9 real sources. SecurityDashboard now prefers canonical score.
Trusted Devices — view
Real entity reads. Device ID, platform, revoke all work.
Trusted Devices — location
Location field never populated. No IP geolocation implemented.
Active Sessions — view
registerSession() writes Session entity on every login. session_id bound client-side.
Active Sessions — revocation
Konekt internal session guard blocks revoked sessions. Base44 token remains platform-managed — no token blacklist API exists.
Login History
Session entity written on every login via securityEventBus.create_session.
Security Alerts — preferences
Full per-type per-channel toggle backed by real entity.
Security Alerts — delivery
Preferences stored, never dispatched. No delivery pipeline.
Risk Center — ATO/SessionRisk
Real entity reads from attackResponseEngine.
Risk Center — fraud auto-detection
Impossible travel, rapid device switching: not computed.
Recovery Health
Backed by PhoneVerificationStatus + EmailVerificationStatus + RecoveryMethod.
Account Timeline — events
All events read from real entity fields with real timestamps.
Account Timeline — immutability
Reconstructed from mutable fields, not append-only log.
Critical Gaps to Fix
PLATFORM LIMITATION: Base44 auth token cannot be server-side invalidated — no token blacklist API exists. Konekt internal session guard (KonektSessionGuard + konektSessionGuardEngine) blocks revoked sessions across all protected routes and financial actions. Base44 token remains platform-managed.
Approximate location is never populated — Session.location_approx always null.
Fraud auto-detection not implemented — impossible travel, device switching: manual only.
Confirmed Real Features
identityTrustEngine: 9-signal trust computation with policy-driven weights and hard-stops.
Protection Score: all 7 factors backed by real entity writes (phone, email, PIN hash, bio, KYC).
PIN: SHA-256(pin+userId) hash stored in SecurityPreference. Never plaintext. Verified server-side.
SecurityAuditLog: logSecurityEvent() called on all security actions (pin change, device revoke, etc.).
KonektSession binding: session_id stored in sessionStorage on every login. Bound to device fingerprint.
KonektSessionGuard: wraps every PhoneVerificationGate-protected route. Checks session validity on load + 45s heartbeat.
Financial action guard: requireActiveSession() blocks revoked sessions from initiating money movement.
Session heartbeat: 45s interval updates last_seen_at and detects revocations in real-time.
Sign out all devices: revokes all Session records, immutable log, critical-severity alert delivered.
Trusted device removal cascade: removes TrustedDevice + revokes all bound Sessions + delivers alert.
Alert delivery: SecurityEventBus delivers in-app + email alerts for all high/critical security events.
Recovery methods: RecoveryMethod entity with verified status, identityTrustEngine reads it.
ATO/SessionRisk events: attackResponseEngine queries real AccountTakeoverEvent + SessionRiskEvent.
Detailed Phase Audit
Phone Verified (+20pts)
REALWritten by otpEngine on OTP success. Queried by accountIdentityEngine.buildAccountState() and identityTrustEngine.scorePhone().
Source: PhoneVerificationStatus.verified
Email Verified (+10pts)
REALWritten by emailVerificationEngine on OTP/magic-link success. identityTrustEngine.scoreEmail() reads it.
Source: EmailVerificationStatus.verified
PIN Configured (+15pts)
REALpinPersistence.savePinHash() writes SHA-256(pin+userId) to SecurityPreference. accountIdentityEngine cross-checks both pin_set=true AND pin_hash present to prevent false positives.
Source: SecurityPreference.pin_set + pin_hash
Biometrics Enabled (+10pts)
REALmarkBiometricSeen() in accountIdentityEngine writes both flags. biometricAuth.js stores device credential. SecurityPreference is source of truth.
Source: SecurityPreference.biometric_enabled + biometric_setup_seen
Trusted Device (+10pts)
PARTIALTrustedDevice records exist and are read. However, device linking is currently manual (ensureCurrentDevice auto-registers on load). No server-side assertion on login.
Source: TrustedDevice entity
⚠ Gap: Device trust is not asserted at authentication time — only at screen load.
Recovery Method (+5pts)
REALAccountRecovery screen writes RecoveryMethod records. identityTrustEngine.scoreRecovery() evaluates both phone+email verified AND RecoveryMethod.verified.
Source: RecoveryMethod.verified
Identity Verified (+30pts)
REALaccountIdentityEngine.buildAccountState() applies a 3-layer check: ISM snapshot → KYCVerificationStatus → KYCCase. identityTrustEngine.scoreKYC() reads face_match_verdict and liveness_passed for document quality.
Source: KYCCase.status + KYCVerificationStatus + IdentityStateSnapshot
Score uses identityTrustEngine
REALSecurityDashboard.jsx now prefers canonical trust engine score (5-min TTL cache, auto-recomputes if stale). Falls back to local computation only if trust engine unreachable.
Source: identityTrustEngine.get_trust
Device ID collected
REALgetDeviceId() generates a stable UUID from browser fingerprint stored in localStorage. Written to TrustedDevice and SecurityAuditLog on every security event.
Source: biometricAuth.getDeviceId()
Platform detected
REALgetPlatform() returns ios/android/web from navigator.userAgent. Stored on TrustedDevice record.
Source: biometricAuth.getPlatform()
Device Name
PARTIALAuto-generated as "iPhone — This device". User cannot rename device in current UI.
Source: TrustedDevice.device_name
⚠ Gap: Rename device action not yet implemented in TrustedDevices screen.
Last Seen
REALupdated_date is maintained by the entity layer on every update. relativeTime() utility formats it.
Source: TrustedDevice.updated_date
Approximate Location
FAKENo IP geolocation is performed. Session.location_approx field exists but is never populated by any backend function.
Source: None
⚠ Gap: Location is never populated. Consider enriching from IP on login.
Untrust / Revoke
REALhandleRevoke() sets trusted=false, biometric_enabled=false, revoked_at, and writes a SecurityAuditLog entry. Device is excluded from future trusted checks.
Source: TrustedDevice.trusted=false + logSecurityEvent()
Sign Out Device
PARTIALRevoking a device sets trusted=false in the entity, but does NOT explicitly invalidate the auth token for that device.
Source: TrustedDevice.trusted=false
⚠ Gap: No server-side session token revocation on device untrust.
Sessions tracked
REALregisterSession() is called on every successful login (SignIn.jsx). It invokes securityEventBus.create_session, which creates a real Session entity record. session_id is stored in sessionStorage via storeSessionId().
Source: Session entity + registerSession()
Sign Out Session
REALhandleRevokeOne() calls securityEventBus.revoke_session which updates Session.status=revoked, writes immutable SecurityAuditLog, and delivers in-app + email alert.
Source: Session.status=revoked + securityEventBus
Sign Out All Others
REALhandleRevokeAll() calls securityEventBus.revoke_all_sessions. All sessions except current are revoked in the entity. Immutable log + critical-severity alert delivered.
Source: Session.status=revoked (loop) + alert
Base44 token revocation
FAKEPLATFORM LIMITATION: Base44 auth tokens are platform-managed and cannot be server-side invalidated. No token blacklist API is exposed. Konekt cannot revoke the underlying JWT/session token.
Source: Platform limitation — no Base44 token blacklist API
⚠ Gap: Base44 token remains valid after session revocation. Konekt compensating controls (below) block access at the Konekt layer.
KonektSession compensating control
REALEvery login binds a session_id (stored in sessionStorage). KonektSessionGuard wraps every protected route via PhoneVerificationGate. On each navigation, the guard checks Session.status via konektSessionGuardEngine. Revoked sessions are redirected to /signin with a security message.
Source: lib/konektSession.js + konektSessionGuardEngine
Route-level session guard
REALKonektSessionGuard is composed inside PhoneVerificationGate, which wraps every user-facing protected route in App.jsx. Auth check + Konekt session check + heartbeat all run at route level.
Source: KonektSessionGuard.jsx → PhoneVerificationGate
Financial action guard
REALrequireActiveSession() is available for all money-movement flows (send, cash-out, QR, agent, etc.). Calls konektSessionGuardEngine.require_active. Throws BlockedBySessionError if session is revoked. Logs permission_denied security event.
Source: lib/financialSessionGuard.js → requireActiveSession()
Session heartbeat (45s)
REALHeartbeat runs every 45 seconds while app is active. Updates last_seen_at on Session entity. Checks revocation status. If revoked is detected mid-session, force-logs out and redirects to /signin.
Source: lib/konektSession.js → startSessionHeartbeat()
Trusted device removal cascade
REALRemoving a trusted device calls securityEventBus.revoke_device which revokes all Session records bound to that device_id, updates TrustedDevice.trusted=false, writes immutable log, and delivers high-severity alert.
Source: securityEventBus.revoke_device
Login events stored
PARTIALLoginHistory reads Session entity filtered by user. Sessions are visible if populated.
Source: Session entity
⚠ Gap: No confirmed automatic session creation on login. Login history may be empty for most users until sessions are explicitly created.
Success/failure status
PARTIALSessions with status="failed" or "revoked" render as failed. Only valid if sessions were written with correct status.
Source: Session.status
SecurityAuditLog for login
PARTIALEVENT_LABELS defines login_success and login_failed event types. securityHub.logSecurityEvent() can write them. But no confirmed call site in the auth flow that writes these automatically.
Source: SecurityAuditLog (login_success, login_failed)
⚠ Gap: SignIn.jsx does not call logSecurityEvent() on auth success/failure.
Alert preferences stored
REALSecurityAlerts screen reads/writes SecurityAlertPreference per user. loadOrCreateAlertPreference() ensures record exists. Toggle writes immediately.
Source: SecurityAlertPreference entity
PIN change alert
PARTIALlogSecurityEvent() is called with "pin_changed" from ChangePin. Record written to SecurityAuditLog. But the actual delivery (push/SMS/email) of alert is not implemented.
Source: SecurityAuditLog (pin_changed)
⚠ Gap: Alert preferences are stored but alerts are never dispatched to the user.
New device login alert
PARTIALdevice_trusted event is logged. SecurityAlertPreference has login_new_device_push/sms/email fields. Delivery engine not implemented.
Source: TrustedDevice + SecurityAuditLog
⚠ Gap: Same gap: preferences exist, dispatch does not.
Suspicious activity alert
PARTIALattackResponseEngine creates AttackEvent and AttackAlert for admin. User-facing alert delivery not implemented.
Source: AttackEvent / AccountTakeoverEvent
Large transaction alert
FAKEPreference field exists. moneyFlowEngine does not check alert prefs or dispatch alerts on large transactions.
Source: SecurityAlertPreference.large_transaction_push
⚠ Gap: Not wired to any transaction pipeline.
AccountTakeoverEvent
REALattackResponseEngine.getMySecurityEvents() filters AccountTakeoverEvent by user_id. RiskCenter and SecurityDashboard read these.
Source: AccountTakeoverEvent entity
SessionRiskEvent
REALSame action returns SessionRiskEvent records. Both entity reads are real.
Source: SessionRiskEvent entity
ContinuousRiskScore
REALgetMySecurityEvents returns risk_score from ContinuousRiskScore. accountIdentityEngine.getGlobalState() also reads it and factors it into trust computation.
Source: ContinuousRiskScore entity
Impossible Travel detection
FAKENo backend function computes impossible travel from login IP sequences. AttackEvent must be manually created by admin.
Source: None
⚠ Gap: Fraud detection signals (impossible travel, rapid device switching) are not automatically generated.
Failed PIN attempt tracking
PARTIALSecurityPreference has failed_pin_attempts and pin_locked_until fields. securityEnforcementEngine increments them on verify. RiskCenter does not display this value.
Source: SecurityPreference.failed_pin_attempts
⚠ Gap: The field exists and is written, but RiskCenter does not surface it.
Risk level computed
PARTIALRiskCenter derives risk level from ATO events severity, not from ContinuousRiskScore directly. ContinuousRiskScore is read in accountIdentityEngine but not in RiskCenter.
Source: ContinuousRiskScore.overall_score
⚠ Gap: RiskCenter should use ContinuousRiskScore.overall_score as primary input.
Recovery email
REALaccountIdentityEngine reads EmailVerificationStatus. identityTrustEngine.scoreRecovery() factors it in. AccountRecovery screen reads RecoveryMethod.
Source: EmailVerificationStatus.verified
Recovery phone
REALPhoneVerificationStatus is the primary phone truth source. Verified status is used in both trust engine and routing engine.
Source: PhoneVerificationStatus.verified
Recovery method health score
PARTIALAccountRecovery shows verified/unverified state. No composite "Recovery Health Score" (0-100) is computed or displayed.
Source: RecoveryMethod entity
⚠ Gap: Recovery health is binary (has/does not have), not a scored metric.
Account Created
REALRead from base44.auth.me().created_date. Immutable platform field.
Source: User.created_date
Phone Verified
REALRead directly from entity. verified_at is set by otpEngine.
Source: PhoneVerificationStatus.verified_at
PIN Created
REALpinPersistence.savePinHash() writes pin_set_at on creation.
Source: SecurityPreference.pin_set_at
Email Verified
REALemailVerificationEngine sets verified_at when verification succeeds.
Source: EmailVerificationStatus.verified_at
Biometrics Enabled
REALmarkBiometricSeen() writes biometric_seen_at.
Source: SecurityPreference.biometric_seen_at
KYC Submitted / Approved
REALKYCCase is queried by created_by=user.email. Both submission and review timestamps exist.
Source: KYCCase.created_date / reviewed_at
Events are immutable
PARTIALTimeline is reconstructed from entity fields on every load — not from an append-only event log. A corrective update to SecurityPreference would silently change the timeline.
Source: Entity records
⚠ Gap: Timeline should read from SecurityAuditLog for immutability. Currently reads mutable entity fields.