Skip to content

Security

provide.foundation.security

TODO: Add module docstring.

Functions

mask_command

mask_command(
    cmd: str | list[str],
    secret_patterns: list[str] | None = None,
    masked: str = MASKED_VALUE,
) -> str

Mask secrets in command for safe logging.

Parameters:

Name Type Description Default
cmd str | list[str]

Command string or list to mask

required
secret_patterns list[str] | None

List of regex patterns to match secrets

None
masked str

Replacement value for matched secrets

MASKED_VALUE

Returns:

Type Description
str

Command string with secrets masked

Source code in provide/foundation/security/masking.py
def mask_command(
    cmd: str | list[str],
    secret_patterns: list[str] | None = None,
    masked: str = MASKED_VALUE,
) -> str:
    """Mask secrets in command for safe logging.

    Args:
        cmd: Command string or list to mask
        secret_patterns: List of regex patterns to match secrets
        masked: Replacement value for matched secrets

    Returns:
        Command string with secrets masked

    """
    # Convert to string if list
    cmd_str = " ".join(cmd) if isinstance(cmd, list) else cmd

    return mask_secrets(cmd_str, secret_patterns, masked)

mask_secrets

mask_secrets(
    text: str,
    secret_patterns: list[str] | None = None,
    masked: str = MASKED_VALUE,
) -> str

Mask secrets in text using regex patterns.

Parameters:

Name Type Description Default
text str

Text to mask secrets in

required
secret_patterns list[str] | None

List of regex patterns to match secrets

None
masked str

Replacement value for matched secrets

MASKED_VALUE

Returns:

Type Description
str

Text with secrets masked

Source code in provide/foundation/security/masking.py
def mask_secrets(
    text: str,
    secret_patterns: list[str] | None = None,
    masked: str = MASKED_VALUE,
) -> str:
    """Mask secrets in text using regex patterns.

    Args:
        text: Text to mask secrets in
        secret_patterns: List of regex patterns to match secrets
        masked: Replacement value for matched secrets

    Returns:
        Text with secrets masked

    """
    if secret_patterns is None:
        secret_patterns = DEFAULT_SECRET_PATTERNS

    result = text
    for pattern in secret_patterns:
        # Pattern should have 2 groups: (prefix)(secret_value)
        # We keep the prefix and mask the value
        result = re.sub(
            pattern,
            lambda m: f"{m.group(1)}{masked}",
            result,
            flags=re.IGNORECASE,
        )

    return result

sanitize_dict

sanitize_dict(
    data: dict[str, Any],
    sensitive_keys: list[str] | None = None,
    redacted: str = REDACTED_VALUE,
    recursive: bool = True,
) -> dict[str, Any]

Sanitize sensitive keys in dictionary for safe logging.

Parameters:

Name Type Description Default
data dict[str, Any]

Dictionary to sanitize

required
sensitive_keys list[str] | None

List of keys to redact (case-insensitive)

None
redacted str

Replacement value for redacted values

REDACTED_VALUE
recursive bool

Whether to recursively sanitize nested dicts

True

Returns:

Type Description
dict[str, Any]

Sanitized dictionary

Source code in provide/foundation/security/sanitization.py
def sanitize_dict(
    data: dict[str, Any],
    sensitive_keys: list[str] | None = None,
    redacted: str = REDACTED_VALUE,
    recursive: bool = True,
) -> dict[str, Any]:
    """Sanitize sensitive keys in dictionary for safe logging.

    Args:
        data: Dictionary to sanitize
        sensitive_keys: List of keys to redact (case-insensitive)
        redacted: Replacement value for redacted values
        recursive: Whether to recursively sanitize nested dicts

    Returns:
        Sanitized dictionary

    """
    if sensitive_keys is None:
        # Use combined list of headers and params as defaults
        sensitive_keys = DEFAULT_SENSITIVE_HEADERS + DEFAULT_SENSITIVE_PARAMS

    # Convert sensitive keys to lowercase for case-insensitive matching
    sensitive_lower = {k.lower() for k in sensitive_keys}

    sanitized: dict[str, Any] = {}
    for key, value in data.items():
        if key.lower() in sensitive_lower:
            sanitized[key] = redacted
        elif recursive and isinstance(value, dict):
            sanitized[key] = sanitize_dict(value, sensitive_keys, redacted, recursive)
        elif recursive and isinstance(value, list):
            # Sanitize list elements if they're dicts
            sanitized[key] = [
                sanitize_dict(item, sensitive_keys, redacted, recursive) if isinstance(item, dict) else item
                for item in value
            ]
        else:
            sanitized[key] = value

    return sanitized

sanitize_headers

sanitize_headers(
    headers: Mapping[str, Any],
    sensitive_headers: list[str] | None = None,
    redacted: str = REDACTED_VALUE,
) -> dict[str, Any]

Sanitize sensitive headers for safe logging.

Parameters:

Name Type Description Default
headers Mapping[str, Any]

Headers dictionary to sanitize

required
sensitive_headers list[str] | None

List of header names to redact (case-insensitive)

None
redacted str

Replacement value for redacted headers

REDACTED_VALUE

Returns:

Type Description
dict[str, Any]

Sanitized headers dictionary

Source code in provide/foundation/security/sanitization.py
def sanitize_headers(
    headers: Mapping[str, Any],
    sensitive_headers: list[str] | None = None,
    redacted: str = REDACTED_VALUE,
) -> dict[str, Any]:
    """Sanitize sensitive headers for safe logging.

    Args:
        headers: Headers dictionary to sanitize
        sensitive_headers: List of header names to redact (case-insensitive)
        redacted: Replacement value for redacted headers

    Returns:
        Sanitized headers dictionary

    """
    if sensitive_headers is None:
        sensitive_headers = DEFAULT_SENSITIVE_HEADERS

    # Convert sensitive headers to lowercase for case-insensitive matching
    sensitive_lower = {h.lower() for h in sensitive_headers}

    sanitized = {}
    for key, value in headers.items():
        if key.lower() in sensitive_lower:
            sanitized[key] = redacted
        else:
            sanitized[key] = value

    return sanitized

sanitize_uri

sanitize_uri(
    uri: str,
    sensitive_params: list[str] | None = None,
    redacted: str = REDACTED_VALUE,
) -> str

Sanitize sensitive query parameters in URI for safe logging.

Parameters:

Name Type Description Default
uri str

URI to sanitize

required
sensitive_params list[str] | None

List of parameter names to redact (case-insensitive)

None
redacted str

Replacement value for redacted parameters

REDACTED_VALUE

Returns:

Type Description
str

Sanitized URI string

Source code in provide/foundation/security/sanitization.py
def sanitize_uri(
    uri: str,
    sensitive_params: list[str] | None = None,
    redacted: str = REDACTED_VALUE,
) -> str:
    """Sanitize sensitive query parameters in URI for safe logging.

    Args:
        uri: URI to sanitize
        sensitive_params: List of parameter names to redact (case-insensitive)
        redacted: Replacement value for redacted parameters

    Returns:
        Sanitized URI string

    """
    if sensitive_params is None:
        sensitive_params = DEFAULT_SENSITIVE_PARAMS

    # Parse URI
    parsed = urlparse(uri)

    # If no query string, return as-is
    if not parsed.query:
        return uri

    # Parse query parameters
    params = parse_qs(parsed.query, keep_blank_values=True)

    # Convert sensitive params to lowercase for case-insensitive matching
    sensitive_lower = {p.lower() for p in sensitive_params}

    # Sanitize sensitive parameters
    sanitized_params = {}
    for key, values in params.items():
        if key.lower() in sensitive_lower:
            # Redact all values for this parameter
            sanitized_params[key] = [redacted] * len(values)
        else:
            sanitized_params[key] = values

    # Rebuild query string
    new_query = urlencode(sanitized_params, doseq=True)

    # Rebuild URI
    return urlunparse((parsed.scheme, parsed.netloc, parsed.path, parsed.params, new_query, parsed.fragment))

should_mask

should_mask(
    text: str, secret_patterns: list[str] | None = None
) -> bool

Check if text contains secrets that should be masked.

Parameters:

Name Type Description Default
text str

Text to check

required
secret_patterns list[str] | None

List of regex patterns to match secrets

None

Returns:

Type Description
bool

True if text contains secrets

Source code in provide/foundation/security/masking.py
def should_mask(text: str, secret_patterns: list[str] | None = None) -> bool:
    """Check if text contains secrets that should be masked.

    Args:
        text: Text to check
        secret_patterns: List of regex patterns to match secrets

    Returns:
        True if text contains secrets

    """
    if secret_patterns is None:
        secret_patterns = DEFAULT_SECRET_PATTERNS

    return any(re.search(pattern, text, flags=re.IGNORECASE) for pattern in secret_patterns)

should_sanitize_body

should_sanitize_body(content_type: str | None) -> bool

Determine if body should be sanitized based on content type.

Parameters:

Name Type Description Default
content_type str | None

Content-Type header value

required

Returns:

Type Description
bool

True if body should be sanitized

Source code in provide/foundation/security/sanitization.py
def should_sanitize_body(content_type: str | None) -> bool:
    """Determine if body should be sanitized based on content type.

    Args:
        content_type: Content-Type header value

    Returns:
        True if body should be sanitized

    """
    if not content_type:
        return False

    # Sanitize JSON and form data, skip binary formats
    content_type_lower = content_type.lower()
    return any(
        ct in content_type_lower
        for ct in [
            "application/json",
            "application/x-www-form-urlencoded",
            "multipart/form-data",
            "text/plain",
        ]
    )