elliot-martins/CurateChain ·
Commit: 988cb53 ·
Audited February 21, 2026
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.
| Metric | Score | Weight | Weighted |
|---|---|---|---|
| Financial risk | 2 — holds/transfers STX via tips + fees | 3 | 6 |
| Deployment likelihood | 1 — student project | 2 | 2 |
| Code complexity | 2 — ~280 lines, multiple interactions | 2 | 4 |
| User exposure | 0 — 0 stars/forks | 1.5 | 0 |
| Novelty | 2 — new category: content curation | 1.5 | 3 |
| Total | 15 (≥6 ✓) | ||
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)
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.
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)
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.
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.
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."
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.
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).
stx-transfer?)index-of on a list variable — correct but limits topics to 10enumerate workaround for Clarity's lack of dynamic loops is clever but constrainingoriginator ≠ flagger)print for off-chain indexing