Skip to content

fix: bound tar.gz metadata decompression - #5179

Open
mnriem wants to merge 4 commits into
github:mainfrom
mnriem:mnriem-archive-metadata-limits
Open

mnriem wants to merge 4 commits into
github:mainfrom
mnriem:mnriem-archive-metadata-limits

Conversation

@mnriem

@mnriem mnriem commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator

Description

Bound decompressed tar.gz reads and seeks before GNU/PAX metadata parsing in both archive format detection and extraction. The shared 54 MiB stream limit includes headers, padding, metadata, and file contents while preserving the existing member-size and total-file-size limits.

Report limit violations through the caller's error type, preserve caller-owned streams, and document the limit in the shared catalog reference.

Testing

  • Tested locally with uv run specify --help
  • Ran existing tests with uv sync && uv run pytest
  • Tested with a sample project (if applicable)

The full suite used the documented worktree-local interpreter instead of the template's uv run pytest invocation:

  • LC_ALL=en_US.UTF-8 .venv/bin/python -m pytest tests -n 4 -q --tb=short — passed: 13,691 passed, 130 skipped, 63 warnings.
  • uv run specify --help — passed.

A separate sample project is not applicable to this shared archive-helper change.

AI Disclosure

  • I did not use AI assistance for this contribution
  • I did use AI assistance (fill in the disclosure below)

AI disclosure: GitHub Copilot using GPT-6.1 Sol performed autonomous, user-directed implementation and validation and generated the code, tests, documentation, and PR description. Reasoning effort was not exposed to the session. A GitHub Copilot security-review subagent assisted with the initial assessment; its model was not reported. No line-by-line human review is claimed.

Enforce a shared decompressed-stream limit before tar metadata reads and forward seeks in archive detection and extraction.

Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI auto review requested due to automatic review settings October 29, 2026 14:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Tiny archives can trigger excessive memory use without a dedicated GNU/PAX metadata limit, while exception-handling flaws can reject valid ZIPs and obscure format-mismatch and size-limit diagnostics.

3 open issues
What changed in this PR

Hardens Spec Kit’s shared archive helpers by bounding decompressed tar.gz access before GNU/PAX metadata parsing during format detection and extraction.

Changes:

  • Adds a 54 MiB decompressed-stream budget alongside existing member and total-file-size limits.
  • Adds coverage for oversized metadata, read/seek boundaries, and caller-owned stream handling.
  • Documents the stream limit in the shared catalog reference.

AI disclosure: GitHub Copilot performed this review autonomously for the requester; model/settings unavailable. Review text and suggested fixes are AI-generated.

File Description
tests/​test_download_security.py Adds metadata-budget, stream-boundary, and stream-ownership coverage.
src/​specify_cli/​_download_security.py Adds a bounded gzip reader for tar detection and extraction.
docs/​reference/​catalogs.md Documents the 54 MiB limit and its scope.

Review effort: Max (auto)

Comment thread src/specify_cli/_download_security.py
Comment thread src/specify_cli/_download_security.py Outdated
Comment thread src/specify_cli/_download_security.py Outdated
@mnriem mnriem added the triage-must-have Verdict: high-value, important work for Spec Kit — do first label Oct 29, 2026
Bound individual GNU/PAX payloads before recursive metadata parsing, preserve size-limit errors for OSError-derived caller types, and treat malformed gzip headers as format-probe failures.

Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI auto review requested due to automatic review settings October 29, 2026 16:26
@mnriem

mnriem commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed commit 373ca76: GNU/PAX metadata payloads are now capped at 64 KiB before recursive parsing reads them, size-limit diagnostics are preserved for OSError-derived caller errors, and malformed gzip headers no longer hide format mismatches or reject otherwise valid ZIP archives. The targeted archive and installer suites passed all 1,791 tests. AI disclosure: posted on behalf of @mnriem by GitHub Copilot using GPT-6.1 Sol in autonomous, user-directed mode; reasoning effort was not exposed. The code, tests, documentation, and this summary were AI-generated.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

GNU sparse maps bypass the metadata cap and allow large in-memory lists, while extension and workflow wrappers conceal archive-limit errors.

2 open issues
3 resolved since last review

Review effort: Max (auto)

Comment thread src/specify_cli/_download_security.py
Comment on lines +253 to +254
except _TarLimitError as exc:
_raise_from(error_type, str(exc), exc)
Reject old GNU and GNU sparse 1.0 map parsing while retaining bounded PAX sparse formats. Preserve tar safety diagnostics through extension URL installs and suffixless workflow archive sniffing.

Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI auto review requested due to automatic review settings October 29, 2026 18:47
@mnriem

mnriem commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed commit 66ab41a: old GNU sparse and GNU sparse 1.0 archives are rejected before their separately stored maps are parsed, while bounded PAX 0.0/0.1 sparse maps remain supported. Extension URL installs and suffixless workflow downloads now preserve archive safety diagnostics instead of substituting authentication advice or falling through to YAML parsing. All 1,892 targeted archive and installer tests passed. AI disclosure: posted on behalf of @mnriem by GitHub Copilot using GPT-6.1 Sol in autonomous, user-directed mode; reasoning effort was not exposed. The code, tests, documentation, and this summary were AI-generated.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Archive validation still permits quadratic memory growth from global PAX headers, unhandled recursion errors from long metadata chains, and metadata-limit bypasses via negative sizes.

1 open issue
3 previously missed issues

In code that hasn't changed since last review

High severity Bound cumulative global PAX metadata to prevent quadratic memory growth

src/​specify_cli/​_download_security.py:124

Interleaving global PAX headers with tiny files accumulates archive.pax_headers, which tarfile copies into every retained member. This allows quadratic memory growth during validation while staying under the 54 MiB stream, 64 KiB payload, and 512-entry limits. Bound cumulative global PAX metadata per archive before parsing, raise _TarLimitError when that budget is exceeded, and add an interleaved-header regression case.

Medium severity Reject negative base-256 metadata sizes before bounded reads

src/​specify_cli/​_download_security.py:120

On supported Python patch releases where tarfile._block() forwards negative sizes, a base-256 metadata size of -512 passes this guard and becomes a negative read length. _BoundedTarGzipFile.read() treats that as an unsized read, allowing more than 64 KiB to reach the metadata parser. Reject sizes outside 0..MAX_TAR_METADATA_BYTES and add a base-256 negative-size regression case.

Medium severity Bound PAX header chain depth to prevent raw RecursionError

src/​specify_cli/​_download_security.py:125

A chain of 400 tiny PAX headers causes this recursive call to raise a raw RecursionError, even though the archive is far below both new byte limits. That bypasses the caller's error_type and normal CLI error handling. Bound metadata chain depth on the archive, raise _TarLimitError at that bound, and add a long-chain regression case.

1 resolved since last review

Review effort: Max (auto)

Track global PAX bytes and metadata chain depth per archive, reject negative metadata sizes before parsing, and cover propagation of these safety failures through extension and workflow callers.

Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI auto review requested due to automatic review settings October 30, 2026 12:13
@mnriem

mnriem commented Oct 30, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed commit 36566e4: global PAX payloads now have an 8 KiB cumulative per-archive budget, metadata chains are limited to 32 headers, and negative base-256 metadata sizes are rejected before parsing. Regression coverage includes interleaved global headers, exact budget and depth boundaries, independent archive state, and legacy negative-read behavior. The extension/workflow wrapper fix from 66ab41a remains in place; caller-level tests now also verify preservation of all three new safety diagnostics. All 1,937 targeted archive and installer tests passed. AI disclosure: posted on behalf of @mnriem by GitHub Copilot using GPT-6.1 Sol in autonomous, user-directed mode; reasoning effort was not exposed. The code, tests, documentation, and this summary were AI-generated.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Per-member PAX metadata lacks a cumulative budget, allowing an accepted 73 KiB archive within all new limits to drive peak memory usage to about 666 MiB.

1 open issue

Review effort: Max (auto)

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

triage-must-have Verdict: high-value, important work for Spec Kit — do first

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants