CurateChain — Content Curation Protocol

elliot-martins/CurateChain · Commit: 988cb53 · Audited February 21, 2026

0
Critical
3
High
3
Medium
1
Low
1
Info

Overview

CurateChain is a decentralized content curation protocol built on Stacks. Users submit content items (paying an STX fee to the protocol admin), vote on items (+1/-1 appraisals that affect both item scores and voter reputation), send STX gratuities to content creators, and flag problematic content. An admin can adjust fees, remove items, and manage topic categories.

The protocol's core value proposition is its reputation system — but the reputation mechanism is exploitable, and the flagging system lacks basic duplicate prevention.

Priority Matrix Score: 15

MetricScoreWeightWeighted
Financial risk2 — holds/transfers STX via tips + fees36
Deployment likelihood1 — student project22
Code complexity2 — ~280 lines, multiple interactions24
User exposure0 — 0 stars/forks1.50
Novelty2 — new category: content curation1.53
Total15 (≥6 ✓)

Findings

HIGH H-01: Reputation Inflation via Repeated Same-Direction Voting

Location: appraise-item function

A user can call appraise-item with the same vote direction repeatedly on the same item. While the item's score correctly doesn't change (delta = appraisal − previous = 0), the voter's reputation increments by appraisal on every call:

(map-set participant-credibility
  { participant: tx-sender }
  { metric: (+ (get metric appraiser-standing) appraisal) }
)

Impact: Any user can inflate their reputation to arbitrary values by repeatedly voting +1 on any item. If reputation is ever used for access control, weighting, or trust signals, the entire system is compromised.

Recommendation: Only update reputation when the vote actually changes:

(asserts! (not (is-eq appraisal previous-appraisal)) ERR_DUPLICATE_ENTRY)

HIGH H-02: Unlimited Duplicate Flagging Per User

Location: flag-item function

There is no tracking of which users have flagged an item. A single user can call flag-item repeatedly, inflating the flag count to any value:

(map-set curated-items
  { item-identifier: item-identifier }
  (merge target-item { flags: (+ (get flags target-item) u1) })
)

Impact: A single malicious user can make any content appear heavily flagged, potentially triggering admin removal of legitimate content. This is a griefing vector against content creators.

Recommendation: Add a participant-flags map (similar to participant-appraisals) and assert no prior flag exists before incrementing.

HIGH H-03: Self-Voting on Own Content Allowed

Location: appraise-item function

There is no check preventing a content originator from voting on their own submission. Combined with H-01, an originator can boost their own content's score and inflate their reputation simultaneously.

Impact: Undermines the integrity of the curation system. Content creators can self-promote without any cost or restriction.

Recommendation:

(asserts! (not (is-eq tx-sender (get originator target-item))) ERR_INVALID_APPRAISAL)

MEDIUM M-01: State Updated Before Transfer in reward-originator

Location: reward-originator function

The gratuity counter is updated via map-set before the stx-transfer? call. In Clarity, a failed try! rolls back the entire transaction, so this doesn't cause data corruption. However, the pattern is a code smell — state-before-transfer is the #1 bug pattern in Solidity and signals inattention to execution order.

Additionally, no minimum amount check exists. A zero-amount transfer will fail at stx-transfer? but wastes gas.

Recommendation: Add (asserts! (> gratuity-amount u0) ERR_INADEQUATE_BALANCE) and move map-set after the transfer.

MEDIUM M-02: No Admin Role Transfer Mechanism

Location: PROTOCOL_ADMINISTRATOR constant

Admin is set as tx-sender at deploy time via define-constant. There is no way to transfer admin rights. If the deployer's key is compromised or lost, all admin capabilities (fee adjustment, content moderation, topic management) are permanently unavailable.

Recommendation: Change to a define-data-var with a guarded transfer function.

MEDIUM M-03: Expunged Items Leave Counter Inconsistent

Location: expunge-item function

When an item is deleted, aggregate-submissions is not decremented. Since item IDs are sequential and monotonically increasing, retrieve-top-items will attempt to look up deleted items (returning none, filtered out). The counter no longer reflects actual content count.

Recommendation: Maintain a separate active-items-count variable, or document that the counter represents "total ever submitted."

LOW L-01: Top Items Query Hard-Capped at 10

Location: enumerate helper function

The enumerate function generates a static list of up to 10 elements. This is a Clarity limitation (no dynamic loops), but means retrieve-top-items can never return more than 10 items regardless of how many exist.

Recommendation: Document the limitation. Consider adding offset-based pagination for scalability.

INFO I-01: tx-sender vs contract-caller Considerations

Location: All public functions

The contract uses tx-sender throughout. For admin functions, contract-caller would be more secure against proxy-based authorization bypass. For user functions, tx-sender is appropriate.

Recommendation: Use contract-caller for admin authorization checks (adjust-submission-charge, expunge-item, introduce-topic).

Architecture Notes

What Works Well