Vulcan v2.3.1
Release Date: February 2026
This release includes DISA process documentation, SRG ID display in satisfaction relationships, DRY refactors (UnifiedRuleForm, satisfaction text), export system improvements, authorization safety net, and infrastructure hardening.
Upgrades
- Ruby 3.4.9 - Upgraded from 3.3.9 (latest patch release with security fixes)
- Puma 7.2.0 - Upgraded from 5.6.9 for Heroku Router 2.0 compatibility and performance
- parallel_tests - Added for faster local test execution
- Vitest CI integration - Frontend tests (1114 tests) now run in GitHub Actions
- Rails 8.0.4 - Patch update with security and compatibility fixes
- axios 1.13 - Dependency update
- sass 1.97 - Dependency update
Security
- Updated rexml and rack gems (CVE-2025-58767, GHSA-625h)
- Resolved all ESLint errors and warnings (20 errors, 6 warnings to 0)
- Configurable SSL for Docker deployments (#700, #702)
- YAML.safe_load replaces YAML.load_file in SearchAbbreviationService (deserialization safety)
- Database deployment safety: removed dangerous
DISABLE_DATABASE_ENVIRONMENT_CHECKfrom Docker entrypoint; flag now only used in Heroku review app postdeploy where it's needed fordb:schema:load - XXE prevention: removed NOENT flag from XML parser, added NONET to block external entity expansion and network DTD fetching
- Upload validation: file size limits (50 MB XML, 100 MB ZIP, 50 MB spreadsheet) and content-type checks on all upload endpoints
- Rate limiting via rack-attack: login throttling (5/min/IP, 5/min/email), upload throttling (10/min/IP)
- Configurable input length limits on all text fields across 9 models (18
VULCAN_LIMIT_*env vars) - Content Security Policy headers (script-src, style-src, frame-src, etc.)
- HappyMapper NONET patch prevents SSRF via external DTD URIs in XCCDF parsing
- Rack-attack flaky test isolation (fresh cache per test, unique IPs/emails)
- Authorization scoping:
based_on_same_srgnow scoped to user-accessible projects and released STIGs only - Nil guard on compare action for invalid component IDs (prevents unhandled exceptions)
params[:sections]type safety: Array coercion in rules and reviews controllersproject_visibilityenum allowlist validation to reject unexpected values- HappyMapper XXE patch extended to handle IO and StringIO inputs, not just file paths
- JSON::ParserError rescue on
component_filterparam (malformed JSON no longer raises 500) - Password generation characters shuffled (eliminates predictable character-class ordering)
- Locked users query capped at 100 results (prevents unbounded admin queries)
Features
Security and Access Control
- Account lockout (Devise
:lockable) with admin unlock UI, AC-07 compliance - Configurable concurrent session limits (AC-10) with session history tracking via
devise-securityfork - PBKDF2-SHA512 password hashing (FIPS 140-2 compliant) with transparent bcrypt migration
- Devise hardening: paranoid mode, DELETE logout (CSRF protection), cookie security (secure + httponly + SameSite), email/password change notifications, 2-hour password reset window
- Classification banner and consent modal
- Per-section rule field locking (lock individual rule sections independently)
- Password complexity policy (configurable DoD 2222 defaults)
- Last-admin protection (prevents demoting/deleting only admin)
- Admin user management (create, edit, delete, lock/unlock accounts)
- AC-8 server-side consent tracking with configurable TTL (
VULCAN_CONSENT_TTL) - Consent modal renders before login (AC-8 compliant: acknowledgement before authentication)
API and Version Infrastructure
- GET
/api/versionendpoint (public, returns version, Rails version, Ruby version, environment) lib/vulcan/version.rbas single source of truth for application version- Health check endpoint includes version in response
- Navbar receives version from server rather than reading from
package.json rake version:synckeepsVERSIONfile in sync withpackage.json
Infrastructure
- Unified multi-stage Dockerfile with CLI and improved .dockerignore
- Admin bootstrap with first-user-admin and env var support
- Health check endpoints for Kubernetes and Docker deployments
- DB_SUFFIX environment variable for worktree database isolation
- Database deployment safety:
db:prepare(Docker),db:migrate(Heroku prod/staging),db:schema:load(review apps) with 13 regression tests - CSP dynamically whitelists OIDC provider domain and
api.github.combased on configuration - Auto
parallel:prepareruns afterdb:migrateanddb:reset(no manual step needed) rake spec:parallelandbin/parallel_rspecwrapper with 8-processor cap.foremansetsport: 3000for consistent Okta/OIDC redirect URI- Tag-triggered releases using git-cliff to generate changelogs in Keep a Changelog format
- release-please replaced with simpler tag-based release workflow
Search
- Global search infrastructure with pg_search gem
- Query transformation service with abbreviation expansion
- Search results across projects, components, rules, SRGs, and STIGs
UI Improvements
- Command bars for view and edit pages
- Redesigned MembersModal with tabbed interface
- Redesigned RuleActionsToolbar with two-row layout
- Severity filter buttons as connected button group with CAT I/II/III labels
- UnifiedRuleForm replacing Basic and Advanced forms
- CSV export with configurable column picker
- NYD (Not Yet Determined) tooltip guidance on disabled fields
- NYD section locking disabled (locking is meaningless before a determination is made)
Data Handling
- Postel's Law applied to satisfaction parsing and session timeout config
- CSV header aliases for import compatibility
- SRG auto-detect from spreadsheet import
VULCAN_SEED_DEMO_DATAguard prevents demo seeding in production
Fixes
- Fix nested attributes not saving in rules controller (#692)
- Fix Also Satisfies resetting parent rule status
- Fix Vue reactivity for satisfied_by relationship field visibility
- Fix SRG search result links to use /srgs/ route
- Database config with DATABASE_URL support
- Consolidate Devise modules into single call in User model
- RuleList severity counts moved to computed property (now reactive to prop changes)
- ComponentCard: added
resetDelete()method for error recovery after failed delete - MembersModal: scoped element IDs to prevent DOM collisions, replaced
form.submitwith axios for all operations - App.vue
signOut: replacedform.submitwith axios using FormMixin CSRF token handling - ProjectComponent: replaced
toRef(plainObject)withcomputed()for correct Vue 2.7 reactivity - RuleList
initialSelectedRule: markedrequired: falseand added watcher for prop sync - UpdateFromSpreadsheetModal: memoized
diffWordsLCS computation (performance fix for large diffs) - GlobalSearch:
encodeURIComponentapplied to URL params, removed dead code paths - ConsentModal: migrated to FormMixin for consistent CSRF handling
- Idempotent seeds: fixed missing Component titles that caused re-seed failures
- Remove explicit
secure:cookie flag — Rails SSL middleware handles it automatically (prevented dev/test breakage) - Replace thread-unsafe
@@components_to_exportclass variable with session storage (fixes Puma race condition) - Fix config ENV var parsing, connection pool mismatch, and dead configuration keys
- Remove redundant ENV fallbacks and UserMailer default-from override
- Fix consent modal crash on first render before consent state is loaded
Infrastructure
- Vitest testing infrastructure for Vue 2 components
- Centralized terminology constants (BENCHMARK_TERM, EXPORT_FORMATS)
- Comprehensive import/export and deployment documentation
- Parallel test execution in CI with parallel_tests gem
frozen_string_literal: trueadded to all 65 migration files- Config verification specs and orphaned settings table cleanup
- Security regression specs for authorization, input validation, and XXE fixes
- Deployment blocker regression specs (13 tests covering db:prepare, db:migrate, db:schema:load paths)
Changes Since v2.2.1
- 362 commits
- 762 files changed
Version: v2.3.1 Type: Feature Release
