From b40e539fd9bcbdbfe89b45ae03f9c47c0d9db2c7 Mon Sep 17 00:00:00 2001 From: KCEE0901 Date: Tue, 26 May 2026 22:06:17 +0000 Subject: [PATCH] Add pino logger with redaction for Stellar secret keys (issue #442) --- SECURITY.md | 9 ++++++ backend/package.json | 1 + backend/src/logger.test.ts | 19 ++++++++++++ backend/src/logger.ts | 41 +++++++++++++++++++++++++ backend/src/middleware/requestLogger.ts | 13 ++------ 5 files changed, 73 insertions(+), 10 deletions(-) create mode 100644 backend/src/logger.test.ts create mode 100644 backend/src/logger.ts diff --git a/SECURITY.md b/SECURITY.md index bbe6e57..3dc7874 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -32,3 +32,12 @@ Once a report is received through the GitHub Security Advisory form, we commit t ## GitHub Security Advisories Maintainers: Please ensure that **GitHub Security Advisories** are enabled for this repository to allow researchers to submit reports privately. + +## Logging and Secret Redaction + +Server logs are configured to redact Stellar secret keys to prevent accidental leakage. The backend uses `pino` with the following protections: + +- Structured field redaction for paths: `*.secretKey`, `*.privateKey`, `*.seed`. +- Regex-based redaction for any string matching `^S[0-9A-Z]{55}$` (Stellar secret seed format). + +If you discover logs containing secret material, please follow the private reporting process above. diff --git a/backend/package.json b/backend/package.json index eb58bc8..5b6565e 100644 --- a/backend/package.json +++ b/backend/package.json @@ -19,6 +19,7 @@ "express-rate-limit": "^7.5.0", "jsonwebtoken": "^9.0.2", "p-limit": "^4.0.0", + "pino": "^8.11.0", "swagger-ui-express": "^5.0.1", "ws": "^8.20.0", diff --git a/backend/src/logger.test.ts b/backend/src/logger.test.ts new file mode 100644 index 0000000..f82a87f --- /dev/null +++ b/backend/src/logger.test.ts @@ -0,0 +1,19 @@ +import { redactObject } from "./logger"; + +describe("logger redaction", () => { + test("redacts stellar secret keys in objects and strings", () => { + const secret = "S" + "A".repeat(55); + const obj = { + nested: { secretKey: secret, other: "ok" }, + token: secret, + arr: [secret, { privateKey: secret }], + } as const; + + const redacted = redactObject(obj as any); + + expect(redacted.nested.secretKey).toBe("[REDACTED]"); + expect(redacted.token).toBe("[REDACTED]"); + expect(redacted.arr[0]).toBe("[REDACTED]"); + expect(redacted.arr[1].privateKey).toBe("[REDACTED]"); + }); +}); diff --git a/backend/src/logger.ts b/backend/src/logger.ts new file mode 100644 index 0000000..bc004bb --- /dev/null +++ b/backend/src/logger.ts @@ -0,0 +1,41 @@ +import pino from "pino"; + +const STELLAR_SECRET_REGEX = /^S[0-9A-Z]{55}$/; + +function redactValue(value: unknown): unknown { + if (typeof value === "string" && STELLAR_SECRET_REGEX.test(value)) { + return "[REDACTED]"; + } + return value; +} + +function redactObject(obj: any): any { + if (obj == null) return obj; + if (Array.isArray(obj)) return obj.map(redactObject); + if (typeof obj === "object") { + const out: Record = {}; + for (const [k, v] of Object.entries(obj)) { + if (k === "secretKey" || k === "privateKey" || k === "seed") { + out[k] = "[REDACTED]"; + } else { + out[k] = redactObject(v); + } + } + return out; + } + return redactValue(obj); +} + +const logger = pino({ + level: process.env.LOG_LEVEL || "info", + // keep path-based redaction for structured fields + redact: { paths: ["*.secretKey", "*.privateKey", "*.seed"], censor: "[REDACTED]" }, + // ensure values (strings) that match Stellar secret pattern are redacted anywhere + formatters: { + log(obj: Record) { + return redactObject(obj); + }, + }, +}); + +export { logger, redactObject, STELLAR_SECRET_REGEX }; diff --git a/backend/src/middleware/requestLogger.ts b/backend/src/middleware/requestLogger.ts index 3bca350..8be9250 100644 --- a/backend/src/middleware/requestLogger.ts +++ b/backend/src/middleware/requestLogger.ts @@ -1,5 +1,6 @@ import { Request, Response, NextFunction } from "express"; import crypto from "crypto"; +import { logger } from "../logger"; declare global { namespace Express { @@ -30,16 +31,8 @@ export function requestLogger(req: Request, res: Response, next: NextFunction) { duration: `${duration}ms`, }; - // ✅ STEP 4: Make logs readable in development - if (process.env.NODE_ENV === "production") { - // Machine-readable (for logging systems) - console.log(JSON.stringify(logEntry)); - } else { - // Human-readable (for local dev) - console.log( - `${req.method} ${req.originalUrl} ${res.statusCode} - ${duration}ms | id=${requestId}` - ); - } + // ✅ STEP 4: Use structured logger with redaction + logger.info(logEntry, `${req.method} ${req.originalUrl} ${res.statusCode} - ${duration}ms | id=${requestId}`); }); // ✅ STEP 5: Continue request