TW
Tripwire
Find contracts. Test them. Review real vulns.

Analysis Run

019be3d6-81f6-7356-8bab-3144a73c2765

failed
Chain ID
1
Address
Block Number
Created
Thu, Jan 22, 2026 3:54 AM
Review Summary

What survived the funnel

Open this run to answer one question: is this real attacker-relevant behavior with real impact, or just normal contract plumbing?

Confirmed
0
Validated
0
Source
Verified
Review Backing
Present
Best current signal
Untrusted DELEGATECALL target reachable
crit 0 high 0 proxy context implementation known
Review Checklist
1. Read the findings table first. Ignore raw jobs until the result looks real.
2. Confirm attacker control, not just the existence of a sink.
3. Confirm money or privilege impact.
4. Keep it if the behavior is real. Kill it if this is normal proxy, admin, or upgrade plumbing.

Processing Jobs

Status & attempts
ID Type Status Attempts Heartbeat
40561 artifact.fetch complete 1 / 3 2 months ago
40562 proxy.resolve complete 1 / 3 2 months ago
40563 tool.cast_disasm complete 1 / 3 2 months ago
40564 analysis.bundle complete 1 / 3 2 months ago
40565 capability.graph complete 1 / 3 2 months ago
40566 detector.run complete 1 / 3 2 months ago
40567 validation.fork complete 1 / 3 2 months ago
41239 report.consolidate complete 1 / 6 2 months ago
41240 tool.slither failed 2 / 2 2 months ago

Artifact

Runtime bytecode: available
Creation TX:
Deployer:
Proxy: yes onchain_only
Verified Source: yes
Proxy evidence
{
    "method": "minimal_proxy",
    "status": "onchain_only",
    "implementation": "0x387a294a2b92387cf46714faa537f1f81d50c210",
    "onchain_method": "minimal_proxy"
}

Slither

