This project currently supports the latest main branch.
Please report suspected vulnerabilities privately to project maintainers. Do not open public issues for unpatched vulnerabilities.
Include:
- affected component (R app, Python app, gateway, compose)
- reproduction steps
- impact assessment
- suggested mitigation (if available)
- No hardcoded credentials in source.
- Containerized service boundaries.
- Gateway-only host exposure on port
8000. - Password hashing with bcrypt.
- Nginx
auth_requestgate for protected Shiny routes. - Session-based auth with HttpOnly cookies and CSRF protection for state-changing forms.
- Role-based authorization for per-app access control.
gateway(Nginx) is the only public entrypoint and enforces authentication before traffic reaches protected Shiny routes.auth-admin(FastAPI) owns identity logic: login, logout, session validation, role checks, and admin CRUD for users/roles.postgresstores users, roles, role-app grants, user-role mappings, and active sessions.r-shinyandpython-shinyare treated as protected upstream apps and do not implement independent login logic.
- Protected routes:
/rlang-app/and/python-app/. - Nginx uses
auth_requestto call an internal endpoint (/_auth_check) that proxies toauth-admin:/auth/check. - If auth returns
401, Nginx redirects the client to/auth/login?next=<original_path>. - If auth returns
403, Nginx serves/auth/forbidden(authenticated but not authorized). - If auth returns
200, Nginx forwards the request to the target Shiny app.
/_auth_checkis declaredinternal, so clients cannot call it directly.- For auth sub-requests, Nginx disables body forwarding (
proxy_pass_request_body off) and clearsContent-Lengthto keep checks lightweight. - Nginx forwards client cookies to
auth-adminduring checks (proxy_set_header Cookie $http_cookie) so session validation is server-side. - The gateway blocks direct public access to
/auth/checkwith404; only internal/_auth_checkis valid for route protection. - Unauthenticated access to protected routes is handled with
error_page 401 = @auth_signin, which centralizes redirect behavior. @auth_signinpreserves target navigation via?next=$request_uri./auth/*and/admin/*are proxied toauth-admin, while/rlang-app/*and/python-app/*are proxied only after successful auth checks.- Nginx app locations map both
401and403explicitly:401goes to sign-in redirect and403goes to the forbidden page.
- Passwords are hashed with
bcryptbefore storage. - Authentication is session-cookie based.
- Session cookies are configured
HttpOnly,SameSite=Lax, andSecureis controlled byAPP_COOKIE_SECURE. - Session identifiers are stored server-side as SHA-256 token hashes (
token_hash), not raw tokens. - Session validity requires all of: active user, existing session row, and non-expired session timestamp.
- Authorization validity for app routes requires role grant:
adminusers bypass role checks, while non-admin users needuser_rolesmembership connected torole_app_accessfor the requested app key.
- State-changing operations require CSRF token validation (admin user mutations and logout).
GET /auth/logoutprovides a user-facing confirmation page.POST /auth/logoutvalidates CSRF, deletes the server-side session row, and clears the browser cookie.
- The auth service initializes schema and bootstrap admin during app lifespan startup.
- Database writes and reads use parameterized SQL through
psycopgto prevent SQL injection.
Login flow details:
- Verify submitted password against stored bcrypt hash.
- Generate a random session token.
- Store only SHA-256 token hash in
sessions.token_hash. - Generate and store a distinct CSRF token.
- Set raw session token as the browser cookie value.
/auth/check validation checks:
- Cookie exists.
- Token hash exists in
sessions. - Session is not expired (
expires_at > NOW()). - Associated user is still active.
- If request targets a protected app, role permission exists for that app.
/auth/check response contract:
200for valid sessions.401for missing, invalid, or expired sessions.403for authenticated users missing required app permission.
User-management safeguards:
- Prevent deleting/deactivating the last active admin.
- Prevent self-delete and self-deactivation for current admin session.
- Invalidate all sessions when a user is deactivated.
- Role assignment changes take effect immediately for subsequent auth checks.
- Bootstrap admin credentials are sourced from environment variables and upserted at auth-service startup.
- Deletion/deactivation protections prevent removing the last active admin account.
- Deactivating a user invalidates all active sessions for that user.
- Production deployments should run behind HTTPS and set
APP_COOKIE_SECURE=true. - Secrets are supplied via environment management and are not committed to source control.
- Internal service-to-service traffic runs inside the Compose network boundary.