Harden backup restore tar extraction #574

Merged
ghreprimand merged 1 commit from ghreprimand/harden-backup-restore-tar into main 2026-06-01 20:08:46 +02:00
ghreprimand commented 2026-06-01 16:54:34 +02:00 (Migrated from github.com)

Summary

  • validate backup archive members before restore and verify
  • reject symlink, hardlink, and special-file entries instead of passing them to tar.extractall()
  • extract only regular files and directories under data/ manually
  • skip symlinked files when creating snapshots so generated backups match the hardened restore contract

Why

The restore path already rejected absolute paths and ../ traversal, but tar archives can also redirect writes through link entries. For example, an archive can create a link inside data/ and then include a later regular file beneath that link. Using extractall() leaves that class of archive behavior to tarfile.

This keeps restores constrained to the data/ tree by accepting only regular files/directories and copying file contents directly after validation. It also makes odysseus-backup verify reject archives that restore would refuse.

Tests

  • venv/bin/python -m pytest tests/test_backup_cli_security.py -q
  • venv/bin/python -m pytest tests/test_security_regressions.py -q
  • venv/bin/python -m py_compile scripts/odysseus-backup
  • git diff --check
## Summary - validate backup archive members before restore and verify - reject symlink, hardlink, and special-file entries instead of passing them to tar.extractall() - extract only regular files and directories under data/ manually - skip symlinked files when creating snapshots so generated backups match the hardened restore contract ## Why The restore path already rejected absolute paths and ../ traversal, but tar archives can also redirect writes through link entries. For example, an archive can create a link inside data/ and then include a later regular file beneath that link. Using extractall() leaves that class of archive behavior to tarfile. This keeps restores constrained to the data/ tree by accepting only regular files/directories and copying file contents directly after validation. It also makes odysseus-backup verify reject archives that restore would refuse. ## Tests - venv/bin/python -m pytest tests/test_backup_cli_security.py -q - venv/bin/python -m pytest tests/test_security_regressions.py -q - venv/bin/python -m py_compile scripts/odysseus-backup - git diff --check
sleepy merged commit d8451ea849 into main 2026-06-01 20:08:46 +02:00
Owner

Merged via squash. Restore path now validates members before extraction, rejects symlinks/hardlinks/special files, and uses safe member-by-member extraction instead of extractall(). All 4 tests pass.

Merged via squash. Restore path now validates members before extraction, rejects symlinks/hardlinks/special files, and uses safe member-by-member extraction instead of extractall(). All 4 tests pass.
Sign in to join this conversation.
No description provided.