Kamino klend — SPECTRE Scan Validation (2026-05-19)
Target: Kamino-Finance/klend (HEAD, shallow clone)
Scanner: pinpoint spectre scan . --local (CLI v1.2.2, strict profile, default confidence 0.78)
Codebase: 1 Anchor program, ~1137 Rust files
Bounty program: Immunefi — Kamino (max $1.5M Critical / $100K High / $10K Medium, PoC required for all severities)
Final decision: do not submit
The initial scan emitted 6 HIGH and 100 MEDIUM findings. Manual validation against Immunefi's scope rules and the actual klend source determined 0 are submission-worthy. The scan also surfaced two precision gaps in our own pinpoint-rules-solana crate, which have been fixed in this commit.
Initial findings vs. validated outcome
| # | Rule | Severity | Validation | Reason |
|---|---|---|---|---|
| HIGH-01 | ACC-013 referrer_account (flash_borrow) |
HIGH | Out-of-scope | Worst-case impact = referral-fee misdirection; Immunefi scope page explicitly excludes "Referral fee issues". |
| HIGH-02 | ACC-013 referrer_account (flash_repay) |
HIGH | Out-of-scope | Same as HIGH-01. |
| HIGH-03 | ACC-013 withdraw_ticket_owner (recover_invalid_ticket) |
HIGH | False positive (rule fixed) | handler_recover_invalid_ticket_collateral.rs:137-140 actually has #[account(mut, address = withdraw_ticket.load()?.owner)]. The owner is bound. |
| HIGH-04 | ACC-013 user_destination_liquidity (withdraw_queued) |
HIGH | False positive (rule fixed) | handler_withdraw_queued_liquidity.rs:349-352 has #[account(mut, address = withdraw_ticket.load()?.user_destination_liquidity_ta)]. Destination is pinned. |
| HIGH-05 | ACC-013 withdraw_ticket_owner (withdraw_queued) |
HIGH | False positive (rule fixed) | handler_withdraw_queued_liquidity.rs:375-378 has #[account(mut, address = withdraw_ticket.load()?.owner)]. |
| HIGH-06 | CPI-030 farms_program |
HIGH | False positive — not deployed code (walker fixed) | File is libs/klend-interface/docs/cpi_deposit_and_borrow.rs, declared // This file is a reference example — it is NOT compiled and included via #![doc = include_str!(...)]. |
| MEDIUM ×100 | TOK-022 / TOK-023 | MEDIUM | Out-of-scope + false positives (walker fixed) | (a) "Token 22 issues that do not result in irrecoverable loss of funds" is explicitly out-of-scope per Immunefi, (b) all 100 hits fire on the same non-compiled docs/ example file. |
Why none are submittable
- Immunefi requires a PoC for every severity (including the flat $10K medium tier). Our findings are static pattern matches without runnable exploits.
- High floor is $5K of demonstrable funds at risk; Critical floor is $50K. A theoretical missing-constraint without a measured loss path doesn't clear the floor even if the rule fire were real.
- Out-of-scope classes (referral fees, Token-22 non-irrecoverable-loss) are flat exclusions regardless of severity.
- Submitting false-positive HIGHs would damage Immunefi standing for zero expected payout.
Post-fix scan output
After applying the two precision fixes below and rebuilding the CLI:
=== Findings by Rule ===
ACC-013: 2
Down from 6 HIGH + 100 MEDIUM. The remaining two are the honest referrer_account rule fires (HIGH-01 and HIGH-02), which classify out-of-scope.
Internal precision fixes applied this commit
Fix 1 — ACC-013 recognizes address = … as an identity binding
code/cli/crates/pinpoint-rules-solana/src/rules/acc_013.rs. The rule previously suppressed only on has_one / constraint / owner. Added address to the suppression set: Anchor's #[account(address = X)] enforces account.key() == X at deserialization, which is functionally identical to the other identity guards. Eliminated false positives HIGH-03 / HIGH-04 / HIGH-05.
New fixture: code/cli/crates/pinpoint-rules-solana/tests/fixtures/acc_013/safe_address_constraint.rs. New test: safe_address_constraint_emits_zero_findings. Mirrors klend's exact shape (multi-line attribute, chained-method-call value).
Fix 2 — Walker excludes **/docs/**
code/cli/crates/pinpoint-cli/src/scan/default_excludes.rs. Added **/docs/** to DEFAULT_EXCLUDES. Cargo only compiles sources under src//examples//tests//benches/; .rs files under docs/ are by convention non-compiled samples (klend-interface's docs/cpi_*.rs files are included as docstrings via #![doc = include_str!(...)]). Markdown and other non-Rust content under docs/ was already inert to language extractors. Eliminated false positives HIGH-06 + all 100 MEDIUM TOK-022 / TOK-023 hits on the doc-example file.
Fix 3 — ACC-013 sibling-constraint suppression
code/cli/crates/pinpoint-rules-solana/src/rules/acc_013.rs. Added sibling_constraint_references_field: when another account_field vertex in the same #[derive(Accounts)] struct carries a constraint = … clause whose text references <this_field>.<sub>, the candidate field's identity is pinned through the sibling. Drift v2's pattern — admin: Signer<'info> with constraint = admin.key() == state.admin next to state: Box<Account<'info, State>> — was firing ACC-013 4× on admin.rs/keeper.rs in the corpus scan. Mirrors ACC-015's pre-existing detection (closed in commit 0c3b387a); pulled the same logic across to ACC-013 via the graph instead of re-parsing.
New fixture: tests/fixtures/acc_013/safe_sibling_constraint.rs. New test: safe_sibling_constraint_emits_zero_findings.
Fix 4 — is_test_path recognizes Rust test/fuzz/bench conventions
code/cli/crates/pinpoint-rules-coverage-quality/src/quality/dead_code.rs. The helper previously only matched /tests///test///__tests__/ directories and *.test.*/*.spec.* filenames. Extended to also match:
- Filename equal to
tests.rs,test.rs,test_utils.rs,test_helpers.rs. - Filename ending with
_test.rsor_tests.rs. - Path contains
/fuzz/(cargo-fuzz) or/benches/(cargo bench).
These are the de-facto Rust scaffolding conventions that the corpus QUAL-003 numbers (2938 fires across 50 protocols) were flooded with. Used by QUAL-003 (complexity) and QUAL-001 (dead code). Three new test cases lock the new patterns in and assert no substring collisions on production filenames (src/contestants.rs, src/fuzzy_match.rs, etc.).
Empirical validation
- klend (post Fixes 1+2): 6 HIGH + 100 MEDIUM → 2 HIGH + 0 MEDIUM (held by Fix 3+4).
- Drift v2 (post Fixes 1+2+3): 8 HIGH ACC-013 → 5 HIGH ACC-013. The 3 cleared correspond to
InitializeSpotMarket,InitializePerpMarket, andHotAdminUpdateStateadmin handlers, all using the sibling-constraint pattern. The 5 remaining are user-callable init handlers wherestate: Box<Account<'info, State>>is mut with no sibling binding — these are honest rule fires that depend on the program being a singleton (a semantic property ACC-013 cannot generically prove).
Full corpus replay (25-protocol common set; binary built with --features internal)
Re-ran code/cli/crates/pinpoint-rules-solana/benches/solana/runner/run.sh against the on-disk baseline at commit 6dd12439 and the post-fix HEAD. Filtered to the 25 protocols present in both runs for an apples-to-apples comparison.
| Rule | Baseline | Post-fix | Δ | Attribution |
|---|---|---|---|---|
| ACC-013 | 325 | 238 | −87 (−26.8%) | This commit's Fix 1 (address =) + Fix 3 (sibling-constraint) |
| QUAL-003 | 1884 | 2 | −1882 | Pre-existing strict-gate exclusion (commit 0e899fbd); the runner result snapshots at 6dd12439 had been generated without strict — not attributable to this work. Fix 4 still improves precision in QUAL-001/dead-code paths that are in strict. |
| All other 35 rules | — | — | 0 | No regressions across ACC-010/011/012/014/015/020/021/030, ARI-040, AUTH-001/100, CLOSE-080/090, CONFIG-010, COV-001, CPI-003/020/030, CROSS-005/007/010/CDF, DEPVULN-001, EVT-001, GOV-001, INV-001, ITER-001, LIAB-001, ORC-002/003, PDA-031/040, PKT-003, RACE-004, TOK-040. |
Top per-protocol ACC-013 reductions from the corpus replay:
12-ottersec-drift-v2.json: 8 → 5 (matches the standalone Drift v2 scan).25-drift-v2-architectural-reference.json: similar admin-handler pattern.33-kamino-lending.json: held at 2 (thereferrer_accounthonest fires).
Reproduction
cargo build --release --features internal -p pinpoint-cli
cd code/cli/crates/pinpoint-rules-solana/benches/solana/runner
SCAN_TIMEOUT=180 bash run.sh
# Compare counts:
for f in results/*.json; do jq -r '.findings[].rule_id' "$f" 2>/dev/null; done | sort | uniq -c | sort -rn
Tests
cargo test -p pinpoint-rules-solana --test acc_013_test— 14/14 pass (12 prior + 2 new).cargo test -p pinpoint-cli --lib default_excludes— 6/6 pass (5 prior + new fixture path in 2 existing assertions).cargo test -p pinpoint-rules-coverage-quality --lib is_test_path— 11/11 pass (8 prior + 3 new).- Full
pinpoint-rules-solana+pinpoint-rules-coverage-qualitysuites — 0 regressions.
(One pre-existing failure in pinpoint-cli's rule_count_matches_expected is registry drift on main, unrelated to these changes — verified by re-running with the fixes stashed.)
Reproduction
git clone --depth 1 https://github.com/Kamino-Finance/klend.git /tmp/klend
cd code/cli && cargo build --release -p pinpoint-cli
/tmp/klend && pinpoint spectre scan . --local --output summary
Out-of-scope rule fires (kept for reference)
ACC-013-OOS-01 — referrer_account writable without authority check (flash_borrow)
- File:
programs/klend/src/handlers/handler_flash_borrow_reserve_liquidity.rs:109 - Struct:
FlashBorrowReserveLiquidity - Field:
referrer_account: Option<AccountInfo<'info>>with#[account(mut)]and no signer/address/seed binding. - Impact reachable from this primitive: misdirection of referral fees credited to the caller-supplied account.
- Why not submitted: Immunefi scope page lists "Referral fee issues" as out-of-scope. Even with a working PoC the report would be closed without reward.
ACC-013-OOS-02 — referrer_account writable without authority check (flash_repay)
- File:
programs/klend/src/handlers/handler_flash_repay_reserve_liquidity.rs:147 - Struct:
FlashRepayReserveLiquidity - Same shape and same out-of-scope classification as ACC-013-OOS-01.
Suggested follow-ups (not for submission)
- Pull the exact in-scope commit Immunefi pins for KLend (the Information page lists assets but not commits; the Scope page links each asset's repo at the bounty-relevant tag). Re-scan against that.
- Run
--profile all --min-confidence 0.0against the suite (klend + scope + kvault + kfarms + limo) withspectre linkto surface the cross-program findings already documented inkamino-suite-audit-2026-05.md, which include the in-scope ORC-002 / AUTH-100 / GOV-001 findings. - For any genuine submission candidate from that broader pass, build a localnet PoC (Anchor test that triggers the impact end-to-end) before drafting the Immunefi report.