diff --git a/remarkbox/__init__.py b/remarkbox/__init__.py index c97efc5..51bb356 100644 --- a/remarkbox/__init__.py +++ b/remarkbox/__init__.py @@ -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): diff --git a/remarkbox/models/namespace.py b/remarkbox/models/namespace.py index b57e00f..e5d0127 100644 --- a/remarkbox/models/namespace.py +++ b/remarkbox/models/namespace.py @@ -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. diff --git a/remarkbox/models/node.py b/remarkbox/models/node.py index f4fb578..aadcbd0 100644 --- a/remarkbox/models/node.py +++ b/remarkbox/models/node.py @@ -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: diff --git a/remarkbox/views/list_nodes.py b/remarkbox/views/list_nodes.py index 15504bd..8df52a4 100644 --- a/remarkbox/views/list_nodes.py +++ b/remarkbox/views/list_nodes.py @@ -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 )