Skip to content

How to Use Security Utilities

Foundation provides security utilities for protecting sensitive data in logs, command output, and API requests.

Overview

The security module provides two main categories:

  • Secret Masking: Automatically hide secrets in logs and command output
  • Data Sanitization: Remove sensitive data from HTTP headers, URIs, and dictionaries

Secret Masking

Automatic Secret Detection

Foundation automatically masks common secret patterns:

from provide.foundation.security import mask_secrets

# Masks API keys, tokens, passwords
text = "API_KEY=sk-1234567890abcdef DATABASE_PASSWORD=secret123"
masked = mask_secrets(text)
print(masked)
# "API_KEY=***MASKED*** DATABASE_PASSWORD=***MASKED***"

Default patterns masked: - API keys (api_key, apikey) - Tokens (token, auth_token, access_token) - Passwords (password, passwd, pwd) - Secrets (secret, client_secret) - Credentials (credentials) - Private keys (private_key, priv_key)

Custom Secret Patterns

Add your own secret patterns:

from provide.foundation.security import mask_secrets, DEFAULT_SECRET_PATTERNS

# Add custom pattern
custom_patterns = DEFAULT_SECRET_PATTERNS + [
    r"CUSTOM_SECRET=[^\s]+",
    r"SESSION_ID=[^\s]+"
]

text = "CUSTOM_SECRET=abc123 SESSION_ID=xyz789"
masked = mask_secrets(text, patterns=custom_patterns)

Mask Command Output

Protect secrets in shell command output:

from provide.foundation.security import mask_command

# Mask secrets in command strings
command = "curl -H 'Authorization: Bearer secret-token' https://api.example.com"
masked = mask_command(command)
print(masked)
# "curl -H 'Authorization: Bearer ***MASKED***' https://api.example.com"

Check If Should Mask

Test if a string contains secrets:

from provide.foundation.security import should_mask

if should_mask("password=secret123"):
    print("Contains secrets - mask before logging")

Data Sanitization

Sanitize HTTP Headers

Remove sensitive headers from HTTP requests/responses:

from provide.foundation.security import sanitize_headers

headers = {
    "Authorization": "Bearer secret-token",
    "X-API-Key": "api-key-123",
    "Content-Type": "application/json",
    "User-Agent": "MyApp/1.0"
}

safe_headers = sanitize_headers(headers)
print(safe_headers)
# {
#     "Authorization": "***REDACTED***",
#     "X-API-Key": "***REDACTED***",
#     "Content-Type": "application/json",
#     "User-Agent": "MyApp/1.0"
# }

Default sensitive headers: - Authorization - X-API-Key, API-Key - Cookie, Set-Cookie - X-Auth-Token, X-Access-Token - Proxy-Authorization

Sanitize URIs

Remove sensitive query parameters:

from provide.foundation.security import sanitize_uri

uri = "https://api.example.com/users?api_key=secret123&user_id=456"
safe_uri = sanitize_uri(uri)
print(safe_uri)
# "https://api.example.com/users?api_key=***REDACTED***&user_id=456"

Default sensitive parameters: - api_key, apikey - token, access_token, auth_token - password, passwd - secret, client_secret - key

Sanitize Dictionaries

Recursively sanitize dictionary data:

from provide.foundation.security import sanitize_dict

data = {
    "user_id": "user_123",
    "api_key": "secret-key",
    "settings": {
        "password": "secret-password",
        "theme": "dark"
    }
}

safe_data = sanitize_dict(data)
print(safe_data)
# {
#     "user_id": "user_123",
#     "api_key": "***REDACTED***",
#     "settings": {
#         "password": "***REDACTED***",
#         "theme": "dark"
#     }
# }

Integration with Logging

Combine with Foundation logger for secure logging:

from provide.foundation import logger
from provide.foundation.security import sanitize_dict, mask_secrets

def log_api_request(url: str, headers: dict, body: dict):
    """Log API request with sanitized data."""
    from provide.foundation.security import sanitize_headers

    logger.info(
        "api_request_sent",
        url=url,
        headers=sanitize_headers(headers),
        body=sanitize_dict(body)
    )

# Usage
log_api_request(
    url="https://api.example.com/users",
    headers={"Authorization": "Bearer secret-token"},
    body={"password": "secret123", "email": "[email protected]"}
)
# Logs with sensitive data masked

Configuration

Automatic Sanitization

Foundation can automatically sanitize logs:

from provide.foundation import get_hub, LoggingConfig, TelemetryConfig

config = TelemetryConfig(
    logging=LoggingConfig(
        sanitization_enabled=True,  # Enable auto-sanitization
        sanitization_sanitize_dicts=True,  # Sanitize dict values
        sanitization_mask_patterns=[  # Custom patterns
            r"CUSTOM_KEY=[^\s]+",
        ]
    )
)

hub = get_hub()
hub.initialize_foundation(config)

Environment Variables

# Enable sanitization
export PROVIDE_LOG_SANITIZATION_ENABLED=true

# Sanitize dictionaries
export PROVIDE_LOG_SANITIZATION_SANITIZE_DICTS=true

# Add custom patterns (comma-separated)
export PROVIDE_LOG_SANITIZATION_MASK_PATTERNS="CUSTOM_KEY=[^\\s]+,SESSION=[^\\s]+"

Best Practices

✅ DO: Sanitize Before Logging

# ✅ Good: Sanitize sensitive data
from provide.foundation import logger
from provide.foundation.security import sanitize_dict

logger.info("user_data", data=sanitize_dict(user_dict))

# ❌ Bad: Log raw sensitive data
logger.info("user_data", data=user_dict)  # May contain passwords!

✅ DO: Mask Command Output

# ✅ Good: Mask commands before logging
from provide.foundation import logger
from provide.foundation.security import mask_command

cmd = "curl -H 'X-API-Key: secret' https://api.example.com"
logger.debug("executing_command", command=mask_command(cmd))

# ❌ Bad: Log commands directly
logger.debug("executing_command", command=cmd)  # Exposes API key!

✅ DO: Use Custom Patterns for Domain-Specific Secrets

# ✅ Good: Add domain-specific patterns
from provide.foundation.security import mask_secrets, DEFAULT_SECRET_PATTERNS

COMPANY_PATTERNS = DEFAULT_SECRET_PATTERNS + [
    r"INTERNAL_TOKEN=[^\s]+",
    r"VENDOR_KEY=[^\s]+"
]

text = "INTERNAL_TOKEN=abc123 VENDOR_KEY=xyz789"
safe = mask_secrets(text, patterns=COMPANY_PATTERNS)

# ❌ Bad: Rely only on default patterns
safe = mask_secrets(text)  # May miss company-specific secrets

✅ DO: Sanitize User Input

# ✅ Good: Sanitize user-provided data
from provide.foundation.security import sanitize_dict

def process_user_input(data: dict):
    safe_data = sanitize_dict(data)
    store_in_database(safe_data)

# ❌ Bad: Store raw user input
def process_user_input_bad(data: dict):
    store_in_database(data)  # May contain passwords/tokens!

Common Patterns

Secure HTTP Client

from provide.foundation import logger
from provide.foundation.security import sanitize_headers, sanitize_uri
import httpx

def secure_http_request(method: str, url: str, **kwargs):
    """Make HTTP request with secure logging."""
    headers = kwargs.get("headers", {})

    logger.info(
        "http_request_started",
        method=method,
        url=sanitize_uri(url),
        headers=sanitize_headers(headers)
    )

    response = httpx.request(method, url, **kwargs)

    logger.info(
        "http_request_completed",
        status_code=response.status_code,
        headers=sanitize_headers(dict(response.headers))
    )

    return response

Secure Configuration Logging

from provide.foundation import logger
from provide.foundation.security import sanitize_dict

def log_application_config(config: dict):
    """Log application configuration securely."""
    # Sanitize before logging
    safe_config = sanitize_dict(config)

    logger.info(
        "application_configured",
        config=safe_config
    )

Next Steps


Tip: Always sanitize data before logging to prevent accidental secret exposure.