Building on Avalanche? Ava Labs can cover up to 75% of your audit, and we take 10% off our quote. Avalanche audits: up to 75% covered. See how

Work & reportsReport

Virto NetworkPallet Pass Security Review

Guvenkaya reviewed the Virto Network Pass pallet and its passkey authenticators.

Virto Network logo

Date of engagement: 9th July 2025 - 18th July 2025

  • Timur GüvenkayaPrincipal advisor

Engagement Dashboard

Audit results

Guvenkaya conducted a security assessment of the Virto network Substrate Pallet Pass from 9th July 2025 to 18th July 2025. During this engagement, a total of 8 findings were reported. 1 of the findings was high, 4 medium and the remaining were low severity. Major issues are fixed by the Virto Network team.

Engagement record

Client
Virto Network
System
Virto Network Pass pallet (Substrate)
Review period
9th July 2025 - 18th July 2025
Paths in scope
2
Principal advisor
Timur Güvenkaya

Timeline

  1. Review start9th July 2025
  2. Draft report21st July 2025
  3. Final report19th August 2025

Findings by severity

  • Critical 0
  • High 1
  • Medium 4
  • Low 3
  • Informational 0

Finding status at final report

8Raised
7Fixed
1Scheduled

Remediation complexity

5Simple
3Moderate

Impact

2Severe
3Moderate
3Negligible

Likelihood

1Likely
5Possible
2Rare

Advisory and Client

The advisory

Guvenkaya specializes in security for digital assets and financial infrastructure, from smart contracts and protocols to applications, infrastructure, custody and cryptography.

Our team, including the sitting Heads of Security at Gauntlet and Agora, brings experience from Kraken, OpenZeppelin, Binance, ZKsync, Nillion, Invicti and EY. Across the team: 200+ security reviews, $30B+ in volume secured, 13 published cryptography research papers, and credentials including OSCP, OSWE and CISM.

  • Every reviewer has a public bio and is named before you sign. We match expertise to your system, with a principal leading scoping and checking findings.

The client

Virto Network is next generation payment infrastructure for impactful communities.

Scope and Review Boundaries

Review depths

Deep
Line-by-line review with explicit attack hypotheses, adversarial reasoning about state transitions, and reproduction attempts for each hypothesis that survived reading.
Supporting
The code was read for its effect on a reviewed flow. Behaviour outside that effect was not assessed.
Targeted
Entry points were exercised or triaged against the flow's security properties rather than read in full.

This is the code we read, and how closely. 2 paths are in scope. Each path links to the file at the reviewed commit.

Out of scope

The audit will include reviewing the code for security vulnerabilities. The audit does not include a review of the tests and dependencies.

Assessment Framework

Guvenkaya rates findings by likelihood and impact so remediation can be prioritized by technical risk, business context, and the decision the engagement is meant to support.

Rating chain applied to every finding

  1. 01 Likelihood Likely, possible or rare
  2. 02 Impact Severe, moderate or negligible
  3. 03 Severity Derived from the matrix below
  4. 04 Remediation complexity Simple, moderate or difficult
  5. 05 Status Not fixed, fixed, acknowledged or scheduled

Impact

ImpactWhat it means here
SevereExploitation could result in critical loss or compromise, such as full system control, substantial financial loss, or severe reputational damage.
ModerateExploitation may lead to limited data loss, partial compromise, moderate financial impact, or noticeable degradation of services.
NegligibleExploitation has minimal impact, such as minor data exposure without significant consequences or slight inconvenience without substantial disruption.

Likelihood

LikelihoodWhat it means here
LikelyThe vulnerability is easily discoverable and not overly complex to exploit.
PossibleThe vulnerability presents some challenges either in discovery or in the complexity of the attack.
RareThe vulnerability is either very difficult to discover or complex to exploit, or both.

This matrix provides a nuanced view, taking into account both the ease of discovering a vulnerability and the complexity involved in exploiting it.

Attack Complexity \ Discovery EaseObviousConcealedHidden
ComplexPossibleRareRare
ModerateLikelyPossibleRare
StraightforwardLikelyPossiblePossible

Severity

