Skip to content

credential_scanner

credential_scanner

Credential pattern scanner for agent output redaction.

Scans stdout_tail and stderr_tail for API key patterns before storage. This prevents credentials from propagating to CheckpointState, learning store, dashboard, diagnostics, and MCP resources.

Addresses F-003: No output scanning for credential patterns. Addresses F-023: Missing GitHub, Slack, and Hugging Face token patterns.

Key patterns detected: - Anthropic API keys: sk-ant-api (30+ chars after prefix) - OpenAI API keys: sk-proj-, sk-[a-zA-Z0-9]{20,} (not short sk- strings) - Google API keys: AIzaSy (35+ chars) - AWS access keys: AKIA (20 chars total) - Bearer tokens: Authorization: Bearer - GitHub tokens: ghp_, gho_, github_pat_ (36+ chars after prefix) - Slack tokens: xoxb-, xoxp-, xapp- (hyphenated segments) - Hugging Face tokens: hf_ (20+ alphanumeric chars after prefix)

The scanner is deliberately conservative — better to miss a non-credential than to redact legitimate output. False positives degrade output quality; false negatives are caught by log review.

Functions

redact_credentials

redact_credentials(text)

Redact credential patterns from text.

Scans the input for known API key patterns and replaces them with descriptive placeholders. Designed to be called on stdout_tail and stderr_tail before storage.

Parameters:

Name Type Description Default
text Any

String to scan. None is passed through unchanged.

required

Returns:

Type Description
Any

The text with credential patterns replaced by [REDACTED_*] labels.

Any

None if input was None.

Source code in src/marianne/utils/credential_scanner.py
def redact_credentials(text: Any) -> Any:
    """Redact credential patterns from text.

    Scans the input for known API key patterns and replaces them with
    descriptive placeholders. Designed to be called on stdout_tail and
    stderr_tail before storage.

    Args:
        text: String to scan. None is passed through unchanged.

    Returns:
        The text with credential patterns replaced by [REDACTED_*] labels.
        None if input was None.
    """
    if text is None:
        return None
    if not isinstance(text, str) or not text:
        return text

    result = text
    for pattern, replacement, _desc in _CREDENTIAL_PATTERNS:
        result = pattern.sub(replacement, result)
    return result

scan_for_credentials

scan_for_credentials(text)

Detect credential patterns without redacting.

Returns a list of human-readable descriptions of detected credential types. Useful for logging warnings when credentials are found.

Parameters:

Name Type Description Default
text str

String to scan.

required

Returns:

Type Description
list[str]

List of credential type descriptions found (empty if clean).

Source code in src/marianne/utils/credential_scanner.py
def scan_for_credentials(text: str) -> list[str]:
    """Detect credential patterns without redacting.

    Returns a list of human-readable descriptions of detected credential
    types. Useful for logging warnings when credentials are found.

    Args:
        text: String to scan.

    Returns:
        List of credential type descriptions found (empty if clean).
    """
    if not text:
        return []

    found: list[str] = []
    for pattern, _replacement, description in _CREDENTIAL_PATTERNS:
        if pattern.search(text):
            found.append(description)
    return found