tool.slither
Status
failed slither failed (exit 1): 'forge clean' running (wd: /tmp/slither-mgy0qvnf) 'forge config --json' running 'forge build --build-info --skip ./test/** ./script/** --force' running (wd: /tmp/slither-mgy0qvnf) 'forge' returned non-zero exit code 1 Error: Encountered invalid solc version in lib/openzeppelin-contracts/contracts/token/common/ERC2981.sol: No solc version exists that matches the version requirement: ^0.8.20 stderr: Encountered invalid solc version in lib/openzeppelin-contracts/contracts/utils/Address.sol: No solc version exists that matches the version requirement: ^0.8.20 Traceback (most recent call last): File "/var/www/tripwire/.venv/bin/slither", line 10, in <module> sys.exit(main()) ~~~~^^ File "/var/www/tripwire/.venv/lib/python3.13/site-packages/slither/__main__.py", line 776, in main main_impl(all_detector_classes=detectors, all_printer_classes=printers) ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/var/www/tripwire/.venv/lib/python3.13/site-packages/slither/__main__.py", line 882, in main_impl ) = process_all(filename, args, detector_classes, printer_classes) ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/var/www/tripwire/.venv/lib/python3.13/site-packages/slither/__main__.py", line 96, in process_all compilations = compile_all(target, **vars(args)) File "/var/www/tripwire/.venv/lib/python3.13/site-packages/crytic_compile/crytic_compile.py", line 722, in compile_all compilations.append(CryticCompile(target, **kwargs)) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^ File "/var/www/tripwire/.venv/lib/python3.13/site-packages/crytic_compile/crytic_compile.py", line 211, in __init__ self._compile(**kwargs) ~~~~~~~~~~~~~^^^^^^^^^^ File "/var/www/tripwire/.venv/lib/python3.13/site-packages/crytic_compile/crytic_compile.py", line 633, in _compile self._platform.compile(self, **kwargs) ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^ File "/var/www/tripwire/.venv/lib/python3.13/site-packages/crytic_compile/platform/foundry.py", line 102, in compile hardhat_like_parsing( ~~~~~~~~~~~~~~~~~~~~^ crytic_compile, str(self._target), build_directory, str(self._project_root) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ) ^ File "/var/www/tripwire/.venv/lib/python3.13/site-packages/crytic_compile/platform/hardhat.py", line 52, in hardhat_like_parsing raise InvalidCompilation(txt) crytic_compile.platform.exceptions.InvalidCompilation: Compilation failed. Can you run build command? /tmp/slither-mgy0qvnf/out/build-info is not a directory.
Attempts
2 / 2
Findings
0
Solc
View stderr
Traceback (most recent call last):
  File "/var/www/tripwire/worker/worker.py", line 198, in main
    payload = dispatch_job(job_type, job_id, job_meta, run_data, config, laravel_client)
  File "/var/www/tripwire/worker/worker.py", line 114, in dispatch_job
    return slither_run.run(job_id, job_meta, run_data, config, log, laravel_client)
           ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/var/www/tripwire/worker/jobs/slither_run.py", line 1084, in run
    stdout_text, stderr_text, result_json = _run_slither(
                                            ~~~~~~~~~~~~^
        workspace,
        ^^^^^^^^^^
    ...<4 lines>...
        target_path=target_path,
        ^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/var/www/tripwire/worker/jobs/slither_run.py", line 958, in _run_slither
    raise RuntimeError(f"slither failed (exit {result.returncode}): {stderr_text or stdout_text}")
RuntimeError: slither failed (exit 1): 'forge clean' running (wd: /tmp/slither-mgy0qvnf)
'forge config --json' running
'forge build --build-info --skip ./test/** ./script/** --force' running (wd: /tmp/slither-mgy0qvnf)
'forge' returned non-zero exit code 1
Error: Encountered invalid solc version in lib/openzeppelin-contracts/contracts/token/common/ERC2981.sol: No solc version exists that matches the version requirement: ^0.8.20
stderr: Encountered invalid solc version in lib/openzeppelin-contracts/contracts/utils/Address.sol: No solc version exists that matches the version requirement: ^0.8.20
Traceback (most recent call last):
  File "/var/www/tripwire/.venv/bin/slither", line 10, in <module>
    sys.exit(main())
             ~~~~^^
  File "/var/www/tripwire/.venv/lib/python3.13/site-packages/slither/__main__.py", line 776, in main
    main_impl(all_detector_classes=detectors, all_printer_classes=printers)
    ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/var/www/tripwire/.venv/lib/python3.13/site-packages/slither/__main__.py", line 882, in main_impl
    ) = process_all(filename, args, detector_classes, printer_classes)
        ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/var/www/tripwire/.venv/lib/python3.13/site-packages/slither/__main__.py", line 96, in process_all
    compilations = compile_all(target, **vars(args))
  File "/var/www/tripwire/.venv/lib/python3.13/site-packages/crytic_compile/crytic_compile.py", line 722, in compile_all
    compilations.append(CryticCompile(target, **kwargs))
                        ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^
  File "/var/www/tripwire/.venv/lib/python3.13/site-packages/crytic_compile/crytic_compile.py", line 211, in __init__
    self._compile(**kwargs)
    ~~~~~~~~~~~~~^^^^^^^^^^
  File "/var/www/tripwire/.venv/lib/python3.13/site-packages/crytic_compile/crytic_compile.py", line 633, in _compile
    self._platform.compile(self, **kwargs)
    ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
  File "/var/www/tripwire/.venv/lib/python3.13/site-packages/crytic_compile/platform/foundry.py", line 102, in compile
    hardhat_like_parsing(
    ~~~~~~~~~~~~~~~~~~~~^
        crytic_compile, str(self._target), build_directory, str(self._project_root)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/var/www/tripwire/.venv/lib/python3.13/site-packages/crytic_compile/platform/hardhat.py", line 52, in hardhat_like_parsing
    raise InvalidCompilation(txt)
crytic_compile.platform.exceptions.InvalidCompilation: Compilation failed. Can you run build command?
/tmp/slither-mgy0qvnf/out/build-info is not a directory.

Codex

report.consolidate
Job Status
complete
Attempts
1 / 6
Findings
1
No findings?
no
Summary
One access-control concern: the initializer caller retains ADMIN_ROLE indefinitely, which may be unexpected if only the configured owner should control admin actions.
View stdout
{"no_findings":false,"summary":"One access-control concern: the initializer caller retains ADMIN_ROLE indefinitely, which may be unexpected if only the configured owner should control admin actions.","findings":[{"severity":"low","category":"access_control","title":"Initializer retains ADMIN_ROLE even when not the owner","exploit_scenario":"If `initialize` is called by a factory or deployer address different from `deploymentConfig.owner`, that caller keeps ADMIN_ROLE permanently. They can still call `updateConfig`, `reserveMint`, and `withdrawFees` after ownership is transferred, enabling unexpected control over pricing/metadata and reserve minting.","recommendation":"Revoke ADMIN_ROLE from `msg.sender` when it is not the intended owner, or pass an explicit admin address in the deployment config and only grant ADMIN_ROLE to that address (optionally enforcing it equals the owner).","evidence":"`initialize` grants ADMIN_ROLE to `msg.sender` and then transfers ownership without revoking ADMIN_ROLE from the initializer.","locations":["contracts/templates/NFTCollection.sol:124","contracts/templates/NFTCollection.sol:131","contracts/templates/NFTCollection.sol:132"]}]}
View stderr
OpenAI Codex v0.77.0 (research preview)
--------
workdir: /tmp/codex-up0_2k2d
model: gpt-5.2-codex
provider: openai
approval: never
sandbox: read-only
reasoning effort: high
reasoning summaries: auto
session id: 019be3df-7abf-7e91-bafa-0fde2e118d91
--------
user
Context: The user requested the proxy at 0x377f2fd104692e592a5259cf75756037ae180fcb.
Audit the implementation at 0x387a294a2b92387cf46714faa537f1f81d50c210.
Findings should refer to the implementation unless explicitly about proxy mechanics.

You are a security auditor. Audit this Solidity codebase for exploitable vulnerabilities.
Focus on: reentrancy, access control, upgradeability/proxy issues, signature validation, oracle manipulation,
accounting/invariant breaks, external call patterns, unchecked return values, and dangerous delegatecall.

Scope rules:
- You MUST fully read `AUDIT_BUNDLE.txt` before concluding. It contains the complete contents of all first-party Solidity sources in this workspace.
- Treat `lib/` and `@openzeppelin/` as third-party dependencies; skip them unless there is a concrete misuse angle in first-party code.
- If there are only dependency files and no first-party sources, set no_findings=true and explain in summary.

Output MUST be valid JSON matching the provided schema.
- If you find nothing meaningful: set no_findings=true and findings=[].
- Otherwise: set no_findings=false and include findings with severity/category/title and file/line locations when possible.

Output constraints (IMPORTANT):
- severity MUST be exactly one of: info, low, medium, high, critical (lowercase).
- category MUST be exactly one of: access_control, reentrancy, math, oracle, upgradeability, auth, other.
Keep summary short and concrete.
mcp startup: no servers

thinking
**Preparing to read audit file**
exec
/bin/bash -lc ls in /tmp/codex-up0_2k2d succeeded in 6ms:
AUDIT_BUNDLE.txt
codex-output-schema.json
contracts
erc721a
@openzeppelin
exec
/bin/bash -lc 'rg --files' in /tmp/codex-up0_2k2d succeeded in 30ms:
codex-output-schema.json
AUDIT_BUNDLE.txt
contracts/templates/NFTCollection.sol
contracts/lib/ITemplate.sol
erc721a/contracts/ERC721A.sol
contracts/lib/Base64.sol
contracts/lib/ERC2981.sol
@openzeppelin/contracts/interfaces/IERC2981.sol
@openzeppelin/contracts/access/AccessControl.sol
@openzeppelin/contracts/access/IAccessControl.sol
@openzeppelin/contracts/utils/Strings.sol
@openzeppelin/contracts/utils/cryptography/MerkleProof.sol
@openzeppelin/contracts/utils/Context.sol
@openzeppelin/contracts/utils/Address.sol
@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol
@openzeppelin/contracts/token/ERC721/IERC721.sol
@openzeppelin/contracts/proxy/utils/Initializable.sol
@openzeppelin/contracts/utils/introspection/ERC165.sol
@openzeppelin/contracts/utils/introspection/IERC165.sol
@openzeppelin/contracts/token/common/ERC2981.sol

thinking
**Planning full read of AUDIT_BUNDLE.txt**
exec
/bin/bash -lc 'cat AUDIT_BUNDLE.txt' in /tmp/codex-up0_2k2d succeeded in 7ms:
This file concatenates the full contents of first-party Solidity sources for auditing.
Do not treat dependency code under lib/ or @openzeppelin/ as first-party.

================================================================================
FILE: contracts/lib/Base64.sol
================================================================================
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/// @title Base64
/// @notice Provides a function for encoding some bytes in base64
/// @author Brecht Devos <brecht@loopring.org>
library Base64 {
    bytes internal constant _TABLE =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

    /// @notice Encodes some bytes to the base64 representation
    function encode(bytes memory data) internal pure returns (string memory) {
        uint256 len = data.length;
        if (len == 0) return "";


thinking
**Clarifying AUDIT_BUNDLE composition**

The AUDIT_BUNDLE includes full contents of first-party Solidity sources, which covers contracts under contracts/lib like Base64 and ERC2981, but it also includes ERC721A from erc721a/contracts, which is ambiguous since erc721a isn't explicitly marked as third-party or first-party; given the instructions treat only lib/ and @openzeppelin as third-party, ERC721A might be considered first-party here despite being a dependency.
thinking
**Verifying NFTCollection and ERC721A inclusion**

The AUDIT_BUNDLE truncation after NFTCollection’s paymentProvided modifier hints at appended ERC721A code, but NFTCollection itself likely ends there. To clarify if ERC721A is appended or truly included, I’ll review the actual NFTCollection source next.
exec
/bin/bash -lc "sed -n '1,240p' contracts/templates/NFTCollection.sol" in /tmp/codex-up0_2k2d succeeded in 7ms:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "erc721a/contracts/ERC721A.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";

import "../lib/ERC2981.sol";
import "../lib/Base64.sol";
import "../lib/ITemplate.sol";

contract NFTCollection is
    ERC721A,
    ERC2981,
    AccessControl,
    Initializable,
    ITemplate
exec
/bin/bash -lc "sed -n '240,520p' contracts/templates/NFTCollection.sol" in /tmp/codex-up0_2k2d succeeded in 8ms:
     *****************/

    /// Mint a token from the reserve
    function reserveMint(address to, uint256 amount)
        external
        onlyRole(ADMIN_ROLE)
    {
        require(amount <= reserveRemaining, "Not enough reserved");

        reserveRemaining -= amount;
        _mintTokens(to, amount);
    }

    /// Get full contract information
    /// @dev Convenience helper
    function getInfo() external view returns (ContractInfo memory info) {
        info.version = VERSION;
        info.deploymentConfig = _deploymentConfig;
        info.runtimeConfig = _runtimeConfig;
    }
exec
/bin/bash -lc "sed -n '520,920p' contracts/templates/NFTCollection.sol" in /tmp/codex-up0_2k2d succeeded in 5ms:
        return _runtimeConfig.presaleMerkleRoot;
    }

    function baseURI() public view returns (string memory) {
        return _runtimeConfig.baseURI;
    }

    function metadataUpdatable() public view returns (bool) {
        return _runtimeConfig.metadataUpdatable;
    }

    function prerevealTokenURI() public view returns (string memory) {
        return _runtimeConfig.prerevealTokenURI;
    }
}


Reconnecting... 1/5




thinking
**Analyzing role assignments during initialization**

The initialization grants ADMIN_ROLE to msg.sender, then transfers ownership to deploymentConfig.owner (initially zero), which causes revoking of roles from the zero address without affecting msg.sender's ADMIN_ROLE. Afterward, the new owner receives both ADMIN_ROLE and DEFAULT_ADMIN_ROLE. This confirms the access control setup correctly delegates roles despite the zero-address intermediate state.
thinking
**Identifying access control nuances and potential config risks**

The deployer (initializer) retains ADMIN_ROLE alongside the owner, allowing persistent admin privileges that might be intended for factory use but could pose risks if unintended. Also, runtimeConfig lacks validation during initialization, enabling a malicious initializer to set invalid values before owner control begins, potentially locking configurations via irreversible freeze flags.
thinking
**Confirming no critical vulnerabilities but noting minor edge cases**

Reentrancy is effectively prevented by ERC721A’s guard during minting, and supply accounting correctly enforces max supply including reserved tokens. Minor issues include initializer not validating runtime config allowing improper initial settings, potential admin role retention by deployer, and that transferAdminRights can burn admin roles by assigning to zero address. Overall, no exploitable vulnerabilities stand out.
thinking
**Confirming initializer’s admin retention risk and minor config concerns**

The initializer keeps ADMIN_ROLE after ownership transfer, allowing persistent admin privileges that may be unintended if deployer differs from owner. Runtime config lacks validation in initialize, enabling invalid initial settings like excessive royalties or frozen metadata. No critical vulnerabilities found beyond these minor issues.
exec
/bin/bash -lc "sed -n '1,200p' erc721a/contracts/ERC721A.sol" in /tmp/codex-up0_2k2d succeeded in 13ms:
// SPDX-License-Identifier: MIT
// Creator: Chiru Labs

pragma solidity ^0.8.4;

import '@openzeppelin/contracts/token/ERC721/IERC721.sol';
import '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol';
import '@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol';
import '@openzeppelin/contracts/utils/Address.sol';
import '@openzeppelin/contracts/utils/Context.sol';
import '@openzeppelin/contracts/utils/Strings.sol';
import '@openzeppelin/contracts/utils/introspection/ERC165.sol';

error ApprovalCallerNotOwnerNorApproved();
error ApprovalQueryForNonexistentToken();
error ApproveToCaller();
error ApprovalToCurrentOwner();
error BalanceQueryForZeroAddress();
error MintToZeroAddress();
error MintZeroQuantity();


thinking
**Assessing ERC721A reentrancy protection**

I'm verifying that ERC721A's _safeMint and _safeTransferFrom update state before external calls to contracts, using a reentrancy guard in _safeMint but none in _transfer; this aligns with standard patterns where reentrancy is possible but state remains consistent, suggesting no immediate vulnerability.
thinking
**Identifying low-severity admin retention risk**

I found that the initializer retains the ADMIN_ROLE after deployment, allowing it to update config, mint reserved tokens, and withdraw fees, which poses an access control risk if the initializer is not trusted; also, lack of runtimeConfig validation in initialize may allow frozen or inconsistent config states but is mitigated if admin is trusted.
exec
/bin/bash -lc "nl -ba contracts/templates/NFTCollection.sol | sed -n '120,220p'" in /tmp/codex-up0_2k2d succeeded in 6ms:
   120	        _preventInitialization = true;
   121	    }
   122	
   123	    /// Contract initializer
   124	    function initialize(
   125	        DeploymentConfig memory deploymentConfig,
   126	        RuntimeConfig memory runtimeConfig
   127	    ) public initializer {
   128	        require(!_preventInitialization, "Cannot be initialized");
   129	        _validateDeploymentConfig(deploymentConfig);
   130	
   131	        _grantRole(ADMIN_ROLE, msg.sender);
   132	        _transferOwnership(deploymentConfig.owner);
   133	
   134	        _deploymentConfig = deploymentConfig;
   135	        _runtimeConfig = runtimeConfig;
   136	
   137	        reserveRemaining = deploymentConfig.reservedSupply;
   138	    }
   139	


thinking
**Analyzing access control and config validation**

The initializer keeps the admin role, allowing fee withdrawal but only to a fixed treasury address set at deployment. Malicious initializer could set treasuryAddress to self initially, but likely also sets owner to self for full control. RuntimeConfig’s lack of validation and freezing can cause misconfigurations but may not be a clear vulnerability since owner/admin roles control key changes. Considering reporting a low-severity finding on persistent admin role and potential misconfiguration.
thinking
**Concluding low-severity finding on initializer admin persistence**

Decided to report a low-severity access control finding that the initializer retains the admin role post-initialization, allowing lasting control over config and minting functions despite ownership transfer. Recommendation will advise revoking admin rights from the initializer if it differs from the owner. Preparing detailed finding with evidence and line references next.
codex
{"no_findings":false,"summary":"One access-control concern: the initializer caller retains ADMIN_ROLE indefinitely, which may be unexpected if only the configured owner should control admin actions.","findings":[{"severity":"low","category":"access_control","title":"Initializer retains ADMIN_ROLE even when not the owner","exploit_scenario":"If `initialize` is called by a factory or deployer address different from `deploymentConfig.owner`, that caller keeps ADMIN_ROLE permanently. They can still call `updateConfig`, `reserveMint`, and `withdrawFees` after ownership is transferred, enabling unexpected control over pricing/metadata and reserve minting.","recommendation":"Revoke ADMIN_ROLE from `msg.sender` when it is not the intended owner, or pass an explicit admin address in the deployment config and only grant ADMIN_ROLE to that address (optionally enforcing it equals the owner).","evidence":"`initialize` grants ADMIN_ROLE to `msg.sender` and then transfers ownership without revoking ADMIN_ROLE from the initializer.","locations":["contracts/templates/NFTCollection.sol:124","contracts/templates/NFTCollection.sol:131","contracts/templates/NFTCollection.sol:132"]}]}
tokens used
51,691
Metadata
{
    "codex": {
        "ran_at": "2026-01-22T04:10:07.020949+00:00",
        "returncode": 0
    },
    "project": {
        "mode": "source",
        "file_count": 19,
        "import_count": 27,
        "first_party_contracts_count": 5
    },
    "summary": "One access-control concern: the initializer caller retains ADMIN_ROLE indefinitely, which may be unexpected if only the configured owner should control admin actions.",
    "raw_final": {
        "summary": "One access-control concern: the initializer caller retains ADMIN_ROLE indefinitely, which may be unexpected if only the configured owner should control admin actions.",
        "findings": [
            {
                "title": "Initializer retains ADMIN_ROLE even when not the owner",
                "category": "access_control",
                "evidence": "`initialize` grants ADMIN_ROLE to `msg.sender` and then transfers ownership without revoking ADMIN_ROLE from the initializer.",
                "severity": "low",
                "locations": [
                    "contracts/templates/NFTCollection.sol:124",
                    "contracts/templates/NFTCollection.sol:131",
                    "contracts/templates/NFTCollection.sol:132"
                ],
                "recommendation": "Revoke ADMIN_ROLE from `msg.sender` when it is not the intended owner, or pass an explicit admin address in the deployment config and only grant ADMIN_ROLE to that address (optionally enforcing it equals the owner).",
                "exploit_scenario": "If `initialize` is called by a factory or deployer address different from `deploymentConfig.owner`, that caller keeps ADMIN_ROLE permanently. They can still call `updateConfig`, `reserveMint`, and `withdrawFees` after ownership is transferred, enabling unexpected control over pricing/metadata and reserve minting."
            }
        ],
        "no_findings": false
    },
    "no_findings": false,
    "proxy_context": {
        "proxy_run_id": "019be3d6-81f6-7356-8bab-3144a73c2765",
        "proxy_address": "0x377f2fd104692e592a5259cf75756037ae180fcb",
        "implementation_run_id": "019be3d6-c7d2-72ca-99c8-428dc13e465f",
        "implementation_address": "0x387a294a2b92387cf46714faa537f1f81d50c210"
    },
    "schema_version": 1
}

