This document describes the security features, compliance posture, and best practices for deploying Unchained Engine in security-sensitive environments including banking, government, and enterprise contexts.
If you discover a security vulnerability, please report it responsibly:
- Email: hello@unchained.shop
- Do NOT open public GitHub issues for security vulnerabilities
We will acknowledge receipt within 48 hours and provide a detailed response within 7 days.
This section describes how Unchained Engine can support compliance efforts. Note: Compliance certifications (PCI DSS, ISO 27001, SOC 2, etc.) are issued to organizations and their processes, not to software products. The controls described here can help organizations meet technical requirements.
| Standard | Support Level | What This Means |
|---|---|---|
| PCI DSS SAQ-A | Compatible | No card data storage; uses tokenization. Eligibility depends on your full deployment. |
| ISO 27001 | Technical Controls | Implements access control, audit logging, and cryptographic standards. ISMS policies and processes are your responsibility. |
| FIPS 140-3 | Algorithm Compatible | Uses FIPS-approved algorithms (PBKDF2, SHA-256/512, AES-256-GCM). Requires FIPS-validated runtime for full compliance. |
| SOC 2 | Audit Support | Provides tamper-evident audit logs for evidence collection. SOC 2 audits evaluate your organization's controls, not software. |
| FINMA 2023/1 | Technical Controls | Audit logging, access control, and cryptography support ICT risk management requirements. The circular is principle-based; organizational controls are your responsibility. |
| GDPR | Technical Measures | Audit logging supports Article 30 requirements. Data protection policies are your responsibility. |
Unchained Engine uses modern, standards-compliant cryptography throughout:
- Algorithm: PBKDF2 with SHA-512
- Iterations: 300,000 (exceeds OWASP recommendation of 210,000)
- Salt: 16 bytes, cryptographically random
- Key Length: 256 bytes
- Implementation: Web Crypto API (
crypto.subtle)
// packages/core-users/src/module/pbkdf2.ts
const PBKDF2_ITERATIONS = 300000;
const PBKDF2_KEY_LENGTH = 256;
const PBKDF2_SALT_LENGTH = 16;
// Uses SHA-512 via crypto.subtle.deriveBits()- Token Generation:
crypto.randomUUID()(CSPRNG-based, 128 bits of entropy) - Token Storage: SHA-256 hashed before database storage
- Token Expiration: Time-limited (1 hour for verification tokens)
- Single Use: Tokens are invalidated after use
Why SHA-256 for Tokens (not PBKDF2)?
Per OWASP guidance, slow hashing algorithms (bcrypt, PBKDF2, Argon2) are designed for low-entropy user passwords. API access tokens generated with CSPRNG have high entropy (128+ bits), making brute-force computationally infeasible regardless of hash speed. Using SHA-256 for high-entropy tokens is both secure and performant for stateless API authentication where every request must be verified.
// packages/core-users/src/module/configureUsersModule.ts
// Preferred: Server generates high-entropy token
const result = await modules.users.createAccessToken('admin');
console.log(result.token); // e.g., "550e8400-e29b-41d4-a716-446655440000"- Hash IDs: Generated using
crypto.getRandomValues()(CSPRNG) - Nonces:
crypto.randomUUID()for WebAuthn/Web3 challenges - No weak RNG:
Math.random()is never used for security-sensitive operations
- Algorithm: AES-256-GCM (authenticated encryption)
- Key Size: 32 bytes
- IV Size: 16 bytes
- Implementation: kruptein library
- HMAC-SHA-256: Datatrans, Payrexx, Saferpay, GridFS uploads
- HMAC-SHA-512: PostFinance Checkout
Full support for passwordless authentication via the WebAuthn standard, enabling hardware security keys and platform authenticators.
Unchained Engine uses FIPS 140-3 approved algorithms and can run on FIPS-validated runtimes. Note: The software itself is not FIPS-validated; validation requires certification by a NIST-accredited lab. For true FIPS compliance, deploy on a FIPS-validated runtime.
All cryptographic operations in Unchained use FIPS 140-3 approved algorithms:
| Operation | Algorithm | FIPS Status |
|---|---|---|
| Password Hashing | PBKDF2-SHA-512 | Approved |
| Token Hashing | SHA-256 | Approved |
| Session Encryption | AES-256-GCM | Approved |
| Payment Signatures | HMAC-SHA-256/512 | Approved |
| Random Generation | CSPRNG | Approved |
Use the Chainguard node-fips container image which includes a FIPS-validated OpenSSL module:
FROM cgr.dev/chainguard/node-fips:latest
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
# FIPS mode is enabled by default in this image
CMD ["node", "index.js"]Build or use a Node.js binary compiled with OpenSSL 3.x FIPS provider:
# Enable FIPS mode via environment variable
export OPENSSL_CONF=/path/to/openssl-fips.cnf
node --enable-fips your-app.jsExample openssl-fips.cnf:
openssl_conf = openssl_init
[openssl_init]
providers = provider_sect
alg_section = algorithm_sect
[provider_sect]
fips = fips_sect
base = base_sect
[fips_sect]
activate = 1
[base_sect]
activate = 1
[algorithm_sect]
default_properties = fips=yesimport crypto from 'crypto';
// Check if FIPS mode is enabled
console.log('FIPS mode:', crypto.getFips() === 1 ? 'enabled' : 'disabled');All cryptographic operations in Unchained use FIPS 140-3 approved algorithms:
| Operation | Algorithm | FIPS Status |
|---|---|---|
| Password Hashing | PBKDF2-SHA-512 | Approved |
| Token Hashing | SHA-256 | Approved |
| Session Encryption | AES-256-GCM | Approved |
| Payment Signatures | HMAC-SHA-256/512 | Approved |
| Random Generation | CSPRNG | Approved |
-
Pure PBKDF2: Unchained uses only PBKDF2-SHA512 for password hashing, ensuring full FIPS 140-3 compatibility for all password operations.
-
Third-Party Libraries: Verify that any additional npm packages you add use Node.js crypto APIs or are otherwise FIPS-compliant.
-
Cryptopay Plugin: Uses
@noble/curvesand@noble/hashesfor cryptocurrency operations. These implement FIPS-approved primitives but are not FIPS-certified modules.
Unchained implements comprehensive RBAC with:
- 128+ defined actions covering all API operations
- Built-in roles: admin, logged-in user, guest
- Ownership validation: Users can only access their own resources
- Field-level permissions: GraphQL type resolvers enforce access
// Example permission check
role.allow(actions.updateOrder, async (obj, params, context) => {
const order = await modules.orders.findOrder({ orderId: params.orderId });
return order.userId === context.userId;
});All GraphQL mutations are protected with permission checks:
// packages/api/src/resolvers/mutations/index.ts
export default {
logout: acl(actions.logout)(logout),
createUser: acl(actions.createUser)(createUser),
setRoles: acl(actions.manageUsers)(setRoles),
// ... all mutations protected
};Unchained is designed for PCI DSS SAQ-A eligibility:
- Credit card numbers (PAN) are never stored
- CVV/CVC codes are never stored
- Only payment provider tokens are stored
All payment integrations use tokenization:
| Provider | Tokenization Method |
|---|---|
| Stripe | PaymentIntent / SetupIntent |
| Datatrans | Secure Fields |
| Saferpay | Redirect with token |
| Braintree | Client SDK tokenization |
| PayPal | Order ID reference |
// packages/core-payment/src/db/PaymentCredentialsCollection.ts
type PaymentCredentials = {
paymentProviderId: string;
userId: string;
token?: string; // Provider-issued token only
isPreferred?: boolean;
meta: any; // Provider metadata
};// Secure defaults
{
httpOnly: true, // Prevent XSS access
secure: true, // HTTPS only (unless explicitly disabled)
sameSite: 'none', // Configurable
maxAge: 604800, // 7 days
}| Variable | Purpose | Default |
|---|---|---|
UNCHAINED_TOKEN_SECRET |
Session encryption (min 32 chars) | Required |
UNCHAINED_COOKIE_NAME |
Cookie name | unchained_token |
UNCHAINED_COOKIE_DOMAIN |
Cookie domain restriction | - |
UNCHAINED_COOKIE_SAMESITE |
SameSite attribute | none |
UNCHAINED_COOKIE_INSECURE |
Disable secure flag | false |
Errors are designed to prevent information leakage:
- Authentication errors: Generic "Invalid credentials" message
- Token errors: "Token invalid or expired" (doesn't distinguish)
- Permission errors: "Not authorized" (no action details)
- Password validation: "Too insecure" (no requirements revealed)
- User enumeration prevention: Password reset returns success regardless of user existence
- Log sanitization: Error objects are never logged directly; only message and name are captured
All user-supplied strings used in regular expressions are escaped to prevent Regular Expression Denial of Service (ReDoS) attacks:
import { escapeRegexString } from '@unchainedshop/mongodb';
// User input is escaped before regex construction
const regex = new RegExp(escapeRegexString(userInput), 'i');The escapeRegexString function escapes all special regex characters ([-/\\^$*+?.()|[\]{}]) and includes:
- Type validation (throws TypeError for non-strings)
- Length limits (max 255 characters)
- Empty string rejection
All query builder functions that accept user input for text search apply proper escaping to prevent injection attacks.
Unchained provides append-only, tamper-evident audit logging based on the OCSF (Open Cybersecurity Schema Framework). OCSF is an industry-standard schema developed by 120+ organizations (AWS, Splunk, IBM) and is now a Linux Foundation project. It is natively supported by AWS Security Lake, Google Chronicle, Datadog, Elastic, and other SIEM systems.
- OCSF-based schema - Uses OCSF v1.4.0 structure with e-commerce extensions
- JSON Lines format - Easy parsing and integration
- Append-only - No update or delete operations
- Tamper-evident - SHA-256 hash chain for integrity verification
- File-based - No external dependencies (MongoDB-free)
- HTTP push - Optional push to OpenTelemetry Collector, Fluentd, or Vector
- SIEM-ready - Direct ingestion into security monitoring tools
- Event integration - Automatic capture of authentication, orders, and payments
- E-commerce specific - Checkout, payment, refund, and access denied events
import {
createAuditLog,
OCSF_CLASS,
OCSF_AUTH_ACTIVITY,
OCSF_ACCOUNT_ACTIVITY,
OCSF_API_ACTIVITY,
} from '@unchainedshop/events';
// Create audit log instance (file-based)
const auditLog = createAuditLog('./audit-logs');
// Or with HTTP push to collector
const auditLog = createAuditLog({
directory: './audit-logs',
collectorUrl: 'http://otel-collector:4318/v1/logs',
batchSize: 10,
flushIntervalMs: 5000,
});
// Log authentication event
await auditLog.logAuthentication({
activity: OCSF_AUTH_ACTIVITY.LOGON,
userId: user._id,
userName: user.email,
success: true,
remoteAddress: req.ip,
sessionId: req.sessionID,
isMfa: true,
});
// Log failed login attempt
await auditLog.logAuthentication({
activity: OCSF_AUTH_ACTIVITY.LOGON,
userId: user._id,
success: false,
remoteAddress: req.ip,
message: 'Invalid password',
});
// Log account change event
await auditLog.logAccountChange({
activity: OCSF_ACCOUNT_ACTIVITY.ATTACH_POLICY, // Role change
userId: targetUser._id,
actorUserId: adminUser._id, // Who made the change
success: true,
});
// Log user creation
await auditLog.logAccountChange({
activity: OCSF_ACCOUNT_ACTIVITY.CREATE,
userId: newUser._id,
userName: newUser.email,
success: true,
});
// Log API activity (payments, orders, etc.)
await auditLog.logApiActivity({
activity: OCSF_API_ACTIVITY.UPDATE,
userId: user._id,
operation: 'processPayment',
success: true,
remoteAddress: req.ip,
message: 'Payment completed',
});
// Log access denied
await auditLog.logApiActivity({
activity: OCSF_API_ACTIVITY.READ,
userId: user._id,
success: false,
remoteAddress: req.ip,
message: 'Access denied',
});
// Query audit logs
const logs = await auditLog.find({
classUids: [OCSF_CLASS.AUTHENTICATION],
userId: 'user-id',
success: false,
startTime: new Date('2024-01-01'),
limit: 100,
});
// Get failed login attempts (for lockout policies)
const failedAttempts = await auditLog.getFailedLogins({
remoteAddress: '192.168.1.1',
since: new Date(Date.now() - 15 * 60 * 1000), // Last 15 minutes
});
// Verify integrity of audit log chain
const result = await auditLog.verify();
if (!result.valid) {
console.error('Audit log tampering detected:', result.error);
}
// Close audit log (flushes pending events)
await auditLog.close();For automatic audit logging of all security-relevant events, use the integration layer:
import { createAuditLog, configureAuditIntegration } from '@unchainedshop/events';
// Create audit log instance
const auditLog = createAuditLog('./audit-logs');
// Enable automatic event capture
const cleanup = configureAuditIntegration(auditLog);
// Events automatically captured:
// - API_LOGIN_TOKEN_CREATED → Authentication (LOGON)
// - API_LOGOUT → Authentication (LOGOFF)
// - USER_CREATE → Account Change (CREATE)
// - USER_REMOVE → Account Change (DELETE)
// - USER_UPDATE_PASSWORD → Account Change (PASSWORD_CHANGE)
// - USER_ADD_ROLES → Account Change (ATTACH_POLICY)
// - ORDER_CREATE → API Activity (CREATE)
// - ORDER_CHECKOUT → API Activity (CHECKOUT)
// - ORDER_ADD_PRODUCT → API Activity (UPDATE)
// - ORDER_PAY → API Activity (PAYMENT)
// - And more...
// On shutdown
cleanup();
await auditLog.close();| Class | UID | Use Cases |
|---|---|---|
| Authentication | 3002 | Login, logout, failed login, MFA |
| Account Change | 3001 | User CRUD, password changes, role changes |
| API Activity | 6003 | API access, payments, orders, access denied |
Audit logs are stored as JSON Lines (one JSON object per line):
{"class_uid":3002,"category_uid":3,"type_uid":300201,"activity_id":1,"severity_id":1,"time":1735570800000,"message":"User Login","user":{"uid":"user-123","name":"john@example.com"},"src_endpoint":{"ip":"192.168.1.1"},"status_id":1,"is_mfa":true,"metadata":{"version":"1.4.0","product":{"name":"Unchained Engine","version":"4.5"}},"unmapped":{"seq":42,"prev_hash":"abc123...","hash":"def456..."}}Authentication Activities (OCSF_AUTH_ACTIVITY):
| Activity | ID | Use Case |
|---|---|---|
LOGON |
1 | User login (success or failure) |
LOGOFF |
2 | User logout |
OTHER |
99 | Other authentication events |
Account Change Activities (OCSF_ACCOUNT_ACTIVITY):
| Activity | ID | Use Case |
|---|---|---|
CREATE |
1 | User creation |
PASSWORD_CHANGE |
3 | Password changed by user |
PASSWORD_RESET |
4 | Password reset by admin |
DELETE |
6 | User deletion |
ATTACH_POLICY |
7 | Role/permission changes |
MFA_ENABLE |
10 | MFA enabled |
MFA_DISABLE |
11 | MFA disabled |
OTHER |
99 | Other account changes |
API Activity Types (OCSF_API_ACTIVITY):
| Activity | ID | Use Case |
|---|---|---|
CREATE |
1 | Create operations |
READ |
2 | Read operations |
UPDATE |
3 | Update operations |
DELETE |
4 | Delete operations |
CHECKOUT |
90 | Order checkout (e-commerce extension) |
PAYMENT |
91 | Payment processing (e-commerce extension) |
REFUND |
92 | Refund processing (e-commerce extension) |
EXPORT |
93 | Data export (GDPR extension) |
IMPORT |
94 | Data import (extension) |
ACCESS_DENIED |
95 | Authorization failure (extension) |
OTHER |
99 | Other API activities |
Note: IDs 90-95 are Unchained-specific extensions. Standard OCSF defines activity_id 1-4 and 99 for API Activity. SIEM systems may display these as "Other" or "Unknown" unless configured to recognize the extended values.
| Level | ID | Use Case |
|---|---|---|
| INFORMATIONAL | 1 | Normal operations (default for success) |
| LOW | 2 | Minor issues |
| MEDIUM | 3 | Standard operations |
| HIGH | 4 | Security-relevant events (default for failures) |
| CRITICAL | 5 | Critical security events |
| FATAL | 6 | System failures |
Audit log files (audit-YYYY-MM-DD.jsonl) can be directly ingested by SIEM systems:
Filebeat (Elastic):
filebeat.inputs:
- type: log
paths:
- /path/to/audit-logs/*.jsonl
json.keys_under_root: true
json.add_error_key: truePromtail (Loki/Grafana):
scrape_configs:
- job_name: unchained-audit
static_configs:
- targets: [localhost]
labels:
job: audit
__path__: /path/to/audit-logs/*.jsonl
pipeline_stages:
- json:
expressions:
class_uid: class_uid
activity_id: activity_id
user_id: user.uidOpenTelemetry Collector (HTTP push):
receivers:
otlphttp:
endpoint: 0.0.0.0:4318
exporters:
elasticsearch:
endpoints: ["https://es:9200"]The audit log is configured programmatically. Example using environment variables:
import { createAuditLog } from '@unchainedshop/events';
const auditLog = createAuditLog({
directory: process.env.UNCHAINED_AUDIT_DIR || './audit-logs',
collectorUrl: process.env.UNCHAINED_AUDIT_COLLECTOR_URL,
});In addition to persistent audit logs, Unchained emits transient events for real-time processing:
USER_CREATE,USER_UPDATE,USER_REMOVEUSER_UPDATE_PASSWORD,USER_UPDATE_ROLESUSER_ACCOUNT_ACTION(reset-password, verify-email, enroll-account)
import { emit } from '@unchainedshop/events';
// Transient events (2-day TTL in MongoDB)
await emit('USER_UPDATE_PASSWORD', { user });Rate limiting should be implemented at the reverse proxy level (nginx, Cloudflare, AWS ALB, etc.) rather than in the application layer.
nginx example:
# Define rate limit zones
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
limit_req_zone $binary_remote_addr zone=api:10m rate=100r/s;
server {
# Rate limit login/auth endpoints
location /graphql {
# Stricter limits for mutations (detected via POST)
limit_req zone=api burst=50 nodelay;
proxy_pass http://unchained:4000;
}
}Cloudflare:
- Use Rate Limiting Rules for
/graphqlendpoint - Configure Bot Fight Mode
- Enable Under Attack Mode for emergencies
AWS ALB:
- Configure WAF rate-based rules
- Set thresholds per IP address
| Endpoint | Recommended Limit | Rationale |
|---|---|---|
| Login mutations | 5/minute per IP | Prevent brute force |
| Password reset | 3/hour per IP | Prevent enumeration |
| Registration | 10/hour per IP | Prevent spam |
| GraphQL queries | 100/second per IP | General protection |
| Bulk import | 1/minute per IP | Resource intensive |
- Set
UNCHAINED_TOKEN_SECRETto a strong, unique value (32+ chars) - Enable HTTPS/TLS termination
- Configure MongoDB with authentication and TLS
- Enable session encryption if storing sensitive data
- Configure rate limiting at reverse proxy (nginx, Cloudflare, ALB)
- Enable audit logging via
modules.auditLog(built-in, persisted indefinitely) - Configure monitoring and alerting
- Set up log aggregation for audit logs
- Regular security updates for dependencies
# Required
UNCHAINED_TOKEN_SECRET=<strong-random-value-32+chars>
NODE_ENV=production
# Recommended
UNCHAINED_COOKIE_SAMESITE=strict
UNCHAINED_COOKIE_DOMAIN=.yourdomain.com
# For FIPS mode
OPENSSL_CONF=/etc/ssl/openssl-fips.cnf- TLS 1.2+: Enforce modern TLS versions
- HSTS: Enable HTTP Strict Transport Security
- CSP: Configure Content Security Policy for admin UI
- CORS: Restrict allowed origins
# Connection with authentication and TLS
MONGO_URL=mongodb://user:pass@host:27017/unchained?tls=true&authSource=adminRun regular dependency audits:
npm audit
npm audit fixThe project explicitly trusts only necessary native modules:
{
"trustedDependencies": ["@mongodb-js/zstd"]
}