Authentication & Authorization
Implemented Controls
Multi-Factor Authentication
-
TOTP (
lib/git_gud/accounts/two_factor.ex,lib/git_gud/accounts/recovery_code.ex)- RFC 6238 compliant time-based one-time passwords
- 10 single-use recovery codes per user (bcrypt-hashed)
- Instance-level enforcement configurable via
TwoFactor.enforcement_status/1 - Grace period support (
TwoFactor.enforced_atvsTwoFactor.grace_period_days) - Attempt tracking via
TwoFactorAttempts(shared with login)
-
WebAuthn (
lib/git_gud/accounts/webauthn.ex,lib/git_gud/accounts/webauthn_credential.ex)- Passwordless or second-factor authentication
- Ceremony managed via
Waxlibrary - Sudo-mode gating for credential changes
- Credential management through
WebauthnController
-
Magic Links — Email-based authentication via
UserNotifier- Token-based login without password entry
- Tokens stored server-side with expiry
Session Management
-
Session tokens stored in
users_tokenstable- 14-day expiry window
- Server-side revocation capability
- Renewal on activity (reissue after 7 days)
- Session fixation prevention via
renew_session/2on login - Cross-session invalidation via
disconnect_sessions/1
-
Remember-me cookies
- Signed cookies with 14-day persistence
- Tied to user’s
authenticated_attimestamp
Personal Access Tokens
-
Scoped tokens (
lib/git_gud/accounts/personal_access_token.ex)- Scope-based access control (e.g.,
issues:read,repo:write) - Stored as hashes (not plaintext)
- Explicit scope checking in mobile API controller
- Per-action validation in
api/mobile/controllers
- Scope-based access control (e.g.,
-
Runner JWTs (
lib/git_gud/workflows/runner_jwt.ex)- Per-job short-lived tokens minted on
FetchTask - Claims:
sub,jti,tid(task ID),rid(run ID),pid(repo ID),perm(permissions) - Verified via
RunnerJwt.verify_active/1which checks job is still running - 5-minute grace period after job completion (for post-job steps)
- Permissions scoped to workflow
permissions:block
- Per-job short-lived tokens minted on
Authorization Layers
-
Repository visibility via
RepoAuthplug- Read-gate enforced at LiveView mount level
- Visibility enforcement:
public,internal,private - Preload-based checks to minimize queries
-
Admin scope via
Scope.for_user/1- Gated through
require_adminmount hook - Instance-level admin-only routes
- Gated through
-
Sudo mode
- Password or second-factor re-authentication required
- 10-minute validity window for sensitive operations
- Credential changes gated behind re-authentication
Security Observations
Critical
Insecure default secrets in config (config/config.exs:141-149)
- Secrets master key default:
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="(all zeros) - Runner JWT secret default:
"dev-runner-jwt-secret-do-not-use-in-prod" - If
runtime.exsdoesn’t override these, all secrets are encrypted with a known key and tokens are forgeable - Mitigation: production deployment must override via environment variables
High
HS256-only JWT signing (lib/git_gud/workflows/runner_jwt.ex:174)
- Runner JWTs use symmetric HS256 signing
- Same secret signs all tokens across the instance
- If leaked from any single runner, attacker can mint arbitrary runner tokens
- No key rotation at JWT layer (only app-level master key rotation for secrets)
- Recommendation: consider asymmetric signing (RS256/ES256) or at minimum per-instance secret rotation
Medium
No explicit account lockout
- Only MFA attempts are tracked via
TwoFactorAttempts - No lockout mechanism for failed login attempts
- Recommendation: add progressive rate limiting or account lockout after N failed attempts
PAT scopes not uniformly validated
- Scoped token validation exists in mobile API but not consistently across all REST API endpoints
- Recommendation: centralize PAT scope validation in a plug applied to all API pipelines