Under development. Data in mock mode. Not real data.

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.

Grading rubric: each kind of evidence, the highest tier it supports and what it does not support.
RowEvidenceSupports up toDoes not support
1Honeypot or WAF log showing version probes, path enumeration, or fingerprinting requests tied to the vulnerable componentscanningattempts (no payload)
2Captured exploit payload with no execution evidence, or with HTTP 500 and no child processattemptspoc-validated (no fingerprint reproduction)
3Public PoC exists but no reproduction recordedattemptspoc-validated (existence is not validation)
4Core maintainer or two independent orgs reproduce RCE against a recorded fingerprintpoc-validated for that fingerprintconfirmed (lab, not a victim)
5Vendor says "exploited in the wild" without artifactsattemptsanything higher
6Aggregate telemetry ("we blocked 2M requests")attemptsconfirmed (blocks are not compromises)
7IR artifacts from a real system (webshell, cron, outbound C2) attributed to the CVE by someone with system accessconfirmedn/a
8Third-party report of a compromise with no first-hand accessattempts, with source_urlconfirmed

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.

Fields of a sighting: type, requirement, allowed values and purpose.
FieldTypeRequiredValuesWhat it is for
schema_versionstringrequired1.0Always 1.0 for this schema.
idstringrequired^SIGHT-CVE-\d{4}-\d{4,}-\d{4}$Sighting id: SIGHT-, the CVE, then a four digit sequence. Matches the file name.
cvestringrequired^CVE-\d{4}-\d{4,}$The CVE this observation is about.
reporterobjectrequiredWho is reporting. A real org is what lets a sighting be graded above attempts.
reporter.orgstringrequiredat least 1 charactersReal entity. 'anonymous' is permitted and caps the accepted tier at 2.
reporter.handlestringrequiredat least 1 charactersStable handle for the reporter inside their org. Not a personal name.
reporter.contact_hashstringif 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.rolestringoptionaltrusted-reporter contributor coreSet by core, not claimed by the reporter.
observed_atstring (date-time)requiredWhen the activity happened, never when it was reported.
reported_atstring (date-time)requiredSet by the system on submission.
claimed_tierstringrequirednone scanning attempts poc-validated confirmedThe tier you believe the evidence supports. A hint, not a vote. A reviewer grades against the rubric.
evidence_typestringrequiredhoneypot waf-log ids-alert ir-artifact poc-reproduction telemetry-aggregate third-party-reportWhat kind of evidence this is. The rubric reads it to set the ceiling on the tier.
fingerprintobject, see fingerprintif claimed_tier is poc-validated or confirmedThe deployment this observation applies to. Required for poc-validated and confirmed.
volumeobjectoptionalHow much activity was seen. Context only. Counts never raise a tier.
volume.countintegeroptionalmin 0Number of events seen in the window.
volume.unique_sourcesintegeroptionalmin 0Distinct sources behind the events.
volume.window_hoursnumberoptionalabove 0Length of the observation window, in hours.
indicatorsobjectoptionalNo raw payloads, no PoC code, no victim identifiers. Automated checks reject them.
indicators.pathsarray of stringoptionalRequest path patterns seen.
indicators.headersarray of stringoptionalHeader names or patterns seen.
indicators.user_agentsarray of stringoptionalUser agent strings seen.
indicators.payload_sha256array of stringoptional^[0-9a-f]{64}$SHA-256 hashes of captured payloads. Hashes only, never the payload.
indicators.source_cidrsarray of stringoptionalAggregated ranges only. Expire 30 days after last_seen.
indicators.file_pathsarray of stringoptionalFile paths dropped or touched on a compromised host.
indicators.process_cmdlinesarray of stringoptionalProcess command lines observed, as indicators. Subject to the payload checks.
indicators.rule_refsarray of objectoptionalLinks to published detection rules.
indicators.rule_refs[].kindstringrequiredsigma yara suricata snortRule language.
indicators.rule_refs[].urlstring (uri)requiredWhere the rule is published.
indicators.rule_refs[].namestringoptionalRule name.
outcomestringif claimed_tier is poc-validated or confirmed; claimed_tier is attemptsunknown failed succeededRequired when claimed_tier is attempts or higher.
summarystringrequired20 to 500 charactersWhat you saw, in a sentence or two. 20 to 500 characters. No payloads.
source_urlstring or null (uri)optionalPublic write-up, when the report is second-hand. Null when there is none.
signaturestringrequired^(sigstore|pgp):.+Signature over the canonical JSON of every field except review and signature.
reviewobjectoptionalWritten only by core.
review.statusstringrequiredpending accepted rejected needs-infoWhere the sighting is in review. Only accepted sightings count toward an entry.
review.reviewerstringoptional^core/[a-z0-9-]+$The core reviewer who graded it.
review.second_reviewerstringoptional^core/[a-z0-9-]+$Required for tier 4 acceptance.
review.accepted_tierstringoptionalnone scanning attempts poc-validated confirmedThe tier the rubric supports. Often lower than claimed_tier, which is normal.
review.rubric_rowintegeroptionalmin 1, max 8Which row of the grading rubric applied.
review.notestringoptionalup to 500 charactersWhy it graded the way it did.
review.reviewed_atstring (date-time)optionalWhen the review was recorded.
review.recusalsarray of stringoptionalReviewers 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.

Requirements that depend on another field. Schema rows list the fields that become required; validator rows state the rule.
WhenThenEnforced by
claimed_tier is poc-validated or confirmedfingerprint outcome reporter.contact_hashSchema
claimed_tier is attemptsoutcomeSchema
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, with source_url. Does not support confirmed.
  • 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.

Fields of a deployment fingerprint: type, requirement, allowed values and purpose.
FieldTypeRequiredValuesWhat it is for
runtimestringrequiredphp node python ruby java otherThe language runtime. Part of the group key. Pick other if none fits.
versionstringoptionalup to 32 charactersExact (8.3.35), range (<8.2.35), or branch (8.1.x). Recorded in the group, does not change which group.
sapistringrequiredmod_php php-fpm php-cgi cli embedded n/a unknownHow the runtime is served. Part of the group key. For PHP this often decides the tier, so be exact.
serverstringoptionalapache nginx litespeed iis caddy other unknownThe web server in front of the runtime. Part of the group key.
osstringoptionalup to 32 charactersdistro-version, e.g. debian-12, alpine-3.20, windows. Recorded in the group, does not change which group.
config_flagsarray of stringoptional^[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.
appstringoptionalwordpress drupal joomla magento laravel symfony none other unknownThe application on top, when it matters to the exploit. Recorded in the group, does not change which group.