curatechain
CurateChain — Content Curation Protocol
elliot-martins/CurateChain ·
Commit: 988cb53 ·
Audited February 21, 2026
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
| 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 ✓) | ||
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
- Single-contract design — clean and self-contained
- STX flows: submission fees → admin, gratuities → originator (both via
stx-transfer?) - Reputation system is read-only state with no on-chain enforcement — a social signal only
- Topic validation uses
index-ofon a list variable — correct but limits topics to 10 - The
enumerateworkaround for Clarity's lack of dynamic loops is clever but constraining
What Works Well
- Clean error handling with descriptive constants
- Overflow check on submission counter
- Topic validation against a configurable whitelist
- Self-flagging prevention (
originator ≠ flagger) - Event emission via
printfor off-chain indexing