This document describes what NeuralBridge protects, how, and where the limits are.
| Asset | Threat | Control | Where |
|---|---|---|---|
| Shared text | Read by a stranger who guesses a code | 60-bit CSPRNG codes; per-IP rate limits on joins (HTTP + circuit); optional PIN | SessionCodeGenerator, AppRateLimiter, SessionService.JoinAsync |
| Shared text | Persisted somewhere unexpected | Text lives only in InMemoryDocumentStore; never in DB, logs or events; purged on close or expiry, and orphans are swept |
IDocumentStore, SessionCleanupService |
| PIN | Online guessing | 5 attempts → 5-minute lockout per session; rate limits | SharedSession.RegisterFailedPinAttempt |
| PIN | Offline cracking after DB leak | PBKDF2-HMAC-SHA512, 210k iterations, 128-bit salt | Pbkdf2SecretHasher |
| Session existence | Enumeration ("is this code protected?") | Identical response for unknown code, missing PIN and wrong PIN | SessionError.NotFoundOrPinIncorrect |
| Participant identity | Token theft from DB | Only SHA-256 hashes of 256-bit tokens are stored | TokenService |
| Participant identity | Token theft in browser | Stored per tab in sessionStorage, encrypted and MAC'd with ASP.NET Data Protection; strict CSP limits script injection |
SessionCredentialStore |
| Owner powers | Guest calls owner operations | Every owner operation re-authorizes token + IsOwner inside the per-session lock |
SessionOwnerService.ExecuteAsync |
| Session isolation | Hub client reads/writes another session | Connection binds to one session at Attach; later calls use server-side binding only; broadcasts go to session:{id} groups |
SessionHub, HubNotificationRelay |
| Browser | XSS through shared text | Text only set as textarea.value or Razor-encoded; no MarkupString; CSP script-src 'self' (no inline, no eval); links limited to http/https |
editor.js, DetectedLinks.razor, SecurityHeadersMiddleware |
| Browser | Clickjacking | frame-ancestors 'none', X-Frame-Options: DENY |
middleware |
| Browser | Code leak via Referer | Referrer-Policy: no-referrer; links use rel="noopener noreferrer nofollow" |
middleware, DetectedLinks.razor |
| Accounts | Login CSRF / open redirect | Google login and logout are POST + antiforgery; return URLs must be local paths (//, /\\, CR/LF and absolute URLs fall back to /) |
AccountEndpoints.SafeReturnUrl |
| Google OAuth | Code interception / CSRF on callback | Authorization code flow with PKCE (S256), state + correlation cookie validated by the ASP.NET Core OAuth handler; forged callbacks end at /account/login?error=google_failed |
GoogleOAuthSetup |
| Google OAuth | Client secret exposure | Secret read only from user secrets, environment variables or secret files; used only server-side in the token request; never rendered, logged or returned by /api/auth/providers; test endpoint overrides ignored outside Development/Testing |
AuthOptions, AuthStartupReport, AuthProviderStatus |
| Accounts | Takeover via a Google account with the victim's email | Google identities match only by Google subject ID. An existing account with the same email is never linked automatically; linking is an explicit action while signed in | ExternalAccountService |
| Accounts | Password guessing | 10+ character passwords (Identity hasher), lockout after 5 failures for 15 minutes, per-IP limit on login/register/reset; generic error messages; reset requests answer identically for unknown emails | Login.razor, ForgotPassword.razor, AuthenticationSetup |
| Accounts | Stale sessions after password change / "sign out everywhere" / deletion | Security stamp checked every minute (cookies and live circuits) | IdentityRevalidatingAuthenticationStateProvider |
| Accounts | Data left behind on deletion | Deleting an account closes its running sessions (everyone is disconnected and the text purged), deletes history and saved text, then the user | AccountDataService.DeleteAllAsync |
| Calls | Eavesdropping / joining another session's call | Media is DTLS-SRTP between browsers; signaling goes only through the authenticated circuit and only between members of the same session's call; per-participant rate and size limits | CallService.SignalAsync |
| Calls | Camera/microphone used unexpectedly | Requested only on an explicit Join click; Permissions-Policy allows them only for this origin; tracks are stopped on leave, session end, removal and page hide |
call.js, SecurityHeadersMiddleware |
| Cookies | Theft / CSRF | HttpOnly; Secure outside Development; SameSite=Lax (needed for the OAuth return) |
AuthenticationSetup |
| Service | Resource exhaustion | Content cap (50k chars, client + server); 512 KB message limits; global per-IP HTTP limiter; per-participant update token bucket | options + WebApplicationSetup |
| Location | Over-collection | Requested only on click; rounded to about 1 km; lookup made server-side; never stored or logged; can be disabled | LocalTimeCard.razor, LocationDisplayService |
Logs carry internal session and participant GUIDs, outcomes and counts. They never contain document text, join codes, PINs, tokens, IP addresses or coordinates. When adding log statements, keep it that way.
- TLS: run behind HTTPS. The app redirects to HTTPS by default (
NeuralBridge:Hosting:HttpsRedirection) and sends HSTS outside Development. - Reverse proxies: only set
NeuralBridge:Hosting:ForwardedHeadersEnabled=truebehind a proxy you control. Otherwise clients could spoofX-Forwarded-Forand escape rate limits. - Data Protection keys protect auth cookies and the per-tab participant credentials. Persist them (
NeuralBridge:Hosting:DataProtectionKeysPath) on a private volume. Rotating them signs everyone out and forces tabs to rejoin. For stronger at-rest protection, configure a key-encryption method (certificate, Azure Key Vault, DPAPI). - Secrets: Google credentials and the SMTP password come only from user secrets, environment variables or secret files (
/run/secretsorNEURALBRIDGE_SECRETS_PATH).appsettings.jsonhas no credential keys at all. Setting only one of ClientId/ClientSecret stops startup without printing either value. - Avatars: only
https://*.googleusercontent.compictures are kept, loaded withreferrerpolicy=no-referrer, and allowed by the CSPimg-src.
NeuralBridge does not and must not read the OS clipboard in the background, capture the screen, record keystrokes outside its own editor, control other devices, or help anyone bypass organizational security controls.
- An unprotected session's code is a bearer secret. Anyone who sees it can join until the owner rotates it or the session ends. The UI says so, and nudges owners to add a PIN.
- Content is end-to-end encrypted in transit (TLS) but is visible to the server process while the session runs. True end-to-end encryption, with keys in the link fragment, is on the roadmap.
- A removed participant who still knows the code can rejoin. The removal dialog tells the owner to rotate the code or set a PIN.