Skip to content

DEP-161: Mask sensitive information in API logs - #2733

Merged
NatSquared merged 16 commits into
mainfrom
DEP-161-mask-sensitive-info
Feb 4, 2026
Merged

DEP-161: Mask sensitive information in API logs#2733
NatSquared merged 16 commits into
mainfrom
DEP-161-mask-sensitive-info

Conversation

@NatSquared

Copy link
Copy Markdown
Collaborator

Issue #: 🎟️ DEP-161

Description of changes:

  • Security Implemented automatic sensitive data masking in API logs.
    • Added logging filter to automatically mask passwords, tokens, API keys, and database credentials
    • Replaced print() statements with logger calls to ensure masking coverage
    • Added linting rules (pylint + flake8) to prevent print statements and enforce logging
    • Updated logging configuration to apply masking app-wide
    • See met_api.utils.logging_masker for implementation details

User Guide update ticket (if applicable):

    • Yes, a user guide update ticket has been created. (Link to ticket)
    • No, a user guide update ticket is not required.

Common component changes:

    • Yes, I have updated CONTRIBUTING.md and the docblocks to document any changes to the common components.
    • No, there are no changes to the common components.

Comment thread met-api/src/met_api/utils/logging_masker.py Fixed
@codecov-commenter

codecov-commenter commented Feb 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 73.91304% with 24 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.91%. Comparing base (f96a0cc) to head (dad93f6).
⚠️ Report is 7 commits behind head on main.

Files with missing lines Patch % Lines
met-api/src/met_api/utils/logging_masker.py 74.60% 16 Missing ⚠️
met-api/src/met_api/services/engagement_service.py 0.00% 3 Missing ⚠️
met-api/src/met_api/models/engagement.py 50.00% 1 Missing ⚠️
met-api/src/met_api/resources/staff_user.py 0.00% 1 Missing ⚠️
met-api/src/met_api/resources/tenant.py 50.00% 1 Missing ⚠️
met-api/src/met_api/services/rest_service.py 50.00% 1 Missing ⚠️
met-api/src/met_api/utils/notification.py 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2733      +/-   ##
==========================================
+ Coverage   74.89%   74.91%   +0.01%     
==========================================
  Files         543      544       +1     
  Lines       20377    20451      +74     
  Branches     1578     1578              
==========================================
+ Hits        15262    15321      +59     
- Misses       5108     5123      +15     
  Partials        7        7              
Flag Coverage Δ
metapi 86.31% <73.91%> (-0.05%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
met-api/src/met_api/__init__.py 91.96% <100.00%> (+1.05%) ⬆️
met-api/src/met_api/config.py 97.29% <ø> (-0.04%) ⬇️
met-api/src/met_api/services/cdogs_api_service.py 59.01% <ø> (-0.36%) ⬇️
met-api/src/met_api/utils/datetime.py 50.00% <100.00%> (ø)
met-api/src/met_api/utils/filter_types.py 100.00% <100.00%> (ø)
met-api/src/met_api/utils/roles.py 100.00% <100.00%> (ø)
met-api/src/met_api/utils/tenant_validator.py 85.00% <100.00%> (ø)
met-api/src/met_api/models/engagement.py 78.86% <50.00%> (+0.10%) ⬆️
met-api/src/met_api/resources/staff_user.py 84.89% <0.00%> (ø)
met-api/src/met_api/resources/tenant.py 79.26% <50.00%> (ø)
... and 4 more
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jareth-whitney jareth-whitney left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job obfuscating sensitive data and updating logging behaviour! As usual, nothing blocking, just some things to check out before merging:

  • Idea: wrap the print function and deprecate it, so IDE's can provide a nice little tooltip-style message to the coder
  • Logging removed without replacement (likely intentional)
  • Singular vs plural engagement/engagements
  • Possible opportunities for map function to keep things concise
  • Copyright 2021 vs 2026 (not sure which is right)

def visit_call(self, node: nodes.Call) -> None:
"""Check if the node is a print function call."""
if isinstance(node.func, nodes.Name) and node.func.name == "print":
# Allow print in config.py for startup messages before logging is initialized

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice caveat

Print statements bypass logging masking and pose a security risk by potentially
exposing sensitive data (passwords, tokens, API keys, etc.) in logs.

Use current_app.logger or logging.getLogger(__name__) instead.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is a crazy idea, maybe we could wrap the print function in a user defined function that is deprecated, so the coder sees a pretty little on-hover suggestion to use current_app.logger when they try to use print.

import builtins
builtins.print = deprecated_print

"""Update scheduled engagements to published."""
datetime_due = datetime.utcnow()
print('Publish due date (UTC) ------------------------', datetime_due)
logging.getLogger(__name__).debug('Publish due date (UTC): %s', datetime_due)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specifiers bring me back to php <3

template = {'template': ('template', file_handle, 'multipart/form-data')}

current_app.logger.info('Uploading template %s', template_file_path)
print('Uploading template %s', template_file_path)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sometimes logging is just too verbose. I'm guessing it's intentional to have no replacement logging here.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes; plus it exposed file path data which I couldn't think of a good way to parse separately from URL paths.

True,
)
print('Engagements published added to email queue: ', engagement.id)
current_app.logger.info('Engagement published added to email queue: %s', engagement.id)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Engagement/engagements, singular vs plural

"""

# Mask string to replace sensitive data
MASK = '***REDACTED***'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now we're working for the government!

if any(sensitive in key.lower() for sensitive in sensitive_keys):
masked_data[key] = '***REDACTED***'
elif isinstance(value, dict):
masked_data[key] = mask_dict(value, sensitive_keys)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice recursion

if not callable(value):
config_dict[attr] = value

masked_dict = mask_dict(config_dict)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if this could be achieved with a map.

@@ -0,0 +1,82 @@
# Copyright © 2021 Province of British Columbia

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if the date should stay at 2021 or be 2026, just flagging it in case

msg=normal_message, args=(), exc_info=None
)
filter_obj.filter(log_record)
assert log_record.msg == normal_message

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding tests as well.

@sonarqubecloud

sonarqubecloud Bot commented Feb 4, 2026

Copy link
Copy Markdown

@NatSquared
NatSquared merged commit a63d134 into main Feb 4, 2026
12 checks passed
@NatSquared
NatSquared deleted the DEP-161-mask-sensitive-info branch February 4, 2026 23:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants