Skip to content

Python: keep attachments close#7038

Merged
moonbox3 merged 2 commits into
microsoft:mainfrom
moonbox3:codex/keep-attachments-close
Jul 10, 2026
Merged

Python: keep attachments close#7038
moonbox3 merged 2 commits into
microsoft:mainfrom
moonbox3:codex/keep-attachments-close

Conversation

@moonbox3

Copy link
Copy Markdown
Contributor

Motivation & Context

A local store should honor its own boundary.

Description & Review Guide

  • What are the major changes? Tighten attachment path resolution and cover its edge cases.
  • What is the impact of these changes? Valid attachment IDs behave as before.
  • What do you want reviewers to focus on? The storage seam.

Related Issue

N/A

Contribution Checklist

  • The code builds clean without any errors or warnings
  • All unit tests pass, and I have added new tests where possible
  • The PR follows the Contribution Guidelines
  • This PR is linked to an issue and there is no other open PR for this issue (see Related Issue above).
  • This is not a breaking change. If it is a breaking change, add the breaking change label (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.

Copilot AI review requested due to automatic review settings July 10, 2026 06:13
@giles17 giles17 added the python Usage: [Issues, PRs], Target: Python label Jul 10, 2026

@github-actions github-actions Bot 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.

Automated Code Review

Reviewers: 5 | Confidence: 94%

✓ Correctness

Clean security hardening of the attachment store's path resolution. The implementation correctly prevents path traversal (../, absolute paths) and nested directory access by resolving paths and checking both containment and direct-child constraints. The tests cover all relevant edge cases. No correctness issues found.

✓ Security Reliability

This PR correctly implements path traversal prevention for the local attachment store. The fix resolves paths canonically and validates that the resulting path is a direct child of the uploads directory, blocking directory traversal (../), nested paths, absolute paths, empty strings, and symlink-based escapes. The implementation is sound and well-tested.

✓ Test Coverage

The test coverage for this security-focused change is solid. The PR adds both a positive test (valid attachment ID resolves correctly) and a well-chosen parametrized negative test covering path traversal (../), nested paths, absolute paths, empty strings, and current-directory references. Assertions are meaningful—checking exact path equality and specific ValueError messages. The core path-resolution boundary is adequately tested.

✓ Failure Modes

This PR correctly hardens the file-based attachment store against path traversal by resolving paths and validating that attachment IDs resolve to direct children of the uploads directory. The two-part check (is_relative_to AND parent equality) covers traversal via '..', subdirectory injection, absolute paths, empty strings, and '.' correctly. All existing calers (delete_attachment, read_attachment_bytes) benefit from the check since they call get_file_path, and the raised ValueError is appropriate behavior for invalid input. No silent failure modes are introduced.

✗ Design Approach

The boundary check in get_file_path is a good hardening step, but the new ValueError path is not carried through the request boundary: tampered attachment IDs now surface as 500s from the sample API instead of a client error. I found one design issue to address before merging.

Flagged Issues

  • get_file_path now raises ValueError for invalid IDs, but both /upload/{attachment_id} and /preview/{attachment_id} catch that as a generic exception and return HTTP 500 (app.py:595,612-614,627,643-645). Malicious or malformed requests like /preview/. are correctly rejected by the store but still reported to clients as internal server failures instead of 4xx client errors.

Automated review by moonbox3's agents

@github-actions

Copy link
Copy Markdown
Contributor

Flagged issue

get_file_path now raises ValueError for invalid IDs, but both /upload/{attachment_id} and /preview/{attachment_id} catch that as a generic exception and return HTTP 500 (app.py:595,612-614,627,643-645). Malicious or malformed requests like /preview/. are correctly rejected by the store but still reported to clients as internal server failures instead of 4xx client errors.


Source: automated DevFlow PR review

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.

Pull request overview

This PR hardens the ChatKit integration sample’s local attachment store by constraining attachment path resolution to stay within the configured uploads directory, and adds tests to cover path traversal and related edge cases.

Changes:

  • Resolve uploads_dir to an absolute path at initialization to ensure consistent boundary checks.
  • Validate attachment_id-derived paths in get_file_path and raise on IDs that escape the uploads directory.
  • Add end-to-end sample tests exercising valid IDs and rejecting invalid/path-like IDs.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
python/samples/05-end-to-end/chatkit-integration/attachment_store.py Tightens attachment filesystem path resolution to enforce uploads directory boundaries.
python/tests/samples/end_to_end/test_chatkit_attachment_store.py Adds coverage for valid IDs and rejects invalid/path-like attachment IDs.

Comment thread python/tests/samples/end_to_end/test_chatkit_attachment_store.py
@moonbox3

Copy link
Copy Markdown
Contributor Author

Addressed in ccf5270. Invalid attachment IDs now return HTTP 400 from both upload and preview routes, with focused regression coverage.

@moonbox3 moonbox3 marked this pull request as ready for review July 10, 2026 06:35

@github-actions github-actions Bot 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.

Automated Code Review

Reviewers: 5 | Confidence: 90%

✓ Correctness

The PR correctly implements path traversal protection for the attachment store. The get_file_path method uses defense-in-depth: a syntactic pre-check (rejecting empty, '.', '..', '/', '') followed by a resolved-path verification ensuring the result is a direct child of uploads_dir. The HTTP endpoints properly catch ValueError and return 400. Internal calers (delete_attachment, read_attachment_bytes) allow the exception to propagate, which is appropriate since those receive IDs from the application's own data store. No correctness issues found.

✓ Security Reliability

The PR implements a well-structured path traversal defense for the attachment store. It applies syntactic pre-validation (rejecting separators, empty strings, dot-segments) combined with post-resolution verification (confirming the resolved path is a direct child of uploads_dir). The HTTP endpoints correctly catch ValueError and return 400 responses. The uploads_dir is resolved at init time, preventing TOCTOU issues. No security or reliability concerns were found.

✓ Test Coverage

Test coverage for this PR is solid. The parametrized unit test covers 9 invalid ID patterns, the happy path is verified, and the integration test confirms both HTTP routes return 400 for invalid IDs. The only notable gap is that delete_attachment and read_attachment_bytes also call get_file_path (and thus inherit the new ValueError) but have no test documenting this behavior, though this is low severity since those are internal methods and the HTTP boundary is well covered.

✓ Failure Modes

The PR cleanly hardens attachment path resolution with both a pre-validation check (rejecting separators, empty, dot-segments) and a post-resolve check (parent must equal uploads_dir). ValueError is properly caught at both HTTP-facing boundaries and translated to 400 responses. Internal callers (delete_attachment, read_attachment_bytes) use system-generated IDs so the unhandled ValueError there is acceptable. No new silent failure modes, lost errors, or partial-write issues are introduced.

✗ Design Approach

The new filename validation is a good hardening step, but it is only translated at the upload and preview HTTP boundaries. One remaining attachment-read path now turns the same malformed IDs into an unhandled server error, so the storage boundary is still inconsistent.

Flagged Issues

  • get_file_path() now raises ValueError for malformed IDs, but WeatherChatKitServer._fetch_attachment_data() still forwards attachment_id straight into read_attachment_bytes() and the main request handler re-raises any exception from processing (app.py:249-258 and :565-578). A thread item referencing attachment_id='.' will therefore fail the whole ChatKit request with a 500 instead of being rejected at the boundary the way /upload/{attachment_id} and /preview/{attachment_id} now are.

Automated review by moonbox3's agents

@github-actions

Copy link
Copy Markdown
Contributor

Flagged issue

get_file_path() now raises ValueError for malformed IDs, but WeatherChatKitServer._fetch_attachment_data() still forwards attachment_id straight into read_attachment_bytes() and the main request handler re-raises any exception from processing (app.py:249-258 and :565-578). A thread item referencing attachment_id='.' will therefore fail the whole ChatKit request with a 500 instead of being rejected at the boundary the way /upload/{attachment_id} and /preview/{attachment_id} now are.


Source: automated DevFlow PR review

@moonbox3 moonbox3 enabled auto-merge July 10, 2026 06:45
@moonbox3

Copy link
Copy Markdown
Contributor Author

Flagged issue

get_file_path() now raises ValueError for malformed IDs, but WeatherChatKitServer._fetch_attachment_data() still forwards attachment_id straight into read_attachment_bytes() and the main request handler re-raises any exception from processing (app.py:249-258 and :565-578). A thread item referencing attachment_id='.' will therefore fail the whole ChatKit request with a 500 instead of being rejected at the boundary the way /upload/{attachment_id} and /preview/{attachment_id} now are.

Source: automated DevFlow PR review

False positive. No action.

@moonbox3 moonbox3 added this pull request to the merge queue Jul 10, 2026
Merged via the queue into microsoft:main with commit 9ac548a Jul 10, 2026
37 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants