
smart-contract-security-review
by scalus3
smart-contract-security-reviewは、other分野における実用的なスキルです。複雑な課題への対応力を強化し、業務効率と成果の質を改善します。
SKILL.md
name: smart-contract-security-review description: Security review for Scalus/Cardano smart contracts. Analyzes @Compile annotated validators for vulnerabilities like redirect attacks, inexact value validation, missing token verification, integer overflow, and self-dealing. Use when reviewing on-chain code, before deploying validators, or when /security-review is invoked. Requires explicit path argument.
Smart Contract Security Review
Analyze Scalus/Cardano smart contracts for security vulnerabilities.
Target Code Identification
Find on-chain code by searching for:
- Objects/classes with
@Compileannotation - Objects extending
Validator,DataParameterizedValidator, orParameterizedValidator - Objects compiled with
PlutusV3.compile(),PlutusV2.compile(), orPlutusV1.compile()
Search patterns:
grep -rn "@Compile" --include="*.scala" <path>
grep -rn "extends Validator" --include="*.scala" <path>
grep -rn "extends DataParameterizedValidator" --include="*.scala" <path>
Workflow
- Discovery: Find all
@Compileannotated code in specified path - Classification: Identify validator type (spend/mint/reward/certify/vote/propose)
- Analysis: Check each validator against vulnerability checklist
- False Positive Verification: For each potential issue, verify it's not a false positive
- Reporting: Generate structured report with severity levels (only verified issues)
- Remediation: Use TodoWrite to track issues, fix one-by-one with user confirmation
Vulnerability Checklist
Based on Cardano Developer Portal security guidelines and Scalus-specific patterns.
For detailed patterns and code examples, see references/vulnerabilities.md.
Critical Severity
| ID | Name | Risk | Detection |
|---|---|---|---|
| V001 | Redirect Attack | Funds stolen via output redirection | outputs.at(idx) without address validation |
| V002 | Token/NFT Not Verified | State token excluded | Missing quantityOf on outputs |
| V003 | Inexact Burn/Mint | Extra tokens minted | >= instead of === for quantities |
| V004 | Integer Overflow | Arithmetic overflow | Custom encoding without bounds |
| V005 | Double Satisfaction | Pay once, satisfy many | outputs.exists without unique linking |
High Severity
| ID | Name | Risk | Detection |
|---|---|---|---|
| V006 | Index Validation Missing | Invalid index access | .at(idx) without bounds check |
| V007 | Self-Dealing/Shill Bidding | Price manipulation | No seller/bidder separation |
| V008 | Double Spend via Index | Same UTxO processed twice | Index lists without uniqueness |
| V009 | Inexact Refund Amount | Fund manipulation | >= for refunds instead of === |
| V010 | Other Redeemer Attack | Bypass via different redeemer | Multiple script purposes |
| V011 | Other Token Name Attack | Unauthorized token minting | Policy doesn't check all tokens |
| V012 | Missing UTxO Authentication | Fake UTxO injection | No auth token verification |
| V025 | Oracle Data Validation | Price manipulation, stale data | Oracle data without signature/freshness |
Medium Severity
| ID | Name | Risk | Detection |
|---|---|---|---|
| V013 | Time Handling | Time manipulation | Incorrect interval bounds |
| V014 | Missing Signature | Unauthorized actions | No isSignedBy checks |
| V015 | Datum Mutation | Unauthorized state change | No field comparison |
| V016 | Insufficient Staking Control | Reward redirection | No staking credential check |
| V017 | Arbitrary Datum | Unspendable UTxOs | No datum validation |
| V024 | Parameterization Verification | Script substitution (varies) | ParameterizedValidator with auth params, no token |
Low Severity / Design Issues
| ID | Name | Risk | Detection |
|---|---|---|---|
| V018 | Unbounded Value | UTxO size limit | Unlimited tokens in output |
| V019 | Unbounded Datum | Resource exhaustion | Growing datum size |
| V020 | Unbounded Inputs | TX limit exceeded | Many required UTxOs |
| V021 | UTxO Contention / Concurrency DoS | Bottleneck, DoS | Shared global state, no rate limit |
| V022 | Cheap Spam/Dust | Operation obstruction | No minimum amounts |
| V023 | Locked Value | Permanent lock | Missing exit paths |
False Positive Verification
CRITICAL: Before reporting ANY vulnerability, you MUST verify exploitability by tracing code execution with a concrete attack transaction.
Verification Method: Attack Transaction Tracing
For each potential vulnerability:
-
Construct a concrete attack transaction
- Define specific inputs (UTxOs with concrete values/datums)
- Define the redeemer values
- Define the outputs the attacker would create
- Define signatories
-
Execute the validator logic mentally with this transaction
- Go line-by-line through the validator code
- Track what each variable evaluates to with your attack tx
- Check EVERY
require()statement - does it pass or fail?
-
If ANY require fails, the attack fails → False Positive
-
Only report if ALL requires pass with the attack transaction
Example: V005 Double Satisfaction Verification
Potential vulnerability detected: handlePay uses getAdaFromOutputs(sellerOutputs) without unique linking.
Construct attack transaction:
Inputs:
- EscrowA: 12 ADA, datum={seller=S, buyer=B, escrowAmount=10, initAmount=2}
- EscrowB: 12 ADA, datum={seller=S, buyer=B, escrowAmount=10, initAmount=2}
Outputs:
- 12 ADA to seller S (single output for both!)
- 1 ADA to buyer B
Signatories: [B]
Trace execution for EscrowA:
// Line 57-58:
val contractInputs = txInfo.findOwnInputsByCredential(contractAddress.credential)
// → Returns [EscrowA, EscrowB] (both have same script credential)
val contractBalance = Utils.getAdaFromInputs(contractInputs)
// → 12 + 12 = 24 ADA
// Line 118-120 in handlePay:
require(contractBalance === escrowDatum.escrowAmount + escrowDatum.initializationAmount)
// → require(24 === 10 + 2)
// → require(24 === 12)
// → FAILS! ❌
Result: Attack transaction fails at line 118-120. This is a FALSE POSITIVE.
When to Write a Test
If the attack trace is complex or you're uncertain, write an actual test:
test("V005: Double satisfaction attack should fail") {
// Setup: Create two escrow UTxOs with same seller
val escrowA = createEscrowUtxo(seller = S, buyer = B, amount = 10.ada)
val escrowB = createEscrowUtxo(seller = S, buyer = B, amount = 10.ada)
// Attack: Try to spend both with single output to seller
val attackTx = Transaction(
inputs = List(escrowA, escrowB),
outputs = List(TxOut(sellerAddress, 12.ada)), // Only pay once!
redeemers = Map(escrowA -> Pay, escrowB -> Pay)
)
// Verify: Should this pass or fail?
// If it passes → Real vulnerability
// If it fails → False positive
evaluateValidator(EscrowValidator, escrowA, attackTx) shouldBe failure
}
Verification Checklist
Before reporting, answer these questions:
| Question | Answer Required |
|---|---|
| What is the specific attack transaction? | Inputs, outputs, redeemers, signatories |
| Which line would the attacker exploit? | File:line reference |
| Did you trace through EVERY require in the code path? | Yes/No |
| Does the attack pass ALL requires? | Yes (report) / No (false positive) |
| What value does the attacker gain? | Concrete amount/asset |
Do NOT Report If
- You only found a pattern match without tracing execution
- You haven't constructed a specific attack transaction
- Any
require()in the code path would fail the attack - You're unsure whether the attack works (investigate more or write a test)
Output Format
Use clickable file_path:line_number format for all code locations.
Finding Format
For each vulnerability found, output in this format:
### [SEVERITY] ID: Vulnerability Name
**Location:** `full/path/to/File.scala:LINE`
**Method:** methodName
**Issue:** Brief description of what's wrong
**Vulnerable code** (`full/path/to/File.scala:LINE-LINE`):
```scala
// actual code from file
Fix:
// proposed fix
### Summary Table
At the end, provide a summary with clickable locations:
Summary
| ID | Severity | Location | Issue | Status |
|---|---|---|---|---|
| C-01 | Critical | path/File.scala:123 | Missing mint validation | Fixed |
| H-01 | High | path/File.scala:87 | Token not in output | Declined |
| M-01 | Medium | path/File.scala:200 | Missing signature | False Positive |
False Positives
| ID | Location | Reason |
|---|---|---|
| M-01 | path/File.scala:200 | Authorization is done via NFT ownership in verifyAuth helper |
Security Grade: A/B/C/D/F
### Location Format Rules
1. Always use full path from project root: `scalus-examples/jvm/src/.../File.scala:123`
2. For ranges use: `File.scala:123-145`
3. For method references: `File.scala:123` (methodName)
4. Make locations clickable by using backticks
## Interactive Workflow
For each finding:
1. Display issue with location and proposed fix
2. Prompt: "Apply fix? [y/n/s/d/f]"
- y: Apply fix, mark completed, verify with `sbtn compile`
- n: Skip, log as "declined"
- s: Skip without logging
- d: Show more details (attack scenario)
- f: Mark as false positive (prompts for reason, logged to summary)
3. After all findings: run `sbtn quick` to verify fixes
4. Generate summary report including:
- Fixed issues
- Declined issues
- False positives with reasons
## Reference
For detailed vulnerability patterns and code examples, see:
- `references/vulnerabilities.md` - Full pattern documentation with Scalus-specific examples
スコア
総合スコア
リポジトリの品質指標に基づく評価
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
1ヶ月以内に更新
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
レビュー
レビュー機能は近日公開予定です



