Skip to content

🛡️ Sentinel: [HIGH] Fix insecure anonymous session management#102

Open
guangtouwangba wants to merge 1 commit intomasterfrom
sentinel-auth-fix-10163844036524269235
Open

🛡️ Sentinel: [HIGH] Fix insecure anonymous session management#102
guangtouwangba wants to merge 1 commit intomasterfrom
sentinel-auth-fix-10163844036524269235

Conversation

@guangtouwangba
Copy link
Owner

  • Vulnerability: Anonymous sessions were generated from User-Agent hashing, causing session collisions for users with identical browser configurations.
  • Fix: Implemented unique session ID generation.
    • Frontend: Generates a UUIDv4 (stored in localStorage) and sends it via X-Anonymous-ID header.
    • Backend: Prioritizes X-Anonymous-ID for session identification, falling back to User-Agent for legacy clients.
  • Verification: Added backend unit tests (tests/unit/test_auth_anon.py) verifying unique IDs are generated when the header is present.
  • Journal: Added entry to .jules/sentinel.md.

- **Vulnerability:** Anonymous sessions were generated from User-Agent hashing, causing session collisions for users with identical browser configurations.
- **Fix:** Implemented unique session ID generation.
    - Frontend: Generates a UUIDv4 (stored in localStorage) and sends it via `X-Anonymous-ID` header.
    - Backend: Prioritizes `X-Anonymous-ID` for session identification, falling back to User-Agent for legacy clients.
- **Verification:** Added backend unit tests (`tests/unit/test_auth_anon.py`) verifying unique IDs are generated when the header is present.
- **Journal:** Added entry to `.jules/sentinel.md`.
@gemini-code-assist
Copy link

Summary of Changes

Hello @guangtouwangba, 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 significantly enhances the security of anonymous user sessions by moving away from a User-Agent-based identification system, which was prone to collisions. The new approach introduces a robust, client-generated unique identifier that is prioritized by the backend, ensuring that each anonymous user maintains a distinct and secure session. This change mitigates the risk of data leakage and unauthorized access for anonymous users.

Highlights

  • Security Fix for Anonymous Sessions: Addressed a high-severity vulnerability where anonymous sessions were insecurely generated using User-Agent hashing, leading to potential session collisions for users with identical browser configurations.
  • Unique Session ID Generation: Implemented a new mechanism for anonymous session identification. The frontend now generates a unique UUIDv4 (stored in localStorage) and sends it via an X-Anonymous-ID header. The backend prioritizes this header for session identification, falling back to User-Agent hashing only for legacy clients.
  • Backend Unit Tests: New unit tests (tests/unit/test_auth_anon.py) were added to verify that unique anonymous IDs are correctly generated when the X-Anonymous-ID header is present, and to confirm the fallback behavior.
  • Changelog Documentation: An entry detailing the vulnerability, the lessons learned, and the implemented prevention strategy has been added to .jules/sentinel.md.
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 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 counter productive. 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.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

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.

Copy link

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

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 effectively resolves the insecure anonymous session management vulnerability by replacing the User-Agent-based session ID with a client-generated unique ID. The backend implementation correctly prioritizes the new X-Anonymous-ID header while maintaining backward compatibility. The addition of unit tests is also a great step towards ensuring the reliability of this security fix. I have provided a couple of suggestions to further enhance the security of the client-side ID generation and improve the test coverage for an important edge case on the backend.

assert user.is_anonymous is True
# Hash of empty string
expected_hash = hashlib.sha256(b"").hexdigest()[:16]
assert user.user_id == f"anon-{expected_hash}"

Choose a reason for hiding this comment

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

medium

The test coverage for the new anonymous session logic is good. To make it even more robust, I suggest adding a test case for when the x-anonymous-id header is present but contains an empty string. The current backend implementation should fall back to using the user-agent in this scenario, and verifying this behavior will cover an important edge case.

Suggested change
assert user.user_id == f"anon-{expected_hash}"
@pytest.mark.asyncio
async def test_empty_header_fallback_to_user_agent(self):
"""Test that an empty X-Anonymous-ID header falls back to User-Agent."""
user_agent = "Mozilla/5.0 (Test)"
req = MagicMock(spec=Request)
req.headers = {
"user-agent": user_agent,
"x-anonymous-id": ""
}
user = await get_optional_user(request=req, authorization=None)
assert user.is_anonymous is True
# Verify it uses UA hash
expected_hash = hashlib.sha256(user_agent.encode()).hexdigest()[:16]
assert user.user_id == f"anon-{expected_hash}"

Comment on lines +382 to +392
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
id = crypto.randomUUID();
} else {
// Fallback for older browsers / non-secure contexts
id = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (
c
) {
const r = (Math.random() * 16) | 0,
v = c === 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});

Choose a reason for hiding this comment

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

medium

The fallback for UUID generation uses Math.random(), which is not cryptographically secure. This is particularly relevant because crypto.randomUUID() is unavailable in non-secure contexts (e.g., http://localhost during development), causing this insecure fallback to be used.

To enhance security, I recommend using crypto.getRandomValues(), which is cryptographically secure and more widely supported, especially in non-secure contexts. This ensures that even in development or older browsers, the generated ID has strong randomness.

    if (typeof crypto !== 'undefined' && crypto.randomUUID) {
      id = crypto.randomUUID();
    } else if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
      // Fallback for browsers without crypto.randomUUID (e.g. in non-secure contexts).
      // This is more secure than using Math.random().
      const rnds = new Uint8Array(16);
      crypto.getRandomValues(rnds);
      rnds[6] = (rnds[6] & 0x0f) | 0x40; // Version 4
      rnds[8] = (rnds[8] & 0x3f) | 0x80; // Variant RFC4122
      const hex: string[] = [];
      for (let i = 0; i < 16; ++i) {
        hex.push((rnds[i] + 0x100).toString(16).substring(1));
      }
      id = `${hex.slice(0, 4).join('')}-${hex.slice(4, 6).join('')}-${hex.slice(6, 8).join('')}-${hex.slice(8, 10).join('')}-${hex.slice(10, 16).join('')}`;
    } else {
      // Insecure fallback for very old browsers without any crypto API.
      console.warn('Generating insecure anonymous ID. Use a modern browser for better security.');
      id = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
        const r = (Math.random() * 16) | 0,
          v = c === 'x' ? r : (r & 0x3) | 0x8;
        return v.toString(16);
      });
    }

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.

1 participant