Skip to content

fix(security): block RFC6598 shared address space in S3 endpoint (Alibaba/Tencent metadata SSRF)#288

Merged
gary-Shen merged 265 commits into
mainfrom
fix/security-vulnerabilities
Jul 13, 2026
Merged

fix(security): block RFC6598 shared address space in S3 endpoint (Alibaba/Tencent metadata SSRF)#288
gary-Shen merged 265 commits into
mainfrom
fix/security-vulnerabilities

Conversation

@gary-Shen

Copy link
Copy Markdown
Member

No description provided.

semantic-release-bot and others added 30 commits October 28, 2024 03:48
## [1.0.7-alpha.8](v1.0.7-alpha.7...v1.0.7-alpha.8) (2024-10-28)

### Bug Fixes

* test notification action ([8fed078](8fed078))
## [1.0.7-alpha.9](v1.0.7-alpha.8...v1.0.7-alpha.9) (2024-10-28)

### Bug Fixes

* test notification action ([851818e](851818e))
## [1.0.7-alpha.10](v1.0.7-alpha.9...v1.0.7-alpha.10) (2024-10-28)

### Bug Fixes

* test notification action ([bc63ee5](bc63ee5))
## [1.0.7-alpha.11](v1.0.7-alpha.10...v1.0.7-alpha.11) (2024-10-28)

### Bug Fixes

* test notification action ([83f43eb](83f43eb))
## [1.0.7-alpha.12](v1.0.7-alpha.11...v1.0.7-alpha.12) (2024-10-28)

### Bug Fixes

* update notification action ([63286cb](63286cb))
## [1.0.9-alpha.1](v1.0.8...v1.0.9-alpha.1) (2024-10-28)

### Bug Fixes

