-
Notifications
You must be signed in to change notification settings - Fork 0
Core Functions
This page documents every major subsystem and feature in SPARC, the Systematic and Regulatory Compliance platform for NIST 800-53 compliance management.
Looking for how-to instructions? This page describes how features work internally. For step-by-step, task-oriented walkthroughs, see the User Guides.
- Document Processing Pipeline
- OSCAL Export & Validation
- SSP Wizard
- SSP Enrichment
- Control Mapping
- Evidence & Attestation Management
- Authorization Boundary Management
- Audit Logging
- Heatmap Analytics
- Document Duplication & Copy
- SAP Generation
- JSON Export
- REST API
- Dark Mode & Theming
- HTTPS Enforcement & Security Headers
- Converters (CCI / AWS / STIG → NIST)
- AWS Labs CDEF Ingestion
- HDF ↔ OSCAL Translation Bridge
- FedRAMP 20x KSI Catalog & Validations
- Authoritative Sources & Federation
- Leveraged Authorizations
- Organizations & Service Accounts
- Document Review & Approval Workflow
All document uploads follow a unified flow driven by the FileUploadable controller concern and the DocumentTypeRegistry model. This architecture ensures consistent behavior across all six document types.
| Type Key | Document Class | Control Class | Field Class | Allowed Extensions |
|---|---|---|---|---|
ssp |
SspDocument |
SspControl |
SspControlField |
.json, .xml, .yaml, .yml
|
sar |
SarDocument |
SarControl |
SarControlField |
.json, .xml, .yaml, .yml
|
cdef |
CdefDocument |
CdefControl |
CdefControlField |
.xml, .json, .yaml, .yml
|
profile |
ProfileDocument |
ProfileControl |
ProfileControlField |
.json, .xml, .yaml, .yml
|
sap |
SapDocument |
SapControl |
SapControlField |
.json, .xml, .yaml, .yml
|
poam |
PoamDocument |
PoamItem |
(none) |
.json, .xml, .yaml, .yml
|
The DocumentTypeRegistry (located at app/models/document_type_registry.rb) is a centralized registry that maps each type key to its associated classes, file extensions, parser services, and user-facing messages. Each entry is a Data.define struct:
Entry = Data.define(
:document_class,
:control_class,
:field_class,
:document_fk,
:allowed_extensions,
:parser_map,
:file_prefix,
:success_message
)Usage:
entry = DocumentTypeRegistry.for(:sar)
entry.document_class # => SarDocument
entry.parser_map # => { "json" => SarJsonParserService, "xml" => SarXmlParserService, ... }The FileUploadable concern (app/controllers/concerns/file_uploadable.rb) implements a five-step pipeline that every document controller shares:
- Validate file presence -- returns an error flash if no file is attached.
-
Detect file type -- maps the file extension against
registry.allowed_extensionsto determine the parser format (e.g.,.jsonmaps to"json"). -
Write to persistent path -- the file is written to
tmp/with a safe prefix and random hex suffix. File path components come exclusively from frozen constants (SAFE_PREFIXES,SAFE_EXTENSIONS), satisfying Brakeman taint analysis. -
Create document record -- a new document is created with
status: "pending", the uploaded file is attached via Active Storage, andDocumentConversionJobis enqueued. - Error handling -- on failure, the temporary file is cleaned up and the user sees the error.
# In any document controller:
def create
handle_file_upload(:ssp, param_key: :ssp_document)
endLarge files are processed asynchronously via Sidekiq:
-
DocumentConversionJobreceives the type key, document ID, and file path. - The job instantiates the appropriate parser service from the registry.
- Document status transitions:
pending->processing->completed(orfailed). - The
ConversionJobmodel tracks job state for monitoring.
Each document type exposes a status endpoint that returns JSON, enabling the frontend to poll for completion:
GET /:document_type/:id/status
# => { "status": "completed" }
Each parser service handles a specific input format and populates the three-level model hierarchy (Document -> Controls -> Fields).
Files: app/services/cdef_json_parser_service.rb, app/services/cdef_xccdf_parser_service.rb
-
CdefJsonParserService-- parses OSCAL Component Definition JSON. -
CdefXccdfParserService-- parses DISA STIG XCCDF XML format, extracting rules, check content, and fix text.
Files: app/services/profile_json_parser_service.rb, app/services/profile_xml_parser_service.rb
Parse OSCAL Profile baselines from JSON and XML formats.
File: app/services/catalog_import_service.rb
Imports NIST 800-53 control catalogs from two formats:
-
OSCAL JSON -- standard NIST OSCAL catalog schema from
oscal-content. - NIST XML -- SP 800-53 SCAP feed schema v2.0.
Key behaviors:
-
Zero-padding: Single-digit control numbers are padded (
AC-1becomesAC-01,AC-10stays unchanged). -
Sub-parts as siblings: Statement parts are stored as sibling
CatalogControlrecords with hierarchical IDs:AC-01,AC-01a,AC-01a.1,AC-01a.1.(a). - Format auto-detection: Inspects file extension and content structure to determine OSCAL JSON vs. NIST XML.
-
Upsert semantics: Uses
find_or_initialize_byfor catalogs, families, and controls, so re-importing updates existing records rather than creating duplicates. - Extracts guidance data including statements, supplemental guidance, related controls, and references.
- Supports all 20 NIST 800-53 control families via a
FAMILY_NAME_TO_CODElookup table.
Files: app/services/sap_json_parser_service.rb, app/services/sap_xml_parser_service.rb, app/services/sap_yaml_parser_service.rb
Parse OSCAL Assessment Plan documents from JSON, XML, and YAML. The XML parser uses Nokogiri to extract metadata, activities, reviewed controls, and assessment subjects, then delegates via a temporary JSON file. The YAML parser loads via YAML.safe_load and delegates via temporary JSON file.
Files: app/services/poam_json_parser_service.rb, app/services/poam_xml_parser_service.rb, app/services/poam_yaml_parser_service.rb
Parse Plan of Action & Milestones documents from JSON, XML, and YAML. The POA&M model uses PoamItem rather than a generic control class, reflecting the distinct structure of POA&M findings. The YAML parser delegates to PoamJsonParserService#parse_from_hash.
Files: app/services/ssp_yaml_parser_service.rb, app/services/sar_yaml_parser_service.rb, app/services/poam_yaml_parser_service.rb, app/services/profile_yaml_parser_service.rb, app/services/cdef_yaml_parser_service.rb, app/services/sap_yaml_parser_service.rb
All six document types support YAML import. YAML parsers use two delegation patterns to avoid duplicating parsing logic:
-
Pattern A (SSP, SAR, POAM) -- the JSON parser exposes a
parse_from_hash(data)method; the YAML parser callsYAML.safe_loadand passes the resulting hash directly. -
Pattern B (Profile, CDEF, SAP) -- the JSON parser has no
parse_from_hash; the YAML parser writes the parsed data to a temporary JSON file and delegates to the JSON parser'sparsemethod.
Both patterns use YAML.safe_load with permitted_classes: [Date, Time] for safe deserialization.
File: app/services/oscal_format_detection_service.rb
Detects the format of an OSCAL file by extension first (.json, .yaml, .yml, .xml), falling back to content sniffing (first non-whitespace character: { or [ for JSON, < for XML, otherwise YAML). Returns a Result struct with format and detected_by fields.
File: app/services/concerns/batch_insertable.rb
Provides high-throughput bulk insertion shared across parser services. Uses the activerecord-import gem to batch insert records within a single database transaction:
- Control batch size: 5,000 records per batch
- Field batch size: 10,000 records per batch
batch_insert_records(
control_class: SarControl,
field_class: SarControlField,
document_fk: :sar_document_id,
control_attrs: control_attrs, # Array of attribute hashes
field_entries: field_entries # Array of [control_index, field_name, field_value]
)Control IDs are returned via returning: :id so field records can be linked to their parent controls by index.
Directory: lib/data_mappings/
Files: ssp_excel.json, sar_excel.json
Service: app/services/data_mapping_schema.rb
Data mapping files are vendor-neutral, declarative JSON definitions that describe how source columns (tabular headers) map to internal model attributes and fields. Each mapping entry includes:
-
source_header-- the normalized column header from the source file. -
key-- the internal attribute or field name. -
storage-- one ofcontrol_attribute(stored on the model),control_field(stored in the field table), orsubject(split on"|"into asset/environment). -
editable-- whether the field is editable in the UI. -
validation-- optional rules (e.g.,allowed_values). -
oscal_mapping-- how the field maps to OSCAL export targets.
schema = DataMappingSchema.load(:ssp_excel)
schema.column_map # => { "paragraph/reqid" => { key: :control_id, control_attr: true } }
schema.editable_fields # => ["status", "implementation_statement", ...]
schema.oscal_mappings # => { "status" => { "target" => "prop", ... } }SPARC provides full OSCAL v1.1.2 export in three formats (JSON, YAML, XML) for all document types, with schema validation against the official NIST JSON schemas and XSD schemas.
Each document type has a dedicated OSCAL export service:
| Service | Document Type | OSCAL Root Key |
|---|---|---|
OscalSspExportService |
SSP | system-security-plan |
OscalComponentDefinitionExportService |
Component Definition | component-definition |
OscalSarExportService |
Assessment Results | assessment-results |
OscalProfileExportService |
Profile | profile |
OscalCatalogExportService |
Catalog | catalog |
OscalPoamExportService |
POA&M | plan-of-action-and-milestones |
OscalAssessmentPlanExportService |
Assessment Plan | assessment-plan |
OscalMappingExportService |
Control Mapping | mapping-collection |
File: app/services/oscal_ssp_export_service.rb
Builds a complete OSCAL v1.1.2 System Security Plan JSON document. The top-level structure includes:
- metadata -- title, version, oscal-version, last-modified, roles, parties, revisions.
- import-profile -- reference to the baseline profile.
- system-characteristics -- system IDs, name, description, security sensitivity level, security impact levels, system status, authorization boundary, network architecture, data flow.
-
system-implementation -- users, components (including
this-system), leveraged authorizations, inventory items. - control-implementation -- implemented requirements with by-components, statements (private/public/inherited), and props (status, control type, origination, responsible entities).
- back-matter -- preserved resources from import metadata.
The service works uniformly: enriched relational data is used when available (regardless of whether the SSP was created via wizard or file import), falling back to placeholder values only when no data exists.
service = OscalSspExportService.new(ssp_document)
json_string = service.export # validates, raises on failure
json_string = service.export_unvalidated # skips validation
result = service.validation_result # inspect errors without raisingSupports NIST, CIS, and DISA source mappings for component definitions.
Exports assessment results with synthesized observations and findings.
File: app/services/oscal_export_format_service.rb
All OSCAL export services produce JSON natively. The OscalExportFormatService wraps these exports to provide YAML and XML output:
-
OscalExportFormatService.to_yaml(json_string)-- parses the JSON and converts to YAML via.to_yaml. -
OscalExportFormatService.to_xml(json_string, model_type)-- delegates toOscalJsonToXmlConverter.
File: app/services/oscal_json_to_xml_converter.rb
Converts OSCAL JSON to XML using Nokogiri::XML::Builder with the OSCAL namespace (http://csrc.nist.gov/ns/oscal/1.0). Uses an explicit ATTRIBUTE_KEYS set (uuid, id, href, type, name, value, etc.) to distinguish XML attributes from child elements per OSCAL convention. Handles plural-to-singular element unwrapping (e.g., controls -> control) and recursive hash-to-XML conversion.
Each document controller provides download_yaml and download_xml actions that call the appropriate export service and format converter. The UI presents a Bootstrap 5 split-button dropdown allowing the user to select the export format (OSCAL JSON validated, JSON, YAML, XML).
File: app/services/oscal_schema_validation_service.rb
Validates OSCAL JSON against the official NIST v1.1.2 JSON schemas using the json_schemer gem (Draft 2020-12 support), and validates OSCAL XML against XSD schemas using Nokogiri::XML::Schema. Supports all eight OSCAL model types:
SCHEMA_MAP = {
component_definition: { file: "oscal_component_schema.json", root_key: "component-definition" },
ssp: { file: "oscal_ssp_schema.json", root_key: "system-security-plan" },
assessment_plan: { file: "oscal_assessment-plan_schema.json", root_key: "assessment-plan" },
assessment_results: { file: "oscal_assessment-results_schema.json", root_key: "assessment-results" },
poam: { file: "oscal_poam_schema.json", root_key: "plan-of-action-and-milestones" },
profile: { file: "oscal_profile_schema.json", root_key: "profile" },
catalog: { file: "oscal_catalog_schema.json", root_key: "catalog" },
mapping: { file: "oscal_mapping_schema.json", root_key: "mapping-collection" }
}The validation service performs:
- Structural pre-check -- verifies the expected root key is present.
-
Full schema validation -- runs
json_schemeragainst the NIST schema. - Error formatting -- produces human-readable messages (caps at 50 errors) with data pointer paths, missing required properties, enum violations, type mismatches, and pattern failures.
Schema files are cached after first load. An internal preprocess_schema step rewrites NIST anchor-style $ref values to standard JSON Pointer format (#/definitions/X) so json_schemer can resolve them locally without network access.
Directory: lib/oscal_xsd_schemas/
XML exports are validated against NIST OSCAL v1.1.2 XSD schemas using Nokogiri::XML::Schema. Seven XSD schema files are stored locally:
| Schema File | OSCAL Model |
|---|---|
oscal_ssp_schema.xsd |
System Security Plan |
oscal_assessment-results_schema.xsd |
Security Assessment Results |
oscal_assessment-plan_schema.xsd |
Security Assessment Plan |
oscal_poam_schema.xsd |
Plan of Action & Milestones |
oscal_profile_schema.xsd |
Profile |
oscal_catalog_schema.xsd |
Catalog |
oscal_component_schema.xsd |
Component Definition |
result = OscalSchemaValidationService.validate_xml(:ssp, xml_string)
result.valid? # => true/false
result.errors # => array of error messagesFive download modes are available in the UI for every document type:
-
Validated (
download_oscal_validated) -- validates against the NIST schema; fails with an error if invalid. -
Unvalidated (
download_oscal_unvalidated) -- always downloads, skipping validation. -
Auto (
download_oscal) -- attempts validated export first; on failure, redirects to the unvalidated endpoint. -
YAML (
download_yaml) -- exports as OSCAL YAML. -
XML (
download_xml) -- exports as OSCAL XML with XSD validation.
File: app/services/oscal_metadata_inheritance_service.rb
Preserves and merges OSCAL metadata (roles, parties, revisions, oscal-version) along the artifact chain:
ControlCatalog -> ProfileDocument -> SspDocument -> SapDocument -> SarDocument
SspDocument -> PoamDocument
Uses OSCAL resolution rules: child overrides parent for scalar fields; array fields (roles, parties) are merged with child entries taking precedence.
OscalMetadataInheritanceService.new(ssp_document).resolve!- PR #53 -- OSCAL schema validation and SSP export (#45)
- PR #67 -- Full schema uplift for CDEF, Catalogs, Profiles, SAR (#58)
- PR #64 -- OSCAL metadata management & inheritance (#52)
- PR #60 -- SSP creation wizard, OSCAL import, enrichment
- Issue #120 -- Full multi-format support (JSON, YAML, XML import and export for all document types)
File: app/services/ssp_wizard_service.rb
The SSP Wizard allows users to create a System Security Plan from scratch rather than importing from a file. The entire process runs in a single database transaction.
-
Profile (baseline) -- a
ProfileDocumentthat serves as the control source. -
Component Definitions (CDEFs) -- optionally attach one or more
CdefDocumentrecords. -
System metadata:
- System name and description
- System status (default:
"operational") - Security sensitivity level
- Security objectives (Confidentiality, Integrity, Availability)
- Authorization boundary description
-
Create SspDocument with
creation_method: "wizard"andstatus: "processing". - Create "this-system" component -- the OSCAL-required default component representing the system itself.
- Create default information type -- placeholder for system information.
-
Create default user -- "General User" with the
system-ownerrole. -
Import CDEF components (if selected) -- creates
SspDocumentCdefDocumentjoin records and correspondingSspComponententries. -
Populate controls from profile -- iterates through the profile's controls, creating
SspControlrecords with default fields (status defaults to "Deferred") and athis-systemby-component entry. -
Auto-fill from CDEFs (if selected) -- matches CDEF controls to SSP controls by NIST ID, populates
implementation_statementfrom the CDEF'simplementation_narrative, upgrades status from "Deferred" to "Implemented" when narrative is present, and creates by-component entries. -
Mark complete -- sets
status: "completed".
Control IDs are normalized for matching: lowercased, whitespace replaced with hyphens, parentheses converted to dots, and multiple dots collapsed.
- PR #60, Issue #30
SSP Enrichment allows users to uplift legacy file-imported SSPs with full OSCAL metadata that cannot be expressed in the original tabular format.
The SSP enrichment UI (enrich / update_enrich actions in SspDocumentsController) supports adding and editing:
- System-level fields: system name, description, system ID, system status, date authorized, security sensitivity level, security objectives (C/I/A), authorization boundary description, network architecture description, data flow description.
- Components: type, title, description, status state, status remarks, purpose, responsible roles, protocols, props, links, remarks.
- Users: UUID, title, description, short name, role IDs, authorized privileges, props, links, remarks.
- Information types: title, description, categorizations, confidentiality/integrity/availability impact (base, selected, adjustment justification).
- Leveraged authorizations: title, party UUID, date authorized.
The controller includes sync helpers that handle create/update/delete of nested OSCAL entities. Each entity section (components, users, information types) can be managed independently, with changes applied via the update_enrich action.
The OscalSspExportService works uniformly -- it uses enriched relational data when available regardless of whether the SSP was created via wizard or file import. File-imported SSPs that have been enriched via the UI get proper exports with the enriched data, while un-enriched SSPs continue to export valid OSCAL with sensible defaults.
Models: app/models/control_mapping.rb, app/models/control_mapping_entry.rb
Control Mapping provides cross-walk functionality between two control catalogs, such as NIST SP 800-53 Rev 4 to Rev 5, or NIST to ISO 27001.
ControlMapping represents the overall mapping collection:
belongs_to :source_catalog, class_name: "ControlCatalog"
belongs_to :target_catalog, class_name: "ControlCatalog"
has_many :control_mapping_entries, dependent: :destroyValidated attributes:
-
name-- required. -
uuid-- auto-generated, unique. -
status-- one of:draft,complete,not-complete,deprecated,superseded. -
method_type-- one of:human,automation,hybrid. -
matching_rationale-- one of:syntactic,semantic,functional.
ControlMappingEntry represents an individual source-to-target control relationship:
-
source_control_idandtarget_control_id-- required. -
source_typeandtarget_type-- either"control"or"statement". -
relationship-- aligned with NIST IR 8477 set-theory relationships:equal,equivalent,subset,superset,intersects. - Unique constraint on
(control_mapping_id, source_control_id, target_control_id).
Mappings follow a lifecycle with status transitions:
- Draft -- initial state for authoring.
- Complete (publish) -- mapping is finalized and ready for use.
- Deprecated / Superseded -- end-of-life states.
The published scope returns mappings with status: "complete".
Mappings can be exported to OSCAL mapping-collection JSON via OscalMappingExportService.
- PR #118 (Issue #98), Issue #119
Models: app/models/evidence.rb, app/models/attestation.rb, app/models/evidence_control_link.rb
Evidence records support uploading compliance artifacts with Active Storage. Each evidence record tracks:
-
Evidence types (enum):
artifact,screenshot,log,config_export,scan_result,signed_statement,policy_document,test_result. -
Status lifecycle (enum):
draft->collected->reviewed->attested->expired. -
File integrity: SHA256 hash computed via
compute_file_hash!for verification:
def compute_file_hash!
return unless file.attached?
self.file_hash = Digest::SHA256.hexdigest(file.download)
self.file_content_type = file.content_type
self.original_filename = file.filename.to_s
self.file_size = file.byte_size
save!
endThe EvidenceControlLink model links evidence to specific controls across any document type:
DOCUMENT_TYPES = %w[SspDocument SarDocument SapDocument CdefDocument PoamDocument]Each link stores control_id, document_type, and document_id, with a uniqueness constraint on the combination. This enables a single piece of evidence to be associated with controls across multiple documents.
The Attestation model provides formal verification by authorized personnel:
-
attester_name-- required. -
statement-- the attestation text (required). -
attested_at-- timestamp (required). -
role-- one of:control_owner,system_owner,isso,ciso,assessor,authorizing_official. -
attester_email-- email address of the attester. -
signature_hash-- SHA256 hash generated from a payload of attester details, statement, timestamp, and evidence ID:
def generate_signature!
payload = "#{attester_name}|#{attester_email}|#{statement}|#{attested_at.iso8601}|#{evidence_id}"
self.signature_hash = Digest::SHA256.hexdigest(payload)
save!
end- PR #75 (Issue #31)
Models: app/models/authorization_boundary.rb, app/models/boundary.rb, app/models/authorization_boundary_membership.rb
Authorization boundaries serve as the top-level container for compliance artifacts, organizing them around a system boundary.
An authorization boundary aggregates:
-
ssp_document(has_one) -
sap_document(has_one) -
sar_document(has_one) -
poam_documents(has_many) -
evidences(has_many) -
boundaries(has_many) -
authorization_boundary_memberships(has_many)
Status (enum): draft, active, authorized, deauthorized.
The artifact_summary method provides a quick overview:
def artifact_summary
{
ssp: ssp_document&.name,
sap: sap_document&.name,
sar: sar_document&.name,
poam_count: poam_documents.count,
boundary_count: boundaries.count,
component_count: boundaries.joins(:cdef_documents).count
}
endSystem boundaries define network and security perimeters. Each boundary:
- Belongs to an authorization boundary.
- Has an
environmentenum:production,development,staging,test. - Links to Component Definitions (CDEFs) via the
BoundaryCdefDocumentjoin table.
AuthorizationBoundaryMembership records assign team members to authorization boundaries with specific roles:
| Role Key | Display Label |
|---|---|
authorizing_official |
Authorizing Official (AO) |
system_owner |
System Owner (SO/ISO) |
ciso |
CISO |
isso |
ISSO |
project_member |
Team Member |
assessor |
Assessor / 3PAO |
view_only |
View Only |
Memberships can be linked to User records by matching email via link_to_user!.
- PR #71 (Issue #46)
Models: app/models/audit_event.rb
Concern: app/controllers/concerns/auditable.rb
Export: app/services/audit_csv_export_service.rb
Admin UI: app/controllers/admin/audit_logs_controller.rb
-
Immutable --
AuditEventhas noupdated_atcolumn and no update methods. Records are write-once. -
Subject tracking -- polymorphic
subject_type/subject_idcolumns record which resource was affected. -
Structured JSON logging -- every event is also emitted to
Rails.logger.infoas structured JSON for integration with CloudWatch, Datadog, or any log aggregator.
Approximately 80 actions are tracked across 16 categories:
| Category | Example Actions |
|---|---|
| Authentication |
login_success, login_failure, logout, password_change
|
| Authorization | authorization_failure |
| User Management |
user_suspended, user_reactivated, admin_bootstrap
|
| Role Management |
role_grant, role_revoke, role_created, role_updated, role_deleted
|
| Team Members |
project_member_added, project_member_removed, authorization_boundary_membership_*
|
| SSP Documents |
ssp_document_created, _updated, _deleted, _exported, _imported
|
| SAR Documents |
sar_document_created, _updated, _deleted, _exported, _imported
|
| CDEF Documents |
cdef_document_created, _updated, _deleted, _exported, _copied
|
| SAP Documents |
sap_document_created, _updated, _deleted, _exported, _imported
|
| POAM Documents |
poam_document_created, _updated, _deleted, _exported, poam_item_*
|
| Profiles |
profile_document_created, _updated, _deleted, _exported, _copied
|
| Control Catalogs |
control_catalog_*, control_family_*, catalog_control_*
|
| Control Mappings |
control_mapping_*, mapping_entry_*
|
| Evidence |
evidence_created, _updated, _deleted, attestation_created, _deleted
|
| Authorization Boundaries |
authorization_boundary_created, _updated, _deleted, boundary_*
|
Controllers include the Auditable concern for a DRY logging helper that automatically captures the current user, IP address, and user agent:
audit_log("ssp_document_created", subject: @ssp_document,
metadata: { name: @ssp_document.name, file_type: file_type })Authorization failures are logged automatically.
The AuditEvent.log class method creates the record and emits the structured JSON log:
AuditEvent.log(
user: current_user,
action: "login_success",
provider: "local",
ip_address: request.remote_ip,
subject: @ssp_document
)The admin audit log interface is available at /admin/audit_logs and supports:
- Filtering by user, action, subject type, category, date range, and free-text search.
- Pagination at 50 events per page via Pagy.
-
CSV export up to 10,000 rows via
AuditCsvExportService, with columns: timestamp, user_email, action, category, subject_type, subject_id, ip_address, user_agent, metadata. - Detail view for individual events.
- PR #121, PR #122 (Issue #101)
Stimulus Controller: app/javascript/controllers/heatmap_controller.js
Aggregation Service: app/services/dashboard_aggregation_service.rb
Interactive heatmap grids provide visual analytics showing control distribution by NIST family and a secondary dimension (status, severity, result, or priority).
| Document Type | Secondary Dimension | Example Values |
|---|---|---|
| SSP | Status | Implemented, Deferred, Not Applicable, Planned, Partially Implemented |
| SAR | Result | Pass, Fail |
| CDEF | Severity | High, Medium, Low |
| Profile | Priority | P1, P2, P3 |
| Dashboard | Status (aggregate) | Compliance across all SSP documents |
The DashboardAggregationService aggregates implementation status counts across ALL SSP documents, grouped by NIST control family (AC, AU, CM, IA, etc.). It returns a triple of [heatmap_data, families, ordered_statuses] matching the contract expected by the shared _heatmap.html.erb partial.
The heatmap_controller.js Stimulus controller provides client-side interactivity:
-
Cell click (
filterByCell) -- filters controls by both family and status/severity. -
Family click (
filterByFamily) -- filters controls by family only. -
Chip click (
filterByChip) -- filters by status/severity only. - Clear -- resets all filters.
- Visual feedback -- active cells are highlighted with an outline; inactive cells fade to 35% opacity.
- Banner -- displays "Showing: AC . Implemented -- 12 control(s)" when a filter is active.
-
URL sync -- optionally pushes filter state into the URL via
history.replaceState. - Keyboard navigation -- Enter/Space activates cells; Escape clears the filter.
-
ARIA support -- updates
aria-pressedstates on badge elements.
Configuration values:
-
filterKey--"status"or"severity"(the secondary dimension). -
initialFamily/initialFilter-- pre-applied filters from URL parameters. -
urlSync-- boolean to enable URL synchronization. -
containerId-- ID of the controls container element (default:"controlsContainer").
- Issue #81 (PR #82), Issue #83 (PR #84)
File: app/services/document_duplication_service.rb
The DocumentDuplicationService clones ProfileDocument and CdefDocument records with all their associated controls and fields.
SUPPORTED_TYPES = {
"ProfileDocument" => { controls: :profile_controls, fields: :profile_control_fields, version_attr: :profile_version },
"CdefDocument" => { controls: :cdef_controls, fields: :cdef_control_fields, version_attr: :cdef_version }
}-
Build document copy -- copies all attributes except
id, timestamps,status,error_message,original_filename,file_type. Setsstatus: "completed". -
Set import_metadata -- records
copied_from(source document ID) andcopied_at(timestamp) for provenance tracking. - Copy controls -- iterates through source controls and their fields, creating new records linked to the copy.
- All operations run within a single transaction.
service = DocumentDuplicationService.new(source_profile)
copy = service.duplicate(new_name: "FY26 Baseline")
copy.import_metadata
# => { "copied_from" => 42, "copied_at" => "2026-03-08T..." }Profiles can also be created from catalog control selection, producing a new ProfileDocument with selected controls.
- PR #76 (Issue #56)
File: app/services/sap_generator_service.rb
The SapGeneratorService generates a Security Assessment Plan (SAP) from existing SSP and/or Profile data.
SapGeneratorService.new(
name: "FY26 Annual Assessment",
ssp_document: ssp,
profile_document: profile,
assessment_type: "annual",
assessment_start: Date.today,
assessment_end: Date.today + 30,
selected_control_ids: ["AC-1", "AC-2"],
assessment_methods: { "AC-1" => "examine", "AC-2" => "test" }
).generateControls are gathered from the SSP (preferred) or Profile (fallback). When using an SSP, implementation status and description are carried forward.
Each control is assigned a default assessment method based on its family:
| Method | Families |
|---|---|
| Test | AC, AU, CM, IA, SC, SI |
| Interview | AT, PS, PE |
| Examine | All other families (CP, IR, MA, etc.) |
The service enriches generated controls with data from two sources:
-
Catalog guidance -- looks up
CatalogControlrecords to populate assessment objectives and descriptions. -
CDEF test mappings -- looks up
CdefControlrecords withcheck_contentfields. If a control has an "examine" method but has available check content from a CDEF, the method is upgraded to "test".
Creates a SapDocument with status: "completed", linked SapControl records (each with assessment method, status "planned", objective, and test case), and associated SapControlField records for implementation details.
- Issue #28
File: app/services/json_export_service.rb
The JsonExportService provides universal JSON export for all six document types. It delegates to each model's to_json_data method:
JsonExportService.export_ssp(ssp_document)
JsonExportService.export_sar(sar_document)
JsonExportService.export_cdef(cdef_document)
JsonExportService.export_profile(profile_document)
JsonExportService.export_sap(sap_document)
JsonExportService.export_poam(poam_document)Each export produces pretty-printed JSON via JSON.pretty_generate.
Namespace: /api/v1/
Controller: app/controllers/api/v1/ssp_documents_controller.rb
The API provides programmatic access to document operations.
| Endpoint | Method | Description |
|---|---|---|
/api/v1/ssp_documents/convert |
POST | Upload and convert a document file |
/api/v1/ssp_documents/:id/update_fields |
PUT | Bulk update control fields |
/api/v1/ssp_documents/:id/export |
GET | Export document as JSON |
Accepts an uploaded file parameter. Creates a temporary file, processes it through the document conversion pipeline, and returns the parsed JSON data along with the document_id.
{
"success": true,
"message": "Conversion successful",
"data": { ... },
"document_id": 42
}Accepts a controls parameter with field updates. Uses SspUpdateService for bulk updates.
Returns the document's JSON representation via JsonExportService.export_ssp.
SAR documents have a parallel set of API endpoints: convert (POST), update_fields (PUT), export (GET).
For web UI uploads, the conversion runs asynchronously via DocumentConversionJob. The convert API endpoint returns a document_id that can be used to poll the status endpoint for completion.
Stimulus Controller: app/javascript/controllers/theme_controller.js
Stylesheet: app/assets/stylesheets/sparc-theme.css
On first visit (no saved preference), the theme follows the OS setting via prefers-color-scheme media query:
const systemDark = window.matchMedia("(prefers-color-scheme: dark)").matches
document.documentElement.setAttribute("data-bs-theme", systemDark ? "dark" : "light")Clicking the theme toggle button saves the preference to localStorage under the sparc-theme key. Once a manual override is set, OS preference changes are ignored:
toggle() {
const current = document.documentElement.getAttribute("data-bs-theme")
const next = current === "dark" ? "light" : "dark"
localStorage.setItem("sparc-theme", next)
document.documentElement.setAttribute("data-bs-theme", next)
}If the user has not manually toggled, OS preference changes are tracked via MediaQueryList.addEventListener("change", ...) and applied automatically.
An inline <script> in the <head> reads localStorage and sets the data-bs-theme attribute on <html> before the first paint, preventing a flash of the wrong theme.
Uses Bootstrap 5.3 color mode support via the data-bs-theme attribute on the root <html> element. Custom SPARC theme overrides are defined in sparc-theme.css for brand-specific colors and component styling.
- Issue #85 (PR #86), Issue #51 (PR #62)
SPARC enforces encrypted communications in production per NIST SP 800-53 SC-8 (Transmission Confidentiality and Integrity) while allowing plain HTTP for local development.
Configured in config/environments/production.rb:
| Setting | Value | Purpose |
|---|---|---|
config.assume_ssl |
true |
Trust X-Forwarded-Proto from reverse proxy |
config.force_ssl |
ENV.fetch("FORCE_SSL", "true") |
Redirect HTTP to HTTPS, set secure cookies |
HSTS max-age
|
1 year | Browsers remember to use HTTPS |
HSTS subdomains
|
true |
Covers all subdomains |
HSTS preload
|
true |
Eligible for browser preload lists |
| Health-check bypass |
/up excluded |
Container probes (ALB, K8s) use HTTP internally |
Set FORCE_SSL=false to disable (e.g., behind a proxy that already handles HTTPS).
config/initializers/security_headers.rb sets defence-in-depth headers on every response:
| Header | Value | Purpose |
|---|---|---|
X-Content-Type-Options |
nosniff |
Prevent MIME-type sniffing |
X-Frame-Options |
SAMEORIGIN |
Clickjacking protection |
Referrer-Policy |
strict-origin-when-cross-origin |
Control Referer leakage |
Permissions-Policy |
camera=(), microphone=(), geolocation=() |
Restrict unused browser APIs |
X-Permitted-Cross-Domain-Policies |
none |
Block Flash/Acrobat cross-domain loading |
config/initializers/content_security_policy.rb configures CSP in report-only mode. Violations appear in the browser console but do not block content. The Bootstrap 5.3 CDN (cdn.jsdelivr.net) is allowlisted for scripts, styles, and fonts.
To switch to enforcing mode set config.content_security_policy_report_only = false.
Development mode (Rails.env.development?) does not set force_ssl, so http://localhost:3000 works without certificates.
The Docker image exposes port 80 (HTTP). HTTPS termination is handled at the reverse proxy / load balancer layer (Nginx, Traefik, ALB). The /up health-check endpoint responds over HTTP so internal probes work without TLS.
The application version is centralized in SparcConfig::VERSION (defined in app/models/sparc_config.rb). Both layout files reference it dynamically via <%= SparcConfig::VERSION %>.
- Issue #106
Converters translate external framework identifiers into NIST SP 800-53 control IDs so that findings expressed in another vocabulary can be mapped into SPARC's control model. They are managed from /converters and are independently refreshable by operators with the converters.write permission.
| Converter | Refresh action | Source |
|---|---|---|
| DISA CCI → NIST | POST /converters/:id/refresh_cci |
DISA CCI list |
| AWS Config → NIST |
POST /converters/:id/refresh_aws_config (#494) |
AwsConfigRefreshService |
| AWS Security Hub → NIST |
POST /converters/:id/refresh_aws_security_hub (#494) |
AwsSecurityHubRefreshService |
| STIG benchmark |
GET /converters/stig_parser, POST /converters/import_stig
|
StigConverterService |
Each converter owns a set of ConverterEntry rows (resources :converter_entries) holding the individual source-ID → NIST-control-ID pairs. Converters seed via db/seeds/converters.rb under SeedRunner.run_section("converters") — bump SeedRunner::CURRENT_VERSIONS["converters"] when editing those seeds or production silently skips the change (see Changelog v1.6.6).
A converter mapping can be bulk-applied to a CDEF via the CDEF bulk_apply_converter preview/apply flow (v1.8.0).
SPARC can ingest OSCAL Component Definitions published by AWS Labs at runtime (#466). The AwsLabsCdefImportService (via AwsLabsCdefSourceClient) pulls CDEFs from the configured repo/branch.
-
Recurring job:
AwsLabsCdefRefreshJobruns on a multi-day Solid Queue schedule (interval viaSPARC_AWS_LABS_CDEF_INTERVAL_DAYS). -
Bootstrap-on-first-deploy: when
SPARC_AWS_LABS_CDEF_ENABLED=trueand no AWS-Labs-sourced rows exist, the first boot enqueues a refresh so tenants don't wait for the weekly tick (#487). -
Manual refresh:
POST /cdef_documents/refresh_aws_labs(admin button). - AWS Labs CDEFs use Security Hub control IDs (e.g.
IAM.1,S3.5); the converters above bridge them to NIST (#491).
Three stateless /api/v1/ endpoints let tenant compliance pipelines move scan data between the HDF (Heimdall Data Format) and OSCAL ecosystems without running the hdf CLI themselves (#449). The hdf binary is baked into the SPARC container.
| Endpoint | Direction |
|---|---|
POST /api/v1/oscal/sar_from_hdf |
HDF results → OSCAL Assessment Results (SAR) |
POST /api/v1/oscal/poam_from_hdf |
HDF results → OSCAL POA&M |
POST /api/v1/hdf/amendments_from_oscal_poam |
OSCAL POA&M → HDF Amendments JSON |
Passing ?authorization_boundary_id=N to either OSCAL emission endpoint merges the boundary's Evidence + Attestation records into the output as back-matter.resources[] (requires evidence.read on the boundary). Implemented by HdfOscalTranslationService + HdfRunner.
SPARC tracks Key Security Indicators (KSIs) — FedRAMP 20x machine-checkable indicators grouped into themes.
-
Read-only catalog:
GET /api/v1/ksi_catalog/themes,GET /api/v1/ksi_catalog/indicators(seeded fromdb/seeds/fedramp_20x_ksi.rb). -
Validations:
KsiValidationrecords are nested under an authorization boundary (resources :ksi_validations), withsummaryandexportcollection actions. -
Export:
KsiExportServiceemits the boundary's KSI validation state.
The authoritative-source system (#372) lets boundaries draw OSCAL back-matter resources from a trusted upstream library, and lets SPARC instances share those libraries peer-to-peer.
-
Authoritative sources:
GET /authoritative_sources— browse the shared back-matter library. -
Promotion queue:
resources :promotion_queueplus per-resourcepromote/approve_promotion/reject_promotionactions move a candidate resource through review into the authoritative library, withBackMatterResourceChangeaudit rows. -
Federation peers:
resources :federation_peerswith asyncaction exchange HMAC-signed OSCAL bundles between instances. Signing uses theSPARC_HASH-derived key material (FederationBundleSigningService); peer key rotation is handled byFederationPeerReencryptionService.
SPARC is a translation engine + UI for OSCAL / policy-as-code, not a system of record — tenant systems own the source of truth; federation shares references, not authority.
A leveraging system can inherit an authorization (and its provider-implemented controls) from an underlying system such as a cloud platform.
-
Create on the leveraging boundary:
resources :leveraged_authorizations(new/create/show/destroy), with apopulateaction that pulls inherited implementation data into the leveraging SSP (#396). -
Read-only leveraged POA&Ms:
resources :leveraged_poam_documents(index/show) surface the leveraged system's POA&M items to the leveraging side (#415). - Implemented by
LeveragedAuthorizationService; inherited rows appear as provider statements in the SSP control view.
Admin-namespace features for tenant and automation identity management.
-
Organizations:
resources :organizations(admin) — organization entities with UUID-based audit traceability for multi-org instances. -
Service accounts:
resources :service_accounts(admin) — non-interactive identities for API automation, each owning API tokens (resources :api_tokens,create/destroy). Tokens aresparc_sa_<token>Bearer credentials with a SHA-256 digest at rest. -
User lifecycle: admin users can be deactivated via
PATCH /admin/users/:id/deactivate. -
API session bridge:
POST /api/v1/sessions/from_tokenexchanges a service-account Bearer token (or OIDC JWT) for a Rails session cookie, enabling headless UI test automation (#573, v1.8.4).
See RBAC for how service accounts and roles interact, and Configuration for the SPARC_API_AUTH modes (token / jwt / hybrid).
An optional review-and-approval gate for documents and baselines, added in v1.9.0 (#640, #630–634). Off by default so existing publish flows are unchanged until an org opts in via SPARC_REQUIRE_DOCUMENT_APPROVAL.
-
Approvablemodel concern — makes a document type reviewable: it carries an approval state and transitions through submit → review → approve/reject. -
Review queue (
review_queue) — reviewers see documents awaiting their decision; promotion queue (promotion_queue) tracks items moving toward a published/authoritative state. -
Services:
DocumentApprovalServicedrives the approve/reject transitions with audit events;BaselineReviewServicehandles baseline/profile review specifically. -
Permission-gated — approval actions require the corresponding
*.approvepermission keys (catalogs.approve,profiles.approve,cdef.approve, and theback_matter.approve_promotiongate for back-matter promotion). See RBAC. -
Configuration:
SPARC_REQUIRE_DOCUMENT_APPROVAL(default off). When enabled, publishing requires an approved review.
See Screens for the review/promotion queue UI and RBAC for the approval permission keys.
Getting Started
User Guides
- User Guides (index)
- Getting Oriented
- Authorization Boundaries
- Control Catalogs & Baselines
- Converters & Imports
- System Security Plans (SSP)
- Component Definitions (CDEF)
- Security Assessment Plan (SAP)
- Security Assessment Results (SAR)
- POA&M
- Evidence & Attestations
- HDF Amendment Triage
- Compliance Library
- Security Keys & Smart Cards
- Administration
Documentation
- RBAC (Role-Based Access Control)
- Data Isolation
- Screens & UI
- Core Functions & Features
- Framework Mapping
- Integrations
- Architecture
- API Reference
Reference
Links