Unified security documentation: OWASP compliance, implementation review, and vulnerability history.
| Standard | Status | Notes |
|---|---|---|
| OWASP Top 10 2025 | ✅ 10/10 | All categories addressed |
| OWASP API Security Top 10 | ✅ 10/10 | Including DoS prevention |
| OWASP GenAI Top 10 | ✅ 10/10 | No new AI attack surface |
- RBAC: Testing Objective and Testing Method require Assessor role.
- Enforcement: Frontend disables fields; backend validates with
authenticate,authorize,requireRole. - Auth: Session-based + Bearer token; role checked on every request.
- HTTPS in production; secure cookies:
httpOnly,sameSite: 'strict',securein production. - No sensitive data in cookies; session timeout configured.
- EC2 (production): Sensitive platform settings (Slack webhook URL, AI tokens, SSO client secrets) use
OSCAL_SECRETS_MODE=aws-smand a single AWS Secrets Manager JSON bundle;config.jsonholds{ "_sm": "OSCAL/..." }pointers only. Saves fail if SM is unavailable (no plaintext fallback). Startup auto-migrates any plaintext to SM. Seebackend/utils/secretsManager.jsandbackend/utils/configSecretMigration.js. - Local / Docker / offline: Secrets are stored as
{ "_cfgenc": "v1$..." }envelopes inconfig.jsonusing PBKDF2-SHA256 + AES-256-GCM (backend/utils/configFieldCrypto.js). SetOSCAL_CONFIG_FIELD_SECRETorSESSION_SECRET. Docker entrypoint generates and persists keys under/data/.field-secretand/data/.session-secret. Pass vault is not required. - Optional operator tooling: The laptop pass vault is optional; set
OSCAL_PASS_DISABLED=1in Docker (default in compose). - Never commit plaintext secrets; use
config.json.examplepointers or_cfgencplaceholders only.
- Current storage format: New and rotated passwords are stored as PBKDF2-SHA256 with random salt and 100,000 iterations (
pbkdf2$sha256$...prefix). Implementation:backend/auth/userManager.js(hashPassword/verifyPassword). - Legacy format: Older rows may still hold a single-round SHA-256 hex of the password (64 hex characters, no
pbkdf2$prefix). Verifying those rows requires the same digest algorithm until each user successfully signs in once with the correct password. - Automatic upgrade: On successful password authentication, if the stored value is legacy format,
migratePasswordToPBKDF2rewrites the stored hash to PBKDF2. No user action beyond normal login. - Removal gate: When no users remain on legacy format in all environments you operate (see Platform Admin GET
/api/users/legacy-password-hash-stats), the legacy verification branch inverifyPasswordmay be deleted; until then, removing it would lock out any account still on legacy hashes. - Static analysis (CodeQL
js/insufficient-password-hash): Tools flag anycreateHash('sha256')used with password material. Here that call exists only for backward-compatible verification of already stored legacy digests, not for new storage. Governance: document this transitional behavior; use GitHub “won’t fix” / dismissal with the same narrative if the alert persists until the legacy branch is removed after migration completes.
- Sanitization: User input sanitized for OSCAL; no raw user input in queries or commands.
- No SQL: Data in JSON files and client storage; no command execution with user input.
- Output encoding for context (HTML, OSCAL).
- Secure defaults; optional fields; separation of SAR vs SSP.
- No stack traces to users; generic error messages in production.
- Settings API (VULN-36998):
GET /api/settingsrequires authentication and Platform Admin (EDIT_SETTINGS); returns full config with secret masking only.GET /api/settings/runtimerequires authentication and returns an allowlisted subset (databaseConfig.enabled,publishedSoaUrl) for non-admin app features. Unauthenticated requests receive 401; non-admin full-settings requests receive 403. POST/api/settingsresponses omit server filesystem paths and redact secrets in returnedconfig.
- Security logging: Auth success/failure, access denied, validation failures.
- Structured logs with OpenTelemetry-style attributes; no secrets in logs.
- Error handling: Specific exceptions; generic user-facing messages.
- Request limits: Export payloads use configurable caps (default 10,000 controls, 512KB metadata); set
OSCAL_EXPORT_MAX_CONTROLSandOSCAL_EXPORT_MAX_METADATA_BYTESfor large catalogs. Same checks apply to CCM/Excel/PDF and async job routes. - Rate limiting: 100 requests per 15 minutes per IP.
- Timeout: 240-second server timeout.
- Token-based CSRF for state-changing browser requests.
- API endpoints use Bearer token (CSRF not applicable); architecture documented for scanner suppression.
- Issue: fast-xml-parser DoS (CVE-2026-25128); transitive via
@aws-sdk/xml-builder. - Scope: AWS Bedrock integration only (optional).
- Fix:
fast-xml-parser5.2.5 → 5.3.4;@aws-sdk/xml-builderupdated.npm audit: 0 vulnerabilities. - Action: If using AWS Bedrock, run integration tests after upgrade.
- Issue: CSRF middleware caused 403 on many API endpoints.
- Fix: API routes under
/api/exempted; Bearer token used for API (CSRF not applicable). State-changing browser flows still use CSRF tokens.
- Issue:
npm auditreported 41 backend findings (11 high), including a legacynodemon1.x dependency chain, and 2 high in the frontend and 1 high plus 1 moderate at the root (vite,follow-redirects,body-parser,qs). The Docker image and direct-run EC2 bootstrap used Node.js 20, which is end-of-life. - Fix: Dependencies updated to the latest releases;
nodemonremoved (development now usesnode --watch); Node.js 24 in the Docker image, Terraform user data and deploy script. Result: backend 5 low (thedeepmergedependency of the Jest test runner, no upstream fix), frontend 0, root 0; Trivy lockfile scan 0; source secret scan 0. - Guardian access controls: Guardian assessment retrieval is off by default. When enabled, IdP groups and tokens stay server-side; the Generic SSO access token is verified (issuer, signature, audience, expiry, custom scope, group) before use; requests go only to owner-approved exact hostnames over HTTPS with no redirects; privileged use needs an explicit confirmation and is audit-logged; any missing setting fails closed. See GUARDIAN_ASSESSMENTS.md.
- Not verified: real Okta/Authentik authentication and live Guardian endpoints. The Terraform scan's 29 Checkov and 24 Trivy hardening findings are not remediated in this release.
- Post-deploy: rebuild the image, run
npm ci, and sign in again; configure Guardian only after the owner prerequisites in GUARDIAN_DEPLOY_INPUTS.md are met.
- Issue: Unauthenticated async job creation (
optionalAuth); job status/download accessible by UUID without ownership check; requester IP in status JSON; job flooding DoS. - Fix:
authenticateon all job routes;canAccessJobowner-or-Platform-Admin checks;sanitizeJobForClient; per-user rate limit and concurrent job cap. See CHANGELOG.md. - Post-deploy: Pentest retest steps 1–4; Jira → Remediated – Pending Retest.
- Issue: Unauthenticated
GET /api/settingsreturned full platform configuration (DB, SSO, group mappings, Bedrock ARNs, legacy SMTP). CWE-200; CVSS 8.2 on production pentest. - Fix: Platform Admin required for full settings;
GET /api/settings/runtimefor allowlisted runtime flags; POST save responses redacted; SMTP/email and self-registration removed. See CHANGELOG.md. - Post-deploy: Rotate exposed credentials; review OAuth redirect URIs in Okta Admin Console.
- Issue: Unauthenticated
/api/proxy-fetchand/api/fetch-catalogueallowed SSRF (hex IP bypass, redirect to IMDS). - Fix: Session auth required;
strictUserFetch/strictCatalogueFetchprofiles; no redirects on protected axios calls; encoded-IP rejection. See CHANGELOG.md.
- Issue: Stolen instance creds could assume cross-account Bedrock role; settings API leaked role ARN to non-admins.
- Fix: Settings redaction; Terraform requires
bedrock_external_idwhen cross-account enabled; Account B runbook for logging and trust policy. See CROSS_ACCOUNT_BEDROCK_PHASE1.md.
- SSAAU-216: Stale Image Factory EMR AMI → dynamic lookup + ASG refresh to IF 3.0.2.
- SSAAU-212: Splunk UF installed but missing
deploymentclient.conf→ automated bootstrap in Terraform user-data/SSM. See terraform/envs/aws4403/README.md. - SSAAU-212, reopened (September 2026): logs still weren't reaching Security Splunk SCC after the above fix. Live diagnosis on Green/Blue found the deployment-client config, journald input stanzas, and meta fields all already correct — the actual failure was repeated
errno 104connection resets on Splunk UF's mutual-TLS output path to the indexer tier (hf3.splunk.adobe.net), caused by the VPC/subnets never having been tagged for Adobe's Emissary network allowlist. Fixed by taggingaws_vpc.main/aws_subnet.publicwithemissary = "trusted"(terraform/vpc.tf); also added a defensiveetc/system/local/deploymentclient.confoverride andsplunk btoolcheck (config-precedence hygiene, not the root cause), and fixed a bootstrap-script bug that would have overwritten Image Factory's already-correct00-secops_meta_appmeta fields. See the 1.7.30 and 1.7.25 entries and Appendix A (reusable runbook) in CHANGELOG.md, andscripts/debug/diagnose-splunk-uf-on-ec2.sh.
- OWASP Top 10 2025
- OWASP API Security Top 10
- OWASP GenAI Top 10
- AI integration – architecture and security
Version: 1.9.0 · Last updated: July 2026