mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-09-12 19:51:00 +05:00
Merge ff457b45bc into 853ca33659
This commit is contained in:
commit
4477be7e5f
38
apps/dokploy/__test__/process/redact-secrets.test.ts
Normal file
38
apps/dokploy/__test__/process/redact-secrets.test.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { redactSecrets } from "@dokploy/server/utils/process/redactSecrets";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
// All key material below is synthetic: these base64 strings decode to the
|
||||
// literal text "synthetic-test-not-a-real-...-key" and are not real keys.
|
||||
|
||||
describe("redactSecrets", () => {
|
||||
it("redacts a PEM private key block written to /tmp/id_rsa", () => {
|
||||
const secret = "c3ludGhldGljLXRlc3Qtbm90LWEtcmVhbC1wcml2YXRlLWtleQ==";
|
||||
const command =
|
||||
`echo "-----BEGIN OPENSSH PRIVATE KEY-----\n${secret}\n-----END OPENSSH PRIVATE KEY-----" > /tmp/id_rsa;` +
|
||||
"chmod 600 /tmp/id_rsa;git clone --branch main --depth 1 git@example.com:org/repo /code";
|
||||
|
||||
const redacted = redactSecrets(command);
|
||||
|
||||
expect(redacted).not.toContain(secret);
|
||||
expect(redacted).toContain("[REDACTED PRIVATE KEY]");
|
||||
expect(redacted).toContain("chmod 600 /tmp/id_rsa");
|
||||
expect(redacted).toContain("git clone --branch main");
|
||||
});
|
||||
|
||||
it("redacts a base64 key piped to base64 -d", () => {
|
||||
const secret = "c3ludGhldGljLXRlc3Qtbm90LWEtcmVhbC1jZXJ0LWtleQ==";
|
||||
const command = `echo "${secret}" | base64 -d > "/etc/dokploy/cert.key";`;
|
||||
|
||||
const redacted = redactSecrets(command);
|
||||
|
||||
expect(redacted).not.toContain(secret);
|
||||
expect(redacted).toContain('echo "[REDACTED]" | base64 -d');
|
||||
});
|
||||
|
||||
it("leaves commands without secrets untouched", () => {
|
||||
const command =
|
||||
"git clone --branch main --depth 1 git@github.com:org/repo.git /tmp/code";
|
||||
|
||||
expect(redactSecrets(command)).toBe(command);
|
||||
});
|
||||
});
|
||||
@ -1,3 +1,5 @@
|
||||
import { redactErrorSecrets, redactSecrets } from "./redactSecrets";
|
||||
|
||||
export interface ExecErrorDetails {
|
||||
command: string;
|
||||
stdout?: string;
|
||||
@ -16,13 +18,19 @@ export class ExecError extends Error {
|
||||
public readonly serverId?: string | null;
|
||||
|
||||
constructor(message: string, details: ExecErrorDetails) {
|
||||
super(message);
|
||||
super(redactSecrets(message));
|
||||
this.name = "ExecError";
|
||||
this.command = details.command;
|
||||
this.stdout = details.stdout;
|
||||
this.stderr = details.stderr;
|
||||
this.command = redactSecrets(details.command);
|
||||
this.stdout = details.stdout
|
||||
? redactSecrets(details.stdout)
|
||||
: details.stdout;
|
||||
this.stderr = details.stderr
|
||||
? redactSecrets(details.stderr)
|
||||
: details.stderr;
|
||||
this.exitCode = details.exitCode;
|
||||
this.originalError = details.originalError;
|
||||
this.originalError = details.originalError
|
||||
? redactErrorSecrets(details.originalError)
|
||||
: details.originalError;
|
||||
this.serverId = details.serverId;
|
||||
|
||||
// Maintains proper stack trace for where our error was thrown (only available on V8)
|
||||
|
||||
30
packages/server/src/utils/process/redactSecrets.ts
Normal file
30
packages/server/src/utils/process/redactSecrets.ts
Normal file
@ -0,0 +1,30 @@
|
||||
// Dokploy embeds some secrets directly into the shell commands it runs: the
|
||||
// SSH key written to /tmp/id_rsa when cloning over SSH, and the base64 TLS key
|
||||
// piped to `base64 -d` when provisioning certificates on a remote server. When
|
||||
// such a command fails, its ExecError (command/stdout/stderr) is logged, which
|
||||
// would otherwise persist the secret in plain text. These helpers strip that
|
||||
// material before it can reach the logs.
|
||||
|
||||
const PRIVATE_KEY_BLOCK =
|
||||
/-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z0-9 ]*PRIVATE KEY-----/g;
|
||||
|
||||
const BASE64_DECODE_PIPE = /echo "[A-Za-z0-9+/=]+"\s*\|\s*base64 -d/g;
|
||||
|
||||
export const redactSecrets = (value: string): string =>
|
||||
value
|
||||
.replace(PRIVATE_KEY_BLOCK, "[REDACTED PRIVATE KEY]")
|
||||
.replace(BASE64_DECODE_PIPE, 'echo "[REDACTED]" | base64 -d');
|
||||
|
||||
// Node's child_process errors repeat the failed command on `message`, `stack`
|
||||
// and `cmd`, so redact those too when wrapping an original error.
|
||||
export const redactErrorSecrets = <T extends Error>(error: T): T => {
|
||||
const candidate = error as T & { cmd?: string };
|
||||
candidate.message = redactSecrets(candidate.message);
|
||||
if (candidate.stack) {
|
||||
candidate.stack = redactSecrets(candidate.stack);
|
||||
}
|
||||
if (candidate.cmd) {
|
||||
candidate.cmd = redactSecrets(candidate.cmd);
|
||||
}
|
||||
return candidate;
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user