A small Flask reference implementation for the BCP-11 workflow:
- fetch an existing CVE from
db.gcve.euorvulnerability.circl.lu; - accept unauthenticated community proposals for existing CVEs;
- generate deterministic
GCVE-65530-YEAR-SEQUENCEumbrella identifiers; - generate UUIDv7
contributionIdvalues; - preserve immutable technical proposal data;
- let one administrator accept (
review.status=reviewed) or reject (review.status=rejected) a proposal; - preserve administrative revision history;
- publish accepted BCP-11 fragments, including superseded/withdrawn history;
- expose
/api/gcve/publicationas a simple BCP-03-style publication endpoint. - generate a complete derived view that preserves the authoritative CVE and attaches all published contributions under
x_gcve.
This is deliberately a small reference service, not a complete production deployment.
python -m venv .venv
. .venv/bin/activate
pip install -r requirements.txt
export BCP11_ADMIN_TOKEN='replace-with-a-long-random-secret'
export BCP11_ADMIN_REVIEWER='GCVE BCP-11 maintainer'
export BCP11_DATABASE='./data/bcp11.sqlite3'
export BCP11_SECRET_KEY='replace-with-a-second-long-random-secret'
flask --app app runOpen http://127.0.0.1:5000/ for the community web interface. Visitors can
submit a structured BCP-11 proposal without an account, follow its review
status, and browse accepted contributions. Maintainers can sign in at
/admin/login with BCP11_ADMIN_TOKEN to inspect the authoritative snapshot
and accept or reject pending proposals. Set a stable, random
BCP11_SECRET_KEY in deployed environments so administrator sessions remain
valid across restarts.
Production deployments should terminate TLS at a reverse proxy and replace the in-process rate limiter with a shared limiter (for example Redis/nginx/HAProxy).
Auto-try db.gcve.eu, then vulnerability.circl.lu:
curl 'http://127.0.0.1:5000/api/cve/CVE-2024-38063'Force one source:
curl 'http://127.0.0.1:5000/api/cve/CVE-2024-38063?source=gcve'
curl 'http://127.0.0.1:5000/api/cve/CVE-2024-38063?source=circl'The upstream request is fixed to:
https://db.gcve.eu/api/vulnerability/<CVE-ID>
https://vulnerability.circl.lu/api/vulnerability/<CVE-ID>
No user-supplied upstream URL is fetched, avoiding an SSRF-style fetch primitive.
curl -sS -X POST \
-H 'Content-Type: application/json' \
--data @example-proposal.json \
http://127.0.0.1:5000/api/proposalsThe service checks that the target CVE exists before storing the proposal. Proposal fields such as review, state, revision, contributionId, recordType, and relationships are server-controlled.
Anonymous submissions are supported by omitting contributors and setting:
{"anonymous": true}Evidence is mandatory even for anonymous submissions.
List pending proposals:
curl -H "Authorization: Bearer $BCP11_ADMIN_TOKEN" \
'http://127.0.0.1:5000/api/admin/proposals?status=pending'Accept:
curl -X POST \
-H "Authorization: Bearer $BCP11_ADMIN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"notes":"Evidence checked against the vendor advisory."}' \
'http://127.0.0.1:5000/api/admin/proposals/urn:uuid:.../accept'BCP-11 does not define an accepted review value, so the workflow state accepted is published as review.status = reviewed.
Reject:
curl -X POST \
-H "Authorization: Bearer $BCP11_ADMIN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"notes":"The evidence refers to a different vulnerability."}' \
'http://127.0.0.1:5000/api/admin/proposals/urn:uuid:.../reject'Accepted contributions can also be withdrawn, and their upstream administrative metadata can be updated. Each administrative change increments revision, changes dateUpdated, and appends a revision snapshot.
Status of any submitted proposal:
GET /api/proposals/<contributionId>
GET /api/proposals/<contributionId>/history
Accepted/public contributions:
GET /api/bcp11/<CVE-ID>
GET /api/bcp11/umbrella/<GCVE-65530-ID>
GET /api/bcp11/contributions/<contributionId>
GET /api/bcp11/combined/<CVE-ID>
GET /api/gcve/publication?page=1&per_page=30&since=2026-08-01T00:00:00Z
The CVE/umbrella lookup returns the BCP-11 convenience index. The contribution endpoint returns the normative standalone fragment.
The combined endpoint fetches the latest authoritative CVE, leaves all CNA fields
unchanged, and appends active, accepted BCP-11 contribution objects to x_gcve by
default. Pass ?contributions=all to include all accepted contributions, including
withdrawn or superseded history.
x_gcve_resolution identifies the source record, policy, included active/reviewed
inputs, and published contributions excluded from resolution (for example withdrawn
or superseded history). The result is explicitly derived community data, not an
authoritative replacement CVE record. The same view is available in the web UI at
/combined/<CVE-ID>.
A new proposal may contain:
{
"supersedes": ["urn:uuid:..."]
}When the administrator accepts it, each referenced accepted contribution for the same CVE is retained but gets a new administrative revision with state = superseded.
- request body capped at 128 KiB by default;
- JSON nesting, collection sizes, and string lengths constrained;
- evidence/contributor URLs restricted to HTTP(S);
- evidence URLs are never fetched automatically;
- only fixed operator-controlled CVE upstreams are fetched;
- submission rate limiting is enabled (single-process implementation);
- admin operations use a long Bearer token from the environment;
- technical fields are immutable and protected by an internal SHA-256 digest check;
- accepted/superseded/withdrawn fragments and all administrative revisions remain stored.
For a real public deployment, add a shared rate limiter, moderation/abuse telemetry, backups, signed publication feeds, schema validation against the final published BCP-11 schema, and a durable database such as PostgreSQL.
The format/storage unit tests do not require a live upstream:
PYTHONPATH=. python -m unittest discover -s tests -p 'test_*.py'