Surfaced during the #211 cross-model review. Verified in code; not yet reproduced — needs a PoC before a fix.
Problem
Harper resolves request.user from session.user early (core auth middleware) — before the plugin's global session-validation middleware runs. That middleware (src/index.ts ~L419–437) detects an expired/revoked token or a removed provider, calls clearOAuthSession(...) to invalidate the STORED session, then unconditionally return next(request) — without clearing request.user or rejecting the request. So the request that triggered detection completes once with the old (now-invalidated) identity.
The stored session is invalidated, so subsequent requests are unauthenticated — but there's a one-request window where a user whose OAuth token was revoked upstream can still perform a protected operation. For a super_user that one operation is significant. (Resources wrapped with withOAuthValidation + requireAuth: true are denied by the wrapper; this gap is for the global path relying on Harper's request.user.)
Scope / severity
One-request window after upstream revocation/expiry; pre-existing (the middleware has always next()-ed). Real but bounded auth gap.
Direction (to design after reproduction)
On invalidation, short-circuit (return 401/deny) or clear request.user / request-local session state before continuing, rather than passing through. Decide whether the global middleware should enforce or remain passive with the wrapper as the sole gate. Reproduce first (login, revoke upstream token, one more request, assert it should be denied).
Priority suggestion: P2.
Surfaced during the #211 cross-model review. Verified in code; not yet reproduced — needs a PoC before a fix.
Problem
Harper resolves
request.userfromsession.userearly (core auth middleware) — before the plugin's global session-validation middleware runs. That middleware (src/index.ts~L419–437) detects an expired/revoked token or a removed provider, callsclearOAuthSession(...)to invalidate the STORED session, then unconditionallyreturn next(request)— without clearingrequest.useror rejecting the request. So the request that triggered detection completes once with the old (now-invalidated) identity.The stored session is invalidated, so subsequent requests are unauthenticated — but there's a one-request window where a user whose OAuth token was revoked upstream can still perform a protected operation. For a
super_userthat one operation is significant. (Resources wrapped withwithOAuthValidation+requireAuth: trueare denied by the wrapper; this gap is for the global path relying on Harper'srequest.user.)Scope / severity
One-request window after upstream revocation/expiry; pre-existing (the middleware has always
next()-ed). Real but bounded auth gap.Direction (to design after reproduction)
On invalidation, short-circuit (return 401/deny) or clear
request.user/ request-local session state before continuing, rather than passing through. Decide whether the global middleware should enforce or remain passive with the wrapper as the sole gate. Reproduce first (login, revoke upstream token, one more request, assert it should be denied).Priority suggestion: P2.