{
  "id": "sample-2026-vault",
  "account_id": "qortrace-sample",
  "account_email": "samples@qortrace.com",
  "name": "DemoVault.sol",
  "project_name": "DemoVault Protocol",
  "contract_address": "0xDEMO1234567890ABCDEF1234567890ABCDEF1234",
  "tier": "standard",
  "language": "solidity",
  "submission_mode": "paste",
  "files": [],
  "source_meta": {
    "is_sample": true
  },
  "status": "delivered",
  "summary": "DemoVault is a simple Ether vault allowing deposits and withdrawals. The contract is intentionally vulnerable, demonstrating classic pitfalls: reentrancy, missing access control, tx.origin usage, and lack of events. It is unsuitable for production deployment.",
  "findings": [
    {
      "id": "e057ab5d-2d80-46df-b2d1-3ff73eac6bd2",
      "status": "open",
      "severity": "critical",
      "title": "Reentrancy vulnerability in withdraw()",
      "description": "The withdraw() function performs an external call before updating balances[msg.sender], allowing an attacker to re-enter and drain funds. A malicious fallback can repeatedly call withdraw() before the balance is decremented.",
      "code_excerpt": "function withdraw(uint256 amount) public {\n    require(balances[msg.sender] >= amount, \"insufficient\");\n    (bool ok, ) = msg.sender.call{value: amount}(\"\");\n    require(ok, \"transfer failed\");\n    balances[msg.sender] -= amount;\n}",
      "location": "DemoVault:withdraw:17-22",
      "recommendation": "Apply the checks-effects-interactions pattern: update balances[msg.sender] before the external call, or use OpenZeppelin's ReentrancyGuard modifier to block reentrant calls."
    },
    {
      "id": "127af40a-04c0-4b31-b6b5-6e869ce7ef26",
      "status": "open",
      "severity": "high",
      "title": "Missing access control on setOwner()",
      "description": "Any caller can invoke setOwner() to replace the owner, effectively taking full control of the contract. No modifier or require statement restricts execution to the current owner.",
      "code_excerpt": "function setOwner(address newOwner) public {\n    owner = newOwner;\n}",
      "location": "DemoVault:setOwner:27-29",
      "recommendation": "Add an onlyOwner modifier (require(msg.sender == owner)) or use OpenZeppelin's Ownable pattern to restrict setOwner() to the current owner only."
    },
    {
      "id": "2c28ce65-fedf-4bff-a2f0-020dce8b6e55",
      "status": "open",
      "severity": "medium",
      "title": "Use of tx.origin for authentication in adminWithdraw()",
      "description": "adminWithdraw() uses tx.origin == owner, which is vulnerable to phishing attacks. A malicious contract can trick the owner into calling it, then the malicious contract calls adminWithdraw() with tx.origin still set to the owner.",
      "code_excerpt": "function adminWithdraw(uint256 amount) public {\n    require(tx.origin == owner, \"not owner\");\n    payable(tx.origin).transfer(amount);\n}",
      "location": "DemoVault:adminWithdraw:34-37",
      "recommendation": "Replace tx.origin with msg.sender for authentication. Use require(msg.sender == owner) to ensure only direct calls from the owner succeed, preventing proxy-attack vectors."
    },
    {
      "id": "337e4364-409e-44d5-ab0c-a364dcc47bfc",
      "status": "open",
      "severity": "low",
      "title": "No event emitted on deposit()",
      "description": "deposit() modifies balances and totalDeposited without emitting an event. Off-chain indexers and front-ends cannot track state changes efficiently, harming transparency and auditability.",
      "code_excerpt": "function deposit() public payable {\n    balances[msg.sender] += msg.value;\n    totalDeposited += msg.value;\n}",
      "location": "DemoVault:deposit:41-44",
      "recommendation": "Declare and emit an event (e.g., event Deposited(address indexed user, uint256 amount)) at the end of deposit() to enable transparent logging of all balance updates."
    },
    {
      "id": "16b28be6-df7c-4a08-841f-fd6607768256",
      "status": "open",
      "severity": "info",
      "title": "Missing NatSpec documentation on totalSupply()",
      "description": "totalSupply() lacks NatSpec comments, reducing code readability and making it harder for developers and automated tools to understand intent and usage.",
      "code_excerpt": "function totalSupply() public view returns (uint256) {\n    return totalDeposited;\n}",
      "location": "DemoVault:totalSupply:48-50",
      "recommendation": "Add NatSpec (/// @notice, @dev, @return) above totalSupply() to document its purpose and clarify that it returns the cumulative deposited Ether, not a token supply."
    }
  ],
  "trust_checks": {
    "access_control": {
      "passed": false,
      "evidence": "setOwner() has no modifier or require check; any caller can reassign ownership."
    },
    "events_emitted": {
      "passed": false,
      "evidence": "deposit() modifies state (balances, totalDeposited) without emitting any event."
    },
    "reentrancy_protection": {
      "passed": false,
      "evidence": "withdraw() calls msg.sender.call{value:...} before updating balances[msg.sender], enabling classic reentrancy."
    },
    "safe_math": {
      "passed": true,
      "evidence": "Solidity ^0.8.0 has built-in overflow/underflow checks; no unchecked arithmetic detected."
    },
    "input_validation": {
      "passed": true,
      "evidence": "withdraw() checks balances[msg.sender] >= amount; deposit() is payable with no unchecked inputs."
    },
    "no_hardcoded_secrets": {
      "passed": true,
      "evidence": "No hardcoded private keys, passwords, or secret constants present in the source."
    },
    "no_tx_origin_for_auth": {
      "passed": false,
      "evidence": "adminWithdraw() uses tx.origin == owner for authentication, susceptible to phishing."
    },
    "error_handling": {
      "passed": true,
      "evidence": "withdraw() and adminWithdraw() use require statements; transfer() reverts on failure by default."
    },
    "documentation_present": {
      "passed": false,
      "evidence": "Only contract-level comment exists; individual functions lack NatSpec (e.g., totalSupply())."
    },
    "tests_referenced": {
      "passed": false,
      "evidence": "No test files or test-case comments are referenced in the supplied source code."
    }
  },
  "security_score": 60,
  "trust_score": 40,
  "model": "claude-sonnet-4-5-20250929",
  "raw_chars": 1822,
  "truncated": false,
  "created_at": "2026-05-04T16:39:55.599155+00:00",
  "started_at": "2026-05-04T16:39:55.599155+00:00",
  "completed_at": "2026-05-04T16:39:55.599155+00:00",
  "delivered_at": "2026-05-04T16:39:55.599155+00:00",
  "is_sample": true,
  "certificate_svg": "/api/audit/sample-2026-vault/certificate.svg",
  "certificate_png": "/api/audit/sample-2026-vault/certificate.png",
  "certificate_html": "/api/audit/sample-2026-vault/certificate.html"
}