Skip to content

fix(security): address 4 High findings from 2026-05-19 audit#125

Closed
Taure wants to merge 4 commits into
mainfrom
security/highs-2026-05-19
Closed

fix(security): address 4 High findings from 2026-05-19 audit#125
Taure wants to merge 4 commits into
mainfrom
security/highs-2026-05-19

Conversation

@Taure

@Taure Taure commented May 19, 2026

Copy link
Copy Markdown
Contributor

Closes the four High findings from the 2026-05-19 security audit.

Summary

  • H1 — WS chat.join / chat.send now route through asobi_chat_acl:authorized/2, the same predicate the HTTP history endpoint already uses. Without this, any authenticated player could silently join dm:<alice>:<bob> and eavesdrop on every DM between them.
  • H2 — New asobi_body_cap_plugin rejects HTTP bodies > 1 MiB and chunked requests without content-length, before nova_request_plugin buffers them into the BEAM heap. Per-route caps (e.g. 256 KB on saves) still apply on top.
  • H3asobi_world_lobby:list_worlds_cached/0,1 caches world.list results for 500 ms via an ETS table owned by asobi_world_lobby_server. Stops a 60 msg/sec WS flood from fanning out 60k synchronous get_info calls per second against running worlds. find_or_create_unsafe stays uncached.
  • H4 — Bump nova to 0.14.3 and add explicit cowboy override so cowlib resolves to 2.16.1. rebar3 audit now reports 1 vuln (LOW) down from 3 (with a HIGH on cowlib 2.16.0). The remaining LOW is pending an upstream cowlib release.

Test plan

  • rebar3 eunit — 274 tests, 0 failures (includes 11 new tests across asobi_chat_acl_tests, asobi_body_cap_plugin_tests, asobi_world_lobby_cache_tests)
  • rebar3 fmt --check clean
  • rebar3 xref clean
  • rebar3 dialyzer clean
  • elp eqwalize-all — 0 errors (down from 2 pre-existing in asobi_world_lobby_ws_SUITE, also fixed)
  • elp lint — no new warnings
  • rebar3 audit — 3 vulns → 1 (LOW) remaining
  • CT suite (requires Docker Postgres; runs in CI)

What is NOT in this PR

The audit's 10 Medium and 6 Low findings are deferred to a follow-up. Mediums worth flagging:

  • M1 (hash auth-cache key)
  • M3 (player metadata size cap)
  • M5 (put_storage size cap)
  • M6 (TLS pinning on Steam/Apple/Google httpc)
  • M4 ban-column story (decision needed: implement or remove)

Taure added 4 commits May 19, 2026 22:51
H1: WebSocket chat.join and chat.send now run asobi_chat_acl:authorized/2
before joining the pg group or forwarding the message. Without this check
any authenticated player could join dm:<alice>:<bob> via the WS path (the
HTTP /api/v1/chat history endpoint already enforced membership) and read
every subsequent DM between the two parties.

H2: New asobi_body_cap_plugin caps HTTP request body size to 1 MiB and
rejects chunked POSTs without content-length. Runs before
nova_request_plugin so oversized bodies are never buffered into BEAM heap.
Per-route caps (256 KB on saves, etc) remain on top of this floor.

H3: list_worlds_cached/0,1 backs WS world.list and the HTTP index route
with a 500 ms TTL cache owned by asobi_world_lobby_server. A 60 msg/sec
WS flood of world.list against 1000 worlds previously fanned out to 60k
synchronous get_info calls per second; the cache absorbs the fan-out
without breaking find_or_create_unsafe (which stays uncached).

H4: Bump nova to 0.14.3 and add explicit override on the cowboy package
so cowlib resolves to 2.16.1, clearing the rebar3 audit HIGH advisory
against 2.16.0. One LOW cowlib advisory remains pending an upstream
cowlib release.

Audit doc: docs/security_audit_2026_05_19.md.
GHSA-g2wm-735q-3f56 is a LOW-severity advisory against
cow_cookie:cookie/1 (cowlib) with no upstream patch on any released
cowlib version, and no patch on ninenines/cowlib master (only a doc
update). asobi never calls cow_cookie:cookie/1 — only setcookie via
cowboy_req — so the advisory does not apply to this codebase. Track
in docs/security_audit_2026_05_19.md.

Requires Taure/erlang-ci#62 (audit-ignores input). Temporarily pinned
to the feature branch SHA; Dependabot will repin to main after merge.
@Taure

Taure commented Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #167. This branch was 8 weeks stale and its H4 (nova/cowlib) changes would now downgrade cowlib — main already carries cowlib 2.17.1 (newer than this PR's 2.16.1 target). #167 re-lands H1/H2/H3 on current main, drops the redundant H4, and fixes three defects a security review found in the original fixes (unbounded cache key, {break}{stop} reject crash, public cache table). Closing in favour of #167.

@Taure Taure closed this Jul 15, 2026
@Taure
Taure deleted the security/highs-2026-05-19 branch July 15, 2026 04:27
Taure added a commit that referenced this pull request Jul 15, 2026
…125) (#167)

* fix(security): address H1-H3 High findings from 2026-05-19 audit

H1 - route WS chat.join/chat.send through asobi_chat_acl:authorized/2,
the shared DM/world/group predicate the HTTP history endpoint already
uses. Without it any authenticated player could join dm:<a>:<b> and
eavesdrop. ACL logic extracted from asobi_chat_controller into
asobi_chat_acl and consumed by both paths.

H2 - asobi_body_cap_plugin rejects HTTP bodies > 1 MiB and chunked
requests without content-length before nova_request_plugin buffers
them into the heap. Wired as the first pre_request plugin.

H3 - asobi_world_lobby:list_worlds_cached/0,1 caches world.list for
500ms via an ETS table owned by asobi_world_lobby_server, stopping a
WS flood from fanning out synchronous get_info calls. find_or_create
stays uncached.

H4 (nova/cowlib bump) already landed on main independently - main is
on cowlib 2.17.1, newer than this audit's 2.16.1 target, so the H4
rebar changes are intentionally dropped.

* fix(security): harden H2/H3 per guardian + security review

Review of the H1-H3 re-land (asobi-architecture-guardian,
beam-security-reviewer, erlang-code-reviewer) surfaced two defects in the
fixes themselves plus test gaps:

H3 (High) - the cache was keyed on the raw, attacker-controlled filter
map. `mode` is unbounded, so a client cycling distinct modes forced a
miss on every request (defeating the cache) and grew the ETS table
without bound - a new memory-DoS inside a DoS mitigation. Now keyed on
the has_capacity boolean only (<=2 rows); mode is applied in-memory on
the cached list. Also bound `mode` to 64 bytes in the HTTP controller to
match the WS path.

H3 (Low, defence-in-depth) - cache table was `public`; any in-node
process could poison the shared world list. Now `protected` and written
only by asobi_world_lobby_server via a new cache_worlds/3 cast; callers
still read directly.

H2 (Medium) - reject/4 returned {break,...}, which nova maps to a
2-tuple {ok,Req} that cowboy's stream handler has no clause for ->
case_clause crash of the request process on every 411/413 (log/CPU
amplification). Now returns {stop,...}, which cowboy handles cleanly.

Tests: cache tests rewritten for the bounded-key scheme incl. a
distinct-modes-do-not-grow-table assertion; body-cap tests assert {stop};
new asobi_chat_ws_SUITE covers the H1 WS wiring end-to-end (third-party
dm join/send rejected, member join allowed).

Fixed a Unicode 'x' in a doc comment (ASCII only).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant