This document consolidates SecureShare's security model across all six implemented phases. For implementation detail and rationale behind each design decision, see the corresponding section of README.md. For how to verify these guarantees hold, see SECURITY_TESTING.md.
Every file is encrypted in the browser, before any network request, using a freshly generated AES-256-GCM key. That key is wrapped (never sent raw) with the uploader's own RSA-OAEP public key for owner re-access, and made available to recipients either via a URL fragment (never transmitted to the server) or a password-derived key. The server only ever stores and serves ciphertext and wrapped keys — it has no code path capable of decrypting an encryptionVersion: 2 file.
Every upload is signed client-side with the uploader's ECDSA P-256 private key, over a SHA-256 hash of the encrypted file. Downloaders verify this signature against the uploader's public signing key before attempting decryption — a mismatch (tampered ciphertext, forged signature, or wrong signer) blocks the download outright, never reaching the decryption step.
No request is trusted by default, regardless of network origin or prior authentication. Every download is evaluated against the specific file's configured policy (country, IP, device, time-of-day, device-count cap, approval requirement) on every attempt — possessing a valid link and even the correct decryption key is necessary but not sufficient if a policy is configured. Sessions are individually revocable and checked on every authenticated request, independent of JWT expiry.
Every new upload is scanned for malicious content — magic-byte type verification, cryptographic hashing, ClamAV signature scanning, optional VirusTotal hash lookup, and heuristic checks (dangerous extensions, macro-enabled documents, encrypted archives) — before it's stored. Files scoring High or Critical risk are automatically quarantined, which unconditionally blocks all future downloads regardless of any other passing check.
Every scan produces a single riskLevel (Low / Medium / High / Critical) from a pure, configurable rule engine (backend/services/riskEngine.js) combining all Phase 4 signals. See the Risk Classification table in SECURITY_TESTING.md §4.8 for the authoritative signal→level mapping, kept in one place to avoid drift between docs.
Every new upload of a supported text-based file is scanned for embedded secrets and PII — emails, phone numbers, credit card numbers, Aadhaar/PAN/passport numbers, IBANs, SWIFT/BIC codes, cloud/source-control/AI-service API keys and tokens, JWTs, PEM private keys, certificates, hardcoded passwords, and .env-style secrets — before it's encrypted. A configurable policy (backend/services/dlp/dlpPolicyConfig.js) resolves findings into one of four decisions: Allow, Warn, Require Approval, or Block. Blocked uploads are refused outright; nothing is encrypted or stored. Detected values are never persisted in full — only masked previews are kept, so the DLP scan history itself never becomes a secondary leak of the secrets it found. Binary/unsupported files are skipped gracefully rather than scanned.
Confidence-based credit card detection: a bare regex match on a card-shaped digit sequence is not, by itself, treated as a credit card. backend/services/dlp/confidenceEngine.js layers Luhn checksum validation and surrounding-text context analysis (card-related keywords raise confidence; non-card identifier keywords like "Ride ID"/"Invoice Number"/"Transaction ID" lower it, and zero it out entirely when no card keyword is also present) into a 0-100 confidence score, mapped to a LOW/MEDIUM/HIGH risk level and, in turn, an Allow/Warn/Block decision. This closes the specific false-positive class where a ride-hailing receipt or invoice number that happens to be 13-19 digits long was previously auto-blocked as a credit card. Every finding — confidence-scored or not — carries a riskReport entry (pattern, confidence, reasons, masked matched text, context, decision) for audit/SIEM purposes.
Every event emitted by Phases 1-5 above — logins, uploads, downloads, quarantines, DLP verdicts, device/session changes, policy denials, and client-reported signature verification outcomes — is normalized into one taxonomy (siemType), assigned a severity (INFO/LOW/MEDIUM/HIGH/CRITICAL) and category, and written through a single logging service (backend/services/siem/siemLogger.js). A rule-based correlation engine (backend/services/siem/correlationEngine.js) groups related events into Incident records (e.g. a quarantined file later denied for download), surfaced in a unified Security Operations Center dashboard (/soc) with severity-ranked alerts, incident tracking, filtering, full-text search, and export.
What the correlation engine does not do: it is detection-only and purely observational. It never blocks, delays, alters, or auto-remediates a request — an Incident is a grouped, labeled view over events that already happened, not an enforcement mechanism. No cryptography, Zero Trust policy evaluation, malware scanning, or DLP detection logic was modified to build this phase; the SIEM only consumes their existing outputs.
Every uploaded file's hash is cross-referenced against a local IOC (Indicator of Compromise) database and, if configured, external reputation providers (VirusTotal, AbuseIPDB, AlienVault OTX, URLHaus, OpenPhish, CIRCL) — all optional and independently disableable, with enrichment always degrading to "no external data" rather than blocking or failing an upload. Matches are further mapped to MITRE ATT&CK techniques (a curated subset, not the full corpus) and checked against stored YARA-style detection rules (backend/services/threatIntel/yaraEngine.js — a documented, simplified strings:/condition: matcher, not a native libyara binding, to avoid a compiled-binary dependency). Results are surfaced on the /threat-intelligence dashboard and fed into the same SIEM taxonomy as every other phase (IOC_MATCH, THREAT_INTEL_MATCH, MITRE_MAPPING, YARA_MATCH, PROVIDER_ERROR).
Zero-knowledge boundary respected: by the time enrichment runs (after upload completes), the server no longer holds the file's plaintext — automatic enrichment only ever operates on the SHA-256/SHA-1/MD5 hashes already computed during the Phase 4 pre-encryption scan. The one place raw text is intentionally examined for embedded URLs/domains/emails/IPs is POST /api/threat-intel/scan-text, a deliberate, explicit, auth-only endpoint mirroring the same "documented scoped exception" pattern Phase 4/5 already use for pre-encryption scanning — it is never invoked against DLP's masked findings, which intentionally never contain raw matched values.
What Phase 7 does not do: like the SIEM correlation engine, it is detection/enrichment-only — a critical IOC/YARA/MITRE match never blocks an upload or a download by itself (that remains Phase 3's Zero Trust policy engine and Phase 4/5's quarantine/block decisions). No existing model, route, or controller behavior was changed; every integration point is additive.
Configurable Automation Rules watch the unified SecurityEvent stream Phase 6 already produces; when a rule's trigger and conditions match, its Playbook's ordered response actions run automatically (quarantine a file, revoke a session, disable a device, notify the owner/an administrator, raise an incident). Every execution — including every individual action's success/failure — is recorded as an AutomationExecution document, and the correlated Incident (if any) is updated with the automation's status and action timeline.
Admin gating is new in this phase: User.isAdmin (default false) is the first role concept in this codebase. Creating, editing, or deleting rules and playbooks requires an admin account, enforced server-side by backend/middleware/requireAdmin.js, which re-checks the User document on every request rather than trusting the JWT's isAdmin claim. Normal users can view automation history but only for their own files.
No real email delivery: notifyUser/notifyAdmin/sendEmail create in-app Notification records, not SMTP-delivered email — there is no mail transport in this codebase. This is documented plainly in the action handlers themselves rather than presented as email delivery.
Recursion safety: SOAR's own actions emit SecurityEvents (notifications, playbook status, audit-log entries), all tagged category: "AUTOMATION". The engine's entry point explicitly ignores events in that category before doing any rule matching, so automation can never trigger itself in a loop.
What Phase 8 does not do: it does not add new detection capability — every trigger is sourced from an event a prior phase already produces. A playbook step failing never blocks or fails the original request that triggered it (e.g. a failed quarantine action doesn't undo an upload); failures are recorded for visibility on the /soar dashboard, not silently swallowed. No existing model, route, or controller behavior was changed — every integration point (the isAdmin field, the JWT claim, the Incident schema additions, the logSecurityEvent hook) is additive.
Login gains TOTP MFA (otplib), WebAuthn passkeys (@simplewebauthn/server/browser), a five-level role model, a configurable global security policy, and risk-based step-up — all layered on top of the email+password flow every prior phase already relies on. This is the one requirement that overrides all others in this phase: existing JWT authentication and plain password login must keep working unchanged for every account that hasn't opted into MFA/passkeys. It does.
Two-step MFA enrollment, never a half-enabled state: POST /api/mfa/setup generates a secret and stores it as User.mfa.pendingSecret — inert until POST /api/mfa/verify proves possession with a real code, at which point (and only then) it's promoted to User.mfa.secret and enabled: true. An abandoned enrollment leaves the account exactly as it was.
MFA-gated login is a two-request exchange, not a session-in-waiting: when MFA is required, POST /api/auth/login returns 202 {mfaRequired: true, mfaToken} — never a real session token. mfaToken is a JWT signed with purpose: "mfa" and a 5-minute expiry; POST /api/mfa/verify-login is the only endpoint that accepts it, and only after checking that exact claim, so it can't be replayed as (or mistaken for) a real session credential.
Recovery codes are bcrypt-hashed, single-use, shown once: User.mfa.recoveryCodeHashes never stores plaintext; services/iam/recoveryCodes.js's consumeRecoveryCode() removes a matched hash from the array so it can never be reused.
RBAC is additive over Phase 8's admin flag, not a replacement: backend/middleware/requireAdmin.js now accepts either User.isAdmin or role being administrator/org_owner — every account granted admin access under Phase 8's original mechanism keeps working exactly as before. Role changes themselves require org_owner (requireRole.js), since granting administrator is itself a privilege-escalation-sensitive action.
Security policy enforcement is deliberately mostly soft: services/iam/policyEngine.js's evaluators return flags (passwordExpired, mfaSetupRequired) rather than denying login, because this application has no self-service password-reset or account-unlock flow — a hard block here would be a permanent, unrecoverable lockout, not a security improvement. The single hard block is the country restriction (evaluateCountryPolicy), since "log in again from an allowed location" is an actually-recoverable failure mode, unlike the others.
Adaptive authentication never blocks, only escalates or nudges: services/iam/loginRiskEngine.js's scoreLogin() is a pure function over three signals (new device, IP matching a local Phase 7 IOC record, country change) — network calls are deliberately excluded from the login path to keep it fast and to avoid Phase 7's optional external providers becoming a login-latency or availability dependency. A High score forces a step-up challenge if the account has MFA/a passkey; otherwise it only logs and recommends, since forcing enrollment mid-login isn't possible.
Closing a real gap while adding SOAR integration: prior to this phase, a failed login attempt was never logged anywhere — Phase 8 shipped with a MULTIPLE_FAILED_LOGINS automation trigger that could never fire because nothing produced the event it needed. services/iam/loginFailureTracker.js now logs login_failed with a rolling failure count on every bad password or MFA code, giving that trigger — and the newly seeded "Account Lockdown Response" playbook — a real source. Successful logins are entirely unaffected by this addition.
What Phase 9 does not do: it does not replace or weaken the existing JWT-based session model — MFA, passkeys, and policies all end at the same issueSessionAndToken() that plain password login always used. It does not implement real email delivery (see Phase 8's note above — sendEmail-style semantics remain in-app only). It does not force MFA on any account that hasn't explicitly enrolled or been targeted by an admin-configured policy/automation action.
Sharpens Phase 9's risk engine and, importantly, fixes two policies that phase defined but never actually enforced — blockUntrustedDevices and sessionTimeoutMinutes were schema fields with no code path checking them until this phase.
Risk scoring is honest about its own limitations: services/iam/loginRiskEngine.js's four-tier score now includes VPN/Tor and impossible-travel signals, but services/iam/networkIntel.js explicitly documents that VPN/Tor detection is local-only (no external IP-intelligence subscription exists in this codebase) and that its illustrative Tor-node list is not exhaustive — real coverage requires an admin importing a maintained exit-node list into the Phase 7 IOC collection. Similarly, "impossible travel" is a country-level time-window heuristic, not a geodesic distance/speed calculation, because this codebase has no lat/long geo-database. Both limitations are stated in the code, not glossed over.
Device restriction is a hard block, deliberately: unlike password expiry or MFA-enrollment (Phase 9's soft blocks, chosen because this app has no account-recovery flow), evaluateDevicePolicy() denies outright. The reasoning is the same "is the recovery trivial?" test Phase 9 already applied to country restrictions — logging in from a device the account has already used, or getting added to an admin's allow-list, is always available to the genuine owner.
Session timeout is enforced on every request, not just at login: backend/middleware/auth.middleware.js now checks evaluateSessionTimeout() against the session's lastActiveAt before refreshing it, so an idle session is actually revoked rather than the policy field being silently decorative. A short in-memory cache on SecurityPolicy.getPolicy() (15 seconds) keeps this from adding a database round-trip to every authenticated request.
No new detection capability, only better-informed automation: the two new SOAR triggers (IMPOSSIBLE_TRAVEL, CRITICAL_RISK_LOGIN) route through the exact same soarEngine.js/playbookRunner.js Phase 8 built — this phase adds signals and a seeded playbook, not new orchestration machinery.
What Phase 9.5 does not do: it does not add a commercial-grade IP intelligence or geolocation integration (documented as a deployment-time extension point, not a built-in guarantee). It does not retroactively enforce the new password policy against existing accounts. It does not change what a JWT looks like, how sessions are revoked, or any Phase 1-9 detection/crypto logic.
A read-only governance layer over Phases 1-9.5 - it evaluates and reports on the state of existing controls, it does not itself enforce anything new. No detection, crypto, or auth code path was modified to build it.
Evidence lifecycle: every runAssessment() call builds one shared context from live queries (File, User, SecurityEvent, Incident, AutomationRule/AutomationExecution, SecurityPolicy), runs each control's evaluator against it, and persists both a ComplianceAssessment (score/status/recommendations) and a ComplianceEvidence document linking the control to the data that justified the verdict. Evidence is retained indefinitely and can be marked approved by an admin (POST /api/compliance/evidence/:id/approve) as a lightweight review trail - approval is advisory, it does not change the assessment's score.
Control catalog is representative, not exhaustive: services/compliance/seedFrameworks.js seeds real, correctly-mapped controls (ISO 27001, SOC 2, GDPR, HIPAA, PCI DSS, NIST CSF, CIS Controls, OWASP ASVS) but intentionally does not attempt each framework's full published control set. This is stated here so the compliance score is understood as "how well SecureShare's actual capabilities satisfy a curated sample of each framework's requirements," not a certified audit result.
Policy versioning, never mutation: CompliancePolicy documents are never updated in place - every change to FILE_RETENTION_DAYS, MAX_UPLOAD_SIZE_MB, BLOCKED_FILE_TYPES, RESTRICTED_COUNTRIES, or DLP_ENFORCEMENT inserts a new document with an incremented version, so the full history of what the policy was at any point in time is preserved for audit purposes.
Deliberately does not duplicate SecurityPolicy: MFA requirement, session timeout, password length, and allowed-countries settings already exist on Phase 9's SecurityPolicy singleton. Phase 10 reads them live via evidenceCollector.js rather than copying them into a second, potentially-divergent policy store.
SOAR integration reuses existing plumbing, not new triggers on the hot path: compliance_scan/control_failed events go through the same logSecurityEvent() → SOAR-engine re-entry every other phase's events already use. The new COMPLIANCE_SCORE_DROP trigger and generateComplianceReport action are additive entries in the existing trigger enum and action registry - no change to soarEngine.js's orchestration logic itself.
Admin-only, not per-user: unlike /identity (a user's own account view), /compliance and every /api/compliance/* route require requireAdmin - this is org-wide governance data (aggregate scores, all users' MFA adoption, all files' encryption ratio), not something scoped to a single account.
What Phase 10 does not do: it does not block uploads, logins, or downloads based on policy violations - policyEvaluator.js's evaluatePolicyViolations() is evidence-only today, feeding scores and recommendations rather than gating Phase 3/4/5's existing enforcement paths (a documented follow-up, not an oversight - wiring a new hard block into the upload path was out of scope for an additive governance layer). It does not certify actual compliance with any framework; it is a continuous internal self-assessment tool.
Continuation - risk scoring is additive weighting, not a new judgment: services/compliance/riskScoring.js's computeRiskScore() only re-weights the same PASS/FAIL/PARTIAL verdicts and control severities the engine already produces; it introduces no new evaluation logic. A control's static severity (set at seed time) and its per-run status are the only inputs.
Continuation - policy approval is advisory, like evidence approval: CompliancePolicy.approvalStatus (Phase 10 continuation) does not gate whether a policy version is "current" - getCurrentPolicyValues() still resolves to the highest-versioned enabled document regardless of approval state. Approval is a review/audit trail, not an activation gate, exactly like ComplianceEvidence.approved.
Continuation - rollback never deletes history: rollbackPolicy() creates a brand-new version copying an older one's value; no CompliancePolicy document is ever mutated or removed, preserving the same append-only audit guarantee the original versioning design established.
Continuation - automation reuses existing triggers, not new hooks into core paths: the "recheck compliance" rules attached to THREAT_FOUND/DLP_BLOCK/MITRE_CRITICAL are ordinary additional AutomationRule documents against triggers those phases already emit - no change to soarEngine.js, ruleMatcher.js's trigger-detection logic, or the malware/DLP/threat-intel code that raises those events in the first place.
A self-scanning layer over SecureShare's own deployment, not a multi-cloud inventory tool - there is no AWS/GCP/Azure footprint to enumerate here, so "cloud asset discovery" means introspecting and self-probing this project's own Express/Next.js stack.
Self-scan only, never an open-ended scanner: the attack surface scanner (services/cloud/attackSurfaceScanner.js) only ever requests paths against SecureShare's own configured base URL (APP_BASE_URL, defaulting to http://localhost:<PORT>) - it accepts no caller-supplied target, so it can't be repurposed to probe third-party hosts. The certificate monitor similarly only connects to domains explicitly configured via CLOUD_MONITORED_DOMAINS/WEBAUTHN_ORIGIN.
Static analysis over live introspection: the asset discovery service parses backend/routes/*.routes.js source files directly (rather than walking a live Express app object) to avoid a circular import between server.js and the new controller, and so discovery works identically whether or not the server process happens to be running.
Findings feed existing pipelines, not a new one: every Phase 11 finding is either a CloudFinding document (config/exposure/certificate/threat-intel) or a SecurityEvent logged through the same logSecurityEvent() every other phase uses - which already re-enters the SOAR engine. A new cloudSecurityEvaluator reads open CRITICAL/HIGH CloudFindings directly inside the existing Compliance evidence context, so cloud posture lowers compliance scores without a second scoring system.
What Phase 11 does not do: it does not perform network port scanning against arbitrary hosts, does not call out to any cloud provider API, and does not block uploads/logins/downloads - like Phase 10, it is a continuous self-assessment layer, not an enforcement gate.
A self-scanning layer over SecureShare's own repository, dependencies, source code, container, and CI/CD config - not a connector to a real GitHub org, CVE feed, or CI system, since none exist for this project to integrate with.
Secrets are never exfiltrated, even to our own database: secretScanner.js persists only a masked preview (abcd...wxyz) and rule metadata for any match, never the full matched value - the same masking discipline DLP's detectors already use. .env/.env.production (this deployment's real, live secrets) are deliberately excluded from scanning entirely - persisting even a masked preview of a real production secret into the findings collection would be a worse exposure surface than not scanning the file at all.
Self-matching is explicitly excluded, not silently wrong: the SAST and secret scanners' own rule definitions describe what they detect in prose (e.g. a rule titled "Use of eval()", a pattern literally containing -----BEGIN ... PRIVATE KEY-----) - scanning backend/services/devsecops/ itself would trivially flag that descriptive text as a finding about itself. Both scanners exclude that directory from their own source-file walk for this reason, discovered and fixed during this phase's own manual verification pass.
Container/IaC scanning is static analysis only: containerScanner.js and iacScanner.js never build, pull, or run any image - they parse the Dockerfile/docker-compose.yml text directly, the same static-analysis approach as services/cloud/configScanner.js.
Artifact "signing" is HMAC integrity, not a code-signing certificate: artifactSecurity.js signs an artifact's SHA-256 hash with an HMAC keyed on the app's existing JWT_SECRET - this proves the hash wasn't altered by someone without that secret, which is a real tamper-detection guarantee, but it is explicitly not the same trust model as a PKI-backed code-signing certificate (no independent, third-party-verifiable identity is attached to the signature).
Findings feed existing pipelines, not a new one: every Phase 12 finding is either a DevSecOpsFinding document (dependency/secret/SAST/container/IaC/pipeline) or a SecurityEvent logged through the same logSecurityEvent() every other phase uses. A new devSecOpsEvaluator reads open CRITICAL/HIGH DevSecOpsFindings directly inside the existing Compliance evidence context, so supply-chain posture lowers compliance scores without a second scoring system.
"Block Deployment" is honestly advisory: the SOAR blockDeployment action records a flag on the triggering finding and logs the event - it does not (and cannot) halt a real CI/CD pipeline, because this project has none configured. This is stated explicitly rather than implied, matching Phase 11's "does not do" precedent below.
What Phase 12 does not do: it does not scan or build container images, does not call any real CVE database/NVD API (the dependency/base-image advisory tables are small, curated, offline lists), does not enforce a real deployment gate, and does not certify supply-chain compliance with any standard - it is a continuous internal self-assessment tool, like Phase 10 and Phase 11.
An operational-resilience layer, not a new detection or crypto boundary - it monitors and reports on the health of SecureShare's managed cloud dependencies (MongoDB Atlas, Redis Cloud, Cloudinary, ClamAV on Render) and its own deployed services (Vercel frontend, Render backend), and its only enforcement-adjacent action is firing existing SOAR playbooks (notify admin, raise incident) when a rule matches. This deployment target has no VPS/host to run Nginx or Docker Compose orchestration on, so there is deliberately no local CPU/disk/memory/network monitoring - every health check is a reachability/latency probe against a managed dependency.
Redis is optional, everywhere it's used: rate limiting, the background job queue, and health checks all check isRedisAvailable() before touching Redis Cloud and fall back to an in-memory/in-process equivalent otherwise. No feature (including uploads, downloads, and every prior phase's scan) becomes unavailable solely because Redis is unset or unreachable - this was a deliberate design constraint, not an incidental property.
Backups are read-only and non-destructive by design: backupManager.js only ever creates archives and re-validates their checksum - there is no restore/import code path at all, so a compromised or malicious backup-management request can read data into an archive but can never overwrite live data.
Configuration backups never include secrets: createConfigurationBackup() reads from an explicit allow-list of non-secret environment variable names (port, log level, WebAuthn RP config, etc.) - JWT_SECRET, database credentials, and API keys are never included, even accidentally, since the backup only ever reads keys present in that allow-list.
Alert events use a distinct SIEM category so SOAR can still act on them: soarEngine.js ignores events categorized AUTOMATION to prevent automation-triggering-automation loops (Phase 8's original safeguard). Platform alerts are categorized PLATFORM instead - a deliberate choice so an admin-notification playbook can still fire in response to, say, a MONGODB_OFFLINE alert.
Authentication metrics reuse existing events rather than adding new instrumentation: the authentication success/failure rate on /platform is computed from Phase 9's existing login/login_failed SIEM events - no code in the auth controller or login flow was touched to build this.
What Phase 13 does not do: it does not implement real email/SMTP delivery (alerts still route through the same in-app Notification mechanism every prior phase uses); it does not provide a destructive database restore; it does not add a new authentication or encryption boundary; and it does not time every scan's duration - threat/malware/DLP scans that run inline during upload aren't instrumented (only re-scans routed through the Phase 13 background queue are), since timing the inline path would require touching Phase 4/5's upload controller.
A client-side hardening pass, not a new authorization boundary - every route this phase touches (SOAR, IAM, Compliance, Cloud Security, DevSecOps, Platform) was already enforced server-side by requireAdmin/requireRole since Phase 8/9. What was missing was the UI honoring the same boundary: a non-admin could previously see (and click into a 403 on) admin-only nav links, dashboard cards, and search results.
Hide, never disable: frontend/components/rbac/RoleGuard.tsx's <AdminOnly>/<RoleGuard> remove admin-only elements from the DOM outright rather than rendering them disabled/grayed-out - a disabled button still leaks that the feature exists and what it's called, which <AdminOnly> avoids entirely.
The frontend guard is a UX convenience, not the security boundary: <RequireRole>'s client-side redirect to /403 (or /login) only prevents a confusing screen flash for someone who manually types an admin URL or has a stale JWT in local storage - the actual authorization decision is still made by the backend's requireAdmin/requireRole middleware on every request, exactly as it was before this phase. A forged or manipulated client-side role check can hide UI but can never grant real access.
Search never queries what the user can't read: frontend/components/shell/QuickSearch.tsx excludes admin-only categories (Users, Compliance evidence, Cloud assets) from its result set before issuing any request - a non-admin's search never fires a request against an endpoint that would 403, avoiding both the wasted round-trip and any risk of the failure leaking through the UI.
What Phase 15 does not do: it does not change any backend route, middleware, or JWT claim; it does not introduce a new role or permission model (it reuses Phase 9's existing role/isAdmin/org_owner claims); and it does not change what an admin account can see or do - only what a non-admin account is shown.
| Purpose | Algorithm | Notes |
|---|---|---|
| File content encryption | AES-256-GCM | Generated fresh per file, 96-bit random IV, browser-only (Web Crypto API). The GCM authentication tag doubles as a tamper-evidence check on decrypt. |
| Key wrapping (owner access) | RSA-OAEP-SHA256 | Per-user keypair, 3072-bit modulus by default (2048-bit minimum supported). Wraps the AES key so the owner can always re-access their own uploads. |
| Key wrapping (password-protected sharing) | PBKDF2-SHA256 → AES-GCM | 210,000 iterations by default; derives a wrapping key from the share password, itself never transmitted to the server. |
| Digital signatures | ECDSA P-256 | Per-user signing keypair, distinct from the RSA encryption keypair. Signs SHA-256(ciphertext) via the Web Crypto API's combined sign-and-hash primitive. |
| Hashing (signatures, integrity, threat intel) | SHA-256 | Primary hash throughout — file integrity hashes, VirusTotal lookups, device/session identifiers. |
| Hashing (Phase 4, interoperability only) | SHA-1, MD5 | Computed alongside SHA-256 for compatibility with legacy threat-intel tooling that keys on them. Never used as the basis for any security decision — informational only. |
| Password storage | bcrypt | User account passwords (login credentials), 10 salt rounds. Distinct from — and unrelated to — the PBKDF2 key derivation used for share-link passwords above. |
| Session/auth tokens | JWT (HMAC via JWT_SECRET) |
Carries a revocable session id (sid) as of Phase 3; sessions are independently checked against a Session collection on every request. |
Trust boundary: the server (and anyone who compromises it — including database backups, Cloudinary storage, or a malicious insider) is assumed to potentially see everything it's sent, but is never trusted with plaintext file content, raw AES keys, or any private key (RSA or ECDSA). The one narrow, deliberate exception is described in Threat Model below.
Client trust: the browser performing encryption/decryption/signing is trusted for the duration of that operation — SecureShare cannot protect against a compromised endpoint (malware on the user's own machine, a malicious browser extension, etc.). This is inherent to any client-side-crypto system and is called out explicitly rather than implied.
Zero-knowledge, with two documented exceptions: Phase 4's malware scanning and Phase 5's DLP scanning both fundamentally cannot operate on ciphertext (encrypted bytes carry no detectable signature or extractable text, regardless of the underlying content). SecureShare resolves this by having the browser send plaintext to POST /api/threats/scan and POST /api/dlp/scan — and only those two endpoints — before any encryption happens. Each buffer exists in server memory for the duration of a single request, is never written to disk or logged, and only the resulting verdict is persisted (hashes/risk level/threat names for Phase 4; masked finding previews/severity/decision for Phase 5 — never the raw matched secret values). This is a conscious, minimal trade-off, not an oversight — see README.md's Phase 4 and Phase 5 sections for the full reasoning and the alternatives that were considered.
Defense in depth: security-critical checks are enforced server-side even when a client-side UI gate exists for UX purposes. For example, the upload page refuses to proceed past a Critical/High-risk scan client-side (fail-fast UX), but the actual security boundary — the download-time quarantine block — holds unconditionally even if that client gate is bypassed entirely (e.g. a direct API call).
- A fully compromised server, including database access, that never held plaintext, raw keys, or the transient Phase 4 scan buffer.
- A network observer (passive or active MITM, absent TLS-stripping) between browser and server — sees only ciphertext, wrapped keys, and hashes.
- A malicious or careless third party gaining read access to Cloudinary storage — sees only ciphertext.
- Tampering with stored ciphertext or its metadata (Phase 2 signatures catch this before decryption).
- Access attempts from unauthorized devices/networks/times against a policy-protected file (Phase 3).
- Distribution of known-malware or disguised-executable files (Phase 4), to the extent ClamAV/VirusTotal/heuristic signals can detect them.
- Accidental upload of files containing embedded secrets or PII in supported text formats (Phase 5), to the extent the configured detectors can recognize them.
- Lack of visibility into related, multi-step suspicious activity (e.g. a quarantined file later targeted for download) — Phase 6's correlation engine surfaces this as a single incident instead of disconnected log rows.
- Configuration drift and exposed attack surface in SecureShare's own deployment (missing security headers, expiring certificates, publicly reachable debug/admin paths) — Phase 11's CSPM/ASM scanner continuously re-checks these rather than relying on a one-time manual review.
- Software supply chain risk in SecureShare's own codebase (known-deprecated/typosquat dependencies, hardcoded secrets, common code-level vulnerability patterns, insecure container/IaC configuration, missing CI/CD security gates) — Phase 12's DevSecOps scanner continuously re-checks these against the real repository rather than a one-time manual review.
- A compromised client device/browser at the moment of encryption, decryption, or signing — the endpoint holding key material in memory during that operation is inherently trusted for that operation.
- Loss of the share link/password with no other recovery path — by design, the server cannot recover a lost key on the user's behalf (that would defeat zero-knowledge).
- Device-bound private keys: RSA/ECDSA private keys live only in the browser's IndexedDB; clearing browser storage or switching devices without the original share link/password permanently loses owner-side access to previously uploaded files.
- The transient plaintext exposure window during Phase 4/5 scanning (see above) — accepted as the minimum necessary trade-off to offer real malware/DLP detection at all.
- Server-reported public keys (RSA encryption keys, ECDSA signing keys) are currently trusted at face value — see Limitations below regarding key pinning.
- Heuristic/signature-based detection limits: ClamAV and VirusTotal only catch known malware signatures and known-suspicious patterns — zero-day or sufficiently obfuscated malicious content can evade both.
- Pattern-based DLP detection limits: regex/heuristic detectors will miss secrets in binary formats, obfuscated/encoded values, or PII formats not covered by an existing detector. Confidence-based scoring (credit cards) substantially reduces false positives on similarly-shaped non-sensitive data (Ride IDs, invoice/tracking/order numbers), but detectors without confidence scoring (passport, phone, IBAN, SWIFT/BIC) still carry a higher false-positive rate (see Limitations).
- Denial of service: rate limiting (
express-rate-limit) mitigates casual abuse but SecureShare has no dedicated DDoS protection layer; that's expected to be handled at the hosting/CDN level in production.
- No out-of-band key verification. A downloader's ECDSA signature check trusts whatever
signingPublicKeythe server currently reports for a file's owner. A fully malicious server could theoretically substitute both the public key and re-sign with a key it controls — a strictly harder attack than tampering with ciphertext alone, but not impossible in a total-server-compromise scenario. A future hardening step would let users cross-verify each other's key fingerprints out-of-band (see README's Phase 2 "Known limitation"). - Geo-IP resolution is a header-based stub, not a dedicated geo-IP database —
allowedCountriespolicy rules rely onCF-IPCountry/X-Vercel-IP-Country-style headers from an upstream CDN/proxy. Locally, or on a host that doesn't inject one, country resolves to"Unknown"and fails closed (anyallowedCountriesrestriction is simply never satisfiable). See DEPLOYMENT.md if you need a real geo-IP provider integrated. requireApprovalhas no dedicated approval workflow. It currently means "the recipient must be an authenticated, already-trusted-device user" — there's no in-app request/approve UI for a new device or user to be granted access; the owner must pre-authorize by other means.- ClamAV/VirusTotal availability is environment-dependent. Neither is bundled with the app; both are optional dependencies that degrade to "unavailable"/"skipped" rather than blocking uploads. A deployment that never configures either still runs, but with meaningfully reduced malware-detection coverage (magic bytes and heuristics only).
- No automated test suite yet for most of the above — SECURITY_TESTING.md documents manual procedures; the DLP engine (
backend/tests/dlp.test.jsandbackend/tests/dlpConfidence.test.js, run vianode --test) is the first automated coverage, converting the remaining pure-function pieces (riskEngine.js,policyEngine.js) into an automated suite is a natural next step. - Legacy (
encryptionVersion: 1) files remain server-side-decryptable by design, using a single global RSA-2048 keypair — this predates the zero-knowledge model and exists purely for backward compatibility with files uploaded before Phase 1. New uploads never use this path. - DLP detection is heuristic, not exhaustive. Passport and phone-number patterns in particular are broad and will false-positive on similarly-shaped IDs; Aadhaar validation uses a first-digit heuristic rather than the full Verhoeff checksum; SWIFT/BIC requires a nearby "SWIFT"/"BIC" keyword to fire at all, so codes mentioned without that label are missed. Credit card detection is confidence-scored (Luhn + context analysis, see above) rather than a blanket regex match, which substantially reduces false positives on ride-receipt/invoice/tracking-number style IDs, but the context-keyword lists are English-language and finite — a false positive/negative is still possible with unusual phrasing or non-English documents. Only text-based files are inspected — secrets embedded in binary formats (compiled binaries, image metadata, PDFs, ZIP-based Office documents, etc.) are not detected, and scanned content is capped at 5MB per file.
backend/services/dlp/textFileSupport.jssniffs magic bytes (utils/magicBytes.js) to hard-exclude known binary containers (PDF, ZIP/DOCX/XLSX/PPTX, RAR, 7z, gzip, executables, RTF) from being misread as text — a fixed bug where a PDF's mostly-printable-ASCII object/xref header let its compressed binary content slip past the old printable-byte-ratio check and get regex/Luhn-scanned as if it were plaintext, producing spurious HIGH-confidence "credit card" findings on harmless receipt/invoice PDFs. Practical implication: PDF receipts/invoices/statements are currently skipped entirely, not scanned — DLP coverage for PDF content specifically would require adding a real PDF text-extraction step, which is not yet implemented. - DLP's
require_approvaldecision has no synchronous confirmation step in the legacy (encryptionVersion: 1) upload flow — since that path is a single request with no round-trip, such findings are refused rather than held for approval; use the v2 (zero-knowledge) flow, which supportsPOST /api/dlp/scans/:id/acknowledge, if you need to override one. - The SIEM correlation engine is rule-based, not ML/anomaly-based. It only recognizes the specific patterns encoded in
backend/services/siem/correlationEngine.js(currently three rules); attack patterns outside those rules won't be automatically grouped into an incident, though the underlying events are still logged and visible individually. - Signature-verification events are self-reported by the client.
POST /api/siem/events/signaturerecords whatever outcome the browser's own ECDSA check produced — a fully compromised client could report a false outcome. This doesn't weaken the actual signature check (which still runs and still blocks a genuinely invalid download client-side); it only means the SIEM's record of that outcome carries the same trust level as any other client-observed telemetry. - Events logged before Phase 6 lack
severity/category/siemType. They still appear in the SIEM's event list and the original Audit Logs page, just without those fields populated (shown as "uncategorized" in SOC views).
If you discover a security vulnerability in SecureShare, please report it privately rather than opening a public issue.
- Do: email the maintainer (see repository contact info) with a clear description, reproduction steps, and — if possible — the affected component/file.
- Do: give a reasonable window to investigate and address the issue before any public disclosure.
- Don't: test against production data you don't own, or any deployment you haven't been explicitly authorized to test.
- Don't open a public GitHub issue with exploit details before a fix is available.
There is currently no formal bug bounty program. Reports are still welcomed and will be credited (with permission) once resolved.