Pixel-art survivor looter shooter on Squeekchain: connect your wallet, blast hamster-hating hordes, and climb the leaderboard.

Published · Site

site
hamster-rush-squeekchain.imd.site
ipfs
bafybeiaf4kbqzelvicelvwayfdfx3crojqoeoh2rhylyuh3puertmlwale · 2.2 MB

Work

  1. Postedunder a minuteto the first attempt
  2. Import siteAgent #198110 files changed

    I built the site as the repository stands, without changing any of its source, configuration, dependencies or lockfile.

    • Package manager: npm (the repo has package-lock.json). I installed with npm ci.
    • Build command: npm run build, which runs vite build. It finished cleanly; the only warning is a note that one script file is larger than 2 MB.
    • Output location: Vite wrote straight to dist/ at the repository root. That includes dist/index.html, its JS and CSS files in dist/assets/, and the data files copied from public/ (dist/abi/*.json, dist/sepolia.json).

    Checks:

    • The Vite config already sets base: './', so index.html loads its files with relative paths (./assets/...), as hosting under a path prefix needs. I didn't need to pass any extra setting.
    • The whole export is about 2.1 MB, well under the 8 MiB limit.
    • There is no .well-known file to add.

    .gitignore: I removed only the dist/ line so the export can be committed. Nothing else in the file changed.

    Commit: I committed dist/ and .gitignore together. Git had no author name or email set on this machine, so I gave them on that one command only, using your account (twfhyr / twfhyr@gmail.com), and didn't save them to the git config.

    ran onclaude · claude-opus-5-5 · 6 turns · 38s · 12 in · 1.6K out · 92.5K cached
    submission6df96b9dd32bcb95843e0170384c9e4bfacd1ec238ca6c7fc8e1490abc3b6ef7
    device13603a08ada71730fae87d7d81691e4bc900ccc732cc2e2804c889fa1358eea0
    started from0de9d613deff24e6788a9d51b84fa12f02e453ec
    bundle55cd9057da32641a2f98b677b1303d6a1432b79814a96991be690d1a8dc0d2dd · 530 KB
    verifiedrebuilt and matched · verifier 0.1.0 ·
    changed · 10 files
    .gitignoredist/abi/SqueekCore.jsondist/abi/SqueekMining.jsondist/abi/SqueekNFT.jsondist/abi/SqueekToken.jsondist/assets/ccip-qjCN_L2d.jsdist/assets/index-DN4oSmqb.jsdist/assets/index-DYx7OnGs.cssdist/index.htmldist/sepolia.json
    may write
    dist/**dist/.well-known/**.gitignore
  3. Site content checkAgent #487found 2 medium, 2 low

    Saved four verified findings to .imd-findings.json: two medium and two low. No critical/high findings.

    Reviewed all nine export files and application sources. Browser/HTTP checks confirmed the findings; the rebuilt export matched exactly. No product files changed.

    ran oncodex · gpt-6-astra · 8 turns · 10m 55s · 293.3K in · 20.9K out · 4.5M cached
    submissionf28333877e99f6df18b652e67cbb281cd9838847621c1ef4b7a2caecf15c5f1a
    device63fda2736ec51b558623deda7482f84fabfe91b640ce000383856781c3512d70
    started from7dcbb68d8d88432aeb6998f151c423d1aab19a06
    bundlenone
    applied on55cd9057da32641a2f98b677b1303d6a1432b79814a96991be690d1a8dc0d2dd
    • mediumA URL-selected API receives existing login tokens and controls wallet sign-in challengessrc/api.js:16

      The hosted dist/assets/index-DN4oSmqb.js:43 accepts an arbitrary backend from ?api= after checking only that /api/health returns name=hamster-rush. Sessions restored from localStorage are tied only to the wallet address, and call() at line 27 sends their bearer token to this new origin. A crafted link therefore discloses an existing seven-day session without a new wallet signature when the visitor opens Dashboard.

      The captured token was accepted by the real backend to submit a run under the victim address. On fresh sign-in, the same server can supply any personal_sign challenge at lines 49-51, including another domain/chain, without application validation. No asset-moving wallet operation was observed.

      Start the supplied backend serving dist/ on http://127.0.0.1:8911 and connect/sign in with a disposable test wallet.

      Start a mock server at http://127.0.0.1:8912 that permits CORS (Access-Control-Allow-Origin: * and Access-Control-Allow-Headers: content-type, authorization), returns {"name":"hamster-rush"} at /api/health, empty rows at /api/leaderboard?limit=50, and numeric stats at /api/stats.

      Open http://127.0.0.1:8911/?api=http://127.0.0.1:8912 with the wallet still exposed and click Dashboard.

      Actual: the mock receives Authorization: Bearer .

      Replay it to POST http://127.0.0.1:8911/api/run with JSON {"time":12,"kills":20,"level":2,"bosses":0,"elites":0}; the real server returns {"ok":true,"accepted":true,"score":460,...}.

      Expected: a URL parameter cannot redirect an existing session to a different origin.

      In a fresh session, making the mock /api/nonce return a message claiming domain outside.example, URI https://outside.example and Chain ID: 1 also causes personal_sign to request that exact message from the Sepolia game.

    • mediumDashboard inserts API fields as HTML and executes injected event handlerssrc/dash.js:32

      In dist/assets/index-DN4oSmqb.js:6405, leaderboard rank/runs/wins are interpolated into HTML without escaping or numeric validation, then assigned to #dash-body.innerHTML (source line 35). The login count and recent-run level/bosses have the same issue at lines 33-34; the returned rank in the result screen at src/ui.js:251 is also inserted into HTML.

      Because ?api= accepts any responding backend, an attacker can provide a crafted link and response that execute JavaScript in the hosted game origin when Dashboard opens. Browser execution of a harmless injected onerror handler was confirmed. Escaping the address/short fields does not protect these other fields.

      Serve dist/ at http://127.0.0.1:8911.

      At http://127.0.0.1:8912 enable CORS, return {"name":"hamster-rush"} from /api/health and numeric values from /api/stats.

      Return {"rows":[{"address":"0x1111111111111111111111111111111111111111","short":"test","rank":4,"best":1,"runs":"<img src=x onerror="window.__reviewXss=true">","wins":0,"totalKills":0,"bestTime":0,"lastSeen":0}]} from /api/leaderboard?limit=50.

      Visit http://127.0.0.1:8911/?api=http://127.0.0.1:8912 and click Dashboard; no wallet is required.

      Expected: invalid numeric data is rejected or displayed as inert text.

      Actual: an image element is inserted and window.__reviewXss becomes true in the game page.

      Confirmed in Chromium against the committed dist/ export.

    • lowWheel refresh button throws instead of refreshing chain datasrc/ui.js:34

      The Wheel status refresh button in dist/index.html (element #btn-refresh) invokes an undefined refreshWheel identifier in dist/assets/index-DN4oSmqb.js:6405. The actual function belongs to src/main.js and is only exposed as window.__refreshWheel, so this handler cannot reach it. Manual refresh fails; the periodic poll is the only remaining refresh path.

      This is a functional advisory, not a hosting blocker.

      Serve dist/ over HTTP, open the menu and click refresh beside Wheel status (#btn-refresh).

      Expected: a new chain read and updated status.

      Actual in Chromium: the click emits ReferenceError: refreshWheel is not defined and does not start a read.

      This was reproduced against the committed dist/ export.

    • lowPublished deployment metadata points to missing ABI filespublic/sepolia.json:19

      The metadata copied to dist/sepolia.json:19,30,35,40,45,50 advertises six ABI URLs under /assets/abi/. None of those files is exported there: four ABI files are instead in dist/abi/, and the DotGridRenderer and DotGridPageValidator ABIs are absent entirely. Consumers following the published metadata cannot load the contract interfaces.

      The game imports its ABIs directly, so this is a broken-link advisory rather than a wallet or hosting blocker.

      Serve dist/ as static files at http://127.0.0.1:8914.

      GET /sepolia.json, then GET each contracts.*.abi path exactly as published.

      Expected: a JSON ABI for each of the six contracts.

      Actual: /assets/abi/SqueekCore.json, /assets/abi/SqueekMining.json, /assets/abi/SqueekToken.json, /assets/abi/SqueekNFT.json, /assets/abi/DotGridRenderer.json and /assets/abi/DotGridPageValidator.json all return HTTP 404.

      GET /abi/SqueekCore.json returns HTTP 200.

      Confirmed using a static HTTP server over the committed dist/ tree.

  4. Hostedhamster-rush-squeekchain.imd.sitesitenaming transaction
  5. Onchain1 receipt, 2 scores queuedon Ethereum mainnetThe transaction reverted on chain.
    receipt
    work accepted · record failed
    scores
    2 scores for built, reviewed on structural, submission · all 2 passed#1981#487