Skip to content
sirosfoundationPublic

About

a GRC toolchain based on OSCAL and github

Topics

Resources

Security policy

Stars

0 stars

Watchers

1 watching

Forks

Repository files navigation

go-grc

Governance, Risk & Compliance (GRC) CLI for managing security compliance as code. Designed for the SirosID wallet platform but applicable to any project that tracks controls, audit findings, and framework mappings in YAML.

grc automates the full compliance lifecycle:

  1. Sync — create and update GitHub issues for each audit finding; discover linked PRs and collect evidence.
  2. Derive — compute control and mapping statuses bottom-up from findings and evidence (verified → in progress → to-do).
  3. Render — generate a complete Docusaurus compliance site with per-control pages, per-requirement pages for each framework, dashboards, finding summaries, risk register, and year-cycle calendar.
  4. Serve — run an HTTP server for the rendered site with optional GitHub webhook listener for automatic re-rendering on push.
  5. Export — produce an auditor-ready JSON evidence package.
  6. Status — print a read-only overview of current compliance posture.

Data model

grc works on a compliance repository with the following layout:

catalog/
  technical/        # Platform security controls (YAML)
  organizational/   # Operator controls (YAML)
  frameworks/       # Normative requirement text per framework
mappings/           # Framework → control cross-references (YAML)
  eudi-secreq.yaml  #   EUDI Wallet Security Requirements
  iso27001-annexa.yaml  #   ISO/IEC 27001:2022 Annex A
  gdpr.yaml         #   GDPR checklist
audits/             # Structured audit findings with evidence (YAML)
risk-register/      # Accepted risks with compensating controls (YAML)

Each control has an ID (e.g. SID-AUTH-01), a status (verified, in_progress, to_do), and references to source code, PRs, deployed endpoints, or external reports that serve as evidence.

Framework mappings

Mapping files cross-reference external requirements to internal controls. Supported frameworks:

Framework Standard Requirements
EUDI ENISA Wallet Security Requirements v0.5 85
FitCEM Nordic EUDIW Certification System – Wallet Instance FitCEM PP —
ISO 27001 ISO/IEC 27001:2022 Annex A 93
GDPR Regulation (EU) 2016/679 19
OWASP ASVS OWASP ASVS 4.0.3 Level 3 —
STRIDE Threat model mapping —

Risk register

Risks (accepted, transferred, monitoring, or draft proposals) are tracked in YAML files under risk-register/. Each risk links to a finding, documents compensating controls, residual severity, and review intervals. The rendered site shows an overview with severity badges and per-risk detail pages.

Every risk must have an owner: the person or role accountable for it, who approves the treatment plan and accepts the residual risk (ISO/IEC 27001:2022 6.1.2 c) 2), 6.1.3 e)). This is distinct from decision.reviewer (who assesses the analysis) and from the register-level owner (the team that owns the register file). grc validate and grc risk validate fail on a risk without one; grc risk list --owner NAME filters by it.

Projects can name their risk assessment methodology document with risk_register.methodology in .grc.yaml. It is treated as authoritative for the register: it is served to MCP clients as grc://risk/methodology, the MCP server instructions and risk_review prompt tell agents to check the register against it, and agents are told never to change the register's schema or assessment rules without the matching change to the document. grc validate fails if the configured file is missing. An accepted risk must record decision.owner_accepted_date (YYYY-MM-DD): when its owner accepted the residual risk and approved the treatment plan, distinct from the decision date and from decision.reviewer (the approving CISO). When a risk records consequence, likelihood and residual_likelihood, validation also checks that severity and residual_severity equal the level derived from them (consequence × likelihood: e.g. medium × possible = medium, medium × unlikely = low), so the recorded levels cannot drift from the assessment.

Every risk that is not a draft must also record a treatment_action: what is done to treat it, who does it, and its status. A draft may omit it but is validated if present.

treatment_action:
  action: "what is being done"            # required
  responsible: "person or role doing it"  # required
  status: open | in_progress | done       # required
  due_date: "YYYY-MM-DD"                  # optional target date
  completed_date: "YYYY-MM-DD"            # required when status is done; must not be set otherwise

It is rendered on the risk page (with the status in the index), included in grc risk list --format json and the grc://risk/register MCP resource, and grc risk summary counts risks by treatment status.

Year-cycle calendar

Recurring GRC activities (reviews, audits, penetration tests) can be loaded from an iCal (.ics) feed and projected onto a single canonical year. The rendered page shows a circular SVG year diagram and a month-by-month schedule.

Controls can reference calendar activities by a stable lookup key derived from the event title (e.g. DPIA Review → dpia-review) rather than calendar UIDs that may change over time.

Audits and internal assessments

Two different things, kept in separate directories, one directory each:

  • Assessments (assessments.dir, default assessments/) are internal. They exist to generate findings (and so tracking issues, privately) and to update the control catalog through grc derive. They are never rendered on the public profile.
    assessments/<name>/assessment.yaml   findings; header key `assessment:`
    assessments/<name>/soa.yaml          optional explicit statement of applicability
    assessments/<name>/references/       supporting material, never rendered
    
    Without a soa.yaml, an assessment's statement of applicability is implicit: the requirement catalogs it lists in assessment.catalogs (framework IDs from .grc.yaml). Flat assessments/<name>.yaml files work too.
  • Audits (audits.dir, default audits/) are conducted by an external auditor, usually after an assessment served as the pre-audit (audit.pre_audit: <assessment name>). A real audit has a more detailed, scoped statement of applicability and a published outcome:
    audits/<cycle>/audit.yaml      findings; header key `audit:` (auditor, pre_audit, ...)
    audits/<cycle>/soa.yaml        statement of applicability
    audits/<cycle>/result.yaml     the compliance result (certificate)
    audits/<cycle>/references/     supporting documents
    

Statement of applicability (soa: header plus requirements: rows of annex_a, title, applicable (true/false/partial), status (implemented/partial/gap/not_applicable), justification, evidence), rendered as a table. soa.status is draft, in_review or approved (with approved_date); an audit's statement is published only once approved. Statements of assessments are never public.

Result (result.yaml):

result:
  status: issued        # planned | pre_audit | in_audit | issued | not_issued | withdrawn
  scheme: ISO/IEC 27001:2022
  body: Certification Body AB
  certificate_id: CERT-123
  scope: Development process
  issued: "2026-11-01"
  valid_until: "2029-10-31"
  documents:
    - {title: Certificate (PDF), file: references/certificate.pdf}
    - {title: Auditor report, url: "https://example.org/report"}

A result is published only when status: issued; any other state, including a missing or unknown one, is internal. Documents given as file (pdf, png, jpg, svg, txt, inside the audit directory, no symlinks out of it) are copied to static/grc-audits/<cycle>/; url documents (https) are linked.

The public site shows, for each audit, only its issued result and approved statement, never findings, pre-audit links or assessments. The private site shows everything, marks unpublished parts with a banner, and lists assessments under Audits > Internal assessments. Pages and documents from an earlier render are removed when nothing is publishable any more. The evidence column of a statement points into the repository and is private only. grc validate checks statements, results (including document paths), pre_audit links and catalogs.

Status derivation

The derive command computes statuses bottom-up:

  • Finding → Control: a control with open findings is downgraded; a control whose findings are all resolved is upgraded to verified. If every one of those findings also carries evidence the control is additionally marked evidenced (a flag, not a separate status).
  • Control → Mapping: a mapping requirement whose controls are all verified is marked full; for result-style frameworks it is compliant only when they are also evidenced. Partial coverage yields partial.

Terminology: "verified" is the only control status for "implemented and confirmed". The old validated status is a deprecated alias, read as verified; "evidence" is tracked separately.

Policies

When the policies directory exists, grc render generates the site's policies section from its *.md files. Two independent front matter properties apply:

---
visibility: public        # public | private: decides who can read it
status: approved          # draft | in_review | approved: lifecycle, display only
approved_date: 2026-09-10 # required when status is approved
---
  • visibility alone decides publication. Only an explicit public appears on the public site; a missing or unknown value is private (fail closed).
  • status never affects visibility. Every policy, public or private, gets a status box under its heading.
  • The private site shows every policy; the public site only visibility: public.
  • grc validate requires both properties, and approved_date for approved ones.
  • Other front matter (sidebar_label, tags, ...) is passed through to Docusaurus.
  • Without a policies directory nothing is generated and hand-curated pages are left alone.

Installation

go install github.com/sirosfoundation/go-grc/cmd/grc@latest

Or build from source:

git clone https://github.com/sirosfoundation/go-grc.git
cd go-grc
make build          # → bin/grc
make install        # → $GOBIN/grc

Container image

docker pull ghcr.io/sirosfoundation/go-grc:latest

# Or build locally
make docker

Usage

All commands take a --root (-r) flag pointing at the compliance repository:

# Show current compliance posture
grc -r /path/to/compliance status

# Sync findings ↔ GitHub issues
grc -r /path/to/compliance sync

# Derive statuses (preview first, then apply)
grc -r /path/to/compliance derive --dry-run
grc -r /path/to/compliance derive

# Generate the Docusaurus compliance site (public or private)
grc -r /path/to/compliance render --profile public
grc -r /path/to/compliance render --profile private

# Serve the rendered site over HTTP
grc -r /path/to/compliance serve --profile private --addr :8080

# Serve with GitHub webhook for automatic re-rendering
grc -r /path/to/compliance serve --webhook --webhook-secret "$GRC_WEBHOOK_SECRET"

# Export evidence package for auditors
grc -r /path/to/compliance export -o /tmp/audit-export

Docker usage

The Docker image includes Node.js and pnpm so that grc serve can run Docusaurus builds inside the container. On startup the server renders markdown, runs pnpm install + docusaurus build, and then serves the styled HTML site.

# Serve the compliance site (renders + builds on startup)
docker run -v /path/to/compliance:/data \
  -p 8080:8080 \
  ghcr.io/sirosfoundation/go-grc \
  serve -r /data --profile private

# With GitHub webhook and daily scheduled rebuilds
docker run -v /path/to/compliance:/data \
  -e GRC_WEBHOOK_SECRET=your-secret \
  -p 8080:8080 \
  ghcr.io/sirosfoundation/go-grc \
  serve -r /data --webhook --rebuild-interval 24h

# Webhook rebuilds are debounced: a burst of pushes yields one rebuild, started
# after --webhook-debounce (default 30s) of quiet. Builds never overlap; a push
# during a build queues exactly one follow-up. 0 rebuilds immediately.

# One-shot render (markdown only, no Docusaurus build)
docker run --rm -v /path/to/compliance:/data \
  ghcr.io/sirosfoundation/go-grc \
  render -r /data --profile public

Docker Compose

Serve both public and private dashboards side by side. A named volume for node_modules caches npm dependencies across container restarts so only the Docusaurus build (~20 s) runs instead of a full pnpm install (~2 min):

# docker-compose.yml
services:
  public:
    image: ghcr.io/sirosfoundation/go-grc:latest
    volumes:
      - .:/data
      - public-site-cache:/data/site/node_modules
    command: ["serve", "-r", "/data", "--profile", "public", "--addr", ":8080"]
    ports:
      - "8080:8080"
    restart: unless-stopped

  private:
    image: ghcr.io/sirosfoundation/go-grc:latest
    volumes:
      - .:/data
      - private-site-cache:/data/site/node_modules
    command: ["serve", "-r", "/data", "--profile", "private", "--addr", ":8080"]
    ports:
      - "8081:8080"
    restart: unless-stopped

volumes:
  public-site-cache:
  private-site-cache:
docker compose up -d
# Public dashboard:  http://localhost:8080
# Private dashboard: http://localhost:8081

To enable automatic re-rendering on GitHub push, add a webhook service:

  webhook:
    image: ghcr.io/sirosfoundation/go-grc:latest
    volumes:
      - .:/data
      - webhook-site-cache:/data/site/node_modules
    command: ["serve", "-r", "/data", "--profile", "private", "--addr", ":8080", "--webhook", "--rebuild-interval", "24h"]
    ports:
      - "8082:8080"
    environment:
      - GRC_WEBHOOK_SECRET=${GRC_WEBHOOK_SECRET}
    restart: unless-stopped

CI pipeline

In a compliance repository with a Makefile wired to grc, the typical CI pipeline is:

make pipeline       # sync → derive → render
make build          # render + docusaurus build

Configuration

Project settings are stored in .grc.yaml at the repository root:

project:
  name: "My Compliance Dashboard"
  repo: "myorg/compliance"
  url: "https://compliance.example.com"

catalog:
  dir: catalog
  subdirs: [technical, organizational]

mappings:
  dir: mappings

audits:
  dir: audits            # real audit cycles: <cycle>/{audit,soa}.yaml
assessments:
  dir: assessments       # internal self-assessments, never public
policies:
  dir: policies          # policy documents; the site's policies section is generated from it

site:
  dir: site/docs

frameworks:
  - id: iso27001
    name: "ISO 27001 Annex A"
    catalog_file: iso27001-annexa.yaml
    mapping_file: iso27001-annexa.yaml
    key_field: annex_a

risk_register:
  dir: risk-register
  files: [platform.yaml]

year_cycle:
  title: "Compliance Year Cycle"
  source: "https://calendar.google.com/calendar/ical/.../basic.ics"
  public: false

Architecture

cmd/grc/
  main.go           CLI entrypoint (cobra)
  sync/             GitHub ↔ findings synchronisation
  derive/           Bottom-up status derivation
  render/           Docusaurus page generation (controls, frameworks,
                    risk register, year-cycle, CSF overview, findings)
  serve/            HTTP server with GitHub webhook support
  export/           Evidence package export
  status/           Read-only status overview
  validate/         Config and data validation
  initialize/       Scaffold a new compliance repository
  oscal/            OSCAL SSP export
  risk/             Risk register CLI operations

pkg/
  catalog/          Control catalog + framework catalog loaders (YAML)
  audit/            Finding & evidence types, audit file loader/saver
  mapping/          Framework mapping types and loader
  config/           Project configuration (.grc.yaml) and path resolution
  github/           go-github wrapper for issue/PR operations
  risk/             Risk register types and loader
  yearcycle/        iCal parser + year-cycle projection

Development

make setup          # Install golangci-lint, goimports, gosec, staticcheck
make test           # Run tests with -race
make lint           # Run golangci-lint
make coverage       # Generate coverage report
make fmt            # gofmt + goimports
make vet            # go vet
make docker         # Build Docker image

OpenSSF Scorecard

License

BSD 2-Clause — see LICENSE.

About

a GRC toolchain based on OSCAL and github

Topics

Resources

Security policy

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages