Commit Graph
2 Commits
Author SHA1 Message Date
shadowdaoandClaude Opus 5.5 d4a63fba0a extract: verify always runs, failures remove the extract, walk fails closed
Security review of 52b1e65:
1. (HIGH) tarfile's stream could enumerate differently from GNU tar. Now
   extract.sh captures tar's exit code (`|| TAR_RC=$?`), ALWAYS runs the
   post-extract verify on whatever tar wrote, and any failure (tar,
   verify, either pre-extract scan) removes $DEST before exiting non-zero.
   The classifier also refuses (exit 3) on any stream exception and when
   its member count differs from GNU tar's own escape-quoted listing.
   Measured: a lone zero block mid-archive stops BOTH readers at the same
   member (no -i is used), so this is a guard, not a known differential.
2. (MEDIUM) os.walk swallowed unreadable directories. verify now passes
   onerror (recorded => fail) and refuses any directory that is not
   readable+searchable. 52b1e65 exited 0 on the unreadable fixture.
3. (MEDIUM) a refused tree stayed in the bind-mounted output. fail_extract
   opens up 0000 dirs (find -P -type d, never follows links), then
   rm -rf -- "$DEST".

Fixtures: cpmove-bad-{unreadable,tarfail,truncated}; a fake tar that lists
one extra member proves the count check. 14 checks fail on 52b1e65.
Docker: unreadable / tarfail runs end with status failed, failed_stage
extract and no extract-work left in /host/sanitized/<id>.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 09:18:34 -07:00
shadowdaoandClaude Opus 5.5 52b1e656c3 extract: refuse parser-differential archives; post-extract link check is the authority
Security review of 4788ca4 (inline python link classifier):
1. Names like cpmove-x/./homedir/mail/..., homedir//mail, homedir/../homedir
   or /cpmove-x/... were matched differently by the classifier than by tar,
   so a mail-tree symlink could escape the strip (measured: the dot, dslash
   and abs fixtures extracted with exit 0 on 4788ca4).
2. A newline in a member name split one --exclude-from pattern in two.

Moved the policy to scripts/lib/mail-tree-links.py:
- scan (pre-extract, an optimisation): REFUSES the archive (exit 3) on any
  control character in a member name or link target, or any non-canonical
  member name / hard-link target (absolute, '..', or normpath() differing
  after one leading './'). Symlink targets are not canonical-checked:
  ordinary cPanel links (../public_html) are legitimate outside the mail
  tree, and every symlink inside it is stripped anyway.
- verify (post-extract, the authority): lstat-walks each mail tree; exit 4
  on any symlink, or any regular file whose st_nlink exceeds its names
  inside that same mail tree (a link leaving it). In-tree Dovecot hard-link
  copies (maildir_copy_with_hardlinks) are kept; a blanket nlink>1 refusal
  would drop real mail.
- Fixtures: cpmove-bad-{dot,dslash,dotdot,abs,newline}; verify tests for
  symlinks and hard links into/out of/between mail trees surviving the
  pre-filter. 14 of the new checks fail on 4788ca4.

Only in IMPORT_INCLUDE_MAIL=true mode; the default path is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 09:13:26 -07:00