Corvo Edge Audited using Semgrep and Claude Opus 5 13 of 19 resolved
Corvo Edge

Every finding, what it risked, and the hour it closed.

Four reviews of Corvo Edge, covering the swap engine that moves your money and the custody path that derives your wallet. Nothing is summarised away: each entry carries the risk, the fix that shipped, and when it landed.

Updated 2026-09-22, 10:45 UTC 19 findings 13 resolved 6 controls verified

The ledger

One tile per finding. Sealed tiles are closed.

C1H1H2M1M4M2M3L1L2A5H3M8M9M10L3M11M12L4L5
1Critical
3High
10Medium
13Resolved
6Tracking

Verdict, pass

Across four reviews, 19 findings, of which 13 are closed and one is partly addressed. The one critical and all three high findings touched money or wallet identity, and every one of them is fixed: a swap client that trusted server-supplied calldata, a standing month-long allowance to the router, a server-controlled slippage floor, and a wallet-identity configuration that could have silently re-derived every X-login wallet. Funds stay in your own wallet, the platform fee is pinned server side and cannot be overridden by a client, secrets are never exposed, and every privileged endpoint refuses unauthenticated access. Automated orders are the one path where a server key signs, and it is bound to your own wallet, deny by default, capped, time-bound and revocable, so it can sign the swaps you scheduled and nothing else. Five items remain open, each named below with a date, and none of them is a path to funds.

Platform review

2026-06-29, full stack

The platform was reviewed across three domains: money paths and secrets; auth, API and web-app; and infrastructure and exposure. Each combined static code review with live probes against production. Internal engineering audit, not a paid third-party certification.

Domain 1, money, signing and secrets

💸 Money paths

Verdict, Strong
P2Aggregator build trusted the client routeSummaryBlast radius is the user's own wallet (they sign; the aggregator re-prices min-out). Defense-in-depth: re-quote server-side. Tracked.
P2BYOK vault uses one static master keyAES-256-GCM construction is correct; concentration risk only. Roadmap: per-record envelope encryption and key rotation.
P3Stale token list in an error stringCosmetic; the real allowlist is enforced elsewhere. No security impact.
Verified secure
Fee recipient correct and non-overridableFee can only be lowered by on-chain tierSelf-custody, no server key signs tradesExact-amount approvals, no infinitePost-swap balance floor, router re-assert.env 600, no secret logged or served

Domain 2, auth, API and web-app

🔑 Application layer

Verdict, Strong
P3Dotfile paths return the app shell (200) not 404Confirmed not a leak (no repo on disk; body is the app shell). Cosmetic status-code only.
P3Rate-limit skip uses a substring matchRate-limiting only, not an authz bypass; no current route exploits it. Switch to a prefix match.
Verified secure
JWT, revocable DB session, HttpOnly Secure cookieAPI keys: bcrypt, scoped, shown onceMCP authz gated, unsigned envelopes onlySSRF: fixed hosts, strict input regexParameterized SQL, no injectionOutput escaped, no XSS sinkAll admin endpoints 401/403 unauth

Domain 3, infrastructure and exposure

🧱 Infrastructure

Verdict, Pass
FixedStray backup files under the web root (one served)10 backup files removed from the web root; the served one now 404s. Deny rules are the fallback.
Fixedsecurity.txt pointed at the old domainCanonical and policy links repointed to corvoedge.xyz.
P3multer 1.x is end-of-lifeUpgrade to 2.x (multipart DoS advisories). Batched.
Verified secure
CSP, HSTS, full header stackPorts locked, app/db/model on localhostTLS valid, server tokens offNo secret / DB / source-map exposureNo directory listings

Swap engine, adversarial

2026-07-05, native v4 sell path

Adversarial review of the token-to-ETH swap engine, where you keep your keys, (Universal Router with Permit2, client-side CDP signing). Threat model: a compromised or MITM'd backend, given the client signs but the server builds.

Critical and high, the drain surface

C1Critical

Client trusted server-supplied Universal Router calldata

Fixed
Exploitexecute() is a generic multicall; the client only checked to == router. A compromised or MITM'd backend could swap TAKE_ALL(to user) for TAKE(to attacker), or inject SWEEP or TRANSFER or PERMIT2_TRANSFER_FROM, and drain the wallet's whole token balance.
FixThe client now ABI-decodes the swap input, enforces a strict action template (SWAP_EXACT_IN_SINGLE, SETTLE_ALL, TAKE_PORTION, TAKE_ALL only, rejecting any TAKE or non-whitelisted command), and rebuilds the transaction itself from the verified input. The opaque server blob is never signed.
Resolved 13:00 UTC, edge-swap.js sell(), verified: legit passes, tampered calldata rejected
H1High

Max, 30-day Permit2 allowance to the router (blast radius)

Fixed
ExploitThe signed permit granted the router MAX_UINT160 for 30 days, a standing full-balance allowance that turned any single malicious response into a total drain, for a month.
FixThe client builds its own permit, scoped to the exact sell amount with a 30-minute expiry. A compromised server can now touch only the tokens in the one swap the user is signing.
Resolved 13:00 UTC
H2High

Server-controlled slippage floor (guaranteed sandwich)

Fixed
ExploitBoth amountOutMinimum and the take floor came from the server. Set to near zero, an observing MEV bot sandwiches the swap for almost all its value with zero revert risk.
FixThe client re-quotes independently via the V4 Quoter and rejects the trade if the floor sits below its own computed minimum (slippage with a 3% timing tolerance). Verified against a live pool.
Resolved 13:00 UTC

Medium, bounded and hardened

M1Medium

Unverified, unbounded platform fee

Fixed
ExploitTAKE_PORTION carries an explicit recipient and bips; a hostile server could repoint the fee to an attacker or raise it toward 100%.
FixThe client asserts the fee recipient equals the hardcoded fee wallet and bips at most 100 (a 1% cap). Tamper test rejected. Fee stays silent in the UI, bounded in the calldata.
Resolved 13:00 UTC
M4Medium

Chain ID hardcoded in the permit domain

Fixed
ExploitCross-chain replay was already closed (the domain binds chainId 8453), but a wallet on the wrong network could sign an 8453-valid permit surfaced later.
FixThe client reads the signer's live chain and asserts it equals 8453 before signing, failing closed on a network mismatch.
Resolved 13:05 UTC
M2Medium

Unlimited ERC-20 approval to Permit2

Accepted
RationaleThe canonical Permit2 pattern, unavoidable and standard. Exposure is now bounded by the scoped Permit2-to-router allowance (H1), and the approval is only requested when actually needed.
Reviewed 13:00 UTC, residual risk accepted
M3Medium

Fee-on-transfer or honeypot input token

Partial
CoveredHoneypots that block sells revert the sell-quote (fail closed); dynamic-fee pools are hard-rejected; the on-chain depth floor kills dust.
PendingA fee-on-transfer-aware minimum (settle the actual received amount) so a skimming token cannot deliver less than shown. Queued.
Tracking, FoT-aware min next

Low and informational

L1Low

Permit nonce and replay

Closed
NotePermit2's sequential per-owner-token-spender nonce, the chainId-bound domain, and a short signature deadline close replay and cross-chain replay. A stale nonce simply reverts, so it is griefing only.
Verified 13:00 UTC
L2Low

Tampered frontend bundle (defense in depth)

Noted
NoteAll client verification defends against a compromised backend, not a swapped JS bundle. Trust anchors (router, Permit2, fee wallet, action whitelist) are baked into the bundle behind a strong CSP; subresource integrity is the remaining defense-in-depth step.
Tracking, SRI pending

Automated orders, delegated signing

2026-07-05, architecture and adversarial review

Limit, stop-loss, take-profit and DCA orders that fire on their own, on the wallet you already trade from. No deposit, no new wallet, no vault. A time-bound, revocable authorization lets the keeper submit only policy-conforming Base swaps while you are offline. Threat model: a compromised keeper, a hostile caller, and a half-provisioned deploy.

A1Control

Grant is bound to your own wallet only

Verified
ModelThe authorization is recorded only if the granted address equals the caller's own signed-in wallet. Nobody can register automation against someone else's wallet.
VerifiedA grant for any other address is rejected with 403. Past or non-future expiry is rejected with 400. The grant window is clamped to 30 days.
Verified 15:10 UTC
A2Control

Deny-by-default policy is the firewall, funds cannot leave your wallet

Verified
ModelThe keeper signs through a CDP policy that allowlists only the Universal Router and Permit2, pins Base, restricts calldata to the swap function, and caps per-order value. A transfer or withdraw to any other address is rejected inside the secure enclave before a signature is ever produced.
EffectA compromised keeper can submit nothing but the capped, policy-conforming swaps you scheduled. It cannot move funds elsewhere. Your keys never leave the enclave.
Design-confirmed 15:15 UTC
A3Control

Fail-closed provisioning

Verified
ModelThe keeper refuses to sign unless all hold: signing credentials present, the policy explicitly confirmed applied, and an active grant on the account. A half-provisioned deploy stays dry, never unguarded.
VerifiedWith no credentials the keeper runs in dry mode and produces no transaction. Trigger evaluation is a pure, unit-checked function.
Verified 15:20 UTC
A4Control

Per-account isolation and input hardening

Verified
VerifiedTwo-account adversarial test: cross-account cancel returns 404, one account sees zero of another's orders, unauthenticated arm, grant and revoke return 401. A SQL-injection token address and a negative amount are both rejected with 400. Rate-limited, with a 40-order cap.
Verified 15:12 UTC
A5Medium

Client-supplied user identifier on the grant

Accepted
NoteThe grant carries a client-supplied user identifier. Because the address is verified against your own wallet and the enclave binds a delegation to that user's own address, a forged identifier yields a non-functional grant, not a path to anyone's funds.
HardeningResolve the identifier server-side from the session once wired. Tracked.
Reviewed 15:18 UTC, residual risk accepted, non-exploitable for theft
A6Info

Third-party order router removed

Done
ChangeThe earlier third-party order-router integration was removed in full. Automated orders are now native and you keep your keys, executed on your own wallet. Fewer trust anchors, smaller surface.
Removed 15:25 UTC, build v1-518

Infrastructure, custody and provenance

2026-09-22, server tree and on-chain review

Scope: the production server, the CDP custody path that derives every X-login wallet, the public static surface, and the on-chain provenance of every contract behind the raise. Method: manual review of the full server tree, Semgrep across four rulesets, and live probes against production rather than trust in a status code.

H3High

Wallet identity ran on code defaults, and drift would have been silent

Fixed
RiskEvery X-login wallet is derived from three configuration values. Two of them were not set explicitly and fell back to values written in code, one of them tied to a separate project identifier. A change to any of them would have handed every user a newly derived, empty wallet. Login would still have succeeded, health would still have reported green, and the existing balance would have sat at an address the application no longer derives. Nothing would have alerted.
FixAll three values are now pinned explicitly, which also breaks the link to the separate project identifier. The application records a fingerprint of the three on first boot and refuses to start if it ever changes, so a silent re-derivation becomes a loud refusal. Verified before deployment against a changed value of each of the three and against an empty value, then verified after deployment that the recorded fingerprint matches the value computed from the live configuration beforehand, so no identity moved.
Resolved 10:39 UTC, login, key set and session flow all reconfirmed live
M8Medium

Stale-file guard missed several editor backup shapes

Fixed
RiskThe server already refused to serve snapshot copies of client files, and every snapshot then on disk was correctly blocked. The rule missed some shapes an editor or package manager can leave behind. A file in one of those shapes, dropped next to a served asset, would have published full client source.
FixThe rule now also covers trailing-tilde files, further editor swap files, merge rejects, package-manager leftovers, a keyword with digits appended, and long date suffixes. Validated against all 560 real files under the public root: the same 31 snapshots are blocked, 526 assets serve unchanged, and the only other responses are three pre-existing canonical redirects.
Resolved 10:29 UTC
M9Medium

A backup web-server config sat inside the live load path

Fixed
RiskThe web server loads every file in its enabled-sites directory, and a dated backup of a live config had been left there. It was parsed as real configuration and produced eight duplicate host definitions, all silently ignored. Load order meant the correct file won, so nothing was broken, but a later rename or a second backup would have reversed that without any error and without failing a config test.
FixThe stray file was moved out of the load path and retained elsewhere. Duplicate host warnings went from eight to zero, the config test passes, and all three hosts serve normally. No other strays remain.
Resolved 10:42 UTC
M10Medium

Login popup placed a serialized payload into script context unescaped

Fixed
RiskThe page that returns a login result to the opening window embedded a serialized object inside a script block without escaping the characters that can end that block early. The visible message was escaped; this copy was not. Not reachable in practice, because every message at the three call sites is a fixed string and the token cannot contain the relevant character, but the page holds the login token, so script execution there would be account compromise rather than defacement.
FixThe payload is now escaped for script context before it is written. Verified that a hostile message no longer emits a block-terminating sequence or any raw angle bracket, that the escaped payload still parses back to the exact original string, and that an ordinary message is unchanged. The result is posted only to the page's own origin, never to a wildcard.
Resolved 10:29 UTC
L3Low

Authenticated-encryption tag length not pinned

Fixed
RiskThe vault that encrypts user-supplied API credentials verified its authentication tag but did not pin the tag length, so a short tag would have been accepted. Hardening rather than a break, and the tag is only ever written by the server.
FixThe tag length is now pinned and a wrong-length tag is rejected outright. Verified that a normal round trip succeeds, a truncated tag is refused, and a single flipped bit is refused. The vault held no records at the time, so no stored data was affected.
Resolved 10:29 UTC
M11Medium

Production tree carries uncommitted changes

Tracking
NoteThe production checkout holds a large number of uncommitted modifications, so there is no single revision that describes what is running and a rollback has no precise target. This is a release-process gap, not a reachable vulnerability.
Tracking, deploys to ship a recorded revision
M12Medium

Dependency advisories, three high

Tracking
NoteTwenty one advisories across the dependency tree, of which three are high and all three trace to a single transitive package with no fixed version published upstream. The automatic remediation is not available here because it downgrades a core library by several major versions, which would be a larger risk than the advisories themselves.
Tracking, named gap, waiting on an upstream release
L4Low

Configuration snapshots and a stray empty database file on disk

Tracking
NoteFour dated copies of the server configuration file sit beside the live one. All carry owner-only permissions and none is reachable from the web, but each widens the blast radius of a host compromise. A separate empty database file in the application root is a correctness hazard rather than an exposure: a tool opened without the configured path reads it and every check passes against nothing.
Tracking, removal pending sign-off, nothing is deleted unilaterally
L5Low

One fee destination written in code rather than configuration

Tracking
NoteEvery fee destination is read from configuration except one, which appears as a literal in two places in the same file. Two copies of one address drift independently, and the value cannot be rotated without a code change and a redeploy.
Tracking, to move into configuration
V1Verified

Confirmed sound under this pass

Verified
CheckedNo credentials written into source anywhere in the server tree. No injection path: every dynamic query fragment is built from fixed text and every value is bound. Token algorithms pinned at both verification points. Session cookies restricted from script access, sent only over TLS, limited on cross-site use, and revocable server side. The outbound proxy validates scheme and host against a single permitted destination and refuses sensitive sub-paths. Tickers outside plain ASCII are rejected as impersonation. Slippage is bounded on every route. Path traversal probes all fail closed. The nightly backup ran and reached off-site storage the morning of the review.
ToolingSemgrep across four rulesets returned 37 results. Thirty three were false positives on inspection, including every raw-markup warning where the value already passes through the escaping helper, and a traversal warning on a route that validates its identifier against a fixed pattern first. The remaining four are recorded above. No credential rule matched anywhere in the tree.
Reviewed 2026-09-22

Method and tooling

Every number on this page came from a tool run or a live probe against production, never an estimate. Versions and counts are listed so the work can be repeated.

Semgrep 1.171.0Static analysis over the server tree and client bundle with the p/javascript, p/nodejs, p/secrets and p/owasp-top-ten rulesets. 500 files scanned, 8 rules fired, 37 results. Thirty three were false positives on inspection and four became findings above. No credential rule matched anywhere in the tree.
npm audit 26 direct depsDependency advisories across the installed tree. 21 open, of which 3 are high and all three trace to a single transitive package with no fixed version published upstream. Recorded as M12 rather than force-fixed, because the automatic remediation downgrades a core library by several major versions.
Node 25.2.1Parse verification. node --check across every changed server file, and node --input-type=module --check for client modules, because a plain check parses as a script and passes browser-fatal syntax errors.
Sourcify and Etherscan v2 chain 8453On-chain provenance for all eight addresses. Sourcify supplied the source trees, compiler settings and constructor arguments; the Etherscan v2 endpoint confirmed what Basescan actually serves for each address.
Live HTTP probes 560 pathsEvery file under the public root requested against the running app, plus targeted probes for path traversal, stale-file shapes and the login endpoints. A status code was never treated as proof on its own.
Manual review full server treeThe findings that mattered came from reading the code, not from a rule. The wallet-identity issue, the script-context injection and the config loaded from a backup file were all found by hand.

Tooling finds the shapes it has rules for. It did not find the highest severity issue on this page, and it reported thirty three things that were not problems, so every result was read rather than counted. This remains an in-house review, not a paid third-party engagement.

Contract provenance

