fix: cap search keywords and query results to prevent CWE-407 amplification

- list_nodes.py: strip empty tokens, cap keywords to 10 before passing to model
- node.py: add .limit(200) per keyword query — was unbounded .all()
- namespace.py: add .limit(500) on root.children in dict_dump — was unbounded iteration
- __init__.py: cap page_number to 1000 — large offsets force full table scans
This commit is contained in:
russell@unturf.com 2026-03-28 15:12:39 -04:00
parent 2061097e13
commit c87fb0bd1f
4 changed files with 11 additions and 5 deletions

View file

@ -512,6 +512,8 @@ def main(global_config, **settings):
def add_page_number(request):
"""Attach page_number starting at 0"""
page_number = int(request.params.get("page", 1))
# Cap to prevent OFFSET DoS — large offsets force full table scans (CWE-407).
page_number = min(page_number, 1000)
return page_number if page_number >= 1 else 1
def add_page_size(request):

View file

@ -437,8 +437,9 @@ class Namespace(RBase, Base):
# TODO: fix when root.created becomes root.created_timestamp.
"timestamp" : root.created,
}
comments = []
for node in root.children:
comments = []
# Limit children per root to prevent unbounded JSON serialization (CWE-407).
for node in root.children.limit(500):
comment = {
"date" : node.created_date,
# TODO: when root.created becomes root.created_timestamp.

View file

@ -514,9 +514,10 @@ def get_root_nodes_by_keywords(dbsession, keywords, namespace=None):
for keyword in keywords:
# extend nodes, with a list of nodes which match this keyword.
# extend nodes, with a list of nodes which match this keyword.
# Limit per-keyword results to prevent memory exhaustion (CWE-407).
keyword_filter = Node.data.ilike("%{}%".format(keyword))
nodes.extend(node_query.filter(keyword_filter).all())
nodes.extend(node_query.filter(keyword_filter).limit(200).all())
# accumulate scores and root node objects.
for node in nodes:

View file

@ -146,9 +146,11 @@ def search(request):
return HTTPFound(get_referer_or_home(request))
# Note: a search from topsecret page does not pass a namespace.
# Cap keywords to prevent O(k*n) amplification (CWE-407).
keyword_list = [k for k in keywords.split(" ") if k][:10]
nodes = get_root_nodes_by_keywords(
request.dbsession,
keywords.split(" "),
keyword_list,
request.namespace
)