# CIR Specification > **Status: draft (v0.5).** Sections are specified one layer at a time. ## Conventions - Dates use ISO 8601 `YYYY-MM-DD`. - Enumerated values are lowercase. - TLP values follow [TLP 2.0](https://www.first.org/tlp/): `CLEAR`, `GREEN`, `AMBER`, `AMBER+STRICT`, `RED`. ## Root | Field | Type | Required | Description | |---|---|---|---| | `cir_version` | string | yes | Version of the CIR specification the record follows, e.g. `"0.5"`. | | `meta` | object | yes | Identity and lifecycle of the record. | | `core` | object | no | Layer 1. | | `threat_intel` | object | no | Layer 2. | | `posture` | object | no | Layer 3. | | `context` | object | no | Layer 4. | | `tags` | list of string | no | Free classification tags. | ## `meta` | Field | Type | Required | Description | |---|---|---|---| | `id` | string | yes | Record identifier, see [Identifier rules](#identifier-rules). | | `revision` | integer >= 1 | yes | Incremented on every update of the record. | | `title` | string | yes | Short descriptive title. | | `status` | enum | yes | `draft`, `published`, `rejected`, `withdrawn`. | | `created` | date | yes | Creation date of the record. | | `updated` | date | yes | Last update date. Must be >= `created`. | | `tlp` | enum | no | TLP ceiling for the whole record, see [TLP rules](#tlp-rules). | | `based_on` | string | no | `id` of a parent CIR this record extends (typically an internal record adding `posture` / `context` to a public one). | | `aliases` | list of string | no | Alternative names, lowercase slugs (`^[a-z0-9]+(-[a-z0-9]+)*$`). | | `authors` | list of object | no | `name` (required), `contact`, `org`. | | `history` | list of object | no | `date` (required), `author`, `change` (required). | ### Identifier rules ``` CIR-YYYY-CODE-NNNN ``` - **Public** record: `^CIR-\d{4}-0[A-Z0-9]{0,7}-\d{4,}$`. `CODE` must be listed in [`authorities.yml`](./authorities.yml). `00` is the root authority. - **Mirror** record: `^CIR-\d{4}-1[A-Z0-9]{1,7}-\d{4,}$`. Auto-generated from an external database, see [Mirror records](#mirror-records). - **Internal** record: `^CIR-\d{4}-[A-Z2-9][A-Z0-9]{1,7}-\d{4,}$`. Managed freely by the organization, not globally unique. Whether a record is public is **derived from the code**: first character `0` (authority) or `1` (mirror) is public, anything else is internal. There is no separate field for it. ### Mirror records A mirror code (`1` + 1 to 7 alphanumerics) mirrors an external database: one CIR per upstream entry, generated automatically by the CIR project, never written by hand. Its `id` is derived from the upstream id, so anyone can compute it without a lookup. | Code | Mirrors | Mapping | Example | |---|---|---|---| | `1CVE` | [CVE](https://www.cve.org) | `CVE-YYYY-N` → `CIR-YYYY-1CVE-N` | `CVE-2021-44228` → `CIR-2021-1CVE-44228` | Mirror codes are defined by this specification only; other `1` codes are reserved. A mirror record: - contains only `meta`, `core` and `threat_intel`, all `CLEAR`. No `posture`, no `context`. - has `core.identifiers` containing the upstream id (for `1CVE`, a `cve` entry equal to the id mapped back). - is filled from public sources only (for `1CVE`: CVE record, NVD, CISA KEV, EPSS); CVSS scores carry `source: nvd` or `vendor`, never `issuer`. - is regenerated when its sources change: `revision` and `updated` are bumped, nothing is merged by hand. - has `status: rejected` when the upstream entry is rejected. To add knowledge on top of a mirror, write a record with `meta.based_on` pointing to it: an authority record (`0` code) for public analysis, an internal record for `posture` / `context`. ### TLP rules - Each layer carries its own `tlp`. - `meta.tlp`, when set, is a ceiling: no layer may be less restrictive than it. - Restriction order: `CLEAR` < `GREEN` < `AMBER` < `AMBER+STRICT` < `RED`. - Exporting a record to an audience means dropping every layer whose `tlp` is more restrictive than what that audience may receive. ### Validation - `id` matches one of the three patterns above. - Authority `id` (`0` code) → `CODE` exists in `authorities.yml`. - Mirror `id` (`1` code) → `CODE` is a defined mirror code and the record follows the [mirror rules](#mirror-records). - `updated` >= `created`. - `based_on`, when set, matches a valid `id` pattern and differs from `id`. - No layer `tlp` is less restrictive than `meta.tlp`. ### Authorities registry Each entry of [`authorities.yml`](./authorities.yml): | Field | Type | Required | Description | |---|---|---|---| | `code` | string | yes | Public `CODE`, `^0[A-Z0-9]{1,7}$`. Unique in the registry. | | `name` | string | yes | Name of the issuing organization. | | `contact` | string | no | Contact address. | | `scope` | string | yes | What the authority publishes. | | `fingerprint` | string | yes, except `00` | OpenPGP fingerprint of the signing key, 40 uppercase hex chars, no spaces. | | `public_key` | string | yes, except `00` | ASCII-armored OpenPGP public key. Its fingerprint must equal `fingerprint`. | | `key_expires` | date | yes if the key expires | Expiration date of the key. Must equal the expiration in `public_key`. Omitted for a key without expiration. | The key is the one the authority signs its public records with. Rotating a key or extending its expiration goes through a pull request, like adding an authority. ## Layers: common rules - Every layer present in a record must have a `tlp`. - A layer can be omitted entirely. - A record with `meta.based_on` inherits the `core` and `threat_intel` layers of its parent when it omits them. This is the normal shape of an internal record adding `posture` and/or `context` to a public one, typically a `1CVE` mirror. ## `core` (layer 1) What the issue is. `core` references existing standards and never redefines them. Products and versions follow the [OSV](https://ossf.github.io/osv-schema/) model so records can be imported from and exported to OSV. | Field | Type | Required | Description | |---|---|---|---| | `tlp` | enum | yes | TLP of the layer. | | `summary` | string | yes | One-line summary. | | `details` | string (markdown) | no | Full description. | | `disclosed` | date | no | Public disclosure date, or internal report date for internal records. | | `identifiers` | list of object | no | External identifiers designating this issue. | | `references` | list of object | no | Typed links. | | `affected` | list of object | no | Affected products and versions. | | `weaknesses` | list of string | no | CWE identifiers (`^CWE-\d+$`). | | `severity` | list of object | no | Severity scores, possibly from several sources. | | `remediations` | list of object | no | Fixes, mitigations and workarounds. | ### `identifiers[]` | Field | Type | Required | Description | |---|---|---|---| | `type` | enum | yes | `cve`, `ghsa`, `osv`, `vendor`, `internal`, `other`. | | `id` | string | yes | The identifier. `cve` must match `^CVE-\d{4}-\d{4,}$`, `ghsa` must match `^GHSA(-[23456789cfghjmpqrvwx]{4}){3}$`. | `internal` identifiers (ticket, pentest report ID...) are forbidden in public records and dropped by any export to a public audience. ### `references[]` | Field | Type | Required | Description | |---|---|---|---| | `type` | enum | yes | `advisory`, `fix`, `report`, `article`, `package`, `web`, `other`. | | `url` | string (URL) | yes | | Exploits and PoCs are not references: they belong to `threat_intel`. ### `affected[]` | Field | Type | Required | Description | |---|---|---|---| | `product.vendor` | string | no | | | `product.name` | string | yes | | | `product.purl` | string | no | [Package URL](https://github.com/package-url/purl-spec). | | `product.cpe` | string | no | CPE 2.3 string. | | `ranges` | list of object | no | Version ranges. | | `versions` | list of string | no | Explicit affected versions, when no range applies. | At least one of `ranges` or `versions` is required. #### `ranges[]` | Field | Type | Required | Description | |---|---|---|---| | `type` | enum | yes | `semver`, `ecosystem`, `git`. | | `repo` | string (URL) | if `git` | Repository the commits belong to. | | `events` | list of object | yes | Ordered list of single-key objects: `introduced`, `fixed`, `last_affected`. | Event semantics follow OSV: a version is affected if it is at or after an `introduced` and before the next `fixed` (or at or before the next `last_affected`). `introduced: "0"` means "since the first version". Several `introduced` / `fixed` pairs in one range describe disjoint affected intervals. For `git` ranges, events are commit hashes. ### `severity[]` | Field | Type | Required | Description | |---|---|---|---| | `type` | enum | yes | `cvss_v3.1`, `cvss_v4.0`. | | `vector` | string | yes | Full vector string including the `CVSS:x.y/` prefix. | | `score` | number 0.0 to 10.0 | yes | Base score. Must match the score computed from `vector`. | | `source` | enum | yes | `nvd`, `vendor`, `issuer`. | ### `remediations[]` | Field | Type | Required | Description | |---|---|---|---| | `type` | enum | yes | `upgrade`, `patch`, `config`, `workaround`, `none`. | | `description` | string | yes | | | `status` | enum | yes | `available`, `planned`, `none`. | | `date` | date | no | Date the remediation became available. | | `reference` | string (URL) | no | | ### `core` validation - `cve` / `ghsa` identifiers match their patterns. - No `internal` identifier in a public record. - Each `affected[]` has `ranges` or `versions`. - Each range has at least one `introduced` event; `git` ranges have a `repo`. - `severity[].score` matches the score computed from `vector`. ## `threat_intel` (layer 2) Who exploits the issue, how, and since when. Two principles: **everything is dated and sourced**, and **entities (actors, malware, campaigns) are references, not descriptions**. | Field | Type | Required | Description | |---|---|---|---| | `tlp` | enum | yes | TLP of the layer. | | `updated` | date | yes | Freshness of the layer. Also the snapshot date of every value in `signals`. | | `exploitation` | object | no | Exploit availability and in-the-wild activity. | | `priority` | object | no | Public priority, computed. See [Scoring](#scoring). | | `signals` | object | no | Snapshots of external feeds. | | `actors` | list of entity | no | Threat actors. | | `malware` | list of entity | no | Malware families. Adds `family_type`. | | `campaigns` | list of entity | no | Campaigns. | | `attack` | list of object | no | MITRE ATT&CK technique/tactic pairs. | | `targeting` | object | no | Targeted sectors and regions. | | `iocs` | list of object | no | Inline indicators (small volume). | | `ioc_bundles` | list of object | no | External indicator bundles (large volume). | | `detections` | list of object | no | Public detection rules. | | `timeline` | list of object | no | Dated events. | | `sources` | list of object | no | Sources referenced by the other fields. | ### Confidence Every assertion that carries a `confidence` uses: `low`, `medium`, `high`, `critical`. ### `exploitation` | Field | Type | Required | Description | |---|---|---|---| | `maturity` | enum | no | `none`, `poc`, `functional`, `weaponized`. | | `in_the_wild` | enum | no | `true`, `false`, `unknown`. | | `first_seen` | date | no | First observed exploitation. | | `exploits[]` | list of object | no | `type` (`poc`, `module`, `template`, `kit`, `writeup`), `source` (`github`, `exploitdb`, `metasploit`, `nuclei`, `other`), `url` (required), `date`. | ### `signals` Values from external feeds are **stored, not resolved live**, so a record stays usable offline. They are a snapshot taken at `threat_intel.updated`: refreshing any signal means bumping `threat_intel.updated`. | Field | Type | Description | |---|---|---| | `kev.listed` | boolean | Listed in CISA KEV. | | `kev.date_added` | date | KEV `dateAdded`. | | `kev.ransomware_use` | enum | KEV `knownRansomwareCampaignUse`: `known`, `unknown`. | | `epss.score` | number 0 to 1 | EPSS probability. | | `epss.percentile` | number 0 to 1 | EPSS percentile. | ### Entities (`actors[]`, `malware[]`, `campaigns[]`) | Field | Type | Required | Description | |---|---|---|---| | `refs` | list of object | no | Identifiers of the same entity in several namespaces: `type` (`mitre`, `mlab`, `misp`, `malpedia`, `other`) and `id`. | | `name` | string | yes | Display name. | | `confidence` | enum | no | Confidence that this entity is linked to the issue. | | `first_seen` | date | no | First observed link. | | `sources` | list of string | no | `id`s from `sources[]`. | | `family_type` | enum | no | `malware[]` only: `ransomware`, `botnet`, `miner`, `rat`, `loader`, `webshell`, `other`. | A `mitre` ref for an actor must match `^G\d{4}$`, for malware `^S\d{4}$`, for a campaign `^C\d{4}$`. ### `attack[]` | Field | Type | Required | Description | |---|---|---|---| | `technique` | string | yes | `^T\d{4}(\.\d{3})?$` | | `tactic` | string | yes | `^TA\d{4}$` | ### `targeting` | Field | Type | Description | |---|---|---| | `sectors` | list of string | `all` or sectors (recommended list: `finance`, `health`, `energy`, `government`, `telecom`, `education`, `manufacturing`, `retail`, `transport`, `defense`, `technology`). | | `regions` | list of string | ISO 3166-1 alpha-2 codes. | ### `iocs[]` and `ioc_bundles[]` Inline `iocs` are meant for a handful of key indicators (about 50 at most). Large sets go to `ioc_bundles`. | Field | Type | Required | Description | |---|---|---|---| | `iocs[].type` | enum | yes | `ip`, `domain`, `url`, `sha256`, `sha1`, `md5`, `pattern`, `other`. | | `iocs[].value` | string | yes | | | `iocs[].first_seen` / `last_seen` | date | no | | | `iocs[].confidence` | enum | no | | | `iocs[].sources` | list of string | no | | | `ioc_bundles[].format` | enum | yes | `stix`, `misp`, `csv`. | | `ioc_bundles[].url` | string (URL) | yes | | ### `detections[]` Public detection rules only. What an organization actually deploys never goes here (see `posture`). | Field | Type | Required | Description | |---|---|---|---| | `format` | enum | yes | `sigma`, `yara`, `suricata`, `snort`, `nuclei`, `kql`, `spl`. | | `name` | string | no | | | `url` | string (URL) | yes | | ### `timeline[]` and `sources[]` | Field | Type | Required | Description | |---|---|---|---| | `timeline[].date` | date | yes | | | `timeline[].event` | string | yes | | | `timeline[].sources` | list of string | no | | | `sources[].id` | string | yes | Local to the record, unique, e.g. `s1`. | | `sources[].name` | string | yes | | | `sources[].url` | string (URL) | no | | | `sources[].date` | date | no | | | `sources[].reliability` | enum | no | Admiralty source reliability: `A` to `F`. | ### `threat_intel` validation - Every `sources` reference points to an existing `sources[].id`. - `sources[].id` values are unique. - `mitre` refs and `attack[]` values match their patterns. - `exploitation.first_seen` and `timeline[].date` are <= `updated`. ## `posture` (layer 3) The state of **one entity** facing the issue, expressed **in generic terms only**. This layer is designed to be shared with a group or sector CERT, which can aggregate it across entities without learning anything about their assets. **Hard rule: `posture` contains no free text, no numbers and no names.** Every value is an enum, a boolean, a date, or an identifier matching a pattern. If a need doesn't fit the enums, the enum is extended in the specification, not worked around in the record. | Field | Type | Required | Description | |---|---|---|---| | `tlp` | enum | yes | TLP of the layer. `AMBER` recommended. | | `updated` | date | yes | Date of this posture assessment. | | `entity` | string | no | Pseudonymous ID assigned by the receiving CERT (`^[A-Z0-9][A-Z0-9-]{0,31}$`). Never the entity name. | | `status` | object | yes | VEX-aligned status. | | `prerequisites` | object | no | What exploitation actually requires at this entity. | | `exposure` | object | no | Reachability and prevalence. | | `controls` | list of object | no | Controls in place against this issue. | | `detection` | object | no | Detection capability. | | `impact` | object | no | What exploitation enables at this entity. | | `remediation` | object | no | Feasibility and constraints of remediation. | `posture` has no score field: its contribution to scoring (the exposure bucket) is derived, see [Scoring](#scoring). ### `status` Aligned with [VEX](https://www.cisa.gov/sites/default/files/2023-04/minimum-requirements-for-vex-508c.pdf), so a posture can be exported as a VEX statement. | Field | Type | Required | Description | |---|---|---|---| | `state` | enum | yes | `not_affected`, `affected`, `mitigated`, `fixed`, `under_investigation`. | | `justification` | enum | if `not_affected` | `component_not_present`, `vulnerable_code_not_present`, `vulnerable_code_not_in_execute_path`, `vulnerable_code_cannot_be_controlled_by_adversary`, `inline_mitigations_already_exist`. | `mitigated` has no direct VEX equivalent and exports as `affected` with the controls as action statement. ### `prerequisites` What exploitation actually requires at this entity. It differs from CVSS, which describes the issue in the abstract (e.g. a network vulnerability only exploitable with a non-default feature enabled). | Field | Type | Description | |---|---|---| | `attack_vector` | enum | `network`, `adjacent`, `local`, `physical`. | | `privileges` | enum | `none`, `low`, `high`. | | `user_interaction` | boolean | | | `configuration` | enum | `default`, `non_default`. | | `conditions` | list of enum | `feature_enabled`, `specific_os`, `specific_version`, `network_position`, `valid_account`, `other`. | ### `exposure` | Field | Type | Description | |---|---|---| | `reachability` | enum | `internet`, `partner`, `internal`, `isolated`, `not_deployed`. | | `prevalence` | enum | `none`, `rare`, `common`, `widespread`. Qualitative bucket, never a count. | ### `controls[]` | Field | Type | Required | Description | |---|---|---|---| | `control` | enum | yes | `segmentation`, `waf`, `ips`, `edr`, `mfa`, `egress_filtering`, `hardening`, `app_allowlisting`, `virtual_patching`, `other`. | | `effect` | enum | yes | `blocks`, `reduces`, `detects`. | | `coverage` | enum | yes | `partial`, `full`. | | `validated` | boolean | yes | `true` if tested (purple team, pentest, simulation), `false` if assumed. | ### `detection` | Field | Type | Description | |---|---|---| | `capability` | enum | `none`, `partial`, `full`. | | `layers` | list of enum | `network`, `endpoint`, `logs`, `waf`, `application`. | | `validated` | boolean | `true` if detection was tested. | ### `impact` | Field | Type | Description | |---|---|---| | `enables` | list of enum | `code_execution`, `data_access`, `privilege_escalation`, `lateral_movement`, `persistence`, `denial_of_service`. | | `blast_radius` | enum | `component`, `host`, `segment`, `domain`, `organization`, `supply_chain`. | | `chained_with` | list of string | CIR IDs or CVE IDs this issue chains with. | ### `remediation` | Field | Type | Description | |---|---|---| | `feasibility` | enum | `easy`, `moderate`, `hard`, `blocked`. | | `constraints` | list of enum | `legacy`, `vendor_dependency`, `downtime_required`, `certification`, `third_party_managed`. | ### `posture` validation - No field outside this specification (unknown keys are rejected, so no free text can be added). - All values match their enum, type or pattern. - `status.justification` is present if and only if `status.state` is `not_affected`. - `chained_with` entries match a CIR or CVE pattern. ## `context` (layer 4) What the issue means for **one organization**: affected assets, business impact, decisions, remediation and communication. This layer never leaves the organization (`RED` by default). Unlike `posture`, free text and names are allowed, but structured fields come first. | Field | Type | Required | Description | |---|---|---|---| | `tlp` | enum | yes | TLP of the layer. `RED` recommended. | | `updated` | date | yes | Last update of this layer. | | `org` | string | no | Organization or business unit. | | `assets` | list of object | no | Key assets, inline. | | `asset_selectors` | list of object | no | Queries against the source of truth, for large volumes. | | `exploitation` | object | no | Whether the issue was exploited in this organization. | | `business_impact` | object | no | Business and regulatory impact. | | `decision` | object | no | Treatment decision. | | `remediation` | object | no | Remediation tracking. | | `communication` | list of object | no | Who was informed, when, how. | | `score` | object | no | Organizational risk, computed. See [Scoring](#scoring). | | `notes` | list of object | no | Free-text notes. | ### `assets[]` | Field | Type | Required | Description | |---|---|---|---| | `ref` | string | yes | Asset ID in the CMDB or inventory. | | `name` | string | no | | | `type` | enum | no | `server`, `workstation`, `application`, `database`, `network_device`, `cloud_resource`, `container`, `saas`, `ot`, `other`. | | `environment` | enum | no | `prod`, `preprod`, `dev`, `test`. | | `exposure` | enum | no | `internet`, `partner`, `internal`, `isolated`. | | `criticality` | enum | no | `low`, `medium`, `high`, `critical`. | | `data_classification` | enum | no | `public`, `internal`, `confidential`, `secret`. | | `owner` | string | no | | | `state` | enum | no | `affected`, `mitigated`, `fixed`, `not_affected`. | ### `asset_selectors[]` | Field | Type | Required | Description | |---|---|---|---| | `source` | enum | yes | `cmdb`, `edr`, `scanner`, `cloud`, `other`. | | `query` | string | yes | Query in the source's own syntax. | | `matched` | integer | no | Number of assets matched at `context.updated`. | ### `exploitation` | Field | Type | Required | Description | |---|---|---|---| | `status` | enum | yes | `none`, `suspected`, `confirmed`. | | `incident_ref` | string | if `suspected` or `confirmed` | Incident ticket. CIR references incidents, it does not describe them. | ### `business_impact` | Field | Type | Description | |---|---|---| | `confidentiality` / `integrity` / `availability` | enum | `none`, `low`, `medium`, `high`, `critical`. | | `services` | list of string | Impacted business processes. | | `data_at_risk` | list of enum | `personal`, `financial`, `health`, `credentials`, `ip`, `other`. | | `regulatory` | list of enum | `gdpr`, `nis2`, `dora`, `pci_dss`, `hds`, `other`. | ### `decision` | Field | Type | Required | Description | |---|---|---|---| | `action` | enum | yes | `fix`, `mitigate`, `accept`, `transfer`, `monitor`, `false_positive`. | | `justification` | string | no | | | `decided_by` | string | no | | | `decided_on` | date | no | | | `review_on` | date | if `accept`, `transfer` or `monitor` | A risk that is not fixed must be reviewed. Must be > `decided_on`. | | `approved_by` | string | no | | ### `remediation` | Field | Type | Required | Description | |---|---|---|---| | `owner` | string | no | Responsible team. | | `assignee` | string | no | | | `sla` | string | no | Internal SLA class, e.g. `P1-7d`. | | `due` | date | no | Stored explicitly. Computing it from a policy is the tooling's job, not the format's. | | `status` | enum | yes | `open`, `in_progress`, `resolved`, `verified`, `reopened`. | | `tickets[]` | list of object | no | `system` (`jira`, `plane`, `servicenow`, `gitlab`, `github`, `other`), `id` (required), `url`. | | `verified_on` | date | if `verified` | | | `verified_by` | string | no | | | `verification_method` | enum | no | `rescan`, `pentest`, `manual`, `other`. | ### `communication[]` The communication log is what makes a CIR a shared reference across teams: who was informed, when and through which channel. | Field | Type | Required | Description | |---|---|---|---| | `audience` | enum | yes | `soc`, `it_ops`, `dev`, `ciso`, `management`, `legal`, `dpo`, `business`, `cert`, `vendor`, `regulator`. | | `date` | date | yes | | | `channel` | enum | no | `email`, `ticket`, `meeting`, `chat`, `report`, `other`. | | `by` | string | no | | ### `score` | Field | Type | Description | |---|---|---| | `value` | number 0 to 10 | Organizational risk. | | `level` | enum | `none`, `low`, `medium`, `high`, `critical`. | | `method` | string | `cir-risk/0.1`, or an organization-specific method name. | ### `notes[]` | Field | Type | Required | |---|---|---| | `date` | date | yes | | `author` | string | no | | `text` | string | yes | ### `context` validation - `decision.review_on` is required when `decision.action` is `accept`, `transfer` or `monitor`, and must be after `decided_on`. - `exploitation.incident_ref` is required when `exploitation.status` is `suspected` or `confirmed`. - `remediation.verified_on` is required when `remediation.status` is `verified`. ## Scoring Method: **`cir-risk/0.1`**. Principles: - **Deterministic**: scores are computed from the record fields only, never typed in. - **Explainable**: additive points that a human can recompute in a meeting. - **Versioned**: a stored score names its `method`; validators recompute it and reject mismatches. - **Comparable**: the method is fixed, so a `high` means the same thing in every organization. An organization may store its own score under another `method` name, but `cir-risk` remains the comparable reference. ``` core threat_intel posture context | | | | SEVERITY EXPLOITATION EXPOSURE IMPACT 0-3 0-3 0-3 0-3 +-------+--------+ | | | | | PUBLIC PRIORITY (0-6) | | +--------------+-----------+------------------+ | ORG RISK (0-12) + overrides ``` Inherited layers (`meta.based_on`) count as present. ### Inputs **Severity** (from `core.severity`). The retained score is chosen by source (`issuer` > `vendor` > `nvd`), then by version (`cvss_v4.0` > `cvss_v3.1`). | Retained CVSS score | Points | |---|---| | >= 9.0 | 3 | | >= 7.0 | 2 | | >= 4.0 | 1 | | < 4.0 | 0 | | no severity | 3 (worst case) | **Exploitation** (from `threat_intel`). | Bucket | Condition | Points | |---|---|---| | `active` | `exploitation.in_the_wild` is `true` or `signals.kev.listed` is `true` | 3 | | `public` | `exploitation.maturity` is `poc`, `functional` or `weaponized` | 2 | | `none` | otherwise | 0 | | unknown | no `threat_intel` layer | 2 (treated as `public`) | An unknown exploitation is treated as `public`, not `active`: internal issues rarely have threat intelligence, and an internal finding (e.g. pentest) implies a working proof of concept. **Exposure** (from `posture`). The first matching bucket applies. | Bucket | Condition | Points | |---|---|---| | `none` | `status.state` is `not_affected` or `fixed`, or `exposure.reachability` is `not_deployed` | override, see below | | `contained` | `exposure.reachability` is `isolated`, or a control has `effect: blocks`, `coverage: full`, `validated: true` | 0 | | `controlled` | `exposure.reachability` is `internal`, or a control has `effect: blocks` (partial or not validated), or a control has `effect: reduces`, `coverage: full`, `validated: true` | 2 | | `open` | otherwise | 3 | | unknown | no `posture` layer | 3 (worst case) | **Impact** (from `context`). The highest value among `assets[].criticality`, `business_impact.confidentiality`, `business_impact.integrity` and `business_impact.availability`. | Highest value | Points | |---|---| | `critical` | 3 | | `high` | 2 | | `medium` | 1 | | `low`, `none` | 0 | | unknown | 3 (worst case) | Unknown severity, exposure and impact default to the worst case: an unknown risk is treated as a dangerous one, which also pushes organizations to fill their layers. ### Public priority Computable without any organizational data. Stored in `threat_intel.priority`. ``` points = severity + exploitation (0 to 6) value = round(points / 6 * 10, 1) ``` | Points | Level | |---|---| | 5 to 6 | `critical` | | 4 | `high` | | 2 to 3 | `medium` | | 0 to 1 | `low` | ### Organizational risk Stored in `context.score`. ``` points = severity + exploitation + exposure + impact (0 to 12) value = round(points / 12 * 10, 1) ``` | Points | Level | |---|---| | 10 to 12 | `critical` | | 7 to 9 | `high` | | 4 to 6 | `medium` | | 0 to 3 | `low` | Overrides, applied in order: 1. Exposure bucket `none` → level `none`, value `0`. 2. `context.exploitation.status` is `confirmed` → level `critical` (value unchanged). The issue is an incident. 3. `signals.kev.listed` is `true` and exposure bucket is `open` → level at least `high`. ### Worked examples | Record | Severity | Exploitation | Exposure | Impact | Result | |---|---|---|---|---|---| | `CIR-2021-00-0001` | 3 (CVSS 10.0) | 3 (KEV, in the wild) | n/a | n/a | Public priority 6 → **10.0 critical** | | `CIR-2021-GRP-0003` | 3 (inherited) | 3 (inherited) | 0 (egress filtering blocks, full, validated) | 3 (critical asset) | 9 → **7.5 high** | | `CIR-2026-LAB-0012` | 1 (CVSS 6.5) | 2 (unknown) | 3 (no posture) | 3 (no context) | 9 → **7.5 high** until the organization fills its layers | # Example records ## CIR-2021-00-0001 ```yaml # Public vulnerability: Log4Shell (CVE-2021-44228) cir_version: "0.5" meta: id: CIR-2021-00-0001 revision: 1 title: "Log4Shell: JNDI lookup RCE in Apache Log4j 2" status: published created: 2026-10-12 updated: 2026-10-12 tlp: CLEAR aliases: [log4shell] core: tlp: CLEAR summary: "Remote code execution via JNDI lookups in logged strings in Apache Log4j 2." details: | Log4j 2 evaluates `${jndi:...}` lookups in logged messages. An attacker who controls any logged string (HTTP header, form field, username...) can make the application load and execute a remote Java class over LDAP/RMI. disclosed: 2021-12-10 identifiers: - type: cve id: CVE-2021-44228 - type: ghsa id: GHSA-jfh8-c2jp-5v3q references: - type: advisory url: https://logging.apache.org/log4j/2.x/security.html - type: report url: https://nvd.nist.gov/vuln/detail/CVE-2021-44228 affected: - product: vendor: Apache name: log4j-core purl: pkg:maven/org.apache.logging.log4j/log4j-core ranges: - type: ecosystem events: - introduced: "2.0-beta9" - fixed: "2.3.1" - introduced: "2.4" - fixed: "2.12.2" - introduced: "2.13.0" - fixed: "2.15.0" weaknesses: [CWE-502, CWE-400, CWE-20, 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 remediations: - type: upgrade description: "Upgrade log4j-core to 2.17.1+ (Java 8), 2.12.4+ (Java 7) or 2.3.2+ (Java 6), which also cover the follow-up CVEs." status: available reference: https://logging.apache.org/log4j/2.x/security.html - type: workaround description: "Remove the JndiLookup class from the classpath: zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class" status: available threat_intel: tlp: CLEAR updated: 2026-10-12 exploitation: maturity: weaponized in_the_wild: true first_seen: 2021-12-01 exploits: - type: module source: metasploit url: https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/multi/http/log4shell_header_injection.rb priority: value: 10.0 level: critical method: cir-risk/0.1 signals: kev: listed: true date_added: 2021-12-10 ransomware_use: known epss: score: 0.99999 percentile: 1.0 actors: - refs: [{ type: mitre, id: G0059 }] name: Magic Hound (APT35) confidence: high sources: [s2] - refs: [{ type: mitre, id: G0143 }] name: Aquatic Panda confidence: high sources: [s2] - refs: [{ type: mitre, id: G0032 }] name: Lazarus Group confidence: medium sources: [s2] malware: - name: Khonsari family_type: ransomware confidence: high sources: [s2] - name: Mirai family_type: botnet confidence: high sources: [s2] attack: - { technique: T1190, tactic: TA0001 } - { technique: T1059, tactic: TA0002 } - { technique: T1105, tactic: TA0011 } targeting: sectors: [all] iocs: - type: pattern value: "${jndi:" confidence: high sources: [s3] detections: - format: sigma name: "Log4j RCE CVE-2021-44228 (SigmaHQ)" url: https://github.com/SigmaHQ/sigma timeline: - { date: 2021-12-01, event: "Earliest observed exploitation", sources: [s3] } - { date: 2021-12-09, event: "Public PoC released", sources: [s3] } - { date: 2021-12-10, event: "Added to CISA KEV", sources: [s1] } sources: - { id: s1, name: "CISA KEV", url: "https://www.cisa.gov/known-exploited-vulnerabilities-catalog", reliability: A } - { id: s2, name: "MITRE ATT&CK and vendor reports", url: "https://attack.mitre.org" } - { id: s3, name: "Cloudflare", url: "https://blog.cloudflare.com/exploitation-of-cve-2021-44228-before-public-disclosure-and-evolution-of-waf-evasion-patterns/" } ``` ## CIR-2021-GRP-0003 ```yaml # Internal record extending a public one (fictional organization). # core and threat_intel are inherited from the parent (meta.based_on). # Exported to the group CERT, the TLP filter keeps posture (AMBER) and drops context (RED). 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-00-0001 posture: tlp: AMBER updated: 2021-12-14 entity: GRP-ENT-07 status: state: mitigated prerequisites: attack_vector: network privileges: none user_interaction: false configuration: default conditions: [] exposure: reachability: internet prevalence: common controls: - { control: waf, effect: reduces, coverage: partial, validated: true } - { control: egress_filtering, effect: blocks, coverage: full, validated: true } - { control: edr, effect: detects, coverage: partial, validated: false } detection: capability: partial layers: [waf, network, endpoint] validated: true impact: enables: [code_execution, lateral_movement] blast_radius: segment chained_with: [] remediation: feasibility: hard constraints: [vendor_dependency, legacy] context: tlp: RED updated: 2021-12-14 org: "Filiale Sud" assets: - ref: CMDB-004812 name: "portail-clients" type: application environment: prod exposure: internet criticality: critical data_classification: confidential owner: "Equipe Digital" state: mitigated asset_selectors: - source: scanner query: "cve:CVE-2021-44228 AND state:open" matched: 37 exploitation: status: suspected incident_ref: "INC-2021-1207" business_impact: confidentiality: high integrity: high availability: medium services: ["Espace client", "Paiement en ligne"] data_at_risk: [personal, financial] regulatory: [gdpr, pci_dss] decision: action: mitigate justification: "Vendor patch unavailable for the payment appliance; WAF and egress filtering in the meantime." decided_by: "CISO" decided_on: 2021-12-11 review_on: 2021-12-21 approved_by: "CIO" remediation: owner: "Infra team" sla: "P1-7d" due: 2021-12-18 status: in_progress tickets: - { system: jira, id: SEC-3321, url: "https://jira.example.internal/browse/SEC-3321" } communication: - { audience: soc, date: 2021-12-10, channel: chat, by: "Internal CERT" } - { audience: management, date: 2021-12-11, channel: meeting, by: "CISO" } - { audience: dpo, date: 2021-12-11, channel: email, by: "CISO" } - { audience: cert, date: 2021-12-14, channel: report, by: "CISO" } score: value: 7.5 level: high method: cir-risk/0.1 notes: - date: 2021-12-12 author: "SOC" text: "JNDI attempts blocked by the WAF since 2021-12-10; one suspicious outbound LDAP request under analysis." ``` ## CIR-2026-LAB-0012 ```yaml # Internal issue: pentest finding, no CVE (fictional organization) 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 authors: - name: "Red team" org: "Acme" core: tlp: AMBER summary: "Any authenticated user can export another customer's invoices by changing the invoice ID." details: | `GET /api/v2/invoices/{id}/export` checks authentication but not ownership. Invoice IDs are sequential, making enumeration trivial. disclosed: 2026-09-28 identifiers: - type: internal id: "PENTEST-2026-Q3-07" references: - type: report url: https://git.acme.internal/sec/pentests/2026-q3#finding-07 - type: fix url: https://git.acme.internal/billing/billing-api/-/merge_requests/482 affected: - product: vendor: Acme name: billing-api ranges: - type: git repo: https://git.acme.internal/billing/billing-api events: - introduced: "a3f9c21" - fixed: "e71b0d4" 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 remediations: - type: patch description: "Ownership check added on invoice export; IDs moved to UUIDv7." status: available date: 2026-10-04 ```