Summary
100% of all REPORTED Findings have been addressed
- 3Future Release
- 8Acknowledged
- 24Solved
- 35All Findings
- Critical1
- 1Solved
- High0
- Medium2
- 2Solved
- Low0
- Informational32
- 21Solved
- 3F. Release
- 8Ack.
INTRODUCTION#
Nuva engaged Halborn to perform a security assessment of their vault module on July 29th, 2026. The assessment scope was limited to the smart contracts provided to Halborn. Commit hashes and additional details are available in the Scope section of this report.
The Nuva codebase in scope consists of a Go-based Provenance Blockchain module for tokenized, single-denomination vaults. It manages deposits and redemptions, share accounting, interest and AUM fees, internal NAV pricing, cross-chain share controls, P2P asset settlement, queued processing, and upgrade migrations.
This report is a diff review of the Provenance Blockchain vault module from release-line commit 69d9478 to the v1.2.1 target a2dbc9e. The module manages single-denomination tokenized vaults, share issuance and redemption, interest and AUM fees, internal NAV pricing, bridge-controlled share movement, P2P settlement, queues, and migrations.
ASSESSMENT SUMMARY#
Halborn was allocated 8 days for this engagement and assigned 1 full-time security engineer to conduct a comprehensive review of the smart contracts within scope. The engineer is an expert in blockchain and smart contract security, with advanced skills in penetration testing and smart contract exploitation, as well as extensive knowledge of multiple blockchain protocols.
The objectives of this assessment are to:
Identify potential security vulnerabilities within the smart contracts.
Verify that the smart contract functionality operates as intended.
In summary, Halborn identified several areas for improvement to reduce the likelihood and impact of security risks, which were addressed by the Nuva team. The main recommendations were:
Enforce marker transfer and identity restrictions on every swap-in funding path, including the bypassed deposit path, and add regression tests for frozen or otherwise ineligible depositors.Protect existing shareholders by requiring the vault to be paused before repricing a held asset, applying a reprice timelock, and enforcing a nonzero minimum withdrawal delay.Make fee collection and reconciliation resilient to restricted assets: treat an uncollectable fee as recoverable, constrain fee rates, and separate fee-recipient control from fee-rate authority.
TEST APPROACH AND METHODOLOGY#
Halborn conducted a combination of manual code review and automated security testing to balance efficiency, timeliness, practicality, and accuracy within the scope of this assessment. While manual testing is crucial for identifying flaws in logic, processes, and implementation, automated testing enhances coverage of smart contracts and quickly detects deviations from established security best practices.
The following phases and associated tools were employed throughout the term of the assessment:
Research into the platform's architecture, purpose and use.
Manual code review and walkthrough of the codebase to identify any logical issues.
Comprehensive assessment of the safety and usage of critical Go variables and functions within scope that could lead to arithmetic-related vulnerabilities.
Local testing using custom scripts (Go test).
Static security analysis of scoped contracts, and imported functions (staticcheck, gosec).
Risk Methodology#
4.1 EXPLOITABILITY
Attack Origin (AO):
Attack Cost (AC):
Attack Complexity (AX):
Metrics:
| EXPLOITABILITY METRIC () | METRIC VALUE | NUMERICAL VALUE |
|---|---|---|
| Attack Origin (AO) | Arbitrary (AO:A) | 1 |
| Specific (AO:S) | 0.2 | |
| Attack Cost (AC) | Low (AC:L) | 1 |
| Medium (AC:M) | 0.67 | |
| High (AC:H) | 0.33 | |
| Attack Complexity (AX) | Low (AX:L) | 1 |
| Medium (AX:M) | 0.67 | |
| High (AX:H) | 0.33 |
4.2 IMPACT
Confidentiality (C):
Integrity (I):
Availability (A):
Deposit (D):
Yield (Y):
Metrics:
| IMPACT METRIC () | METRIC VALUE | NUMERICAL VALUE |
|---|---|---|
| Confidentiality (C) | None (C:N) | 0 |
| Low (C:L) | 0.25 | |
| Medium (C:M) | 0.5 | |
| High (C:H) | 0.75 | |
| Critical (C:C) | 1 | |
| Integrity (I) | None (I:N) | 0 |
| Low (I:L) | 0.25 | |
| Medium (I:M) | 0.5 | |
| High (I:H) | 0.75 | |
| Critical (I:C) | 1 | |
| Availability (A) | None (A:N) | 0 |
| Low (A:L) | 0.25 | |
| Medium (A:M) | 0.5 | |
| High (A:H) | 0.75 | |
| Critical (A:C) | 1 | |
| Deposit (D) | None (D:N) | 0 |
| Low (D:L) | 0.25 | |
| Medium (D:M) | 0.5 | |
| High (D:H) | 0.75 | |
| Critical (D:C) | 1 | |
| Yield (Y) | None (Y:N) | 0 |
| Low (Y:L) | 0.25 | |
| Medium (Y:M) | 0.5 | |
| High (Y:H) | 0.75 | |
| Critical (Y:C) | 1 |
4.3 SEVERITY COEFFICIENT
Reversibility (R):
Scope (S):
Metrics:
| SEVERITY COEFFICIENT () | COEFFICIENT VALUE | NUMERICAL VALUE |
|---|---|---|
| Reversibility () | None (R:N) | 1 |
| Partial (R:P) | 0.5 | |
| Full (R:F) | 0.25 | |
| Scope () | Changed (S:C) | 1.25 |
| Unchanged (S:U) | 1 |
| Critical | High | Medium | Low | Informational |
| 9 - 10 | 7 - 8.9 | 4.5 - 6.9 | 2 - 4.4 | 0 - 1.9 |
Scope#
Assessment Summary & Findings Overview#
# | Title | Severity | Score | Status |
|---|---|---|---|---|
| Swap-In Bypasses the Underlying Marker Send Restrictions | Critical | 10.0 | Solved08/04/2026 | |
| Live Held-Asset NAV Reprice Extracts Value From Existing Shareholders | Medium | 5.4 | Solved08/04/2026 | |
| AUM-Fee Transfer to an Unattributed Tech-Fee Address Bricks a Restricted-Underlying Vault | Medium | 5.0 | Solved08/04/2026 | |
| Genesis Pending-Swap-Out With a Negative Shares Amount Halts the Chain in EndBlocker | Informational | 1.7 | Solved08/04/2026 | |
| Truncation Denies Sub-Unit Yield / Fee Under Frequent Reconciles | Informational | 1.7 | Future Release08/14/2026 | |
| Genesis Swap-Out Queue Accepts Duplicate Ids and Negative Times (InitGenesis Halt / Latent Auto-Pause) | Informational | 0.8 | Acknowledged08/14/2026 | |
| v1→v2 Migration Bricks the Upgrade When a Legacy Vault Fails the Current Validate() | Informational | 0.4 | Solved08/05/2026 | |
| The Shared Swap-Out Batch Budget Has No Fairness Control, So One Actor's Backlog Starves Other Vaults' Redemptions | Informational | 0.3 | Acknowledged08/14/2026 | |
| PayoutVerificationSet Is Not Round-Tripped in Genesis | Informational | 0.2 | Solved08/11/2026 | |
| PayoutVerificationSet Is Drained in Address Order, Not Insertion Order, Deferring High-Address Vaults While Churn Is Sustained | Informational | 0.2 | Solved08/10/2026 | |
| Marker Share-NAV Mirror Goes Stale After a Fee-Only Reconcile | Informational | 0.1 | Solved08/04/2026 | |
| Bridge-Address Rotation Strands the Old Bridge's Shares (Recoverable by Transfer) | Informational | 0.1 | Acknowledged08/12/2026 | |
| ExpDec (17-Term Maclaurin e^x) Sign-Inverts and Is Grossly Wrong for Large |rt| | Informational | 0.1 | Solved08/11/2026 | |
| Unbounded Per-Vault Internal-NAV Walk in GetTVV Inside ABCI | Informational | 0.1 | Solved08/06/2026 | |
| Interest Affordability Forecast Under-Estimates the Applied Window | Informational | 0.1 | Future Release08/14/2026 | |
| Validate() Permits PeriodTimeout / FeePeriodTimeout == math.MinInt64 | Informational | 0.1 | Future Release08/14/2026 | |
| Delegated Management Authority Is an All-or-Nothing Bundle With No Default Interest-Rate Envelope | Informational | 0.0 | Acknowledged08/14/2026 | |
| EstimateSwapIn Query Panics on a Nil Asset Amount | Informational | 0.0 | Solved08/04/2026 | |
| Pro-Rata Share Math Uses Non-Safe Int.Add for the Virtual Offset | Informational | 0.0 | Solved08/04/2026 | |
| Dead Code: CalculateExpiration / CalculatePeriods Are Unreferenced and Carry Latent Long-Running-Loop, Unchecked Int64 and Unguarded Coin.Sub Hazards | Informational | 0.0 | Solved08/05/2026 | |
| Genesis Import Does Not Bound WithdrawalDelaySeconds, Bypassing the Swap-Out Withdrawal Delay | Informational | 0.0 | Solved08/04/2026 | |
| Unguarded sdk.Coin.Sub in BridgeMintShares Panics on a Broken Supply Invariant (Recovered on the Tx Path) | Informational | 0.0 | Solved08/05/2026 | |
| No Maximum-Age Bound on an Internal NAV Price Used for Valuation | Informational | 0.0 | Solved08/12/2026 | |
| Bridge Mint/Burn Sit Outside the Pause Circuit Breaker, and the Spec Contradicts Itself on Whether They Should | Informational | 0.0 | Solved08/10/2026 | |
| BridgeMintShares Carries No Idempotency Key, So the Chain Cannot Tell a Duplicate Bridge-In From a New One | Informational | 0.0 | Acknowledged08/14/2026 | |
| EstimateSwapOut Validates `shares` After the Expensive State Reads, With No Length Bound and Base-0 Parsing | Informational | 0.0 | Solved08/05/2026 | |
| handleReconciledVaults Splits the PayoutVerificationSet Removal and the Timeout Re-Enqueue Across Two Contexts, So an Error Between Them Leaves No Retry Handle | Informational | 0.0 | Solved08/10/2026 | |
| autoPauseVault Builds an Unvalidated `sdk.Coin` With a Nil Amount Inside the EndBlocker | Informational | 0.0 | Solved08/11/2026 | |
| The v1→v2 Fee Conversion Branch Is Unreachable, and a Test and Two Spec Sections Assert Otherwise | Informational | 0.0 | Acknowledged08/14/2026 | |
| OutstandingAumFee Is an Uncapped, Never-Aged Liability With No Signal When It Approaches Gross TVV | Informational | 0.0 | Solved08/11/2026 | |
| Timeout Reschedule Has No Compensating Re-Enqueue When Its Cached Half Fails | Informational | 0.0 | Solved08/10/2026 | |
| Swap-Out Retry Backoff Has No Jitter, So Co-Failing Entries Stay Permanently Synchronized | Informational | 0.0 | Solved08/06/2026 | |
| ExportGenesis Emits Internal-NAV Entries That InitGenesis Will Drop (Round-Trip Asymmetry) | Informational | 0.0 | Acknowledged08/14/2026 | |
| PendingSwapOutQueue.Dequeue Discards the IndexedMap.Has Error and Leaves Its Idempotent Contract Undocumented | Informational | 0.0 | Solved08/08/2026 | |
| The Interest-Disable Cleanup Performs a Full Chain-Wide Queue Scan That Finds Nothing, and Is Undone by the Next Reconcile | Informational | 0.0 | Acknowledged08/14/2026 |
Findings & Tech Details#
Description
Proof of Concept
Recommendation
References
- Provenance x/marker SendRestrictionFn: the WithBypass short-circuit at x/marker/keeper/send_restrictions.go:22-36, and the send-deny check at :130.
- markertypes.WithBypass and HasBypass: x/marker/types/send_restrictions.go:16-38.
- CWE-284: Improper Access Control.
Description
Proof of Concept
Recommendation
References
- keeper/msg_server.go:904, UpdateVaultNAV, and keeper/msg_server.go:943, RemoveVaultNAV: RemoveVaultNAV rejects a denom with a nonzero principal balance at :955-957, but UpdateVaultNAV performs no equivalent check.
- keeper/valuation_engine.go:188-221: GetTVV sums principal underlying plus every priced held denom, so TVV moves the instant a NAV entry is rewritten.
- keeper/nav.go:95-96: the stored UpdatedBlockHeight and UpdatedTime on a NAV entry are written but never consulted by any staleness gate on swap-in or swap-out.
- CWE-840: Business Logic Errors.
Description
Proof of Concept
Recommendation
References
- keeper/reconcile.go:355: the fee SendCoins names the vault as a transfer agent, which does not exempt the send unless the vault holds Access_Transfer on the underlying marker; keeper/reconcile.go:74: the error propagates out of reconcileVault, and the cache context is discarded so FeePeriodStart never advances and the uncollectable amount only grows.
- Provenance x/marker keeper/send_restrictions.go:122 (transfer-agent short-circuit) and :160-186 (required-attribute enforcement on the send recipient).
- Provenance x/attribute abci.go:16: DeleteExpiredAttributes runs every BeginBlock, so attribute loss is an ordinary on-chain event requiring no vault-module authority.
- CWE-703: Improper Handling of Exceptional Conditions.
- keeper/msg_server.go:830: UpdateParams sets TechFeeAddress with no constraint on the chosen address. keeper/msg_server.go:851-870: UpdateVaultAUMFeeBips authorizes on msg.Authority == params.TechFeeAddress only and caps the rate at 10,000 bips (100%/year).
- keeper/reconcile.go:315: PerformVaultFeeTransfer collects the accrued fee up to the available principal balance on each reconcile.
- CWE-284: Improper Access Control (the governance TechFeeAddress plus maximum AUM bips vector).
Description
Proof of Concept
Recommendation
Description
Proof of Concept
Recommendation
Remediation Comment
Description
Proof of Concept
Recommendation
References
- Cosmos SDK genesis validation contract; BaseApp.InitChain does not recover a panic raised from InitGenesis
- cosmossdk.io/collections indexes.Unique: the ref-key conflict is detected inside IndexedMap.Set and returned as collections.ErrConflict before the primary value is written
- CWE-20: Improper Input Validation
Description
Proof of Concept
Recommendation
Description
Proof of Concept
Recommendation
References
- Starvation / missing fairness in a throttled shared queue
- keeper/abci.go:12-16 - the batch-size rationale and issue #75 reference
- keeper/payout_test.go:640,644,669-673 - the module's own test accepts later-entry starvation as expected
- spec/06_blocker.md - documents expedite (Time=0) and the retry/backoff design as intended
Remediation Comment
Description
Proof of Concept
Recommendation
Description
Proof of Concept
Recommendation
Description
Proof of Concept
Recommendation
Description
Proof of Concept
Recommendation
Description
Proof of Concept
Recommendation
Description
Proof of Concept
Recommendation
Description
Proof of Concept
Recommendation
Remediation Comment
Description
Proof of Concept
Recommendation
Remediation Comment
Description
Proof of Concept
Recommendation
Remediation Comment
Description
Proof of Concept
Recommendation
Description
Proof of Concept
Recommendation
Description
Proof of Concept
Recommendation
Description
Proof of Concept
Recommendation
Description
Proof of Concept
Recommendation
Description
Proof of Concept
Recommendation
References
- Not applicable: CWE-1188 ("Initialization of a Resource with an Insecure Default", commonly cited as an unused/ineffective security control). It does not apply in either half. Nothing in the module ever asserted a freshness contract, proto/provlabs/vault/v1/vault.proto:169-172 documents both fields purely descriptively ("the block height at which this entry was last updated"), and vault.proto:150-152 / spec/02_state.md:137 say only that the module is the sole source of truth and that the NAV authority maintains the values. There is no control to be unused and no contract to violate.
- Considered and rejected: CWE-672 ("Operation on a Resource after Expiration or Release"). Requires a resource with a defined lifetime; NAV entries have none, by design.
- Applicable, for the documentation half: CWE-1059, *Insufficient Technical Documentation*. The attribution-only semantics of updated_block_height / updated_time are nowhere stated, which is what allowed them to be misread as an enforcement control in the first place.
- Applicable, for the observability half: CWE-778, *Insufficient Logging*. EventNAVUpdated omits updated_time although the module stores it, leaving event-stream consumers unable to compute wall-clock price age directly.
- keeper/nav.go:95-96, the only writer of the freshness stamps.
- keeper/valuation_engine.go:78-134, :188-221, valuation reads price/volume only.
- keeper/query_server.go:345, :387, the stamps ARE returned to clients.
- types/events.go:396, proto/provlabs/vault/v1/events.proto:477-478, height on the event; time omitted.
- keeper/settlement.go:42-63, spec/01_concepts.md:163, the existing holdings-scoped staleness control.
- provenance@v1.3.2-0.20260720203940-3977c1b90e3c: proto/provenance/marker/v1/marker.proto:102-103, x/marker/keeper/keeper.go:370,385, x/marker/types/msgs.go:665-666, x/metadata/types/msgs.go:951, x/metadata/keeper/scope.go:875,937.
- keeper/payout.go:108,117,139,145 → keeper/vault.go:504,511,515, why an age gate must not sit on the valuation path.
- CWE-1059 (Insufficient Technical Documentation), CWE-778 (Insufficient Logging). CWE-1188 and CWE-672 do not apply; see *CWE mapping*.
- Related: FIND-002 (confirmed), live held-asset NAV reprice. Different root cause, different fix.
Description
Proof of Concept
Recommendation
Description
Proof of Concept
Recommendation
Remediation Comment
Description
Proof of Concept
Recommendation
Description
Proof of Concept
Recommendation
Description
Proof of Concept
Recommendation
Description
Proof of Concept
Recommendation
Description
Proof of Concept
Recommendation
Description
Proof of Concept
Recommendation
Description
Proof of Concept
Recommendation
Description
Proof of Concept
Recommendation
Remediation Comment
Description
Proof of Concept
Recommendation
Description
Proof of Concept
Recommendation
Remediation Comment
Disclaimer#
Halborn strongly recommends conducting a follow-up assessment of the project either within six months or immediately following any material changes to the codebase, whichever comes first. This approach is crucial for maintaining the project’s integrity and addressing potential vulnerabilities introduced by code modifications.
