Skip to content

fix(backup): .backupignore permission check false-positives on Windows #44362

@brettdavies

Description

@brettdavies

Summary

The POSIX permission check in readPatternFile incorrectly rejects .backupignore files on Windows. Windows does not implement POSIX permission bits, so (stat.mode & 0o022) !== 0 always evaluates to true, causing all .backupignore files to be skipped with the warning:

⚠️  --exclude-file …\.backupignore: group/world writable — skipping for security

This breaks two unit tests on Windows CI shard 2.

Reproduction

Consistently reproduces on checks-windows shard 2 in CI. The two failing tests in src/infra/backup-exclude.test.ts:

  • rejects too-long pattern from .backupignore (not just CLI) — the .backupignore file is silently skipped before the too-long pattern validation runs
  • .backupignore auto-loaded from stateDir when present — patterns are never loaded because the file is rejected as "world writable"

Root Cause

src/infra/backup-exclude.ts:123-127:

if (opts.permissionCheck) {
  const isGroupOrWorldWritable = (fileStat.mode & 0o022) !== 0;
  if (isGroupOrWorldWritable) {
    throw new ExcludeFileError(filePath, "group/world writable — skipping for security");
  }
}

On Windows, Node.js fs.stat().mode does not report real POSIX permission bits. The group/world-writable bits are typically set (or meaningless), so this check always triggers. The permission check is called for .backupignore auto-loading (permissionCheck: true at line 179) but not for --exclude-file (which uses throwOnError: true instead).

Possible Fix

Skip the POSIX permission check on Windows since it has no meaningful security value there:

if (opts.permissionCheck && process.platform !== "win32") {
  const isGroupOrWorldWritable = (fileStat.mode & 0o022) !== 0;
  if (isGroupOrWorldWritable) {
    throw new ExcludeFileError(filePath, "group/world writable — skipping for security");
  }
}

Alternatively, use Windows ACL checks via fs.access(filePath, fs.constants.W_OK) on Windows, though the threat model for .backupignore may not warrant that complexity.

CI Evidence

Affected Tests

Both in src/infra/backup-exclude.test.ts:

  • rejects too-long pattern from .backupignore (not just CLI)
  • .backupignore auto-loaded from stateDir when present

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions