zipcheck 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- zipcheck-0.1.0/.dockerignore +25 -0
- zipcheck-0.1.0/CHANGELOG.md +75 -0
- zipcheck-0.1.0/CONTRIBUTING.md +107 -0
- zipcheck-0.1.0/Dockerfile +26 -0
- zipcheck-0.1.0/LICENSE +21 -0
- zipcheck-0.1.0/MANIFEST.in +10 -0
- zipcheck-0.1.0/PKG-INFO +676 -0
- zipcheck-0.1.0/README.md +635 -0
- zipcheck-0.1.0/SECURITY.md +55 -0
- zipcheck-0.1.0/docs/metadata-map.md +299 -0
- zipcheck-0.1.0/docs/production.md +237 -0
- zipcheck-0.1.0/fuzz/fuzz_scan.py +51 -0
- zipcheck-0.1.0/fuzz/generate_seeds.py +172 -0
- zipcheck-0.1.0/packaging/aur/.SRCINFO +18 -0
- zipcheck-0.1.0/packaging/aur/PKGBUILD +29 -0
- zipcheck-0.1.0/packaging/aur/refresh.sh +6 -0
- zipcheck-0.1.0/pyproject.toml +103 -0
- zipcheck-0.1.0/setup.cfg +4 -0
- zipcheck-0.1.0/src/zipcheck/__init__.py +21 -0
- zipcheck-0.1.0/src/zipcheck/__main__.py +4 -0
- zipcheck-0.1.0/src/zipcheck/cli.py +333 -0
- zipcheck-0.1.0/src/zipcheck/detect.py +138 -0
- zipcheck-0.1.0/src/zipcheck/engine.py +968 -0
- zipcheck-0.1.0/src/zipcheck/formats/__init__.py +1 -0
- zipcheck-0.1.0/src/zipcheck/formats/bzip2fmt.py +95 -0
- zipcheck-0.1.0/src/zipcheck/formats/gzipfmt.py +143 -0
- zipcheck-0.1.0/src/zipcheck/formats/probe.py +62 -0
- zipcheck-0.1.0/src/zipcheck/formats/rarfmt.py +280 -0
- zipcheck-0.1.0/src/zipcheck/formats/sevenzipfmt.py +382 -0
- zipcheck-0.1.0/src/zipcheck/formats/tarfmt.py +271 -0
- zipcheck-0.1.0/src/zipcheck/formats/xzfmt.py +170 -0
- zipcheck-0.1.0/src/zipcheck/formats/zipfmt.py +1034 -0
- zipcheck-0.1.0/src/zipcheck/formats/zstdfmt.py +80 -0
- zipcheck-0.1.0/src/zipcheck/limits.py +62 -0
- zipcheck-0.1.0/src/zipcheck/model.py +216 -0
- zipcheck-0.1.0/src/zipcheck/py.typed +0 -0
- zipcheck-0.1.0/src/zipcheck/reader.py +169 -0
- zipcheck-0.1.0/src/zipcheck/verify.py +816 -0
- zipcheck-0.1.0/src/zipcheck.egg-info/PKG-INFO +676 -0
- zipcheck-0.1.0/src/zipcheck.egg-info/SOURCES.txt +54 -0
- zipcheck-0.1.0/src/zipcheck.egg-info/dependency_links.txt +1 -0
- zipcheck-0.1.0/src/zipcheck.egg-info/entry_points.txt +2 -0
- zipcheck-0.1.0/src/zipcheck.egg-info/requires.txt +12 -0
- zipcheck-0.1.0/src/zipcheck.egg-info/top_level.txt +1 -0
- zipcheck-0.1.0/tests/__init__.py +0 -0
- zipcheck-0.1.0/tests/conftest.py +8 -0
- zipcheck-0.1.0/tests/helpers.py +463 -0
- zipcheck-0.1.0/tests/test_cli.py +199 -0
- zipcheck-0.1.0/tests/test_other_formats.py +86 -0
- zipcheck-0.1.0/tests/test_property.py +84 -0
- zipcheck-0.1.0/tests/test_robustness.py +53 -0
- zipcheck-0.1.0/tests/test_tar.py +64 -0
- zipcheck-0.1.0/tests/test_verify.py +219 -0
- zipcheck-0.1.0/tests/test_zip.py +194 -0
- zipcheck-0.1.0/tools/benchmark.py +46 -0
- zipcheck-0.1.0/tools/generate_samples.py +235 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
.git/
|
|
2
|
+
.github/
|
|
3
|
+
.venv/
|
|
4
|
+
.smoke/
|
|
5
|
+
.uv-cache/
|
|
6
|
+
.uv-python/
|
|
7
|
+
.hypothesis/
|
|
8
|
+
.mypy_cache/
|
|
9
|
+
.pytest_cache/
|
|
10
|
+
.ruff_cache/
|
|
11
|
+
.linTools/
|
|
12
|
+
build/
|
|
13
|
+
dist/
|
|
14
|
+
htmlcov/
|
|
15
|
+
**/__pycache__/
|
|
16
|
+
**/*.egg-info/
|
|
17
|
+
.coverage
|
|
18
|
+
*.sarif
|
|
19
|
+
docs/
|
|
20
|
+
fuzz/
|
|
21
|
+
packaging/
|
|
22
|
+
tests/
|
|
23
|
+
tools/
|
|
24
|
+
pytest-of-*
|
|
25
|
+
tmp*/
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here.
|
|
4
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
5
|
+
and this project uses [Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [0.1.0] - 2026-10-24
|
|
8
|
+
|
|
9
|
+
First public release.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Static metadata scanners for ZIP/ZIP64, TAR (including GNU/PAX sparse),
|
|
14
|
+
gzip, bzip2, xz/LZMA, Zstandard, 7-Zip and RAR 4/5.
|
|
15
|
+
- Expansion-ratio and absolute-size policy with `clean` / `suspicious` /
|
|
16
|
+
`dangerous` / `unknown` verdicts.
|
|
17
|
+
- Detection of impossible DEFLATE ratios, stored-size mismatches, overlapping
|
|
18
|
+
entries, shared local headers and inconsistent totals.
|
|
19
|
+
- Bounded nested-archive inspection (ZIP, TAR, gzip, bzip2, xz) with depth,
|
|
20
|
+
archive-count, byte and time budgets.
|
|
21
|
+
- Bounded in-memory decompression probe for formats without declared sizes.
|
|
22
|
+
- Bounded content verification (`--verify`, `--verify-limit`,
|
|
23
|
+
`--require-verified`) that decompresses supported content into an in-memory
|
|
24
|
+
counting sink, never to disk.
|
|
25
|
+
- `--production` fail-closed preset for user-upload pipelines; it implies
|
|
26
|
+
`--verify --require-verified`.
|
|
27
|
+
- Recursive verification of nested archives: images up to 8 MiB are captured
|
|
28
|
+
and scanned with a shared 256 MiB nested-output budget.
|
|
29
|
+
- New integrity findings: `HEADER_MISMATCH`, `ENTRY_COUNT_MISMATCH`,
|
|
30
|
+
`UNDERSTATED_COMPRESSED_SIZE`, `OVERSTATED_COMPRESSED_SIZE`,
|
|
31
|
+
`VERIFICATION_FAILED` and `UNVERIFIED`.
|
|
32
|
+
- `nested_unverified` in JSON output, propagated from child scans to the parent.
|
|
33
|
+
- CLI with human and JSON output, directory recursion, configurable fail
|
|
34
|
+
thresholds and documented exit codes.
|
|
35
|
+
- PEP 517/621 packaging with a typed-package marker, wheel/sdist smoke tests,
|
|
36
|
+
and PyPI trusted publishing through GitHub OIDC.
|
|
37
|
+
- CI across Linux, macOS and Windows on Python 3.10-3.14, with branch coverage,
|
|
38
|
+
ruff and mypy gates.
|
|
39
|
+
- Hypothesis property tests and a coverage-guided Atheris fuzz harness with a
|
|
40
|
+
seed corpus and a weekly fuzz workflow.
|
|
41
|
+
- Arch Linux AUR packaging under `packaging/aur`.
|
|
42
|
+
- Safe test corpus generators, sandbox benchmark tool, Dockerfile, and
|
|
43
|
+
documentation of where each format stores its uncompressed size and why a
|
|
44
|
+
magic "bomb signature" does not exist.
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
|
|
48
|
+
- The ZIP parser now parses the central directory to its declared end instead of
|
|
49
|
+
stopping at the attacker-controlled EOCD entry count, exposing hidden entries.
|
|
50
|
+
- Local headers are cross-checked against central-directory metadata, so tools
|
|
51
|
+
cannot be played against each other with contradictory sizes or methods.
|
|
52
|
+
- Verification detects compressed streams that continue past their declared
|
|
53
|
+
`csize`, catching forged one-byte members.
|
|
54
|
+
- xz content verification drained the decoder's buffered output correctly;
|
|
55
|
+
highly compressible streams were previously under-measured and reported as
|
|
56
|
+
`METADATA_OVERSTATED` instead of being fully verified.
|
|
57
|
+
- Verification failures and unverifiable nested content now fail closed
|
|
58
|
+
(`suspicious`, or `unknown` under `--require-verified`) instead of reporting
|
|
59
|
+
`clean`.
|
|
60
|
+
- Nested gzip inspection now follows concatenated members instead of stopping at
|
|
61
|
+
the first one, so an archive split across members is still inspected.
|
|
62
|
+
- Nested xz and bzip2 prefix capture now drains the decoders' buffered output,
|
|
63
|
+
so a highly compressible inner archive is captured completely.
|
|
64
|
+
- ZIP LZMA members are now decoded for nested-archive sniffing (bounded), not
|
|
65
|
+
skipped silently.
|
|
66
|
+
- Hitting the central-directory parse limit is reported as truncation
|
|
67
|
+
(`MEDIUM`) instead of being misclassified as hidden entries (`HIGH`).
|
|
68
|
+
- A malformed-header unpack in the nested ZIP prefix parser shifted `csize` and
|
|
69
|
+
`usize` by two bytes; fixed.
|
|
70
|
+
- `scan_file` now opens the file before `fstat`ing the same descriptor,
|
|
71
|
+
removing a stat-then-open race.
|
|
72
|
+
- Human-readable output now groups repeated nested siblings correctly instead of
|
|
73
|
+
printing each one at the same indentation.
|
|
74
|
+
|
|
75
|
+
[0.1.0]: https://github.com/spaxe-dev/zipcheck/releases/tag/v0.1.0
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for helping make archive handling less exciting.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
git clone <this repository>
|
|
9
|
+
cd zipcheck
|
|
10
|
+
python -m pip install -e ".[dev]"
|
|
11
|
+
python -m pytest
|
|
12
|
+
ruff check src tests tools
|
|
13
|
+
mypy src/zipcheck
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Python 3.10+ is required. Runtime code must stay dependency-free; if a feature
|
|
17
|
+
ever needs a third-party package, put it in an optional extra and make the
|
|
18
|
+
feature degrade gracefully when that extra is not installed.
|
|
19
|
+
|
|
20
|
+
## Ground rules
|
|
21
|
+
|
|
22
|
+
1. **Never extract member data.** Parsers may read metadata, and may decompress
|
|
23
|
+
a *bounded* prefix into memory for nested-archive inspection. They must
|
|
24
|
+
never write extracted content to disk, emit temp files, or call external
|
|
25
|
+
archive tools.
|
|
26
|
+
2. **Never allocate from a declared size.** A header claiming 10 TiB must not
|
|
27
|
+
cause a 10 TiB allocation. Read in fixed-size chunks and stop at a cap.
|
|
28
|
+
3. **Fail closed.** If metadata cannot be read reliably, the result must be
|
|
29
|
+
`unknown` — not `clean`.
|
|
30
|
+
4. **No exceptions escape `scan_file`.** Malformed input is normal input.
|
|
31
|
+
5. **Add a test for every parser change.** Prefer a metadata-only fixture (a
|
|
32
|
+
few dozen bytes) over a real bomb.
|
|
33
|
+
6. **Keep the output stable.** JSON field names are part of the public API.
|
|
34
|
+
|
|
35
|
+
## Adding a format
|
|
36
|
+
|
|
37
|
+
1. Add magic/extension routing in `detect.py`.
|
|
38
|
+
2. Add a parser module under `src/zipcheck/formats/` exposing
|
|
39
|
+
`scan(fp, size, limits, deadline) -> Facts`.
|
|
40
|
+
3. Read sizes from headers or indexes. If the format has no size field, use
|
|
41
|
+
`formats/probe.py` with a bounded output and explain the limitation in the
|
|
42
|
+
module docstring.
|
|
43
|
+
4. Register it in `engine.PARSERS` and decide whether nested inspection is
|
|
44
|
+
feasible.
|
|
45
|
+
5. Add tests: a clean sample, a declared-huge sample, and a malformed sample.
|
|
46
|
+
6. Document where the size lives in `docs/metadata-map.md` and add the format to
|
|
47
|
+
the README table.
|
|
48
|
+
|
|
49
|
+
## Adding a detection rule
|
|
50
|
+
|
|
51
|
+
- Emit a `Finding` with a stable `code`, a severity, and a human explanation.
|
|
52
|
+
- Policy thresholds belong in `Limits`, not hard-coded in a parser.
|
|
53
|
+
- Keep false positives explainable: "this is a large expansion", not "this is
|
|
54
|
+
malware".
|
|
55
|
+
- Update the rules table in `README.md`.
|
|
56
|
+
|
|
57
|
+
## Test fixtures
|
|
58
|
+
|
|
59
|
+
Use `tests/helpers.py` to build archives. Tests must not contain or download
|
|
60
|
+
live bombs. If a real archive is essential to reproduce a bug, minimise it to a
|
|
61
|
+
metadata-only file first.
|
|
62
|
+
|
|
63
|
+
For integration testing against real bombs, use `tools/generate_samples.py`
|
|
64
|
+
inside a disposable VM/container. The generated files are small on disk but
|
|
65
|
+
enormous when extracted.
|
|
66
|
+
|
|
67
|
+
## Fuzzing
|
|
68
|
+
|
|
69
|
+
Hypothesis property tests run with the normal suite. The coverage-guided
|
|
70
|
+
Atheris harness is Linux-only:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
python -m pip install -e .[fuzz]
|
|
74
|
+
python fuzz/generate_seeds.py fuzz/corpus
|
|
75
|
+
python fuzz/fuzz_scan.py fuzz/corpus -max_total_time=120
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The weekly `fuzz` workflow runs the same harness and uploads crashing inputs.
|
|
79
|
+
|
|
80
|
+
## Releasing
|
|
81
|
+
|
|
82
|
+
1. Bump `__version__` in `src/zipcheck/__init__.py`; the package version is read
|
|
83
|
+
from that attribute. Add a `CHANGELOG.md` entry.
|
|
84
|
+
2. Run the full gate: `pytest`, `mypy src/zipcheck`, `ruff check src tests tools`,
|
|
85
|
+
`python -m build` and `twine check dist/*`.
|
|
86
|
+
3. Commit, then tag and push `vX.Y.Z`. `.github/workflows/release.yml` builds and
|
|
87
|
+
smoke-tests the sdist/wheel, publishes to PyPI through OIDC trusted
|
|
88
|
+
publishing, and creates a GitHub release.
|
|
89
|
+
4. One-time setup: register a trusted publisher on PyPI for owner `spaxe-dev`,
|
|
90
|
+
repository `zipcheck`, workflow `release.yml`, environment `pypi`; create the
|
|
91
|
+
same `pypi` environment in the GitHub repository.
|
|
92
|
+
5. For AUR, wait for the GitHub release, update `pkgver` and checksums in
|
|
93
|
+
`packaging/aur/PKGBUILD`, then run `updpkgsums` and
|
|
94
|
+
`makepkg --printsrcinfo > .SRCINFO` before pushing to the AUR repository.
|
|
95
|
+
|
|
96
|
+
## Style
|
|
97
|
+
|
|
98
|
+
- `ruff check src tests tools` must pass.
|
|
99
|
+
- `mypy src/zipcheck` must pass.
|
|
100
|
+
- Line length 100.
|
|
101
|
+
- Type hints on public functions.
|
|
102
|
+
- Comments explain *why*, not *what*.
|
|
103
|
+
|
|
104
|
+
## Security
|
|
105
|
+
|
|
106
|
+
See [SECURITY.md](SECURITY.md) for how to report parser vulnerabilities
|
|
107
|
+
privately.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Small, non-root image for scanning untrusted archives.
|
|
2
|
+
#
|
|
3
|
+
# docker build -t zipcheck .
|
|
4
|
+
# docker run --rm -v "$PWD:/data:ro" zipcheck /data/file.zip
|
|
5
|
+
#
|
|
6
|
+
# The container only needs the archive mounted read-only. zipcheck never writes
|
|
7
|
+
# extracted data, so the filesystem can be read-only as well:
|
|
8
|
+
#
|
|
9
|
+
# docker run --rm --read-only -v "$PWD:/data:ro" zipcheck /data/file.zip
|
|
10
|
+
|
|
11
|
+
FROM python:3.12-slim
|
|
12
|
+
|
|
13
|
+
ENV PYTHONDONTWRITEBYTECODE=1 \
|
|
14
|
+
PYTHONUNBUFFERED=1
|
|
15
|
+
|
|
16
|
+
WORKDIR /app
|
|
17
|
+
|
|
18
|
+
COPY pyproject.toml README.md LICENSE /app/
|
|
19
|
+
COPY src /app/src
|
|
20
|
+
|
|
21
|
+
RUN pip install --no-cache-dir .
|
|
22
|
+
|
|
23
|
+
RUN useradd --create-home --shell /usr/sbin/nologin scanner
|
|
24
|
+
USER scanner
|
|
25
|
+
|
|
26
|
+
ENTRYPOINT ["zipcheck"]
|
zipcheck-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 zipcheck contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
include CHANGELOG.md
|
|
2
|
+
include CONTRIBUTING.md
|
|
3
|
+
include Dockerfile
|
|
4
|
+
include SECURITY.md
|
|
5
|
+
include .dockerignore
|
|
6
|
+
recursive-include docs *.md
|
|
7
|
+
recursive-include fuzz *.py
|
|
8
|
+
recursive-include packaging *
|
|
9
|
+
recursive-include tests *.py
|
|
10
|
+
recursive-include tools *.py
|