Skip to content

fix(frontend): prevent PII leaks in production console logs#262

Merged
Junman140 merged 3 commits into
Pi-Defi-world:mainfrom
johdanike:main
Apr 28, 2026
Merged

fix(frontend): prevent PII leaks in production console logs#262
Junman140 merged 3 commits into
Pi-Defi-world:mainfrom
johdanike:main

Conversation

@johdanike
Copy link
Copy Markdown
Contributor

@johdanike johdanike commented Apr 23, 2026

Description

This PR addresses a critical hygiene and security issue where Personally Identifiable Information (PII) was leaking into the browser console on sensitive pages (Savings, Bills, Lending, Currency).

To ensure this doesn't happen again, raw console logging has been replaced with a structured utility, and the build process has been updated to mathematically strip standard logs from the production bundle.

Changes Made

  • Added utils/logger.ts: Introduced a centralized logger that only outputs when not in production, or when explicitly bypassed via the NEXT_PUBLIC_DEBUG_LOGGING=true environment flag.
  • Refactored Transfer Paths: Swapped raw console.log statements for the new logger utility across sensitive flows, including the Stellar trustline checks (ensureAcbuTrustlineClient).
  • Updated vite.config.ts: Leveraged esbuild's pure configuration to automatically drop console.log, console.info, and console.debug during the production build step. console.error and console.warn are intentionally preserved for production monitoring tools.

Acceptance Criteria Met

  • Production bundle has no console.log in transfer paths.
  • Local developer experience is uninterrupted (logs still appear in dev mode).

Testing Instructions

  1. Run npm run dev and verify logs still appear in the browser console during local development.
  2. Run npm run build followed by npm run preview.
  3. Navigate through the transfer paths and verify the console remains clear of PII and standard logs.

Summary by CodeRabbit

  • Refactor
    • Implemented centralized logging infrastructure across the application to provide consistent error tracking and debugging capabilities with structured, timestamped log entries.

@drips-wave
Copy link
Copy Markdown

drips-wave Bot commented Apr 23, 2026

Hey @johdanike! 👋 It looks like this PR isn't linked to any issue.

If this PR is for one of the issues assigned to you as part of a Wave, please link it to ensure your contribution is tracked properly. You can do this by adding a keyword to the PR description (e.g., Closes #123), or by clicking a button below:

Issue Title
#214 F-043 — console.log in money handlers Link to this issue

ℹ️ Learn more about linking PRs to issues

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 23, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8beaaa7e-60c5-4781-8972-453751a7e8f3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

A new centralized logger utility is introduced in lib/logger.ts that emits structured JSON logs with timestamps, levels, and optional data. Nine files across app pages, components, contexts, and library utilities are updated to use this shared logger instead of direct console method calls.

Changes

Cohort / File(s) Summary
Logger Infrastructure
lib/logger.ts
New logger module with info, warn, error, and debug methods. Logs are structured JSON with timestamp, level, message, and optional data. Conditional logging based on NEXT_PUBLIC_DEBUG or non-production NODE_ENV; non-debug mode only emits error logs.
App Pages
app/business/page.tsx, app/error.tsx, app/mint/page.tsx, app/savings/deposit/page.tsx, app/savings/withdraw/page.tsx
Replace direct console.error or console.info calls with corresponding logger methods. Changes localized to error handlers and async operation logging with no control flow modifications.
Components & Contexts
components/error-boundary.tsx, contexts/auth-context.tsx
Update error logging in ErrorBoundary.componentDidCatch and AuthProvider.refreshStellarAddress from console.error to logger.error, with error and errorInfo passed as object properties.
Library Utilities
lib/stellar-wallets-kit.ts, lib/stellar/trustlines.ts
Replace console.warn with logger.warn in useStellarWalletsKit error handler and remove browser-guard conditional around console.info calls in ensureAcbuTrustlineClient, replacing with logger.info for structured logging.

Possibly related issues

  • F-043 — console.log in money handlers #214 — Directly implements the request to consolidate and remove console logging in money/transfer-related handlers by introducing a centralized logger utility across the application.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Hopping through logs with a logger so keen,
Structured and tidy, no console in between,
JSON timestamps in every line,
Debug mode dancing, looking so fine!
No more direct console calls—hooray!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(frontend): prevent PII leaks in production console logs' directly and accurately summarizes the main objective of the changeset—introducing a centralized logger to prevent sensitive data from appearing in production console logs.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Contributor

@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: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
app/savings/withdraw/page.tsx (1)

32-37: ⚠️ Potential issue | 🟠 Major

Avoid production logging of receive-address errors.

This still writes a potentially sensitive API error message to the production console because logger.error is not debug-gated. Treat this as a debug/dev warning instead.

🛡️ Proposed fix
-            .catch((e) => {
-                logger.error(
-                    e instanceof Error
-                        ? e.message
-                        : "Failed to load receive address",
-                );
-            });
+            .catch((e) => {
+                logger.warn("Failed to load receive address", e);
+            });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/savings/withdraw/page.tsx` around lines 32 - 37, The catch block that
calls logger.error with the receive-address error should not log potentially
sensitive messages in production; update the .catch handler in page.tsx (the
anonymous callback that references logger) to log a non-sensitive generic
message at error/warn level and move the detailed e.message into a debug-only
log (e.g., logger.debug or gated by process.env.NODE_ENV !== 'production'), or
alternatively replace logger.error with logger.warn and avoid printing e.message
in production while keeping detailed info available in dev/debug logs.
app/mint/page.tsx (1)

197-202: ⚠️ Potential issue | 🟠 Major

Logging the full Stellar account address still leaks a user identifier.

The stated goal of this PR is to keep PII out of browser-console output on sensitive flows. accountId here is the user's full Stellar public key — a stable, user-linked wallet identifier that correlates to the authenticated user. Even though logger.info is gated on non-prod / NEXT_PUBLIC_DEBUG_LOGGING, any environment that flips that flag (staging with real users, a support session, a shared browser, etc.) will emit the full address alongside a txHash, which is exactly the kind of correlation the PR is trying to avoid. The rest of this file already treats the address as sensitive — every user-facing message uses the slice(0,6)…slice(-4) pattern (e.g. lines 160, 188, 252, 285).

Recommend applying the same truncation in the log (and consider whether txHash needs to be logged at all, since on-chain it is trivially linkable back to the account):

🛡️ Proposed fix
-            logger.info("[mint] ACBU trustline ensured", {
-                account: accountId,
-                added: trust?.added,
-                visible: trust?.visible,
-                txHash: trust?.txHash,
-            });
+            logger.info("[mint] ACBU trustline ensured", {
+                account: `${accountId.slice(0, 6)}…${accountId.slice(-4)}`,
+                added: trust?.added,
+                visible: trust?.visible,
+                txHash: trust?.txHash,
+            });

The analogous log in lib/stellar/trustlines.ts (lines 221–224) only logs code/issuer and is fine as-is — worth keeping that pattern consistent here.

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

In `@app/mint/page.tsx` around lines 197 - 202, The logger.info call that
currently logs the full Stellar public key (accountId) and txHash should avoid
emitting PII: replace the direct accountId with a truncated form (e.g.,
accountId.slice(0,6) + "…" + accountId.slice(-4)) and remove or avoid logging
trust?.txHash (or similarly truncate it) in the "[mint] ACBU trustline ensured"
log; update the logger.info invocation in this block (the call referencing
logger.info, accountId, trust?.added, trust?.visible, trust?.txHash) to use the
truncated identifier and omit/obfuscate txHash to match the file's existing
user-facing masking pattern.
🧹 Nitpick comments (1)
lib/stellar/trustlines.ts (1)

221-224: Minor: indentation inconsistent with surrounding block.

The logger.info(...) call at line 221 is indented one level deeper than the surrounding function body (the let asset, try, and return statements at lines 210–225 are at 2 spaces; this block is at 4). Functionally fine, but worth normalizing while touching this line.

✏️ Proposed fix
-    logger.info("[trustline] ensuring ACBU trustline", {
-      code: asset.getCode(),
-      issuer: asset.getIssuer(),
-    });
+  logger.info("[trustline] ensuring ACBU trustline", {
+    code: asset.getCode(),
+    issuer: asset.getIssuer(),
+  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/stellar/trustlines.ts` around lines 221 - 224, The logger.info call for
ensuring the ACBU trustline is mis-indented; align its indentation with the
surrounding function block (same indentation level used by the nearby let asset,
try, and return statements) so the line starting with logger.info(...) sits at
the same indentation as those statements; locate the call by searching for
logger.info("[trustline] ensuring ACBU trustline", { and adjust whitespace
before it to match the block.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/business/page.tsx`:
- Around line 39-42: The catch block that logs business stats failures currently
calls logger.error('Business stats error:', err) and thus emits raw errors in
production; change it so that raw error details are only logged at debug level
in non-production (e.g., guard with NODE_ENV !== 'production' and call
logger.debug(...err...)), and in production replace the error call with a
sanitized warning (e.g., logger.warn('Business stats fallback - details
omitted')) or send sanitized telemetry instead; update the catch in
app/business/page.tsx (the catch around the business stats fetch where
logger.error is called) accordingly so production logs do not contain raw error
objects.

In `@app/error.tsx`:
- Around line 14-16: The current useEffect calls logger.error('Application
error:', error) which may leak raw error details; change the effect (useEffect)
to compute a sanitized identifier (e.g. an error.code or a short digest derived
from error.name/message or error.id) and pass only that identifier to
logger.error, and then emit the full error object to logger.debug (or
logger.warn) only when not in production (guarded by NODE_ENV or an
isDevelopment flag); update the useEffect to use the new sanitizedErrorId and
conditional debug logging while keeping the original logger.error call signature
limited to non-sensitive data.

In `@app/mint/page.tsx`:
- Line 109: The catch currently logs the Error object directly which loses
message/stack during JSON serialization; update the .catch handler that calls
logger.error in page.tsx so it extracts and passes e.message and e.stack (e.g.,
replace the second argument to logger.error with an object containing message
and stack) to preserve error details when logging.

In `@app/savings/deposit/page.tsx`:
- Around line 28-30: The catch handler for loading the receive address currently
calls logger.error with the raw error message; replace this with a
production-safe pattern: in the .catch((e) => { ... }) block use logger.debug or
a feature-flag gated log (e.g. if process.env.NODE_ENV !== 'production' ||
process.env.DEBUG_RECEIVE_ADDRESS) to include e.message/e.stack, but always emit
a generic logger.warn or logger.info like "Failed to load receive address" in
production without the raw API text; update the catch on the receive-address
load so the symbol logger only logs sensitive error details when debug is
explicitly enabled.

In `@components/error-boundary.tsx`:
- Around line 27-29: In componentDidCatch, stop passing the full { error,
errorInfo } to logger.error in production; change componentDidCatch to call
logger.error with only a generic message (e.g., "Uncaught UI error") for
production environments and conditionally log the detailed payload (error and
errorInfo) behind a debug gate (e.g., process.env.NODE_ENV === 'development' or
a debug flag) so that the detailed stack and component info are only emitted
when debugging; ensure references to componentDidCatch, logger.error, error, and
errorInfo are updated accordingly.

In `@contexts/auth-context.tsx`:
- Around line 106-108: The catch block that currently calls logger.error('Failed
to refresh stellar address', e) should avoid emitting raw error objects to
production; change it to log a sanitized message to error-level and send the
full/raw error only to a debug-only channel. Concretely, replace the direct pass
of the caught value to logger.error by either (a) logger.debug('Failed to
refresh stellar address', e) plus logger.error('Failed to refresh stellar
address') or (b) logger.error('Failed to refresh stellar address: ' + (e &&
e.message) ), so that logger.error does not receive the raw error object; update
the catch in the refresh path where logger.error is invoked accordingly.

In `@lib/logger.ts`:
- Line 1: The isDebug flag currently reads NEXT_PUBLIC_DEBUG instead of the
documented NEXT_PUBLIC_DEBUG_LOGGING; update the constant declaration for
isDebug in logger.ts to check process.env.NEXT_PUBLIC_DEBUG_LOGGING === 'true'
(and still OR with process.env.NODE_ENV !== 'production') so the documented
opt-in flag is honored; change the identifier usage where isDebug is defined
only — keep all downstream usages of isDebug unchanged.
- Around line 5-31: logMessage currently allows logger.error to log
caller-supplied data in production and uses unsafe any types; update logMessage
and the exported logger methods to use unknown for the data parameter, restrict
logging of arbitrary data payloads to debug mode only (i.e., when isDebug is
true) so logger.error still outputs the error message but does not serialize
caller data unless isDebug, and add a safe serialization path that normalizes
Error objects (extracting name, message, stack) and wraps JSON.stringify in a
try/catch to fall back to a non-sensitive placeholder on failure; update
signatures for logger.info/warn/error/debug to accept data?: unknown and ensure
the logEntry construction uses the normalized/guarded serialization result
instead of raw data.

---

Outside diff comments:
In `@app/mint/page.tsx`:
- Around line 197-202: The logger.info call that currently logs the full Stellar
public key (accountId) and txHash should avoid emitting PII: replace the direct
accountId with a truncated form (e.g., accountId.slice(0,6) + "…" +
accountId.slice(-4)) and remove or avoid logging trust?.txHash (or similarly
truncate it) in the "[mint] ACBU trustline ensured" log; update the logger.info
invocation in this block (the call referencing logger.info, accountId,
trust?.added, trust?.visible, trust?.txHash) to use the truncated identifier and
omit/obfuscate txHash to match the file's existing user-facing masking pattern.

In `@app/savings/withdraw/page.tsx`:
- Around line 32-37: The catch block that calls logger.error with the
receive-address error should not log potentially sensitive messages in
production; update the .catch handler in page.tsx (the anonymous callback that
references logger) to log a non-sensitive generic message at error/warn level
and move the detailed e.message into a debug-only log (e.g., logger.debug or
gated by process.env.NODE_ENV !== 'production'), or alternatively replace
logger.error with logger.warn and avoid printing e.message in production while
keeping detailed info available in dev/debug logs.

---

Nitpick comments:
In `@lib/stellar/trustlines.ts`:
- Around line 221-224: The logger.info call for ensuring the ACBU trustline is
mis-indented; align its indentation with the surrounding function block (same
indentation level used by the nearby let asset, try, and return statements) so
the line starting with logger.info(...) sits at the same indentation as those
statements; locate the call by searching for logger.info("[trustline] ensuring
ACBU trustline", { and adjust whitespace before it to match the block.
🪄 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: f00c8b2f-b878-4080-85e7-ca3991baaa03

📥 Commits

Reviewing files that changed from the base of the PR and between 9e9f179 and dec1986.

📒 Files selected for processing (10)
  • app/business/page.tsx
  • app/error.tsx
  • app/mint/page.tsx
  • app/savings/deposit/page.tsx
  • app/savings/withdraw/page.tsx
  • components/error-boundary.tsx
  • contexts/auth-context.tsx
  • lib/logger.ts
  • lib/stellar-wallets-kit.ts
  • lib/stellar/trustlines.ts

Comment thread app/business/page.tsx
Comment on lines 39 to 42
} catch (err) {
// Fallback: show loading state instead of error UI
console.error('Business stats error:', err);
logger.error('Business stats error:', err);
} finally {
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.

⚠️ Potential issue | 🟠 Major

Don’t escalate fallback stats failures to production errors.

This path intentionally falls back without user-facing error UI, but logger.error emits the raw caught error in production. Use a debug-gated warning unless there is sanitized telemetry behind it.

🔧 Proposed fix
       } catch (err) {
         // Fallback: show loading state instead of error UI
-        logger.error('Business stats error:', err);
+        logger.warn('Business stats error', err);
       } finally {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch (err) {
// Fallback: show loading state instead of error UI
console.error('Business stats error:', err);
logger.error('Business stats error:', err);
} finally {
} catch (err) {
// Fallback: show loading state instead of error UI
logger.warn('Business stats error', err);
} finally {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/business/page.tsx` around lines 39 - 42, The catch block that logs
business stats failures currently calls logger.error('Business stats error:',
err) and thus emits raw errors in production; change it so that raw error
details are only logged at debug level in non-production (e.g., guard with
NODE_ENV !== 'production' and call logger.debug(...err...)), and in production
replace the error call with a sanitized warning (e.g., logger.warn('Business
stats fallback - details omitted')) or send sanitized telemetry instead; update
the catch in app/business/page.tsx (the catch around the business stats fetch
where logger.error is called) accordingly so production logs do not contain raw
error objects.

Comment thread app/error.tsx
Comment on lines 14 to 16
useEffect(() => {
console.error('Application error:', error);
logger.error('Application error:', error);
}, [error]);
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.

⚠️ Potential issue | 🟠 Major

Only log sanitized error identifiers in production.

Passing the full error object to logger.error can expose arbitrary error details in the production console. Log the digest/generic signal at error level and keep raw details debug-gated.

🛡️ Proposed fix
   useEffect(() => {
-    logger.error('Application error:', error);
+    logger.error('Application error', error.digest ? { digest: error.digest } : undefined);
+    logger.debug('Application error details', error);
   }, [error]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
console.error('Application error:', error);
logger.error('Application error:', error);
}, [error]);
useEffect(() => {
logger.error('Application error', error.digest ? { digest: error.digest } : undefined);
logger.debug('Application error details', error);
}, [error]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/error.tsx` around lines 14 - 16, The current useEffect calls
logger.error('Application error:', error) which may leak raw error details;
change the effect (useEffect) to compute a sanitized identifier (e.g. an
error.code or a short digest derived from error.name/message or error.id) and
pass only that identifier to logger.error, and then emit the full error object
to logger.debug (or logger.warn) only when not in production (guarded by
NODE_ENV or an isDevelopment flag); update the useEffect to use the new
sanitizedErrorId and conditional debug logging while keeping the original
logger.error call signature limited to non-sensitive data.

Comment thread app/mint/page.tsx
}
})
.catch(console.error);
.catch((e) => logger.error('Failed to get fiat accounts', e));
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.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd -t f 'logger.ts' lib
fd -t f 'logger.ts' lib --exec cat {}

Repository: Pi-Defi-world/acbu-frontend

Length of output: 1175


Extract error message and stack before logging.

The logger.error correctly routes through console.error and will not be stripped from production, but there is a critical issue: the Error object passed as the second argument will serialize to {} when JSON.stringify processes it, since Error properties like message and stack are non-enumerable.

Change .catch((e) => logger.error('Failed to get fiat accounts', e)) to .catch((e) => logger.error('Failed to get fiat accounts', { message: e.message, stack: e.stack })) to preserve the actual error details.

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

In `@app/mint/page.tsx` at line 109, The catch currently logs the Error object
directly which loses message/stack during JSON serialization; update the .catch
handler that calls logger.error in page.tsx so it extracts and passes e.message
and e.stack (e.g., replace the second argument to logger.error with an object
containing message and stack) to preserve error details when logging.

Comment on lines 28 to 30
}).catch((e) => {
console.error(e instanceof Error ? e.message : 'Failed to load receive address');
logger.error(e instanceof Error ? e.message : 'Failed to load receive address');
});
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.

⚠️ Potential issue | 🟠 Major

Avoid production logging of receive-address errors.

This sensitive Savings path still sends the caught error message to production via logger.error. Use a debug-gated level and avoid logging raw API text in production.

🛡️ Proposed fix
-    }).catch((e) => {
-      logger.error(e instanceof Error ? e.message : 'Failed to load receive address');
+    }).catch((e) => {
+      logger.warn('Failed to load receive address', e);
     });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
}).catch((e) => {
console.error(e instanceof Error ? e.message : 'Failed to load receive address');
logger.error(e instanceof Error ? e.message : 'Failed to load receive address');
});
}).catch((e) => {
logger.warn('Failed to load receive address', e);
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/savings/deposit/page.tsx` around lines 28 - 30, The catch handler for
loading the receive address currently calls logger.error with the raw error
message; replace this with a production-safe pattern: in the .catch((e) => { ...
}) block use logger.debug or a feature-flag gated log (e.g. if
process.env.NODE_ENV !== 'production' || process.env.DEBUG_RECEIVE_ADDRESS) to
include e.message/e.stack, but always emit a generic logger.warn or logger.info
like "Failed to load receive address" in production without the raw API text;
update the catch on the receive-address load so the symbol logger only logs
sensitive error details when debug is explicitly enabled.

Comment on lines 27 to 29
componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
console.error('ErrorBoundary caught an error:', error, errorInfo);
logger.error('ErrorBoundary caught an error:', { error, errorInfo });
}
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.

⚠️ Potential issue | 🟠 Major

Split production error signal from debug details.

Passing { error, errorInfo } to logger.error keeps arbitrary error details eligible for production console output. Emit only a generic production error and keep stack/component details debug-gated.

🛡️ Proposed fix
   componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
-    logger.error('ErrorBoundary caught an error:', { error, errorInfo });
+    logger.error('ErrorBoundary caught an error');
+    logger.debug('ErrorBoundary caught an error', { error, errorInfo });
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
console.error('ErrorBoundary caught an error:', error, errorInfo);
logger.error('ErrorBoundary caught an error:', { error, errorInfo });
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
logger.error('ErrorBoundary caught an error');
logger.debug('ErrorBoundary caught an error', { error, errorInfo });
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/error-boundary.tsx` around lines 27 - 29, In componentDidCatch,
stop passing the full { error, errorInfo } to logger.error in production; change
componentDidCatch to call logger.error with only a generic message (e.g.,
"Uncaught UI error") for production environments and conditionally log the
detailed payload (error and errorInfo) behind a debug gate (e.g.,
process.env.NODE_ENV === 'development' or a debug flag) so that the detailed
stack and component info are only emitted when debugging; ensure references to
componentDidCatch, logger.error, error, and errorInfo are updated accordingly.

Comment thread contexts/auth-context.tsx
Comment on lines 106 to 108
} catch (e) {
console.error('Failed to refresh stellar address', e);
logger.error('Failed to refresh stellar address', e);
}
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.

⚠️ Potential issue | 🟠 Major

Keep background refresh failures out of production console.

This recoverable auth refresh path now passes the raw caught value to logger.error, which emits in production. Use a debug-gated level or sanitized payload instead.

🛡️ Proposed fix
-    } catch (e) {
-      logger.error('Failed to refresh stellar address', e);
+    } catch (e) {
+      logger.warn('Failed to refresh stellar address', e);
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch (e) {
console.error('Failed to refresh stellar address', e);
logger.error('Failed to refresh stellar address', e);
}
} catch (e) {
logger.warn('Failed to refresh stellar address', e);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contexts/auth-context.tsx` around lines 106 - 108, The catch block that
currently calls logger.error('Failed to refresh stellar address', e) should
avoid emitting raw error objects to production; change it to log a sanitized
message to error-level and send the full/raw error only to a debug-only channel.
Concretely, replace the direct pass of the caught value to logger.error by
either (a) logger.debug('Failed to refresh stellar address', e) plus
logger.error('Failed to refresh stellar address') or (b) logger.error('Failed to
refresh stellar address: ' + (e && e.message) ), so that logger.error does not
receive the raw error object; update the catch in the refresh path where
logger.error is invoked accordingly.

Comment thread lib/logger.ts
@@ -0,0 +1,32 @@
const isDebug = process.env.NEXT_PUBLIC_DEBUG === 'true' || process.env.NODE_ENV !== 'production';
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.

⚠️ Potential issue | 🟠 Major

Use the documented debug flag.

The PR contract says production debug logging is enabled with NEXT_PUBLIC_DEBUG_LOGGING=true, but this checks NEXT_PUBLIC_DEBUG; the documented opt-in will be ignored.

🔧 Proposed fix
-const isDebug = process.env.NEXT_PUBLIC_DEBUG === 'true' || process.env.NODE_ENV !== 'production';
+const isDebug = process.env.NEXT_PUBLIC_DEBUG_LOGGING === 'true' || process.env.NODE_ENV !== 'production';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const isDebug = process.env.NEXT_PUBLIC_DEBUG === 'true' || process.env.NODE_ENV !== 'production';
const isDebug = process.env.NEXT_PUBLIC_DEBUG_LOGGING === 'true' || process.env.NODE_ENV !== 'production';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/logger.ts` at line 1, The isDebug flag currently reads NEXT_PUBLIC_DEBUG
instead of the documented NEXT_PUBLIC_DEBUG_LOGGING; update the constant
declaration for isDebug in logger.ts to check
process.env.NEXT_PUBLIC_DEBUG_LOGGING === 'true' (and still OR with
process.env.NODE_ENV !== 'production') so the documented opt-in flag is honored;
change the identifier usage where isDebug is defined only — keep all downstream
usages of isDebug unchanged.

Comment thread lib/logger.ts
Comment on lines +5 to +31
function logMessage(level: LogLevel, message: string, data?: any) {
if (!isDebug && level !== 'error') return;

const logEntry = {
timestamp: new Date().toISOString(),
level,
message,
...(data !== undefined && { data })
};

// In debug mode, or for errors, output structured logs to console
if (level === 'error') {
console.error(JSON.stringify(logEntry));
} else if (level === 'warn') {
console.warn(JSON.stringify(logEntry));
} else if (level === 'info') {
console.info(JSON.stringify(logEntry));
} else {
console.log(JSON.stringify(logEntry));
}
}

export const logger = {
info: (message: string, data?: any) => logMessage('info', message, data),
warn: (message: string, data?: any) => logMessage('warn', message, data),
error: (message: string, data?: any) => logMessage('error', message, data),
debug: (message: string, data?: any) => logMessage('debug', message, data),
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Confirm no explicit `any` remains in logger payload signatures.
rg -nP '\bdata\?:\s*any\b|function\s+logMessage\([^)]*\bdata\?:\s*any'

Repository: Pi-Defi-world/acbu-frontend

Length of output: 530


Fix security hole in error logging and replace any with unknown.

logger.error bypasses the debug gate, allowing sensitive caller data (PII) to reach production console. The any type on all logger signatures violates type safety and triggers linting errors. Restrict data payload logging to debug mode, normalize Error objects safely, and handle serialization failures gracefully.

Proposed hardening
 type LogLevel = 'info' | 'warn' | 'error' | 'debug';
 
-function logMessage(level: LogLevel, message: string, data?: any) {
+function normalizeLogData(data: unknown) {
+  if (data instanceof Error) {
+    return isDebug
+      ? { name: data.name, message: data.message, stack: data.stack }
+      : { name: data.name };
+  }
+
+  return isDebug ? data : undefined;
+}
+
+function stringifyLogEntry(entry: Record<string, unknown>) {
+  try {
+    return JSON.stringify(entry);
+  } catch {
+    const { data: _data, ...safeEntry } = entry;
+    return JSON.stringify({ ...safeEntry, data: '[unserializable]' });
+  }
+}
+
+function logMessage(level: LogLevel, message: string, data?: unknown) {
   if (!isDebug && level !== 'error') return;
 
+  const normalizedData = data !== undefined ? normalizeLogData(data) : undefined;
   const logEntry = {
     timestamp: new Date().toISOString(),
     level,
-    message,
-    ...(data !== undefined && { data })
+    message: isDebug ? message : 'Application error',
+    ...(normalizedData !== undefined && { data: normalizedData })
   };
 
   // In debug mode, or for errors, output structured logs to console
   if (level === 'error') {
-    console.error(JSON.stringify(logEntry));
+    console.error(stringifyLogEntry(logEntry));
   } else if (level === 'warn') {
-    console.warn(JSON.stringify(logEntry));
+    console.warn(stringifyLogEntry(logEntry));
   } else if (level === 'info') {
-    console.info(JSON.stringify(logEntry));
+    console.info(stringifyLogEntry(logEntry));
   } else {
-    console.log(JSON.stringify(logEntry));
+    console.log(stringifyLogEntry(logEntry));
   }
 }
 
 export const logger = {
-  info: (message: string, data?: any) => logMessage('info', message, data),
-  warn: (message: string, data?: any) => logMessage('warn', message, data),
-  error: (message: string, data?: any) => logMessage('error', message, data),
-  debug: (message: string, data?: any) => logMessage('debug', message, data),
+  info: (message: string, data?: unknown) => logMessage('info', message, data),
+  warn: (message: string, data?: unknown) => logMessage('warn', message, data),
+  error: (message: string, data?: unknown) => logMessage('error', message, data),
+  debug: (message: string, data?: unknown) => logMessage('debug', message, data),
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function logMessage(level: LogLevel, message: string, data?: any) {
if (!isDebug && level !== 'error') return;
const logEntry = {
timestamp: new Date().toISOString(),
level,
message,
...(data !== undefined && { data })
};
// In debug mode, or for errors, output structured logs to console
if (level === 'error') {
console.error(JSON.stringify(logEntry));
} else if (level === 'warn') {
console.warn(JSON.stringify(logEntry));
} else if (level === 'info') {
console.info(JSON.stringify(logEntry));
} else {
console.log(JSON.stringify(logEntry));
}
}
export const logger = {
info: (message: string, data?: any) => logMessage('info', message, data),
warn: (message: string, data?: any) => logMessage('warn', message, data),
error: (message: string, data?: any) => logMessage('error', message, data),
debug: (message: string, data?: any) => logMessage('debug', message, data),
type LogLevel = 'info' | 'warn' | 'error' | 'debug';
function normalizeLogData(data: unknown) {
if (data instanceof Error) {
return isDebug
? { name: data.name, message: data.message, stack: data.stack }
: { name: data.name };
}
return isDebug ? data : undefined;
}
function stringifyLogEntry(entry: Record<string, unknown>) {
try {
return JSON.stringify(entry);
} catch {
const { data: _data, ...safeEntry } = entry;
return JSON.stringify({ ...safeEntry, data: '[unserializable]' });
}
}
function logMessage(level: LogLevel, message: string, data?: unknown) {
if (!isDebug && level !== 'error') return;
const normalizedData = data !== undefined ? normalizeLogData(data) : undefined;
const logEntry = {
timestamp: new Date().toISOString(),
level,
message: isDebug ? message : 'Application error',
...(normalizedData !== undefined && { data: normalizedData })
};
// In debug mode, or for errors, output structured logs to console
if (level === 'error') {
console.error(stringifyLogEntry(logEntry));
} else if (level === 'warn') {
console.warn(stringifyLogEntry(logEntry));
} else if (level === 'info') {
console.info(stringifyLogEntry(logEntry));
} else {
console.log(stringifyLogEntry(logEntry));
}
}
export const logger = {
info: (message: string, data?: unknown) => logMessage('info', message, data),
warn: (message: string, data?: unknown) => logMessage('warn', message, data),
error: (message: string, data?: unknown) => logMessage('error', message, data),
debug: (message: string, data?: unknown) => logMessage('debug', message, data),
};
🧰 Tools
🪛 ESLint

[error] 5-5: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 28-28: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 29-29: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 30-30: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)


[error] 31-31: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

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

In `@lib/logger.ts` around lines 5 - 31, logMessage currently allows logger.error
to log caller-supplied data in production and uses unsafe any types; update
logMessage and the exported logger methods to use unknown for the data
parameter, restrict logging of arbitrary data payloads to debug mode only (i.e.,
when isDebug is true) so logger.error still outputs the error message but does
not serialize caller data unless isDebug, and add a safe serialization path that
normalizes Error objects (extracting name, message, stack) and wraps
JSON.stringify in a try/catch to fall back to a non-sensitive placeholder on
failure; update signatures for logger.info/warn/error/debug to accept data?:
unknown and ensure the logEntry construction uses the normalized/guarded
serialization result instead of raw data.

@Junman140
Copy link
Copy Markdown
Member

@johdanike resolve conflicts

@johdanike
Copy link
Copy Markdown
Contributor Author

Resolved @Junman140

@Junman140 Junman140 merged commit de2028d into Pi-Defi-world:main Apr 28, 2026
1 check passed
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.

2 participants