# T1 follow-up receipt — session layer (`app/routes/session.js`, `server.js`, `config/env/all.js`)

**Trigger:** reviewer asked whether the role check behind `/benefits` actually validates, i.e.
"pull the session logic and see if the state transition validates the role."
**Scope of this receipt:** the session/authz chain referenced by `index.js` — `session.js`
(middleware + login handler), `server.js` (express-session config), `config/env/all.js`
(secrets), `benefits.js` (handler). Same deterministic pass + line review as the index.js
receipt. Public corpus, no credentials.

## Question asked — resolved

**Yes, the role check validates — server-side, per request.**

`isAdminUserMiddleware` (session.js:25–34):

```js
if (req.session.userId) {
    return userDAO.getUserById(req.session.userId, (err, user) => {
       return user && user.isAdmin ? next() : res.redirect("/login");
    });
}
return res.redirect("/login");
```

The role is read from the database on every guarded request, keyed off the server-side
session's `userId` — not from any client-supplied field. Role revocation takes effect on
the next request (no stale token claims). The `index.js:57–60` context-needed item —
whether `isAdmin` is enforced on `/benefits` — therefore resolves as **enforced**:
`app.get/post("/benefits", isLoggedIn, isAdmin, …)` chains both middlewares.

The `user: { isAdmin: true }` literals in `benefits.js:22,45` are template-view variables
only — they render the page chrome, they do not grant anything. Not a finding.

## Adjacent findings the session pull surfaced

| # | location | disposition | basis |
|---|----------|-------------|-------|
| 1 | session.js:115 | **HOLDS — session fixation (MED)** | `req.session.userId = user._id` is set on the pre-login session with no `req.session.regenerate()`. The fix is documented in the file's own comments (A2 block, :104–114) but not applied. A session id planted before login survives the privilege transition. |
| 2 | config/env/all.js:8 | **HOLDS — hardcoded session secret (MED)** | `cookieSecret: "session_cookie_secret_key_here"` ships in the repo. Anyone with the source can sign session cookies; combined with fixation this weakens the whole chain. |
| 3 | server.js:78–99 | **HOLDS — cookie hardening commented out (LOW-MED)** | The `cookie: { httpOnly, secure }` block and generic `key: "sessionId"` rename are inside a `/* … */` — cookie goes over the default name, no `secure`, no `maxAge`. |
| 4 | index.js:41 → session.js:120–122 | HOLDS-WITH-CONTEXT (LOW) | `/logout` is `app.get` — a state transition (`session.destroy`) on GET. Already receipted as CSRF-MUTATING-GET in the index.js receipt. |
| 5 | index.js:72 | HOLDS (HIGH) | `res.redirect(req.query.url)` — unchanged verdict from the first receipt. Confirmed legitimate by reviewer. |

## Could not determine

- Whether the deployed instance overrides `cookieSecret` via environment config — the
  committed default is the finding; a production override would mitigate finding 2.
- Session store backing (default MemoryStore assumed; a different store changes the
  fixation surface but not the missing `regenerate()`).
- Behavior of `validateLogin` internals (DAO layer, outside this pull).

## Bottom line for the thread

The isAdmin gate is real: DB-backed, per-request, correctly chained on `/benefits`. The
weakness is not the role check but the session lifecycle wrapped around it — fixation,
a committed signing secret, and disabled cookie flags. Receipt-grade distinction:
"authz enforced" and "session transport weak" are separate verdicts and stay labeled so.

AI authorship: ARION, autonomous agent (human-supervised). Deterministic pre-review, not
an audit. Source corpus: OWASP NodeGoat master snapshot 2026-09-30.
