fix: add security checks and tests for code review

Security:
- Add authorization checks to list_packages, update_package, delete_package endpoints
- Add MAX_TOTAL_ARTIFACTS limit (1000) to prevent memory exhaustion during dependency resolution
- Add TooManyArtifactsError exception for proper error handling

UI:
- Display reverse dependency errors in PackagePage
- Add warning display for failed dependency fetches in DependencyGraph

Tests:
- Add unit tests for metadata extraction (deb, wheel, tarball, jar)
- Add unit tests for rate limit configuration
- Add unit tests for PyPI registry client
This commit is contained in:
Mondo Diaz
2026-02-04 16:19:16 -06:00
parent 1389a03c69
commit 262aff6e97
8 changed files with 649 additions and 13 deletions

View File

@@ -147,6 +147,7 @@ from .dependencies import (
DependencyConflictError,
DependencyNotFoundError,
DependencyDepthExceededError,
TooManyArtifactsError,
)
from .config import get_settings, get_env_upstream_sources
from .checksum import (
@@ -2666,10 +2667,10 @@ def list_packages(
format: Optional[str] = Query(default=None, description="Filter by package format"),
platform: Optional[str] = Query(default=None, description="Filter by platform"),
db: Session = Depends(get_db),
current_user: Optional[User] = Depends(get_current_user_optional),
):
project = db.query(Project).filter(Project.name == project_name).first()
if not project:
raise HTTPException(status_code=404, detail="Project not found")
# Check read access (handles private project visibility)
project = check_project_access(db, project_name, current_user, "read")
# Validate sort field
valid_sort_fields = {
@@ -2950,13 +2951,13 @@ def update_package(
package_update: PackageUpdate,
request: Request,
db: Session = Depends(get_db),
current_user: Optional[User] = Depends(get_current_user_optional),
):
"""Update a package's metadata."""
user_id = get_user_id(request)
project = db.query(Project).filter(Project.name == project_name).first()
if not project:
raise HTTPException(status_code=404, detail="Project not found")
# Check write access to project
project = check_project_access(db, project_name, current_user, "write")
package = (
db.query(Package)
@@ -3033,6 +3034,7 @@ def delete_package(
package_name: str,
request: Request,
db: Session = Depends(get_db),
current_user: Optional[User] = Depends(get_current_user_optional),
):
"""
Delete a package and all its versions.
@@ -3043,9 +3045,8 @@ def delete_package(
"""
user_id = get_user_id(request)
project = db.query(Project).filter(Project.name == project_name).first()
if not project:
raise HTTPException(status_code=404, detail="Project not found")
# Check write access to project (deletion requires write permission)
project = check_project_access(db, project_name, current_user, "write")
package = (
db.query(Package)
@@ -7137,6 +7138,15 @@ async def resolve_artifact_dependencies(
"max_depth": e.max_depth,
}
)
except TooManyArtifactsError as e:
raise HTTPException(
status_code=400,
detail={
"error": "too_many_artifacts",
"message": str(e),
"max_artifacts": e.max_artifacts,
}
)
# --- Upstream Caching Routes ---