Release v1.7.15: security hardening, async auth, CSP tightening, model validation, SSRF guard, rate limiting improvements, frontend extraction, Docker compose security
Release / build-and-push (push) Successful in 3m12s
Release / build-and-push (push) Successful in 3m12s
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
"""Alert management endpoints."""
|
||||
|
||||
import re
|
||||
from typing import Literal
|
||||
|
||||
from auth import require_auth
|
||||
from bson import ObjectId
|
||||
from database import alerts_collection
|
||||
@@ -10,7 +13,7 @@ router = APIRouter(dependencies=[Depends(require_auth)])
|
||||
|
||||
|
||||
class AlertStatusUpdate(BaseModel):
|
||||
status: str # open | acknowledged | resolved | false_positive
|
||||
status: Literal["open", "acknowledged", "resolved", "false_positive"]
|
||||
|
||||
|
||||
class AlertListResponse(BaseModel):
|
||||
@@ -32,7 +35,7 @@ def list_alerts(
|
||||
if severity:
|
||||
query["severity"] = severity
|
||||
if rule_name:
|
||||
query["rule_name"] = {"$regex": rule_name, "$options": "i"}
|
||||
query["rule_name"] = {"$regex": re.escape(rule_name), "$options": "i"}
|
||||
|
||||
total = alerts_collection.count_documents(query)
|
||||
skip = (page - 1) * page_size
|
||||
|
||||
@@ -75,12 +75,14 @@ def run_fetch(hours: int = 168):
|
||||
|
||||
|
||||
@router.get("/fetch-audit-logs", response_model=FetchAuditLogsResponse)
|
||||
def fetch_logs(
|
||||
async def fetch_logs(
|
||||
hours: int = Query(default=168, ge=1, le=720),
|
||||
user: dict = Depends(require_auth),
|
||||
):
|
||||
import asyncio
|
||||
|
||||
try:
|
||||
result = run_fetch(hours=hours)
|
||||
result = await asyncio.to_thread(run_fetch, hours=hours)
|
||||
log_action(
|
||||
"fetch_audit_logs",
|
||||
"/api/fetch-audit-logs",
|
||||
|
||||
@@ -7,10 +7,18 @@ import structlog
|
||||
from auth import require_auth
|
||||
from database import saved_searches_collection
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
router = APIRouter(dependencies=[Depends(require_auth)])
|
||||
logger = structlog.get_logger("aoc.saved_searches")
|
||||
|
||||
MAX_SAVED_SEARCHES_PER_USER = 50
|
||||
|
||||
|
||||
class SavedSearchCreate(BaseModel):
|
||||
name: str = Field(..., min_length=1, max_length=200)
|
||||
filters: dict = Field(default_factory=dict)
|
||||
|
||||
|
||||
def _user_sub(user: dict) -> str:
|
||||
return user.get("sub", "anonymous")
|
||||
@@ -29,22 +37,25 @@ async def list_saved_searches(user: dict = Depends(require_auth)):
|
||||
|
||||
|
||||
@router.post("/saved-searches")
|
||||
async def create_saved_search(body: dict, user: dict = Depends(require_auth)):
|
||||
async def create_saved_search(body: SavedSearchCreate, user: dict = Depends(require_auth)):
|
||||
"""Save the current filter set."""
|
||||
name = (body.get("name") or "").strip()
|
||||
if not name:
|
||||
raise HTTPException(status_code=400, detail="Name is required")
|
||||
sub = _user_sub(user)
|
||||
existing = saved_searches_collection.count_documents({"created_by": sub})
|
||||
if existing >= MAX_SAVED_SEARCHES_PER_USER:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Maximum of {MAX_SAVED_SEARCHES_PER_USER} saved searches per user reached.",
|
||||
)
|
||||
|
||||
filters = body.get("filters") or {}
|
||||
doc = {
|
||||
"_id": str(uuid.uuid4()),
|
||||
"name": name,
|
||||
"filters": filters,
|
||||
"name": body.name,
|
||||
"filters": body.filters,
|
||||
"created_at": datetime.now(UTC).isoformat().replace("+00:00", "Z"),
|
||||
"created_by": _user_sub(user),
|
||||
"created_by": sub,
|
||||
}
|
||||
saved_searches_collection.insert_one(doc)
|
||||
logger.info("Saved search created", name=name, user=doc["created_by"])
|
||||
logger.info("Saved search created", name=body.name, user=sub)
|
||||
doc["id"] = doc.pop("_id")
|
||||
return doc
|
||||
|
||||
|
||||
@@ -52,7 +52,6 @@ async def graph_webhook(request: Request):
|
||||
"Received Graph notification",
|
||||
change_type=notification.get("changeType"),
|
||||
resource=notification.get("resource"),
|
||||
client_state=client_state,
|
||||
)
|
||||
|
||||
return {"status": "accepted"}
|
||||
|
||||
Reference in New Issue
Block a user