This file provides comprehensive context for AI assistants (Claude, ChatGPT, etc.) working on the GraphGRC codebase. It enables effective collaboration without repeated explanations.
GraphGRC is a Go-based documentation generator that creates interconnected Markdown documentation for GRC (Governance, Risk, and Compliance) programs. It maps controls across multiple compliance frameworks using the Secure Controls Framework (SCF) as a unified reference model.
- Language: Go 1.21
- Repository: https://github.com/engseclabs/graphgrc/
- Published Site: https://engseclabs.github.io/graphgrc/
- License: MIT
Organizations implementing security programs must comply with multiple frameworks simultaneously (SOC 2, ISO 27001, GDPR, NIST 800-53). Each framework has hundreds of controls with significant overlap. GraphGRC:
- Maps relationships between similar controls across frameworks
- Creates navigable documentation showing how one security control can satisfy multiple framework requirements
- Reduces complexity by using SCF as a single unified control set that maps to all frameworks
┌─────────────────────────────────────────┐
│ External Data Sources │
│ (SCF Excel, Framework JSON/Markdown) │
└────────────────┬────────────────────────┘
│ EXTRACT
▼
┌─────────────────────────────────────────┐
│ Transform & Normalize │
│ (Parse Excel/JSON/MD → Go structs) │
└────────────────┬────────────────────────┘
│ TRANSFORM
▼
┌─────────────────────────────────────────┐
│ SCF Mapping Engine │
│ (Create bidirectional mappings) │
└────────────────┬────────────────────────┘
│ MAP
▼
┌─────────────────────────────────────────┐
│ Markdown Generation │
│ (1000+ interconnected .md files) │
└────────────────┬────────────────────────┘
│ GENERATE
▼
┌─────────────────────────────────────────┐
│ GitHub Pages Deploy │
│ (Static site hosting) │
└─────────────────────────────────────────┘
- Hub: SCF (576 controls in 30 families)
- Spokes: SOC 2, GDPR, ISO 27001, ISO 27002, NIST 800-53
- Linking: Bidirectional - frameworks → SCF → frameworks
- Extract: Download framework data from external URLs (or use cached JSON)
- Transform: Parse diverse formats (Excel, JSON, Markdown) into normalized Go structures
- Map: Create cross-references using SCF as the mapping layer
- Generate: Produce 1000+ Markdown files with bidirectional hyperlinks
- Deploy: Publish via GitHub Actions to GitHub Pages
graphgrc/
├── main.go # Entry point - orchestrates entire pipeline
├── go.mod # Go module definition
├── go.sum # Dependency checksums
│
├── internal/ # Core processing logic
│ ├── scf.go # SCF Excel parsing & core mapping engine (326 LOC)
│ ├── soc2.go # SOC 2 JSON processing (159 LOC)
│ ├── gdpr.go # GDPR Markdown parsing (178 LOC)
│ ├── iso.go # ISO 27001/27002 JSON processing (157 LOC)
│ ├── nist80053.go # NIST 800-53 OSCAL JSON processing (305 LOC)
│ └── file.go # Filename sanitization utilities (12 LOC)
│
├── scf.xlsx # SCF 2023.4 controls (4.6MB Excel file)
├── *.json # Cached framework data (6 files)
│
├── scf/ # Generated SCF docs (576 files)
│ └── index.md # SCF control family index
├── soc2/ # Generated SOC 2 docs (59 files)
├── gdpr/ # Generated GDPR docs (55 files)
├── iso27001/ # Generated ISO 27001 docs (10 files)
├── iso27002/ # Generated ISO 27002 docs (7 files)
├── nist80053/ # Generated NIST 800-53 docs (326 files)
│
├── .github/workflows/ # CI/CD
│ └── publish.yml # GitHub Pages deployment
│
├── README.md # User documentation
└── CLAUDE.md # This file - AI engineering guide
Total Generated Output: 1,033 Markdown files, ~44MB
require (
github.com/xuri/excelize/v2 // Excel file parsing for SCF
github.com/go-spectest/markdown // Markdown generation (forked version)
)| Framework | Format | Source |
|---|---|---|
| SCF | Excel (XLSX) | https://securecontrolsframework.com/ |
| SOC 2 | JSON | Prowler cloud (prowler-cloud/prowler) |
| GDPR | Markdown | EnterpriseReady |
| ISO 27001/27002 | JSON | JupiterOne security-policy-templates |
| NIST 800-53 | JSON (OSCAL) | GSA FedRAMP automation |
// Framework identifier (e.g., "SOC 2", "GDPR", "ISO 27001")
type Framework string
// Column header from framework Excel/JSON (e.g., "Control ID", "Description")
type ControlHeader string
// String value for a control field
type ControlValue string
// Control ID (e.g., "IAC-01", "CC6.1", "Article 5")
type ControlID string
// Flexible control representation - map of headers to values
type Control map[ControlHeader]ControlValue
// Core mapping structure: SCF Control ID → Frameworks → Framework Control IDs
// Example: "IAC-01" → { "SOC 2" → ["CC6.1", "CC6.2"], "ISO 27001" → ["A.9.2.1"] }
type SCFControlMappings map[ControlID]map[Framework][]ControlIDscfControlMappings := map[string]map[string][]string{
"IAC-01": {
"SOC 2": []string{"CC6.1", "CC6.2"},
"ISO 27001": []string{"A.9.2.1"},
"NIST 800-53": []string{"IA-2"},
},
}This structure enables bidirectional lookups:
- Given SCF control → find all related framework controls
- Given framework control → find related SCF control (via reverse lookup)
Location: internal/scf.go lines 62-70
var SupportedFrameworks = map[Framework]ControlHeader{
"SOC 2": "AICPA TSC 2017 (Controls)",
"GDPR": "EMEA EU GDPR",
"ISO 27001": "ISO 27001 v2022",
"ISO 27002": "ISO 27002 v2022",
"NIST 800-53": "NIST 800-53 rev5 (moderate)",
// "ISO 27701": "ISO 27701 v2019", // Available but disabled
// "HIPAA": "US HIPAA", // Available but disabled
}How to Use:
- The key is the framework name used throughout the codebase
- The value is the exact column header in
scf.xlsxthat contains the framework's control mappings - To enable a framework: uncomment or add a line
- To disable a framework: comment out the line
Location: main.go line 12
getFile := false // true = download fresh data, false = use cached JSONSet to false during development to use cached JSON files for faster iteration.
Purpose: Orchestrates the entire generation pipeline sequentially.
Flow:
- Process SCF (hub) → Generate mappings
- Process SOC 2 → Link to SCF
- Process GDPR → Link to SCF
- Process ISO 27001 → Link to SCF
- Process ISO 27002 → Link to SCF
- Process NIST 800-53 → Link to SCF
Key Variables:
latestScfLink- URL to SCF Excel filegetFile- Download fresh data vs use cachescfControlMappings- The core mapping structure passed to all generators
Purpose: The brain of the operation. Handles SCF parsing and creates all cross-framework mappings.
Key Functions:
- Downloads/reads SCF Excel file
- Parses all rows into Control structs
- Returns 576 controls with all their metadata
- Iterates through all SCF controls
- For each enabled framework in
SupportedFrameworks:- Extracts framework control IDs from the SCF Excel column
- Builds the mapping: SCF ID → Framework → [Control IDs]
- Returns the complete bidirectional mapping structure
- Creates individual SCF control page (e.g.,
scf/iac-01-user-identification.md) - Includes control description, objective, guidance
- Adds "Mapped Framework Controls" section with links to related framework controls
- Example: IAC-01 page links to SOC 2 CC6.1, ISO 27001 A.9.2.1, etc.
- Creates
scf/index.mdorganized by control families - Groups controls: AST (Asset Management), BCD (Business Continuity), IAC (Identity), etc.
- 30 families total
Important Constants:
SCFControlID- Header for SCF control IDsSCFControlFamilyTitle- Header for family names- Control family codes: AST, BCD, CPL, CRY, DCH, END, GOV, HRS, IAC, IAM, IAO, etc.
Purpose: Parse SOC 2 controls from Prowler JSON format and generate linked documentation.
Key Functions:
- Downloads JSON from Prowler cloud
- Parses into SOC2Framework struct with Requirements array
- Each requirement has: ID, Description, Attributes (trust service criteria)
- Creates individual SOC 2 control page (e.g.,
soc2/cc6.1.md) - Parses multi-section descriptions (headers like "Description:", "Criteria:")
- Reverse mapping: Searches scfMappings to find which SCF controls map to this SOC 2 control
- Adds "Related SCF Controls" section with links back to SCF
- Creates
soc2/index.mdwith all SOC 2 controls listed
Data Structure:
type SOC2Framework struct {
Framework string
Requirements []Requirement
}
type Requirement struct {
Id string
Description string
Attributes []Attribute
}Purpose: Parse GDPR articles from Markdown source and generate linked documentation.
Key Functions:
- Downloads Markdown from EnterpriseReady
- Parses hierarchical structure: Articles contain sub-articles
- Example: Article 5 has sub-articles 5.1(a), 5.1(b), etc.
- Creates article page (e.g.,
gdpr/article-5.md) - Includes all sub-articles with anchor links
- Reverse mapping: Links back to related SCF controls
- Creates
gdpr/index.mdwith article listing
Data Structure:
type GDPRArticle struct {
Title string
ControlNumber string
ControlTitle string
Text string
SubArticles []GDPRArticle // Recursive for hierarchical structure
}Purpose: Parse ISO controls from JupiterOne JSON and generate linked documentation.
Key Functions:
- Downloads JSON from JupiterOne
- Parses domains (organizational categories)
- Handles both ISO 27001 (requirements) and ISO 27002 (controls)
- Creates domain page (e.g.,
iso27001/a.5-organizational-controls.md) - Lists all controls in the domain
- Reverse mapping: Links to related SCF controls
- Converts Framework Control ID to Annex reference
- Example: "iso-27001_a.5.1" → "Annex A.5.1"
Data Structure:
type ISOFramework struct {
Domains []ISODomain
}
type ISODomain struct {
Title string
Ref string
Controls []ISOControl
}
type ISOControl struct {
Ref string
Title string
Description string
}Purpose: Parse NIST 800-53 controls from FedRAMP OSCAL JSON and generate linked documentation.
Key Functions:
- Downloads OSCAL-formatted JSON from GSA FedRAMP
- Parses control families (AC, AT, AU, CA, etc.)
- Handles hierarchical controls (e.g., AC-1, AC-1(1), AC-1(2) are parent and sub-controls)
- Creates control page (e.g.,
nist80053/ac-1.md) - Includes control statement, guidance, parameters
- Lists sub-controls (enhancements)
- Reverse mapping: Links to related SCF controls
- Creates
nist80053/index.mdorganized by control families
Data Structure (OSCAL-based):
type NIST80053 struct {
Families []NISTFamily
}
type NISTFamily struct {
Title string
Controls []NISTControl
}
type NISTControl struct {
ID string
Title string
Parts []NISTPart // Statements, guidance
Controls []NISTControl // Sub-controls (recursive)
Params []NISTParam // Configurable parameters
}Purpose: Filename sanitization for filesystem compatibility.
- Converts to lowercase
- Removes special characters
- Replaces spaces with hyphens
- Ensures valid filesystem names across platforms
| Code | Family Name |
|---|---|
| AST | Asset Management |
| BCD | Business Continuity & Disaster Recovery |
| CPL | Compliance |
| CRY | Cryptography |
| DCH | Data Classification & Handling |
| END | Endpoint Security |
| GOV | Governance |
| HRS | Human Resources Security |
| IAC | Identification & Authentication |
| IAM | Identity & Access Management |
| IAO | Incident Response, Continuity of Operations Planning & Disaster Recovery |
| MDM | Mobile Device Management |
| NET | Network Security |
| PRI | Privacy |
| RSK | Risk Management |
| SDA | Secure Engineering & Architecture |
| SEA | Security Assessment |
| STA | Secure Systems Administration |
| TDA | Technology Development & Acquisition |
| THR | Threat Management |
| TPS | Third-Party Management |
| TPM | Training, Awareness & Education |
| VPM | Vulnerability & Patch Management |
| WEB | Web Security |
| ... | (and 6 others) |
go run main.goThis will:
- Read SCF Excel file (or download if missing)
- Download/read cached framework data
- Generate 1000+ Markdown files in framework directories
- Create index files for each framework
Steps:
-
Update
SupportedFrameworksmap ininternal/scf.go:var SupportedFrameworks = map[Framework]ControlHeader{ "SOC 2": "AICPA TSC 2017 (Controls)", "HIPAA": "US HIPAA", // <-- Add this line }
-
Create new processor file
internal/hipaa.go:package internal func GetHIPAAControls(url string, getFile bool) (HIPAAFramework, error) { // Download and parse HIPAA data } func GenerateHIPAAMarkdown(control HIPAAControl, scfMappings SCFControlMappings) { // Generate HIPAA control pages with SCF links } func GenerateHIPAAIndex(framework HIPAAFramework) { // Generate hipaa/index.md }
-
Add to
main.gopipeline:hipaaLink := "https://example.com/hipaa.json" hipaaFramework, err := internal.GetHIPAAControls(hipaaLink, getFile) if err != nil { log.Fatal(err) } for _, control := range hipaaFramework.Controls { internal.GenerateHIPAAMarkdown(control, scfControlMappings) } internal.GenerateHIPAAIndex(hipaaFramework)
-
Run:
go run main.go
- Download new SCF Excel file from https://securecontrolsframework.com/
- Replace
scf.xlsxin repository root - Update version references in documentation
- Run
go run main.goto regenerate all documentation
Edit SupportedFrameworks map in internal/scf.go:
- Disable: Comment out the line
- Enable: Uncomment the line
Example:
var SupportedFrameworks = map[Framework]ControlHeader{
"SOC 2": "AICPA TSC 2017 (Controls)",
// "GDPR": "EMEA EU GDPR", // Disabled
"ISO 27001": "ISO 27001 v2022",
}Set getFile = false in main.go line 12:
getFile := false // Use cached *.json files instead of downloading- Check SCF Excel file - Verify the framework column header matches
SupportedFrameworksvalue exactly - Print mappings - Add debug logging in
GetComplianceControlMappings():fmt.Printf("SCF %s maps to %s: %v\n", scfID, framework, controlIDs)
- Verify control ID parsing - Check that framework control IDs are correctly extracted from SCF cells
Purpose: Reduce external API calls during development.
Implementation:
- All framework data can be cached as JSON files
getFileparameter controls download vs cache- SCF Excel file is manual (not auto-downloaded)
Trade-offs:
- Faster iteration when
getFile = false - Must manually update cache to get latest framework data
Pattern: Use typed constants for structure, maps for flexibility.
type Framework string // Typed for safety
type Control map[ControlHeader]ControlValue // Map for flexibilityRationale:
- Different frameworks have different fields
- Maps allow adding new fields without struct changes
- Type aliases provide compile-time safety
Pattern: Create mappings in both directions.
Implementation:
- Forward:
scf.gogenerates SCF pages with links to framework controls - Reverse: Each framework processor searches
scfControlMappingsto find which SCF controls reference it
Code Example:
// Forward (scf.go)
for framework, controlIDs := range mappings {
md.PlainText(fmt.Sprintf("- %s: ", framework))
for _, id := range controlIDs {
md.Link(id, fmt.Sprintf("../%s/%s.md", framework, id))
}
}
// Reverse (soc2.go)
for scfID, frameworkMappings := range scfMappings {
if soc2IDs, exists := frameworkMappings["SOC 2"]; exists {
for _, soc2ID := range soc2IDs {
if soc2ID == currentControlID {
md.Link(scfID, fmt.Sprintf("../scf/%s.md", scfID))
}
}
}
}Pattern: Use relative paths for portability.
Examples:
- From SCF to SOC 2:
../soc2/cc6.1.md - From SOC 2 to SCF:
../scf/iac-01.md - Within same framework:
./other-control.md
Benefits:
- Works locally and on GitHub Pages
- No hardcoded URLs
- Easy to move/deploy
Pattern: Errors bubble up to main() for centralized handling.
Implementation:
// main.go
soc2Framework, err := internal.GetSOC2Controls(soc2Link, getFile)
if err != nil {
log.Fatal(err) // Fail fast with clear error
}Rationale:
- Clean failure rather than partial generation
- Easy to identify which stage failed
- No need for complex error recovery
Pattern: Use data structures to declare behavior.
Example: SupportedFrameworks map declares which frameworks to process.
Benefits:
- Easy to add/remove frameworks
- Self-documenting code
- No need to modify multiple locations
After code changes, verify:
-
go run main.gocompletes without errors - All framework directories contain expected number of files
- Generated Markdown files have valid syntax
- Links work (open in Markdown preview)
- Index files are properly organized
- Bidirectional links are correct (SCF → framework and framework → SCF)
Problem: Missing framework controls in SCF mappings
Cause: Column header mismatch in SupportedFrameworks
Fix: Check scf.xlsx for exact column name
Problem: Broken links in generated Markdown
Cause: Filename sanitization or incorrect path construction
Fix: Verify safeFileName() logic and relative path format
Problem: Slow generation
Cause: Downloading fresh data every run
Fix: Set getFile = false to use cached JSON
Problem: Empty framework directory Cause: Download failure or JSON parsing error Fix: Check error logs, verify external URL accessibility
- Make code changes in
internal/*.go - Set caching:
getFile = falseinmain.go - Run:
go run main.go - Verify output: Check generated Markdown files
- Iterate: Repeat steps 1-4
- Final test: Set
getFile = trueand run full pipeline - Commit: Add changes to git (exclude generated .md files if desired)
Generated files: The scf/, soc2/, etc. directories contain generated output. You can:
- Option A: Commit them (current approach) for GitHub Pages
- Option B: Add to
.gitignoreand generate via CI/CD
Currently: Generated files ARE committed for GitHub Pages deployment.
Configuration:
- Workflow:
.github/workflows/publish.yml - Branch: Published from
mainbranch - Directory: Repository root (not
/docs)
Process:
- Commit generated Markdown files
- Push to GitHub
- GitHub Actions builds Jekyll site
- Site published at https://engseclabs.github.io/graphgrc/
Use any Markdown viewer or static site generator:
# Option 1: Python HTTP server
python -m http.server 8000
# Option 2: Jekyll (GitHub Pages locally)
bundle exec jekyll serve| Metric | Value | Notes |
|---|---|---|
| Total files generated | 1,033 | Includes all frameworks + indexes |
| Total size | ~44 MB | Includes all Markdown files |
| SCF controls | 576 | Core mapping layer |
| SCF control families | 30 | Organizational categories |
| Generation time | ~10-30 seconds | Depends on network, caching |
| Lines of Go code | ~1,137 LOC | Internal package only |
-
Add more frameworks:
- HIPAA (already in SCF Excel)
- ISO 27701 (already in SCF Excel)
- PCI DSS
- CIS Controls
- NIST Cybersecurity Framework
-
Enhanced output formats:
- JSON API for programmatic access
- Interactive web UI with search
- PDF export of full documentation
- Graph visualization of control relationships
-
Improved mapping:
- Fuzzy matching for similar controls
- Confidence scores for mappings
- Gap analysis (controls in framework not in SCF)
-
Developer experience:
- Unit tests for parsers
- Integration tests for full pipeline
- CLI flags for selective framework generation
- Progress bars for long operations
-
Data quality:
- Validate external URLs before processing
- Retry logic for downloads
- Schema validation for JSON inputs
- Diff checker for SCF updates
When working on this codebase:
- ✅ Read the relevant
internal/*.gofile before suggesting changes - ✅ Maintain the hub-and-spoke architecture (SCF as hub)
- ✅ Preserve bidirectional linking in both directions
- ✅ Follow Go conventions (error handling, naming)
- ✅ Update
SupportedFrameworkswhen adding frameworks - ✅ Test changes with
go run main.go - ✅ Keep filename sanitization consistent
- ✅ Use relative Markdown links
- ✅ Handle hierarchical controls (GDPR sub-articles, NIST sub-controls)
- ❌ Break the SCF mapping layer
- ❌ Remove bidirectional links
- ❌ Hardcode absolute URLs
- ❌ Skip error handling
- ❌ Modify
scf.xlsxprogrammatically (manual updates only) - ❌ Change generated file structure without updating links
- ❌ Add dependencies without justification
- ❌ Over-engineer simple functionality
- Explain the "why" - What problem does this solve?
- Show the impact - Which files need changes?
- Provide complete code - Don't use placeholders
- Consider backwards compatibility - Will existing links break?
- Test mentally - Walk through the data flow
- Check
SupportedFrameworksmap - is it enabled? - Verify column header in
scf.xlsxmatches exactly - Check if external URL is accessible
- Look for errors in console output
- Verify relative path format:
../framework/control.md - Check filename sanitization - special characters removed?
- Ensure control ID matches filename
- Test link locally with Markdown preview
- Open
scf.xlsxand find the control row - Check the framework column - is it populated?
- Verify control IDs are correctly formatted
- Look for parsing errors in
GetComplianceControlMappings()
- Set
getFile = falseto use cached data - Check network connectivity
- Verify external URLs are responsive
- Consider reducing number of enabled frameworks
- Current: SCF 2023.4
- Go: 1.21
- Initial Release: 2023
- SCF Official Site: https://securecontrolsframework.com/
- Published GraphGRC Site: https://engseclabs.github.io/graphgrc/
- Source Repository: https://github.com/engseclabs/graphgrc/
- Go Documentation: https://go.dev/doc/
This document is maintained as part of the GraphGRC project. When making significant changes to the codebase, please update this file to keep it accurate for future AI assistants and human developers.