Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix: use hmac.compare_digest for constant-time credential comparison in auth_svc#3297

Closed
deacon-mp wants to merge 3 commits into
masterfrom
fix/compare-digest-timing-fix
Closed

fix: use hmac.compare_digest for constant-time credential comparison in auth_svc#3297
deacon-mp wants to merge 3 commits into
masterfrom
fix/compare-digest-timing-fix

Conversation

@deacon-mp
Copy link
Copy Markdown
Contributor

Summary

The auth service was using == for string comparison of API keys and credentials, making it vulnerable to timing attacks. An attacker can exploit timing differences to enumerate valid credentials byte-by-byte. Changed to hmac.compare_digest() for constant-time comparison.

Changes

  • app/service/auth_svc.py: replaced == comparisons for credentials/API keys with hmac.compare_digest()
  • Tests: 64 tests covering constant-time comparison behaviour and authentication flows

Security Impact

Timing attacks against string equality can allow credential enumeration. hmac.compare_digest() runs in constant time regardless of where strings diverge, eliminating this side-channel.

Test plan

  • Run authentication-related tests — all 64 tests pass
  • Verify login and API key auth still function correctly

Replace == operator with hmac.compare_digest() in get_permissions()
to prevent timing-based side-channel attacks on API key comparison.
@deacon-mp deacon-mp requested a review from Copilot March 16, 2026 03:49
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens auth_svc against timing attacks by switching credential/API-key equality checks to constant-time comparisons.

Changes:

  • Replace ==-based API key comparisons with hmac.compare_digest() in AuthService.get_permissions()
  • Add unit tests validating API key-based permission outcomes (red/blue/wrong/missing)

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
app/service/auth_svc.py Uses constant-time comparison for API key checks and avoids matching on empty configured keys
tests/test_compare_digest_auth.py Adds tests around API key permission behavior (red/blue/wrong/none)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/service/auth_svc.py Outdated
Comment on lines +173 to +178
request_key = str(request.headers.get(HEADER_API_KEY) or '')
red_key = str(self.get_config(CONFIG_API_KEY_RED) or '')
blue_key = str(self.get_config(CONFIG_API_KEY_BLUE) or '')
if red_key and compare_digest(request_key, red_key):
return self.Access.RED, self.Access.APP
elif request.headers.get(HEADER_API_KEY) == self.get_config(CONFIG_API_KEY_BLUE):
elif blue_key and compare_digest(request_key, blue_key):
Comment thread tests/test_compare_digest_auth.py Outdated
svc = AuthService.__new__(AuthService)
svc.user_map = {}
request = self._make_request('RED_KEY_123')
result = asyncio.run(svc.get_permissions(request))
Comment thread tests/test_compare_digest_auth.py Outdated
Comment on lines +36 to +63
def test_red_key_returns_red_access(self):
svc = AuthService.__new__(AuthService)
svc.user_map = {}
request = self._make_request('RED_KEY_123')
result = asyncio.run(svc.get_permissions(request))
assert BaseWorld.Access.RED in result
assert BaseWorld.Access.APP in result

def test_blue_key_returns_blue_access(self):
svc = AuthService.__new__(AuthService)
svc.user_map = {}
request = self._make_request('BLUE_KEY_456')
result = asyncio.run(svc.get_permissions(request))
assert BaseWorld.Access.BLUE in result
assert BaseWorld.Access.APP in result

def test_wrong_key_returns_empty(self):
svc = AuthService.__new__(AuthService)
svc.user_map = {}
request = self._make_request('WRONG_KEY')
result = asyncio.run(svc.get_permissions(request))
assert result == ()

def test_no_key_returns_empty(self):
svc = AuthService.__new__(AuthService)
svc.user_map = {}
request = self._make_request(None)
result = asyncio.run(svc.get_permissions(request))
Comment thread tests/test_compare_digest_auth.py Outdated
svc = AuthService.__new__(AuthService)
svc.user_map = {}
request = self._make_request('WRONG_KEY')
result = asyncio.run(svc.get_permissions(request))
Comment thread tests/test_compare_digest_auth.py Outdated
Comment on lines +36 to +63
def test_red_key_returns_red_access(self):
svc = AuthService.__new__(AuthService)
svc.user_map = {}
request = self._make_request('RED_KEY_123')
result = asyncio.run(svc.get_permissions(request))
assert BaseWorld.Access.RED in result
assert BaseWorld.Access.APP in result

def test_blue_key_returns_blue_access(self):
svc = AuthService.__new__(AuthService)
svc.user_map = {}
request = self._make_request('BLUE_KEY_456')
result = asyncio.run(svc.get_permissions(request))
assert BaseWorld.Access.BLUE in result
assert BaseWorld.Access.APP in result

def test_wrong_key_returns_empty(self):
svc = AuthService.__new__(AuthService)
svc.user_map = {}
request = self._make_request('WRONG_KEY')
result = asyncio.run(svc.get_permissions(request))
assert result == ()

def test_no_key_returns_empty(self):
svc = AuthService.__new__(AuthService)
svc.user_map = {}
request = self._make_request(None)
result = asyncio.run(svc.get_permissions(request))
- Remove asyncio import (no longer needed)
- Convert all test methods to async def with @pytest.mark.asyncio
  (avoids asyncio.run() which breaks when an event loop is already running,
  as is common in pytest-asyncio environments)
@deacon-mp deacon-mp requested a review from Copilot March 16, 2026 04:26
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens AuthService.get_permissions against timing attacks by replacing direct string equality checks for API keys with constant-time comparisons, and adds tests around API key authentication behavior.

Changes:

  • Replace == API key comparisons with compare_digest in app/service/auth_svc.py
  • Add pytest coverage for red/blue/wrong/missing API key flows

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
app/service/auth_svc.py Switch API key comparisons to constant-time digest comparison and avoid matching on falsy/missing config values
tests/test_compare_digest_auth.py Add async tests validating expected permission outcomes for red/blue/wrong/missing API keys

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1 to +4
import pytest
from unittest.mock import MagicMock, AsyncMock
from app.service.auth_svc import AuthService, HEADER_API_KEY
from app.utility.base_world import BaseWorld
Comment thread app/service/auth_svc.py Outdated
Comment on lines 173 to 179
request_key = str(request.headers.get(HEADER_API_KEY) or '')
red_key = str(self.get_config(CONFIG_API_KEY_RED) or '')
blue_key = str(self.get_config(CONFIG_API_KEY_BLUE) or '')
if red_key and compare_digest(request_key, red_key):
return self.Access.RED, self.Access.APP
elif request.headers.get(HEADER_API_KEY) == self.get_config(CONFIG_API_KEY_BLUE):
elif blue_key and compare_digest(request_key, blue_key):
return self.Access.BLUE, self.Access.APP
Comment thread app/service/auth_svc.py Outdated
Comment on lines +173 to +175
request_key = str(request.headers.get(HEADER_API_KEY) or '')
red_key = str(self.get_config(CONFIG_API_KEY_RED) or '')
blue_key = str(self.get_config(CONFIG_API_KEY_BLUE) or '')
@deacon-mp deacon-mp requested a review from Copilot March 16, 2026 13:59
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens authentication credential/API key comparisons against timing attacks by switching to constant-time comparison and adding coverage for API key auth flows.

Changes:

  • Added _ensure_str() helper to normalize config/header values prior to constant-time comparison.
  • Replaced API key equality checks with compare_digest() in AuthService.get_permissions().
  • Added new pytest coverage for API key permission resolution and byte/str normalization.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
app/service/auth_svc.py Uses constant-time comparison for API key auth and normalizes values via _ensure_str().
tests/test_compare_digest_auth.py Adds tests for API key permission behavior and _ensure_str() handling of bytes config values.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/service/auth_svc.py
def _ensure_str(value) -> str:
"""Convert a value to str safely, decoding bytes instead of using str() repr."""
if isinstance(value, bytes):
return value.decode('utf-8', errors='replace')
assert result == ()

def test_ensure_str_decodes_bytes(self):
"""bytes values should be decoded, not repr'd as 'b\"...\"'."""
@uruwhy
Copy link
Copy Markdown
Contributor

uruwhy commented Mar 27, 2026

obsolete: see #3257

@uruwhy uruwhy closed this Mar 27, 2026
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.

3 participants