SentinelBridge Audit

AI-Guided Cross-Chain Bridge Security · Smithkeem/SentinelBridge · February 21, 2026

11
Findings
2
Critical
3
High
3
Medium

Overview

SentinelBridge implements an AI-guided cross-chain bridge security system on Stacks. An off-chain AI agent assesses transfer risk in real-time, can pause the bridge, dynamically adjust limits, and block malicious addresses. The contract manages transfer requests with per-chain volume tracking and a multi-level incident response system.

The architecture separates three roles: Contract Owner (admin), AI Agent (risk assessment), and Security Guardians (emergency address blocking). Transfer requests go through risk scoring before approval.

Contract Architecture

Findings

#SeverityTitle
1CRITICALNo actual token transfers — bridge is pure bookkeeping
2CRITICALTransfer limit ratchets to zero permanently
3HIGHDaily volume limits never reset
4HIGHRisk assessment overwrites previous decisions
5HIGHVolume counted on initiation, never refunded on rejection
6MEDIUMtx-sender auth for AI agent bypassable
7MEDIUMImmutable contract owner — no transfer function
8MEDIUMHardcoded limit recovery ignores configured values
9LOWNo transfer execution function
10LOWAsymmetric guardian permissions
11INFONo events for administrative changes

Critical Findings

CRITICAL #1 — No Actual Token Transfers

initiate-transfer never calls stx-transfer? or any token transfer function. The bridge records transfer requests but never locks, escrows, or moves any funds.

(define-public (initiate-transfer (amount uint) (target-chain (string-ascii 10)) (target-address (string-ascii 42)))
    (let (...)
        ;; Creates a record... but never transfers tokens
        (map-set transfer-requests request-id { ... })
        (ok request-id)))

Impact: The entire bridge is non-functional. An "approved" transfer has zero on-chain effect — no funds are secured, locked, or transferred. Users can initiate transfers for amounts they don't hold.

Fix: Add (try! (stx-transfer? amount tx-sender (as-contract tx-sender))) to lock funds on initiation, and an execute-transfer function to release them on approval.

CRITICAL #2 — Transfer Limit Ratchets to Zero Permanently

In analyze-incident-report, warning-level incidents divide the global transfer limit by 4:

(var-set global-transfer-limit (/ (var-get global-transfer-limit) u4))

Due to Clarity's integer floor division, repeated warnings cause irreversible decay:

u10000 → u2500 → u625 → u156 → u39 → u9 → u2 → u0

Once at u0, the limit check (<= amount u0) blocks ALL transfers. Recovery only triggers when threat-score < 10, restoring to hardcoded u10000 — but there's no admin override to manually set the limit.

Impact: A series of warning-level incidents (which are expected in normal bridge operation) permanently disables all transfers. An attacker can trigger this by submitting borderline threat reports.

Fix: Add an admin function to set limits directly. Use subtraction instead of division for graduated reduction, or set a minimum floor (e.g., (max (/ limit u4) u100)).

High Findings

HIGH #3 — Daily Volume Limits Never Reset

current-volume on each chain monotonically increases and never resets:

(map-set supported-chains target-chain 
    (merge chain-config { current-volume: (+ (get current-volume chain-config) amount) }))

The only way to reset is for the owner to call configure-chain manually, which also wipes the chain's risk score. The "daily limit" is effectively a lifetime limit.

Impact: Every chain eventually hits its limit and becomes permanently unusable. The bridge has a finite total capacity equal to the configured limit per chain.

Fix: Track the last reset block, and automatically reset volume when sufficient blocks have passed (e.g., ~144 blocks ≈ 1 day on Stacks).

HIGH #4 — Risk Assessment Can Overwrite Previous Decisions

submit-risk-assessment never checks the current status of the transfer request:

(let ((request (unwrap! (map-get? transfer-requests request-id) ERR-INVALID-REQUEST)))
    (asserts! (is-ai-agent) ERR-NOT-AUTHORIZED)
    ;; No check: (asserts! (is-eq (get status request) STATUS-PENDING) ...)
    (map-set transfer-requests request-id (merge request { status: new-status, risk-score: risk-score })))

Impact: A compromised or buggy AI agent can flip already-approved transfers to rejected (griefing) or flip rejected transfers to approved (bypassing security). The finality of risk decisions is broken.

HIGH #5 — Volume Counted on Initiation, Never Refunded on Rejection

When initiate-transfer is called, the transfer amount is immediately added to the chain's current-volume. If the AI agent later rejects the transfer, the volume is never decremented.

Impact: Grief attack — an attacker spams transfer requests on a target chain. All get rejected, but the chain's volume limit is exhausted. Combined with Finding #3 (no volume reset), this permanently disables the chain.

Fix: Decrement volume in submit-risk-assessment when status is REJECTED, or only count volume for approved transfers.

Medium Findings

MEDIUM #6 — tx-sender Auth for AI Agent

(define-private (is-ai-agent)
    (is-eq tx-sender (var-get authorized-ai-agent)))

Uses tx-sender (the original transaction sender) instead of contract-caller (the immediate caller). If the AI agent is an EOA, any contract it calls could use this contract as an intermediary. Should use contract-caller for tighter access control.

MEDIUM #7 — Immutable Contract Owner

CONTRACT-OWNER is defined as (define-constant CONTRACT-OWNER tx-sender) — set permanently at deploy time. There's no ownership transfer function. If the deployer key is compromised or lost, the contract becomes unmanageable: no new chains, no guardian changes, no AI agent rotation.

MEDIUM #8 — Hardcoded Limit Recovery

(if (< current-score u10)
    (var-set global-transfer-limit u10000)
    true)

Recovery always resets to u10000 regardless of what the operational limit should be. If the bridge was configured for higher throughput (e.g., u1000000), recovery throttles it to 1% of capacity.

Low & Informational

LOW #9 — No Transfer Execution Function

There's no execute-transfer or complete-transfer function. Even after AI approval, nothing happens on-chain. The contract is an incomplete risk-assessment layer with no bridge mechanics underneath.

LOW #10 — Asymmetric Guardian Permissions

Guardians are described as an "emergency" role but can only block/unblock addresses. They cannot pause the bridge, adjust limits, configure chains, or submit risk assessments. For incident response, their powers are surprisingly limited.

INFO #11 — No Events for Administrative Changes

Guardian additions/removals, AI agent changes, and chain configuration changes emit no print events. The contract is designed for off-chain monitoring but provides no visibility into admin actions.

Positive Observations

Verdict

SentinelBridge is architecturally interesting — the AI-guided risk assessment pattern is a novel approach to bridge security. However, the contract is fundamentally incomplete. The most critical issue is that no actual token transfers occur; it's a risk assessment framework without a bridge underneath. Beyond that, the transfer limit mechanism has a fatal ratchet-to-zero bug, volume tracking is broken (no reset), and risk assessments lack finality.

Not safe for production use. Requires fundamental redesign of token handling, limit management, and volume tracking before deployment.