Opcode Heuristics

tool.cast_disasm
Delegatecall
1
Selfdestruct
0
CREATE2
0
CALL-family (heavy)
0
EXT*/BALANCE
0
Total opcodes
24
Flags
delegatecall_present
View cast disassembly output
00000000: CALLDATASIZE
00000001: RETURNDATASIZE
00000002: RETURNDATASIZE
00000003: CALLDATACOPY
00000004: RETURNDATASIZE
00000005: RETURNDATASIZE
00000006: RETURNDATASIZE
00000007: CALLDATASIZE
00000008: RETURNDATASIZE
00000009: PUSH20 0x387a294a2b92387cf46714faa537f1f81d50c210
0000001e: GAS
0000001f: DELEGATECALL
00000020: RETURNDATASIZE
00000021: DUP3
00000022: DUP1
00000023: RETURNDATACOPY
00000024: SWAP1
00000025: RETURNDATASIZE
00000026: SWAP2
00000027: PUSH1 0x2b
00000029: JUMPI
0000002a: REVERT
0000002b: JUMPDEST
0000002c: RETURN

Findings

validated = confirmed for call sinks; sink observed for others
Severity Category Tool Title Confidence Validated Confirmed Details
critical upgradeability detector Untrusted DELEGATECALL target reachable 70 no no
view
trace_id: 0x6c97bb3206b6dfaeef100f6d60330a448769069430e1f99a858fe81e4b3a77d9
call_targets: 0x387a294a2b92387cf46714faa537f1f81d50c210
target_varies: no
classification: constant_target
validation_json
{
    "sink": "DELEGATECALL",
    "errors": 0,
    "status": "sink_reached",
    "attempts": 1,
    "trace_id": "0x6c97bb3206b6dfaeef100f6d60330a448769069430e1f99a858fe81e4b3a77d9",
    "confirmed": false,
    "trace_mode": "callTracer",
    "call_targets": [
        "0x387a294a2b92387cf46714faa537f1f81d50c210"
    ],
    "matched_probe": null,
    "target_varies": false,
    "classification": "constant_target"
}
witness_json
{
    "notes": "heuristic witness; entrypoints are best-effort selectors with zeroed calldata",
    "constraints": [],
    "entrypoints": [
        {
            "calldata": "0x",
            "selector": "fallback",
            "calldata_variants": [
                "0x"
            ]
        }
    ]
}
evidence_json
{
    "examples": [
        {
            "pc": 31,
            "opcode": "DELEGATECALL",
            "returndata_checked": null,
            "value_expression_category": "n/a",
            "target_expression_category": "computed"
        }
    ],
    "guards_detected": [],
    "delegatecall_count": 1,
    "untrusted_delegatecall_count": 1
}
medium upgradeability cast DELEGATECALL present 80 no
view
evidence_json
{
    "notes": "delegatecall enables execution in caller context; common in proxies/upgradeable patterns",
    "delegatecall_count": 1
}
low access_control codex Initializer retains ADMIN_ROLE even when not the owner 65 no
view
evidence_json
{
    "evidence": "`initialize` grants ADMIN_ROLE to `msg.sender` and then transfers ownership without revoking ADMIN_ROLE from the initializer.",
    "locations": [
        "contracts/templates/NFTCollection.sol:124",
        "contracts/templates/NFTCollection.sol:131",
        "contracts/templates/NFTCollection.sol:132"
    ],
    "recommendation": "Revoke ADMIN_ROLE from `msg.sender` when it is not the intended owner, or pass an explicit admin address in the deployment config and only grant ADMIN_ROLE to that address (optionally enforcing it equals the owner).",
    "exploit_scenario": "If `initialize` is called by a factory or deployer address different from `deploymentConfig.owner`, that caller keeps ADMIN_ROLE permanently. They can still call `updateConfig`, `reserveMint`, and `withdrawFees` after ownership is transferred, enabling unexpected control over pricing/metadata and reserve minting."
}