mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-27 11:20:42 +01:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Operators rely on Paperclip server logs for maintenance, incident triage, and support handoffs. > - The HTTP logger persisted request metadata and only redacted authorization headers. > - Request cookies and set-cookie headers can contain active session material and should not be written to durable logs. > - This pull request keeps the fix intentionally narrow: centralize the HTTP log redaction path list and include cookie-bearing headers. > - The benefit is lower credential/session leakage risk from routine server.log collection or sharing. ## Linked Issues or Issue Description No GitHub issue exists for this exact local finding. Inline bug report: - Type: security/privacy bug. - Affected area: server HTTP logging middleware. - Observed problem: local Paperclip maintenance found raw cookies present in server.log. - Expected behavior: durable HTTP logs redact authorization and cookie-bearing request/response headers. - Impact: anyone with access to copied/exported logs could see session-bearing cookie values. - Related/open PRs found during dedup search: #7242, #7306, #7346. This PR is the minimal local fix branch created from the verified local maintenance patch; those PRs may be better upstream candidates if maintainers prefer their broader coverage. ## What Changed - Added `HTTP_LOG_REDACT_PATHS` for HTTP logger redaction paths. - Kept existing `req.headers.authorization` redaction. - Added redaction for `req.headers.cookie`, request `set-cookie`, and response `set-cookie` paths. - Added focused tests asserting the required redaction paths are present and that pino-http output redacts live request/response header secrets. ## Verification - `pnpm exec vitest run server/src/__tests__/http-log-redaction.test.ts` - `pnpm --filter @paperclipai/server typecheck` - Pre-commit TruffleHog scan: 0 verified/unverified secrets. - PR CI observed passing so far for policy, Typecheck + Release Registry, Build, e2e, Socket, Snyk, security-review, and serialized/workspace suites; remaining jobs may still be running. ## Risks - Low runtime risk: this only expands pino redaction paths. - Possible coverage risk: broader redaction helpers in related PRs may cover more serialized variants beyond the pino-http request/response header pipeline tested here. - No migrations, schema changes, or UI changes. ## Model Used - OpenAI Codex via Hermes Agent, model gpt-5.5, tool-using coding/ops session. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] If this change affects the UI, I have included before/after screenshots - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Andrew Aymeloglu <aaymeloglu@gmail.com>
98 lines
3.1 KiB
TypeScript
98 lines
3.1 KiB
TypeScript
import path from "node:path";
|
|
import fs from "node:fs";
|
|
import pino from "pino";
|
|
import { pinoHttp } from "pino-http";
|
|
import { readConfigFile } from "../config-file.js";
|
|
import { resolveDefaultLogsDir, resolveHomeAwarePath } from "../home-paths.js";
|
|
import { HTTP_LOG_REDACT_PATHS } from "./http-log-redaction.js";
|
|
import { shouldSilenceHttpSuccessLog } from "./http-log-policy.js";
|
|
import { redactSensitive } from "./redact-sensitive.js";
|
|
|
|
function resolveServerLogDir(): string {
|
|
const envOverride = process.env.PAPERCLIP_LOG_DIR?.trim();
|
|
if (envOverride) return resolveHomeAwarePath(envOverride);
|
|
|
|
const fileLogDir = readConfigFile()?.logging.logDir?.trim();
|
|
if (fileLogDir) return resolveHomeAwarePath(fileLogDir);
|
|
|
|
return resolveDefaultLogsDir();
|
|
}
|
|
|
|
const logDir = resolveServerLogDir();
|
|
fs.mkdirSync(logDir, { recursive: true });
|
|
|
|
const logFile = path.join(logDir, "server.log");
|
|
|
|
const sharedOpts = {
|
|
translateTime: "SYS:HH:MM:ss",
|
|
ignore: "pid,hostname",
|
|
singleLine: true,
|
|
};
|
|
|
|
export const logger = pino({
|
|
level: "debug",
|
|
redact: [...HTTP_LOG_REDACT_PATHS],
|
|
}, pino.transport({
|
|
targets: [
|
|
{
|
|
target: "pino-pretty",
|
|
options: { ...sharedOpts, ignore: "pid,hostname,req,res,responseTime", colorize: true, destination: 1 },
|
|
level: "info",
|
|
},
|
|
{
|
|
target: "pino-pretty",
|
|
options: { ...sharedOpts, colorize: false, destination: logFile, mkdir: true },
|
|
level: "debug",
|
|
},
|
|
],
|
|
}));
|
|
|
|
export const httpLogger = pinoHttp({
|
|
logger,
|
|
customLogLevel(_req, res, err) {
|
|
if (shouldSilenceHttpSuccessLog(_req.method, _req.url, res.statusCode)) {
|
|
return "silent";
|
|
}
|
|
if (err || res.statusCode >= 500) return "error";
|
|
if (res.statusCode >= 400) return "warn";
|
|
return "info";
|
|
},
|
|
customSuccessMessage(req, res) {
|
|
return `${req.method} ${req.url} ${res.statusCode}`;
|
|
},
|
|
customErrorMessage(req, res, err) {
|
|
const ctx = (res as any).__errorContext;
|
|
const errMsg = ctx?.error?.message || err?.message || (res as any).err?.message || "unknown error";
|
|
return `${req.method} ${req.url} ${res.statusCode} — ${errMsg}`;
|
|
},
|
|
customProps(req, res) {
|
|
if (res.statusCode >= 400) {
|
|
const ctx = (res as any).__errorContext;
|
|
if (ctx) {
|
|
return {
|
|
errorContext: ctx.error,
|
|
reqBody: redactSensitive(ctx.reqBody),
|
|
reqParams: redactSensitive(ctx.reqParams),
|
|
reqQuery: redactSensitive(ctx.reqQuery),
|
|
};
|
|
}
|
|
const props: Record<string, unknown> = {};
|
|
const { body, params, query } = req as any;
|
|
if (body && typeof body === "object" && Object.keys(body).length > 0) {
|
|
props.reqBody = redactSensitive(body);
|
|
}
|
|
if (params && typeof params === "object" && Object.keys(params).length > 0) {
|
|
props.reqParams = redactSensitive(params);
|
|
}
|
|
if (query && typeof query === "object" && Object.keys(query).length > 0) {
|
|
props.reqQuery = redactSensitive(query);
|
|
}
|
|
if ((req as any).route?.path) {
|
|
props.routePath = (req as any).route.path;
|
|
}
|
|
return props;
|
|
}
|
|
return {};
|
|
},
|
|
});
|