Skip to content

Commit ad07c40

Browse files
authored
Merge pull request #740 from northvictor/feature/security-audit-checklist
feat: Add security audit checklist and external audit template (#653)
2 parents 112fe4d + c82451a commit ad07c40

1 file changed

Lines changed: 203 additions & 0 deletions

File tree

SECURITY.md

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,206 @@ Security alerts from CodeQL are surfaced in the **Security** tab of the reposito
175175
- Consider the security impact of any medium or low severity issues
176176

177177
The workflow uses the `security-extended` and `security-and-quality` query suites to provide comprehensive coverage of potential vulnerabilities.
178+
179+
---
180+
181+
## Self-Audit Checklist (Smart Contract / DeFi)
182+
183+
Use this checklist when reviewing the Soroban contract (`contracts/src/lib.rs`) or proposing changes that affect on-chain logic. Each item must be answered before merging.
184+
185+
### Reentrancy
186+
187+
| # | Check | Severity | Mitigation | Status |
188+
|---|-------|----------|------------|--------|
189+
| 1 | External token transfers occur **after** all state updates (checks-effects-interactions pattern) | **High** | Move `TokenClient::transfer()` calls after storage writes; or add a reentrancy guard if transfers must precede state changes | ☐ Open |
190+
| 2 | Cross-contract calls do not allow the callee to re-enter and mutate contract state before the first invocation completes | **High** | Verify that Soroban's host-level call-depth limits are sufficient; add a `REENTRANCY_GUARD` flag stored in temporary storage if untrusted contracts are called | ☐ Open |
191+
| 3 | `claim()` and `refund()` loops call external token contracts on each iteration without intermediate state snapshots | **Medium** | Consider batching transfers or taking a storage snapshot before the loop so a partial failure does not leave state inconsistent | ☐ Open |
192+
193+
### Access Control
194+
195+
| # | Check | Severity | Mitigation | Status |
196+
|---|-------|----------|------------|--------|
197+
| 4 | Admin address is immutable after `initialize()` | **Critical** | Already enforced — `DataKey::Admin` is set once and never updated; verify no `set_admin()` function is added in future PRs | ☐ Pass |
198+
| 5 | Creator-only functions (`cancel_campaign`, `claim`, `update_metadata`) verify caller matches `campaign.creator` | **High** | Already enforced with `require_auth()` + identity comparison; ensure any new creator-gated function follows the same pattern | ☐ Pass |
199+
| 6 | No function allows arbitrary address to withdraw funds from any campaign | **Critical** | `claim()` checks `campaign.creator`; `refund()` checks `contributor.require_auth()` and `HasContributed` key; verify no new withdrawal paths bypass these checks | ☐ Open |
200+
| 7 | Pause mechanism excludes read-only functions to avoid denial-of-service on data reads | **Low** | Already enforced — `require_not_paused()` is called only in state-mutating entry points; verify any new `fn` with side effects does the same | ☐ Pass |
201+
202+
### Integer Overflow / Arithmetic
203+
204+
| # | Check | Severity | Mitigation | Status |
205+
|---|-------|----------|------------|--------|
206+
| 8 | `overflow-checks = true` is set in `Cargo.toml` release profile | **Critical** | Already present on line 27 of `contracts/Cargo.toml`; verify it is never removed or commented out | ☐ Pass |
207+
| 9 | `pledged_amount + amount <= target_amount` guard prevents both overflow and over-funding | **High** | Already implemented at line 361 of `lib.rs`; verify similar guards exist for any new arithmetic in the contract | ☐ Open |
208+
| 10 | Token amounts use `i128` (not `u64` or `u128`) to match Soroban token interface | **Medium** | Already using `i128` throughout; verify new fields also use `i128` and never cast without bounds checks | ☐ Pass |
209+
| 11 | Multiplication before division (or vice versa) does not cause precision loss or overflow | **Medium** | Review any percentage or ratio calculations added in future; prefer `checked_mul().unwrap_or(i128::MAX)` for multiplications that could overflow | ☐ Open |
210+
| 12 | `contributor_count += 1` (and similar counters) cannot overflow | **Low** | With `overflow-checks = true`, a panic would occur; consider using `saturating_add` if graceful handling is preferred over panic | ☐ Open |
211+
212+
### Flash Loan Attack Vectors
213+
214+
| # | Check | Severity | Mitigation | Status |
215+
|---|-------|----------|------------|--------|
216+
| 13 | Contract does not expose a "donate" or "deposit" function that manipulates internal price/balance snapshots used by other operations | **High** | Verify no function accepts tokens without recording a corresponding contribution or refund; flash loans rely on the ability to manipulate oracle-like state | ☐ Open |
217+
| 14 | Campaign balance used for eligibility checks reflects actual token balance of the contract, not a stored snapshot | **Medium** | The contract tracks per-campaign token balances via `CampaignTokenBalance`; consider cross-referencing with `TokenClient::balance()` to prevent balance inflation attacks | ☐ Open |
218+
| 15 | No governance or quorum function relies on a contributor's token balance that could be borrowed for a single transaction | **Medium** | `contributor_count` counts unique addresses (not balances), so flash-loaned tokens cannot inflate voting power; verify the same for any future governance features | ☐ Pass |
219+
220+
### Front-Running
221+
222+
| # | Check | Severity | Mitigation | Status |
223+
|---|-------|----------|------------|--------|
224+
| 16 | `create_campaign` parameters cannot be front-run to replace a legitimate campaign creation with a lookalike | **Medium** | `campaign_id` is a monotonically increasing `u32`, so an attacker can only create their own campaign with a predictable ID; no user-supplied ID exists | ☐ Pass |
225+
| 17 | `claim()` cannot be front-run by an attacker to redirect funds | **High** | `claim()` transfers only to `campaign.creator` (verified via `require_auth()`); funds always go to the intended recipient regardless of transaction ordering | ☐ Pass |
226+
| 18 | Deadline-dependent logic (`claim` vs `refund`) is not susceptible to validator timestamp manipulation | **Medium** | Soroban ledger timestamps are bounded by validator consensus; still, avoid tight time windows where a one-slot difference changes fund disposition | ☐ Open |
227+
| 19 | `contribute()` race condition: two contributions arriving in the same block cannot jointly exceed `target_amount` | **Low** | Each contribution checks `pledged_amount + amount <= target_amount` independently; in practice the gap is bounded by one contribution. Acceptable for MVP | ☐ Open |
228+
229+
### Findings Register (Open Items)
230+
231+
| ID | Category | Finding | Severity | Mitigation | Assigned To | Due Date |
232+
|----|----------|---------|----------|------------|-------------|----------|
233+
| F-01 | Reentrancy | Token transfer precedes state update in `contribute()` — external call before storage write violates checks-effects-interactions | **High** | Swap the order: update `pledged_amount`, `contributor_count`, and per-contributor balance **before** calling `TokenClient::transfer()` |||
234+
| F-02 | Access Control | No two-step admin transfer mechanism exists | **Low** | If admin rotation is needed, implement a two-step pattern (propose + accept) to prevent accidental lockout |||
235+
| F-03 | Arithmetic | Counter increments use raw `+=` rather than `checked_add` | **Low** | With `overflow-checks=true` these will panic on overflow, which is acceptable; upgrade to `checked_add()` for explicit error handling if desired |||
236+
237+
---
238+
239+
## External Audit Firm Template
240+
241+
Use this template when engaging an external security firm to audit the Soroban smart contract. Fill in the fields marked `[...]` before sending.
242+
243+
```markdown
244+
# Audit Request: Stellar Goal Vault — Soroban Smart Contract
245+
246+
## Project Overview
247+
248+
- **Repository:** https://github.com/ritik4ever/stellar-goal-vault
249+
- **Contract:** `contracts/src/lib.rs` — crowdfunding vault
250+
- **Language / Framework:** Rust / Soroban SDK 21.0.0
251+
- **Deployment Target:** Stellar Soroban (testnet → mainnet)
252+
- **Commit Hash:** [GIT_COMMIT_HASH]
253+
- **Prior Audits:** None
254+
255+
## Scope
256+
257+
### In-Scope Files
258+
259+
| File | LOC | Description |
260+
|------|-----|-------------|
261+
| `contracts/src/lib.rs` | ~828 | Main contract — campaign creation, contribution, claiming, refunding, cancellation, deadline extensions, pause, migrate |
262+
| `contracts/src/test.rs` | ~1210 | Unit tests (optional: review test coverage quality) |
263+
264+
### Out of Scope
265+
266+
- Backend API (`backend/`)
267+
- Frontend (`frontend/`)
268+
- Docker / CI configuration
269+
- JavaScript/TypeScript code
270+
271+
## Threat Model
272+
273+
### Assumed Attacker Capabilities
274+
275+
- Can submit arbitrary transactions to the Soroban network
276+
- Can deploy their own Soroban contracts
277+
- Can observe the mempool (subject to Soroban DAG ordering constraints)
278+
- Does **not** control the Stellar validator set
279+
- Does **not** have access to the contract admin key
280+
281+
### Critical Assets
282+
283+
| Asset | Description |
284+
|-------|-------------|
285+
| Campaign funds | Tokens held by the contract on behalf of campaign creators and contributors |
286+
| Admin key | Controls `set_paused()` and `migrate()` — loss or compromise is critical |
287+
| Campaign metadata integrity | Must not be arbitrarily overwritable |
288+
289+
## Focus Areas
290+
291+
Please prioritize the following vulnerability classes during the audit:
292+
293+
1. **Reentrancy** — Cross-contract calls during `contribute()`, `claim()`, and `refund()`; verify checks-effects-interactions pattern
294+
2. **Access control** — Admin, creator, contributor boundaries; `require_auth()` usage
295+
3. **Integer arithmetic** — Overflow, underflow, precision loss in `pledged_amount`, deadline calculations, contribution limits
296+
4. **Flash loan resistance** — Any function that could be exploited with a single-transaction borrow-and-repay cycle
297+
5. **Front-running / MEV** — Transaction ordering dependencies, deadline manipulation, race conditions in `contribute()` and `claim()`
298+
6. **Fund safety** — Tokens cannot be permanently locked, stolen by non-creators, or claimed by unauthorized parties
299+
7. **State consistency** — Idempotency of `migrate()`, correctness of `refund_all()` enumeration, dust handling
300+
301+
## Deliverables
302+
303+
### Required
304+
305+
- [ ] **Audit Report** — PDF or Markdown covering all findings with:
306+
- Title and description of each finding
307+
- Severity rating (Critical / High / Medium / Low / Informational)
308+
- Steps to reproduce or proof-of-concept
309+
- Recommended remediation
310+
- Code references (file + line number)
311+
- [ ] **Status Summary** — Table of all findings with severity, status (Open / Acknowledged / Fixed / Verified), and verification commit hash
312+
- [ ] **Re-Audit Letter** — After fixes are applied, a brief verification report confirming all high-severity items are resolved
313+
314+
### Nice-to-Have
315+
316+
- [ ] **Test Suite Recommendations** — Suggestions for property-based tests or fuzzing harnesses
317+
- [ ] **Gas / Fee Optimization Notes** — Suggestions to reduce contract execution costs
318+
319+
## Timeline & Process
320+
321+
- **Audit Duration:** [e.g., 2 weeks]
322+
- **Communication:** [e.g., Slack / Signal / Email]
323+
- **Submission Method:** Private GitHub repository or encrypted email
324+
- **Fix Verification:** After findings are addressed, one round of re-audit on the updated commit
325+
- **Embargo Period:** Findings must not be publicly disclosed until [DATE] or until the fix has been deployed to mainnet
326+
327+
## Access
328+
329+
- [ ] Audit firm will be granted read-only access to a private fork or mirror of the repository
330+
- [ ] CI/CD secrets and deployment credentials will **not** be shared
331+
- [ ] No production data will be shared
332+
- [ ] All communication will be encrypted
333+
334+
## Acceptance Criteria
335+
336+
This audit is considered complete when:
337+
338+
1. All in-scope files have been reviewed
339+
2. Every finding has a severity rating and remediation recommendation
340+
3. No **Critical** or **High** severity findings remain unresolved without an explicit risk acceptance
341+
4. The re-audit letter confirms all fixes are correctly applied
342+
5. The final report is delivered in the agreed format
343+
```
344+
345+
---
346+
347+
## Audit Sign-Off
348+
349+
Before each production deployment, complete this sign-off:
350+
351+
```markdown
352+
# Security Audit Sign-Off
353+
354+
**Deployment Tag:** [DEPLOYMENT_VERSION]
355+
**Commit:** [GIT_COMMIT_HASH]
356+
**Review Date:** [DATE]
357+
**Reviewer:** [NAME]
358+
359+
## Self-Audit Checklist Sign-Off
360+
361+
All checklist items above have been reviewed:
362+
363+
- [ ] Reentrancy — no high-severity open items
364+
- [ ] Access Control — no critical or high-severity open items
365+
- [ ] Integer Overflow — overflow-checks enabled, no unsafe arithmetic introduced
366+
- [ ] Flash Loan Vectors — no exploitable balance-manipulation paths
367+
- [ ] Front-Running — no transaction-ordering dependencies with fund impact
368+
369+
## External Audit Status
370+
371+
- [ ] External audit has been completed for this scope
372+
- [ ] All critical/high findings remediated and verified
373+
- [ ] Risk acceptance documented for any remaining medium/low items
374+
375+
## Decision
376+
377+
**Approved** — ready for deployment
378+
**Changes requested**[link to required changes]
379+
**Blocked**[reason]
380+
```

0 commit comments

Comments
 (0)