Every contract behind the raise, on Base. All eight serve verified source on Basescan, and the three 45 byte addresses are registered proxies, so a reader lands on the verified implementation. Sourcify independently holds an exact creation-bytecode match for the token, the escrow and the treasury, which confirms the deployed code including its constructor arguments.

AddressRoleVerified asProvenance
0xce58d80d32ce44769bdcfcbf1227599ed126ee79TokenVibesTokenBasescan, Sourcify exact
0xa6f5e2a0547de81a7761567d87c226d36733c4b6EscrowVibesTranchEscrowProxy to implementation
0xcbe7e9e13576838eb4b55d0793fd1cffc1dea43dEscrow implementationVibesTranchEscrowBasescan, Sourcify exact
0xB9695d282B50f476acCa47dfc543B26F5AE95831TreasuryVibesTreasuryEscrowBasescan, Sourcify exact
0xE10Dad942c7043F38e0fd8040BBC0A64435e9C99Liquidity poolPoolProxy to implementation
0xa4e46b4f701c62e14df11b48dce76a7d793cd6d7Pool implementationPoolBasescan, Sourcify
0xefEcd387e60C315a28B8014db161b20111A10972LP fee claimerVibesLPFeeClaimerProxy to implementation
0x3697f4eba88bf657569553ec6ec3178c74d10109Claimer implementationVibesLPFeeClaimerBasescan, Sourcify

Two notes a reader should have. These are platform contracts deployed by the funding platform, not written here, so this section reports their provenance rather than claiming an audit of them. And the token and treasury carry an inherited verification on Basescan, matched against another deployment, which is why constructor arguments are blank there. An attempt to replace that with a direct verification was refused, because an existing verification cannot be overwritten. The independent exact creation match recorded by Sourcify closes that gap and is the stronger statement of the two. The token itself is a fixed-supply design with no mint function after deployment.

Remediation timeline

  1. 2026-06-29Platform audit, verdict PASS 0 critical, 0 high, 3 medium, 4 fixed. Web-root backups purged, security.txt repointed.
  2. 2026-07-05, 12:30 UTCEdge Swap adversarial audit commissioned review of the just-shipped v4 sell and Permit2 flow.
  3. 2026-07-05, 12:52 UTCFindings delivered 1 critical, 2 high, 4 medium, 2 low. C1 flagged as a full-drain class.
  4. 2026-07-05, 12:58 UTCDecode-and-verify logic validated legit response passes; fee-to-attacker tamper caught; independent minOut floor confirmed against a live pool.
  5. 2026-07-05, 13:00 UTCC1, H1, H2, M1 fixed client rebuilds the tx from verified calldata, scopes its own permit, re-quotes the floor, bounds the fee.
  6. 2026-07-05, 13:01 UTCDeployed to production build v1-512, service worker sell-hardening-512.
  7. 2026-07-05, 13:05 UTCM4 read-chainId assert fixed and deployed build v1-513. Wallet must be on Base to sign; fails closed otherwise.
  8. 2026-07-05, 14:50 UTCAutomated orders built on embedded-wallet delegation use-your-own-wallet model, no deposit and no bot wallet, funds never move on grant.
  9. 2026-07-05, 15:12 UTCAdversarial review held unauthenticated access 401, SQL-injection and negative amount rejected, cross-account cancel 404, tenant isolation confirmed, wrong-wallet grant 403.
  10. 2026-07-05, 15:20 UTCFail-closed keeper signing gated on credentials, a confirmed policy, and an active grant. A half-provisioned deploy stays dry.
  11. 2026-07-05, 15:25 UTCThird-party order router removed automation is native and you keep your keys. Deployed build v1-518.
  12. 2026-09-22, 10:29 UTCInfrastructure and custody review full server tree, four Semgrep rulesets, live probes. One high, four medium, three low, two informational.
  13. 2026-09-22, 10:29 UTCM8, M10 and L3 fixed and deployed stale-file guard widened and validated against all 560 public files, script-context payload escaped, encryption tag length pinned.
  14. 2026-09-22, 10:39 UTCH3 fixed, wallet identity pinned and fenced identity values pinned explicitly, boot-time fingerprint added. Recorded fingerprint matches the pre-change value, so no wallet moved.
  15. 2026-09-22, 10:42 UTCM9 fixed, stray web-server config removed from the load path duplicate host warnings eight to zero, all hosts serving.
  16. 2026-09-22, 10:45 UTCContract provenance published all eight addresses confirmed serving verified source, exact creation match recorded for token, escrow and treasury.