> For the complete documentation index, see [llms.txt](https://docs.keystonefi.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.keystonefi.xyz/reference/security.md).

# Security Model

ksUSD is one Anchor program, one vault account the program controls, and one token. There isn't much surface area to attack, which is deliberate.

***

## Access control

| Role          | Capabilities                                                                                                                                                                                                                                                                                                                      |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Admin**     | `initialize`, `enable_phoenix`, `enable_lending`, `set_oracles`, `set_pause`, `update_params`, `collect_fees`, `transfer_admin`, `accept_admin`, `reset_peak`, `init_wind_down`                                                                                                                                                   |
| **Keeper**    | `attest_nav`, `open_position`, `close_position`, `lend_idle_usdc`, `unlend_usdc`. Gated by **exact match** against `authorized_keeper`. Note the direction: `Pubkey::default()` (the post-`initialize` state) matches no signer, so these stay **blocked** until the admin pins a real key — it is not a permissionless fallback. |
| **Any user**  | `deposit`, `withdraw_instant`, `request_withdrawal`, `process_withdrawal`, `claim_wind_down` (during wind-down), `settle`, `emergency_close` (once tripped). None of these consult the keeper key.                                                                                                                                |
| **Vault PDA** | Signs every token operation for the vault. It's derived from the program, so there's no private key anyone could steal.                                                                                                                                                                                                           |

Admin control moves in two steps, `transfer_admin` then `accept_admin`, so it can't be handed to a wrong or unreachable address by a single mistake. Once mainnet is live the admin becomes a multisig, Squads or similar.

***

## Protections

| Protection               | Mechanism                                                                                                                                                                                                    |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Emergency pause          | Admin halt of new deposits + new positions; instant withdrawals stay open                                                                                                                                    |
| Wind-down                | Terminal `init_wind_down` blocks new state; users redeem pro-rata via `claim_wind_down`                                                                                                                      |
| Deposit cap              | `cached_nav_usdc + usdc_amount > deposit_cap_usdc` reverts; `0` fully pauses deposits                                                                                                                        |
| Bootstrap residual guard | First deposit reverts if vault USDC ATA is non-empty (`BootstrapResidualUsdc`) — prevents dilution griefing                                                                                                  |
| Oracle validation        | Pyth staleness (5 min) + confidence (2%) checks; revert on either                                                                                                                                            |
| LST depeg auto-pause     | `settle` reverts AND pauses the vault if jitoSOL/SOL deviates beyond `lst_depeg_bps`                                                                                                                         |
| Integer arithmetic       | `checked_add` / `checked_mul` everywhere; 128-bit intermediates for NAV math; no floating point                                                                                                              |
| Slippage protection      | Jupiter swaps gated by `max_swap_slippage_bps` (default 0.5%) plus explicit `min_*_out` arguments                                                                                                            |
| HWM monotonicity         | Performance fees never charged twice on the same gains; HWM only goes up                                                                                                                                     |
| Drawdown guard           | `emergency_close` is callable by anyone once `peak − current ≥ emergency_close_dd_bps` AND observed across `consecutive_dd_settles_required` settles                                                         |
| Drawdown latency         | Single-tick drawdown reverts with `DrawdownTriggerLatent`                                                                                                                                                    |
| Mode-switch dwell        | `min_dwell_seconds` (default 12 h) between mode transitions — prevents ping-pong                                                                                                                             |
| Funding signal staleness | Opens revert with `FundingSignalStale` if the EMA hasn't been refreshed within `funding_max_staleness_seconds`                                                                                               |
| Funding signal integrity | `settle` is open to anyone, but only the authorized keeper's call updates the funding EMA. Otherwise any caller could steer the signal (or seed it outright on the first call) to force or block basis entry |
| Venue circuit breaker    | Opens revert with `PhoenixMarketNotActive` if SOL-PERP is halted, settling, or delisted                                                                                                                      |
| Position size caps       | The short is bounded by `max_position_base_lots` in absolute base lots (`PerpShortExceedsSizeCap`). There is no on-chain open-interest check — OI discipline is applied off-chain when that cap is set       |
| Funding rate sanity      | Venue-reported rates outside a hard sanity band revert with `FundingRateInsane`                                                                                                                              |
| NAV change cap           | `attest_nav` deltas bounded by `max_nav_change_bps_per_hour`                                                                                                                                                 |
| Withdrawal FIFO          | Strict FIFO order via `WithdrawalNotNextInQueue` guard; per-request price-at-process haircut so depositors never extract more than pro-rata                                                                  |

***

## Account-context layering (strategy instructions)

`open_position`, `close_position`, and `emergency_close` each call several other programs in a single transaction: Phoenix, Ember, Kamino, and Jupiter. Since every one of those needs its own set of accounts, the handler has to divide up one long list. It does that in three steps:

1. Takes the number of accounts per group as `u8` arguments.
2. Splits `remaining_accounts` using `checked_add(...).ok_or(InvalidParams)`, so an offset that would overflow reverts instead of wrapping around.
3. Hands each slice to the right helper, which then checks the accounts it was given.

The result is that the program never reads past the accounts it was actually handed, even if someone passes deliberately bad arguments. And each venue still validates its own accounts, so the vault isn't the only thing standing between a bad call and a bad outcome.

***

## Audit status

The program is **currently unaudited**. An audit is planned before mainnet with a top-tier Solana firm (Ottersec, Sec3, or Neodyme). The scope is small enough to audit properly: one Anchor crate and one state struct.

Internal pre-audit notes are in [the source](https://github.com/kamwithak/keystone-contracts/blob/main/docs/PRE_MAINNET_AUDIT.md).

**Don't deploy significant capital before the audit.**

***

## Reporting

Suspected vulnerabilities:

* Open a private GitHub security advisory on [github.com/kamwithak/keystone-contracts](https://github.com/kamwithak/keystone-contracts/security/advisories), **or**
* Contact the maintainer directly

A formal bug bounty stands up alongside the mainnet audit.

***

## Related

* [Whitepaper — Risk](/how-it-works/whitepaper.md#risks)
* [Fees](/start-here/fees.md) · [NAV & share pricing](/how-it-works/strategy-and-modes/nav-calculation.md)
* [Errors](/build-on-it/quick-start/errors.md) — every revert path enumerated
