<feed xmlns='http://www.w3.org/2005/Atom'>
<title>webao, branch master</title>
<subtitle>WebAO fork</subtitle>
<link rel='alternate' type='text/html' href='https://git.sof.beauty/webao/'/>
<entry>
<title>Self-host fonts instead of fetching from Google</title>
<updated>2026-06-06T04:28:47+00:00</updated>
<author>
<name>Osmium Sorcerer</name>
<email>os@sof.beauty</email>
</author>
<published>2026-06-06T04:28:47+00:00</published>
<link rel='alternate' type='text/html' href='https://git.sof.beauty/webao/commit/?id=5b0c52bbf0d1be3b7c0d6cd924bfb8ee7f459442'/>
<id>5b0c52bbf0d1be3b7c0d6cd924bfb8ee7f459442</id>
<content type='text'>
Third step, `font-src 'self'` is now possible.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Third step, `font-src 'self'` is now possible.
</pre>
</div>
</content>
</entry>
<entry>
<title>Remove asset URL fallback to attorneyoffline.de</title>
<updated>2026-06-06T03:09:27+00:00</updated>
<author>
<name>Osmium Sorcerer</name>
<email>os@sof.beauty</email>
</author>
<published>2026-06-06T02:32:37+00:00</published>
<link rel='alternate' type='text/html' href='https://git.sof.beauty/webao/commit/?id=0523a57ebad8065c844a02da7db9db70cac290d5'/>
<id>0523a57ebad8065c844a02da7db9db70cac290d5</id>
<content type='text'>
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
</pre>
</div>
</content>
</entry>
<entry>
<title>Replace addLinks with a stub implementation</title>
<updated>2026-06-06T03:09:27+00:00</updated>
<author>
<name>Osmium Sorcerer</name>
<email>os@sof.beauty</email>
</author>
<published>2026-06-06T02:31:14+00:00</published>
<link rel='alternate' type='text/html' href='https://git.sof.beauty/webao/commit/?id=8fdcb0fb1c6be75186f553f439604414d6bcf12f'/>
<id>8fdcb0fb1c6be75186f553f439604414d6bcf12f</id>
<content type='text'>
Turn it into the identity function because I completely messed up the
URL highlighting due to replacing the OOC chat lines with plain text
rather than them getting appended to the inner HTML.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Turn it into the identity function because I completely messed up the
URL highlighting due to replacing the OOC chat lines with plain text
rather than them getting appended to the inner HTML.
</pre>
</div>
</content>
</entry>
<entry>
<title>Remove commented-out testModcall button</title>
<updated>2026-06-06T03:09:27+00:00</updated>
<author>
<name>Osmium Sorcerer</name>
<email>os@sof.beauty</email>
</author>
<published>2026-06-06T02:31:00+00:00</published>
<link rel='alternate' type='text/html' href='https://git.sof.beauty/webao/commit/?id=88fc8a3412c628263de6cdbbf8159f9b70c0b6bc'/>
<id>88fc8a3412c628263de6cdbbf8159f9b70c0b6bc</id>
<content type='text'>
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
</pre>
</div>
</content>
</entry>
<entry>
<title>CSP hardening: remove inline styles</title>
<updated>2026-06-06T03:09:27+00:00</updated>
<author>
<name>Osmium Sorcerer</name>
<email>os@sof.beauty</email>
</author>
<published>2026-06-06T02:27:32+00:00</published>
<link rel='alternate' type='text/html' href='https://git.sof.beauty/webao/commit/?id=8bf3cae6ac89de9569a7ec629594954804a2b55a'/>
<id>8bf3cae6ac89de9569a7ec629594954804a2b55a</id>
<content type='text'>
Similar to removal of inline scripts, everything was taken out into the
CSS files, with the same styles applied there directly. This lets us
use `script-src 'self'` in the CSP.

Additionally, serve Golden Layout CSS locally to avoid third-party
connection.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Similar to removal of inline scripts, everything was taken out into the
CSS files, with the same styles applied there directly. This lets us
use `script-src 'self'` in the CSP.

Additionally, serve Golden Layout CSS locally to avoid third-party
connection.
</pre>
</div>
</content>
</entry>
<entry>
<title>CSP hardening: remove inline scripts</title>
<updated>2026-06-06T03:09:27+00:00</updated>
<author>
<name>Osmium Sorcerer</name>
<email>os@sof.beauty</email>
</author>
<published>2026-06-06T02:07:05+00:00</published>
<link rel='alternate' type='text/html' href='https://git.sof.beauty/webao/commit/?id=e0ce108e0806d18353ad85125b2b5f1b1c67e07d'/>
<id>e0ce108e0806d18353ad85125b2b5f1b1c67e07d</id>
<content type='text'>
The next layer after input validaton to achive the paranoid levels of
security. Remove all event handlers inside HTML attributes and add them
in TS for each element, allowing `script-src 'self'` to be used as a CSP
directive.

Buttons that passed some value and had a shared function went into
a global listener with data-action attribute, while all the individual
elements received their own event listener. This is a mess, but my goal
was to end up as close as I could to one-to-one translation of how
functions were originally attached to elements.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
The next layer after input validaton to achive the paranoid levels of
security. Remove all event handlers inside HTML attributes and add them
in TS for each element, allowing `script-src 'self'` to be used as a CSP
directive.

Buttons that passed some value and had a shared function went into
a global listener with data-action attribute, while all the individual
elements received their own event listener. This is a mess, but my goal
was to end up as close as I could to one-to-one translation of how
functions were originally attached to elements.
</pre>
</div>
</content>
</entry>
<entry>
<title>Remove safeTags, decodeChat, and prepChat</title>
<updated>2026-06-06T03:09:27+00:00</updated>
<author>
<name>Osmium Sorcerer</name>
<email>os@sof.beauty</email>
</author>
<published>2026-06-03T11:23:33+00:00</published>
<link rel='alternate' type='text/html' href='https://git.sof.beauty/webao/commit/?id=fd75f3116aa30eb4958cc747f944f202ec69a484'/>
<id>fd75f3116aa30eb4958cc747f944f202ec69a484</id>
<content type='text'>
Following the removal of innerHTML manipulation, we no longer need these
sanitization functions.

I've reviewed every safeTags call site to make sure the outputs don't
end up anywhere unsafe, and malicious input can't malipulate DOM or
execute code. These values either end up either as plain text
(textContent, innerText, createTextNode, title, option) or as a URL
path to request assets to the server (encoded using encodeURI).

That is, if safeTags was even effective, considering all that function
did was replace '&lt;' and '&gt;' symbols with Unicode lookalikes. Even the
comment was suggesting the use of fundamentally safer functions instead
of these hacks.

Replace remaining uses of prepChat with unescapeChat as we still need
to do the token substitution (like "&lt;and&gt;" to "&amp;"). decodeChat was
escaping Unicode sequences like \uXXXX, but I don't see the reason for
this, AO2 Client doesn't have this feature, and considering WebSocket
text frames are strictly UTF-8, we don't need these encodings.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Following the removal of innerHTML manipulation, we no longer need these
sanitization functions.

I've reviewed every safeTags call site to make sure the outputs don't
end up anywhere unsafe, and malicious input can't malipulate DOM or
execute code. These values either end up either as plain text
(textContent, innerText, createTextNode, title, option) or as a URL
path to request assets to the server (encoded using encodeURI).

That is, if safeTags was even effective, considering all that function
did was replace '&lt;' and '&gt;' symbols with Unicode lookalikes. Even the
comment was suggesting the use of fundamentally safer functions instead
of these hacks.

Replace remaining uses of prepChat with unescapeChat as we still need
to do the token substitution (like "&lt;and&gt;" to "&amp;"). decodeChat was
escaping Unicode sequences like \uXXXX, but I don't see the reason for
this, AO2 Client doesn't have this feature, and considering WebSocket
text frames are strictly UTF-8, we don't need these encodings.
</pre>
</div>
</content>
</entry>
<entry>
<title>Eliminate innerHTML manipulation</title>
<updated>2026-06-06T03:09:27+00:00</updated>
<author>
<name>Osmium Sorcerer</name>
<email>os@sof.beauty</email>
</author>
<published>2026-06-03T11:08:07+00:00</published>
<link rel='alternate' type='text/html' href='https://git.sof.beauty/webao/commit/?id=bd8b53cd6046cef9802d593d8257392d81afb5ce'/>
<id>bd8b53cd6046cef9802d593d8257392d81afb5ce</id>
<content type='text'>
Construct DOM nodes directly instead of trying to sanitize every input
string and dynamically updating HTML.

Replace all uses of innerHTML with textContent, replaceChildren, and
appendChild.

This removes the need to use safeTags and replace newlines, but now
requires preserving whitespace via CSS pre-wrap.

Every OOC chat line is now placed into its own element instead of simply
being appended to the log. This might be worse, and createTextNode
is another alternative.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Construct DOM nodes directly instead of trying to sanitize every input
string and dynamically updating HTML.

Replace all uses of innerHTML with textContent, replaceChildren, and
appendChild.

This removes the need to use safeTags and replace newlines, but now
requires preserving whitespace via CSS pre-wrap.

Every OOC chat line is now placed into its own element instead of simply
being appended to the log. This might be worse, and createTextNode
is another alternative.
</pre>
</div>
</content>
</entry>
<entry>
<title>Add passkey authentication (WebAuthn)</title>
<updated>2026-06-06T03:09:27+00:00</updated>
<author>
<name>Osmium Sorcerer</name>
<email>os@sof.beauty</email>
</author>
<published>2026-04-07T13:19:40+00:00</published>
<link rel='alternate' type='text/html' href='https://git.sof.beauty/webao/commit/?id=da790cb4fa1cc221a0b557d2cf64585b8e4ebcf9'/>
<id>da790cb4fa1cc221a0b557d2cf64585b8e4ebcf9</id>
<content type='text'>
Bring in the subprotocol (the same as what's used on the desktop client
for public-key authentication) to carry the relevant messages:

- AuthRequest: first step in the flow, the client sends it to signal the
  intent to authenticate to the server.

- AssertCredential and AssertionFinish: server's challenge and client's
  response, respectively, to finalize the flow.

- RegisterCredential and RegistrationFinish: same structure as the
  above. Unlike the simple public-key auth with an out-of-band setup,
  passkeys require user interaction to register. User must be
  authorized.

Validate all relevant checks on the API side, and hand the data over to
the server for it to verify attestations and assertions.

Because it's a primary auth mechanism (not a second factor), require
user verification.

As we don't use any other method on web, add a passkey button as the
only sign-in interface. Passkeys are discoverable, we don't even need a
username.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Bring in the subprotocol (the same as what's used on the desktop client
for public-key authentication) to carry the relevant messages:

- AuthRequest: first step in the flow, the client sends it to signal the
  intent to authenticate to the server.

- AssertCredential and AssertionFinish: server's challenge and client's
  response, respectively, to finalize the flow.

- RegisterCredential and RegistrationFinish: same structure as the
  above. Unlike the simple public-key auth with an out-of-band setup,
  passkeys require user interaction to register. User must be
  authorized.

Validate all relevant checks on the API side, and hand the data over to
the server for it to verify attestations and assertions.

Because it's a primary auth mechanism (not a second factor), require
user verification.

As we don't use any other method on web, add a passkey button as the
only sign-in interface. Passkeys are discoverable, we don't even need a
username.
</pre>
</div>
</content>
</entry>
<entry>
<title>Remove defunct CAPTCHA</title>
<updated>2026-06-06T03:09:27+00:00</updated>
<author>
<name>Osmium Sorcerer</name>
<email>os@sof.beauty</email>
</author>
<published>2026-04-07T03:16:18+00:00</published>
<link rel='alternate' type='text/html' href='https://git.sof.beauty/webao/commit/?id=cf0cd1cd8838f402502d89ea2055caaaa8a866b5'/>
<id>cf0cd1cd8838f402502d89ea2055caaaa8a866b5</id>
<content type='text'>
The hCaptcha integration has been abandoned for a while. It added yet
another questionable third-party API (which also set a Cloudflare
cookie), and its effectiveness is unclear considering its client-side
nature.

A custom CAPTCHA implementation (such as PoW challenge) is an
interesting prospect, but it'll require proper server-side support.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
The hCaptcha integration has been abandoned for a while. It added yet
another questionable third-party API (which also set a Cloudflare
cookie), and its effectiveness is unclear considering its client-side
nature.

A custom CAPTCHA implementation (such as PoW challenge) is an
interesting prospect, but it'll require proper server-side support.
</pre>
</div>
</content>
</entry>
</feed>
