Skip to content

Async module script can run before div#progress exists, breaking Chrome #1782

Description

@atnetws

Describe the bug

On the proof-of-work challenge page, the client-side script (main.mjs) can throw an uncaught TypeError when it tries to access the #progress element:

Uncaught (in promise) TypeError: Cannot read properties of null (reading 'style')
    at main.ts:159:12

Line 159 corresponds to:

progress.style.display = "inline-block";

with progress obtained via:

const progress: HTMLDivElement = g("progress") as HTMLDivElement;

Likely root cause

In the generated challenge HTML, the progress div is located after the module script tag in documnet order:

<script id="anubis-main" async type="module" src="/.within.website/x/cmd/anubis/static/js/main.mjs?..."></script>
<script>...bootstrap/retry logic...</script>
<div id="progress" role="progressbar" aria-labelledby="status">...</div>

A <script type="module"> without async behaves like defer and waits for the document to finish parsing. With async set, the module executes as soon as it's fetched, regardless of whether the rest of the document (including progress) has been parsed yet. If main.mjs loads fast enough, the script can run before progress exists in the DOM, causing getElementById("progress") to fail.

This could explain why it's browser-dependent. Chrome's module fetch/execution timing made the race condition reproducible consistently in our setup, while Firefox and Safari don't trigger it.

Steps to reproduce

  1. Run Anubis natively with the reverse proxy on the same host as Anubis.
  2. Load the challenge page in Chrome or Chrome Canary

Expected behavior

It should just work in Chrome. :D

Your operating system and its version.

MacOS 26

Your browser and its version.

Chrome 151.0.7922.72

Additional context

  • Anubis version: v1.26.2 deb install
  • OS: Debian 7 Wheezy. I'm aware this is far past EOL. It's a legacy client server I cannot migrate right now for budget reasons. Aside from this issue, Anubis itself runs completely fine.
  • Reverse proxy: Apache 2.2, mod_proxy, Anubis on 127.0.0.1
  • Affected browser: Chrome 151.0.7922.72 (macOS, arm64) and Chrome Canary, reproducible on every page load/reload
  • Not affected: Firefox and Safari on the same machine/network load the same page without error

Workaround

We worked around this at the reverse-proxy level by stripping the async attribute using mod_substitute

RequestHeader unset Accept-Encoding

<Location "/">
    AddOutputFilterByType SUBSTITUTE text/html
    Substitute "s|<script id=\"anubis-main\" async|<script id=\"anubis-main\"|"
</Location>

This removes the async attribute so the module script behaves like defer and the error no longer occurs.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions