Skip to content

critical: close unauthenticated admin-claim branch in reputation set_admin() #80

Description

@EmeditWeb

Problem

set_admin() in contracts/reputation-contract/src/lib.rs (lines 189–209) contains an "initialization" branch that fires whenever no admin is stored: it writes new_admin with no require_auth() call whatsoever. Anyone can claim admin of the reputation contract before the deployer does. Admin of the reputation contract controls set_updater(), and updaters control increase_score, decrease_score, and set_score — i.e., the entire creditworthiness layer. A hijacked reputation admin can mint maximum scores (set_score(user, MAX_SCORE)) for sybil borrowers, who then pass validate_reputation() in creditline-contract (lib.rs:284–301), unlock the highest credit tier (credit_limit(score) at line 432: 10,000 at score ≥ 90, lowest interest rate), and drain the liquidity pool through loans that were never underwritten.

Note the asymmetry: the admin-exists branch correctly does old_admin.require_auth() + access::require_admin(), while the no-admin branch does nothing. The same flaw class was fixed in the liquidity pool by requiring auth unconditionally.

Ground Rules

  1. Read context/architecture-context.md in full
  2. Read context/code-standards.md in full
  3. Read context/progress-tracker.md before starting
  4. Read contracts/reputation-contract/src/lib.rs in full plus src/access.rs and src/storage.rs
  5. Read contracts/liquidity-pool-contract/src/lib.rs:28–44 for the canonical guarded-init pattern

What To Build

  1. Split initialization out of set_admin() into an explicit initialize(env, admin) that requires admin.require_auth() and rejects re-initialization, matching the other contracts.
  2. Make set_admin() panic with NotInitialized (or equivalent error) when no admin exists — it must become impossible to take admin without a signature.
  3. Keep the existing admin-present behavior identical (old_admin.require_auth() then access::require_admin()).
  4. Add tests: (a) unauthenticated first-time set_admin call fails; (b) initialize works once with auth; (c) second initialize fails; (d) updater flows still function end-to-end.
  5. Coordinate redeployment: this changes the contract's public interface; update deployed-testnet.json and downstream integrations (creditline stores the reputation address via set_reputation_contract) accordingly.

Files To Touch

  • contracts/reputation-contract/src/lib.rs
  • contracts/reputation-contract/src/tests.rs
  • context/progress-tracker.md

Acceptance Criteria

  • No code path grants admin without a successful require_auth() from the previous admin or an explicit one-time authorized initialization
  • Tests prove the unauthenticated claim path is closed
  • Existing updater/admin tests still pass unchanged
  • Build passes with zero errors

Mandatory Checks Before Opening PR

  • All context/ files read and understood
  • Code follows context/code-standards.md exactly
  • progress-tracker updated
  • Build and full test suite green
  • New tests added
  • PR references this issue number exactly

PRs failing any check will be closed without review.

Metadata

Metadata

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions