|
16 | 16 | still bounded, and so is the scrypt work an unauthenticated caller can cause. |
17 | 17 | - Documented in `docs/OPERATIONS.md` → Reverse proxies and tunnels and in INSTALL.md: without the |
18 | 18 | setting, all remote logins behind a proxy share one backoff bucket. |
| 19 | +## Web and Slack surface hardening (security audit batch 1) |
| 20 | + |
| 21 | +- **Admin mode is admin-only on every surface.** The Slack Settings mode control refuses to switch |
| 22 | + a channel into Admin mode unless the clicker is a current organization admin, the same rule |
| 23 | + `/mode admin` and the `set_channel_admin_mode` tool already enforce. A channel manager keeps |
| 24 | + every other control in a channel that is already in Admin mode (changing Auto, Lean or the |
| 25 | + network switch leaves the mode alone) and may step the channel down to Worker or Read-only; the |
| 26 | + "Admin" option is listed only for someone who may enable it or when it is the current mode. |
| 27 | +- **Case-variant URLs never reach the admin router.** Express case-sensitive routing is on, and the |
| 28 | + Host/Origin guard, the no-password lockdown, the 401 branch and the run-API key gate all compare |
| 29 | + lowercased paths, so `/API/settings` is refused exactly like `/api/settings` instead of slipping |
| 30 | + past them into the router. |
| 31 | +- **Security headers.** Every response carries `X-Content-Type-Options: nosniff`, |
| 32 | + `X-Frame-Options: DENY` and `Referrer-Policy: same-origin`. The admin shell and the login page are |
| 33 | + served with a Content-Security-Policy that admits same-origin scripts only plus the import map |
| 34 | + by content hash (no inline script anywhere; the login handler lives in `public/login.js`), |
| 35 | + same-origin styles with inline attributes, same-origin fonts, images and connections, |
| 36 | + `frame-ancestors 'none'`, `object-src 'none'`. |
| 37 | +- **No stack traces.** One Express error handler answers unexpected failures with a bare 500 (logged |
| 38 | + server-side) and keeps exposable client errors such as a malformed JSON body as their 4xx with |
| 39 | + the message only; the systemd unit also sets `NODE_ENV=production`. `/resume` requires the admin |
| 40 | + session; the `/internal/*` IPC secret is compared in constant time. |
| 41 | +- **Background-job status button.** "Check status" on a background job card runs through the same |
| 42 | + authorization gate as `/status` (admin, approved, or an explicit guest of that channel) and only |
| 43 | + reports a job that belongs to that channel; the log-tail fallback reads the channel's own log. |
| 44 | +- **File explorer exports re-prove the file.** Share, Send to me, Preview and Edit open the resolved |
| 45 | + file `O_NOFOLLOW` and re-resolve the kernel descriptor through `/proc/self/fd`, refusing a file |
| 46 | + that no longer sits under the channel root, which closes the symlink-swap race the agent-side |
| 47 | + export tools already closed. |
| 48 | +- **License card.** The organization, tier and limit values from the licensing platform are |
| 49 | + HTML-escaped before rendering. |
| 50 | +- **Dependencies.** `npm audit fix` applied: undici, zod, hono, fast-uri, body-parser and |
| 51 | + ip-address moved to their patched releases; `npm audit --omit=dev` reports no vulnerabilities. |
19 | 52 |
|
20 | 53 | ## Admin access to Slack controls |
21 | 54 |
|
|
0 commit comments