Skip to content

Release v2.3.1: Feature release with security hardening and DRY refactors#706

Merged
wdower merged 429 commits intomasterfrom
v2.3.1
Apr 3, 2026
Merged

Release v2.3.1: Feature release with security hardening and DRY refactors#706
wdower merged 429 commits intomasterfrom
v2.3.1

Conversation

@aaronlippold
Copy link
Copy Markdown
Member

@aaronlippold aaronlippold commented Feb 14, 2026

Summary

Major feature release with 370 commits covering security hardening, UI redesign, export system overhaul, DISA compliance features, and infrastructure modernization.

Highlights

  • Security hardening: SQL injection fixes, XXE prevention, PBKDF2 password hashing, deny-by-default authorization, CSP headers, account lockout (AC-07), consent modal (AC-8)
  • Export overhaul: Unified export service with XCCDF, InSpec, Excel, CSV, JSON archive formatters; VendorSubmission and Backup modes; progressive disclosure ExportModal
  • UI redesign: Unified rule form, command bars, Bootstrap slideovers, BenchmarkViewer with keyboard navigation, standardized list pages
  • Infrastructure: Ruby 3.4.8, Node 24 LTS, PostgreSQL 18, multi-stage Dockerfile, tag-triggered releases with git-cliff, CI sharding with parallel_rspec

Upgrades

  • Ruby 3.3.9 → 3.4.8, Puma 5.6.9 → 7.2.0, Node 22 → 24 LTS, PostgreSQL 12/16 → 18
  • Replaced overcommit with lefthook; added pre-push checks

Security

  • Sanitize SQL LIKE input to prevent injection in search queries
  • Deny-by-default authorization on all controller actions
  • XXE prevention via Nokogiri NONET patch on HappyMapper
  • PBKDF2 password hashing with transparent bcrypt migration
  • Account lockout (STIG AC-07) with Devise Lockable
  • AC-8 consent modal with configurable TTL
  • CSP headers for OIDC provider and application security
  • Input length limits, upload validation, rate limiting
  • CVE patches: rexml, rack, faraday, uri gems

New Features

  • Global search via pg_search across rules, STIGs, SRGs
  • Admin bootstrap: first-user-admin and env var support
  • JSON archive backup/restore with full-fidelity serializer
  • Excel export with caxlsx, per-cell lock styling, Source column
  • CSV export with configurable column picker and header aliases
  • SRG auto-detection from spreadsheet import
  • Configurable password complexity (DoD 8500.2/2222)
  • Classification banner and consent modal
  • Health check endpoints for Kubernetes/Docker
  • GET /api/version endpoint
  • Tag-triggered release automation with git-cliff

Testing

  • Vitest infrastructure for Vue 2 components (1200+ frontend tests)
  • 36 spec files converted to let_it_be (~65% faster backend suite)
  • shoulda-matchers 7.0 validation contract specs
  • CI sharding: 6 parallel_rspec shards with ci-gate job
  • Request specs for components, rules, exports, backup round-trip

Documentation

  • VitePress documentation site
  • Deployment, authentication, security, and DISA process guides
  • Updated ENVIRONMENT_VARIABLES.md and CHANGELOG.md

Test Plan

  • CI passing (lint, frontend, backend 6 shards, ci-gate)
  • Security review: 0 actionable findings
  • Review app deployed and verified (vulcan-v2-3-1-adq4r4qqmttccwmi)
  • Pre-push hooks: RuboCop 0 offenses, Brakeman 0 warnings, ESLint clean
  • Manual XLSX locked cells test in Excel

- Remove redundant type: declarations in 36 spec files (InferredSpecType)
- Use implicit association style in 3 factories (AssociationStyle)
- Use have_http_status matcher in stigs_spec (HaveHttpStatus/HttpStatus)
- Use have_css matcher in system specs (HaveSelector)

Authored by: Aaron Lippold<lippold@gmail.com>
@aaronlippold aaronlippold temporarily deployed to vulcan-v2-3-1-adq4r4qqmttccwmi February 14, 2026 22:26 Inactive
- Add .reload to 5 satisfaction tests to prevent association caching
  (matches pattern already used by 12 other tests in same context)
- Rename jbuilder_index_spec.rb → jbuilder_index.rb to prevent
  double-loading (support files must not end in _spec.rb)

Authored by: Aaron Lippold<lippold@gmail.com>
@aaronlippold aaronlippold temporarily deployed to vulcan-v2-3-1-adq4r4qqmttccwmi February 15, 2026 00:29 Inactive
The srg_id DB column on component rules is nil — the correct value
lives in srg_rule.version. The member path worked because as_json
overrides it, but the non-member Jbuilder path read the nil column.

- Compute srg_id from rule.srg_rule.version in Jbuilder viewer branch
- Add satisfies/satisfied_by arrays to non-member view
- Add 4 regression tests covering both member and non-member paths

Authored by: Aaron Lippold<lippold@gmail.com>
@aaronlippold aaronlippold temporarily deployed to vulcan-v2-3-1-adq4r4qqmttccwmi February 15, 2026 14:49 Inactive
- RuleNavigator: all 3 locations use srg_id with tooltips, nested
  children ALWAYS show SRG IDs regardless of toggle
- RuleSatisfactions: flat srg_id instead of nested srg_rule.version
- RulesCodeEditorView: modal uses srg_id, localStorage persists toggles
- Rules.vue: satisfaction push uses srg_id field
- terminology.js: "Select SRG requirements that this rule satisfies"

Authored by: Aaron Lippold<lippold@gmail.com>
Remove Multiselect import/CSS, dead data properties (truncateId,
showSRGIdChecked, filteredSelectRules, selectedSatisfiesRuleIds,
satisfiesSearchText), dead watch/mounted/beforeUnmount lifecycle,
and dead methods (filterRules, addCustomSatisfaction,
addMultipleSatisfiedRules, clearSelectedRules, updateShowSRGIdChecked).

The real satisfaction modal lives in RulesCodeEditorView.vue.

Authored by: Aaron Lippold<lippold@gmail.com>
…ewer

RuleList: vertical severity buttons, keyboard navigation with roving
tabindex (ArrowUp/Down, Enter/Space), ARIA listbox/option roles,
scrollIntoView on focus.

RuleOverview: satisfaction display parsed from VulnDiscussion text,
truncated SRG ID badges with tooltips, shows "None" when absent.

Authored by: Aaron Lippold<lippold@gmail.com>
- RuleOverview: 8 tests for satisfaction display (VulnDiscussion parsing,
  badges, tooltips, sorted output, empty states)
- RuleList: 12 tests for keyboard nav (arrow keys, wrap, Enter/Space),
  row styling (selected/focused/default), ARIA attributes
- RuleEditorHeader: rewritten spec for actual behavior (header display,
  locked/review state, review actions, readOnly mode)
- RuleNavigator: 6 TDD tests for SRG ID display and nested children
- Rules.spec: fix stale mock data (version -> srg_id)
- terminology.spec: updated assertion for SRG requirements text
- rules_spec.rb: backend contract tests for satisfaction shape

Authored by: Aaron Lippold<lippold@gmail.com>
Authored by: Aaron Lippold<lippold@gmail.com>
- Replace random FFaker values with sequential IDs in SRG and STIG factories
- Fix rails_helper fixture_paths from spec/fixtures/fixtures to spec/fixtures

Authored by: Aaron Lippold<lippold@gmail.com>
- Add 31 tests for RuleSatisfactions component (srg_id display, remove modals, readOnly mode)
- Add 10 tests for RulesCodeEditorView satisfaction modal (filterRulesForSatisfies, addMultipleSatisfiedRules, clearSelectedRules)
- Verify SRG ID truncation, fallback to prefix-rule_id, and eligible rule filtering

Authored by: Aaron Lippold<lippold@gmail.com>
Authored by: Aaron Lippold<lippold@gmail.com>
- Block rm, git rm, git clean, git checkout ., git reset --hard
- Enforced by claude-code-safety-net hooks (cannot be bypassed)

Authored by: Aaron Lippold<lippold@gmail.com>
- Strips stale "Satisfies:"/"Satisfied By:" text from vendor_comments and vuln_discussion
- Scoped to component Rules only (type='Rule'), leaves SRG/STIG data untouched
- 10 tests covering regex patterns, scoping, and SQL strip behavior

Authored by: Aaron Lippold<lippold@gmail.com>
…exes

- seed_xccdf_spec: 42 tests for title-based and directory-path classification
- settings_defaults_spec: 19 tests for config defaults across deployment types
- schema_indexes_spec: 4 tests for composite index existence on base_rules

Authored by: Aaron Lippold<lippold@gmail.com>
Reusable password input with eye/eye-slash visibility toggle.
Uses v-model with internal localValue to prevent Vue #6313 bug
where :value prop is cleared when input type changes dynamically.

19 Vitest tests covering rendering, toggle, form integration,
accessibility, and Vue #6313 regression.

Authored by: Aaron Lippold<lippold@gmail.com>
Register PasswordField in login.js Vue instance and replace bare
password_field helpers across all 4 Devise views:
- sessions/_local.html.haml (login)
- sessions/_ldap.html.haml (LDAP login)
- registrations/_form.html.haml (signup: password + confirmation)
- passwords/edit.html.haml (reset: password + confirmation)

Password reset page also gets Vue mount point (#login div),
javascript_include_tag, Bootstrap form-group classes, and btn styling.

Authored by: Aaron Lippold<lippold@gmail.com>
Authored by: Aaron Lippold<lippold@gmail.com>
- Component: add name and title presence validations (prefix already had one)
- AdditionalAnswer: null-safe additional_question access + use .present? over .empty?
- Membership: guard against nil membership association in permission check
- Review: guard against nil user/rule in validate_project_permissions

Authored by: Aaron Lippold<lippold@gmail.com>
- Add shoulda-matchers gem to test group
- Configure RSpec integration in rails_helper.rb

Authored by: Aaron Lippold<lippold@gmail.com>
63 shoulda-matchers tests covering presence, uniqueness, inclusion,
numericality, and association validations across Component, Rule,
Review, User, Membership, Project, and AdditionalAnswer models.

Authored by: Aaron Lippold<lippold@gmail.com>
Component now validates presence of name and title. Updated all spec
files that create Components directly to include these required fields,
matching what the frontend NewComponentModal already requires.

Authored by: Aaron Lippold<lippold@gmail.com>
…mponents

Prevent TypeError when name or email is null/undefined in search filter
computed properties. Uses (value || '') pattern before .toLowerCase().

Files: MembersModal, MembershipsTable, Project, ProjectsTable,
SecurityRequirementsGuidesTable, UsersTable

Authored by: Aaron Lippold<lippold@gmail.com>
New specs: MembershipsTable, NewMembership, CommentModal, History,
6 mixins (ConfirmComponentRelease, DisplayedComponent, EmptyObject,
FindAndReplace, HistoryGrouping, HumanizedTypes), syntaxHighlighter.
Updated: FilterBar (requirement-based rewrite), Stigs.

Authored by: Aaron Lippold<lippold@gmail.com>
Prettier double-quotes, semicolons, line-wrapping in 10 spec files.
Vue attribute ordering and component option ordering in 7 Vue files.

Authored by: Aaron Lippold<lippold@gmail.com>
@wdower wdower temporarily deployed to vulcan-v2-3-1-adq4r4qqmttccwmi April 2, 2026 01:11 Inactive
@wdower wdower temporarily deployed to vulcan-v2-3-1-adq4r4qqmttccwmi April 2, 2026 01:14 Inactive
wdower added 24 commits April 1, 2026 21:37
- Add rubocop:disable for Rails/StrongParametersExpect on require.permit
  calls (params.expect breaks nested array attributes)
- Add rubocop:disable for Rails/SkipsModelValidations on soft-delete
  update_columns (intentional bypass)
- Use %r{} for regex literals in disa_guide_controller and routes
- Use modifier if for single-line warning log
- Remove extra blank line in rules_spec block body
- Use response.parsed_body instead of JSON.parse(response.body)

Signed-off-by: Will Dower <will@dower.dev>
…ments (B1, B8, B10)

- Defensive || [] for additional_answers_attributes in AdditionalAnswerForm
- Add onAutoSave callback to useRuleAutosave composable
- Change history grouping from per-minute to 5-second interval
- Update specs for autosave callback and grouping logic

Signed-off-by: Will Dower <will@dower.dev>
- C1: Only show truncation toggle when description > 75 chars
- C2: Rename tab to "Generated Control (Read-Only)"
- C6: Add inspec to working_copy mode in exportConfig and Registry
- C7: Update DiffViewer spec for "Base (older)" / "Compare (newer)"
- Update ExportModal spec for working_copy inspec enablement
- Add ProjectsTable spec for description truncation

Signed-off-by: Will Dower <will@dower.dev>
- Legacy export_helper: generate stub controls for Not Yet Determined
  rules with impact 0.0 and TODO comment
- Auto-populate inspec_control_body on rule creation with commented-out
  describe block via after_create callback
- Add helper specs for NYD stub inclusion and status filtering

Signed-off-by: Will Dower <will@dower.dev>
Replace traditional form POST for role changes and data-method delete
links with axios calls. Add loading spinner for remove action. UI
now updates without full page reload.

Signed-off-by: Will Dower <will@dower.dev>
- Add commonmarker gem for server-side markdown rendering (DISA guide)
- bin/dev auto-detects available port if 3000 is taken (IPv4+IPv6)
- Procfile.dev uses bin/dev for port detection in foreman

Signed-off-by: Will Dower <will@dower.dev>
- Move INSPEC_STUB_BODY constant above private section
- Remove .freeze on heredoc (immutable in Ruby 3.4)
- Fix callback order (after_create before before_destroy)
- Add rubocop:disable for update_column in seed callback

Signed-off-by: Will Dower <will@dower.dev>
Auto-fix Prettier formatting: multi-line attributes on ComponentCard,
RuleRevertModal, RuleActionsToolbar, App navbar, and
RulesCodeEditorView.

Signed-off-by: Will Dower <will@dower.dev>
…ding

- Sanitize attachment filename with File.basename to prevent path traversal
- Add authorize_component_access to components#histories action
- Seed inspec_control_body after bulk SRG import (bypassed after_create callback)

Signed-off-by: Will <will@dower.dev>
- CVE-2026-33658 (activestorage DoS): Rails 8.0.4 → 8.0.5
- CVE-2026-32700 (devise email confirm race): devise 4.9.4 → 5.0.3
- CVE-2026-33306 (bcrypt JRuby overflow): bcrypt 3.1.21 → 3.1.22
- Loofah improper URI detection: loofah 2.25.0 → 2.25.1

Devise 5 breaking change (secret key lookup) has no impact — Vulcan
uses SECRET_KEY_BASE env var which maps to application.secret_key_base,
the only source Devise 5 checks. No credentials.yml or secrets.yml
exist. No outstanding user tokens will be invalidated.

bundler-audit: 0 vulnerabilities
Signed-off-by: Will Dower <will@dower.dev>
- Add RuleRevertModal spec (12 tests): two-phase flow, revert action,
  reset on hide, button text, modal open/close
- Add AdditionalAnswerForm spec (6 tests): defensive || [] init for
  undefined/null attributes, answer CRUD, URL validation
- Add MembershipsTable AJAX specs (5 tests): role change PUT, remove
  DELETE, confirm cancel, loading state, memberRemoved event

Test count: 2008 (was 1985, +23 new tests)

Signed-off-by: Will Dower <will@dower.dev>
Upstream merge overwrote the fix from 1dfaad9. Component factory
uses bulk import (skips callbacks), so the test must create a rule
individually to exercise the after_create seed_inspec_control_body
callback.

Signed-off-by: Will Dower <will@dower.dev>
Read demo admin password from VULCAN_SEED_ADMIN_PASSWORD env var
with fallback default for local dev convenience. Add default value
to GitGuardian ignored_matches. Stop printing password in seed
output. Document override in .env.example.

Signed-off-by: Will Dower <will@dower.dev>
- Fix undefined method: export_inspec → export_inspec_component
- Update export_inspec_project tests to filter for inspec.yml entries
  only, since NYD stub controls now add .rb files to the zip

Signed-off-by: Will Dower <will@dower.dev>
- Registry spec: expect [:csv, :excel, :inspec] for working_copy
- Authorization coverage: add disa_guide#show and disa_guide#attachment
  to AUTHENTICATE_ONLY_ACTIONS (uses authorize_logged_in)

Signed-off-by: Will Dower <will@dower.dev>
SonarCloud flags workflow-level permissions as a security concern.
Move contents:read to build job, pages:write and id-token:write
to deploy job, per GitHub Actions security best practices.

Signed-off-by: Will Dower <will@dower.dev>
- Dockerfile: add --chmod=755 to COPY to prevent write permissions
  on copied application files
- release.yml: pin all GitHub Actions to full commit SHAs instead
  of mutable version tags (supply chain security best practice)

Signed-off-by: Will Dower <will@dower.dev>
- Extract FIELD_ARTIFACT_DESCRIPTION and NO_FILE_PROVIDED constants
- Use RuleConstants::STATUS_APPLICABLE_CONFIGURABLE instead of string
- Add default clause to DISA status case statement
- Replace pluck(:id) with .ids in controllers (5 occurrences)
- Use optional chaining in useRuleAutosave
- Use structuredClone instead of _.cloneDeep in Rules.vue
- Use globalThis instead of window in App.vue
- Merge duplicate html selectors in application.scss
- Move require_relative to top of health_check initializer
- Refactor seed_component to use keyword args (was 8 positional params)
- Use ArgumentError instead of raising a string literal in seeds

Signed-off-by: Will Dower <will@dower.dev>
Dockerfile optimizations (1.1GB → 487MB):
- Remove unused libvips42 (image_processing gem is disabled)
- Strip gem build artifacts (.o, .c, .h) from bundle
- Consolidate bootsnap + asset precompile + cleanup into single layer
- Remove docs, dotfiles, yarn.lock, package.json from production image
- Combine user creation + dir setup + bundle strip into single RUN

Compose file rename (convention alignment):
- docker-compose.prod.yml → docker-compose.yml (default for deployment)
- docker-compose.yml → docker-compose.dev.yml (database for native dev)
- docker compose up now runs the full app (the expected default)
- bin/setup updated to use -f docker-compose.dev.yml
- Add web service to dev compose for optional containerized dev
- Update all doc references (README, setup guides, env docs)

Signed-off-by: Will Dower <will@dower.dev>
Docker silently binds 0.0.0.0:3000 even when another app occupies
the port on a specific address. Use VULCAN_PORT env var (default
3000) so users can override: VULCAN_PORT=3001 docker compose up

Signed-off-by: Will Dower <will@dower.dev>
Rails production defaults force_ssl to true, which redirects all
HTTP to HTTPS. Without a reverse proxy providing SSL termination,
this causes ERR_SSL_PROTOCOL_ERROR. Default to false in compose
environment so docker compose up works out of the box. Users
enable SSL when configuring caddy/nginx proxy profiles.

Signed-off-by: Will Dower <will@dower.dev>
projects#histories was missing authorization, allowing any
authenticated user to view project history. Add to
authorize_viewer_project before_action to require project
membership. Fixes authorization_coverage_spec failure.

Signed-off-by: Will Dower <will@dower.dev>
seed_component was refactored from named keyword params to **opts
with fetch(). Update spec to check for .fetch(:title) instead of
matching title in the method signature.

Signed-off-by: Will Dower <will@dower.dev>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 114 out of 797 changed files in this pull request and generated 11 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/controllers/stigs_controller.rb Outdated
def show
@stig_json = @stig.to_json(methods: %i[stig_rules])
# Eager load associations for performance (set_stig loads basic STIG)
@stig = Stig.includes(stig_rules: %i[disa_rule_descriptions checks]).find_by(id: params[:id])
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

find_by can return nil, which will cause a 500 when @stig.to_json is called in the HTML responder. Use find(params[:id]) (and let RecordNotFound be handled) or explicitly handle the nil case consistently with set_stig (flash + redirect) to avoid a runtime error.

Suggested change
@stig = Stig.includes(stig_rules: %i[disa_rule_descriptions checks]).find_by(id: params[:id])
@stig = Stig.includes(stig_rules: %i[disa_rule_descriptions checks]).find(params[:id])

Copilot uses AI. Check for mistakes.
not_determined_msg = "The following controls are 'Not Yet Determined': #{not_determined_controls}" if not_determined_controls.present?

# Identify rules that can't be locked due to incomplete data (B10: warn but proceed)
skipped_ids = Set.new
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Set is from Ruby's stdlib and typically requires require 'set'. Without it, this can raise NameError: uninitialized constant Set at runtime. Add require 'set' (e.g., at the top of this file) or replace Set usage with an Array + uniq/include? logic.

Copilot uses AI. Check for mistakes.
Comment thread app/controllers/reviews_controller.rb Outdated
Comment on lines +154 to +161
rule.update!(locked_fields: fields)
action_word = locked ? 'Locked' : 'Unlocked'
rule.audits.create!(
action: 'update',
audited_changes: { 'locked_fields' => [old_fields, fields] },
user: current_user,
comment: comment.presence || "#{action_word} sections: #{sections.join(', ')}"
)
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will likely produce duplicate audit entries: update! on an Audited model already creates an audit record, and then a second audit is created manually. Prefer setting rule.audit_comment = ... before update! (similar to RulesController#section_locks) so a single audit entry is recorded with the desired comment.

Suggested change
rule.update!(locked_fields: fields)
action_word = locked ? 'Locked' : 'Unlocked'
rule.audits.create!(
action: 'update',
audited_changes: { 'locked_fields' => [old_fields, fields] },
user: current_user,
comment: comment.presence || "#{action_word} sections: #{sections.join(', ')}"
)
action_word = locked ? 'Locked' : 'Unlocked'
rule.audit_comment = comment.presence || "#{action_word} sections: #{sections.join(', ')}"
rule.update!(locked_fields: fields)

Copilot uses AI. Check for mistakes.

try {
const response = await axios.delete(
`/projects/${this.membership_id}/project_access_requests/${requestId}`,
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This DELETE request does not specify a JSON format. If Rails routes default to HTML here, axios may receive a redirect/HTML response instead of JSON. Use a .json suffix (e.g., /.../${requestId}.json) or set an Accept: application/json header to ensure the controller responds with the JSON branch.

Suggested change
`/projects/${this.membership_id}/project_access_requests/${requestId}`,
`/projects/${this.membership_id}/project_access_requests/${requestId}.json`,

Copilot uses AI. Check for mistakes.
Comment on lines +257 to +263
async roleChanged(_event, project_member) {
try {
const response = await axios.put(`/memberships/${project_member.id}.json`, {
membership: { role: project_member.role },
});
this.alertOrNotifyResponse(response);
} catch (error) {
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the role update request fails, the UI will keep showing the new (unsaved) project_member.role because it’s already mutated via v-model. Capture the previous role before the change and revert it in the catch path (or refetch the memberships) so the client state stays consistent with the server.

Suggested change
async roleChanged(_event, project_member) {
try {
const response = await axios.put(`/memberships/${project_member.id}.json`, {
membership: { role: project_member.role },
});
this.alertOrNotifyResponse(response);
} catch (error) {
async roleChanged(_event, project_member) {
const previousRole = project_member.role_was ?? _event?.target?.defaultValue ?? null;
try {
const response = await axios.put(`/memberships/${project_member.id}.json`, {
membership: { role: project_member.role },
});
this.alertOrNotifyResponse(response);
} catch (error) {
project_member.role = previousRole;

Copilot uses AI. Check for mistakes.
Comment thread app/constants/rule_constants.rb Outdated
Comment thread app/controllers/disa_guide_controller.rb Outdated
Comment thread app/controllers/project_access_requests_controller.rb Outdated
Comment thread CONTRIBUTING.md Outdated
Comment on lines +210 to 217
confirmDelete() {
this.isDeleting = true;
this.$emit("deleteComponent", this.component.id);
},
resetDelete() {
this.isDeleting = false;
this.showDeleteConfirmation = false;
},
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

confirmDelete() sets isDeleting = true, but there’s no call site shown that invokes resetDelete() on failure (or on cancel after confirmation). If the delete request fails and the component remains on screen, the overlay will stay stuck in the “Removing component…” state. Consider emitting an event/callback for delete completion/failure so the parent can tell this card to reset, or handle promise outcomes directly in the card.

Copilot uses AI. Check for mistakes.
- Use explicit string for STATUS_APPLICABLE_CONFIGURABLE (not index)
- Use find() instead of find_by() in stigs#show to raise RecordNotFound
- Fix duplicate audit in reviews bulk_section_locks (use audit_comment
  before update! instead of manual audits.create!)
- Add .json suffix to access request DELETE for consistent JSON response
- Revert role on failed membership update to keep UI consistent
- Guard satisfied_by/satisfies arrays before .some()/.push()
- Sanitize rendered markdown HTML in DISA guide controller
- Fix typo: Sucessfully → Successfully
- Fix CONTRIBUTING.md duplicate section heading

Signed-off-by: Will Dower <will@dower.dev>
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Apr 3, 2026

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants