{"id":"HSEC-2026-0014","summary":"Arbitrary file write via path traversal in unpackToDir","details":"# Arbitrary file write via path traversal in unpackToDir\n\n`Codec.Archive.unpackToDir` extracts a tar (or other archive supported by the\nlibarchive C library) from a `ByteString` into a directory. `unpackToDirLazy`\nis the streaming variant; both funnel through the same internal\n`unpackEntriesFp` routine.\n\n`unpackEntriesFp` joined each entry's name onto the destination directory with\n`System.FilePath.(\u003c/\u003e)` without validating the name. Entry names starting with\n`..` sequences therefore escape the destination: `fp \u003c/\u003e \"../../../../etc/x\"`\nresolves outside the extraction root. The joined path is written back into the\narchive entry (`archiveEntrySetPathname`) and handed to libarchive's\n`archive_read_extract` with no `ARCHIVE_EXTRACT_SECURE_*` flags, so neither the\nHaskell layer nor the C library rejects the traversal. The `..` check is not\nlimited to tar archives: any format whose entry names libarchive reports\nverbatim is affected. Extraction result codes were also discarded, so write\nfailures could pass unnoticed.\n\nAn attacker who can supply an archive to a service that extracts it with these\nAPIs gains an arbitrary file write with the privileges of the extracting\nprocess. A demonstrated escalation (verified in a container): an archive with\nmember `../../../../etc/profile.d/zz-poc-zipslip.sh` plants a shell script\nsourced by every subsequent login shell, converting the file write into code\nexecution when any user logs in.\n\nThe reported traversal is fixed in libarchive-3.0.6.0, which fails with\n`ArchiveFatal` when an entry path begins with `..`\n(commit `325a2296379905557de1fa21b28d86f34bab2441`).\n\nUsers should upgrade to libarchive-3.0.6.0 or later. If pinned to an older\nversion, validate entry names before extraction (reject absolute paths and any\npath containing a `..` component) or extract inside a sandboxed directory.\n\nExternal vulnerability reported by Akash Sebastian (justabughunter).\n","modified":"2026-11-13T11:15:03.323437701Z","published":"2026-11-13T11:01:54Z","database_specific":{"osvs":"https://raw.githubusercontent.com/haskell/security-advisories/refs/heads/generated/osv-export","repository":"https://github.com/haskell/security-advisories","home":"https://github.com/haskell/security-advisories"},"references":[{"type":"PACKAGE","url":"https://hackage.haskell.org/package/libarchive"},{"type":"FIX","url":"https://github.com/vmchale/libarchive/commit/325a2296379905557de1fa21b28d86f34bab2441"}],"affected":[{"package":{"name":"libarchive","ecosystem":"Hackage","purl":"pkg:hackage/libarchive"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0.1.0.0"},{"fixed":"3.0.6.0"}]}],"versions":["0.1.0.0","0.2.0.0","0.2.1.0","0.2.1.1","0.2.1.2","1.0.0.0","1.0.1.0","1.0.2.0","1.0.3.0","1.0.4.0","1.0.5.0","1.0.5.1","2.0.0.0","2.0.0.1","2.0.0.2","2.1.0.0","2.1.0.1","2.1.1.0","2.1.2.0","2.1.2.1","2.1.3.0","2.1.3.1","2.1.3.2","2.2.0.0","2.2.0.1","2.2.0.2","2.2.1.0","2.2.2.0","2.2.3.0","2.2.4.0","2.2.5.0","2.2.5.1","2.2.5.2","3.0.0.0","3.0.1.1","3.0.2.0","3.0.2.1","3.0.2.2","3.0.3.0","3.0.3.1","3.0.3.2","3.0.4.1","3.0.4.2","3.0.5.0"],"ecosystem_specific":{"affected_api":[{"name":"unpackToDir","module":"Codec.Archive"},{"module":"Codec.Archive","name":"unpackToDirLazy"},{"module":"Codec.Archive","name":"unpackArchive"}]},"database_specific":{"human_link":"https://github.com/haskell/security-advisories/tree/main/advisories/published/2026/HSEC-2026-0014.md","osv":"https://raw.githubusercontent.com/haskell/security-advisories/refs/heads/generated/osv-export/2026/HSEC-2026-0014.json","source":"https://github.com/haskell/security-advisories/blob/generated/osv-export/2026/HSEC-2026-0014.json"},"severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"}]}],"schema_version":"1.9.2"}