|
|
@@ -41,6 +41,21 @@ The final review pass found five remaining issues:
|
|
|
- Do not add backward compatibility for stale stop-server PID files unless Drew
|
|
|
explicitly approves that tradeoff.
|
|
|
|
|
|
+## Inherited Security Invariants
|
|
|
+
|
|
|
+This fixup preserves the auth hardening already designed and implemented:
|
|
|
+
|
|
|
+- `.last-token` and `state/server-info` remain sensitive owner-only state.
|
|
|
+- Fallback tokens may appear in startup JSON and `state/server-info`, but must
|
|
|
+ not be written to `.last-token`.
|
|
|
+- Cookies remain port-named, `HttpOnly`, `SameSite=Strict`, and scoped to `/`.
|
|
|
+- WebSocket upgrades still require a valid key or cookie.
|
|
|
+- WebSocket `Origin` checks remain enforced when the browser supplies an
|
|
|
+ `Origin` header.
|
|
|
+- Direct no-`Origin` clients remain allowed only when they carry the session key.
|
|
|
+- Generated same-origin screen JavaScript and future same-origin vendored
|
|
|
+ libraries are trusted. Sandboxing malicious screen HTML remains deferred.
|
|
|
+
|
|
|
## Design
|
|
|
|
|
|
### 1. Rebase Onto Current `dev`
|
|
|
@@ -51,8 +66,12 @@ work. Resolve the `evals` submodule conflict by taking `dev`.
|
|
|
After the rebase:
|
|
|
|
|
|
- `evals` must not appear in the PR diff.
|
|
|
-- PR #1720 can still mention eval evidence that was run elsewhere.
|
|
|
+- PR #1720 can still mention eval evidence that was run elsewhere, but it must
|
|
|
+ include exact external evidence: eval repo commit, scenario path, command,
|
|
|
+ result artifact path or id, and RED/GREEN outcome.
|
|
|
- The PR body must not imply the evals submodule bump is part of this PR.
|
|
|
+- Any earlier PR-body text or comment implying the submodule bump is included
|
|
|
+ must be superseded by the final PR-body evidence.
|
|
|
|
|
|
### 2. Root Screen Containment
|
|
|
|
|
|
@@ -67,9 +86,11 @@ one when the platform reports link counts.
|
|
|
Expected behavior:
|
|
|
|
|
|
- A symlink under `content/` pointing outside `content/` is ignored.
|
|
|
-- A hardlink under `content/` to a file outside `content/` is ignored when the
|
|
|
- platform exposes enough metadata to detect it.
|
|
|
+- A hardlink under `content/` to `state/server-info` is ignored when
|
|
|
+ `fs.linkSync` succeeds and `lstat.nlink > 1`.
|
|
|
- If no safe screen file remains, the waiting page is served.
|
|
|
+- Existing `/files/*` containment behavior remains unchanged: empty names,
|
|
|
+ dotfiles, symlinks, hardlinks, and directories still return 404.
|
|
|
|
|
|
### 3. Fallback Token Isolation
|
|
|
|
|
|
@@ -78,7 +99,9 @@ Port fallback must not reuse a token loaded from persisted `.last-token`.
|
|
|
Token source should be explicit in code:
|
|
|
|
|
|
- `BRAINSTORM_TOKEN` from the environment is an intentional operator/test
|
|
|
- override and remains unchanged on fallback.
|
|
|
+ override. If the preferred port is occupied while an explicit environment
|
|
|
+ token is set, the server must fail closed instead of falling back, because the
|
|
|
+ occupied server may be using the same explicit token.
|
|
|
- `.last-token` is persisted state for same-port reconnect convenience. If the
|
|
|
server falls back because the preferred port is occupied, discard that loaded
|
|
|
token and generate a fresh unpersisted token for the fallback process.
|
|
|
@@ -94,16 +117,23 @@ The fallback server must continue to avoid overwriting `.last-port` and
|
|
|
Node as an inert command-line argument, for example:
|
|
|
|
|
|
```text
|
|
|
---brainstorm-server-id=<opaque-id>
|
|
|
+node server.cjs --brainstorm-server-id=<id>
|
|
|
```
|
|
|
|
|
|
The id is not an auth credential. It is only process-ownership evidence for the
|
|
|
local lifecycle scripts. `server.cjs` can ignore the argument.
|
|
|
|
|
|
+The id must use a shell/MSYS-safe alphabet, such as
|
|
|
+`^[A-Za-z0-9_-]{32,64}$`. Store it in `state/server-instance-id` with
|
|
|
+owner-only permissions.
|
|
|
+
|
|
|
`stop-server.sh` should read the expected id from state and only signal the PID
|
|
|
-when the target process command line contains the exact id argument. Existing
|
|
|
-port-to-PID checks may remain as additional evidence, but they should not be the
|
|
|
-only path that permits killing an ambiguous process.
|
|
|
+when the target process argv contains the exact argument
|
|
|
+`--brainstorm-server-id=<id>` as a full argv token, not as a loose substring.
|
|
|
+Prefer `/proc/<pid>/cmdline` when available, then fall back to wide `ps` output.
|
|
|
+A matching instance id is sufficient proof even when `server-info` is missing
|
|
|
+or `lsof` is unavailable. Existing port-to-PID checks may remain as additional
|
|
|
+evidence.
|
|
|
|
|
|
Fail closed when ownership cannot be proven:
|
|
|
|
|
|
@@ -116,6 +146,18 @@ Fail closed when ownership cannot be proven:
|
|
|
This intentionally prefers leaving a stale process running over killing an
|
|
|
unrelated process.
|
|
|
|
|
|
+Operator-visible outcomes should be explicit:
|
|
|
+
|
|
|
+- missing PID file returns `not_running`
|
|
|
+- missing or malformed server id returns `stale_pid`
|
|
|
+- unavailable command line returns `stale_pid`
|
|
|
+- wrong or absent argv id returns `stale_pid`
|
|
|
+- successful stop returns `stopped`
|
|
|
+
|
|
|
+On `stale_pid` and `stopped` outcomes, remove `server.pid` and
|
|
|
+`server-instance-id` so future stop attempts do not keep targeting the same
|
|
|
+ambiguous process. Do not remove persistent session content.
|
|
|
+
|
|
|
### 5. Test Hardening
|
|
|
|
|
|
The test pass should be deterministic across macOS and the Windows Git Bash host
|
|
|
@@ -131,6 +173,20 @@ Required changes:
|
|
|
assertion when the host cannot create usable test symlinks.
|
|
|
- Tests that create impostor processes must assert that the impostor survives
|
|
|
when lifecycle metadata is missing or insufficient.
|
|
|
+- Windows/MSYS start-server tests must assert that Windows-like detection still
|
|
|
+ clears `BRAINSTORM_OWNER_PID`, still auto-foregrounds when appropriate, and
|
|
|
+ still passes the instance-id argv exactly.
|
|
|
+
|
|
|
+### 6. Docs And PR Consistency
|
|
|
+
|
|
|
+Before Jesse reviews, reconcile reviewer-visible docs and PR metadata:
|
|
|
+
|
|
|
+- Update the issue catalog so dispositions match what this PR actually ships.
|
|
|
+- Keep auto-open docs consistent with the implemented `--open` behavior.
|
|
|
+- Keep the documented default idle timeout at 4 hours everywhere.
|
|
|
+- Review the PR body against the template after the rebase.
|
|
|
+- Record macOS, Windows, browser/manual, and external eval evidence in the PR
|
|
|
+ body with concrete commands and results.
|
|
|
|
|
|
## Testing Strategy
|
|
|
|
|
|
@@ -144,20 +200,31 @@ Use TDD for each behavior change:
|
|
|
|
|
|
Required focused regressions:
|
|
|
|
|
|
-- root screen symlink escape is not served
|
|
|
-- root screen hardlink escape is not served when the platform supports the check
|
|
|
-- fallback after occupied preferred port uses a token different from the
|
|
|
- persisted preferred-port token
|
|
|
-- fallback token does not authenticate to the original preferred-port server
|
|
|
-- `stop-server.sh` does not kill an impostor when `server-info` or the new
|
|
|
- server id proof is missing
|
|
|
-- fixed-port tests fail clearly if fallback occurs unexpectedly
|
|
|
-- shell cleanup traps reap all background children on failure
|
|
|
+| Behavior | Test File | Focused Command | Expected RED | Expected GREEN |
|
|
|
+| --- | --- | --- | --- | --- |
|
|
|
+| Root route ignores symlink escape | `tests/brainstorm-server/server.test.js` | `node tests/brainstorm-server/server.test.js` | authenticated `GET /` serves linked outside content | response serves waiting page or safe screen |
|
|
|
+| Root route ignores supported hardlink escape | `tests/brainstorm-server/server.test.js` | `node tests/brainstorm-server/server.test.js` | authenticated `GET /` serves hardlinked `server-info` | hardlink candidate is ignored when `nlink > 1` |
|
|
|
+| `/files/*` containment stays unchanged | `tests/brainstorm-server/server.test.js` | `node tests/brainstorm-server/server.test.js` | existing containment test regresses | empty, dotfile, directory, symlink, hardlink cases remain 404 |
|
|
|
+| Persisted-token fallback rotates token | `tests/brainstorm-server/lifecycle.test.js` | `node tests/brainstorm-server/lifecycle.test.js` | fallback URL key equals persisted preferred-port key | fallback URL key differs and is not written to `.last-token` |
|
|
|
+| Explicit-token fallback fails closed | `tests/brainstorm-server/lifecycle.test.js` | `node tests/brainstorm-server/lifecycle.test.js` | server falls back while `BRAINSTORM_TOKEN` is set | process exits non-zero and does not start fallback |
|
|
|
+| Fallback key cannot authenticate to original server | `tests/brainstorm-server/lifecycle.test.js` | `node tests/brainstorm-server/lifecycle.test.js` | fallback key receives 200 from original port | original port rejects fallback key |
|
|
|
+| Correct instance id permits stop | `tests/brainstorm-server/stop-server.test.sh` | `bash tests/brainstorm-server/stop-server.test.sh` | real start-server-launched server survives | stop returns `stopped` and process exits |
|
|
|
+| Wrong, missing, malformed, or stale id is safe | `tests/brainstorm-server/stop-server.test.sh` | `bash tests/brainstorm-server/stop-server.test.sh` | impostor is signaled | stop returns `stale_pid` and impostor survives |
|
|
|
+| Fixed-port suites cannot pass through fallback | `tests/brainstorm-server/server.test.js`, `tests/brainstorm-server/auth.test.js` | respective `node` commands | test silently talks to fallback port | test fails clearly or uses reported port intentionally |
|
|
|
+| Shell cleanup traps run on failures | `tests/brainstorm-server/stop-server.test.sh` | `bash tests/brainstorm-server/stop-server.test.sh` | failure leaves child processes | trap reaps background children |
|
|
|
+| Windows/MSYS start behavior keeps lifecycle invariants | `tests/brainstorm-server/start-server.test.sh`, `tests/brainstorm-server/windows-lifecycle.test.sh` | `bash` test commands on macOS and `ballmer` | owner PID or argv handling regresses | owner PID is cleared, foreground detection holds, id argv is present |
|
|
|
+
|
|
|
+Each RED/GREEN cycle should leave a short evidence note for the PR body: focused
|
|
|
+command, failing assertion before the fix, passing assertion after the fix, and
|
|
|
+whether the evidence was gathered on macOS or Windows.
|
|
|
|
|
|
## Verification
|
|
|
|
|
|
Before calling the fixup complete, run:
|
|
|
|
|
|
+- `git fetch origin dev && git rebase origin/dev`
|
|
|
+- `git diff --quiet origin/dev...HEAD -- evals`
|
|
|
+- `gh pr view 1720 --json mergeStateStatus,statusCheckRollup,headRefOid`
|
|
|
- `cd tests/brainstorm-server && npm test`
|
|
|
- relevant focused test commands used during TDD
|
|
|
- `git diff --check`
|
|
|
@@ -174,10 +241,14 @@ Manual/browser testing comes only after the automated pass is green.
|
|
|
- `evals` is absent from the PR diff.
|
|
|
- Root screen serving cannot read outside `content/` through symlink or
|
|
|
supported hardlink escapes.
|
|
|
-- A fallback server does not share a persisted token with the occupied
|
|
|
+- `/files/*` containment protections remain unchanged.
|
|
|
+- No fallback server runs with a token that may be shared with the occupied
|
|
|
preferred-port server.
|
|
|
- `stop-server.sh` does not signal unrelated processes when ownership proof is
|
|
|
missing or ambiguous.
|
|
|
+- `stop-server.sh` can still stop a legitimate server with a matching instance
|
|
|
+ id when `server-info` or `lsof` is unavailable.
|
|
|
+- Focused RED/GREEN evidence is recorded for each regression.
|
|
|
- macOS and Windows validation evidence is recorded in the PR body.
|
|
|
- The PR body accurately describes what is in the branch and what evidence was
|
|
|
gathered externally.
|