# Security

The stack's security standard is the OWASP Application Security Verification Standard (ASVS),
version 5.0, at level 2, the way its components hold to WCAG 2.2 AA. This file says what that
claim means, what the stack owns, what an application built on it still owns, and the rules that
keep the second list short.

## The claim

Every level 1 and level 2 requirement in the standard has an owner in
[`security/asvs.csv`](https://github.com/torrinworx/aweft/blob/9a5bb24770dc7555257f8a307d79917efc58df70/docs/security/asvs.csv): `stack`, `application`, `operator` or `none`. A `stack`
row names the check that proves it: a case of `securityChecks()` on `@aweftjs/testing`, a test in
a package's own suite, or a section of a document. `npm run security` fails when a `stack` row
names a check that does not exist, or a case cites a requirement the table does not give to the
stack, and the root gate runs it. So "the stack meets every requirement it owns" is a sentence
the gate checks, and the `application` rows are the whole list an application has left to do.
Each of those rows names the pattern or the tool that meets it. `npm run security` prints the
counts.

The standard is written for applications. A framework meets the requirements it builds
(sessions, the gate, the bounds, the encoders, the files it serves) and cannot meet the ones that
depend on what the application stores and who its users are. Level 2 rather than 1, because
anything with a sign-in is past level 1 by the standard's own guidance; rather than 3, because
level 3 asks for what sits outside one process.

The table carries ids and the stack's own columns only. The requirement text is the standard's,
at https://owasp.org/www-project-application-security-verification-standard/ .

## What the stack owns

- **The gate.** Required, outside every module: `identify` once per connection or request,
  `access` before a module sees a connection, a call or a request. A module is private unless it
  says `public: true`. The context a module sees is the gate's word and never the client's: no
  header, body or argument moves it.
- **Sessions.** Tokens are sixteen bytes from the platform's secure source, a new one on every
  sign-in, dead at the next handshake and request after sign-out, swept once over for thirty
  days. The cookie is HttpOnly, SameSite=Lax, Secure over TLS. Passwords are eight to 256
  characters of any composition, hashed with scrypt, compared in constant time, and never
  stored.
- **The bounds.** Six hundred requests a minute per address before the gate. Five sign-in
  attempts per email and twenty per address in fifteen minutes, eight password hashes in
  flight. A body and a frame of at most 1 MiB. Every number is a default one line of
  configuration widens or removes.
- **Origin.** A browser that names another origin is refused on a state-changing request and
  at the handshake; a client that names none passes.
- **Errors.** A route that throws answers a bare 500. A call that throws anything but a
  refusal answers `failed` and nothing of the error; the operator hears it through
  `handlers.failed`, or on the console when there is none, and the process goes on. Bytes that
  are not a frame end the link and nothing else.
- **The encoders.** `decodeCommit`, `decodeValue` and `decodeFrame` parse what an attacker
  sends and answer or refuse in the stack's own shape, never anything else, under seeded fuzz.
  A slot named `__proto__` is a slot.
- **Files.** The static battery decodes a path once, refuses `..` and dotfiles, serves the type
  its table says, lists nothing, and answers `nosniff` on everything; every answer the server
  gives carries the same header.
- **Drawings fetched at run time.** `fromUrl` refuses a body that can run or reach out.
- **What the logs hold.** The logs battery records no typed value, no private slot, and no
  password field's key.

## The rules an application follows

1. **Authority lives in a document the user cannot write, and a composed gate reads it.**
   `auth/State` shares the user's own document and accepts every commit, because it is theirs.
   A `role` kept there is the user's to set. Keep who is what where no client reaches it: a
   module's own instance, or a document nothing shares, filled from the application's own
   source; write a gate that `deps` on `auth/Gate` and reads it; give the reserved modules a
   word (`admin: true`) that gate reads. The server reads no such word: a gate you write gives
   it meaning. `securityChecks()` proves this shape with its administrator case, over a gate
   of its own composed on yours and a word of its own, so your gate's word is untouched.
2. **Never share a document holding a secret.** A document shared to a page or into a room
   crosses whole, underscore slots included. The leading underscore keeps a slot from wildcard
   observers, and so from the logs, not from the wire. A secret goes in a document nobody
   shares, or in a slot of a module's instance.
3. **Every share says who may write.** A share on a connection without `accept` ends the
   connection. In `accept`, refuse what the module does not own: a commit is `deltas`, and a
   write to a slot of the root object is a delta whose `ref` is `{ kind: 'object', key }`, so
   `commit.deltas.some((d) => d.ref.kind === 'object' && d.ref.key === 'owner')` is the check
   that keeps `owner` the module's. `check` from `@aweftjs/schema` is the tool for a whole
   shape.
4. **Behind a proxy, read the proxy's entry.** `forwarded: true` or `forwarded: 'x-real-ip'` on
   the listener, or every request is one address and the counts see the whole internet as one
   client.
5. **Over TLS, name the cookie `__Host-session`.** `cookie` on `auth/Session`'s configuration.
   The prefix makes a browser refuse the cookie over plain HTTP, which is why it is not the
   default.
6. **A content security policy, HSTS and a referrer policy are yours.** The stack sets
   `X-Content-Type-Options: nosniff` on every answer and no other page policy, because a policy
   depends on what the page loads.
7. **Pass `handlers.failed`.** A call that throws is written to the console when there is no
   handler and the process goes on. A hook, a route or the gate that throws with no handler is
   raised where nothing catches it, and the process says so and ends. That is loud on purpose
   in development; in production, a route bug a client can reach is then a way to stop the
   process, so pass a handler.
8. **Run the suite against your own application.**
   [`recipes/full-stack/tests/security.test.ts`](/docs/recipes/full-stack/files/tests/security.test.ts) is
   the whole file: your sources, your store, your gate, every case.

## Accepted risks

- **Enumeration.** `auth/Check` answers whether an email has an account, and a sign-up answers
  201 where a sign-in answers 200, so anyone can learn whether an address is registered. A
  sign-in form asks before asking for a password, and one route signs up and in. What makes it
  survivable is the attempt count: knowing an address exists buys five tries in fifteen minutes.
- **A store that can be read holds the tokens.** A session token is the name of its document.
  The store is the application's boundary.
- **Counts are per process, and bounded.** The request and attempt counts are in memory; a
  restart clears them, and a fleet counts per instance. Each holds at most 65 536 keys, and
  past that the oldest key under its count goes first: a flood of fresh addresses or emails
  frees no key at its count, and a flood that puts that many keys at their count frees the
  oldest of them.

## What is not built

Password change and reset and email verification wait on the notify battery. No second
factor, no inactivity timeout (`sessionMs` is absolute), no roles or administrator beyond the
pattern above, no breached-password list (`refusePassword` is where one goes), no read
filtering on a shared document.

## Reporting a vulnerability

[`SECURITY.md`](https://github.com/torrinworx/aweft/blob/9a5bb24770dc7555257f8a307d79917efc58df70/SECURITY.md) at the root of the repository.
