Security
Security at VerifyWise
Security is central to the VerifyWise AI governance platform. We're committed to secure development throughout our entire lifecycle with a transparent, rigorous approach.
Our security framework
Comprehensive security practices built into every aspect of our platform
Secure development practices
Automated vulnerability scanning, license violation checks, and commit reviews to detect credential leakage using GitHub for secure source code hosting.
Continuous improvement
Values community feedback, allowing security experts to identify vulnerabilities. Security issues reported to security@verifywise.ai.
Compliance & legal
Committed to GDPR and privacy laws. Works with Data Protection Officer and maintains compliance with security standards.
Deployment & monitoring
Incorporates automated testing, provides deployment security guidelines, and continuous vulnerability monitoring.
Application security
Data encryption in rest and transit, role-based access controls (RBAC), and restricted system access.
Transparency
Transparent security philosophy that welcomes professional contributions for faster vulnerability addressing.
Additional security measures
Comprehensive security practices across all aspects of development
Community security guidelines
Secure coding practices required with detailed pull request review process.
Dependency management
GitHub tools scan third-party dependencies with routine dependency checks and updates.
Code review process
Internal and community code reviews with automated and manual security checks.
Open governance
Trusted contributor selection and monitoring of maintainer and contributor integrity.
Transparent security philosophy
Our transparent approach enables security experts worldwide to identify vulnerabilities and contribute to improvements. This model allows for faster vulnerability addressing and professional community contributions.
- Community-driven security improvements
- Transparent vulnerability disclosure
- Professional security expert contributions
- Faster security issue resolution
Security contact
Found a security issue? We value responsible disclosure and community feedback.

SOC 2 Type I attested
VerifyWise has completed a SOC 2 Type I attestation, independently verified for security, availability, and confidentiality controls.
Security overview
Platform security overview
An evidence-based account of security controls in the VerifyWise codebase. Every claim references specific source files. Where controls are partial or absent, this document says so.
Version 1.0 ยท February 2026 ยท Public
Executive summary
VerifyWise is an AI governance platform for managing compliance frameworks, model inventories, risk assessments, vendor due diligence, and policy lifecycles. Deployed self-hosted, organizations retain full control over their data and infrastructure.
Tenant isolation
Schema-per-tenant architecture using SHA-256 derived identifiers, ensuring complete data separation between organizations.
Authentication
JWT-based access tokens (1-hour lifetime) with separate refresh tokens (30-day lifetime, httpOnly cookies), verified through a 7-layer middleware chain.
Authorization
Role-based access control with four roles (Admin, Reviewer, Editor, Auditor), enforced at the middleware layer with real-time database cross-checks.
Encryption
AES-256-CBC with per-operation random initialization vectors for sensitive fields; bcrypt for password hashing.
Input validation
Custom validation framework combined with parameterized SQL queries throughout.
Rate limiting
Tiered rate limits across authentication, general API, file operations, and AI scan endpoints.
Dependency scanning
Weekly Dependabot scans across npm, pip, GitHub Actions, and Docker ecosystems with CI-enforced audit checks.
Docker Compose for development and small-scale deployments; Kubernetes with Kustomize for production environments.
Architecture overview
Technology stack, multi-tenancy model, and deployment patterns
Technology stack
| Component | Technology | Version |
|---|---|---|
| Backend API | Node.js / Express / TypeScript | Node 24 (Alpine) |
| Database | PostgreSQL | 16.8 |
| Cache / Queue | Redis | 7 |
| Frontend | React 18 / TypeScript | - |
| Evaluation Server | Python (FastAPI) | - |
| Container Runtime | Docker / Kubernetes | - |
Multi-tenancy model
VerifyWise implements schema-per-tenant isolation at the PostgreSQL level. Each organization receives a dedicated database schema derived from its organization ID:
- 1. The organization ID (integer) is hashed using SHA-256
- 2. The digest is encoded as base64
- 3. Non-alphanumeric characters are stripped
- 4. The result is truncated to exactly 10 characters
This produces a deterministic, collision-resistant schema identifier. All tenant data resides within its dedicated schema, and every query targets the tenant-specific schema qualified by the authenticated user's tenant hash.
Trust boundaries
Application layer
Internal network only โ no direct internet access
All database and cache services communicate over an internal Docker bridge network and are not exposed to external traffic. The backend API is the sole gateway to data services.
Deployment models
Docker Compose
Defined in docker-compose.yml for development and small-scale deployments, with a production overlay in docker-compose.prod.yml. Secrets via .env file.
Kubernetes
Kustomize-based manifests with a dedicated verifywise namespace, PersistentVolumeClaims for PostgreSQL, and optional TLS Ingress via cert-manager. Secrets via Kubernetes Secrets.
Authentication & access control
JWT-based authentication, role-based authorization, and rate limiting
JWT dual-token pattern
VerifyWise uses a dual-token pattern. An access token is signed with JWT_SECRET using HMAC-SHA256 (HS256) and expires after 1 hour. A refresh token is signed with a separate REFRESH_TOKEN_SECRET and expires after 30 days, stored in an httpOnly cookie.
| Attribute | Value |
|---|---|
| httpOnly | true (always) |
| path | /api/users |
| expires | 30 days from issuance |
| secure | true in production, false otherwise |
| sameSite | none in production, lax otherwise |
Password hashing
Passwords are hashed using bcrypt v6 with a cost factor of 10. The bcrypt library handles salt generation automatically. Password verification uses bcrypt.compare(), which performs constant-time comparison to mitigate timing attacks.
7-layer authentication middleware chain
Every authenticated request passes through the following sequential checks. If any layer fails, the request is rejected before reaching the controller.
Token presence
Extracts Bearer token from the Authorization header. Returns 400 if absent.
JWT signature verification
Verifies the token signature against JWT_SECRET using jsonwebtoken.verify(). Returns 401 if invalid.
Token expiration
Compares the expire timestamp in the payload against Date.now(). Returns 406 if expired.
Payload structure validation
Verifies id is a positive number and roleName is a non-empty string. Returns 400 if malformed.
Organization membership
Queries the database to confirm the user belongs to the claimed organization. Returns 403 if not a member.
Role consistency
Fetches the user's current role from the database and compares it to the token's roleName. Returns 403 if the role has changed since token issuance.
Tenant hash validation
Validates the tenant hash format using a regex, then recomputes the hash from the organization ID and compares. Returns 400 if mismatched.
Role-based access control
| Role ID | Role name | Description |
|---|---|---|
| 1 | Admin | Full system access |
| 2 | Reviewer | Review and approval permissions |
| 3 | Editor | Content editing permissions |
| 4 | Auditor | Read-only audit access |
Rate limiting
Four rate limiter tiers are implemented using express-rate-limit. Rate limit headers follow the IETF RateLimit-* standard.
| Limiter | Window | Max requests | Applied to |
|---|---|---|---|
authLimiter | 15 minutes | 5 | Authentication endpoints |
generalApiLimiter | 15 minutes | 100 | Standard API endpoints |
fileOperationsLimiter | 15 minutes | 50 | File upload/download/delete |
aiDetectionScanLimiter | 60 minutes | 10 | AI detection scan operations |
Data protection
Encryption, tenant isolation, and SQL injection prevention
Encryption at rest
Sensitive fields (API keys, OAuth tokens, integration credentials) are encrypted using AES-256-CBC:
| Parameter | Value |
|---|---|
| Algorithm | aes-256-cbc (128-bit block cipher with 256-bit key) |
| Key | Derived from ENCRYPTION_KEY env var, padded or truncated to exactly 32 bytes |
| Initialization vector | 16 random bytes generated via crypto.randomBytes(16) per encryption operation |
| Output format | {iv_hex}:{ciphertext_hex} โ IV and ciphertext stored together, colon-delimited, both hex-encoded |
Tenant data isolation
Tenant isolation is enforced at multiple layers:
Schema separation
Each tenant's data resides in a dedicated PostgreSQL schema created by createNewTenant(), which executes CREATE SCHEMA.
JWT-bound tenant hash
The tenant hash is embedded in the JWT payload at login and verified on every request.
Organization membership check
Every authenticated request verifies the user belongs to the claimed organization via a database query.
Tenant hash format validation
A regex (/^[a-zA-Z0-9]{10}$/) rejects any tenant identifier that does not match the expected format.
Identifier escaping
SQL queries that interpolate schema names use escapePgIdentifier(), which validates the identifier and double-quotes it.
SQL injection prevention
All database queries use parameterized statements via Sequelize's replacements option. Tenant schema names, which cannot be parameterized in PostgreSQL, are validated against the strict alphanumeric regex and escaped via escapePgIdentifier() before interpolation.
Secure sharing
Share links use 64-character hex tokens generated by crypto.randomBytes(32).toString("hex"). Token format is validated server-side using /^[a-f0-9]{64}$/. Share links support configurable field visibility and optional expiration dates.
Application security
Input validation, XSS prevention, file upload controls, and security headers
Input validation framework
VerifyWise implements a custom validation framework with type-specific validators, composed into schema objects and applied as Express middleware:
validateString()Required, minLength, maxLength, regex pattern, empty string, whitespace trimming
validateNumber()Required, min/max range, integer constraint, positive constraint
validateEnum()Validates against allowed set, supports arrays
validateDate()Valid date parsing, min/max date, future-only and past-only constraints
validateForeignKey()Validates positive integer IDs for database foreign key references
XSS prevention
Frontend (DOMPurify)
Rich text content is sanitized with explicit allowlists:
- Allowed tags: p, br, strong, b, em, i, u, h1-h6, blockquote, code, pre, ul, ol, li, a, img, span, div
- Forbidden tags: script, object, embed, iframe, form, input, button
- Forbidden attributes: onerror, onload, onclick, onmouseover, onfocus, onblur
- URI validation: http, https, mailto, tel only
Backend (striptags)
HTML stripping is performed using the striptags library for content validation, such as checking that policy content contains meaningful text after tag removal.
File upload security
| Control | Implementation |
|---|---|
| Size limit | 30 MB maximum, enforced by multer |
| MIME whitelist | Documents (PDF, DOC, DOCX, XLS, XLSX, CSV, MD), images (JPEG, PNG, GIF, WEBP, SVG, BMP, TIFF), videos (MP4, MPEG, MOV, AVI, WMV, WEBM, MKV) |
| Extension validation | File extension must match the declared MIME type |
| Filename sanitization | Special characters stripped, spaces replaced with underscores, length limited to 200 characters |
| Storage | Memory storage (multer memoryStorage()), files stored as database BLOBs โ no temp files on disk |
| Access control | Upload and delete restricted to Admin, Reviewer, Editor via authorize() middleware |
| Rate limiting | 50 requests per 15 minutes via fileOperationsLimiter |
CORS & security headers
CORS configuration
- Origin must match
HOSTenv var, localhost, 127.0.0.1, or ::1 - Requests with no origin are permitted
credentials: truefor cookie transmission- Allowed headers: Authorization, Content-Type, X-Requested-With
Security headers (helmet)
- HSTS
- X-Frame-Options: DENY
- X-Content-Type-Options: nosniff
- X-DNS-Prefetch-Control
- Referrer-Policy
Infrastructure & deployment security
Docker configuration, Kubernetes security, and secrets management
Docker configuration
- Backend container built from
node:24-alpine3.20(pinned version, not :latest) - Dependencies installed using
npm cifor reproducible builds - Health checks defined for PostgreSQL (
pg_isready) and Redis (redis-cli ping) - Backend depends on both services being healthy before starting
Kubernetes configuration
Namespace isolation
All resources deployed to the verifywise namespace.
Persistent storage
PersistentVolumeClaims for PostgreSQL data.
TLS Ingress
Optional cert-manager integration for automatic Let's Encrypt certificates, forced HTTPS redirect, and security headers.
Secrets management
Kubernetes Secrets for database credentials, JWT secrets, and encryption keys (base64-encoded, template provided).
Database & Redis security
PostgreSQL 16.8
Runs as an internal service with password authentication. Database port not exposed to host network โ only the backend accesses it via Docker's internal network.
Redis 7
Handles rate limiting and background job queues. Uses expose (internal only) rather than ports (host-mapped).
Logging, monitoring & auditability
Structured logging, audit trails, and access tracking
Application logging
VerifyWise uses Winston with DailyRotateFile for structured, tenant-specific application logging:
| Configuration | Value |
|---|---|
| File pattern | app-YYYY-MM-DD.log |
| Max file size | 10 MB per file |
| Retention | 14 days (auto-rotated) |
| Timezone | UTC (forced) |
| Directory | Tenant-specific subdirectories |
| Production mode | File logging only (no console) |
| Development mode | File + colorized console output |
Structured log format
{logId}, {timestamp (ISO 8601 UTC)}, {state}, {description}, {functionName}, {fileName}Three logging functions enforce consistent usage: logProcessing() records the start of an operation, logSuccess() records successful completion, and logFailure() records errors.
Database audit trail
Non-read operations are automatically logged to the tenant-specific event_logs table:
| Column | Description |
|---|---|
event_type | Create, Update, Delete, or Error |
description | Human-readable description of the event |
user_id | ID of the user who performed the action |
Software supply chain
Dependency scanning, CI/CD security checks, and base image pinning
Dependency scanning
Dependabot is configured for automated dependency scanning across all ecosystems:
| Ecosystem | Directory | Frequency | PR limit |
|---|---|---|---|
| npm (frontend) | /Clients | Weekly (Monday) | 10 |
| npm (backend) | /Servers | Weekly (Monday) | 10 |
| pip (evaluation) | /EvaluationModule | Weekly (Monday) | 5 |
| GitHub Actions | / | Weekly (Monday) | 5 |
| Docker (frontend) | /Clients | Weekly | - |
| Docker (backend) | /Servers | Weekly | - |
CI/CD security checks
Backend checks
npm audit --audit-level=highโ flags known vulnerabilities- TypeScript compilation (
npm run build) dependency-review-actionโ blocks PRs with high-severity CVEs or restrictive licenses
Frontend checks
- Equivalent
npm auditand dependency review - Build checks for the frontend codebase
CI permissions
Both workflows use minimal GitHub token permissions: contents: read and security-events: write.
Base image pinning
Docker base images use pinned versions rather than floating tags:
- Backend:
node:24-alpine3.20 - Database:
postgres:16.8 - Cache:
redis:7
Secure development lifecycle
Code review, CI verification, secrets prevention, and vulnerability disclosure
Code review process
All code changes are submitted via pull requests targeting master or develop, triggering automated CI checks. Changes require review before merging.
CI verification
On every PR, the CI pipeline verifies TypeScript compiles without errors, npm audit reports no high-severity vulnerabilities, new dependencies don't introduce high-severity CVEs or restrictive licenses, and backend tests pass.
Secrets prevention
The .gitignore file excludes .env files from version control. The Kubernetes secrets example file documents the pattern for managing secrets without committing actual values.
Vulnerability disclosure
A SECURITY.md file exists at the repository root, directing reporters to create security advisories via GitHub's security advisory feature.
Implement secure AI governance
VerifyWise provides evidence-based security controls for AI risk management. Deploy on-premises or in your cloud with full control over your data and infrastructure.