Security audit: fix IDOR, add rate limiting, HMAC auth, token hashing, XSS sanitization
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 3m22s

Critical: Add ownership checks to all order routes (IDOR fix).
High: Redis rate limiting on auth endpoints, HMAC-signed internal
service calls replacing header-presence-only checks, nh3 HTML
sanitization on ghost_sync and product import, internal auth on
market API endpoints, SHA-256 hashed OAuth grant/code tokens.
Medium: SECRET_KEY production guard, AP signature enforcement,
is_admin param removal, cart_sid validation, SSRF protection on
remote actor fetch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-26 13:30:27 +00:00
parent 404449fcab
commit c015f3f02f
27 changed files with 607 additions and 33 deletions

View File

@@ -11,12 +11,23 @@ from shared.browser.app.payments.sumup import create_checkout as sumup_create_ch
from shared.config import config
from shared.infrastructure.http_utils import vary as _vary, current_url_without_page as _current_url_without_page
from shared.infrastructure.cart_identity import current_cart_identity
from bp.cart.services import check_sumup_status
from shared.browser.app.utils.htmx import is_htmx_request
from .filters.qs import makeqs_factory, decode
def _owner_filter():
"""Return SQLAlchemy clause restricting orders to current user/session."""
ident = current_cart_identity()
if ident["user_id"]:
return Order.user_id == ident["user_id"]
if ident["session_id"]:
return Order.session_id == ident["session_id"]
return None
def register() -> Blueprint:
bp = Blueprint("order", __name__, url_prefix='/<int:order_id>')
@@ -32,12 +43,15 @@ def register() -> Blueprint:
"""
Show a single order + items.
"""
owner = _owner_filter()
if owner is None:
return await make_response("Order not found", 404)
result = await g.s.execute(
select(Order)
.options(
selectinload(Order.items).selectinload(OrderItem.product)
)
.where(Order.id == order_id)
.where(Order.id == order_id, owner)
)
order = result.scalar_one_or_none()
if not order:
@@ -58,7 +72,10 @@ def register() -> Blueprint:
If already paid, just go back to the order detail.
If not, (re)create a SumUp checkout and redirect.
"""
result = await g.s.execute(select(Order).where(Order.id == order_id))
owner = _owner_filter()
if owner is None:
return await make_response("Order not found", 404)
result = await g.s.execute(select(Order).where(Order.id == order_id, owner))
order = result.scalar_one_or_none()
if not order:
return await make_response("Order not found", 404)
@@ -115,7 +132,10 @@ def register() -> Blueprint:
Manually re-check this order's status with SumUp.
Useful if the webhook hasn't fired or the user didn't return correctly.
"""
result = await g.s.execute(select(Order).where(Order.id == order_id))
owner = _owner_filter()
if owner is None:
return await make_response("Order not found", 404)
result = await g.s.execute(select(Order).where(Order.id == order_id, owner))
order = result.scalar_one_or_none()
if not order:
return await make_response("Order not found", 404)