Defense Mode

Server-side transaction enforcement for the Yield API

Overview

Defense Mode is Shield running server-side — validating every outbound transaction inside the Yield API's pipeline. Transactions that fail validation are rejected before they reach the client.

Today, Shield Defense runs as an in-process library inside the Yield API. This gives the API an independently-versioned, hardened validation layer that catches tampering inside the API's own transaction construction — but it does not yet provide infrastructure-level isolation: a compromise of the Yield API process can in principle also reach Shield. Full isolation via a separate deployment is on the roadmap (see "Planned: Infrastructure Isolation" below).

Defense Mode is complementary to client-side Shield. Running Shield on the client validates that nothing was tampered with between the API and the signing flow. Running Shield server-side in Defense Mode validates that nothing was tampered with inside the API itself. For maximum security, run both.


How it works

In the standard Yield API flow, a client requests a transaction (e.g., deposit ETH into a Lido staking position), and the API constructs an unsigned transaction and returns it. The client then presents this transaction for signing.

With Defense Mode enabled, Shield intercepts every constructed transaction just before it's returned to the client:

Client Request
    ↓
Yield API constructs unsigned transaction
    ↓
┌─────────────────────────────────────┐
│  Shield (Defense Mode)              │
│                                     │
│  1. Decode calldata                 │
│  2. Validate against known pattern  │
│  3. Check receiver, owner, spender  │
│  4. Detect tampering                │
│  5. ALLOW or BLOCK                  │
└─────────────────────────────────────┘
    ↓                    ↓
  ALLOW               BLOCK
    ↓                    ↓
Transaction         Transaction rejected.
returned to         Client receives error.
client

Shield validates the transaction using the same pattern-matching engine as the client-side library — same validators, same vault registry, same calldata tamper detection. The difference is enforcement: in Defense Mode, a failed validation means the transaction is never sent.


Deployment architecture

Current architecture (in-process library)

Shield Defense today runs as the @yieldxyz/shield npm package embedded inside the Yield API:

  • ShieldValidationService wraps shield.validate() calls within the API's transaction pipeline.
  • Validation runs in the same process, in the same call stack, as transaction construction.
  • No network hop, no separate deployment — validation adds single-digit milliseconds of overhead.
  • Versioning is via the npm package; updates ship through the standard API deploy process.
┌──────────────────────────────────────────┐
│  Yield API process                       │
│                                          │
│  Transaction construction                │
│       ↓                                  │
│  ShieldValidationService                 │
│  └── shield.validate() ─── ALLOW/BLOCK   │
│       ↓                                  │
│  Return to client (if ALLOW)             │
└──────────────────────────────────────────┘

Ownership boundaries:

ResponsibilityOwner
Shield validators, vault registry, validation logicYield team
Shield npm package versioning and releasesYield team
ShieldValidationService integration in the Yield APIYield API team
Monitoring and alerting for Shield eventsShared (Yield + Ops)
Incident response for Shield-related blocksShared (Yield + Ops + Client team)

Planned: Infrastructure Isolation

The current in-process architecture means that a compromise of the Yield API process can also reach Shield. Full isolation — where Shield runs in a separate deployment with independent access controls — is on the roadmap.

The separation principle: An attacker who gains access to the Yield API's infrastructure cannot tamper with Shield unless they also compromise Shield's independent deployment environment. The blast radius of a single compromise is contained.

When implemented, Shield Defense would be deployed as a lightweight API endpoint on a separate hosting platform:

┌──────────────────┐         ┌──────────────────┐
│   Yield API      │         │  Shield Defense  │
│   (AWS)          │ ──────► │  (Vercel)        │
│                  │  HTTP   │                  │
│  Constructs tx   │ ◄────── │  Validates tx    │
│  Returns or      │ Result  │  Returns ALLOW   │
│  blocks based    │         │  or BLOCK        │
│  on result       │         │                  │
└──────────────────┘         └──────────────────┘

This work is tracked but not yet scheduled.


What Defense Mode validates

Defense Mode runs the same validation checks as the client-side Shield library. Every outbound transaction is validated against the full suite of checks for its detected type.

Validation checks

For all EVM transactions:

CheckWhat it does
Contract whitelisttx.to must be a known, whitelisted contract address
Calldata pattern matchFunction selector and parameters must match a known-safe pattern
Receiver enforcementDecoded receiver in calldata must match the user's address (or an explicitly approved receiver via args.receiverAddress)
Owner enforcementFor withdrawals, the decoded owner must match the user's address
Spender whitelistFor approvals, the spender must be a whitelisted vault or contract
Tamper detectionRe-encoded calldata is compared byte-for-byte against the original
Chain scopeVault addresses are validated against the correct chain ID

For Solana transactions:

  • Stake account validation
  • Delegate authority verification
  • Transfer recipient verification

For Tron transactions:

  • Validator address verification
  • Owner address verification

What gets blocked (examples)

ScenarioWhat happenedShield's response
Fund redirectiondeposit(amount, receiver) where receiver is not the userBLOCK — receiver does not match user address
Approval hijackingapprove(spender, amount) where spender is not a whitelisted vaultBLOCK — spender not in vault registry
Wrong withdrawal ownerredeem(shares, receiver, owner) where owner is not the userBLOCK — owner does not match user address
Calldata tamperingExtra bytes appended to valid calldataBLOCK — re-encoded calldata does not match original
Unknown contractTransaction targets a contract not in the vault registryBLOCK — vault address not whitelisted

Unsupported yields are not blocked server-side. ShieldValidationService performs an isSupported(yieldId) check and skips validation when no validator is registered for a yield. Server-side enforcement only applies to yields with a registered validator. For yields outside Shield's coverage, the API passes the transaction through unvalidated — Defense Mode does not add protection for those flows. Client-side Shield returns { isValid: false } for unsupported yields and therefore provides stronger guarantees for those cases.

What gets allowed (examples)

ScenarioWhy it passes
Standard depositdeposit(amount, userAddress) to a whitelisted vault — all checks pass
Legitimate approvalapprove(whitelistedVault, amount) — spender is in the registry
Custodial deposit with approved receiverdeposit(amount, custodialAddress) where args.receiverAddress matches — intent declared and confirmed
Native token wrapWETH.deposit() for a vault that requires wrapped native tokens

Threat model

What Defense Mode mitigates

Threat classDescriptionHow Shield addresses it
Internal compromiseAn attacker gains access to the API runtime or codebase and modifies transaction construction to redirect fundsShield independently validates every transaction against known-safe patterns. Modified transactions are blocked even if the API's transaction construction code was compromised.
Supply chain attackA compromised dependency modifies transaction calldata during constructionCalldata tamper detection catches any modification, including subtle parameter changes
Insider threatA malicious or compromised team member modifies transaction logicShield's independent versioning means a code change to the API's transaction layer does not automatically bypass Shield's validation
API response tamperingMan-in-the-middle modification of API responses between the API and clientClient-side Shield catches this; server-side Defense Mode ensures the API itself sent a valid transaction

What Defense Mode does not mitigate

Threat classWhyRecommended mitigation
Protocol-level exploitsIf the on-chain protocol itself is compromised (e.g., a smart contract vulnerability), the transaction Shield approves may interact with a compromised contractDeFi risk monitoring, protocol pause capabilities
Client-side tamperingIf the client modifies a valid transaction after receiving it from the API, Shield Defense has already approved the originalClient-side Shield validation, hardware wallet confirmation
Phishing / social engineeringUser is tricked into initiating a legitimate but unwanted transactionWallet-level confirmation, user education
Gas price manipulationShield does not currently validate gas parameters (maxFeePerGas, gasLimit, etc.)Planned: per-chain gas ceiling enforcement in BaseEVMValidator
Amount manipulationShield validates amount > 0 but does not currently compare against the user's intended amountPlanned: args.amount cross-referencing

Failure modes

Shield throws during validation

In BLOCK mode: the exception is caught by ShieldValidationService and re-thrown as an InternalServerErrorException. The client receives an HTTP 500 response with a standard NestJS error body.

In MONITOR mode: the exception is logged with full context and the transaction is returned to the client as normal.

Rationale: fail-closed under enforcement, fail-open under observation.

Shield fails to initialize at module startup

If the @yieldxyz/shield package fails to initialize (corrupted vault registry, missing required fields), the Yield API process fails to start. No transactions flow. Misconfiguration is caught at boot rather than at the time of the first transaction.

False positives (blocks good traffic)

Impact: A legitimate transaction that Shield incorrectly rejects will prevent the user from completing their action.

How this happens:

  • A new vault is added to the API but not yet in Shield's embedded registry
  • A protocol changes its transaction format (e.g., USDT's zero-approval reset)
  • An edge case in receiver/owner matching (e.g., custodial flows without args.receiverAddress)

Mitigation:

  • Monitor mode soak period before enabling block mode catches most false positives
  • Shield logs every validation failure with full context for rapid diagnosis
  • Vault registry is kept in sync with the API's supported yields
  • Rollback to monitor mode can be executed without a full deployment

False negatives (misses bad traffic)

Impact: A tampered transaction passes Shield validation and reaches the client.

How this happens:

  • A yield ID has no validator (unsupported yields pass through server-side — see note in "What gets blocked")
  • Amount manipulation — Shield checks amount > 0 but does not currently cross-reference against the user's intended amount

Mitigation:

  • Client-side Shield provides a second layer of validation
  • Known coverage gaps are tracked and prioritized
  • Fork testing and mutation testing in CI catch gaps before they reach production

Enforcement policy

Response semantics

When Shield Defense blocks a transaction in BLOCK mode, the API returns a standard NestJS error response:

HTTP Status: 500 Internal Server Error

{
  "statusCode": 500,
  "message": "Transaction blocked by security validation"
}

Detailed block reasons — validator name, decoded parameters, rejected addresses — are logged server-side and are not returned to the client. Exposing validation failure details would let an attacker iteratively probe Shield's logic to find bypasses.

What is internal-only (not returned to clients):

  • Raw calldata and decoded parameters
  • Specific validator diagnostics (rejected addresses, expected vs. actual values)
  • The detected transaction type and exact reason for rejection

Rule categories

Shield's validation rules are deterministic and fall into these categories:

CategoryExamplesFailure mode
Contract whitelisttx.to must be in the vault registryBlock: unknown contract
Parameter integrityReceiver must be user, owner must be user, spender must be whitelistedBlock: parameter mismatch
Calldata integrityRe-encoded calldata must match original bytesBlock: tamper detected
Chain scopeVault must be registered for the correct chain IDBlock: cross-chain mismatch
Pattern matchTransaction must match at least one known-safe patternBlock: no matching pattern

Rule updates

Shield's validation rules are embedded in the npm package (validators and vault registry). Updates follow this process:

  1. Vault registry update — New vaults added in the API are exported to a registry JSON, which is embedded in the next Shield release. This is currently a manual export; an automated CI/CD pipeline is planned.
  2. Validator code changes — New validators or validation logic changes go through standard code review, CI testing, and release.
  3. Shield package release — A new version is published with SHA-256 checksums and artifact attestations.
  4. API-side version bump — The Yield API's Shield dependency is updated to the new version.

Observability

What exists today

Every Shield validation produces a structured JSON log entry via the NestJS logger. The logged fields are:

FieldDescription
yieldIdThe yield integration ID
messageHuman-readable summary (e.g., "Shield validation passed" / "Shield validation failed")
originalTypeAuto-detected transaction type
networkThe blockchain network
reasonValidation failure reason (if blocked)
detailsAdditional diagnostic context
modeMONITOR or BLOCK

Logs flow to Betterstack and are queryable by yieldId, mode, and reason.

Planned

The following observability features are on the roadmap:

  • Per-validation metrics emission (validation counts, latency percentiles, rule-hit-rate by category)
  • Direct partner alerting on blocks (implementing safely to avoid side effects under burst traffic)
  • Block-rate threshold alerts per project

Proving Shield is active

To verify that Shield Defense is enabled and working for a specific integration:

  1. Audit log — Every validation (allowed and blocked) is logged with full context in Betterstack. Logs can be filtered by yieldId and exported for security review.
  2. Test transactions — Known-bad test payloads can be submitted in a staging environment to confirm Shield blocks them. For example, a transaction with a modified receiver address should return HTTP 500.

Rollout plan

Defense Mode follows a two-phase rollout to minimize false-positive risk.

Phase 1: Monitor

Shield validates every transaction and logs the result. No transactions are blocked. Soak for 48+ hours on the target project's live traffic before proceeding.

Entry criteria:

  • Shield is deployed and the vault registry includes all target-project vaults
  • Logging surface is wired up and logs are flowing to Betterstack

Exit criteria:

  • 48+ hours of clean operation with no unexpected validation failures
  • Any flagged issues investigated and resolved or have documented workarounds

Phase 2: Block

Per-project enforcement is enabled. Transactions that fail validation return HTTP 500 with a standard NestJS error body. The enforcement flag is project-level configuration — no deployment required to flip it.

Entry criteria:

  • Phase 1 exit criteria met
  • Client team notified and prepared for potential blocks
  • Rollback path (flipping enforcement back to monitor) tested

Ongoing:

  • Every block is investigated within SLA
  • New yields or vault changes trigger a re-validation cycle in Monitor mode before enabling enforcement

Rollback procedure

If Defense Mode causes unexpected disruptions:

  1. Immediate (< 1 min): Switch Shield from BLOCK to MONITOR mode via configuration change. Transactions are no longer blocked but continue to be logged.
  2. Short-term (< 15 min): Deploy a configuration update that disables Shield validation for a specific project or yield ID.
  3. Full rollback (< 30 min): Revert the Shield package version. The Yield API falls back to operating without server-side validation.

No full code deployment is required for steps 1 or 2 — mode changes are configuration-driven.


Known limitations

Shield's coverage is actively evolving. For integrations with high-value or sensitive flows, contact the Yield team for the current list of known coverage gaps and planned improvements. We disclose these directly to integrators rather than publishing them.


For clients

What you need to know

If you're integrating with the Yield API and Defense Mode is enabled for your integration:

  • No action required on your end. Defense Mode runs server-side. Your integration code does not change.
  • Blocked transactions return an HTTP 500 error indicating the transaction could not be validated. The response body is a standard error payload — detailed block reasons are logged server-side and are not exposed in the response. If you encounter a block you believe is incorrect, contact the Yield team with the request timestamp and your API key; we will correlate against server-side logs to determine the exact reason.
  • We recommend also running Shield client-side for defense in depth. See the Shield documentation for installation and usage.

Running Shield on your end (recommended)

For maximum security, run Shield as an additional validation step in your own signing flow. This gives you two independent checkpoints:

  1. Server-side (Defense Mode) — Validates transactions inside the Yield API before they're sent to you
  2. Client-side (your integration) — Validates transactions after you receive them, before presenting them for signing

Even if one layer is compromised, the other catches tampered transactions.

import { Shield } from '@yieldxyz/shield';

const shield = new Shield();

// Validate every transaction before signing — even though
// Defense Mode already validated it server-side
for (const tx of action.transactions) {
  const result = shield.validate({
    unsignedTransaction: tx.unsignedTransaction,
    yieldId: action.yieldId,
    userAddress: userWalletAddress,
  });

  if (!result.isValid) {
    // This should never happen if Defense Mode is working,
    // but defense in depth means checking anyway
    throw new Error(`Shield validation failed: ${result.reason}`);
  }
}

Definition of done

This checklist is used to verify that Defense Mode is fully operational for a client integration:

  • Vault registry includes all client-specific vaults — Including allocator vaults (OAVs) and fee wrapper addresses
  • Monitor mode soak completed — 48+ hours with no unexpected false positives for the target client's yields
  • Block mode enabled — Shield is actively blocking invalid transactions for the target client
  • Sample test cases executed and recorded:
  • Known-good transaction (standard deposit) — allowed
  • Known-bad transaction (wrong receiver) — blocked
  • Known-bad transaction (non-whitelisted contract) — blocked
  • Edge case (USDT zero-approval, custodial receiver) — handled correctly
  • Alerts are configured and tested:
  • Blocked transaction log entry appears in Betterstack
  • Shield-related exceptions surface in error monitoring
  • On-call / escalation path documented — Shield-related incidents have a clear owner and escalation path
  • Rollback procedure tested — Mode switch from BLOCK to MONITOR confirmed to work without deployment
  • Client notified — Client team is aware that Defense Mode is active and understands the error semantics