Appearance
Changelog
All notable changes to Vulcan will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
[v2.4.1] - 2026-08-26
Added
- Three-column triage split-pane — triagers see a persistent rule sidebar (col-2), rule content (col-5), and comment + triage form (col-5) side by side, replacing the prev/next nav + modal workflow. Sidebar shows rules grouped with pending/total counts, search filter, and keyboard navigation. Reuses BenchmarkViewer layout pattern. (PR #746)
- Triage progress bar — summary pills with clickable status filter + thin stacked bar above the comments table. Shows per-status counts (All, Pending, Accepted, Declined, etc.) with "N of M resolved (X%)" summary. Click a pill to filter; click again to reset. Works on both component and project triage pages. (PR #746)
- DRY triage color palette — centralized CSS custom properties in
triage-tints.cssas single source of truth. Colors: green (Accepted), blue (Accepted with Changes — ISO 3864 mandatory-action), red (Declined), yellow (Informational), grey (Pending), purple (Withdrawn — GitHub pattern), teal (Duplicate — Linear pattern). (PR #746) - ARIA landmarks + focus management — split-pane uses
<nav>,role="main",role="complementary"landmarks. Skip links for keyboard users. Focus lands on content heading on entry and after Save & Next (WAI-ARIA APG content-first pattern). Sidebar is a composite widget (single Tab stop, arrow keys inside). (PR #746) - InfoTooltip + InfoNotice shared components — consistent (i) icon tooltip pattern (15 instances migrated) and inline info notice pattern (2 instances) across the app. (PR #746)
- 2D queue navigation — skip-start/end icons for rule-level nav, chevrons for comment-level, Browse popover panel with search + keyboard nav. (PR #746)
- By-rule accordion view — collapsible groups by rule with pending/total counts, Expand All toggle, auto-expand on filter change. (PR #746)
- Staleness badge — "Section updated since this comment" when rule content changed after comment was posted. (PR #746)
- Admin actions inline — dropdown on triage form button row (replaces sidebar). Force-withdraw, restore, move-to-rule, hard-delete with typed-ID confirmation. (PR #746)
- Status color dots on the requirement-status filter toggles — each Status filter switch now renders the same single-source
.status-dotused by sidebar rows and badges, giving the filter an at-a-glance color key. Dots are decorative for screen readers (the label carries the status) and every dot color meets 3:1 contrast in light and dark mode. (PR #746) - Documentation build as a PR gate — the VitePress build (with its dead-link check) now runs on pull requests that touch docs, reproducing the deploy build exactly instead of surfacing dead links at the Heroku deploy preview. In development, visiting
/docsbefore the site is built now answers with build instructions instead of a bare "Not found". (PR #746)
Changed
- Show Resolved toggle removed — replaced by clickable progress bar pills that serve as both status indicators and filter controls. "Pending" pill replaces the default filter; "All" pill shows everything. Eliminates the state model conflict between toggle and pill filters. (PR #746)
- Renamed "Triage Queue" to "Comments" (heading, breadcrumbs, aria labels). (PR #746)
- Commented/All toggle moved to rule ID row as simple switch. (PR #746)
- Locked fields now allow comments (lock prevents editing, not commenting). (PR #746)
- Backup format version bumped to 1.1 — exports now declare
backup_format_version: "1.1"(was 1.0). The format itself is a forward-compatible bump: reviewcreated_at/updated_atcarry microsecond precision (already emitted asiso8601(6)), andManifestValidatoraccepts both1.0and1.1. The matcher'slegacy_format?branch (second-precision normalization) now triggers only on1.0, so two reviews <1s apart with identical rule_id and comment no longer collapse intopair_degenerateon fresh exports. Old 1.0 archives are still importable. (PR #746) - Minted requirement identifiers are tracked by a persisted sequence — recognizing a minted (authored-derivation) requirement no longer parses its identifier string, which could not distinguish a raw five-segment DISA container core from a minted one and broke the never-renumber invariant on re-release.
mint!now stamps aminted_sequencecolumn, recognition reads it, and the next-sequence high-water mark derives from it — immune to SRG rebases and abbreviation changes. Schema migration20260817120000+ per-row backfill20260817120100; production deploys requiredb:migrate. (PR #746)
Fixed
- SRG viewer sidebar highlighted all rules instead of first selected —
SrgRuleBlueprintwas missingidentifier :id. (PR #746) - Split-pane exits when filter changes — watcher now selects first available comment instead of exiting when the active comment is filtered out. (PR #746)
doSaveonly adjudicates on "Save & next", not "Save decision". (PR #746)- Factory traits
after(:build)DB writes fixed tobefore(:create). (PR #746) - Triage queue loads every comment past the server page cap — the split-view/by-rule queue silently dropped comments beyond the server's 100-per-page limit (a 150-comment component showed only 100, and the tail could not be selected or triaged); it now pages until the full total is loaded. (PR #746)
- Triage progress bar updates live on in-place triage/adjudicate — the bar's counts previously froze until the next full reload, making an adjudication look like a no-op until a filter switch. (PR #746)
- Archive import preserves review edit timestamps — imported reviews hardcoded
updated_attocreated_at, discarding the edit time the backup carries. (PR #746) - Comment moves carry the entire reply subtree — replies-to-replies at any depth now survive a comment's transition between containers; previously only direct replies were carried, silently dropping deeper descendants. (PR #746)
- Catalog and review N+1 queries removed — the SRG catalog index computed latest-release currency with several queries per row (now one query for the whole catalog), and the cached component-reviews path re-queried user/response associations per review (now batch-preloaded). (PR #746)
- Reaction toggles on comment replies render immediately (Vue reactivity: index assignment replaced with
splice). (PR #746) - Export modal's select-all no longer includes the disabled SRG component rows in the export payload. (PR #746)
- Comment composer fully resets on close — reopening after a manual close no longer shows a stale success alert or auto-closes from an orphaned timer. (PR #746)
- Rule search cancels its in-flight debounce on clear, so a trailing keystroke no longer re-applies a cleared search; the timer is per-instance. (PR #746)
- Find & Replace surfaces search failures instead of leaving the spinner stuck with buttons disabled. (PR #746)
- Copying a personal access token works in non-HTTPS deployments (clipboard API fallback + failure toast). (PR #746)
- Admin token-revoke justification is captured in a modal instead of
window.prompt, which is blockable in sandboxed contexts and silent on cancel. (PR #746) - Navbar global search input has an accessible name for screen readers. (PR #746)
- Consent "I Agree" no longer looks like a dead button — a bodyless success (Rails
head :ok) made the API client throw while parsing the empty body, so the consent modal stayed open even though consent had been recorded server-side; the client now resolves empty (204 /head :ok) responses instead of failing on them, and a genuine failure surfaces a visible error. (PR #766) - Component-creation progress toast is kind-neutral — creating a STIG no longer shows an "SRG" reference in the progress toast. (PR #766, #767)
- Source-SRG picker is themed while disabled — before a document kind is chosen, the multi-select source picker matches the dark theme instead of rendering as an unstyled light-gray box. (PR #767)
- SRG-authoring migration is release-phase-safe —
AddSrgAuthoringColumnsbuilds its index concurrently and adds its foreign key and check constraint unvalidated-then-validated, so adb:migrateon a populated database no longer aborts under strong_migrations or blocks writes.
Security
- Login lockout and rate limiting enforced on the JSON login path —
/api/auth/loginpreviously authenticated with a bare password check, bypassing Devise lockout (failed attempts were never counted and locked accounts could still sign in — STIG AC-07) and all login throttling. It now authenticates through the full Devise checks, returns a distinctaccount_locked401, and both login paths share generalized IP- and email-keyed throttles. (PR #746) - Local-login toggle enforced server-side — disabling local login previously only hid the form; a crafted POST to either login path could still sign in with local credentials on an SSO-only instance. A shared guard now rejects both paths server-side. (PR #746)
[v2.3.7] - 2026-05-10
Added
- Component-level comments —
Reviewrecords can now target aComponentdirectly (in addition to the existingRuletarget), enabling overall-component feedback that doesn't fit a single requirement. NewPOST /components/:id/reviewsendpoint with toolbar + comment-period-banner entry points and dedup-banner support at component scope. The triage queue, project-aggregate counters, My Comments, and the DISA disposition CSV all surface component-scoped comments alongside rule-scoped ones (Section column readsOverall Component; Rule column reads(component)). The composer's Section dropdown adds an "Overall Component" option that flips the modal to component scope. Schema migration20260508210000_add_polymorphic_commentable_to_reviews.rb— production deploys requiredb:migrate. (Closes #725) - Project-aggregate disposition CSV export — operators can now export ALL comments across ALL components in a project as a single CSV via the existing Export CSV button when viewing project-level comments, instead of one component at a time. Mirrors the per-component CSV with a leading "Component" column for disambiguation. New
POST /projects/:id/export?type=disposition_csvendpoint with author-tier PII gate (admin-tier+ may opt intoinclude_email=true); audit row recorded against the Project on export. (Closes #724)
Changed
- Replies are now accepted on active comment threads after the public-comment period has officially closed, as long as the parent rule's component is in any triaging-active phase (open OR closed+adjudicating). New top-level comments still require
accepting_new_comments?. Closed+finalized still blocks all new content. Resolves the friction where commenters' replies to "needs_clarification" round-trips were getting rejected when the deadline passed mid-conversation. Defensive checks on the smuggledresponding_to_review_id: parent must exist, parent must be a comment-action Review (not state-changing), parent's component must match the rule's component. (Closes #726) - DISA disposition CSV separator characters are now ASCII (
|,-) instead of Unicode (·,–) so macOS Numbers and Excel decode the file correctly without a UTF-8 BOM. (general)section label renamed toOverall Requirementeverywhere (rule composer, triage modal, CSV exports, vocabulary file) for parallelism with the new "Overall Component" option.- Rule toolbar's "Leave a Comment" button is now labelled "Comment" to match the parallel "Comment History" panel button and the new component-level "Comment" entry point.
- Documentation site (VitePress) top navigation consolidated from 11 items to 7 to fix overflow at moderate browser zoom on smaller laptop displays. Aligns visual treatment with the upcoming SAF site refresh; fixes the "latest version" reference that was stuck at v2.3.1.
[v2.3.6] - 2026-05-08
Changed
- BREAKING (Docker): Base image changed from
ruby:3.4.9-slim(Debian) toregistry.access.redhat.com/ubi9/ubi-minimal:9.7(Red Hat UBI 9). Ruby is now compiled from source in the build stage. This aligns the container with Iron Bank / DISA compliance requirements. jemalloc is compiled from source and re-enabled viaLD_PRELOAD. - BREAKING (Docker Compose): PostgreSQL 18 requires the volume mount at
/var/lib/postgresql(was/var/lib/postgresql/data). Existing deployments must migrate data before upgrading:docker compose down, then either (a) back up and restore the database, or (b) rename the volume contents to match the new layout. Runningdocker compose upwith an oldvulcan_dbdatavolume on the new mount path will initialize a fresh empty database. - Project viewers can now post comments on rules. Previously the
viewerrole was strictly read-only; it now grants read + comment access. Save / Approve / Request Changes / Lock / Unlock remain restricted to higher roles. To restrict commenting you must remove the user's project membership. - Authorization rejection responses for JSON requests now return a structured
403 Forbiddenbody ({ error: 'permission_denied', message, admins: [...], toast }) instead of500 Internal Server Error. The legacytoastshape is kept alongside so existing AlertMixin consumers keep working unchanged.
Added
- Comment reactions (👍/👎) on rule comments and replies. Reactions render as counts on each comment in the rule editor pullout, the comment thread (reply rows), and the triage modal; click the people-icon to see reactor names (works on hover, focus, and tap — accessible to keyboard and touch). Reactions are merged into the parent comment's
Thread Repliescell in the disposition-matrix CSV export (alongside text replies, in chronological order) as[name · timestamp] reacted thumbs-upentries. Audited viavulcan_auditedso the toggle history is preserved. - Rate limits on reaction endpoints: 60 toggles/min/user (POST) and 300 hover-fetches/min/user (GET) via Rack::Attack, with IP fallback for unauthenticated traffic.
Review::VALID_ACTIONSallowlist + inclusion validator onReview#actionso unknown action strings no longer save silently as state-mutating no-ops.- AlertMixin now renders structured permission-denied responses as a "Permission denied" toast that lists the project administrators (name and email) the user should contact for access — no more silent or generic failures on rejected actions.
Fixed
rescue_fromordering bug inApplicationController—NotAuthorizedErrorwas being shadowed by the catch-allStandardErrorrescue (ActiveSupport::Rescuablematches handlers viareverse_each, so the LAST-declared rescue wins). The dedicatednot_authorizedhandler was effectively dead code in any non-development environment for JSON requests, surfacing every unauthorized action as a 500 instead of the proper 401/403. Reordered so the specific rescue wins.
[v2.3.5] - 2026-04-11
Added
- Server-side user search endpoint
GET /api/users/searchwith admin authorization scope=membersparameter for searching within existing project/component members (PoC selection — accessible to any member)Project#search_available_membersandProject#search_members(combine exclusion + ILIKE search)Component#search_available_membersandComponent#search_members(mirrorsall_userssemantics for inherited + direct members)- Async server-side user search via
vue-multiselectinNewMembership,MembersModal, andUpdateComponentDetailsModal(debounced 300ms, min 2 characters) - Contract tests asserting
/components/:id.jsoneditor refresh response shape matchesComponentBlueprint :editorexactly - Regression guards in
ComponentBlueprintandrules_specassertingavailable_membersandall_usersare not present in serialized payloads (information disclosure regression guard) - Dedicated
release.ymlworkflow triggered only on release published events
Changed
available_membersandall_usersremoved fromComponentBlueprintandProjectBlueprintpayloads (no longer pre-loaded into the DOM)MembershipsTablederives pending access request user info fromaccess_requestsdirectly instead of cross-referencingavailable_membersComponentsController#showeditor JSON now rendersComponentBlueprint :editordirectly, eliminating a parallel jbuilder code path that produced a different shape than the initial rendershow.json.jbuildersimplified to non-member only (BenchmarkViewer's lightweight rule shape)- Docker release workflow split out of
ci.ymlso test suite no longer reruns on release publish - All GitHub Actions pinned to full commit SHAs for supply chain safety
Fixed
- Information disclosure: pre-loaded full user directory removed from project/component pages — admins could previously enumerate every registered user via the page payload
- Editor refresh shape drift:
refreshComponent()(called byUpdateComponentDetailsModal,UpdateMetadataModal,AddQuestionsModalafter save) replaced localcomponent.membershipswith name/email-less stripped versions, silently breakingMembersModaldisplay until full page reload MembershipsTable.getAccessRequestIdwas reading stalerequest.user_idand would crash Accept/Reject after the access_requests payload moved to nestedrequest.user.{id,name,email}shapeComponent#search_available_members/#search_memberswere missing entirely (controller dispatched to@target.search_*but onlyProjecthad them), causingNoMethodErrorfor anymembership_type=Componentrequestfirst_user_adminafter_create callback was silently promoting test users to site admin in new request specs, masking project-level authorization assertions- SBOM tag mismatch (
v2.3.4vs2.3.4) in release workflow
Security
- The
/api/users/searchendpoint enforces admin-only access for non-member searches and member-only access forscope=memberssearches, preventing unauthorized user enumeration
v2.3.4 - 2026-04-07
Added
- Blueprinter JSON serialization framework with 15 blueprint classes and context-specific views (:index, :show, :editor, :navigator, :viewer)
- blueprinter-activerecord auto-preloader for automatic N+1 prevention
- Oj fast JSON generator (~2x faster than stdlib)
- Rule and Review test factories
- 12 query performance regression tests
- Session auth method tracking (session[:auth_method]) — distinguishes "signed in via" from "account linked to"
- Unlink identity feature with password verification
- VULCAN_AUTO_LINK_USER global setting for automatic provider-to-local account linking
- Admin password management UI: always show all options regardless of SMTP configuration
Changed
- All controllers migrated from to_json(methods:[]) to Blueprint.render
- All model as_json overrides removed (BaseRule, Rule, Review, Membership)
- Project#details consolidated from 9 COUNT queries to 3 (GROUP BY)
- Project#available_members uses SQL WHERE NOT IN instead of Ruby set subtraction
- Project#available_components uses .select() for column filtering
- Component#reviews uses pluck(:id, :rule_id) instead of loading full rule objects
- Rule creation uses DB lookup instead of parsing multi-MB XML
- UsersController audit query bounded with .limit(200)
- ApplicationController check_access_request_notifications rewritten (N+1 → single query)
- Replaced gitlab_omniauth-ldap with omniauth-ldap 2.3.3 (removes nkf VM crash)
- Ruby 3.4.8 → 3.4.9
- Bumped version to v2.3.4
Fixed
- OIDC provider conflict: symbol/string comparison bug in User.from_omniauth
- Provider+uid-first lookup pattern (GitLab pattern) prevents provider hijacking
- rescue_from ordering: StandardError defined before ProviderConflictError
- Production /stigs crash (R14/R15 memory, H12 timeout) — SeverityCounts concern auto-excludes xml/binary columns
- VulcanAudit bitwise & → && fix for nil rule
- OmniAuth backtrace logging gated on development only — now logs in all environments
- email_verified OIDC claim hardened with ActiveModel::Type::Boolean.new.cast
- Polymorphic membership_type filter in access request notifications
- JSON.parse round-trip eliminated in component show jbuilder (render_as_hash)
- Slack notification firing on every user update instead of only admin changes
- Polymorphic audit query missing user_type filter
- PROJECT_MEMBER_ADMINS normalized from scalar string to array
- UsersTable typeColumn uses falsy check for undefined provider
- Exception message no longer leaked to client in rescue blocks
- update_columns used for password reset token to skip validations
- Visibility chain (stray public keyword) fixed in registrations controller
- valid_password? bcrypt→PBKDF2 rehash side-effect documented at unlink call site
v2.3.1 - 2026-03-03
Added
- Multi-stage Dockerfile with CLI integration and improved .dockerignore
- Admin bootstrap: first-user-admin on registration and env var (
VULCAN_ADMIN_EMAIL/VULCAN_ADMIN_PASSWORD) support - Health check endpoints for Kubernetes/Docker readiness probes
- DB_SUFFIX environment variable for worktree database isolation
- GET /api/version endpoint
- Tag-triggered release automation with git-cliff changelog generation
- Frontend tests added to CI pipeline
- Centralized version infrastructure and parallel test stability improvements
- Global search using pg_search: full-text search across rules, STIGs, SRGs, and SRG rules via unified API endpoint and frontend composable
- FilterBar and FilterGroup shared components with disabled state support and configurable display defaults
- EasyMDE markdown editor with custom Shiki syntax highlighting for rule content fields
- Centralized terminology constants for consistent UI text across components
- Unified rule form replacing separate Basic and Advanced forms, with IA Control/CCI display and severity override guidance
- RuleFormGroup shared component for DRY form field rendering
- Per-section rule locking: backend field-level editability abstraction and locking UI
- Rule panel buttons and actions toolbar with two-row layout redesign
- Auto-select first visible rule on page load
- Right sidebar panels converted to Bootstrap slideovers
- Command bars for rule view and edit pages with unified layout structure
- Project page standardized with command bar, sidepanels, and ComponentActionPicker for component creation
- Projects, Released Components, STIGs, SRGs, and Users list pages standardized with breadcrumbs and command bars
- User Profile page converted to Vue with breadcrumb navigation and comprehensive settings
- Redesigned MembersModal with tabbed interface
- Redesigned severity filter buttons as connected button group
- Unified BenchmarkViewer with composable navigation, SRG detail pages, sortable columns, severity badges, and keyboard navigation
- STIG/SRG XCCDF export with frontend integration
- CSV export with configurable column picker for STIGs and SRGs
- Export service with Registry, formatters (XCCDF, InSpec, Excel, CSV, JSON archive), and mode-first ExportModal with progressive disclosure
- VendorSubmission mode for DISA-compliant exports; PublishedStig and Backup export modes
- JSON archive backup export with full-fidelity serializer, membership backup/restore, and dry-run import support
- Restore from backup: component picker modal, per-component detail, POST /projects/create_from_backup endpoint
- Export pre-flight warning for components with all NYD (Not Yet Determined) rules
- Exclude-satisfied-by toggle for Excel/CSV exports
- Satisfaction data import/export via CSV column and VulnDiscussion parsing; Postel's Law applied to ingest (liberal) and export (canonical)
- CSV header aliases for backward-compatible import
- SRG auto-detection from spreadsheet import
- Spreadsheet update modal with word-diff preview
- Excel exporter switched from FastExcel to caxlsx with Source column and per-cell lock styling
- Configurable Remember Me with 8-hour default
- PasswordField component with show/hide toggle, replacing all password inputs
- Configurable password complexity policy (DoD 8500.2/2222 compliant)
- Admin user management UI: create, edit, password reset tools
- Account lockout (STIG AC-07): Devise lockable module, lock/unlock endpoints, navbar notifications, and audit trail
- Shared notification event bus for cross-component reactivity
- Authentication security hardening: PBKDF2 password hashing, session hardening, Devise audit logging
- Frontend form validation composable
- Classification banner and configurable consent modal (AC-8)
- AC-8 server-side consent tracking with configurable TTL (
VULCAN_CONSENT_TTL) - Input length limits (configurable via Settings), CSP headers, and detailed import error messages
- VULCAN_SEED_DEMO_DATA guard to prevent demo seeding in production
- Reusable delete confirmation system with JSON responses for axios compatibility
- TimeoutParser with Postel's Law for flexible session timeout configuration
Changed
- Upgraded Ruby from 3.3.9 to 3.4.9 and Puma to 7.2.0
- Upgraded Node.js to 24 LTS
- Upgraded PostgreSQL from 12/16 to 18 across Docker, CI, and documentation
- Replaced overcommit with lefthook for git hooks; added pre-push checks for RuboCop, ESLint, and Brakeman
- Added RuboCop plugins: rubocop-capybara, rubocop-factory_bot, rubocop-rspec_rails
- Applied SonarCloud-driven improvements across Ruby, Vue, and JavaScript files
- Replaced BasicRuleForm and AdvancedRuleForm with a single UnifiedRuleForm; removed dead severities prop chain
- Extracted shared ControlsCommandBar and ControlsSidepanels components; reorganized buttons by semantic group
- Renamed History to Activity in rule command bar; moved Members button to modal actions group
- Replaced RulesReadOnlyView with route-based views; updated RulesCodeEditorView to use composables
- Migrated all rule and component UI references to RULE_TERM terminology constants
- Removed old NewProject page system; replaced dead Stig components with RuleFormGroup
- Added Vitest infrastructure for Vue 2 component testing with coverage reporting
- Converted 36 spec files to
let_it_befor approximately 65% faster backend test suite - Added shoulda-matchers 7.0 and validation contract specs for all core models
- Added composite indexes for severity count queries and Jbuilder collection caching
- DRY'd seed data, model concerns, SRG ID serialization, satisfaction text, and notification dispatch
- Added request specs for components, rules, exports, and backup round-trip integration
- Added frontend test coverage for mixins, modals, utilities, banner, consent, lockout, and section locking
- Wired XCCDF and InSpec exports through a unified export service
- Pinned Devise to ~> 4.9 to prevent accidental upgrade to v5
- Increased CI backend shards from 4 to 6; added frozen_string_literal to all migration files
- Updated deployment documentation: Docker, database setup, env vars, port registry, and authorization
- Added backup/restore, data management, AC-8 consent, and security control documentation
- Optimized Heroku slug size with .slugignore and node_modules cleanup
- Bumped version to v2.3.1
Fixed
- Sanitize SQL LIKE input to prevent injection in search queries
- Enforce deny-by-default authorization on all controller actions; prevent provider hijacking on existing accounts
- Input security hardening: XXE prevention, upload validation, rate limiting
- Avoid cleartext password storage during bcrypt-to-PBKDF2 migration
- Replace thread-unsafe class variables in export controller with session storage
- Remove dangerous DISABLE_DATABASE_ENVIRONMENT_CHECK from Docker entrypoint
- Remove explicit secure cookie flag; let Rails SSL middleware set it automatically
- Add Devise Lockable migration for existing deployments
- Consent modal now shown before login, not after (AC-8 compliance)
- Resolve nested attributes not saving in rules controller (#692)
- Also Satisfies no longer resets parent rule status; show disabled buttons in read-only mode
- Display SRG IDs in satisfaction relationships and all rule views; enable paste/type input
- Derive srg_id from association in non-member component view
- Sort rules by rule_id and version before auto-selecting first visible rule
- Show New Project button for non-admin users with create permission; show delete button for project admins
- Correct SRG search result links to use /srgs/ route
- Respect component_ids selection for XCCDF and InSpec exports
- Add null guards for missing SRG data and name/email in search filters
- Fix v-b-tooltip directive pattern app-wide
- Use CAT I/II/III labels, fix text contrast for severity badges
- Replace table with div for accessible listbox in RuleList
- Fix body padding offset for fixed classification banner
- Correct file picker accept attribute for component import
- Enable Remember Me checkbox for OmniAuth/LDAP logins
- Docker build fix, configurable SSL for Docker deployments (#700, #702, #703)
- Database config with DATABASE_URL support; DRY database.yml defaults
- CSP configuration for OIDC provider and Vue 2 unsafe-eval
- Use CONCURRENTLY for GIN and composite index migrations to avoid table locks
- Make seeds idempotent using find_or_create_by!
- Resolve ESLint and RuboCop linting issues
- Resolve SonarCloud reliability bugs, security hotspots, and CI workflow issues
- Update rexml, rack, faraday, and uri gems to patch known CVEs
v2.2.1 - 2025-08-16
Changed
- Improved Heroku Review App deployment configuration
- Enhanced Kubernetes deployment examples with better security practices
- Strengthened environment validation in utility scripts
Fixed
- Email template accessibility improvements (added missing HTML attributes)
- Deployment configuration issues in app.json
- Minor formatting issues in Kubernetes YAML examples
Security
- Enhanced deployment security configurations
- Improved environment checks for utility scripts
v2.2.0 - 2025-08-16
This release represents a major modernization of the Vulcan platform, bringing it up to the latest versions of Ruby, Rails, and Node.js while significantly improving performance, security, and developer experience.
🚀 Major Upgrades
Framework Modernization
- Rails 8.0.2.1: Complete upgrade from Rails 7.0.8.7 through progressive path (7.0 → 7.1 → 7.2 → 8.0)
- Ruby 3.3.9: Upgraded from Ruby 3.1.6 for improved performance and memory efficiency
- Node.js 22 LTS: Modernized from Node.js 16 for better JavaScript tooling support
- esbuild: Migrated from Webpacker for 10x faster JavaScript builds
Test Suite Overhaul (#683)
- Migrated all controller specs to request specs (Rails 8 requirement)
- Migrated all feature specs to system specs (Rails 5.1+ standard)
- Removed anti-patterns like
any_instance_of - Fixed Devise authentication with Rails 8 lazy route loading
- All 190 tests passing with improved performance
Docker & Container Optimization
- Image size reduced by 73%: From 6.5GB to 1.76GB
- Memory usage reduced by 20-40% using jemalloc
- Multi-stage builds for improved security
- Full support for corporate SSL certificates
- Container-friendly JSON structured logging
🛡️ Security Improvements
Critical fixes:
- SQL injection vulnerability in
Component#duplicate_rulesfixed with parameterized queries - Mass assignment vulnerabilities resolved with Rails 8
expectAPI - All Rails 8 deprecation warnings resolved
- SQL injection vulnerability in
Dependency updates:
- axios: 1.6.8 → 1.11.0 (fixes SSRF vulnerabilities)
- factory_bot: 5.2.0 → 6.5.4
- ESLint: 8.x → 8.57.1
- Prettier: 2.8.8 → 3.6.2
- Added bundler-audit for vulnerability scanning
✨ New Features
OIDC Auto-Discovery
- Automatic endpoint configuration from provider metadata
- Support for Okta, Auth0, Keycloak, Azure AD
- Configuration reduced from 8+ to just 4 environment variables
- Session-based caching with 1-hour TTL
Enhanced Developer Experience
- Comprehensive environment variable documentation
- Automatic secret generation script (
setup-docker-secrets.sh) - Production-ready Docker Compose configurations
- SonarCloud integration for code quality
🐛 Bug Fixes
- Fixed 'Applicable - Configurable' status field display issue (#684)
- Fixed overlay component seed data rule counts
- Fixed Vue template compilation errors in STIG pages
- Fixed component
rules_countcounter cache - Fixed Capybara Selenium driver for Selenium 4.x compatibility
📦 UI Updates
- Complete migration from MDI to Bootstrap icons
- Removed @mdi/font package dependency (300KB reduction)
- Updated all navbar and component icons
- Improved icon consistency across the application
⚠️ Breaking Changes
- Ruby 3.3.9 now required (was 3.1.6)
- Node.js 22 LTS now required (was Node.js 16)
- Rails 8.0.2.1 now required (was Rails 7.0.8.7)
- Webpacker removed in favor of jsbundling-rails with esbuild
- RSpec Rails 6.0+ required for test suite
- Spring gem removed (Rails 8 uses built-in reloader)
📝 Migration Guide
Update Ruby and Node.js:
bashrbenv install 3.3.9 nvm install 22Update dependencies:
bashbundle install yarn installRun database migrations:
bashrails db:migrateClear caches:
bashrails tmp:cache:clearUpdate test environment if you have custom settings in
config/environments/test.rb
🔮 Coming Soon
- Vue 3 migration (currently Vue 2.6.11)
- Bootstrap 5 upgrade (currently Bootstrap 4.4.1)
- Turbolinks removal for simplified architecture
v2.1.9 - 2024-06-13
Major Features
- OIDC Auto-Discovery Enhancement (#672)
- Automatic configuration discovery for OpenID Connect providers
- Reduced configuration complexity
Infrastructure Improvements
- Enhanced Docker Compose configurations with production defaults
- Fixed Anchore SBOM artifact naming (#668)
- Updated GitHub Actions to v4
Bug Fixes
- Fixed critical OIDC authentication case sensitivity bug
- Fixed LDAP authentication (#669)
- Fixed User
effective_permissionsmethod visibility - Resolved axios compatibility issues
Data Updates
- Updated CCI mappings to latest rev5 (#627)
- Revised Excel/CSV column ordering to align with DISA SRGTemplate (#660)
v2.1.8 - 2024-06-28
Updates
- Updated CCI mapping with latest Rev 5 mappings (#626)
v2.1.7 - 2024-05-21
Security Updates
- Multiple npm dependency updates for security
- axios upgrade from 0.21.4 to 1.6.0 (#617)
Infrastructure
- Upgraded to new Heroku plan (#624)
v2.1.6 - 2023-11-08
Security
- Container now runs as non-root user (#612)
- Security dependency updates
Previous Releases
For releases prior to v2.1.6, please see the GitHub releases page.