Skip to content

Feature log masking#38

Open
tr-emp-260 wants to merge 14 commits intomasterfrom
feature-log-masking
Open

Feature log masking#38
tr-emp-260 wants to merge 14 commits intomasterfrom
feature-log-masking

Conversation

@tr-emp-260
Copy link
Copy Markdown

@tr-emp-260 tr-emp-260 commented Mar 27, 2026

Summary by CodeRabbit

  • New Features

    • Log output now redacts sensitive information (emails, phone numbers, names, and address fields) before being recorded.
  • Tests

    • Jest is configured as the project test runner.
  • Chores

    • Added combined.log to ignored files to prevent it from being tracked.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 27, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d8f4aa12-d9fa-46c1-97de-f83063cba092

📥 Commits

Reviewing files that changed from the base of the PR and between 88ee734 and 37df9e8.

📒 Files selected for processing (1)
  • src/logger/mask.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/logger/mask.js

📝 Walkthrough

Walkthrough

Adds a masking utility to redact sensitive fields in logs and integrates it into the logger; updates the test script to run Jest and adds Jest as a devDependency; and ignores combined.log via .gitignore.

Changes

Cohort / File(s) Summary
Configuration
\.gitignore, package.json
Ignored combined.log. Replaced placeholder test script with "jest" and added devDependencies.jest = ^30.2.0.
Logging — mask utility
src/logger/mask.js
Added maskData(data) exporting masking logic: regex redaction for emails/phones, name-specific masking, field-based exceptions (_id, id, org_id, user_id, campaign_id, publisher_id), recursive object traversal with circular reference detection, region address field obfuscation, and opt-out via process.env.MASK_LOGS === "false".
Logging — integration
src/logger/logger.js
Imported maskData and updated log formatting to sanitize info.message using maskData; if masking returns an object it is JSON.stringified before embedding in the formatted log string.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Logger
  participant Mask
  participant FileSystem

  Client->>Logger: send log info (level, message, meta)
  Logger->>Mask: maskData(info.message)
  Mask-->>Logger: maskedMessage
  Logger->>FileSystem: write formatted log (uses maskedMessage) to combined.log / stdout
  FileSystem-->>Logger: write ACK
  Logger-->>Client: (completed)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I nibbled through logs with care,

I blurred the names and numbers there.
Jest hops in to run the test,
combined.log now takes a rest.
A tidy hop — the logs feel fair.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feature log masking' directly summarizes the main change: introducing log masking functionality across the codebase via new mask.js module and updated logger integration.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature-log-masking

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/logger/mask.js (2)

104-114: Region masking logic is correct but slightly confusing.

The recursive maskObject(value, seen) call processes all nested fields, but then address, city, state, and zipcode are immediately overwritten with MASK (if truthy) or their original falsy value. This means:

  1. Any regex masking applied to these four fields by maskObject is discarded
  2. The spread ...maskedRegion is only useful for preserving other nested fields that aren't explicitly listed

Consider documenting this intent or simplifying by excluding these fields from the recursive call if they'll always be fully masked anyway.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/logger/mask.js` around lines 104 - 114, The region masking block calls
maskObject(value, seen) then immediately overwrites address/city/state/zipcode
which discards any regex-based masks; update the logic so either (a) exclude
these four fields from the recursive mask call and then spread the remaining
maskedRegion fields into cloned[key] before setting address/city/state/zipcode
to MASK (if truthy), or (b) keep the recursive call but remove those keys from
its result before overwriting—make the change in the if (key === "region" ...)
branch referencing maskObject, MASK, cloned, and the address/city/state/zipcode
fields and add a short comment explaining the chosen approach.

10-13: Phone regex may produce false positives.

The pattern /\+?\d[\d\s\-]{8,}\d/g will match any sequence of 10+ digits with optional spaces/hyphens. This could inadvertently mask numeric identifiers like order IDs, tracking numbers, or timestamps that happen to be long numeric strings.

Consider narrowing the pattern or adding additional heuristics if false positives become problematic in practice.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/logger/mask.js` around lines 10 - 13, The phone-matching rule using the
regex /\+?\d[\d\s\-]{8,}\d/g is too broad and may mask long numeric IDs; narrow
it by tightening the pattern and/or adding heuristics in the mask pipeline:
update the regex (or add a new predicate around it) to require phone-like cues
(e.g., leading +country code, area-code parentheses, common separator patterns,
or word boundaries) and check the candidate string with the existing maskPhone
function to confirm it looks like a real phone before masking; locate the rule
object that contains the regex and replacer referencing maskPhone and modify it
to include these additional checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/logger/mask.js`:
- Around line 104-114: The region masking block calls maskObject(value, seen)
then immediately overwrites address/city/state/zipcode which discards any
regex-based masks; update the logic so either (a) exclude these four fields from
the recursive mask call and then spread the remaining maskedRegion fields into
cloned[key] before setting address/city/state/zipcode to MASK (if truthy), or
(b) keep the recursive call but remove those keys from its result before
overwriting—make the change in the if (key === "region" ...) branch referencing
maskObject, MASK, cloned, and the address/city/state/zipcode fields and add a
short comment explaining the chosen approach.
- Around line 10-13: The phone-matching rule using the regex
/\+?\d[\d\s\-]{8,}\d/g is too broad and may mask long numeric IDs; narrow it by
tightening the pattern and/or adding heuristics in the mask pipeline: update the
regex (or add a new predicate around it) to require phone-like cues (e.g.,
leading +country code, area-code parentheses, common separator patterns, or word
boundaries) and check the candidate string with the existing maskPhone function
to confirm it looks like a real phone before masking; locate the rule object
that contains the regex and replacer referencing maskPhone and modify it to
include these additional checks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ef91936a-c3bc-46bf-b744-bba30ffabc45

📥 Commits

Reviewing files that changed from the base of the PR and between 1877637 and 249b7a4.

📒 Files selected for processing (5)
  • .gitignore
  • README.md
  • package.json
  • src/logger/logger.js
  • src/logger/mask.js

@tr-emp-260 tr-emp-260 requested a review from tr-emp-40 March 27, 2026 08:28
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/logger/mask.js (1)

109-118: Region field handling has redundant processing and inconsistent behavior.

The code first recursively masks value (which would apply regex masking to any string fields including address, city, etc.), then spreads the result, and finally overwrites address-related fields with MASK if the original values were truthy. This causes:

  1. Redundant work: String fields are regex-masked, then immediately overwritten with MASK.
  2. Inconsistent masking: Address fields inside region get hard-masked with ********, but address fields in other nested objects only get regex-masked.

Consider simplifying by either skipping the recursive call for region and only applying the explicit field masking, or documenting why region needs this special treatment.

Suggested simplification
         if (key === "region" && value !== null && typeof value === "object") {
-            const maskedRegion = maskObject(value, seen);
-            cloned[key] = {
-                ...maskedRegion,
-                address: value.address ? MASK : value.address,
-                city: value.city ? MASK : value.city,
-                state: value.state ? MASK : value.state,
-                zipcode: value.zipcode ? MASK : value.zipcode
-            };
+            // Hard-mask known PII fields, recursively process others
+            const maskedRegion = {};
+            for (const regionKey of Object.keys(value)) {
+                if (['address', 'city', 'state', 'zipcode'].includes(regionKey)) {
+                    maskedRegion[regionKey] = value[regionKey] ? MASK : value[regionKey];
+                } else {
+                    maskedRegion[regionKey] = maskObject(value[regionKey], seen);
+                }
+            }
+            cloned[key] = maskedRegion;
             return;
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/logger/mask.js` around lines 109 - 118, For the special-case handling of
the "region" object in src/logger/mask.js, remove the initial recursive call
maskObject(value, seen) (which causes redundant regex-masking) and instead build
cloned[key] by: copying non-address fields through maskObject individually (to
preserve regular recursive masking for those fields) and explicitly setting
address, city, state, and zipcode to MASK when their original values are truthy
(keeping original falsy values). Update code around the key === "region" branch
(referencing key, value, cloned, maskObject, and MASK) so region-specific
address fields are deterministically hard-masked while other region properties
still get proper recursive masking without double-processing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/logger/mask.js`:
- Around line 104-107: The condition in the if-statement that checks key and
value has an operator precedence bug: (key === "name") currently bypasses the
typeof check and calls maskName(value) even when value is not a string. Update
the condition in the block that uses key, value and cloned so it only calls
maskName when the key is "name" or "user" AND value is a string (e.g., group the
key checks together before &&), preserving the existing behavior of maskName and
its guard clause but ensuring the type check applies to both keys.

---

Nitpick comments:
In `@src/logger/mask.js`:
- Around line 109-118: For the special-case handling of the "region" object in
src/logger/mask.js, remove the initial recursive call maskObject(value, seen)
(which causes redundant regex-masking) and instead build cloned[key] by: copying
non-address fields through maskObject individually (to preserve regular
recursive masking for those fields) and explicitly setting address, city, state,
and zipcode to MASK when their original values are truthy (keeping original
falsy values). Update code around the key === "region" branch (referencing key,
value, cloned, maskObject, and MASK) so region-specific address fields are
deterministically hard-masked while other region properties still get proper
recursive masking without double-processing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9c88d459-3a7b-4364-90be-eb78cf889ce2

📥 Commits

Reviewing files that changed from the base of the PR and between f7caa74 and 88ee734.

📒 Files selected for processing (1)
  • src/logger/mask.js

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.

1 participant