Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions apps/backend/app/github/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,16 @@ def _enforce_clone_size(self, destination: Path) -> None:

def _directory_size(self, path: Path) -> int:
total = 0
for child in path.rglob("*"):
try:
if child.is_file() and not child.is_symlink():
total += child.stat().st_size
except OSError:
continue
for root, dirs, files in os.walk(path, followlinks=False):
for name in files:
child = Path(root, name)
try:
if not child.is_symlink():
total += child.stat().st_size
except OSError:
continue
# Do not descend into symlinked directories: prevents a crafted
# symlink (e.g. to / or a parent dir) from causing disclosure or a
# size-measurement loop during clone budget enforcement.
dirs[:] = [d for d in dirs if not Path(root, d).is_symlink()]
return total
5 changes: 4 additions & 1 deletion apps/backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@
from typing import Any, Literal
from uuid import uuid4

from fastapi import FastAPI, Request, status
from fastapi import Depends, FastAPI, Request, status
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse, PlainTextResponse
from sqlalchemy import text

from sqlalchemy import inspect as sa_inspect

from app.api.router import api_router
from app.api.deps import get_current_user
from app.api.openapi import (
documented_responses,
remove_suppressed_automatic_validation_errors,
Expand Down Expand Up @@ -296,11 +297,13 @@ def readiness() -> dict[str, object] | JSONResponse:
@app.get(
"/metrics",
tags=["system"],
dependencies=[Depends(get_current_user)],
response_class=PlainTextResponse,
responses=documented_responses(
status.HTTP_200_OK,
"Prometheus-compatible runtime metrics.",
"partha_http_requests_total 1\\n",
status.HTTP_401_UNAUTHORIZED,
status.HTTP_500_INTERNAL_SERVER_ERROR,
media_type="text/plain",
schema={"type": "string"},
Expand Down
3 changes: 1 addition & 2 deletions apps/backend/tests/test_openapi_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
("POST", "/auth/logout"),
("GET", "/health"),
("GET", "/ready"),
("GET", "/metrics"),
}

# Refresh is unauthenticated in the HTTPBearer sense, but requires a valid
Expand Down Expand Up @@ -63,7 +62,7 @@
("POST", "/export"): {200, 401, 404, 409, 422, 429, 500},
("GET", "/health"): {200, 500},
("GET", "/ready"): {200, 503, 500},
("GET", "/metrics"): {200, 500},
("GET", "/metrics"): {200, 401, 500},
}

BODY_MEDIA_TYPES = {
Expand Down
6 changes: 3 additions & 3 deletions apps/backend/tests/test_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ def test_readiness_endpoint(client):
}


def test_metrics_endpoint_exposes_request_counters(client):
client.get("/health")
def test_metrics_endpoint_exposes_request_counters(auth_client):
auth_client.get("/health")

response = client.get("/metrics")
response = auth_client.get("/metrics")

assert response.status_code == 200
assert "text/plain" in response.headers["content-type"]
Expand Down
Loading