mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-13 23:14:53 +09:00
Redact credentials from logged HTTP request headers (#1054)
The controller HTTP access log and the express error log wrote the full request headers at the http level, which both default log transports keep. Clients send their access token in X-Access-Token or Authorization: Bearer, so user and host tokens ended up in plain text in controller and cluster logs, and the cluster log can be read back over the API with core.log.query. Mask the authorization, x-access-token, cookie and proxy-authorization headers before logging. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
069f537bde
commit
fe1391903a
2 files changed
+36
-2
No files matched your search
@@ -409,7 +409,7 @@ export default class Controller {
|
||||
method: req.method,
|
||||
url: req.originalUrl,
|
||||
httpVersion: req.httpVersion,
|
||||
headers: req.headers,
|
||||
headers: Controller.loggableHeaders(req.headers),
|
||||
query: req.query,
|
||||
},
|
||||
});
|
||||
@@ -897,7 +897,7 @@ export default class Controller {
|
||||
statusCode: res.statusCode,
|
||||
responseTime: durationMs,
|
||||
httpVersion: req.httpVersion,
|
||||
headers: req.headers,
|
||||
headers: Controller.loggableHeaders(req.headers),
|
||||
query: req.query,
|
||||
},
|
||||
});
|
||||
@@ -1226,6 +1226,22 @@ export default class Controller {
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy of request headers with credentials masked for logging
|
||||
*
|
||||
* @param headers - headers of the incoming request.
|
||||
* @returns headers safe to write to the log.
|
||||
*/
|
||||
static loggableHeaders(headers: http.IncomingHttpHeaders) {
|
||||
const result = { ...headers };
|
||||
for (const name of ["authorization", "cookie", "proxy-authorization", "x-access-token"]) {
|
||||
if (result[name] !== undefined) {
|
||||
result[name] = "[redacted]";
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serves the web interface with the root path set appropriately
|
||||
*
|
||||
|
||||
@@ -86,6 +86,24 @@ describe("controller/src/Controller", function() {
|
||||
await assert.rejects(Controller.loadJsonObject(path.join(dir, "invalid.json")), SyntaxError);
|
||||
});
|
||||
});
|
||||
describe(".loggableHeaders()", function() {
|
||||
it("should redact credentials and keep other headers", function() {
|
||||
const headers = {
|
||||
"authorization": "Bearer secret",
|
||||
"x-access-token": "secret",
|
||||
"cookie": "a=secret",
|
||||
"user-agent": "test",
|
||||
};
|
||||
assert.deepEqual(Controller.loggableHeaders(headers), {
|
||||
"authorization": "[redacted]",
|
||||
"x-access-token": "[redacted]",
|
||||
"cookie": "[redacted]",
|
||||
"user-agent": "test",
|
||||
});
|
||||
assert.equal(headers["x-access-token"], "secret");
|
||||
});
|
||||
});
|
||||
|
||||
describe(".serveWeb()", function() {
|
||||
let server, port;
|
||||
before(async function() {
|
||||
|
||||
Reference in new issue
Block a user