Specification · draft v0.1

The format, field by field.

Types, enums, required fields and validation rules for every layer, plus the scoring method. This page is rendered from SPEC.md, which remains the reference.

Status: draft (v0.1). 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: CLEAR, GREEN, AMBER, AMBER+STRICT, RED.

Root

FieldTypeRequiredDescription
cir_versionstringyesVersion of the CIR specification the record follows, e.g. "0.1".
metaobjectyesIdentity and lifecycle of the record.
coreobjectnoLayer 1.
threat_intelobjectnoLayer 2.
postureobjectnoLayer 3.
contextobjectnoLayer 4.
tagslist of stringnoFree classification tags.

meta

FieldTypeRequiredDescription
idstringyesRecord identifier, see Identifier rules.
revisioninteger >= 1yesIncremented on every update of the record.
titlestringyesShort descriptive title.
statusenumyesdraft, published, rejected, withdrawn.
createddateyesCreation date of the record.
updateddateyesLast update date. Must be >= created.
tlpenumnoTLP ceiling for the whole record, see TLP rules.
based_onstringnoid of a parent CIR this record extends (typically an internal record adding posture / context to a public one).
aliaseslist of stringnoAlternative names, lowercase slugs (^[a-z0-9]+(-[a-z0-9]+)*$).
authorslist of objectnoname (required), contact, org.
historylist of objectnodate (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. 00 is the root authority.
  • Internal record: ^CIR-\d{4}-[A-Z1-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 is 0); there is no separate field for it.

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 two patterns above.
  • Public id → CODE exists in authorities.yml.
  • 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:

FieldTypeRequiredDescription
codestringyesPublic CODE, ^0[A-Z0-9]{1,7}$. Unique in the registry.
namestringyesName of the issuing organization.
contactstringnoContact address.
scopestringyesWhat the authority publishes.
fingerprintstringyes, except 00OpenPGP fingerprint of the signing key, 40 uppercase hex chars, no spaces.
public_keystringyes, except 00ASCII-armored OpenPGP public key. Its fingerprint must equal fingerprint.
key_expiresdateyes if the key expiresExpiration 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.

core (layer 1)

What the issue is. core references existing standards and never redefines them. Products and versions follow the OSV model so records can be imported from and exported to OSV.

FieldTypeRequiredDescription
tlpenumyesTLP of the layer.
summarystringyesOne-line summary.
detailsstring (markdown)noFull description.
discloseddatenoPublic disclosure date, or internal report date for internal records.
identifierslist of objectnoExternal identifiers designating this issue.
referenceslist of objectnoTyped links.
affectedlist of objectnoAffected products and versions.
weaknesseslist of stringnoCWE identifiers (^CWE-\d+$).
severitylist of objectnoSeverity scores, possibly from several sources.
remediationslist of objectnoFixes, mitigations and workarounds.

identifiers[]

FieldTypeRequiredDescription
typeenumyescve, ghsa, osv, vendor, internal, other.
idstringyesThe 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[]

FieldTypeRequiredDescription
typeenumyesadvisory, fix, report, article, package, web, other.
urlstring (URL)yes

Exploits and PoCs are not references: they belong to threat_intel.

affected[]

FieldTypeRequiredDescription
product.vendorstringno
product.namestringyes
product.purlstringnoPackage URL.
product.cpestringnoCPE 2.3 string.
rangeslist of objectnoVersion ranges.
versionslist of stringnoExplicit affected versions, when no range applies.

At least one of ranges or versions is required.

ranges[]

FieldTypeRequiredDescription
typeenumyessemver, ecosystem, git.
repostring (URL)if gitRepository the commits belong to.
eventslist of objectyesOrdered 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[]

FieldTypeRequiredDescription
typeenumyescvss_v3.1, cvss_v4.0.
vectorstringyesFull vector string including the CVSS:x.y/ prefix.
scorenumber 0.0 to 10.0yesBase score. Must match the score computed from vector.
sourceenumyesnvd, vendor, issuer.

remediations[]

FieldTypeRequiredDescription
typeenumyesupgrade, patch, config, workaround, none.
descriptionstringyes
statusenumyesavailable, planned, none.
datedatenoDate the remediation became available.
referencestring (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.

FieldTypeRequiredDescription
tlpenumyesTLP of the layer.
updateddateyesFreshness of the layer. Also the snapshot date of every value in signals.
exploitationobjectnoExploit availability and in-the-wild activity.
priorityobjectnoPublic priority, computed. See Scoring.
signalsobjectnoSnapshots of external feeds.
actorslist of entitynoThreat actors.
malwarelist of entitynoMalware families. Adds family_type.
campaignslist of entitynoCampaigns.
attacklist of objectnoMITRE ATT&CK technique/tactic pairs.
targetingobjectnoTargeted sectors and regions.
iocslist of objectnoInline indicators (small volume).
ioc_bundleslist of objectnoExternal indicator bundles (large volume).
detectionslist of objectnoPublic detection rules.
timelinelist of objectnoDated events.
sourceslist of objectnoSources referenced by the other fields.

Confidence

Every assertion that carries a confidence uses: low, medium, high, critical.

exploitation

FieldTypeRequiredDescription
maturityenumnonone, poc, functional, weaponized.
in_the_wildenumnotrue, false, unknown.
first_seendatenoFirst observed exploitation.
exploits[]list of objectnotype (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.

FieldTypeDescription
kev.listedbooleanListed in CISA KEV.
kev.date_addeddateKEV dateAdded.
kev.ransomware_useenumKEV knownRansomwareCampaignUse: known, unknown.
epss.scorenumber 0 to 1EPSS probability.
epss.percentilenumber 0 to 1EPSS percentile.

Entities (actors[], malware[], campaigns[])

FieldTypeRequiredDescription
refslist of objectnoIdentifiers of the same entity in several namespaces: type (mitre, mlab, misp, malpedia, other) and id.
namestringyesDisplay name.
confidenceenumnoConfidence that this entity is linked to the issue.
first_seendatenoFirst observed link.
sourceslist of stringnoids from sources[].
family_typeenumnomalware[] 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[]

FieldTypeRequiredDescription
techniquestringyes^T\d{4}(\.\d{3})?$
tacticstringyes^TA\d{4}$

targeting

FieldTypeDescription
sectorslist of stringall or sectors (recommended list: finance, health, energy, government, telecom, education, manufacturing, retail, transport, defense, technology).
regionslist of stringISO 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.

FieldTypeRequiredDescription
iocs[].typeenumyesip, domain, url, sha256, sha1, md5, pattern, other.
iocs[].valuestringyes
iocs[].first_seen / last_seendateno
iocs[].confidenceenumno
iocs[].sourceslist of stringno
ioc_bundles[].formatenumyesstix, misp, csv.
ioc_bundles[].urlstring (URL)yes

detections[]

Public detection rules only. What an organization actually deploys never goes here (see posture).

FieldTypeRequiredDescription
formatenumyessigma, yara, suricata, snort, nuclei, kql, spl.
namestringno
urlstring (URL)yes

timeline[] and sources[]

FieldTypeRequiredDescription
timeline[].datedateyes
timeline[].eventstringyes
timeline[].sourceslist of stringno
sources[].idstringyesLocal to the record, unique, e.g. s1.
sources[].namestringyes
sources[].urlstring (URL)no
sources[].datedateno
sources[].reliabilityenumnoAdmiralty 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.

FieldTypeRequiredDescription
tlpenumyesTLP of the layer. AMBER recommended.
updateddateyesDate of this posture assessment.
entitystringnoPseudonymous ID assigned by the receiving CERT (^[A-Z0-9][A-Z0-9-]{0,31}$). Never the entity name.
statusobjectyesVEX-aligned status.
prerequisitesobjectnoWhat exploitation actually requires at this entity.
exposureobjectnoReachability and prevalence.
controlslist of objectnoControls in place against this issue.
detectionobjectnoDetection capability.
impactobjectnoWhat exploitation enables at this entity.
remediationobjectnoFeasibility and constraints of remediation.

posture has no score field: its contribution to scoring (the exposure bucket) is derived, see Scoring.

status

Aligned with VEX, so a posture can be exported as a VEX statement.

FieldTypeRequiredDescription
stateenumyesnot_affected, affected, mitigated, fixed, under_investigation.
justificationenumif not_affectedcomponent_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).

FieldTypeDescription
attack_vectorenumnetwork, adjacent, local, physical.
privilegesenumnone, low, high.
user_interactionboolean
configurationenumdefault, non_default.
conditionslist of enumfeature_enabled, specific_os, specific_version, network_position, valid_account, other.

exposure

FieldTypeDescription
reachabilityenuminternet, partner, internal, isolated, not_deployed.
prevalenceenumnone, rare, common, widespread. Qualitative bucket, never a count.

controls[]

FieldTypeRequiredDescription
controlenumyessegmentation, waf, ips, edr, mfa, egress_filtering, hardening, app_allowlisting, virtual_patching, other.
effectenumyesblocks, reduces, detects.
coverageenumyespartial, full.
validatedbooleanyestrue if tested (purple team, pentest, simulation), false if assumed.

detection

FieldTypeDescription
capabilityenumnone, partial, full.
layerslist of enumnetwork, endpoint, logs, waf, application.
validatedbooleantrue if detection was tested.

impact

FieldTypeDescription
enableslist of enumcode_execution, data_access, privilege_escalation, lateral_movement, persistence, denial_of_service.
blast_radiusenumcomponent, host, segment, domain, organization, supply_chain.
chained_withlist of stringCIR IDs or CVE IDs this issue chains with.

remediation

FieldTypeDescription
feasibilityenumeasy, moderate, hard, blocked.
constraintslist of enumlegacy, 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.

FieldTypeRequiredDescription
tlpenumyesTLP of the layer. RED recommended.
updateddateyesLast update of this layer.
orgstringnoOrganization or business unit.
assetslist of objectnoKey assets, inline.
asset_selectorslist of objectnoQueries against the source of truth, for large volumes.
exploitationobjectnoWhether the issue was exploited in this organization.
business_impactobjectnoBusiness and regulatory impact.
decisionobjectnoTreatment decision.
remediationobjectnoRemediation tracking.
communicationlist of objectnoWho was informed, when, how.
scoreobjectnoOrganizational risk, computed. See Scoring.
noteslist of objectnoFree-text notes.

assets[]

FieldTypeRequiredDescription
refstringyesAsset ID in the CMDB or inventory.
namestringno
typeenumnoserver, workstation, application, database, network_device, cloud_resource, container, saas, ot, other.
environmentenumnoprod, preprod, dev, test.
exposureenumnointernet, partner, internal, isolated.
criticalityenumnolow, medium, high, critical.
data_classificationenumnopublic, internal, confidential, secret.
ownerstringno
stateenumnoaffected, mitigated, fixed, not_affected.

asset_selectors[]

FieldTypeRequiredDescription
sourceenumyescmdb, edr, scanner, cloud, other.
querystringyesQuery in the source's own syntax.
matchedintegernoNumber of assets matched at context.updated.

exploitation

FieldTypeRequiredDescription
statusenumyesnone, suspected, confirmed.
incident_refstringif suspected or confirmedIncident ticket. CIR references incidents, it does not describe them.

business_impact

FieldTypeDescription
confidentiality / integrity / availabilityenumnone, low, medium, high, critical.
serviceslist of stringImpacted business processes.
data_at_risklist of enumpersonal, financial, health, credentials, ip, other.
regulatorylist of enumgdpr, nis2, dora, pci_dss, hds, other.

decision

FieldTypeRequiredDescription
actionenumyesfix, mitigate, accept, transfer, monitor, false_positive.
justificationstringno
decided_bystringno
decided_ondateno
review_ondateif accept, transfer or monitorA risk that is not fixed must be reviewed. Must be > decided_on.
approved_bystringno

remediation

FieldTypeRequiredDescription
ownerstringnoResponsible team.
assigneestringno
slastringnoInternal SLA class, e.g. P1-7d.
duedatenoStored explicitly. Computing it from a policy is the tooling's job, not the format's.
statusenumyesopen, in_progress, resolved, verified, reopened.
tickets[]list of objectnosystem (jira, plane, servicenow, gitlab, github, other), id (required), url.
verified_ondateif verified
verified_bystringno
verification_methodenumnorescan, 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.

FieldTypeRequiredDescription
audienceenumyessoc, it_ops, dev, ciso, management, legal, dpo, business, cert, vendor, regulator.
datedateyes
channelenumnoemail, ticket, meeting, chat, report, other.
bystringno

score

FieldTypeDescription
valuenumber 0 to 10Organizational risk.
levelenumnone, low, medium, high, critical.
methodstringcir-risk/0.1, or an organization-specific method name.

notes[]

FieldTypeRequired
datedateyes
authorstringno
textstringyes

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 scorePoints
>= 9.03
>= 7.02
>= 4.01
< 4.00
no severity3 (worst case)

Exploitation (from threat_intel).

BucketConditionPoints
activeexploitation.in_the_wild is true or signals.kev.listed is true3
publicexploitation.maturity is poc, functional or weaponized2
noneotherwise0
unknownno threat_intel layer2 (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.

BucketConditionPoints
nonestatus.state is not_affected or fixed, or exposure.reachability is not_deployedoverride, see below
containedexposure.reachability is isolated, or a control has effect: blocks, coverage: full, validated: true0
controlledexposure.reachability is internal, or a control has effect: blocks (partial or not validated), or a control has effect: reduces, coverage: full, validated: true2
openotherwise3
unknownno posture layer3 (worst case)

Impact (from context). The highest value among assets[].criticality, business_impact.confidentiality, business_impact.integrity and business_impact.availability.

Highest valuePoints
critical3
high2
medium1
low, none0
unknown3 (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)
PointsLevel
5 to 6critical
4high
2 to 3medium
0 to 1low

Organizational risk

Stored in context.score.

points = severity + exploitation + exposure + impact     (0 to 12)
value  = round(points / 12 * 10, 1)
PointsLevel
10 to 12critical
7 to 9high
4 to 6medium
0 to 3low

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

RecordSeverityExploitationExposureImpactResult
CIR-2021-00-00013 (CVSS 10.0)3 (KEV, in the wild)n/an/aPublic priority 6 → 10.0 critical
CIR-2021-GRP-00033 (inherited)3 (inherited)0 (egress filtering blocks, full, validated)3 (critical asset)9 → 7.5 high
CIR-2026-LAB-00121 (CVSS 6.5)2 (unknown)3 (no posture)3 (no context)9 → 7.5 high until the organization fills its layers