SeverityWhat it means here
CriticalAssigned to vulnerabilities with severe impact and a likely likelihood of exploitation.
HighFor vulnerabilities with either severe impact but only a possible likelihood, or moderate impact with a likely likelihood.
MediumUsed for vulnerabilities with severe impact but a rare likelihood, moderate impact with a possible likelihood, or negligible impact with a likely likelihood.
LowFor vulnerabilities with moderate impact and rare likelihood, or negligible impact with a possible likelihood.
InformationalThe lowest severity rating, typically for vulnerabilities with negligible impact and a rare likelihood of exploitation.
Likelihood \ ImpactSevereModerateNegligible
LikelyCriticalHighMedium
PossibleHighMediumLow
RareMediumLowInformational

Remediation complexity

This measures how difficult it is to remediate a finding once it has been identified.

Remediation complexityWhat it means here
SimplePatches or fixes are readily available and easily implemented.
ModerateRequires some time and resources to remediate, but well within the capabilities of most organizations.
DifficultRemediation requires significant resources, specialized skills, or substantial changes to systems or architecture.

Finding status

This tracks the client's response to each finding and whether remediation has been completed, accepted, or scheduled.

StatusWhat it means here
Not FixedIndicates that the vulnerability has been identified but no remedial action has been taken yet. This status is crucial for newly discovered vulnerabilities or those awaiting prioritization.
FixedThis status is applied when the vulnerability has been successfully remediated. It implies that appropriate measures, such as patching, configuration changes, or architectural modifications, have been implemented to resolve the issue.
AcknowledgedThis status is used for vulnerabilities that have been recognized, but for various reasons (such as risk acceptance, cost, or other business decisions), have not been fixed. It indicates that the risk posed by the vulnerability is known and has been consciously accepted.
ScheduledThis status indicates that the vulnerability has been acknowledged and a plan is in place to fix it in the future. It signifies that while remediation hasn't yet occurred, the issue has been prioritized and is part of the planned development roadmap.

Findings Summary

8All findings
0 Critical
1 High
4 Medium
3 Low
0 Informational

Findings register

Detailed Findings

GUV-1: DoS of The Main Functionality Through Session Key Hijacking

Severity
High
Impact
Moderate
Likelihood
Likely
Remediation Complexity
Simple
Status
Fixed

Description

In Pallet Pass, session keys are publicly exposed through the SessionCreated event (emitted in add_session_key with the raw session_key value) and are queryable via the SessionKeys storage map. An attacker can monitor events or query storage to obtain existing session keys from other users. Due to insufficient checks in add_session_key, the attacker can then call this extrinsic with the stolen key for their own pass account. This triggers try_remove_session_key, which removes the key from the original owner's storage (decrementing their consideration count and emitting SessionRemoved), and re-assigns it to the attacker with a new expiration. The uniqueness check !frame_system::Pallet::<T>::account_exists(session_key) only prevents collisions with persistent system accounts, not with existing ephemeral session keys in the pallet's storage. As a result, attackers can hijack keys before their natural expiry, rendering the session key functionality unreliable.

add_session_key:pallet_pass/src/lib.rs
ConsiderationHandler::<        T::AccountId,        SessionKeyConsiderations<T, I>,        T::SessionKeyConsideration,T::AccountId,>::increment(address)?;     Self::try_remove_session_key(session_key)?;     let until = duration        .unwrap_or(T::MaxSessionDuration::get())        .min(T::MaxSessionDuration::get());    SessionKeys::<T, I>::insert(session_key.clone(), (address.clone(), until));    Self::schedule_next_removal(session_key, duration)?;    ...}
try_remove_session_key:pallet_pass/src/lib.rs
if let Some(address) = &Self::pass_account_from_session_key(session_key) {        ConsiderationHandler::<            T::AccountId,            SessionKeyConsiderations<T, I>,            T::SessionKeyConsideration,            T::AccountId,        >::decrement(address)?;         SessionKeys::<T, I>::remove(session_key);         Self::deposit_event(Event::<T, I>::SessionRemoved {            session_key: session_key.clone(),        })    ...}

Impact

  • Denial-of-Service (DoS) of main functionality: Attackers can continuously hijack all active session keys (e.g., by monitoring events and calling add_session_key with short durations like 1 block), making sessions unusable for legitimate users. Victims must re-authenticate via passkeys/devices, degrading UX and potentially overwhelming the system with re-auth requests.
  • Griefing Vector: As outlined, attackers can target specific users (e.g., high-value accounts) or broadly disrupt the session feature, rendering it "useless" without constant re-creation. With short durations, attackers avoid risks to their own accounts while forcing expirations.
  • Economic Waste: Victims lose active sessions prematurely, incurring re-auth costs (e.g., time/gas for new keys).
  • Reputation Damage: Undermines trust in session keys as a reliable "short-lived" auth mechanism.

Proof of concept: GUV-1 PoC

Recommendation

  • Prevent Key Reuse: Add a check in add_session_key to ensure the key isn't already in SessionKeys: ensure!(!SessionKeys::<T, I>::contains_key(session_key), Error::<T, I>::SessionKeyInUse);.
  • Obscure Keys in Events: Emit a hash instead of raw session_key in SessionCreated (e.g., session_key_hash: T::Hashing::hash(&session_key.encode())).

Remediation

The Virto Network team has fixed the issue: GUV-1

GUV-2: Phishing and Cross-Site Credential Reuse Attacks Through Missing Origin & rpId Validation

Severity
Medium
Impact
Severe
Likelihood
Rare
Remediation Complexity
Simple
Status
Fixed

Description

Pallet Pass's WebAuthn verification lacks checks for the origin (client domain) in client_data JSON and rpIdHash in authenticator_data, allowing credentials generated for one site to be reused on another. Per WebAuthn specs, these must match the expected Relying Party (RP) to prevent cross-origin misuse. The verifier only does signature checks, ignoring these fields.

Impact

  • Cross-Site Credential Reuse: Allows auth from unintended domains, bypassing site isolation.
  • Compliance Failures: Non-spec-compliant; may fail interop with standard WebAuthn clients, causing auth denials or bugs.

Proof of concept: GUV-2 PoC

Recommendation

To ensure the passkey is genuine and from a verified source, you should:

  • Parse and Validate Client Data: Ensure origin matches expected RP domain.
  • Validate rpIdHash: Parse authenticator_data; verify rpIdHash == SHA256(expected rpId).

The Virto Network team has fixed the issue: GUV-2

GUV-3: Arbitrary Device Registration Through Missing Validation in Device Attestation

Severity
Medium
Impact
Severe
Likelihood
Rare
Remediation Complexity
Moderate
Status
Fixed

Description

The is_valid method in the DeviceAttestation implementation always returns true, bypassing critical verification steps required for secure device registration in a WebAuthn-based system. This method is part of the DeviceChallengeResponse trait and is invoked during the Authenticator::verify_device process, which handles new device attestations for user accounts. Normally, attestation verification should follow the WebAuthn Level 3 specification, including parsing the attestation object (CBOR-encoded with fields like fmt, attStmt, and authData), validating signatures against certificate chains, checking RP ID and origin bindings, and ensuring the authenticator's authenticity via AAGUID and FIDO Metadata. However, the current implementation skips all these checks.

is_valid:authenticators/webauthn/src/runtime/attestation.rs
    impl<Cx> DeviceChallengeResponse<Cx> for Attestation<Cx>where    Cx: Parameter + Copy + 'static,{    // TODO: @pandres95, considering that DeviceChallengeResponse is used for creating a new    // authentication device, webauth_verify wouldn't work here. We need to implement a new    // verification method exclusively for credential creation.    fn is_valid(&self) -> bool {        true    }     fn used_challenge(&self) -> (Cx, Challenge) {        (self.meta.context, self.challenge())    }

Impact

  • Arbitrary Device Registration: Attackers can register new accounts with malicious public keys, enabling front-running of legitimate users. For existing accounts, combined with phishing, it allows adding backdoor devices, leading to permanent unauthorized access and actions.
  • Phishing Amplification: Weak attestation accepts cross-origin or fake attestations, making it easier to phish users into registering cloned/malicious devices.
  • Reputation and Compliance Risks: Violates WebAuthn standards, exposing users to cloning/replay attacks.

Recommendation

  • Implement Full Attestation Verification: Replace is_valid = true with spec-compliant logic in DeviceChallengeResponse per this specification: https://www.w3.org/TR/webauthn-3/#sctn-registering-a-new-credential
  • Enhance Challenge Binding: Use non-empty xtc in verify_device to prevent precomputation.
  • Add Uniqueness and Limits: Enforce unique devices per account; cap devices per account (e.g., via BoundedVec).

Remediation

The Virto Network team has fixed the issue: GUV-3 Pallet Pass, GUV-3 Pass Authenticators

GUV-4: Potential DoS and Resource Exhaustion Through Underestimated Benchmarks and Fixed Weights

Severity
Medium
Impact
Moderate
Likelihood
Possible
Remediation Complexity
Moderate
Status
Scheduled

Description

The benchmarks and weights in the pallet are not correctly configured to handle the variability introduced by WebAuthn-specific paths, particularly in functions like register and authenticate that involve parsing variable-sized fields such as client_data (a Vec<u8> containing JSON). The benchmarks rely on fixed-size, small inputs generated via BenchmarkHelper for SubstrateKey (sr25519-based) attestations, which are typically 100-200 bytes and do not exercise the WebAuthn variant. As a result, the weights are fixed and do not scale parametrically with input size.

Impact

  • Denial of Service (DoS) Vulnerability: Attackers can submit extrinsics with large client_data which are charged the same low fixed weight but incur high actual execution time and memory. This can exceed block production limits, cause node crashes, or fill blocks with resource-intensive transactions, disrupting network consensus.
  • Economic Exploitation: Undercharged weights allow spamming without proportional gas fees, enabling cheap DoS attacks that waste validator resources (CPU, memory) while refunding minimal costs to the attacker.
  • Degraded Performance: In production, unaccounted WebAuthn paths lead to slower block times or validation failures under load, especially if extensions inflate data sizes. This erodes pallet reliability for WebAuthn users.
  • Incomplete Security Model: Fixed weights ignore adversarial inputs, potentially allowing griefing where large extrinsics pass validation but strain nodes, leading to operational costs and reduced scalability.

Recommendation

  • Update Benchmarks for Variability: Revise benchmarks to include WebAuthn-specific paths with parametric inputs.
  • Implement Parametric Weights: Calculate weights as base_weight + per_byte_weight * client_data_len, incorporating measured costs for decoding, parsing, and verification.
  • Add Input Bounds: Enforce limits on Vec<u8>fields (e.g., via BoundedVec<u8, ConstU32<4096>>) to cap maximum sizes and prevent extreme cases.
  • Separate Paths in Benchmarks: Benchmark SubstrateKey and WebAuthn variants independently to ensure accurate weights for each authenticator type.

Remediation

The Virto Network team has scheduled the fix of the issue: GUV-4

GUV-5: Potential Out-of-Memory (OOM) and Degraded Performance Due to Inefficient Parsing

Severity
Medium
Impact
Moderate
Likelihood
Possible
Remediation Complexity
Moderate
Status
Fixed

Description

The find_challenge_from_client_data function (and related get_from_json_then_map) employs a simplistic, non-robust parsing approach for extracting keys like "challenge" from clientDataJSON (a Vec<u8>). It converts the entire input to a string via String::from_utf8 then performs O(n) operations like split and linear iteration with find_map scanning substrings for matches. This allocates memory proportional to the input size (O(n)) and iterates in O(n) time worst-case, making it vulnerable to large or adversarial inputs.

find_challenge_from_client_data:src/runtime/helpers.rs
pub fn find_challenge_from_client_data(client_data: Vec<u8>) -> Option<Challenge> {    get_from_json_then_map(client_data, "challenge", |challenge| {        base64::decode_engine(challenge.as_bytes(), &BASE64_URL_SAFE_NO_PAD).ok()    })}...// get_from_json_then_map function declarationlet json = String::from_utf8(json).ok()?; let value = json    .split(",")    .find_map(|kv| kv.contains(key).then_some(kv.split_once(":")?.1))    .map(|v| v.trim_matches(|c: char| c.eq(&' ') || c.eq(&'"')))    .and_then(map)?;    ...

Impact

  • Out-of-Memory (OOM) Errors: Large inputs during String::from_utf8 or iteration can cause allocation failures, crashing the node or extrinsic execution, leading to failed transactions or consensus disruptions.
  • Degraded Performance and DoS: O(n) time for huge JSON causes excessive CPU usage, slowing block validation/production and enabling cheap DoS by spamming large extrinsics that tie up resources without proportional weight charges.
  • Resource Exhaustion: In validators, sustained large inputs consume memory/CPU, reducing throughput, increasing latency, or evicting other data, impacting overall network health and scalability.

Recommendation

  • Use Efficient Parsing: Replace naive string operations with a proper JSON parser that validates structure, handles duplicates/escapes, and extracts keys without full buffering.
  • Implement Incremental Parsing: Scan for keys without converting to a full string (e.g., byte-level searching), reducing memory to O(1) where possible.
  • Enforce Size Limits: Bound client_data (e.g., BoundedVec<u8, ConstU32<8192>>) to a reasonable max based on WebAuthn specs, rejecting larger inputs early.
  • Add Early Validation: Check UTF-8 validity and size before parsing; fail fast on anomalies to minimize resource use.

Remediation

The Virto Network team has fixed the issue: GUV-5

GUV-6: Silent or Unauthorized Authentication Through Missing Authenticator Flags Validation

Severity
Low
Impact
Negligible
Likelihood
Possible
Remediation Complexity
Simple
Status
Fixed

Description

No parsing/validation of authenticator_data flags: UP (User Present), UV (User Verified), BE (Backup Eligible), BS (Backup State). Specs require UP (always), UV (if needed), and BE/BS consistency to ensure user interaction and detect backups/clones. Verifier ignores these, accepting silent auth.

Impact

  • Silent Auth Without Consent: No UP/UV allows auth without touch/biometrics, enabling scripted attacks (e.g., malware on device).
  • Backup/Cloning Undetected: No BE/BS checks fails to handle multi-device risks (e.g., insecure backups).
  • Security Downgrade: Bypasses user verification, weakening passkey protection against physical access attacks.

Proof of concept: GUV-6 PoC

Recommendation

  • Follow flag verification per this specification: https://www.w3.org/TR/webauthn-3/#sctn-verifying-assertion

Remediation

The Virto Network team has fixed the issue: GUV-6

GUV-7: Possible Storage Bloating Due To Unbounded Devices and Session Keys Per User

Severity
Low
Impact
Negligible
Likelihood
Possible
Remediation Complexity
Simple
Status
Fixed

Description

No bounds on devices or session keys per user allow unlimited additions, leading to storage bloat. Costs exist (deposits via linear StoragePrice), but first device/session are free (FirstItemsFree), and privileged origins (Root/Communities) skip entirely (SkipConsideration). Attackers can loop registrations (pay per cycle) + free additions, orphaning data. Alternatively, a single account can add unlimited number of keys or devices.

Impact

  • Storage Bloat: Unlimited devices and sessions per account can fill storage maps, increasing node costs and degrading performance.

Recommendation

  • Introduce configurable limits, e.g., MaxDevicesPerAccount u32 = 5; MaxSessionsPerAccount u32 = 5. Check in extrinsics.

Remediation

The Virto Network team has fixed the issue: GUV-7

GUV-8: Authentication Through Incorrect Credential Type

Severity
Low
Impact
Negligible
Likelihood
Possible
Remediation Complexity
Simple
Status
Fixed

Description

The verifier doesn't check the type field in client_data JSON (must be "webauthn.get" for assertions per specs). It hashes client_data without parsing, accepting any type (e.g., "webauthn.create" for registrations misused as assertions).

Impact

  • Type Confusion Attacks: Allows "create" credentials (for registration) to auth as assertions, potentially creating invalid sessions or bypassing checks.
  • Compliance Issues: Violates specs; may cause interop failures with clients enforcing types.

Proof of concept: GUV-8 PoC

Recommendation

  • Parse Client Data Type: Deserialize client_data; ensure type == "webauthn.get".

Remediation

The Virto Network team has fixed the issue: GUV-8

The web version and the PDF (18 pages) are generated from the same source. The report as first delivered is kept in the public reports archive.

Tell us what you need to secure.

Describe your system, main concern, and deadline. We’ll reply with scoping questions and a proposed next step.

Discuss your scope