From 9fa33160cf792ce872fe1bcdcb51f3b2415270f9 Mon Sep 17 00:00:00 2001 From: Furox Date: Sat, 5 Sep 2026 02:09:18 +0300 Subject: [PATCH] chore: remove temporary issue 416 security workflow --- .../workflows/issue-416-security-round5.yml | 150 ------------------ 1 file changed, 150 deletions(-) delete mode 100644 .github/workflows/issue-416-security-round5.yml diff --git a/.github/workflows/issue-416-security-round5.yml b/.github/workflows/issue-416-security-round5.yml deleted file mode 100644 index 8663cdcaf..000000000 --- a/.github/workflows/issue-416-security-round5.yml +++ /dev/null @@ -1,150 +0,0 @@ -name: Issue 416 Security Round 5 - -on: - push: - branches: - - feat/issue-416-backup-destinations - paths: - - .github/workflows/issue-416-security-round5.yml - -permissions: - contents: write - -jobs: - fix-and-verify: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - with: - ref: feat/issue-416-backup-destinations - - uses: pnpm/action-setup@v5 - with: - version: 10.22.0 - - uses: actions/setup-node@v5 - with: - node-version: 24.4.0 - cache: pnpm - - run: pnpm install --frozen-lockfile - - name: Apply guarded final hardening - run: | - python - <<'PY' - from pathlib import Path - - def replace_once(text, old, new, label): - count = text.count(old) - if count != 1: - raise SystemExit(f"{label}: expected one match, found {count}") - return text.replace(old, new, 1) - - utils = Path("packages/server/src/utils/backups/utils.ts") - text = utils.read_text() - old = '''\t\tif (destination.secretAccessKey) {\n\t\t\tconst obscuredPassword = await obscureRclonePassword(\n\t\t\t\tdestination.secretAccessKey,\n\t\t\t);\n\t\t\tflags.push(`--${backend}-pass=${quote([obscuredPassword])}`);\n\t\t}\n\t\tflags.push(...additionalFlags);\n\t\treturn {\n''' - new = '''\t\tif (destination.secretAccessKey) {\n\t\t\tconst obscuredPassword = await obscureRclonePassword(\n\t\t\t\tdestination.secretAccessKey,\n\t\t\t);\n\t\t\tflags.push(`--${backend}-pass=${quote([obscuredPassword])}`);\n\t\t}\n\t\tflags.push(...additionalFlags);\n\t\tif (provider === RCLONE_DESTINATION_PROVIDERS.FTP) {\n\t\t\t// CLI options override RCLONE_* environment defaults. Keep TLS\n\t\t\t// certificate verification enabled on the execution host.\n\t\t\tflags.push(\n\t\t\t\t"--ftp-no-check-certificate=false",\n\t\t\t\t"--no-check-certificate=false",\n\t\t\t);\n\t\t}\n\t\treturn {\n''' - text = replace_once(text, old, new, "FTP secure CLI overrides") - utils.write_text(text) - - redact = Path("packages/server/src/utils/backups/redact.ts") - existing = redact.read_text() - required = [ - "export const redactRcloneCredentials", - "sftp-key-file-pass", - "export const getSafeRcloneErrorMessage", - ] - if not all(marker in existing for marker in required): - raise SystemExit("redact.ts no longer matches audited structure") - redact.write_text(r'''/** - * Redacts credentials from rclone command strings before they reach logs or - * user-facing error output. Handles both the existing S3 flags and the - * provider-specific FTP/SFTP credential flags used by backup destinations. - */ -export const redactRcloneCredentials = (command: string): string => { - return command.replace( - /(--(?:s3-access-key-id|s3-secret-access-key|ftp-pass|sftp-pass|sftp-key-file-pass)=)(?:(?:"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|\\[^\r\n]|[^\s"'\\])+)/g, - '$1"[REDACTED]"', - ); -}; - -export const getSafeRcloneErrorMessage = (error: unknown): string => - redactRcloneCredentials( - error instanceof Error ? error.message : String(error), - ); -''') - - test = Path("apps/dokploy/__test__/backups/redact-credentials.test.ts") - text = test.read_text() - if 'from "shell-quote"' not in text: - text = replace_once( - text, - 'import { describe, expect, it } from "vitest";', - 'import { quote } from "shell-quote";\nimport { describe, expect, it } from "vitest";', - "shell-quote test import", - ) - extra = '''\n\tit("should fully redact shell-quote output with embedded quotes and whitespace", () => {\n\t\tconst secret = "PART_A' PART_B\\\" $PART_C;\\\\PART_D";\n\t\tconst cmd = `rclone lsf --s3-secret-access-key=${quote([secret])} --s3-region=us-east-1 :s3:bucket`;\n\t\tconst redacted = redactRcloneCredentials(cmd);\n\n\t\tfor (const fragment of ["PART_A", "PART_B", "PART_C", "PART_D"]) {\n\t\t\texpect(redacted).not.toContain(fragment);\n\t\t}\n\t\texpect(redacted).toContain('--s3-secret-access-key="[REDACTED]"');\n\t\texpect(redacted).toContain("--s3-region=us-east-1");\n\t});\n''' - idx = text.rfind("\n});") - if idx == -1: - raise SystemExit("redaction test describe end not found") - if "fully redact shell-quote output" not in text: - text = text[:idx] + extra + text[idx:] - test.write_text(text) - - backup_test = Path("apps/dokploy/__test__/utils/backups.test.ts") - text = backup_test.read_text() - extra = '''\n\ttest("forces certificate verification on after user flags", async () => {\n\t\tconst result = await getRclonePathAndFlags(\n\t\t\tdestination({\n\t\t\t\tprovider: RCLONE_DESTINATION_PROVIDERS.FTP,\n\t\t\t\tendpoint: "storage.example.com",\n\t\t\t\taccessKey: "backup-user",\n\t\t\t\tsecretAccessKey: "",\n\t\t\t\tregion: "",\n\t\t\t\tbucket: "backups",\n\t\t\t\tadditionalFlags: ["--ftp-explicit-tls"],\n\t\t\t}),\n\t\t);\n\t\texpect(result.flags.slice(-2)).toEqual([\n\t\t\t"--ftp-no-check-certificate=false",\n\t\t\t"--no-check-certificate=false",\n\t\t]);\n\t});\n''' - idx = text.rfind("\n});") - if idx == -1: - raise SystemExit("FTP test describe end not found") - if "forces certificate verification on after user flags" not in text: - text = text[:idx] + extra + text[idx:] - backup_test.write_text(text) - PY - - name: Format changed files - run: | - pnpm exec biome check --write \ - packages/server/src/utils/backups/utils.ts \ - packages/server/src/utils/backups/redact.ts \ - apps/dokploy/__test__/backups/redact-credentials.test.ts \ - apps/dokploy/__test__/utils/backups.test.ts - - name: Focused backup and security regressions - run: | - pnpm --filter=dokploy exec vitest --config __test__/vitest.config.ts \ - __test__/backups/redact-credentials.test.ts \ - __test__/utils/backups.test.ts \ - __test__/utils/issue-416-path-safety.test.ts --run - - name: Typecheck - run: pnpm typecheck - - name: Server build - run: pnpm server:build - - name: Biome clean - run: | - pnpm exec biome check \ - packages/server/src/utils/backups/utils.ts \ - packages/server/src/utils/backups/redact.ts \ - apps/dokploy/__test__/backups/redact-credentials.test.ts \ - apps/dokploy/__test__/utils/backups.test.ts - - name: Verify final security invariants - run: | - python - <<'PY' - from pathlib import Path - u=Path("packages/server/src/utils/backups/utils.ts").read_text() - assert '"--ftp-no-check-certificate=false"' in u - assert '"--no-check-certificate=false"' in u - r=Path("packages/server/src/utils/backups/redact.ts").read_text() - assert "sftp-key-file-pass" in r - assert "[^\\r\\n]" in r - t=Path("apps/dokploy/__test__/backups/redact-credentials.test.ts").read_text() - assert 'fully redact shell-quote output' in t - print("environment-default and complete shell-word redaction invariants verified") - PY - - name: Commit verified hardening - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add \ - packages/server/src/utils/backups/utils.ts \ - packages/server/src/utils/backups/redact.ts \ - apps/dokploy/__test__/backups/redact-credentials.test.ts \ - apps/dokploy/__test__/utils/backups.test.ts - if ! git diff --cached --quiet; then - git commit -m "fix: harden rclone environment and credential redaction" - git push origin HEAD:feat/issue-416-backup-destinations - fi