Auditor DogSubmit a repository
Security audit report

Kamino-Finance/scope

commit dab7be9 · claude-opus-4-5 · corpus auditor-skill@4.3 · 2026-09-10 19:59 UTC

0
Critical
0
High
0
Medium
0
Low
0
Info

Provenance

Visibility
public
Highest severity
none / 10
Report sha256
ca9fbba52d182990876c9460f62a4fdfddd8ab5b922c82c4f9d232f971db9453
Usage
0 in · 0 out · 0 cached · $0.00
Source file
Auditor-Dog/auditor-attest/reports/…
README badge
[![audit](http://localhost:3000/badge/Kamino-Finance/scope.svg)](http://localhost:3000/r/Kamino-Finance/scope)

On-chain attestation · Solana devnet

Transaction
rdTuqJ2e7oWV4yFsDQBCUxTwAcN7n2Jb69gMfjBksZwxcTo4LsVdJdZDyGk5iUcf4R6FpPa8URndEeT9vxFF5fi
Account
6j6jGxQVmDDmDtsbMGmA71W1AAqUZ23i2P1oq6NNS2t9
Attester
4ZwnFVFENrXwLjdxz8g835TSQq4J8cVByH45vXPDenQf
Program
sXtvdoheTtFBukx8vCppJHhiiJHW2xa3xc5KaPAhzkC

Verify without us: the account address is the PDA of sha256(lowercased repo url) + the commit bytes under the program; its data holds the report sha256 above.

Security Audit Report - Kamino Scope (Oracle Aggregator)

  • Repository / Program: Kamino Scope - C:/Users/felip/auditor_validation/Kamino/scope/programs
  • Scope: PROGRAM (Scope core program + *-itf source-adapter crates for foreign-account deserialization)
  • Audit date: 2026-06-19
  • Asset at risk: Price integrity (no fund custody in this program)
  • Method: AI-assisted first-pass static review, with independent skeptic re-verification of all candidate findings

1. Executive Summary

Scope is an on-chain oracle aggregator. It has no custody of user funds; the security-relevant asset is the integrity and freshness of the prices it publishes for downstream consumers (lending markets, vaults, etc.). The core trust model verified during this pass:

  • Every base price account is checked against oracle_mappings.price_info_accounts[idx] before deserialization (handler_refresh_prices.rs:83), preventing permissionless account substitution.
  • check_execution_ctx (handler_refresh_prices.rs:188) blocks the refresh instruction from running under CPI and requires preceding instructions to be Compute Budget only, defeating sandwich/replay manipulation.
  • Privileged instructions (mapping/config changes, admin transfer, freeze, TWAP reset) are gated by has_one = admin checks.
  • Chainlink and Pyth Lazer refresh paths verify reports via external programs with hardcoded program IDs.
  • Securitize hardcodes ACRED_VAULT_PK; RedStone and Switchboard-on-demand check program owner explicitly.

Confirmed findings: 0. After independent skeptic re-verification, none of the raw candidate observations were confirmed as exploitable, real-world bugs within the program''s threat model. The candidates fell into three groups: (a) staleness checks deliberately delegated to the consumer (documented in-code), (b) "missing owner check" gaps that are neutralized by the mandatory address-equality check at refresh time and by hardcoded/admin-locked addresses, and (c) admin-gated or correctness/UX issues with no permissionless attack path.

Repository Risk Score

LOW - highest confirmed severity = none (0 confirmed findings).

Plain-language verdict: In this first-pass review, the Scope program looked clean. The aggregator''s central invariant - that an attacker cannot inject a price account it controls without admin authority - held up under review. The most interesting candidates (Pyth/Switchboard/RedStone staleness) reflect a deliberate design choice to push freshness enforcement to consumers, not a latent program bug. They are documented below as informational so integrators are aware of the contract.

Severity distribution (CONFIRMED findings only)

SeverityCount
Critical0
High0
Medium0
Low0
Total0

2. Confirmed Findings

None.

No findings survived independent verification in this pass. See Section 3 for the full set of unconfirmed/informational candidates and why each was not promoted.


3. Unconfirmed / Informational Candidates

These were generated as raw candidates and refuted or down-graded during verification. They are listed for completeness and to inform consumers/integrators. They are not confirmed vulnerabilities and must not be reported as such. Do not inflate.

#CandidateLocationRaw sevWhy not confirmed
1PythPull staleness check bypassed (i64::MAX age)oracles/pyth_pull.rs:227Intentional design: code comment states staleness "should be filtered by the caller." Freshness is delegated to downstream consumers via the published unix_timestamp. No in-program invariant is violated. Informational contract note for integrators.
2PythPullEMA staleness not enforcedoracles/pyth_pull_ema.rs:147Same deliberate delegation as #1. MAXIMUM_AGE is declared but unused by design; clamp_timestamp_to_now prevents future timestamps. Consumer-side responsibility.
3Switchboard On-Demand ignores max_stalenessoracles/switchboard_on_demand.rs:556Consistent with the consumer-enforced freshness model; result.slot is propagated for downstream staleness checks. Not an in-program break.
4RedStone missing absolute max-age checkoracles/redstone.rs:385Monotonic timestamp guard present; absolute window enforcement delegated to consumer. Same freshness contract.
5SplStake/MsolStake: validate_oracle_cfg returns Ok(())oracles/mod.rs:332,3365Requires admin (or admin-key compromise) to register the mapping. Not permissionless. Admin is a trusted role in the model.
6msol_stake::get_price no owner checkoracles/msol_stake.rs:144Address is locked to the admin-registered mapping and checked at refresh (:83); substitution requires admin. Defense-in-depth gap only.
7spl_stake::get_price no owner checkoracles/spl_stake.rs:204Same as #6 - admin-locked address, refresh-time address equality enforced.
8jupiter_lp no program-owner check (discriminator-only)oracles/jupiter_lp.rs:23,614Address pre-registered by admin and equality-checked at refresh; discriminator collision at a fixed admin-locked address is not a practical permissionless path.
9flashtrade_lp::get_price no owner checkoracles/flashtrade_lp.rs:434validate_flashtrade_pool checks owner at registration; refresh-time address lock prevents substitution. Defensive gap only.
10adrena_lp::get_price no owner checkoracles/adrena_lp.rs:444Same structural pattern as #9; address-locked + admin-gated.
11Switchboard validate_confidence panics on negative mantissaoracles/switchboard_on_demand.rs:794Reaching a negative stdev_mantissa requires admin-crafted/owned account data; with overflow-checks=true the tx merely reverts (per-token refresh skipped when batch). Admin-gated DoS, no price corruption.
12Chainlink v7/v9 missing confidence interval checkoracles/chainlink.rs:2894Chainlink reports are CPI-verified against the Chainlink verifier with hardcoded IDs; v9 retains ripcord + nav_date age gates. Signal-quality gap, not an injection path.
13PythPullEMA uses spot conf to validate EMA priceoracles/pyth_pull_ema.rs:234Real behavioral quirk (spot conf vs ema_conf), but effect is feed availability / weakened gate, not price injection or fund loss. Worth fixing; not a confirmed vulnerability.
14JitoRestaking u16 fee-bps addition can panicoracles/jito_restaking.rs:322Requires admin to point mapping at a crafted vault-shaped account; admin-gated DoS of one feed only.
15securitize::get_sacred_price no owner checkoracles/securitize.rs:363Neutralized: check_accounts enforces key == ACRED_VAULT_PK (hardcoded). Substitution impossible.
16Orca/Raydium/Meteora no explicit owner checkoracles/orca_whirlpool.rs:30, raydium_ammv3.rs:12, meteora_dlmm.rs:333Address locked to mapping; permissionless substitution prevented. Discriminator collision at a fixed admin-locked address is highly constrained.
17approve_admin_cached does not clear admin_cachedhandlers/handler_approve_admin_cached.rs:263Stale state / monitoring confusion only; no escalation (admin == admin_cached after transfer), no fund/price impact.
18check_confidence_interval unchecked u128 mulutils/math.rs:2143All current callers pass equal exponents; would require a new oracle integration/misconfig with extreme exponent skew. With overflow-checks=true it reverts, not corrupts.
19CreateMintMap stores caller-supplied bumphandlers/handler_create_mint_map.rs:43-592Anchor init derives the canonical bump independently for creation; on-chain code never re-derives from stored bump. Correctness issue for off-chain integrators only.
20Composite oracle types do not reject self-referenceoracles/most_recent_of.rs:119-1382Requires trusted admin to mis-configure; no permissionless path or fund drain.
21price_of_lamports_to_price_of_tokens unchecked u64 mulutils/math.rs:1042Bounded by Solana max-18 decimals in practice; with overflow-checks=true reverts rather than corrupts.

Cross-cutting note on the "missing owner check" cluster (#6-10, #15, #16): the recurring pattern is that several get_price adapters rely on Anchor discriminator checks rather than an explicit owner-equality check. In every case the exploit requires either (a) admin authority to set the mapping or (b) a post-registration owner reassignment at a fixed, admin-locked address. The mandatory address-equality check at handler_refresh_prices.rs:83 removes the permissionless substitution vector. These remain reasonable defense-in-depth hardening suggestions (add explicit owner checks in each get_price), but none is a confirmed vulnerability in the current threat model.

Cross-cutting note on the staleness cluster (#1-4): Scope deliberately publishes raw oracle timestamps and pushes max-age enforcement to consumers. This is documented in-code. It is a contract that integrators must honor (always check unix_timestamp/last_updated_slot freshness), not a Scope bug. If product intent is to enforce a hard max-age in-program, that would be an enhancement, not a fix.


4. Coverage & Methodology

What was read

  • Core program: handler_refresh_prices.rs (refresh dispatch, address check, CPI guard), oracles/mod.rs (OracleType dispatch, validate_oracle_cfg, get_non_zero_price), state definitions (configuration, oracle_mappings, oracle_prices, oracle_twaps, token_metadatas, mints_to_scope_chains, oracle_type), and utils/ (math.rs, price_impl.rs, pdas.rs, scope_chain.rs, source_entries.rs, consts).
  • All handlers: initialize, update_mapping_and_metadata, set_admin_cached, approve_admin_cached, set_emergency_council, freeze_price, reset_twap, create_mint_map, close_mint_map, resume_chainlinkx_price, refresh_prices, refresh_chainlink_price, refresh_pyth_lazer_price.
  • All oracle adapters: pyth, pyth_pull, pyth_pull_ema, pyth_lazer, switchboard_on_demand, chainlink, redstone, securitize, jupiter_lp, adrena_lp, flashtrade_lp, orca_whirlpool, raydium_ammv3, meteora_dlmm, ktokens, ktokens_token_x, msol_stake, spl_stake, jito_restaking, spl_balance, staked_sol_balance, total_mint_supply, fixed_price, most_recent_of, capped_most_recent_of, multiplication_chain, capped_floored, conditional, twap, discount_to_maturity.
  • *Foreign-account -itf adapter crates: adrena-perp-itf, flashtrade-perp-itf, jup-perp-itf, lb-clmm-itf, redstone-itf, sbod-itf, securitize-itf, switchbord-itf.

What was verified safe

  • Base account address always checked against oracle_mappings.price_info_accounts[idx] before deserialization (no permissionless substitution).
  • check_execution_ctx blocks CPI/sandwich replay of refresh; only Compute Budget instructions may precede it.
  • Securitize hardcodes ACRED_VAULT_PK; RedStone and Switchboard-on-demand explicitly check program owner.
  • Chainlink refresh is CPI-gated through the Chainlink verifier with hardcoded pubkeys; Pyth Lazer enforces monotone timestamp, confidence interval, channel ID, and feed ID.
  • Pyth legacy (v1) enforces 10-min slot staleness and ORACLE_CONFIDENCE_FACTOR.
  • admin has_one checks on all privileged instructions; mappings cross-linked via has_one in refresh paths.
  • Freeze flag preserved/stripped correctly across read/write paths; zero-price guard (get_non_zero_price) covers all non-exempt types.
  • overflow-checks = true in the release profile (Cargo.toml) - arithmetic overflows revert rather than wrap silently.
  • Chainlink bigint parsing rejects negatives/oversized values; kToken sqrt-price uses Decimal arithmetic; TWAP EMA tracker logic correct.

Limitations - read this

  • This is an AI-assisted FIRST-PASS review. It is not exhaustive and is not a substitute for a full professional audit.
  • Not verified: deploy-time configuration, upgrade-authority/governance setup, key management, admin operational security, off-chain keeper/relayer behavior, and any on-chain operations/runtime state.
  • The threat model treats admin as a trusted role. Findings whose only attack path is "admin (or compromised admin key) does X" were therefore not promoted to confirmed. If admin-key compromise is in-scope for your threat model, re-evaluate candidates #5-7, #8-11, #14, #20.
  • Staleness behavior (#1-4) is a consumer contract, not assessed against any specific downstream integrator''s enforcement.
  • Dynamic testing, fuzzing, and formal verification were not performed.

5. Disclaimer

This report is provided on a best-effort, as-is basis and reflects an automated, AI-assisted static analysis pass. It does not guarantee the absence of vulnerabilities. The absence of confirmed findings in this pass does not certify the program as secure.

All findings - including the unconfirmed/informational candidates in Section 3 - must be independently re-verified by a human security engineer before any bug-bounty submission or production reliance. Do not submit any item from Section 3 to a bug-bounty program without first reproducing it against the live program and confirming a concrete, in-scope impact; several were explicitly refuted and submitting them as-is would be inaccurate.