* test notification action ([83f43eb](83f43eb))
* test notification action ([bc63ee5](bc63ee5))
* test notification action ([851818e](851818e))
* test notification action ([8fed078](8fed078))
* update notification action ([63286cb](63286cb))
* update notification action ([046f47a](046f47a))
* update notification action ([e9152c9](e9152c9))
* update notification action ([04e0388](04e0388))
* update notification action ([b6abe17](b6abe17))
* Upgrade frontend to v5.3.0-alpha.4. See the details in [release notes](https://github.com/opendatalab/labelU-Kit/releases/tag/v5.3.0-alpha.4) ([01fc3b5](01fc3b5))
* Upgrade frontend to v5.3.0-alpha.5. See the details in [release notes](https://github.com/opendatalab/labelU-Kit/releases/tag/v5.3.0-alpha.5) ([f97601d](f97601d))
## [1.0.9-alpha.2](v1.0.9-alpha.1...v1.0.9-alpha.2) (2024-10-28)

### Bug Fixes

* Upgrade frontend to v5.3.0-alpha.6. See the details in [release notes](https://github.com/opendatalab/labelU-Kit/releases/tag/v5.3.0-alpha.6) ([9f5e698](9f5e698))
## [1.0.9-alpha.3](v1.0.9-alpha.2...v1.0.9-alpha.3) (2024-10-28)

### Bug Fixes

* Upgrade frontend to v5.3.0-alpha.7. See the details in [release notes](https://github.com/opendatalab/labelU-Kit/releases/tag/v5.3.0-alpha.7) ([7cb455e](7cb455e))
## [1.0.9-alpha.4](v1.0.9-alpha.3...v1.0.9-alpha.4) (2024-10-29)

### Bug Fixes

* Upgrade frontend to v5.5.0-alpha.1. See the details in [release notes](https://github.com/opendatalab/labelU-Kit/releases/tag/v5.5.0-alpha.1) ([7b04670](7b04670))
* Upgrade frontend to v5.5.0-alpha.2. See the details in [release notes](https://github.com/opendatalab/labelU-Kit/releases/tag/v5.5.0-alpha.2) ([491cf33](491cf33))
## [1.0.9-alpha.5](v1.0.9-alpha.4...v1.0.9-alpha.5) (2024-10-30)

### Bug Fixes

* Upgrade frontend to v5.5.0-alpha.3. See the details in [release notes](https://github.com/opendatalab/labelU-Kit/releases/tag/v5.5.0-alpha.3) ([dc022fd](dc022fd))
add pascal voc, yolo, tfrecord exporting format files
# [1.1.0-alpha.1](v1.0.9-alpha.5...v1.1.0-alpha.1) (2024-11-05)

### Bug Fixes

* Upgrade frontend to v5.5.0-alpha.4. See the details in [release notes](https://github.com/opendatalab/labelU-Kit/releases/tag/v5.5.0-alpha.4) ([7622524](7622524))

### Features

* supports exporting "csv" format files ([d35a93a](d35a93a))
* supports exporting "labelme" format files ([8d4ea8e](8d4ea8e))
* supports exporting "pascal voc" format files ([4344f99](4344f99))
* supports exporting "tfrecord" format files ([f728fd6](f728fd6))
* supports exporting "xml" format files ([b165cce](b165cce))
* supports exporting "yolo" format files ([b3d07b4](b3d07b4))
fix: fix category name in pascal_voc and tfrecord format files
# [1.1.0-alpha.2](v1.1.0-alpha.1...v1.1.0-alpha.2) (2024-11-05)

### Bug Fixes

* fix category name in pascal_voc and tfrecord format files ([b089074](b089074))
fix: supports rotated image in yolo and tfrecord files
# [1.1.0-alpha.3](v1.1.0-alpha.2...v1.1.0-alpha.3) (2024-11-06)

### Bug Fixes

* supports rotated image in yolo and tfrecord files ([4d5afdf](4d5afdf))
* Upgrade frontend to v5.5.0-alpha.5. See the details in [release notes](https://github.com/opendatalab/labelU-Kit/releases/tag/v5.5.0-alpha.5) ([27f27be](27f27be))
fix: update attributes in xml and pascal_voc
gary-Shen and others added 28 commits September 30, 2025 22:54
# [1.3.0-alpha.18](v1.3.0-alpha.17...v1.3.0-alpha.18) (2025-10-01)

### Bug Fixes

* cli options ([2c42b7e](2c42b7e))
…nticated arbitrary file read)

_resolve only stripped leading slashes from the key; it neither handled
".." nor verified the resolved path stayed within MEDIA_ROOT. Because the
/api/v1/tasks/attachment/{path} and /partial/{path} download endpoints are
unauthenticated, an attacker could traverse with "../" to read any
process-readable file (e.g. labelu.sqlite, /etc/passwd).

_resolve now resolve()s both MEDIA_ROOT and the target and enforces
containment, raising 404 on escape. This covers read/write/delete/exists.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…itrary file write)

Attachment upload only sanitized the last path segment; the directory part
"/".join(path_filename[:-1]) was kept verbatim and could contain "..",
letting save_file -> _resolve -> shutil.move create files anywhere the
process can write (potential RCE).

Each directory segment is now sanitized the same way and "", "." and ".."
segments are dropped. Legitimate subdirectories still work while traversal
is neutralized; uploads always land under upload/<task_id>/.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… (CWE-89)

crud_sample.list_by interpolated the sort column/direction into an ORDER BY
clause via a raw text() f-string, and the route pattern used an unanchored
re.search that could be bypassed with "valid_col:dir,<injection>", letting
an authenticated user blind-extract arbitrary data.

Primary fix: the crud layer now maps allowed column names to real ORM
columns, accepts only asc/desc, and skips anything else, removing the
text() interpolation entirely.
Defense in depth: the route pattern is anchored to ^(col:dir)(,col:dir)*$,
rejecting injection at validation and closing the substring bypass while
still allowing comma-separated multi-column sorts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CreateDataSourceCommand/UpdateDataSourceCommand only bounded the length of
endpoint, and _build_s3_client passed it straight to boto3 as endpoint_url,
so an authenticated user could point it at http://169.254.169.254 and other
internal/metadata addresses, triggering outbound requests via
list_objects/import_from_s3 (cloud IAM credential theft, internal scanning).

The endpoint is now validated: only http(s) is allowed, and the host is
resolved and rejected if it maps to a loopback/private/link-local/reserved/
multicast/unspecified address.

Note: connection-time DNS-rebinding re-check (TOCTOU) is left as a follow-up
defense-in-depth item.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sample create/import/list/get/patch/delete and export create/status/download
did not verify the caller could access the corresponding task, so any
authenticated user could enumerate auto-increment IDs to read, modify or
delete other users' annotation data, or download their export results.

Adds access.assert_task_access(task, current_user) (owner or collaborator)
and enforces it across the sample service and export endpoints; get/patch
additionally verify sample.task_id matches the URL task to prevent
referencing another user's sample through a task you own.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
task.update neither received current_user nor checked ownership
(inconsistent with get/delete/collaborator management in the same file), so
any authenticated user could PATCH and overwrite another user's task
config/name/description/media_type.

update now receives current_user and calls assert_task_access (owner or
collaborator).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Data source get/update/delete/list_objects and S3 import did not check
ownership, so any authenticated user could read others' S3 configuration,
tamper with/delete data sources, or use the victim's decrypted S3
credentials to list their private bucket or import objects.

Adds access.assert_owner(resource, current_user) (owner-only; data sources
have no collaborator concept) and enforces it on the four data source
endpoints and in sample.import_from_s3 before the credentials are used;
list_objects checks access before building the S3 client, preventing
credential abuse.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pre-annotation create/list/get/delete/delete_file already received
current_user but never used it for authorization, so any authenticated user
could read/inject/delete another task's pre-annotations (delete_file also
physically removed the underlying files).

All endpoints now call assert_task_access; get additionally verifies
pre_annotation.task_id matches the URL task, and delete checks access for
each pre-annotation's task.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The test suite persisted state across runs in two places: test.db was
pinned to the cwd (so sqlite_sequence accumulated and task_ids kept
growing) and MEDIA_ROOT pointed at the real appdirs data directory
(~/Library/Application Support/labelu/media). Fresh task_ids collided
with leftover upload/<task_id>/... files, intermittently failing uploads
with a 400 "file already exists" (e.g. test_upload_file_with_hash_symbol).

- Redirect both the sqlite test DB and MEDIA_ROOT into a per-run temp
  directory created at import time (the engine is built at module import,
  before any tmp_path/monkeypatch fixture could run); remove it at session
  end via the _hermetic_storage fixture.
- Force STORAGE_BACKEND=local and clear the get_storage_backend lru_cache
  so it resolves against the new MEDIA_ROOT.
- Seed the test user at session scope so it exists before the
  module-scoped login fixture (previously it survived only because test.db
  persisted across runs).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…bilities

Make the test suite hermetic by isolating the sqlite test DB and media
storage into a per-run temp directory, so tests no longer pollute the real
appdirs data directory or the cwd and can be run repeatedly.
…loyments

The SSRF guard rejected any endpoint resolving to a non-public address,
which broke the common on-prem deployment backed by an internal MinIO/
S3-compatible store on a LAN or the same host.

Adds ALLOW_PRIVATE_S3_ENDPOINT (default true): private (RFC1918/ULA) and
loopback endpoints are permitted by default so internal deployments work
out of the box, and can be locked down to public-only with a strict mode
(set false). The cloud metadata endpoint (link-local 169.254.0.0/16,
fe80::/10), multicast, reserved and unspecified addresses are ALWAYS
rejected regardless of the flag, so the highest-impact SSRF (IAM credential
theft) stays closed even in the permissive default.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…baba/Tencent metadata SSRF)

The SSRF guard's always-block set only covered link-local (169.254.0.0/16),
so the Alibaba/Tencent cloud metadata service at 100.100.100.200 was
reachable: 100.64.0.0/10 (RFC6598 CGNAT) is not flagged
private/link-local/reserved by the stdlib, so it passed in BOTH the default
and strict modes, allowing an authenticated user to steal instance RAM
credentials via list_objects/import_from_s3.

100.64.0.0/10 is now always rejected regardless of ALLOW_PRIVATE_S3_ENDPOINT.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
create_batch_job loaded the task and checked only existence and media type,
with no ownership check — unlike the single-sample auto_label.create which
already verifies owner/collaborator. Any authenticated user could start a
batch auto-label job on any task_id, overwriting another user's AI
pre-annotations (when AI_AUTO_LABEL_ENABLED is on).

Adds assert_task_access(task, current_user) after the task lookup.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Regression guard for the IDOR access checks: adds a second user as a task
collaborator and asserts they can list, read and patch (annotate) samples in
the shared task, confirming assert_task_access grants owner-or-collaborator
access and does not lock collaborators out.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ilities

# Conflicts:
#	labelu/internal/application/command/datasource.py
#	labelu/tests/internal/adapter/routers/test_sample.py
#	labelu/tests/internal/application/command/test_datasource.py
#	labelu/version.py
@gary-Shen gary-Shen merged commit e6a256e into main Jul 13, 2026
1 check passed
github-actions Bot pushed a commit that referenced this pull request Jul 13, 2026
## [1.5.4](v1.5.3...v1.5.4) (2026-07-13)

### Bug Fixes

* **security:** block RFC6598 shared address space in S3 endpoint (Alibaba/Tencent metadata SSRF) ([#288](#288)) ([e6a256e](e6a256e))
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.

2 participants