|
| 1 | +# Accretion Audit Delta Brief — `program-v2` `audit-baseline-2026-02-accretion` → `audit-pending-v1` |
| 2 | + |
| 3 | +**Repository:** `lazor-kit/program-v2` |
| 4 | +**Previous audit:** Accretion Labs, Solana Foundation, February 2026, A26SFR1 |
| 5 | +([audits/2026-accretion-solana-foundation-lazorkit-audit-A26SFR1.pdf](../../audits/2026-accretion-solana-foundation-lazorkit-audit-A26SFR1.pdf)) |
| 6 | +**Previous baseline tag:** `audit-baseline-2026-02-accretion` → commit `d1eaaeb` (Merge PR #49 fix/audit-hardening, 17/17 findings resolved) |
| 7 | +**Delta tag:** `audit-pending-v1` → commit `9c97fe2` |
| 8 | +**Delta scope:** 21 files in `program/`, +4168 / −444 lines |
| 9 | + |
| 10 | +This delta brief is for an **Accretion follow-up engagement** to confirm the |
| 11 | +changes between the two tags introduce no new vulnerabilities versus the prior |
| 12 | +audited state. Most of the new code is **byte-identical with the |
| 13 | +already-audited `lazorkit-protocol` repo** (commercial sibling under the same |
| 14 | +audit engagement); this is documented per-file in |
| 15 | +[upstream-parity.txt](./upstream-parity.txt). |
| 16 | + |
| 17 | +--- |
| 18 | + |
| 19 | +## Strategic context (deploy plan) |
| 20 | + |
| 21 | +`program-v2` ships at the **same mainnet program ID** as `lazorkit-protocol` |
| 22 | +(`LazorjRFNavitUaBu5m3WaNPjU1maipvSW2rZfAFAKi`). The foundation contract |
| 23 | +period uses the `program-v2` (no-fee) build at that slot; afterwards, the |
| 24 | +upgrade authority swaps the binary at the same slot to the `lazorkit-protocol` |
| 25 | +(commercial, with-fee) build. |
| 26 | + |
| 27 | +**Implication for audit scope:** binary-swap compatibility requires that |
| 28 | +program-v2 and lazorkit-protocol share identical: |
| 29 | +- Account layouts (Wallet, Authority, Session, DeferredExec — verified |
| 30 | + byte-identical via `state/*.rs` ↔ upstream) |
| 31 | +- Instruction encoding (discriminators 0–9, account orderings — kept aligned |
| 32 | + via the IDL sync in P5.4) |
| 33 | +- Auth verification logic (auth/secp256r1/* — byte-identical with upstream |
| 34 | + after P5.1) |
| 35 | + |
| 36 | +Audit confirmation of this delta therefore also benefits the slot-share |
| 37 | +strategy: any wallet/authority/session created on either binary remains |
| 38 | +verifiable by the other after swap. |
| 39 | + |
| 40 | +--- |
| 41 | + |
| 42 | +## Delta by phase |
| 43 | + |
| 44 | +The 23 commits between the two tags group into 5 phases: |
| 45 | + |
| 46 | +### P0 — Cherry-pick guardrails (zero-audit, mechanical) |
| 47 | + |
| 48 | +Tooling-only. Adds `scripts/strip-fee.sh`, `scripts/check-no-fee.sh`, |
| 49 | +`scripts/fee-paths.txt`, and `.github/workflows/check-no-fee.yml` to enforce |
| 50 | +that fee/admin/FeeRecord surface from `lazorkit-protocol` cannot leak into |
| 51 | +`program-v2` during cherry-picks. **No `program/` source changed.** |
| 52 | + |
| 53 | +### P1 — Action types port + execute enforcement (delta-audit) |
| 54 | + |
| 55 | +**Most security-relevant section of the delta.** New on-chain feature. |
| 56 | + |
| 57 | +Files touched: |
| 58 | +- `program/src/state/action.rs` (NEW, 697 lines) — byte-identical with |
| 59 | + upstream `lazorkit-protocol/program/src/state/action.rs` (already audited). |
| 60 | + Defines 8 action types + parser + validator. |
| 61 | +- `program/src/state/session.rs` — minor (+23 lines) — adds |
| 62 | + `SESSION_HEADER_SIZE` const and `actions_slice()` helper. Byte-identical |
| 63 | + with upstream. |
| 64 | +- `program/src/state/mod.rs` — adds `pub mod action;`. |
| 65 | +- `program/src/processor/create_session.rs` — adopts variable-size session PDA |
| 66 | + for optional actions buffer + validation at creation. Byte-identical with |
| 67 | + upstream. |
| 68 | +- `program/src/processor/execute_actions.rs` (NEW, 1644 lines) — |
| 69 | + byte-identical with upstream `lazorkit-protocol/program/src/processor/execute/actions.rs`. |
| 70 | + Pre-CPI program whitelist/blacklist + token snapshots; post-CPI delta |
| 71 | + computation, SOL/token cap enforcement, recurring window resets, vault |
| 72 | + invariant defenses against System::Assign / SetAuthority escapes. |
| 73 | +- `program/src/processor/execute.rs` — wires pre/post action evaluation |
| 74 | + around the CPI loop, adds the L5 anti-CPI guard for sessions, gross SOL |
| 75 | + outflow tracking. Differs from upstream `processor/execute/immediate.rs` |
| 76 | + by **1 import-path line** (`processor::execute_actions::` vs |
| 77 | + `processor::execute::actions::`) due to flat vs nested processor layout. |
| 78 | +- `program/src/error.rs` — adds 13 error variants (3020–3032) for action |
| 79 | + validation, enforcement, and vault invariant defense. |
| 80 | + |
| 81 | +**Audit ask (P1):** Confirm action enforcement engine + execute integration |
| 82 | +introduce no new vulnerabilities vs. the audited upstream version. |
| 83 | + |
| 84 | +### P2 — Dual-cluster + security_txt (zero-audit, mechanical) |
| 85 | + |
| 86 | +- `program/src/lib.rs` — embeds `security_txt!` with program-v2-specific URLs |
| 87 | + (only difference vs upstream is the URL strings — see `upstream-parity.txt`) |
| 88 | +- `assertions/src/lib.rs`, `assertions/Cargo.toml`, `program/Cargo.toml` — |
| 89 | + Pattern D feature flags: `--features mainnet` embeds `LazorjRF…` (slot |
| 90 | + shared with upstream); `--features devnet` embeds `FLb7…`; no feature → |
| 91 | + `compile_error!` (prevents accidental cross-cluster deploy) |
| 92 | + |
| 93 | +No on-chain logic change. Build-time configuration only. |
| 94 | + |
| 95 | +### P3 — SDK consolidation (no audit) |
| 96 | + |
| 97 | +Off-chain only. `program-v2/sdk/solita-client/` deleted; `program-v2/tests-sdk/` |
| 98 | +migrated to `@lazorkit/sdk-legacy` from npm (the same SDK |
| 99 | +`lazorkit-protocol` ships). |
| 100 | + |
| 101 | +### P4 — Release infrastructure (no audit) |
| 102 | + |
| 103 | +`CHANGELOG.md`, `docs/MAINNET_DEPLOY_RUNBOOK.md`, `.github/workflows/release.yml`. |
| 104 | +Documentation + CI only. |
| 105 | + |
| 106 | +### P5 — Consolidate: auth + processor port + IDL sync (delta-audit) |
| 107 | + |
| 108 | +**Aligns the remaining processor + auth files with upstream so the slot-share |
| 109 | +strategy works end-to-end.** |
| 110 | + |
| 111 | +Files (all byte-identical with upstream after this phase, except where noted): |
| 112 | +- `program/src/auth/secp256r1/{mod,webauthn,introspection}.rs` — port from |
| 113 | + upstream. Replaces older typeAndFlags-format auth (which reconstructed |
| 114 | + clientDataJSON server-side) with the format that embeds full raw |
| 115 | + clientDataJSON in the auth payload. **Byte-identical with upstream.** |
| 116 | +- `program/src/processor/{create_wallet,manage_authority,transfer_ownership, |
| 117 | + execute_deferred,revoke_session}.rs` — port from upstream. Brings authority |
| 118 | + data layout to: |
| 119 | + `Secp256r1 authority = header(48) + cred_hash(32) + pubkey(33) + rpIdHash(32) = 145B` |
| 120 | + (previously variable-length raw rpId; now precomputed SHA256 digest at |
| 121 | + offset 113, saves one syscall per Execute). **Byte-identical with upstream.** |
| 122 | +- `program/src/instruction.rs` — Shank IDL declarations resynced from |
| 123 | + upstream (account metadata: writable modifiers, positions, descriptions); |
| 124 | + 5 fee instruction variants (disc 10–14) stripped. Runtime not affected |
| 125 | + (sdk-legacy uses hand-written builders, not generated IDL). |
| 126 | + |
| 127 | +**Audit ask (P5):** Since these files are byte-identical with upstream, |
| 128 | +confirm Accretion's prior review of the upstream files extends to this |
| 129 | +program. Specifically: |
| 130 | +- Auth payload format change (typeAndFlags → embedded clientDataJSON) — was |
| 131 | + this reviewed in the upstream audit? Any concerns specific to program-v2 |
| 132 | + context? |
| 133 | +- Authority layout change (raw rpId → rpIdHash) — same question. |
| 134 | + |
| 135 | +--- |
| 136 | + |
| 137 | +## Files NOT changed since baseline (sanity) |
| 138 | + |
| 139 | +- `program/src/auth/ed25519.rs` — unchanged |
| 140 | +- `program/src/auth/mod.rs` — unchanged |
| 141 | +- `program/src/auth/traits.rs` — unchanged |
| 142 | +- `program/src/utils.rs` — unchanged |
| 143 | +- `program/src/state/wallet.rs` — unchanged (verified byte-identical with |
| 144 | + upstream — wallet account layout is the cross-binary contract for |
| 145 | + slot-share) |
| 146 | +- `program/src/state/authority.rs` — unchanged (same reasoning as wallet) |
| 147 | +- `program/src/state/deferred.rs` — unchanged |
| 148 | +- `program/src/processor/authorize.rs` — unchanged |
| 149 | +- `program/src/processor/reclaim_deferred.rs` — unchanged |
| 150 | +- `program/src/entrypoint.rs` — unchanged dispatch (still discs 0–9) |
| 151 | + |
| 152 | +--- |
| 153 | + |
| 154 | +## Strip surface — what's NOT in `program-v2` |
| 155 | + |
| 156 | +Per the slot-share strategy, the following are intentionally absent: |
| 157 | + |
| 158 | +- `program/src/state/protocol_config.rs` — admin + fee config |
| 159 | +- `program/src/state/integrator_record.rs` — FeeRecord |
| 160 | +- `program/src/state/treasury_shard.rs` — treasury |
| 161 | +- `program/src/processor/protocol/{initialize_protocol,update_protocol, |
| 162 | + register_integrator,withdraw_treasury,initialize_treasury_shard}.rs` |
| 163 | +- Discriminators 10–14 in entrypoint dispatch |
| 164 | +- `try_collect_fee` function in entrypoint |
| 165 | +- `ProtocolError` enum (codes 4001–4007) |
| 166 | +- `ProtocolConfig` / `FeeRecord` / `TreasuryShard` discriminator slots (5/6/7) |
| 167 | + in `state/mod.rs::AccountDiscriminator` |
| 168 | + |
| 169 | +`scripts/check-no-fee.sh` enforces this via `scripts/fee-paths.txt`. CI fails |
| 170 | +on any introduction. Verified clean at `audit-pending-v1`. |
| 171 | + |
| 172 | +--- |
| 173 | + |
| 174 | +## What we want from this engagement |
| 175 | + |
| 176 | +Specific questions for Accretion: |
| 177 | + |
| 178 | +1. **P1 action enforcement** — is the `evaluate_pre_actions` / |
| 179 | + `evaluate_post_actions` engine + integration into `execute.rs` and |
| 180 | + `create_session.rs` introducing any new vulnerabilities the prior audit |
| 181 | + didn't cover? Action discriminator collision, integer overflow in |
| 182 | + recurring-limit window math, race conditions between snapshot/execute, |
| 183 | + vault invariant gaps? |
| 184 | + |
| 185 | +2. **P5 auth port** — confirm the typeAndFlags → embedded-clientDataJSON |
| 186 | + format change is safe in this context. Particular attention to: |
| 187 | + - origin field validation (intentionally omitted — see [audit doc L1 |
| 188 | + comment](../../program/src/auth/secp256r1/mod.rs)) |
| 189 | + - challenge field base64url encoding/decoding round-trip |
| 190 | + - rpIdHash computation timing |
| 191 | + |
| 192 | +3. **P5 authority layout change** — confirm the raw rpId → rpIdHash storage |
| 193 | + change preserves all binding properties. Specifically that the on-chain |
| 194 | + stored rpIdHash remains tied to the credential at registration. |
| 195 | + |
| 196 | +4. **Slot-share compatibility** — given program-v2 will live at |
| 197 | + `LazorjRF…` mainnet slot and later be replaced in-place by |
| 198 | + `lazorkit-protocol`'s commercial binary, confirm: |
| 199 | + - State account layouts are forward-compatible (existing Wallet, |
| 200 | + Authority, Session, DeferredExec accounts created by program-v2 are |
| 201 | + readable + valid for the commercial binary) |
| 202 | + - Auth verification continues working for sessions/wallets created |
| 203 | + pre-swap |
| 204 | + |
| 205 | +5. **Anything Accretion flagged in the prior audit that may have regressed** |
| 206 | + — full diff bundle at [program-src.diff](./program-src.diff) for |
| 207 | + line-by-line review. |
| 208 | + |
| 209 | +--- |
| 210 | + |
| 211 | +## Deliverables included |
| 212 | + |
| 213 | +- [DELTA_BRIEF.md](./DELTA_BRIEF.md) (this file) |
| 214 | +- [program-src.diff](./program-src.diff) — full unified diff of `program/` |
| 215 | + between the two tags (~5600 lines) |
| 216 | +- [program-src.diff.stat](./program-src.diff.stat) — per-file changed-line |
| 217 | + counts |
| 218 | +- [upstream-parity.txt](./upstream-parity.txt) — byte-identity report vs |
| 219 | + `lazorkit-protocol` |
| 220 | +- Git tags `audit-baseline-2026-02-accretion` (commit `d1eaaeb`) and |
| 221 | + `audit-pending-v1` (commit `9c97fe2`) |
| 222 | +- This brief is intended to be sent alongside the on-chain `security_txt!` |
| 223 | + pointer to the prior PDF. |
| 224 | + |
| 225 | +## Out of scope for this brief |
| 226 | + |
| 227 | +- `tests-sdk/`, `sdk/`, `docs/`, `scripts/`, `.github/`, `Cargo.toml`, |
| 228 | + `assertions/Cargo.toml` — non-program changes (test infrastructure, build |
| 229 | + config). Available in the full `git diff audit-baseline-2026-02-accretion..audit-pending-v1` |
| 230 | + if Accretion requests but not part of the on-chain audit ask. |
| 231 | +- Operational items (mainnet deploy, multisig setup, binary swap procedure) |
| 232 | + — handled in [docs/MAINNET_DEPLOY_RUNBOOK.md](../MAINNET_DEPLOY_RUNBOOK.md). |
| 233 | + |
| 234 | +--- |
| 235 | + |
| 236 | +## Build reproducibility |
| 237 | + |
| 238 | +```bash |
| 239 | +git checkout audit-pending-v1 |
| 240 | +cd program && cargo build-sbf --features mainnet |
| 241 | +shasum -a 256 ../target/deploy/lazorkit_program.so |
| 242 | +# Hash should match the artifact in the eventual mainnet deploy. |
| 243 | +``` |
| 244 | + |
| 245 | +CI workflow at `.github/workflows/release.yml` reproduces and publishes the |
| 246 | +hash on each tag push. |
| 247 | + |
| 248 | +--- |
| 249 | + |
| 250 | +## Contact |
| 251 | + |
| 252 | +- Email: security@lazorkit.app |
| 253 | +- GitHub Security Advisories: https://github.com/lazor-kit/program-v2/security/advisories/new |
| 254 | +- On-chain pointer: `solana program show LazorjRFNavitUaBu5m3WaNPjU1maipvSW2rZfAFAKi` |
| 255 | + → `security_txt!` block links to this repo + audit PDF. |
0 commit comments