Safeguard
Application Security•

ASVS 5.0 V16.2.5: Keep Sensitive Data Out of Your Logs

ASVS V16.2.5 asks that logging follows each data type's protection level: never log credentials, mask or hash tokens. Redaction code and a leak test.

Safeguard Security Team
3 min read

ASVS 5.0 V16.2.5 requires that when sensitive data might be logged, the application enforces rules based on its protection level. Some data, such as passwords and payment card numbers, must never be logged. Other data, such as session tokens, may be logged only hashed or masked. It is Level 2. Classify the data, then redact in code and test that nothing leaks.

Why it matters

Logs are copied widely, kept for months and read by many people. A password in a log is a credential leak that no access control on the application can undo. Real incidents have come from request body logging, verbose debug modes and exception messages that include connection strings.

How to implement it

First write a short classification table:

  • Never log: passwords, private keys, full card numbers, card security codes, API secrets.
  • Log masked or hashed: session ids, access tokens, email addresses, phone numbers.
  • Log freely: opaque internal ids, event names, status codes.

Then enforce it with a redaction layer at the logger, so it does not depend on each developer remembering:

import logging, re, hashlib

DENY_KEYS = {"password", "passwd", "secret", "card_number", "cvv", "private_key"}
HASH_KEYS = {"session_id", "access_token", "refresh_token"}

def scrub(fields: dict) -> dict:
    out = {}
    for k, v in fields.items():
        lk = k.lower()
        if lk in DENY_KEYS:
            out[k] = "[REDACTED]"
        elif lk in HASH_KEYS:
            out[k] = hashlib.sha256(str(v).encode()).hexdigest()[:12]
        else:
            out[k] = v
    return out

class Scrubber(logging.Filter):
    def filter(self, record):
        if isinstance(getattr(record, "fields", None), dict):
            record.fields = scrub(record.fields)
        return True

logging.getLogger().addFilter(Scrubber())

Pair this with pattern scanning for values that look like card numbers or keys in free text messages. Do not log whole request or response bodies by default. Turn off debug body logging in production.

Catch leaks before they ship with a secrets scan in CI, which finds credentials committed in code and test fixtures. The demo below shows the idea.

Click through it at your own pace, or open it full screen.

How to test it

  1. Run the main flows in a test environment with known canary values: a password such as Canary-Pw-91827, a fake card number from a test range and a recognizable token.
  2. Search all log destinations for each canary. Any hit is a failure.
  3. Force errors, such as a failed database connection and a malformed request, and check exception output.
  4. Enable the highest log level used in production and repeat.
  5. Keep the canary search as an automated test that runs on every release.

Common mistakes

  • Redacting by key name only, so the same value logged under a different key escapes.
  • Logging the full URL, which may hold tokens in the query string.
  • Hashing low entropy values such as phone numbers without a secret, which can be reversed by guessing.
  • Forgetting that third party libraries log request details on their own.

Quick check

Can I log a hashed password?

Do not. Credentials fall in the never log category, and hashes can still be attacked offline.

Which level is this?

Level 2.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.