fix: P2+ security bugs — zip-slip, unbounded request body, query length limit#8
fix: P2+ security bugs — zip-slip, unbounded request body, query length limit#8beautifulrem with Copilot wants to merge 3 commits into
Conversation
…th limit Agent-Logs-Url: https://github.com/beautifulrem/ARIN/sessions/dfefde9a-03fd-435d-b276-c2b9195e9ad0 Co-authored-by: beautifulrem <98527099+beautifulrem@users.noreply.github.com>
…ity_fixes Agent-Logs-Url: https://github.com/beautifulrem/ARIN/sessions/dfefde9a-03fd-435d-b276-c2b9195e9ad0 Co-authored-by: beautifulrem <98527099+beautifulrem@users.noreply.github.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 075203c198
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if not target.is_relative_to(resolved_root): | ||
| raise ValueError( | ||
| f"Blocked unsafe zip member path (zip-slip): {member.filename!r}" | ||
| ) | ||
| archive.extract(member, extract_dir) |
There was a problem hiding this comment.
Validate all ZIP members before extracting any file
Because extraction happens inside the same loop that performs the path check, an archive containing both safe entries and a later traversal entry will extract the early files and only then raise ValueError. In this failure mode, _extract_zip_files leaves a partially populated extract_dir, and future sync runs skip that archive entirely due the extract_dir.exists() guard, which can silently preserve incomplete/corrupted data. Pre-validating all members (or extracting to a temp dir and committing atomically) avoids this partial-write state.
Useful? React with 👍 / 👎.
|
@copilot Please continue the full-repository audit from issue #7 and this PR. Do not treat the three current fixes as the end of the audit. Please look specifically for additional confirmed P2+ bugs or security/reliability risks, and either add focused commits to this PR or open a follow-up PR if a separate patch is cleaner. Please avoid speculative/style findings. For each confirmed issue, include the vulnerable path, trigger condition, impact, and test coverage. Follow-up areas to verify:
Please prioritize exploitable P2+ issues only. If a checked area is safe, briefly state why and what invariant/test proves it. |
|
@beautifulrem I've opened a new pull request, #9, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
Closing because this Copilot PR has been superseded by the direct master commit 03c4ff6, which includes these fixes and additional hardening for direct retrieval payloads, external downloads, archive extraction, and llm_response request limits. |
Full-repo audit surfaced three P2+ issues: path traversal via malicious zip archives, OOM/hang from unbounded HTTP request bodies, and denial-of-service via arbitrarily large query inputs to the NLU pipeline.
Changes
ZIP slip —
query_intelligence/external_data/sync.py_extract_zip_filescalledarchive.extractall()with no path validation; a crafted entry like../../etc/cron.d/evilwrites outside the target dir_safe_zip_extractall()that resolves every member path and raisesValueError(blocking extraction) if any entry escapes the target directory viaPath.is_relative_to()Unbounded request body —
scripts/llm_response.py_read_json_bodyread exactlyContent-Lengthbytes with no ceiling; an unauthenticated caller declaringContent-Length: 1073741824causes OOM_MAX_REQUEST_BODY_BYTES = 10 MBconstant; guard raisesValueError→ HTTP 400 before any read when the declared length exceeds the capQuery length DoS —
query_intelligence/contracts.pyAnalyzeRequest.queryandPipelineRequest.querywere unboundedstr; a megabyte-scale input fans out through CRF, TF-IDF, entity resolution, and PostgreSQL FTSMAX_QUERY_LENGTH = 2000; both fields now carryField(min_length=1, max_length=MAX_QUERY_LENGTH), producing HTTP 422 before the pipeline is enteredTests
Added
tests/test_security_fixes.py(12 tests): zip traversal blocked, normal extraction unaffected, realThreadingHTTPServerreturns 400 on oversizedContent-Length, Pydantic rejects over-limit and empty queries, API endpoints return 422/200 as expected.