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:
ShieldValidationServicewrapsshield.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:
| Responsibility | Owner |
|---|---|
| Shield validators, vault registry, validation logic | Yield team |
| Shield npm package versioning and releases | Yield team |
| ShieldValidationService integration in the Yield API | Yield API team |
| Monitoring and alerting for Shield events | Shared (Yield + Ops) |
| Incident response for Shield-related blocks | Shared (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:
| Check | What it does |
|---|---|
| Contract whitelist | tx.to must be a known, whitelisted contract address |
| Calldata pattern match | Function selector and parameters must match a known-safe pattern |
| Receiver enforcement | Decoded receiver in calldata must match the user's address (or an explicitly approved receiver via args.receiverAddress) |
| Owner enforcement | For withdrawals, the decoded owner must match the user's address |
| Spender whitelist | For approvals, the spender must be a whitelisted vault or contract |
| Tamper detection | Re-encoded calldata is compared byte-for-byte against the original |
| Chain scope | Vault 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)
| Scenario | What happened | Shield's response |
|---|---|---|
| Fund redirection | deposit(amount, receiver) where receiver is not the user | BLOCK — receiver does not match user address |
| Approval hijacking | approve(spender, amount) where spender is not a whitelisted vault | BLOCK — spender not in vault registry |
| Wrong withdrawal owner | redeem(shares, receiver, owner) where owner is not the user | BLOCK — owner does not match user address |
| Calldata tampering | Extra bytes appended to valid calldata | BLOCK — re-encoded calldata does not match original |
| Unknown contract | Transaction targets a contract not in the vault registry | BLOCK — vault address not whitelisted |
Unsupported yields are not blocked server-side.
ShieldValidationServiceperforms anisSupported(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)
| Scenario | Why it passes |
|---|---|
| Standard deposit | deposit(amount, userAddress) to a whitelisted vault — all checks pass |
| Legitimate approval | approve(whitelistedVault, amount) — spender is in the registry |
| Custodial deposit with approved receiver | deposit(amount, custodialAddress) where args.receiverAddress matches — intent declared and confirmed |
| Native token wrap | WETH.deposit() for a vault that requires wrapped native tokens |
Threat model
What Defense Mode mitigates
| Threat class | Description | How Shield addresses it |
|---|---|---|
| Internal compromise | An attacker gains access to the API runtime or codebase and modifies transaction construction to redirect funds | Shield independently validates every transaction against known-safe patterns. Modified transactions are blocked even if the API's transaction construction code was compromised. |
| Supply chain attack | A compromised dependency modifies transaction calldata during construction | Calldata tamper detection catches any modification, including subtle parameter changes |
| Insider threat | A malicious or compromised team member modifies transaction logic | Shield's independent versioning means a code change to the API's transaction layer does not automatically bypass Shield's validation |
| API response tampering | Man-in-the-middle modification of API responses between the API and client | Client-side Shield catches this; server-side Defense Mode ensures the API itself sent a valid transaction |
What Defense Mode does not mitigate
| Threat class | Why | Recommended mitigation |
|---|---|---|
| Protocol-level exploits | If the on-chain protocol itself is compromised (e.g., a smart contract vulnerability), the transaction Shield approves may interact with a compromised contract | DeFi risk monitoring, protocol pause capabilities |
| Client-side tampering | If the client modifies a valid transaction after receiving it from the API, Shield Defense has already approved the original | Client-side Shield validation, hardware wallet confirmation |
| Phishing / social engineering | User is tricked into initiating a legitimate but unwanted transaction | Wallet-level confirmation, user education |
| Gas price manipulation | Shield does not currently validate gas parameters (maxFeePerGas, gasLimit, etc.) | Planned: per-chain gas ceiling enforcement in BaseEVMValidator |
| Amount manipulation | Shield validates amount > 0 but does not currently compare against the user's intended amount | Planned: 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 > 0but 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:
| Category | Examples | Failure mode |
|---|---|---|
| Contract whitelist | tx.to must be in the vault registry | Block: unknown contract |
| Parameter integrity | Receiver must be user, owner must be user, spender must be whitelisted | Block: parameter mismatch |
| Calldata integrity | Re-encoded calldata must match original bytes | Block: tamper detected |
| Chain scope | Vault must be registered for the correct chain ID | Block: cross-chain mismatch |
| Pattern match | Transaction must match at least one known-safe pattern | Block: no matching pattern |
Rule updates
Shield's validation rules are embedded in the npm package (validators and vault registry). Updates follow this process:
- 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.
- Validator code changes — New validators or validation logic changes go through standard code review, CI testing, and release.
- Shield package release — A new version is published with SHA-256 checksums and artifact attestations.
- 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:
| Field | Description |
|---|---|
yieldId | The yield integration ID |
message | Human-readable summary (e.g., "Shield validation passed" / "Shield validation failed") |
originalType | Auto-detected transaction type |
network | The blockchain network |
reason | Validation failure reason (if blocked) |
details | Additional diagnostic context |
mode | MONITOR 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:
- Audit log — Every validation (allowed and blocked) is logged with full context in Betterstack. Logs can be filtered by
yieldIdand exported for security review. - 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:
- Immediate (< 1 min): Switch Shield from BLOCK to MONITOR mode via configuration change. Transactions are no longer blocked but continue to be logged.
- Short-term (< 15 min): Deploy a configuration update that disables Shield validation for a specific project or yield ID.
- 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:
- Server-side (Defense Mode) — Validates transactions inside the Yield API before they're sent to you
- 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
Updated about 19 hours ago
