Skip to content

fix(bcos): reject duplicate cert_id instead of silently overwriting + erasing anchor#8011

Open
Vyacheslav-Tomashevskiy wants to merge 1 commit into
Scottcjn:mainfrom
Vyacheslav-Tomashevskiy:fix/bcos-reattest-overwrites-anchor
Open

fix(bcos): reject duplicate cert_id instead of silently overwriting + erasing anchor#8011
Vyacheslav-Tomashevskiy wants to merge 1 commit into
Scottcjn:mainfrom
Vyacheslav-Tomashevskiy:fix/bcos-reattest-overwrites-anchor

Conversation

@Vyacheslav-Tomashevskiy

Copy link
Copy Markdown
Contributor

What

bcos_attest() (node/bcos_routes.py) stored the certificate with INSERT OR REPLACE, but cert_id is UNIQUE (schema line 184) and the handler carries an except sqlite3.IntegrityError -> 409 "Certificate ... already exists" guard.

INSERT OR REPLACE resolves a UNIQUE conflict with a silent DELETE + INSERT, so it never raises IntegrityError. The 409 guard was therefore dead code, and a second attest for a known cert_id silently overwrote repo / tier / trust_score / reviewer / report.

The REPLACE column list also omits anchor_tx and passes anchored_epoch=None, so re-attesting a ledger-anchored certificate wiped its anchor_tx back to NULL. That breaks the documented idempotency of /api/v1/bcos/anchor (an already-anchored cert should return its existing tx hash, not write a second ledger row): after the wipe, a re-anchor writes a second ledger row with a fresh tx hash for the same certificate.

Auth passes on is_admin OR valid Ed25519 signature over a caller-supplied signer_pubkey, so any party can self-sign a report they authored and overwrite a known public cert_id.

Repro (deterministic)

  1. attest BCOS-x (repo Scottcjn/Rustchain, L2, trust 90) -> anchor it (anchor_tx set).
  2. re-attest the same cert_id with repo attacker/evil, trust 100.
  3. Before fix: no IntegrityError; row becomes {repo: attacker/evil, trust_score: 100, anchored_epoch: None, anchor_tx: None} -- anchor erased, trust forged, 409 never fires.

Fix

Plain INSERT (one word). The duplicate now raises IntegrityError, the existing 409 guard fires, and the original record + its anchor are preserved. cert_id is meant to be immutable once issued -- exactly what the "already exists" 409 already intended.

Same overwrite class the repo already treats as a real bug in node/tests/test_attestation_overwrite_reward_loss.py (that one is miner_attest_recent; this is the untouched bcos_attestations table).

Tests

  • Adds node/tests/test_bcos_reattest_preserves_anchor.py -- fails on old code, passes on the fix (mutation-checked).
  • Existing node/tests/test_bcos_routes.py (8 tests) still green.

RTC reward address: RTCd1554f0f35576faf01d386a6be1c947f560dd0b7

… erasing anchor

bcos_attest stored with INSERT OR REPLACE, but cert_id is UNIQUE and the
handler carries an `except sqlite3.IntegrityError -> 409 "already exists"`
guard. INSERT OR REPLACE resolves a UNIQUE conflict with a silent DELETE+
INSERT and never raises IntegrityError, so the 409 guard was dead code: a
second attest for a known cert_id silently overwrote repo/tier/trust_score/
reviewer/report.

The REPLACE column list also omits anchor_tx and passes anchored_epoch=None,
so re-attesting a ledger-anchored cert wiped its anchor_tx back to NULL. That
breaks /api/v1/bcos/anchor's documented idempotency (an already-anchored cert
should return its existing tx hash, not write a second ledger row): after the
wipe a re-anchor writes a second ledger row with a fresh tx hash.

Because auth passes on admin key OR a valid Ed25519 signature over a
caller-supplied signer_pubkey, any party could self-sign a report and
overwrite a public cert_id.

Fix: plain INSERT so a duplicate raises IntegrityError and the existing 409
guard fires, preserving the original record and its anchor. Same overwrite
class as node/tests/test_attestation_overwrite_reward_loss.py.

Adds node/tests/test_bcos_reattest_preserves_anchor.py (fails on old code,
passes on fix; existing 8 bcos tests still green).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added BCOS-L1 Beacon Certified Open Source tier BCOS-L1 (required for non-doc PRs) BCOS-L2 Beacon Certified Open Source tier BCOS-L2 (required for non-doc PRs) node Node server related tests Test suite changes size/M PR: 51-200 lines labels Jul 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

BCOS-L1 Beacon Certified Open Source tier BCOS-L1 (required for non-doc PRs) BCOS-L2 Beacon Certified Open Source tier BCOS-L2 (required for non-doc PRs) node Node server related size/M PR: 51-200 lines tests Test suite changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant