# CIR by example

> Concrete guide for language models and humans in a hurry. Read this, then the
> [specification](https://cirsec.org/spec.md) when you need a field's exact rules.
> Status: draft v0.5, fields may still change.

## The idea in 30 seconds

A CVE says *what* a vulnerability is. It does not say who exploits it, whether
you can detect it, or what your organization decided. CIR (Cyber Issue Record)
is one YAML file that carries all of it, for public CVEs and for internal
findings (pentest, misconfiguration) that have no CVE.

A record has four layers, each with its own TLP marking:

| Layer | Answers | Typical TLP | Shared with |
|---|---|---|---|
| `core` | What is the issue? (CVE, CVSS, CWE, affected versions, fixes) | CLEAR | everyone |
| `threat_intel` | Who exploits it, how, since when? (KEV, EPSS, actors, ATT&CK, IOCs) | CLEAR | everyone / community |
| `posture` | How does it behave in a real environment? Enums only, no names | AMBER | group CERT, ISAC |
| `context` | What does it mean for *this* organization, what was decided? | RED | the organization |

Sharing a record with an audience = **dropping the layers whose TLP is above
what that audience may see**. Nothing is rewritten.

## Reading an identifier

```
CIR-YYYY-CODE-NNNN
```

The first character of `CODE` tells you who issued it:

| ID | Kind | Meaning |
|---|---|---|
| `CIR-2021-00-0001` | public, authority | Issued by the root authority (the CIR project) |
| `CIR-2026-0CERTFR-0042` | public, authority | Issued by a registered authority (`0` + up to 7 chars), signed with its OpenPGP key |
| `CIR-2021-1CVE-44228` | public, mirror | Auto-generated copy of `CVE-2021-44228`. Never hand-written |
| `CIR-2026-LAB-0012` | internal | Organization-private code, not globally unique |

### CVE ↔ CIR, no lookup needed

```
CVE-YYYY-N   →   CIR-YYYY-1CVE-N
CVE-2021-44228  →  CIR-2021-1CVE-44228
CVE-2024-3094   →  CIR-2024-1CVE-3094
```

Every CVE already has a CIR id. To talk about a CVE in CIR, point at its mirror.
The record is served as YAML at `https://cirsec.org/cir/CIR-YYYY-1CVE-N.yml`
(e.g. [CIR-2021-1CVE-44228.yml](https://cirsec.org/cir/CIR-2021-1CVE-44228.yml)),
and as a page at `https://cirsec.org/cir/CVE-YYYY-N`.

## Example 1: a 1CVE mirror (public, generated)

Contains only `meta`, `core` and `threat_intel`, all `CLEAR`, filled from
public sources (CVE, NVD, CISA KEV, EPSS).

```yaml
cir_version: "0.5"
meta:
  id: CIR-2021-1CVE-44228
  revision: 4                 # bumped on each regeneration
  title: "Apache Log4j2 JNDI features do not protect against attacker controlled LDAP"
  status: published           # rejected if the CVE is rejected
  created: 2026-10-15
  updated: 2026-10-15
  tlp: CLEAR

core:
  tlp: CLEAR
  summary: "Remote code execution via JNDI lookups in logged strings in Apache Log4j 2."
  disclosed: 2021-12-10
  identifiers:
    - { type: cve, id: CVE-2021-44228 }     # required: the mirrored CVE
  affected:
    - product: { vendor: Apache, name: log4j-core }
  weaknesses: [CWE-502, CWE-917]
  severity:
    - type: cvss_v3.1
      vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H"
      score: 10.0
      source: nvd                           # never "issuer" in a mirror

threat_intel:
  tlp: CLEAR
  updated: 2026-10-15
  exploitation: { maturity: weaponized, in_the_wild: true }
  signals:
    kev: { listed: true, date_added: 2021-12-10, ransomware_use: known }
    epss: { score: 0.99999, percentile: 1.0 }
```

## Example 2: an organization extends the mirror

The organization writes **only** what it knows: `posture` and `context`.
`core` and `threat_intel` are inherited through `based_on`.

```yaml
cir_version: "0.5"
meta:
  id: CIR-2021-GRP-0003
  revision: 3
  title: "Log4Shell, subsidiary GRP-ENT-07"
  status: published
  created: 2021-12-11
  updated: 2021-12-14
  tlp: AMBER
  based_on: CIR-2021-1CVE-44228

posture:                      # shareable with the group CERT
  tlp: AMBER
  updated: 2021-12-14
  status: { state: mitigated }
  exposure: { reachability: internet, prevalence: common }
  controls:
    - { control: egress_filtering, effect: blocks, coverage: full, validated: true }
  detection: { capability: partial, layers: [waf, network, endpoint], validated: true }

context:                      # never leaves the organization
  tlp: RED
  updated: 2021-12-14
  assets:
    - { ref: CMDB-004812, name: "portail-clients", environment: prod, criticality: critical }
  decision:
    action: mitigate
    justification: "Vendor patch unavailable; WAF and egress filtering in the meantime."
    decided_by: "CISO"
    decided_on: 2021-12-11
    review_on: 2021-12-21
```

Exported to the group CERT, `context` (RED) is dropped and `posture` (AMBER)
is kept. The CERT can aggregate "most entities block it with egress filtering"
without learning which servers are affected.

## Example 3: an internal finding with no CVE

Same structure, internal code, restrictive TLP, an `internal` identifier.

```yaml
cir_version: "0.5"
meta:
  id: CIR-2026-LAB-0012
  revision: 2
  title: "IDOR on invoice export endpoint in billing-api"
  status: published
  created: 2026-09-28
  updated: 2026-10-05
  tlp: AMBER

core:
  tlp: AMBER
  summary: "Any authenticated user can export another customer's invoices by changing the invoice ID."
  identifiers:
    - { type: internal, id: "PENTEST-2026-Q3-07" }   # forbidden in a public record
  weaknesses: [CWE-639]
  severity:
    - type: cvss_v3.1
      vector: "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N"
      score: 6.5
      source: issuer
```

## Common tasks

| Task | How |
|---|---|
| Find the CIR of a CVE | `CVE-YYYY-N` → `CIR-YYYY-1CVE-N`, fetch `https://cirsec.org/cir/CIR-YYYY-1CVE-N.yml` |
| Is a record public? | `CODE` starts with `0` (authority) or `1` (mirror) |
| Add my org's view on a CVE | New internal record, `based_on: CIR-YYYY-1CVE-N`, only `posture` / `context` |
| Share with a CERT / ISAC | Drop every layer whose `tlp` is above AMBER |
| Publish to everyone | Keep only `CLEAR` layers (usually `core` + `threat_intel`) |
| Get a priority score | Compute `cir-risk/0.1` from the layers ([scoring](https://cirsec.org/scoring)); never type a score in by hand |
| Validate a record | [Builder](https://cirsec.org/builder) or the rules in the [specification](https://cirsec.org/spec.md) |

## Rules a model must not break

- **IDs**: `0…` must be in the [authorities registry](https://github.com/cirsec/spec/blob/main/authorities.yml); `1…` is a mirror (only `1CVE` exists); anything else is internal.
- **Mirrors** have no `posture` or `context`, only `CLEAR` layers, and reference their CVE in `core.identifiers`.
- **TLP order**: `CLEAR` < `GREEN` < `AMBER` < `AMBER+STRICT` < `RED`. No layer may be less restrictive than `meta.tlp`.
- **`posture` holds no free text, no numbers, no names**: only enums, booleans, dates and ids. Hostnames and products go in `context`.
- **Reference, don't redefine**: CVE, GHSA, CWE, CVSS, ATT&CK, KEV, EPSS keep their own ids and values.
- **Dates** are `YYYY-MM-DD`; enum values are lowercase.
- **Scores are computed**, never invented: if a layer is missing, the score assumes the worst for it.

## More

- [Specification (Markdown)](https://cirsec.org/spec.md)
- [Full template, every field](https://github.com/cirsec/spec/blob/main/cir.yml)
- [Everything in one file](https://cirsec.org/llms-full.txt)
