# Security Review

> Verify that code changes do not introduce OAuth security vulnerabilities. Use when modifying token handling, client authentication, scope validation, redirect URI checks, secret comparison, or grant flows in Doorkeeper.

- Skill: `doorkeeper-gem/security-review` (Agent Skill)
- Install (CLI): `npx skillmds@latest add doorkeeper-gem/security-review`
- Raw SKILL.md: https://api.skillmd.com/api/skills/doorkeeper-gem/security-review/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: doorkeeper-gem (https://skillmd.com/u/doorkeeper-gem)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/doorkeeper-gem/security-review

---


# Security Review

When implementing or reviewing changes to Doorkeeper, use this skill to verify that changes do not introduce security vulnerabilities. Doorkeeper is an OAuth 2 provider library — security is the primary constraint.

## Constant-Time Comparisons

All comparisons of secrets MUST use `ActiveSupport::SecurityUtils.secure_compare` or equivalent, never `==`:

- Token values
- Client secrets
- PKCE code challenge verification
- Refresh tokens
- Any cryptographic digest comparison

**Check:** grep for `==` near variables named `token`, `secret`, `code_challenge`, `refresh_token`. If found, replace with `secure_compare`.

**Reference:** The codebase already uses `secure_compare` in `lib/doorkeeper/secret_storing/base.rb`. Follow that pattern.

## Token Leakage Prevention

Tokens and secrets must never appear in:
- Log output (Rails parameter filtering should exclude them)
- Error response bodies (beyond what RFC 6749 requires)
- URL query parameters for sensitive values (except where RFC requires, e.g., authorization codes)
- Exception messages that might be captured by error reporters

**Check:** Doorkeeper registers `Doorkeeper.setup_filter_parameters` (in `lib/doorkeeper.rb`) which adds `client_secret`, `authentication_token`, `access_token`, and `refresh_token` to `Rails.application.config.filter_parameters`, plus `code` when the `authorization_code` grant flow is enabled. Verify any newly-introduced sensitive parameter is covered by that list.

## Redirect URI Validation (RFC 6749 §3.1.2, RFC 8252 §7)

- Redirect URIs must be compared using exact string matching
- Exception: loopback redirects (127.0.0.1, [::1]) allow port variation per RFC 8252 §7.3
- Fragment components (`#`) are forbidden
- Relative URIs are forbidden
- The `urn:ietf:wg:oauth:2.0:oob` redirect is special-cased

**Reference:** `lib/doorkeeper/oauth/helpers/uri_checker.rb` and `lib/doorkeeper/orm/active_record/redirect_uri_validator.rb`

## Client Authentication (RFC 6749 §2.3)

- A client MUST NOT use more than one authentication method per request
- The codebase enforces this via `Doorkeeper::Request.validate_client_authentication!` — ensure new auth methods participate in this check
- Confidential clients must always authenticate; public clients use `none`
- `by_uid_and_secret` must be the resolution path — don't bypass it

**Reference:** `lib/doorkeeper/client_authentication/` and `lib/doorkeeper/request.rb`

## Grant Code Single-Use (RFC 6749 §4.1.2)

- Authorization codes MUST be single-use
- If a code is presented twice, all tokens issued from that code must be revoked
- Race conditions are handled via database locking (`lock!`)

**Reference:** `lib/doorkeeper/oauth/authorization_code_request.rb` — `InvalidGrantReuse` handling

## Scope Validation

- Requested scopes must be a subset of configured server scopes
- Per-application scopes further restrict what a client can request
- `scopes_by_grant_type` can restrict scopes per flow
- Dynamic scopes (if enabled) must still match the configured delimiter pattern

**Reference:** `lib/doorkeeper/oauth/helpers/scope_checker.rb`

## PKCE (RFC 7636)

- When `force_pkce` is true, `code_challenge` is mandatory
- S256 is the recommended (and often required) challenge method
- Code verifier comparison must use the correct transform (SHA256 + base64url for S256, plain for plain)
- Note: the codebase currently uses `==` for PKCE comparison — this is a known issue

## Input Validation

- All user-supplied parameters must be validated before use
- `grant_type`, `response_type`, `response_mode` must match registered values
- Token endpoint enforces `application/x-www-form-urlencoded` content type (when `enforce_content_type` is enabled)
- Multiple values for `scope` in the same request must be rejected at the authorization endpoint

## Revocation (RFC 7009)

- Token revocation should return 200 even for invalid tokens (to avoid information leakage)
- Revoking a refresh token should also revoke associated access tokens
- Client authentication is required for revocation (default behavior)

## Patterns to Avoid

```ruby
# BAD: String comparison on secrets
token == stored_token

# GOOD: Constant-time comparison
ActiveSupport::SecurityUtils.secure_compare(token, stored_token)

# BAD: Token in error message
raise "Invalid token: #{token}"

# GOOD: Generic error
raise Doorkeeper::Errors::InvalidToken

# BAD: Redirect URI with partial matching
uri.start_with?(registered_uri)

# GOOD: Exact match (already handled by URIChecker)
Doorkeeper::OAuth::Helpers::URIChecker.valid_for_authorization?(uri, registered_uris)

# BAD: Skipping client authentication
# Never bypass validate_client_authentication! for token-endpoint requests

# GOOD: All token requests go through client auth
# Request.client_authentication_method calls validate_client_authentication!(request) internally
Doorkeeper::Request.client_authentication_method(request)
```

## After Making Changes

1. Run security-related specs: `bundle exec rspec spec/requests/flows/ spec/lib/oauth/`
2. Check if Brakeman would flag the change: `gem install brakeman && brakeman --no-pager`
3. Verify no token values appear in test output or logs

