Cantina audit fixes — v1.3.0 - #206
Open
maxencerb wants to merge 13 commits into
Open
Conversation
setBondConfig/removeBondConfig move from VAULT_MANAGER_ROLE to OPERATOR_ROLE. The bond is paid with borrowed assets before the full input is committed, so the fund cannot bound the bond leg against held balances: the recipient address is the only guardrail on bond payments, and it is now defined by the most-trusted role. An overstated redeem input can still consume shares as bond, but only towards an operator-vouched Midas recipient, recoverable off-band. Refs 3F-509.
… growth (#208) The freed collateral of an LTV-down operation is sized net of its own expected yield (w = x * (1 + Yr) / (1 + Yc)), so measuring the settlement price drift against unity double counted the expected collateral yield as a phantom surplus. remediationDelta now takes the collateralYieldRate the operation was sized with and pivots the surplus/shortfall split at the expected growth 1 + Yc (selector change; no consumer in this repo). Cantina finding #22 (3F-495).
* fix: harden Retargetter rebalance path and flash-loan callback - rebalance: totalAssets must not grow while the operation's Request is unrepaid and short of its deadline (value-conservation gate, all callers) - rebalance: a module above target only reverts when it worsened, so an untouched module no longer blocks the other legs - onFlashLoan: bound to the payload committed at the window open (transient digest) and to full delivery of the principal (pre-loan balance snapshot) * fix: measure bridge value on the whole book and scope window authority to the payload The value-conservation gate read the position manager's totalAssets, which drops any module whose debt exceeds its collateral, so it read flat while bridge capital was poured into an underwater module. It now measures quoted collateral minus debt summed over every module (PositionValueIncreased). Window step authority moved from the whole flash-loan call into the callback around the committed payload, so a module holds no authority in its own frame and cannot source the delivery check's balance growth from the position manager itself. * refactor: read the Request through syncRepaidStatus and store the payload digest as a uint The value gate derived the effective repaid state from the mirrored deadline plus the stale repaid flag; syncRepaidStatus returns exactly that disjunction and folds a past-deadline Request into the flag, the same idiom resolve uses. The payload digest now goes through the existing tStoreUint/tLoadUint, so LibTransientSlot is untouched by this PR. * perf: read the position aggregates once per rebalance snapshot _positionSnapshot returns the aggregate LTV and the net value from a single read of collateralAmountQuoted/debtAmount, replacing the two helpers that each re-read the pair. The post-call check hands the LTV back to the direction checks so they do not read it again, which also keeps valueAfter off the rebalance stack (it would otherwise re-trigger stack-too-deep). Halves the module iterations on the rebalancer path (8 to 4) and drops the runtime size by 169 bytes. * docs: point the rebalance NatSpec at the renamed value-gate helpers
* fix: harden the borrow-offers fill accounting and offer lifecycle - consume walk: evaluate the profitability/bonus floor once per offer on its whole remaining amounts (scale-invariant), not on the rounded chunk, so partitioning a target cannot skip an exactly-at-floor offer and the isConsumable view agrees with the walk - consume write-back: release a partially-filled slot whose leftover is no longer strictly profitable (unusable dust must not hold slab slots until expiry); below-floor-but-profitable leftovers stay, like any below-floor offer, so a floor change can re-admit them - band pre-liquidation: after the Morpho repay settles, re-quote the position at the entry price and revert (LtvNotReduced) unless the LTV strictly decreased; kills the virtual-share-boundary dust fills whose settled totals leave the LTV flat or higher - revokeOffers: the recorded proposer may revoke its own offer at any time (before and after activeAt); guardian and registry-owner revocation unchanged, authorization now checked per offer * refactor: per-offer revoke auth via registry boolean read, hoist marketId, silence cast lints Review follow-ups on the offer lifecycle hardening: - revokeOffers resolves the registry guardian/owner power once as a boolean (canRevokeOffer replaces the reverting checkCanRevokeOffer) and the per-offer proposer check moves into LibBorrowOffers.removeOffer; the Unauthorized error keeps Solady's selector so external behavior is unchanged - _positionLtvFrame and _consumeOffers read marketId from storage once - _checkLtvReduced NatSpec records why the cross-multiplication needs fullMulDiv's 512-bit intermediate (price-scaled values can overflow 256 bits) - forge 1.5.1 unsafe-typecast lints in LibBorrowOffers silenced with justified disable comments - new tests: empty revoke batch no-op, guardian revoke of an active offer, per-offer OfferRevoked events in a batch * fix: derive band settlement LTV frame before repay to close reentrancy drain The offer-band pre-liquidation guard re-read live position state after the Morpho repay, whose onPreLiquidate callback (plus permissionless repay / supplyCollateral) could drain the position through the reentrant proportional path and re-supply a dust atom so the post-repay check passed. Compute the settled frame deterministically from the fill amounts before the repay (reproducing Morpho shares-mode rounding), so no callback can move it.
…nservation, pre-transfer rebase) (#211) * fix: performance-fee reference and basis hardening Cantina #14 (3F-470): cap the levered performance basis at the zero-floored NAV gain since the reference so an external deleveraging (liquidation, direct repay) cannot mint a fee on a NAV loss; hold the reference when the cap zeroes the basis, and convert a seizure NAV deficit into carry form in rebaseSnapshot so the held mark survives capital flows instead of re-anchoring at the trough. Cantina #32 (3F-481): hold the reference when a positive basis carries a performance entitlement that converts to zero fee shares, so splitting one gain across many checkpoints cannot erase the fee; the interval's management fee joins the held accumulator. Cantina #36 (3F-482): rebase the performance reference (and check maxRebalanceLoss) before the outgoing token transfers in burn() and rebalance(), so a token callback cannot move module state into the new reference or donate to mask a loss. Cantina #15 (3F-475): document the accepted policy that recovery of an in-cap rebalance loss is charged as performance (rebalance() NatSpec). * fix: harden fee-reference fixes from PR review Three residuals from the PR #211 review round: - rebaseSnapshot now takes the larger of the levered carry and the NAV deficit, so a seizure mark survives repeated flows instead of decaying through the levered read once a carry exists. - A carry at or above the post-flow debt no longer floors the reference debt at the bootstrap sentinel: the mark re-anchors at NAV + carry with the reference debt at the pre-flow reference LTV, so fees stay suspended until NAV clears the preserved mark and resume on the same terms as before the flow. - A held positive pending entitlement (zero-share or BPS-stage hold) is preserved across flows, capped at the NAV gain above the mark and scaled with the supply, so economically empty rebalances cannot erase the fee; _pendingFees additionally holds the reference when the BPS multiplication rounds a nonzero net entitlement to zero fee assets. Seven new or updated tests, each verified to fail against the previous code at the finding-specific assertion. Full suite: 2328 tests plus 12 invariant tests pass; fmt checked with the CI forge binary.
…tions (Cantina) (#213) * fix: Retargetter operation snapshot, cap re-check, PM whitelist and clock rewind Cantina audit fixes across the Retargetter operation lifecycle, the Request cap gates, and the fund-adapter assumptions. Code fixes: - 3F-502: snapshot horizon/tickDuration/tickThreshold into the operation at start so setConfig cannot reprice a funded operation (zero extra storage slots; _owed reads the snapshot). - 3F-506: cancelOrder requires IFund.cancel to return State.EMPTY before clearing the local order. - 3F-483: bind operations only to an owner-whitelisted PositionManager (setPositionManager / isPositionManager, pair checked at whitelist time). - 3F-477: move the principal-cap gate in consume to after the Request call so a maker callback cannot invalidate it. - 3F-478: bound maxPrincipal by the quoter's one-trip repayment bound (new ltvUpOneTripPrincipal) so a cap-sized operation keeps settlement headroom. - 3F-485: rewind the loan clock to unstarted when a revocation leaves the operation with no PT, no YT and no pending authorization. Documented in NatSpec (no behaviour change): 3F-494 minimum-tick payout, 3F-496 per-call rebalance cooldown, 3F-499 per-instance operation guard, 3F-503 CONSUMER_ROLE self-authorization, 3F-472 firm-commitment mint policy, 3F-487 exact-transfer fund-asset assumption. Fixes 3F-521 Fixes 3F-515 Fixes 3F-514 * refactor: bind Retargetter to a single owner-set PositionManager (3F-483) Replace the position-manager whitelist with a single instance binding: the Retargetter runs every operation against one owner-bound PositionManager instead of taking it as a caller-supplied argument. - Bind via initialize (optional positionManager_ param, threaded through createRetargetter) or setPositionManager(address), rebindable by the owner only while no operation is active. Both validate the contract and its asset-pair match. - Drop the positionManager argument from startRetargetting and startSyncRetargetting; they read the bound manager. maxPrincipal() drops its argument too. isPositionManager -> boundPositionManager view. - Error PositionManagerNotWhitelisted -> PositionManagerNotBound; event PositionManagerSet -> PositionManagerBound. Smaller than the whitelist (no mapping, no per-call arg): Retargetter runtime 23,949 B on the ci profile, 627 B under EIP-170 (was 24,136 / 440 B). Full suite 2349 passing, 74 invariants passing; the not-bound guard is mutation-checked. * docs: state the IFund exact-transfer assumption per-transfer, not per single pull (3F-487) The assumption is a token property (every transfer/burn/wrap moves exactly the requested amount), independent of how many legs an adapter uses. Drop the Centrifuge-specific 'pull order.input and forward that same amount' wording, which misdescribes multi-leg adapters like the Midas redeem (bond + redemption legs summing to the input). * refactor: derive the Retargetter pair from its position manager (3F-483) The asset pair is the position manager's own pair, so read it from the manager at initialize instead of taking collateralAsset/debtAsset as params. The pair is stored (still read on every hot path) and immutable, so setFund's one-time validation and the start-time whitelist trust stay intact; rebinds must report the same pair. - initialize(owner, positionManager, config): position manager is required, the pair is derived from its assets(). createRetargetter drops the two asset params and reports the derived pair in its event. - setPositionManager(address) also accepts the zero address to unbind (abandon) the instance while idle; operations then revert PositionManagerNotBound until a same-pair manager is bound again. The pair is retained across an unbind. Full suite 2346 passing, 74 invariants passing; the not-bound guard is mutation-checked. Retargetter runtime 24,004 B on the ci profile, 572 B under EIP-170. * refactor: share one bind function for init/setPositionManager, write-once pair (3F-483) Collapse the position-manager binding into a single internal _bindPositionManager used by both initialize and setPositionManager: for a nonzero manager it reads its assets and, while the stored pair is still zero, derives the immutable pair (else it must match); the zero address unbinds and leaves the pair untouched. The pair is therefore write-once regardless of whether the first bind happens at init or later, and initialize now accepts the zero address (create unbound, bind later). Removes the separate _checkPair and the duplicated init/setter logic. Smaller and simpler: Retargetter runtime 23,829 B on the ci profile, 747 B under EIP-170. Full suite 2347 passing, 74 invariants passing; the pair-match guard is mutation-checked. * refactor: drop operation.positionManager, key active on fund + bound PM The operation always runs against the instance-bound position manager, which cannot change while active, so storing it on the operation record was pure duplication. Remove the field: - fund becomes the operation-active flag (both start paths set it, only clearOperation clears it, and no fund can be the zero address since setFund contract-checks it). isActive, _checkStart and setPositionManager's active guard key on fund; checkActive keys on fund and returns the bound PM. - consume/authorizeMinting/resolve read the bound PM from assets storage; the operation() view derives its positionManager (bound PM when active, else zero) instead of returning a stored field, keeping the same ABI and semantics. RetargetterOperation repacks to 7 slots (request now shares slot 0, fund + the order mode share slot 1); the storage-layout and stray-order tests are updated. Full suite 2347 passing, 74 invariants passing; the fund active flag is mutation-checked. Retargetter runtime 23,953 B on the ci profile, 623 B under EIP-170. * fix: settle the one-trip bound's rounding order and state the cap's minter trust Review follow-ups on the Retargetter batch: - RetargetterQuoter: the one-trip repayment bound now grows the collateral before applying the target (the order settlement itself uses) and rounds the drifted debt up, so the worst permitted repayment of a bound-sized principal is borrowable at target in integers, not just in the reals. The reversed order could oversize the bound by a few units and trip the settlement direction check by one unit of LTV. - Retargetter/IRetargetter: the post-call principal cap NatSpec no longer claims only the position manager's owner or rebalancer can move collateral after consume returns; it names the minter-role trust boundary the read relies on and the required role separation. - Tests: regression fixture with the reviewer's six-decimal counterexample plus a fuzz invariant asserting the worst repayment stays borrowable at target under worst-case realized integer state.
…no flow rescaling, last-share clear) (#214) * fix: consume the held management-fee deduction only up to the basis (Cantina #6) A positive basis smaller than the pending deduction — producible permissionlessly, e.g. a dust repay through the market — advanced the performance reference and cleared the whole heldManagementFeeAssets accumulator, so the next crystallization overcharged LPs by the forgiven deduction. A crystallizing advance now consumes the deduction only up to the basis and carries the excess past the new mark; a reseed advance (bootstrap sentinel, empty vault) still clears it, which the rebaseSnapshot fallback branches rely on. The fee-cap early return backs the unminted interval fee out of the accumulator instead of clearing it on advance. Closes 3F-473. * fix: never rescale the held management-fee deduction on capital flows (Cantina #30) rebaseSnapshot scaled heldManagementFeeAssets by the share-supply ratio, but that ratio is a permissionless value-detached lever in both directions: near zero NAV the mint denominator is the virtual asset base, so a dust repay plus a dust deposit nearly doubles the supply and would manufacture credit far beyond the fees ever charged, while any down-only variant lets a deposit/exit round trip that restores the vault state grind the deduction toward zero with reversible capital and overcharge the next crystallization. The accumulator now stays nominal across all flows, matching its definition (fees actually charged since the last netting); its only transitions are fee charges, consumption up to a crystallized basis, and the reseed/reset clears. The residual recipient-side overhang after a large exit is bounded by fees already collected and remediable via resetPerformanceReference. Regression tests at both levels (harness round trip, vault-level deposit/burn round trip, zero-NAV and dust-positive-NAV deposits), each verified to fail against the scaled variants. Closes 3F-480. * fix: clear the held management-fee deduction when the last share exits (Cantina #7) The pending deduction is owed to current holders, but nothing wrote it down when the supply emptied: the empty-vault reseed clear only runs on a later accrual — and not at all while bad debt is present — so a departed cohort's credit could sit in storage and shield a future cohort that never paid the management fees. rebaseSnapshot now zeroes the accumulator on a flow that burns the last share, before the reference-hold early return. Unlike a supply-ratio rescale (removed for Cantina #30) the terminal clear is not a permissionless lever: only the sole remaining holder can trigger it, and the only shield it destroys is their own. The finding's other half — an exit that empties the good-debt universe while shares remain — is uniform under the never-rescale rule (the credit stays nominal like any other exit, the documented reset-remediable residual) and is pinned by the new unit test alongside the reference hold. The recorded recommendation (hoist the supply-ratio scaling above the early return) predates the #30 resolution and would reintroduce both the zero-NAV up-lever and the deposit/exit round-trip grind; it is deliberately not implemented. Both new tests fail without the clear (the credit survives the last burn). Full suite: 2426 passed, fmt clean. Closes 3F-474. * test: pin the held-deduction consume on both accrual branches; align accumulator docs Strengthen the two Cantina #6 branch regressions so each detects its pre-fix clear-on-advance mutation independently: - holiday test: routed to the interest-free market; the no-recipient variant now advances on a permissionless dust-positive basis below the pending deduction and asserts the advance mints nothing, moves the reference, and persists exactly `held - basis`. - fee-cap test: a flat held stretch seeds the deduction and a dust repay flips the basis positive before the cap-binding dormancy; the accrual must still advance, mint nothing, and back out to `held - basis`. Both verified by mutation: reverting either consume site in `_pendingFees` to clear-on-advance fails its test with held == 0. Extract the repeated permissionless dust-repay block into `_dustRepayMarket2()` and reuse it in the existing dust tests. Docs: define the accumulator as fees "charged and not yet netted against a crystallized basis" everywhere (the old "since the reference last advanced" predates the carry semantics), list the last-share flow clear on `feeData`, and document all three fee-cap back-out outcomes (hold, crystallizing advance, reseed advance). Part of 3F-518 (3F-473)
* docs(3F-488): forbid Facility as fee recipient * docs(3F-489): explain zero-share fee accrual * docs(3F-490): document held fee asset accounting * docs(3F-493): document offer walk rounding * docs(3F-500): record superseded exit rounding * docs(3F-507): document offer timelock grandfathering * docs(3F-510): document Midas oracle freshness * docs: remove known-issues registry * docs: clarify offer halts and oracle validation
…ee eligibility cliff (#216) * fix: expose the bad-debt exclusion flag via hasBadDebt() (Cantina #40) LibView.totalAssets() skips any borrow module whose debt exceeds its quoted collateral, but PositionManager dropped the resulting flag and no public view returned it, so monitoring had to query every module and re-implement the collateral >= debt comparison to tell whether the reported NAV covers all modules or only the solvent ones. Add a separate additive hasBadDebt() getter sourced from the same single-pass LibView.totalAssets() iteration, and document on IPositionManager.totalAssets() that the value covers only the solvent modules while the flag is set. totalAssets() keeps its bare uint256 return: widening it would change return decoding for generated bindings (alloy, viem, cast) and break the ERC-4626 shape. Closes 3F-505. Part of 3F-520. * docs: management fee uses checkpoint-end eligibility over the full interval (Cantina #4) The collateral >= debt filter in LibView.totalAssets() is a one-wei cliff, and fee accrual applies it at checkpoint end over the whole elapsed interval: a module that re-enters the fee basis at any point -- anyone can flip the filter by repaying one base unit on the underlying market -- is charged management fee for the full interval, because storage keeps no per-module inclusion time. Documented and accepted as-is: charging from first observed eligibility would require a per-module inclusion timestamp, stored state we would rather not carry for an edge case liquidation is expected to prevent long before 100% LTV. The cliff was already described in the LibView internal NatSpec; this surfaces the fee consequence there and on the fee-facing documentation (_pendingFees and the feeData() managementFee return). Closes 3F-486. Part of 3F-520. * fix: PR #216 review — scope re-inclusion cost to the shortfall, harden hasBadDebt coverage Docs: re-including an excluded module costs its full debt - collateral shortfall, not one base unit (that only holds at the exact boundary) — corrected in the feeData()/hasBadDebt() NatSpec, the LibView cliff note, and _pendingFees. Added the converse (an excluded module pays nothing for the interval, foregone permanently since lastFeeAccrualTimestamp refreshes unconditionally), a note that hasBadDebt() reverts on a module quote revert so monitors treat that as unknown rather than no-bad-debt, and dropped the leftover reviewer-voice tail in LibView. Tests: collateral == debt boundary (verified to fail against a >= to > regression), partial exclusion with the flag true while totalAssets() is still positive, and hasBadDebt() in ReentrantMinter's READ_VIEWS probe (reverts mid-operation, works outside).
* docs: clarify that borrow-offer views report booked slots, not consumable ones (3F-484 #1, #2) offerCount(), offer() and offers() all key off `liveBits`, which marks an allocated slab slot and never checks expiry. Expired offers are pruned lazily (on the next _alloc that needs a slot, or when a consume walk passes them), so until then they still count and still appear in the listings. Document the meaning of "live" once on the views section rather than per function, and point integrators at `expiresAt` / isConsumable() for offers that would actually fill. * docs: disambiguate the performance-fee LTV from the withdrawal-buffer LTV (3F-484 #6) setFeeData named the performance-fee reference LTV `LTV_prev`, a name used nowhere else in the codebase, in the same interface that exposes setLtv(ltv_) for the unrelated withdrawal buffer. Rename it to `LTV_ref` to match `FeeData` in LibStorage (the derivation this comment already points at) and state that it is derived from the fee reference, not configured. * docs: explain why raw debt is cleared before Morpho pulls the repayment (3F-484 #7) onMorphoFlashLoan() zeroes the raw debt at the end of the callback, before Morpho actually transfers the repayment in. Note that the pull happens immediately after the callback returns and reverts the whole transaction on failure, so the cleared debt is only ever observable in a transaction where the repayment succeeded. * docs: document that a consumed offer is single-use regardless of fill size (3F-484 #9) Request.consume() prorates the YT mint on a partial fill, but _validateOffer writes the offer's nonce into the maker-wide nonce, invalidating this offer and every lower-nonce one. The unfilled remainder is not fillable; the maker must sign a fresh, higher-nonce offer. Out of the triaged scope for 3F-484 (item 9 was excluded), kept as it is doc-only and the behaviour was undocumented at the public entry point. * refactor: replace the minOfferBonus +1 sentinel with an explicit set flag (3F-484 #10) The bonus floor was stored biased by one so that a never-configured collateral (reads DEFAULT_MIN_OFFER_BONUS_BPS) stayed distinguishable from an explicit 0 (disables the floor). Store the value unbiased next to a `minOfferBonusSet` boolean instead, which states that distinction directly and drops the +1/-1 round trip and its overflow note. Still a single packed slot (136 -> 144 bits). Storage layout change: the namespaced OfferConfig struct is not deployed yet, so no migration is involved. * refactor: pack MidasFundStorage from 18 slots to 15 (3F-484 #12) Group fields that are read together so they share a slot: - `asset` + `assetScale`: initialize() rejects assets with more than 18 decimals, so the scale is 10**(18-d) in [1, 1e18] and fits uint64 (max ~1.8e19). The pair is read together on the deposit, redeem and quoting paths. - `oracle` + `internalState` + `hasResolvedAmounts` + `instantRedeemUnlocked` (20+1+1+1 bytes): commit() writes the flags and reads the oracle in the same call. `bondPaid` moves ahead of `bondConfig` and `endedOrders`, which each force a slot boundary and were stranding it alone. Measured -49k gas on the redeem fuzz path. No ABI change: assetScale has no external getter, and BondConfig is left alone since it crosses the interface. Storage layout change: MidasFund is not deployed yet, so no migration is involved. Field order is now layout-significant and noted as such. * feat: emit DepositVaultUpdated from MidasFund.initialize (3F-484 #11) initialize() already emits OracleUpdated through _setOracle but wrote $.depositVault raw, so an indexer folding OracleUpdated + DepositVaultUpdated picked up the oracle at init and nothing for the vault, which only appeared in the factory's FundCreated. Emit the event at init so the vault binding is readable from the same stream as every later setDepositVault().
…s (3F-481) (#218) * fix: keep the held performance entitlement nominal across flow rebases (3F-481) The Cantina #32 zero-share hold preserved a positive pending entitlement, but the flow rebase scaled it by the supply ratio: a deposit into a dust-NAV vault could turn a one-atom held gain into a material fee charged against the fresh principal. The gain now stays nominal across flows (like heldManagementFeeAssets), falling back to the supply-scaled read only once it outgrows half the post-flow NAV so the mark cannot degenerate to zero on a draining flow. Claude-Session: https://claude.ai/code/session_01AZowKnphCAYPA1MoEGXLjd * test: deposit fairness properties (fresh-quote and matched-ratio invariants) The minted share fraction is fair under a true price P iff (P - p) * (d*C - c_q*D) == 0: either the quote is fresh or the deposited debt-to-collateral ratio matches the pool's. Pins case 1 (carry mints its own value, same-state accruals mint nothing), case 2 (matched-ratio deposits are repricing-neutral and charged only their own slice's gain, verified against a state-snapshot counterfactual), and demonstrates the inherent transfer of the mismatched-ratio stale-quote case as an operational constraint. Claude-Session: https://claude.ai/code/session_01AZowKnphCAYPA1MoEGXLjd * test: widen deposit-fairness fuzz and add the PM-12 fee-conservation invariant The fairness properties now fuzz the fee configuration (0-200 bps management, 0-5000 bps performance) and elapsed time (time alone must never create a performance fee on fresh principal). The stateful handler gains ghost accounting: NAV gains observed outside capital flows, the management-fee allowance per settled accrual interval, and the value of fee shares at mint. PM-12 asserts fees can never materially exceed those allowances under the handler's full state space (fuzzed fees, 0.1x-10x prices, warps, liquidations, third-party repays), so capital flows alone can never fund a fee. Claude-Session: https://claude.ai/code/session_01AZowKnphCAYPA1MoEGXLjd * test: mirror the flow-fairness invariants on the exit side Exits obey the same theorem as deposits with the signs flipped: fair under a true price P iff the quote is fresh or the removed debt-to-collateral ratio matches the pool's. Renames the fairness file to PositionManagerFlowFairness and adds: a fresh-quote withdraw burns exactly the carry's value under any ratio and fee configuration (and neither the exit nor time creates a performance fee), a matched-ratio withdraw is repricing-neutral for the stayers (snapshot counterfactual), burn() is ratio-matched by construction and therefore neutral at any stale quote, and the mismatched-ratio stale-quote exit demonstrably hands the exiter's levered upside to the stayers. Claude-Session: https://claude.ai/code/session_01AZowKnphCAYPA1MoEGXLjd
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Integration branch for the v1.3.0 audit fixes.
All Cantina finding links point to the 3F: Grunt Cantina repo.
Merged fixes
Other commits on the branch (no Cantina finding): f6bcfce (ci: pin GitHub Actions to commit SHAs), a255e50 (branch open).
Unaddressed findings (not in this PR): #3, #44, #46, #47