Skip to content

fix: use constant-time comparison in basic auth middleware - #322

Open
mvanhorn wants to merge 3 commits into
redhat-data-and-ai:mainfrom
mvanhorn:fix/273-constant-time-basic-auth
Open

fix: use constant-time comparison in basic auth middleware#322
mvanhorn wants to merge 3 commits into
redhat-data-and-ai:mainfrom
mvanhorn:fix/273-constant-time-basic-auth

Conversation

@mvanhorn

@mvanhorn mvanhorn commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Changes

📝 Description

What changed?

The basic auth middleware compared the request username and password against configured credentials with ==, which short-circuits on the first mismatched byte. This change uses crypto/subtle.ConstantTimeCompare for both fields, computes both comparisons before combining them, and authorizes only when both match.

Why is this change needed?

The == comparison leaks timing information about how many leading bytes of the username or password match, which can help an attacker recover credentials byte by byte (OWASP A07, identification and authentication failures). Fixes #273.

Dependencies

  • N/A (uses the standard library crypto/subtle)

🧪 Testing

Test Coverage

Added internal/httpapi/middleware/basic_auth_test.go covering: valid credentials authorize, wrong password returns 401, wrong username returns 401, empty username/password returns 401, and a request matching the second configured credential authorizes.

Commands run:

  • go build ./internal/httpapi/middleware/... passed
  • go test ./internal/httpapi/middleware/... passed

Performance Impact

  • Negligible. Constant-time comparison over short credential strings.

🚀 Deployment

Deploy Steps

  1. N/A

Prerequisites

  • N/A

Post-Deployment Monitoring

  • N/A

Rollback Plan

  • N/A

⚠️ Breaking Changes

  • This PR contains breaking changes
  • Migration guide provided (if applicable)

Details:

  • N/A. Authorization outcomes are unchanged; only the comparison method changed.

⚙️ Configuration Changes

  • N/A

✅ Developer Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added positive and negative tests that prove my fix is effective or that my feature works
  • Relevant documentation (README, tech specs, etc.) has been added or updated
  • All CI/CD checks are passing

Summary by CodeRabbit

  • Security Enhancements

    • Improved protection for Basic Authentication credential comparisons.
    • Preserved existing authentication behavior and unauthorized responses.
  • Tests

    • Added coverage for valid, invalid, empty, and multiple credential scenarios.
    • Verified authenticated client identification and protected-route responses.

Signed-off-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the security of the basic authentication middleware by mitigating potential timing side-channel vulnerabilities. By switching to constant-time comparisons, the application no longer leaks information about credential matching through response latency, aligning with security best practices for authentication mechanisms.

Highlights

  • Security Improvement: Replaced standard string comparison with crypto/subtle.ConstantTimeCompare to prevent timing attacks during basic authentication.
  • Testing: Added comprehensive unit tests in internal/httpapi/middleware/basic_auth_test.go to verify authorization logic for valid, invalid, and empty credentials.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.


A secret hidden in the time, / A byte-by-byte and subtle crime. / We mask the wait with constant flow, / So attackers have no way to know.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the BasicAuth middleware to use constant-time comparison for usernames and passwords to prevent timing attacks, and adds a comprehensive test suite for the middleware. The review feedback highlights a remaining timing vulnerability where subtle.ConstantTimeCompare can leak length information if the input lengths differ, and suggests hashing the inputs with SHA-256 prior to comparison to fully mitigate the issue.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines 17 to 18
"crypto/subtle"
"net/http"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To support secure constant-time comparison of arbitrary-length strings without leaking length information, we need to hash the inputs using SHA-256 first. Please import the crypto/sha256 package.

Suggested change
"crypto/subtle"
"net/http"
"crypto/sha256"
"crypto/subtle"
"net/http"

Comment on lines +40 to +42
usernameMatches := subtle.ConstantTimeCompare([]byte(username), []byte(u.Username))
passwordMatches := subtle.ConstantTimeCompare([]byte(password), []byte(u.Password))
if usernameMatches&passwordMatches == 1 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

subtle.ConstantTimeCompare returns early if the lengths of the slices are different, which leaks the length of the expected username and password via timing. To prevent this timing side-channel leak, hash both the input and the expected values using SHA-256 before performing the constant-time comparison.

			usernameHash := sha256.Sum256([]byte(username))
			uUsernameHash := sha256.Sum256([]byte(u.Username))
			passwordHash := sha256.Sum256([]byte(password))
			uPasswordHash := sha256.Sum256([]byte(u.Password))

			usernameMatches := subtle.ConstantTimeCompare(usernameHash[:], uUsernameHash[:])
			passwordMatches := subtle.ConstantTimeCompare(passwordHash[:], uPasswordHash[:])
			if usernameMatches&passwordMatches == 1 {

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@mvanhorn, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 26 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 35b0eda9-ac6a-4116-8287-88801fb1619d

📥 Commits

Reviewing files that changed from the base of the PR and between cf442d5 and 036c91d.

📒 Files selected for processing (1)
  • internal/httpapi/middleware/basic_auth.go
📝 Walkthrough

Walkthrough

Basic authentication now validates usernames and passwords with SHA-256 hashes and constant-time comparisons. Table-driven tests cover valid, invalid, empty, and multiple-user credentials.

Changes

Basic authentication security

Layer / File(s) Summary
Credential validation and middleware coverage
internal/httpapi/middleware/basic_auth.go, internal/httpapi/middleware/basic_auth_test.go
The middleware compares hashed credentials with constant-time comparison. Tests verify authorization status codes and authenticated client IDs for multiple credential scenarios.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: constant-time comparison in the basic authentication middleware.
Description check ✅ Passed The description covers the required sections, explains the change and rationale, documents tests, and identifies deployment and compatibility impact.
Linked Issues check ✅ Passed The implementation satisfies issue [#273] by hashing both credentials and comparing fixed-length SHA-256 values in constant time before authorization.
Out of Scope Changes check ✅ Passed The changes are limited to the basic authentication comparison logic and focused middleware tests required by [#273].
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Comment thread internal/httpapi/middleware/basic_auth.go Fixed
Comment thread internal/httpapi/middleware/basic_auth.go Fixed
subtle.ConstantTimeCompare returns early when the slice lengths differ, so
comparing the raw strings leaked the length of the configured username and
password through timing. Hash both sides with SHA-256 first so the comparison
always runs over 32 bytes.

Signed-off-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
@mvanhorn
mvanhorn force-pushed the fix/273-constant-time-basic-auth branch from a316fa7 to cf442d5 Compare August 2, 2026 22:59
The previous revision hashed each credential with SHA256 before
subtle.ConstantTimeCompare. That is the pattern in the net/http
Request.BasicAuth documentation, and it was never storing a digest, but
CodeQL cannot distinguish length-equalisation from password storage and
reported two high-severity 'weak hashing algorithm on sensitive data'
alerts against it.

Compare keyed tags instead: a 32-byte per-process random key feeds an
HMAC-SHA256 over each value, and hmac.Equal does the constant-time
comparison. This keeps the two properties the original was after, no
timing signal and no length leak, and adds one the bare digest lacked,
since an attacker who learns a tag cannot recompute it without the key.

The key is generated at startup, never persisted and never leaves the
process.

Signed-off-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
@mvanhorn

mvanhorn commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

Bumping this one. Both bot findings are addressed and every check is green.

The gemini note about subtle.ConstantTimeCompare leaking length, and the CodeQL alert about SHA-256 being unsuitable for password hashing, pull in opposite directions. 036c91da resolves both by comparing keyed HMAC-SHA256 tags with hmac.Equal: fixed-length so no length leak, and keyed with a per-process random value that is never persisted, so it is not password storage.

Ready for review whenever someone has a moment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[H2] Basic auth uses timing-vulnerable plaintext comparison

2 participants