Docs
What a sighting must contain, and what the validator checks. The field reference is generated from the schema files when the site is built, so it cannot drift from what the validator enforces. Submit a sighting covers the mechanics of filing one.
What tier you can claim
Your claimed_tier is a hint. A reviewer grades the evidence against the rubric and records review.accepted_tier, which is often lower. That is normal. It is not a rejection.
This table says what each kind of evidence supports, and what it does not.
| Row | Evidence | Supports up to | Does not support |
|---|---|---|---|
| 1 | Honeypot or WAF log showing version probes, path enumeration, or fingerprinting requests tied to the vulnerable component | scanning | attempts (no payload) |
| 2 | Captured exploit payload with no execution evidence, or with HTTP 500 and no child process | attempts | poc-validated (no fingerprint reproduction) |
| 3 | Public PoC exists but no reproduction recorded | attempts | poc-validated (existence is not validation) |
| 4 | Core maintainer or two independent orgs reproduce RCE against a recorded fingerprint | poc-validated for that fingerprint | confirmed (lab, not a victim) |
| 5 | Vendor says "exploited in the wild" without artifacts | attempts | anything higher |
| 6 | Aggregate telemetry ("we blocked 2M requests") | attempts | confirmed (blocks are not compromises) |
| 7 | IR artifacts from a real system (webshell, cron, outbound C2) attributed to the CVE by someone with system access | confirmed | n/a |
| 8 | Third-party report of a compromise with no first-hand access | attempts, with source_url | confirmed |
Two timestamps
This is the most common mistake. observed_at is when the activity happened. reported_at is when it was written up.
Use the observation time. It drives every date on the entry, including first_sighting, and first_sighting is the number embargo arguments turn on. A sighting seen on 17 October and written up on 22 October is dated 17 October.
How a sighting is structured
One observation submitted by a reporter. Sightings are the only thing contributors write; entries are derived from accepted sightings. Leave review out of your submission. Core writes it.
This table lists every field, nested ones with a dotted path.
| Field | Type | Required | Values | What it is for |
|---|---|---|---|---|
schema_version | string | required | 1.0 | Always 1.0 for this schema. |
id | string | required | ^SIGHT-CVE-\d{4}-\d{4,}-\d{4}$ | Sighting id: SIGHT-, the CVE, then a four digit sequence. Matches the file name. |
cve | string | required | ^CVE-\d{4}-\d{4,}$ | The CVE this observation is about. |
reporter | object | required | Who is reporting. A real org is what lets a sighting be graded above attempts. | |
reporter.org | string | required | at least 1 characters | Real entity. 'anonymous' is permitted and caps the accepted tier at 2. |
reporter.handle | string | required | at least 1 characters | Stable handle for the reporter inside their org. Not a personal name. |
reporter.contact_hash | string | if claimed_tier is poc-validated or confirmed | ^sha256:[0-9a-f]{64}$ | Hash of a contact address held privately by core. Required for tier 3 and 4. |
reporter.role | string | optional | trusted-reporter contributor core | Set by core, not claimed by the reporter. |
observed_at | string (date-time) | required | When the activity happened, never when it was reported. | |
reported_at | string (date-time) | required | Set by the system on submission. | |
claimed_tier | string | required | none scanning attempts poc-validated confirmed | The tier you believe the evidence supports. A hint, not a vote. A reviewer grades against the rubric. |
evidence_type | string | required | honeypot waf-log ids-alert ir-artifact poc-reproduction telemetry-aggregate third-party-report | What kind of evidence this is. The rubric reads it to set the ceiling on the tier. |
fingerprint | object, see fingerprint | if claimed_tier is poc-validated or confirmed | The deployment this observation applies to. Required for poc-validated and confirmed. | |
volume | object | optional | How much activity was seen. Context only. Counts never raise a tier. | |
volume.count | integer | optional | min 0 | Number of events seen in the window. |
volume.unique_sources | integer | optional | min 0 | Distinct sources behind the events. |
volume.window_hours | number | optional | above 0 | Length of the observation window, in hours. |
indicators | object | optional | No raw payloads, no PoC code, no victim identifiers. Automated checks reject them. | |
indicators.paths | array of string | optional | Request path patterns seen. | |
indicators.headers | array of string | optional | Header names or patterns seen. | |
indicators.user_agents | array of string | optional | User agent strings seen. | |
indicators.payload_sha256 | array of string | optional | ^[0-9a-f]{64}$ | SHA-256 hashes of captured payloads. Hashes only, never the payload. |
indicators.source_cidrs | array of string | optional | Aggregated ranges only. Expire 30 days after last_seen. | |
indicators.file_paths | array of string | optional | File paths dropped or touched on a compromised host. | |
indicators.process_cmdlines | array of string | optional | Process command lines observed, as indicators. Subject to the payload checks. | |
indicators.rule_refs | array of object | optional | Links to published detection rules. | |
indicators.rule_refs[].kind | string | required | sigma yara suricata snort | Rule language. |
indicators.rule_refs[].url | string (uri) | required | Where the rule is published. | |
indicators.rule_refs[].name | string | optional | Rule name. | |
outcome | string | if claimed_tier is poc-validated or confirmed; claimed_tier is attempts | unknown failed succeeded | Required when claimed_tier is attempts or higher. |
summary | string | required | 20 to 500 characters | What you saw, in a sentence or two. 20 to 500 characters. No payloads. |
source_url | string or null (uri) | optional | Public write-up, when the report is second-hand. Null when there is none. | |
signature | string | required | ^(sigstore|pgp):.+ | Signature over the canonical JSON of every field except review and signature. |
review | object | optional | Written only by core. | |
review.status | string | required | pending accepted rejected needs-info | Where the sighting is in review. Only accepted sightings count toward an entry. |
review.reviewer | string | optional | ^core/[a-z0-9-]+$ | The core reviewer who graded it. |
review.second_reviewer | string | optional | ^core/[a-z0-9-]+$ | Required for tier 4 acceptance. |
review.accepted_tier | string | optional | none scanning attempts poc-validated confirmed | The tier the rubric supports. Often lower than claimed_tier, which is normal. |
review.rubric_row | integer | optional | min 1, max 8 | Which row of the grading rubric applied. |
review.note | string | optional | up to 500 characters | Why it graded the way it did. |
review.reviewed_at | string (date-time) | optional | When the review was recorded. | |
review.recusals | array of string | optional | Reviewers who stepped back from this sighting. |
The conditional requirements
A required column cannot show these, and they are the ones people get wrong. The first rows come from the schema. The rest are checks the validator makes beyond it.
| When | Then | Enforced by |
|---|---|---|
| claimed_tier is poc-validated or confirmed | fingerprint outcome reporter.contact_hash | Schema |
| claimed_tier is attempts | outcome | Schema |
| A fingerprint is required for tier 3 and tier 4. | fingerprint must be present when claimed_tier or review.accepted_tier is poc-validated or confirmed. A tier belongs to a configuration, so a tier 3 claim with no fingerprint is rejected. | Validator |
| An anonymous reporter is capped at attempts. | A reporter.org that is empty or reads "anonymous" cannot claim, or be accepted at, poc-validated or confirmed. Name a real organisation to go higher. | Validator |
| The signature must be well formed. | signature must start with sigstore: or pgp: followed by the signature. The prototype checks the format only. It does not verify the signature cryptographically, so a well-formed signature is not yet proof of who sent the record. | Validator |
What gets rejected before a human sees it
Every string in the record is scanned, in every field. A match rejects the whole record before a person reads it. The record is rejected if any string in it contains one of these.
- A PHP open tag.
- A call to eval.
- A long run of base64-style characters, more than 200 in a row.
- A download piped straight into a shell.
- A recursive delete chained onto another command.
- A shell invoked with a command string.
This is deliberate and blunt. It catches payloads regardless of intent, so it will occasionally reject something harmless. If you think a rejection is wrong, say so in the pull request. Do not reword the string to get past the check.
Send hashes, header names, path patterns, aggregated volumes and links to published detection rules instead.
A worked example
SIGHT-CVE-2026-109618-0006 claimed confirmed and was accepted at attempts. It is a real record, read from the registry when this page was built.
{
"schema_version": "1.0",
"id": "SIGHT-CVE-2026-109618-0006",
"cve": "CVE-2026-109618",
"reporter": {
"org": "[WordPress security vendor]",
"handle": "threat-intel",
"contact_hash": "sha256:0000000000000000000000000000000000000000000000000000000000000006",
"role": "trusted-reporter"
},
"observed_at": "2026-10-21T07:34:00Z",
"reported_at": "2026-10-21T08:05:00Z",
"claimed_tier": "confirmed",
"evidence_type": "third-party-report",
"fingerprint": {
"runtime": "php",
"version": "unknown",
"sapi": "mod_php",
"server": "apache",
"app": "wordpress"
},
"volume": {
"count": 6,
"unique_sources": 3,
"window_hours": 4
},
"outcome": "unknown",
"summary": "Hosting partners reported multiple active exploitation incidents several hours before the scheduled release. No incident response artifacts shared with the Observatory; reports relayed second-hand.",
"source_url": "https://example.invalid/wp-vendor-post",
"signature": "sigstore:PLACEHOLDER",
"review": {
"status": "accepted",
"reviewer": "core/asmith",
"accepted_tier": "attempts",
"rubric_row": 8,
"note": "Claimed confirmed. Third-party report without first-hand artifacts supports attempts at most. Reporter asked to forward partner IR artifacts if available.",
"reviewed_at": "2026-10-21T09:12:00Z",
"recusals": []
}
}
Why it graded lower:
- Evidence type:
third-party-report. - Rubric row 8: Third-party report of a compromise with no first-hand access Supports up to
attempts, withsource_url. Does not supportconfirmed. - Reviewer note: Claimed confirmed. Third-party report without first-hand artifacts supports attempts at most. Reporter asked to forward partner IR artifacts if available.
The fingerprint
A tier belongs to a configuration. The same CVE can be validated against one SAPI and server pair and fail against another, and the entry shows both. The fingerprint is how a tier comes to mean something to a specific operator.
Describes the deployment an observation applies to. Sightings group by runtime, sapi, server and config_flags. The group id is the sha256 of those fields as canonical JSON, first 12 hex chars, prefixed 'fp-'. An unknown value counts as missing and never forms a group of its own.
Be exact. A vague fingerprint puts the sighting in the wrong group, or in none, and the operator running that stack reads a tier that does not describe them. Write unknown only when you do not know. Do not guess.
| Field | Type | Required | Values | What it is for |
|---|---|---|---|---|
runtime | string | required | php node python ruby java other | The language runtime. Part of the group key. Pick other if none fits. |
version | string | optional | up to 32 characters | Exact (8.3.35), range (<8.2.35), or branch (8.1.x). Recorded in the group, does not change which group. |
sapi | string | required | mod_php php-fpm php-cgi cli embedded n/a unknown | How the runtime is served. Part of the group key. For PHP this often decides the tier, so be exact. |
server | string | optional | apache nginx litespeed iis caddy other unknown | The web server in front of the runtime. Part of the group key. |
os | string | optional | up to 32 characters | distro-version, e.g. debian-12, alpine-3.20, windows. Recorded in the group, does not change which group. |
config_flags | array of string | optional | ^[a-zA-Z0-9_.]+=[a-zA-Z0-9_.\-]+$unique | Settings that change exposure, as name=value pairs. Part of the group key: a flag can be the vector, so two values of one flag are two groups. Sorted before the id is computed. |
app | string | optional | wordpress drupal joomla magento laravel symfony none other unknown | The application on top, when it matters to the exploit. Recorded in the group, does not change which group. |