Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,10 @@ When `children` is provided, it **takes over** rendering entirely — the root d

Renderers emit semantic HTML with `data-gemara-*` attributes and no styling. The attribute taxonomy is the public API for CSS:
- `data-gemara-artifact="ControlCatalog"` on the root `<article>`.
- `data-gemara-part="header" | "groups" | "group" | "control" | "control-id" | "control-title" | "objective" | "requirements" | "requirement" | "applicability" | "mappings" | "control-list"` for structural slots.
- `data-gemara-part="header" | "groups" | "group" | "control" | "control-id" | "control-title" | "objective" | "requirements" | "requirement" | "applicability" | "references" | "references-summary" | "mappings" | "control-list"` for structural slots.
- `data-gemara-control-id`, `data-gemara-group-id`, `data-gemara-requirement-id`, `data-gemara-id` for stable selectors.
- `data-gemara-mappings-label="guidelines" | "threats"` to distinguish mapping sections.
- `data-gemara-part="references"` is an *uncontrolled* native `<details>` (collapsed by default, no `open`) wrapping a control/threat/guideline's mapping sections, with `data-gemara-part="references-summary"` on its `<summary>` ("References to Other Documents"). It carries no client JS — that's why it can live in the server-component-clean renderers (unlike `CollapsibleGroup`, whose `useState` forces it into `/interactive`). Consumers style open/closed via the native `details[open]` selector.
- `data-gemara-mappings-label="guidelines" | "threats" | "principles" | "capabilities" | "vectors"` to distinguish mapping sections (each nested inside `references`).
- `data-gemara-ref="artifact" | "entry" | "mapping-reference"` + `data-gemara-ref-id` on resolver output.
- `data-gemara-prose=""` on the wrapper element emitted by the `Prose` primitive (text fields rendered as plain text with `white-space: pre-wrap`).
- `data-gemara-part="format-tabs" | "format-tablist" | "format-tab" | "format-panel" | "format-code"` on the `FormatTabs` interactive viewer, with `data-gemara-tab-id` on tabs/panels, `data-gemara-selected=""` on the active tab, and `data-gemara-language` on the code `<pre>`. `FormatTabs` does no conversion — the consumer supplies the Preview node and pre-converted (gemaraconv, server-side) format strings as props.
Expand Down
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,10 @@ The library ships no CSS. The public styling contract is the set of `data-gemara

- `data-gemara-artifact="ControlCatalog" | "GuidanceCatalog" | "CapabilityCatalog" | "PrincipleCatalog" | "ThreatCatalog" | "VectorCatalog"` on each renderer's root `<article>`.
- `data-gemara-id` — the artifact `metadata.id`.
- `data-gemara-part="header" | "title" | "meta" | "groups" | "group" | "control" | "guideline" | "capability" | "principle" | "threat" | "vector" | "control-id" | "control-title" | "guideline-id" | "guideline-title" | "capability-id" | "capability-title" | "principle-id" | "principle-title" | "threat-id" | "threat-title" | "vector-id" | "vector-title" | "objective" | "rationale" | "description" | "requirements" | "requirement" | "applicability" | "actors" | "actor" | "mappings" | "control-list" | "guideline-list" | "capability-list" | "principle-list" | "threat-list" | "vector-list" | "front-matter" | "extends"` for structural slots.
- `data-gemara-part="header" | "title" | "meta" | "groups" | "group" | "control" | "guideline" | "capability" | "principle" | "threat" | "vector" | "control-id" | "control-title" | "guideline-id" | "guideline-title" | "capability-id" | "capability-title" | "principle-id" | "principle-title" | "threat-id" | "threat-title" | "vector-id" | "vector-title" | "objective" | "rationale" | "description" | "requirements" | "requirement" | "applicability" | "actors" | "actor" | "references" | "references-summary" | "mappings" | "control-list" | "guideline-list" | "capability-list" | "principle-list" | "threat-list" | "vector-list" | "front-matter" | "extends"` for structural slots.
- `data-gemara-control-id`, `data-gemara-guideline-id`, `data-gemara-capability-id`, `data-gemara-principle-id`, `data-gemara-threat-id`, `data-gemara-vector-id`, `data-gemara-group-id`, `data-gemara-requirement-id` for stable selectors.
- `data-gemara-mappings-label="guidelines" | "threats" | "principles" | "capabilities" | "vectors"` on mapping sections.
- `data-gemara-part="references"` is a native `<details>` (collapsed by default) wrapping a control/threat/guideline's cross-document mapping sections, with `data-gemara-part="references-summary"` on its `<summary>` ("References to Other Documents"). It carries no JavaScript — style the open/closed states with the native `details[open]` selector.
- `data-gemara-mappings-label="guidelines" | "threats" | "principles" | "capabilities" | "vectors"` on mapping sections (nested inside `references`).
- `data-gemara-ref="artifact" | "entry" | "mapping-reference"` + `data-gemara-ref-id` on resolver output.
- `data-gemara-prose=""` on the `Prose` wrapper element (plain-text fields).
- `CollapsibleGroup` (interactive) emits `data-gemara-part="collapsible" | "collapsible-trigger" | "collapsible-content"`, plus `data-gemara-open=""` on the wrapper when expanded.
Expand Down Expand Up @@ -121,6 +122,7 @@ import { ControlCatalog } from "@gemara/react/control-catalog";
aria-label="Catalog formats"
tabs={[
{ id: "preview", label: "Preview", preview: <ControlCatalog data={catalog} /> },
{ id: "yaml", label: "YAML", language: "yaml", content: rawYaml },
{ id: "markdown", label: "Markdown", language: "markdown", content: markdown },
{ id: "oscal", label: "OSCAL", language: "json", content: oscalJson },
]}
Expand All @@ -129,6 +131,14 @@ import { ControlCatalog } from "@gemara/react/control-catalog";

Each tab is either a `preview` node (rendered as-is) or `content` text (rendered in a `<pre><code>` with `data-gemara-language` for your highlighter — the library ships no highlighting). A tab with neither is the seam for loading/empty states: pass `preview={<Spinner />}` while a conversion is still in flight.

**`content` is rendered verbatim — formatting and tab selection are yours.** The component does no conversion, pretty-printing, or validation, so what you pass is exactly what renders:

- **Pretty-print upstream.** A compact OSCAL string from the hub API (`{"catalog":{…}}`) shows as one flat line. Indent it before passing it in: `JSON.stringify(JSON.parse(oscalJson), null, 2)`. go-gemara's `oscalexport` CLI already emits indented JSON; a raw `fetch()` may not.
- **You own which tabs exist.** There's no automatic format discovery. The raw **YAML** tab is just the source string you already parsed — no go-gemara round-trip needed (`content: rawYaml`). Only add an OSCAL or Markdown tab for artifact types that actually have a `gemaraconv` converter; for others, omit the tab.
- **Validation lives upstream**, in go-gemara and the spec — `FormatTabs` will faithfully display malformed input. Use the empty-tab seam (`preview={<ErrorNote />}`) to surface a conversion that failed.

A worked Astro example wiring all three (Preview + raw YAML + pre-converted OSCAL) lives in [`examples/astro/`](./examples/astro/src/components/MultiFormatViewer.tsx).

It implements the ARIA tabs pattern with **automatic activation** — Arrow keys move focus and switch the panel in one step (Left/Right wrap; Home/End jump to first/last). Provide `aria-label` (or `aria-labelledby`) so the tablist has an accessible name — important when several viewers share a page. Because it holds tab state, it lives in `@gemara/react/interactive` and carries `"use client"`.

## React Server Components
Expand Down
55 changes: 55 additions & 0 deletions examples/astro/src/components/MultiFormatViewer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// SPDX-License-Identifier: Apache-2.0
import { FormatTabs } from "@gemara/react/interactive";
import { ControlCatalog } from "@gemara/react/control-catalog";
import { GemaraProvider } from "@gemara/react/provider";
import type { ControlCatalog as ControlCatalogData } from "@gemara/react/types";

interface Props {
/** Parsed catalog, used for the rendered Preview tab. */
data: ControlCatalogData;
/** The raw source the page already read — no go-gemara round-trip needed. */
yaml: string;
/** Markdown projection produced server-side by go-gemara's `ToMarkdown`. */
markdown: string;
/** OSCAL projection produced server-side by go-gemara's `oscalexport`. */
oscal: string;
}

/**
* Composes `FormatTabs` inside a React island.
*
* The `preview` tab is a *rendered node* (`<ControlCatalog />`), which can't be
* passed as a prop across Astro's island boundary — only serializable props
* survive. So the page hands this wrapper plain data + strings, and the preview
* element is constructed here, client-side, inside React.
*
* Three lessons live in this file, not in the library:
* 1. `FormatTabs` renders `content` verbatim — any formatting is the
* consumer's job. We pretty-print the OSCAL below.
* 2. The raw YAML tab is just the source string the page already has.
* 3. The component never converts or validates; go-gemara produced the OSCAL.
*/
export default function MultiFormatViewer({ data, yaml, markdown, oscal }: Props) {
// `JSON.stringify(JSON.parse(x), null, 2)` normalizes a compact API response
// into indented JSON. It's a no-op on already-indented OSCAL like ours, but
// shows the idiom for when the string arrives as one flat line from the hub.
const prettyOscal = JSON.stringify(JSON.parse(oscal), null, 2);

return (
<GemaraProvider>
<FormatTabs
aria-label="Catalog formats"
tabs={[
{
id: "preview",
label: "Preview",
preview: <ControlCatalog data={data} headingLevel={3} />,
},
{ id: "yaml", label: "YAML", language: "yaml", content: yaml },
{ id: "markdown", label: "Markdown", language: "markdown", content: markdown },
{ id: "oscal", label: "OSCAL", language: "json", content: prettyOscal },
]}
/>
</GemaraProvider>
);
}
260 changes: 260 additions & 0 deletions examples/astro/src/fixtures/good-ccc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
# FINOS Cloud Control Catalog

Version: 2024.1

_FINOS Cloud Control Catalog_ is a Gemara 0.20.0 ControlCatalog by FINOS (Human, `finos`).

### Description

FINOS CCC is an open standard project that describes consistent controls for
compliant public cloud deployments in the financial services sector.

### Requirement Applicability Groups

The following groups are used to specify the circumstance within which an assessment requirement is mandated.

- **tlp_clear** — TLP:Clear: Information may be shared without restriction.

- **tlp_green** — TLP:Green: Information may be shared with partners and restricted to the
organization.

- **tlp_amber** — TLP:Amber: Information may be shared with partners and restricted to the
organization.

- **tlp_red** — TLP:Red: Information is restricted to the organization.

_Summary: 5 control(s), 10 assessment requirement(s)._

## Table of contents

- [Data Protection](#data-protection)
- [CCC.C01: Prevent Unencrypted Requests](#ccc-c01-prevent-unencrypted-requests)
- [CCC.C06: Prevent Deployment in Restricted Regions](#ccc-c06-prevent-deployment-in-restricted-regions)
- [CCC.C08: Enable Multi-zone or Multi-region Data Replication](#ccc-c08-enable-multi-zone-or-multi-region-data-replication)
- [CCC.C09: Prevent Tampering, Deletion, or Unauthorized Access to Access Logs](#ccc-c09-prevent-tampering-deletion-or-unauthorized-access-to-access-logs)
- [CCC.C10: Prevent Data Replication to Destinations Outside of Defined
Trust Perimeter
](#ccc-c10-prevent-data-replication-to-destinations-outside-of-defined-trust-perimeter)

## data-protection: Data Protection

Data protection controls ensure that data is protected from unauthorized
access, disclosure, and tampering. This includes encryption of data at
rest and in transit, access controls, and data retention policies.

### CCC.C01: Prevent Unencrypted Requests

**Objective**

Ensure that all communications are encrypted in transit to protect data
integrity and confidentiality.

#### Guidelines

This control aids in the application of the following guidelines:

| Source | References |
| :--- | :--- |
| **CSF** | PR.DS-02 — Data-in-transit is protected |
| **CCM** | IVS-03 · IVS-07 |
| **ISO-27001** | 2013 A.13.1.1 — This control is closely related to 2013 A.13.1.1. |
| **NIST-800-53** | SC-8 · SC-13 |

#### Threats

This control aids in the mitigation of the following threats:

| Source | References |
| :--- | :--- |
| **CCC** | CCC.TH02 — Data is Intercepted in Transit |

#### CCC.C01.TR01

When a port is exposed for non-SSH network traffic, all traffic MUST
include a TLS handshake AND be encrypted using TLS 1.2 or higher.

**Applicability:** tlp_clear, tlp_green, tlp_amber, tlp_red

#### CCC.C01.TR02

When a port is exposed for SSH network traffic, all traffic MUST
include a SSH handshake AND be encrypted using SSHv2 or higher.

**Applicability:** tlp_clear, tlp_green, tlp_amber, tlp_red

### CCC.C06: Prevent Deployment in Restricted Regions

**Objective**

Ensure that resources are not provisioned or deployed in
geographic regions or cloud availability zones that have been
designated as restricted or prohibited, to comply with
regulatory requirements and reduce exposure to geopolitical
risks.

#### Guidelines

This control aids in the application of the following guidelines:

| Source | References |
| :--- | :--- |
| **CCM** | DSI-06 — This control is closely related to DSI-06. · DSI-08 — This control is closely related to DSI-08. |
| **ISO-27001** | 2013 A.11.1.1 — This control is closely related to 2013 A.11.1.1. |
| **NIST-800-53** | AC-6 — This control is closely related to AC-6. |
| **CSF** | PR.DS-1 — Data-at-rest is protected |

#### Threats

This control aids in the mitigation of the following threats:

| Source | References |
| :--- | :--- |
| **CCC** | CCC.TH03 — Deployment Region Network is Untrusted |

#### CCC.C06.TR01

When a deployment request is made, the service MUST validate
that the deployment region is not to a restricted or regions
or availability zones.

**Applicability:** tlp_clear, tlp_green, tlp_amber, tlp_red

#### CCC.C06.TR02

When a deployment request is made, the service MUST validate that
replication of data, backups, and disaster recovery operations
will not occur in restricted regions or availability zones.

**Applicability:** tlp_clear, tlp_green, tlp_amber, tlp_red

### CCC.C08: Enable Multi-zone or Multi-region Data Replication

**Objective**

Ensure that data is replicated across multiple
zones or regions to protect against data loss due to hardware
failures, natural disasters, or other catastrophic events.

#### Guidelines

This control aids in the application of the following guidelines:

| Source | References |
| :--- | :--- |
| **CSF** | PR.DS-5 — Protections against data leaks are implemented |
| **CCM** | BCR-08 — Backup |
| **NIST-800-53** | CP-2 — Contingency plan · CP-10 — Information system recovery and reconstitution |

#### Threats

This control aids in the mitigation of the following threats:

| Source | References |
| :--- | :--- |
| **CCC** | CCC.TH06 — Data is Lost or Corrupted |

#### CCC.C08.TR01

When data is stored, the service MUST ensure that data is
replicated across multiple availability zones or regions.

**Applicability:** tlp_green, tlp_amber, tlp_red

#### CCC.C08.TR02

When data is replicated across multiple zones or regions,
the service MUST be able to verify the replication state,
including the replication locations and data synchronization
status.

**Applicability:** tlp_green, tlp_amber, tlp_red

### CCC.C09: Prevent Tampering, Deletion, or Unauthorized Access to Access Logs

**Objective**

Access logs should always be considered sensitive.
Ensure that access logs are protected against unauthorized
access, tampering, or deletion.

#### Guidelines

This control aids in the application of the following guidelines:

| Source | References |
| :--- | :--- |
| **CCM** | LOG-02 — Audit log protection · LOG-04 — Audit log access and accountability · LOG-09 — Log protection |
| **NIST-800-53** | AU-9 — Protection of audit information |

#### Threats

This control aids in the mitigation of the following threats:

| Source | References |
| :--- | :--- |
| **CCC** | CCC.TH07 — Logs are Tampered with or Deleted · CCC.TH09 — Logs or Monitoring Data are Read by Unauthorized Users · CCC.TH04 — Data is Replicated to Untrusted or External Locations |

#### CCC.C09.TR01

When access logs are stored, the service MUST ensure that
access logs cannot be accessed without proper authorization.

**Applicability:** tlp_amber, tlp_red, tlp_green, tlp_clear

#### CCC.C09.TR02

When access logs are stored, the service MUST ensure that
access logs cannot be modified without proper authorization.

**Applicability:** tlp_amber, tlp_red, tlp_green, tlp_clear

#### CCC.C09.TR03

When access logs are stored, the service MUST ensure that
access logs cannot be deleted without proper authorization.

**Applicability:** tlp_amber, tlp_red, tlp_green, tlp_clear

### CCC.C10: Prevent Data Replication to Destinations Outside of Defined
Trust Perimeter

**Objective**

Prevent replication of data to untrusted destinations outside
of defined trust perimeter. An untrusted destination is defined
as a resource that exists outside of a specified trusted
identity or network or data perimeter.

#### Guidelines

This control aids in the application of the following guidelines:

| Source | References |
| :--- | :--- |
| **CSF** | PR.DS-5 — Protections against data leaks are implemented |
| **CCM** | DSP-10 — Sensitive data transfer · DSP-19 — Data location |
| **NIST-800-53** | AC-4 — Information flow enforcement |

#### Threats

This control aids in the mitigation of the following threats:

| Source | References |
| :--- | :--- |
| **CCC** | CCC.TH04 — Data is Replicated to Untrusted or External Locations |

#### CCC.C10.TR01

When data is replicated, the service MUST ensure that
replication is restricted to explicitly trusted destinations.

**Applicability:** tlp_green, tlp_amber, tlp_red

### Mapping References

- **CCC** — FINOS Common Cloud Controls Threats (v2024.1)
- **CSF** — NIST Cybersecurity Framework (v2.0)
- **CCM** — Cloud Security Alliance Cloud Controls Matrix (v4.0)
- **ISO-27001** — ISO/IEC 27001 (v2013)
- **NIST-800-53** — NIST Special Publication 800-53 (vRev. 5)

Loading
Loading