Contributing
The Observatory grades what was observed, not what was claimed. A contribution is a sighting: one observation, signed, with enough detail that someone else can judge how strong it is. The core team adjudicates. It does not do the reporting, and if it ends up doing most of the reporting the project has failed at the community half.
Read docs/SPEC.md for the rubric and the governance model. This file covers the mechanics.
Submit one
A sighting is a single JSON file at data/entries/<CVE>/sightings/SIGHT-<CVE>-<seq>.json, opened as a pull request.
cp data/entries/CVE-2026-109618/sightings/SIGHT-CVE-2026-109618-0001.json \
data/entries/CVE-2026-109618/sightings/SIGHT-CVE-2026-109618-0042.json
# edit it, then:
npm run validate # schema + payload safety + signature format
Fields that decide how your report grades:
observed_at. When the activity happened, not when you wrote it up. Observation time drives every date on the entry, andfirst_sightingis the number an embargo decision hinges on. A sighting seen on 17 October and reported on 22 October is dated 17 October.evidence_type. One ofhoneypot,waf-log,ids-alert,ir-artifact,poc-reproduction,telemetry-aggregate,third-party-report. This is what the rubric reads to decide the ceiling on your tier.claimed_tier. What you believe you saw. It is a hint, not a vote. A reviewer grades against the rubric and recordsreview.accepted_tier, which is often lower.fingerprint. Required for any claim ofpoc-validatedorconfirmed. Runtime, version, SAPI, server, OS and config flags. Without it a tier 3 claim cannot be accepted, because a tier belongs to a configuration.outcome. Required atattemptsand above.unknownandfailedare useful answers. A payload that returned HTTP 500 with no child process is a real, gradeable observation.signature. Sign your record. For now the check is format only,sigstore:orpgp:followed by the signature.
Expect this
Automated checks reject malformed records, unsigned records and anything carrying a raw payload, before a human looks at it. Trusted reporters' tier 1 and 2 sightings publish immediately with a pending flag. Everything else waits in the queue.
Review targets are 4 hours during an active incident, 72 hours in steady state. A tier 4 claim needs two reviewers and one of them will contact you directly before it is accepted.
A reviewer will accept, reject or ask for more. Rejections and needs-info come back with the reason attached, and you resubmit through the same door.
Know the caps
- Anonymous stops at attempts. Tiers 3 and 4 need a named org and a contact the core team can reach. The contact is held privately.
- One reporter, one weight. Five sightings from your org about the same window count once toward independence. Independence means different orgs with different sensors.
- Blocked is not exploited. Block counts grade to
attemptsat most, however large the number. - A public proof of concept is not validation. Existence is tier 2. Someone has to run it against a recorded fingerprint and write down the result.
- Negative results are wanted. "The proof of concept does not achieve execution against php-fpm on nginx" is a sighting worth filing. It lowers no tier and it is frequently the most useful line on the entry.
Never include
No raw exploit payloads. No proof-of-concept code or fragments. No victim identifiers. No raw source IPs outside an aggregated honeypot or WAF count. Nothing above TLP:GREEN, because this is a public project.
Hashes, header names, request-path patterns, aggregated volumes and references to published detection rules are all first-class. Submit those instead.
The automated checks enforce this and they are deliberately blunt. If a check rejects something you believe is safe, say so in the pull request rather than working around it.
Have an agent build it
A sighting is a JSON file against a published schema, which is the kind of thing a coding agent is good at and you are bored by. Every entry page carries a ready-made prompt for this under Agent prompts, generated from that entry's own record, so it already knows the CVE, the fingerprints that exist and the indicators that have been published. Copy the sighting prompt from the entry you are reporting against and give it to your agent.
There is a skill for this. Download observatory-sighting-skill.zip and unzip it into your agent's skills directory, ~/.claude/skills/ for Claude Code. It carries the tier rubric, the full field reference and the redaction rules, so the agent grades against the same table a reviewer will use rather than against its impression of how bad the CVE sounds. It is built from this repository on every deploy, so it cannot drift from the schema it describes.
If you would rather start from nothing, this is the whole brief:
Build one Exploitation Observatory sighting as JSON for <CVE>.
Read the schema at <site>/docs.html#fields and follow it exactly.
Use only what I tell you. Do not infer a tier, a version or an outcome
that I have not stated, and do not fill a field by guessing. Where you
need something from me that I have not given, stop and ask.
Never include an exploit payload, proof-of-concept code, a victim name,
an internal hostname or an IP that identifies a victim.
Output the JSON only.
Two things to check before you send it, because an agent will not catch either:
- The tier is a claim about evidence, not about severity. An agent that
reads "critical RCE" in an advisory will reach for a high tier. The rubric asks what you observed, and a captured payload with no execution evidence is attempts no matter how bad the bug is.
- The fingerprint is the part worth being slow about. Runtime, SAPI,
server and config flags, and absent is not the same as unset. A sighting with a vague fingerprint still counts, but it cannot raise the tier for a configuration, which is the thing this registry exists to record.
Sign the file and open a pull request as above. The agent writes the record; you are still the reporter, and the signature is yours.
Join the core
The core adjudicates. It does not report, because a registry whose maintainers write most of its own sightings is a vendor feed with extra governance.
What the role is: grading submitted sightings against the rubric, recusing from your own employer's submissions, and arguing in public about the hard ones. It is a reviewing job, not a writing one. Expect a few hours a week.
What we look for, in order:
1. A track record of grading evidence conservatively. Sightings you have already filed here are the strongest signal, including ones where you argued your own report down. 2. A deployment you understand in production. The fingerprint axis only works if somebody on the core actually runs the configuration. 3. Willingness to be overruled in a log that never gets edited.
Two caps apply and neither bends:
- No more than two core maintainers from one employer, which counts the
sponsor exactly like anybody else. If your employer already holds two seats, the answer is no today regardless of how good you are, and that is the cap working.
- You recuse from grading your own employer's submissions, and every
recusal is logged publicly.
To apply, open a pull request adding yourself to GOVERNANCE.md with your employer, the fingerprints you run, and links to sightings you have filed. That is the application. It is public, which is the point: the same record a reader can check later is the one we decide on.
Propose a change
Rubric and schema changes go through a pull request with a 7-day comment window and need a majority of core. A decision lands within 14 days. Adding or removing a core maintainer needs two thirds.
Entries are derived, never hand-edited. If an entry looks wrong, the fix is to a sighting or to a grading decision, and the grading log records it. Nothing in the grading log is ever deleted or edited.
Sources
Each entry can carry a reading list at data/entries/<CVE>/sources.json. A separate curation application writes it. The file is optional; an entry without one shows no Sources section.
The shape is { "schema_version": "1.0", "sources": [ ... ] }, defined by schema/source.schema.json and schema/sources.schema.json. Each source needs url (http or https), title, publisher (a plain name), published_at, kind and a curation object with verdict (included or excluded), rationale, curated_by and curated_at. Optional: relates_to (CVE ids), link_health (status, checked_at, archive_url) and notes. kind is one of vendor-writeup, independent-research, advisory, postmortem, press, detection-rule, talk, mailing-list.
Sources are context, not evidence. They never change a tier, never enter the grading log and never count toward sighting_count. A third-party report that shows exploitation belongs in a sighting with its source_url. Choose sources on what they contribute, never on who published them: the page orders by kind and date and ignores publisher. Write the rationale as what the piece adds. excluded records are kept but never shown. Report a dead link with link_health.status: "dead" and the page stops linking it. The build makes no network calls, so link health is only what the file says. Link to proof-of-concept writeups, never copy from them: the payload safety check runs over this file too.
Notes
data/is the database. There is no server to deploy against and no account to create. A sighting is a file and a pull request.- Validate before you open the PR.
npm run validateruns the same checks CI does, and it names the field path that tripped a rule, which is faster than reading a CI log. - The entry you see in
data/entries/<CVE>/entry.jsonis output. Runningnpm run recompute -- <CVE>regenerates it from accepted sightings so you can see what your submission would change.