Research
Per-protocol2026-05-19

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

  1. Immunefi requires a PoC for every severity (including the flat $10K medium tier). Our findings are static pattern matches without runnable exploits.
  2. 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.
  3. Out-of-scope classes (referral fees, Token-22 non-irrecoverable-loss) are flat exclusions regardless of severity.
  4. 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.rs or _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, and HotAdminUpdateState admin handlers, all using the sibling-constraint pattern. The 5 remaining are user-callable init handlers where state: 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 (the referrer_account honest 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-quality suites — 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)

  1. 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.
  2. Run --profile all --min-confidence 0.0 against the suite (klend + scope + kvault + kfarms + limo) with spectre link to surface the cross-program findings already documented in kamino-suite-audit-2026-05.md, which include the in-scope ORC-002 / AUTH-100 / GOV-001 findings.
  3. 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.