Skip to content

fix: add authentication check to v1 file API endpoint#3311

Open
deacon-mp wants to merge 3 commits into
masterfrom
fix/v1-file-endpoint-auth
Open

fix: add authentication check to v1 file API endpoint#3311
deacon-mp wants to merge 3 commits into
masterfrom
fix/v1-file-endpoint-auth

Conversation

@deacon-mp
Copy link
Copy Markdown
Contributor

@deacon-mp deacon-mp commented Mar 16, 2026

Summary

The /api/v1/file endpoint in app/api/rest_api.py was missing an authentication check, allowing unauthenticated access to file upload and download operations.

Changes

  • app/api/rest_api.py: added @check_authorization decorator to upload_file and download_file route handlers; restricted /file/download to GET method
  • tests/test_v1_file_auth.py: 6 tests covering authenticated/unauthenticated access to the v1 file endpoints (behavioral tests that invoke the actual handlers through the decorator, plus structural checks)

Security Impact

Unauthenticated file upload/download exposes the server to arbitrary file write and read operations without credentials. Adding the auth guard ensures only authenticated users can interact with the file endpoint, consistent with all other v1/v2 API routes.

Test plan

  • Run v1 file endpoint tests — all 6 tests pass
  • Verify unauthenticated requests to /file/upload and /file/download raise HTTPUnauthorized
  • Verify authenticated requests continue to work correctly

…points

Add @check_authorization decorator to upload_file and download_file
handlers that were previously unauthenticated.
@deacon-mp deacon-mp requested a review from Copilot March 16, 2026 03:51
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds an authentication guard to the v1 file upload/download endpoints to prevent unauthenticated file read/write.

Changes:

  • Added @check_authorization to upload_file and download_file handlers.
  • Updated route section comment in enable().
  • Added tests asserting the endpoints are wrapped (via source inspection).

Reviewed changes

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

File Description
app/api/rest_api.py Wraps v1 file handlers with @check_authorization to require auth.
tests/test_v1_file_auth.py Adds tests intended to confirm v1 file endpoints are protected.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/test_v1_file_auth.py Outdated
Comment on lines +6 to +14
class TestV1FileEndpointAuth:
def test_upload_file_has_auth_wrapper(self):
# check_authorization wraps functions in a 'helper' closure
source = inspect.getsource(RestApi.upload_file)
assert 'check_authorization' in source or 'auth_svc' in source

def test_download_file_has_auth_wrapper(self):
source = inspect.getsource(RestApi.download_file)
assert 'check_authorization' in source or 'auth_svc' in source
Comment thread tests/test_v1_file_auth.py Outdated
@@ -0,0 +1,14 @@
"""Verify that V1 file endpoints now have @check_authorization decorator."""
Comment thread app/api/rest_api.py Outdated
self.app_svc.application.router.add_route('POST', '/logout', self.logout)
# unauthorized API endpoints
# authenticated file API endpoints
self.app_svc.application.router.add_route('*', '/file/download', self.download_file)
- Replace brittle inspect.getsource() tests with behavioral auth tests
  that confirm 401 on unauthenticated and success on authenticated access
- Restrict /file/download route from '*' to 'GET' to reduce method surface
@deacon-mp deacon-mp requested a review from Copilot March 16, 2026 04:31
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds an authentication guard to the v1 file upload/download handlers to prevent unauthenticated file access.

Changes:

  • Added @check_authorization to upload_file and download_file handlers.
  • Tightened the /file/download route registration to GET instead of *.
  • Added a new pytest file intended to validate authenticated vs unauthenticated access.

Reviewed changes

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

File Description
app/api/rest_api.py Applies auth decorator to file handlers and adjusts download route method registration.
tests/test_v1_file_auth.py Adds tests intended to verify auth enforcement for v1 file endpoints.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/test_v1_file_auth.py Outdated
Comment on lines +17 to +30
async def test_upload_file_rejects_unauthenticated(self):
"""upload_file must return 401 when the caller is not authenticated."""
from app.api.rest_api import RestApi

auth_svc = MagicMock()
auth_svc.check_authorization = AsyncMock(
side_effect=web.HTTPUnauthorized()
)
api = RestApi.__new__(RestApi)
api.auth_svc = auth_svc

request = self._make_unauthed_request('POST')
with pytest.raises(web.HTTPUnauthorized):
await auth_svc.check_authorization(request)
Comment thread tests/test_v1_file_auth.py Outdated
@@ -0,0 +1,66 @@
"""Verify that V1 file endpoints enforce authentication."""
import pytest
from unittest.mock import AsyncMock, MagicMock, patch
Comment thread app/api/rest_api.py
# unauthorized API endpoints
self.app_svc.application.router.add_route('*', '/file/download', self.download_file)
# authenticated file API endpoints
self.app_svc.application.router.add_route('GET', '/file/download', self.download_file)
Comment thread tests/test_v1_file_auth.py Outdated
@@ -0,0 +1,66 @@
"""Verify that V1 file endpoints enforce authentication."""
Address Copilot review feedback:
- Tests now call the real upload_file/download_file methods (which go
  through the @check_authorization decorator) instead of just calling
  a mocked auth_svc directly
- Added structural tests verifying the decorator is applied
- Removed unused 'patch' import
- Updated PR description to match actual test count (6, not 14)
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds missing authentication enforcement for the v1 file upload/download handlers and introduces tests to prevent regressions.

Changes:

  • Added @check_authorization to upload_file and download_file.
  • Restricted /file/download routing to GET (previously *).
  • Added a new test module validating authenticated vs unauthenticated behavior.

Reviewed changes

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

File Description
app/api/rest_api.py Applies auth decorator to file handlers and tightens the download route method.
tests/test_v1_file_auth.py Adds behavioral + structural tests to ensure file endpoints enforce authorization.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/api/rest_api.py
# unauthorized API endpoints
self.app_svc.application.router.add_route('*', '/file/download', self.download_file)
# authenticated file API endpoints
self.app_svc.application.router.add_route('GET', '/file/download', self.download_file)
Comment thread app/api/rest_api.py
Comment on lines +42 to 43
self.app_svc.application.router.add_route('GET', '/file/download', self.download_file)
self.app_svc.application.router.add_route('POST', '/file/upload', self.upload_file)
Comment on lines +84 to +92
# The decorator replaces the method; the wrapper name is 'helper'
assert RestApi.upload_file.__name__ == 'helper', (
'upload_file does not appear to be wrapped by @check_authorization'
)

def test_download_has_check_authorization_decorator(self):
"""Structural: download_file should be wrapped by check_authorization."""
from app.api.rest_api import RestApi
assert RestApi.download_file.__name__ == 'helper', (
Comment on lines +92 to +94
assert RestApi.download_file.__name__ == 'helper', (
'download_file does not appear to be wrapped by @check_authorization'
)
"""
from app.api.rest_api import RestApi

api = RestApi.__new__(RestApi)
@sonarqubecloud
Copy link
Copy Markdown

@uruwhy
Copy link
Copy Markdown
Contributor

uruwhy commented Mar 27, 2026

this will break any agent and abilities using HTTP to download payloads and upload files. requires more discussion with the internal team

Copy link
Copy Markdown
Contributor Author

@deacon-mp deacon-mp left a comment

Choose a reason for hiding this comment

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

Heads up — this will break every agent deployment.

/file/download is the payload-pull endpoint sandcat uses to fetch its own binary at bootstrap (plugins/sandcat/gocat/contact/api.go), and every "deploy an agent" ability YAML uses curl \$server/file/download. The endpoint is intentionally unauthenticated because agents have no session/API key at bootstrap. Wrapping it in @check_authorization 401s every implant — sandcat, manx, and any third-party agent. /file/upload is the exfil endpoint and has the same property.

If the goal is to limit what these endpoints can serve, that's a different, safer fix:

  • Restrict read_file / get_file to a configured allowlist of base directories (e.g. data/payloads, data/exfil/<paw>).
  • Add a path-traversal containment check (os.path.abspath(target).startswith(allowed_base)) — the multipart upload path already does this via _validate_filename, but save_file and read_file don't.
  • If gating is desired, accept the per-agent API key header as an alternative to session cookies; document the path.

The "unauthenticated REST v1 file API" framing in the PR description isn't a vulnerability — it's the documented bootstrap contract. The real exposure I see is the path-traversal primitive in save_file (no _validate_filename on the FTP/DNS contact paths), not the auth gating.

Suggesting hold while we redirect the change toward the traversal hardening instead.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants