Skip to content

Implement WootzApp header injection in Chromium after SAML authentication#362

Open
aashish1601 wants to merge 2 commits into
wootzapp:chromiumfrom
aashish1601:nginx
Open

Implement WootzApp header injection in Chromium after SAML authentication#362
aashish1601 wants to merge 2 commits into
wootzapp:chromiumfrom
aashish1601:nginx

Conversation

@aashish1601
Copy link
Copy Markdown
Contributor

@aashish1601 aashish1601 commented Aug 28, 2025

User description

📋 Overview

This PR implements a comprehensive header injection system in Chromium that automatically adds WootzApp-specific headers to outgoing HTTP requests for internal domains after successful SAML authentication. This enables zero-trust browser identification and access control for internal resources.

🎯 Problem Statement

Previously, the system relied on User-Agent header modification for browser identification, which proved unreliable in Chromium. Internal URLs were accessible from any browser, lacking proper access control based on SAML authentication status.

✅ Solution

Implemented a robust header injection system that:

  • Integrates with Chromium's NetworkDelegate for reliable header injection
  • Triggers after SAML authentication to ensure proper access control
  • Uses custom headers instead of unreliable User-Agent modification
  • Provides extensive logging for debugging and monitoring

🔧 Technical Implementation

C++ Code Changes

chromium/src/components/saml_verifier/saml_verifier.h

  • Added static member variables for header injection state management
  • Declared public accessor methods for header injection control
  • Added utility methods for header generation and domain validation

chromium/src/components/saml_verifier/saml_verifier.cc

  • Implemented GetWootzAppHeaders() function for header generation
  • Added ShouldInjectWootzAppHeaders() for domain validation
  • Integrated SAML authentication status tracking
  • Added comprehensive logging throughout the authentication flow
  • Key Change: Replaced unreliable User-Agent injection with X-WootzApp-Browser: true

chromium/src/services/network/network_service_network_delegate.cc

  • Modified OnBeforeStartTransaction() to inject headers for internal domains
  • Added integration with SAML verifier for header generation
  • Implemented extensive logging for request processing

Server-Side Configuration

nginx.conf

  • Added forwarding for all WootzApp headers to SAML bridge
  • Configured X-WootzApp-Browser and X-WootzApp-Version header forwarding

saml-bridge-service.py

  • Enhanced to validate X-WootzApp-Browser header as alternative to User-Agent
  • Improved logging for header validation and authentication flow

📦 Headers Injected

The system now injects the following headers for internal domains:

X-WootzApp-Client: true
X-Request-Source: wootzapp-browser
X-WootzApp-Browser: true
X-WootzApp-Version: 2.0
X-SAML-Auth-Status: true/false
X-SAML-Auth-Timestamp: <timestamp>
X-SAML-Auth-User-ID: <user_id>
X-SAML-Auth-User-Email: <user_email>

🔍 Logging and Debugging

Added comprehensive logging throughout the system:

Chromium C++ Logs

[WootzApp] 🔧 Generating headers for hostname: internal.aashish.icu
[WootzApp] ✅ INTERNAL DOMAIN DETECTED - Injecting headers
[WootzApp] 📦 Got 8 headers to inject
[WootzApp] 🚀 SENDING HEADERS TO INTERNAL URL:
[WootzApp]    📤 X-WootzApp-Browser = true
[WootzApp] ✅ Header injection COMPLETE

SAML Bridge Logs

WootzApp validation: X-WootzApp-Browser: 'true'
WootzApp validation result: True
Authorization granted - created new session

🧪 Testing

Manual Testing

  • ✅ Header injection verified in Chromium logs
  • ✅ SAML bridge receives and validates headers correctly
  • ✅ Internal URLs accessible only through WootzApp browser
  • ✅ External URLs bypass header injection as expected

Test Scripts

  • Created manual_test_headers.sh for curl-based testing
  • Created check_browser_headers.py for Python-based testing
  • Added test_header_debug.html for client-side debugging

🚀 Deployment

Prerequisites

  • Chromium build environment configured
  • Docker Compose setup for nginx and SAML bridge services

Steps

  1. Rebuild Chromium with updated C++ code
  2. Restart nginx to pick up configuration changes:
    docker-compose -f docker-compose-nginx.yml restart nginx
  3. Verify header injection through browser logs and SAML bridge logs

🔒 Security Impact

  • Enhanced Access Control: Internal URLs now require WootzApp browser identification
  • SAML Integration: Access granted only after successful SAML authentication
  • Header Validation: Server-side validation of browser identity headers
  • Zero-Trust Model: No implicit trust in browser identity

📈 Performance Impact

  • Minimal Overhead: Header injection only occurs for internal domains
  • Efficient Logging: Structured logging with clear prefixes for easy filtering
  • Optimized Validation: Domain matching uses efficient string operations

🔄 Backward Compatibility

  • External URLs: Unaffected by header injection
  • Existing SAML Flow: Maintains current authentication process
  • Server Configuration: Graceful handling of missing headers

🐛 Known Issues

  • User-Agent Injection: Intentionally removed due to Chromium limitations
  • Build Dependencies: Requires Chromium build environment

📚 Documentation

  • Updated NGINX_AUTHENTICATION_GUIDE.md with header injection details
  • Added inline code comments for complex C++ logic
  • Created testing guides for header validation

🎉 Results

After implementation:

  • 403 errors resolved: Internal URLs now accessible through WootzApp browser
  • Header validation working: SAML bridge correctly identifies WootzApp browser
  • SAML integration complete: Authentication status properly reflected in headers
  • Zero-trust model achieved: Internal resources protected by browser identification

Related Issues: #123 (Header injection for internal domains)
Breaking Changes: None
Dependencies: Chromium build environment, Docker Compose


PR Type

Enhancement


Description

  • Implement WootzApp header injection system for internal domain access control

  • Integrate SAML authentication status with HTTP request headers

  • Add comprehensive logging for debugging and monitoring

  • Remove duplicate Android copy-paste snackbar implementation


Diagram Walkthrough

flowchart LR
  A["SAML Authentication"] --> B["Header Injection State"]
  B --> C["Network Delegate"]
  C --> D["Internal Domain Check"]
  D --> E["Inject WootzApp Headers"]
  E --> F["HTTP Request"]
Loading

File Walkthrough

Relevant files
Miscellaneous
copy_paste_blocked_snackbar_bridge.cc
Remove duplicate copy-paste snackbar bridge                           

src/chrome/browser/android/renderer_context_menu/copy_paste_blocked_snackbar_bridge.cc

  • Complete file deletion (96 lines removed)
  • Removes duplicate Android JNI bridge implementation
+0/-96   
Enhancement
saml_verifier.cc
Implement SAML-based header injection system                         

src/components/saml_verifier/saml_verifier.cc

  • Add header injection state management after SAML authentication
  • Implement GetWootzAppHeaders() function for header generation
  • Add domain validation with ShouldInjectWootzAppHeaders()
  • Include comprehensive logging for authentication flow
+156/-0 
wootzapp_header_injector.cc
Add WootzApp header injection utilities                                   

src/components/saml_verifier/wootzapp_header_injector.cc

  • New file implementing global header injection functions
  • Add InjectWootzAppHeaders() for URLRequest integration
  • Include domain validation helper functions
+60/-0   
network_service_network_delegate.cc
Integrate header injection in network delegate                     

src/services/network/network_service_network_delegate.cc

  • Modify OnBeforeStartTransaction() to inject headers
  • Add integration with SAML verifier for internal domains
  • Include extensive logging for request processing
+36/-0   
saml_verifier.h
Add header injection interface declarations                           

src/components/saml_verifier/saml_verifier.h

  • Add static member variables for header injection state
  • Declare public accessor methods for authentication status
  • Add utility method declarations for header generation
+22/-0   
Configuration changes
BUILD.gn
Update build configuration for header injector                     

src/components/saml_verifier/BUILD.gn

  • Add wootzapp_header_injector.cc to build sources
+1/-0     

@qodo-code-review
Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 No relevant tests
🔒 Security concerns

Sensitive information exposure:
Multiple INFO logs print user IDs, emails, full URLs, and all injected headers. These can leak PII and authentication context into logs. Recommend:

  • Gate such logs behind a debug/verbose flag or DCHECK_IS_ON.
  • Redact or hash PII fields and avoid logging header values like X-SAML-Auth-User-Email.
  • Avoid logging full URLs if they may contain sensitive paths or query params.
    Additionally, the code injects headers with authentication state for any matching hostname. Ensure the internal domain list cannot be trivially controlled by an attacker (e.g., via subdomain tricks) and consider pinning to exact domains or PSL-aware matching.
⚡ Recommended focus areas for review

PII Logging

User identifiers and emails are logged in plaintext during header generation and SAML processing; this may leak sensitive data in logs and should be redacted or gated behind debug-only logging.

  if (attr.name == "user_id" || attr.name == "uid" || attr.name == "nameID") {
    if (!attr.values.empty()) {
      authenticated_user_id_ = attr.values[0];
      LOG(INFO) << "[WootzApp] Extracted User ID from SAML: " << authenticated_user_id_;
    }
  } else if (attr.name == "email" || attr.name == "mail") {
    if (!attr.values.empty()) {
      authenticated_user_email_ = attr.values[0];
      LOG(INFO) << "[WootzApp] Extracted User Email from SAML: " << authenticated_user_email_;
    }
  }
}

LOG(INFO) << "[WootzApp] ✅ SAML authentication successful - Header injection ENABLED";
LOG(INFO) << "[WootzApp] 📋 Authentication Summary:";
LOG(INFO) << "[WootzApp]    - User ID: " << authenticated_user_id_;
LOG(INFO) << "[WootzApp]    - User Email: " << authenticated_user_email_;
LOG(INFO) << "[WootzApp]    - SAML Authenticated: " << (saml_authenticated_ ? "YES" : "NO");
Hardcoded Domains

Internal domains are hardcoded; consider sourcing from configuration or enterprise policy to avoid rebuilds for changes and reduce risk of stale values.

bool SamlVerifier::header_injection_enabled_ = true;
std::vector<std::string> SamlVerifier::internal_domains_ = {
  "internal.aashish.icu",
  "app.internal.aashish.icu", 
  "admin.internal.aashish.icu",
  "api.internal.aashish.icu",
  "dashboard.internal.aashish.icu",
  "test.aashish.icu"
};
Verbose Logging

Extensive INFO-level request logging (including full URLs and injected headers) may flood logs and expose sensitive information; consider reducing log level, sampling, or redaction.

LOG(INFO) << "[WootzApp] 🌐 Processing request for: " << hostname;
LOG(INFO) << "[WootzApp] 🔗 Full URL: " << full_url;

bool injection_enabled = saml_verifier::SamlVerifier::IsHeaderInjectionEnabled();
bool saml_authenticated = saml_verifier::SamlVerifier::IsSamlAuthenticated();
std::string user_id = saml_verifier::SamlVerifier::GetAuthenticatedUserId();
std::string user_email = saml_verifier::SamlVerifier::GetAuthenticatedUserEmail();

LOG(INFO) << "[WootzApp] ⚙️  Header injection enabled: " << injection_enabled;
LOG(INFO) << "[WootzApp] 🔐 SAML authenticated: " << saml_authenticated;
LOG(INFO) << "[WootzApp] 👤 User ID: " << user_id;
LOG(INFO) << "[WootzApp] 📧 User Email: " << user_email;

if (saml_verifier::SamlVerifier::ShouldInjectWootzAppHeaders(hostname)) {
  LOG(INFO) << "[WootzApp] ✅ INTERNAL DOMAIN DETECTED - Injecting headers for: " << hostname;

  auto wootzapp_headers = saml_verifier::SamlVerifier::GetWootzAppHeaders(hostname);
  LOG(INFO) << "[WootzApp] 📦 Got " << wootzapp_headers.size() << " headers to inject";

  LOG(INFO) << "[WootzApp] 🚀 SENDING HEADERS TO INTERNAL URL:";
  for (const auto& [name, value] : wootzapp_headers) {
    request->SetExtraRequestHeaderByName(name, value, true);
    LOG(INFO) << "[WootzApp]    📤 " << name << " = " << value;
  }

  LOG(INFO) << "[WootzApp] ✅ Header injection COMPLETE for: " << hostname;
  LOG(INFO) << "[WootzApp] 🎯 Request ready to send to internal domain with WootzApp headers";
} else {
  LOG(INFO) << "[WootzApp] ❌ Not an internal domain - skipping header injection for: " << hostname;
}

@qodo-code-review
Copy link
Copy Markdown

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Fix cross-process state and layering

The network service reads SAML/auth state via static globals in SamlVerifier,
but that state lives in a different process (and thread), causing broken
behavior and violating Chromium layering by including components code in
services/network. Replace the static, process-local state with per-profile,
thread-safe state plumbed to the network service via Mojo/NetworkContext params
or perform header injection in the browser process (e.g., URLLoaderThrottle)
where the SAML state resides. This avoids layering violations, ensures
correctness across processes, and prevents race conditions.

Examples:

src/services/network/network_service_network_delegate.cc [118-131]
  bool injection_enabled = saml_verifier::SamlVerifier::IsHeaderInjectionEnabled();
  bool saml_authenticated = saml_verifier::SamlVerifier::IsSamlAuthenticated();
  std::string user_id = saml_verifier::SamlVerifier::GetAuthenticatedUserId();
  std::string user_email = saml_verifier::SamlVerifier::GetAuthenticatedUserEmail();
  
  LOG(INFO) << "[WootzApp] ⚙️  Header injection enabled: " << injection_enabled;
  LOG(INFO) << "[WootzApp] 🔐 SAML authenticated: " << saml_authenticated;
  LOG(INFO) << "[WootzApp] 👤 User ID: " << user_id;
  LOG(INFO) << "[WootzApp] 📧 User Email: " << user_email;
  

 ... (clipped 4 lines)
src/components/saml_verifier/saml_verifier.h [331-335]
  static bool header_injection_enabled_;
  static std::vector<std::string> internal_domains_;
  static std::string authenticated_user_id_;
  static std::string authenticated_user_email_;
  static bool saml_authenticated_;

Solution Walkthrough:

Before:

// In services/network (Network Process)
class NetworkServiceNetworkDelegate {
  OnBeforeStartTransaction(...) {
    // Reads state from static variables in its own process
    if (saml_verifier::SamlVerifier::ShouldInjectWootzAppHeaders(hostname)) {
      auto headers = saml_verifier::SamlVerifier::GetWootzAppHeaders(hostname);
      // ... inject headers
    }
  }
}

// In components/saml_verifier (Browser Process)
class SamlVerifier {
  // State is stored in static variables, local to the browser process
  static bool saml_authenticated_;
  
  ProcessSamlResponse(...) {
    // State is written in the browser process
    saml_authenticated_ = true;
  }
}

After:

// Option 1: Plumb state via Mojo to Network Service

// In browser process
// NetworkContext is configured with the necessary state
network_context_params->wootzapp_auth_info = GetAuthInfo(); // Contains user ID, email, etc.
network_service->CreateNetworkContext(..., std::move(network_context_params));

// In services/network (Network Process)
class CustomHeaderInjector {
  // State is received from browser process and stored per-NetworkContext
  WootzAppAuthInfo auth_info_;
  
  InjectHeaders(request) {
    // Uses local, correctly plumbed state
    if (auth_info_.is_authenticated) {
      // ... add headers
    }
  }
}
Suggestion importance[1-10]: 10

__

Why: This suggestion correctly identifies a critical architectural flaw: the use of process-local static variables for state shared between the browser process and the network service process, which will not work and is a major layering violation.

High
General
Remove unreachable code

There is unreachable code after the return statement. The second return false;
will never be executed and should be removed to avoid compiler warnings and
improve code clarity.

src/components/saml_verifier/wootzapp_header_injector.cc [52-55]

 // Check if this is an internal domain
 return SamlVerifier::ShouldInjectWootzAppHeaders(hostname);
 
-return false;
-
  • Apply / Chat
Suggestion importance[1-10]: 4

__

Why: The suggestion correctly identifies and removes an unreachable return false; statement, which improves code clarity and prevents potential compiler warnings.

Low
  • More

@1311-hack1
Copy link
Copy Markdown
Contributor

This feature is on standby, we should not close this PR, nor merge it right now. @pandey019

@pandey019
Copy link
Copy Markdown
Collaborator

@1311-hack1 convert this code to new code architecture

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants