{"id":"GHSA-49g6-96jj-f7r9","kind":"vulnerability","aliases":["GHSA-49g6-96jj-f7r9"],"related":[],"title":"Quadratic time unique_items list validation in Pydantic V1","description":"### Summary\r\n\r\nValidating an untrusted list into a field constrained with `unique_items=True` with Pydantic V1 takes quadratic time: an input of a few hundred kilobytes takes seconds to validate.\r\n\r\n### Details\r\n\r\nThe [`ConstrainedList.unique_items_validator`](https://github.com/pydantic/pydantic/blob/v1.10.26/pydantic/types.py#L612-L620) validator (in [`pydantic/types.py`](https://github.com/pydantic/pydantic/blob/v1.10.26/pydantic/types.py)) checks each item against all the following ones:\r\n\r\n```python\r\n@classmethod\r\ndef unique_items_validator(cls, v: 'Optional[List[T]]') -> 'Optional[List[T]]':\r\n    if v is None:\r\n        return None\r\n\r\n    for i, value in enumerate(v, start=1):\r\n        if value in v[i:]:\r\n            raise errors.ListUniqueItemsError()\r\n\r\n    return v\r\n```\r\n\r\nFor a list with no duplicates (the common, valid case), this results in a quadratic number of comparisons.\r\n\r\nThe issue was fixed in commit https://github.com/pydantic/pydantic/commit/6ec75e57f277a820ec583e601167f25fe8dc7c87.\r\n\r\nIf you are relying on the `pydantic.v1` namespace in Pydantic V2, upgrade to [V2.14.2](https://github.com/pydantic/pydantic/releases/tag/v2.14.2) or later.\r\n\r\n### Impact\r\n\r\nDenial of service through validation of untrusted input. Affected are applications validating untrusted lists into fields constrained with `unique_items=True` (using `conlist()` or `Field()`).\r\n\r\nThe fix only applies to lists of builtin scalar types (`int`, `float`, `str`, `bytes`, `bool` and `None`), for which duplicates can be detected in linear time. Lists of other types (e.g. models or dictionaries) are still checked in quadratic time, as their items aren't guaranteed to be hashable: bound their size using the `max_items` constraint.\r\n\r\nPydantic V2 models are not affected.","summary":"Pydantic V1 checks each item in a list constrained with unique_items=True against all following items, so validating a large untrusted list costs quadratic time. An attacker sending a list of a few hundred kilobytes can make validation take seconds, causing denial of service.","severity":"medium","cvss":6.9,"epss":null,"exploited":false,"exploited_at":null,"products":[{"name":"pydantic","note":"1.9.0,<=1.10.27","fixed":["1.10.27"],"affected":"1.9.0, <= 1.10.27","ecosystem":"pypi"}],"ecosystems":["pypi"],"tags":["cwe-407","package","pydantic","pydantic-v1","python"],"not_affected_if":["max-items-set","no-untrusted-list-input","pydantic-v2-models"],"how_to_check":"- Check the installed Pydantic version with `pip show pydantic` and look for a `1.x` version between `1.9.0` and `1.10.26`.\n- Search the codebase for `unique_items=True`, `conlist(` and `Field(` with `unique_items` to find fields that validate untrusted lists.\n- Check whether those list fields have a `max_items` constraint.","fix":"- Pydantic (PyPI) `1.10.27` or later, per the fixed version in the product data.\n- For lists of non-scalar types (models, dictionaries), the advisory says to bound list size with `max_items` as a workaround until upgrading.\n- Pydantic V2 models are not affected; if using `pydantic.v1` under V2, upgrade to `2.14.2` or later per the advisory.","status":"open","published_at":"2026-10-29T16:48:03+00:00","first_seen_at":"2026-11-09T22:53:21+00:00","triaged_at":"2026-11-09T22:54:34+00:00","updated_at":"2026-11-09T22:54:34+00:00","category":"package","confidence":0.8,"triage":{"at":"2026-11-09T22:54:34Z","model":"claude-haiku-5-5","output":{"fix":"- Pydantic (PyPI) `1.10.27` or later, per the fixed version in the product data.\n- For lists of non-scalar types (models, dictionaries), the advisory says to bound list size with `max_items` as a workaround until upgrading.\n- Pydantic V2 models are not affected; if using `pydantic.v1` under V2, upgrade to `2.14.2` or later per the advisory.","reach":"internet","title":"Pydantic V1: unique_items list validation takes quadratic time, enabling slowdowns","impact":"low","summary":"Pydantic V1 checks each item in a list constrained with unique_items=True against all following items, so validating a large untrusted list costs quadratic time. An attacker sending a list of a few hundred kilobytes can make validation take seconds, causing denial of service.","audience":"developers","category":"package","nicknames":[],"who_cares":["python","pydantic","pydantic-v1"],"confidence":0.8,"fix_status":"available","needs_auth":"unknown","worst_case":"Requests cause seconds of CPU per request, degrading service availability.","analyst_note":"","how_to_check":"- Check the installed Pydantic version with `pip show pydantic` and look for a `1.x` version between `1.9.0` and `1.10.26`.\n- Search the codebase for `unique_items=True`, `conlist(` and `Field(` with `unique_items` to find fields that validate untrusted lists.\n- Check whether those list fields have a `max_items` constraint.","plain_action":"","product_name":"","needs_analyst":false,"not_affected_if":["pydantic-v2-models","no-untrusted-list-input","max-items-set"],"who_is_affected":"Applications that use Pydantic V1 (versions 1.9.0 through 1.10.26) to validate untrusted input into list fields marked unique_items=True, via conlist() or Field(). Pydantic V2 models are not affected, and lists of scalar types were fixed separately from lists of models or dictionaries, which remain slow unless bounded with max_items.","user_interaction":"no","product_ecosystem":"","needs_special_setup":"yes","severity_if_unknown":"unknown"},"cost_usd":0.000895,"input_tokens":5201,"output_tokens":749,"severity_before":"medium"},"analyst_status":null,"analyst_note":null,"search":"'-407':22B '/pydantic/pydantic/blob/v1.10.26/pydantic/types.py#l612-l620)':111C '/pydantic/pydantic/blob/v1.10.26/pydantic/types.py))':117C '/pydantic/pydantic/commit/6ec75e57f277a820ec583e601167f25fe8dc7c87.':189C '/pydantic/pydantic/releases/tag/v2.14.2)':206C '1':153C '49g6':12A,17A '96jj':13A,18A 'affect':218C,297C 'appli':238C 'applic':220C 'aren':278C 'attack':56B 'bool':249C 'bound':284C 'builtin':242C 'byte':248C 'case':172C 'caus':70B 'check':31B,118C,271C 'classmethod':127C 'cls':132C 'commit':186C 'common':170C 'comparison':180C 'conlist':232C 'constrain':37B,82C,226C 'constrainedlist.unique':106C 'constraint':291C 'cost':52B 'cwe':21B 'def':128C 'denial':71B,210C 'detail':104C 'detect':257C 'dictionari':268C 'duplic':168C,254C 'e.g':265C 'enumer':150C 'errors.listuniqueitemserror':160C 'f7r9':14A,19A 'field':81C,225C,234C 'fix':184C,236C 'float':246C 'follow':44B,124C 'ghsa':11A,16A 'ghsa-49g6-96jj-f7r9':10A,15A 'github.com':110C,116C,188C,205C 'github.com/pydantic/pydantic/blob/v1.10.26/pydantic/types.py#l612-l620)':109C 'github.com/pydantic/pydantic/blob/v1.10.26/pydantic/types.py))':115C 'github.com/pydantic/pydantic/commit/6ec75e57f277a820ec583e601167f25fe8dc7c87.':187C 'github.com/pydantic/pydantic/releases/tag/v2.14.2)':204C 'guarante':280C 'hashabl':283C 'hundr':63B,98C 'impact':209C 'input':94C,217C 'int':245C 'issu':182C 'item':4A,33B,40B,45B,85C,107C,120C,130C,229C,277C,290C 'kilobyt':64B,99C 'larg':49B 'later':208C 'linear':259C 'list':5A,36B,51B,59B,78C,135C,138C,165C,223C,240C,261C 'make':66B 'max':289C 'model':266C,294C 'namespac':197C 'none':143C,145C,251C 'number':178C 'one':125C 'option':134C,137C 'packag':23B 'pydant':8A,24B,26B,29B,88C,199C,292C 'pydantic':20A 'pydantic-v1':25B 'pydantic.v1':196C 'pydantic/types.py':114C 'python':28B,126C 'quadrat':1A,53B,91C,177C,273C 'rais':159C 'reli':193C 'result':174C 'return':144C,161C 'scalar':243C 'second':69B,101C 'send':57B 'servic':73B,212C 'size':286C 'start':152C 'still':270C 'str':247C 'summari':74C 'take':68B,90C,100C 'time':2A,54B,92C,260C,274C 'true':41B,86C,230C 'type':244C,264C 'uniqu':3A,39B,84C,129C,228C 'untrust':50B,77C,216C,222C 'upgrad':201C 'use':231C,287C 'v':133C,141C,151C,157C,162C 'v1':9A,27B,30B,89C 'v2':200C,293C 'v2.14.2':203C 'valid':6A,47B,67B,75C,103C,108C,112C,131C,171C,214C,221C 'valu':148C,155C","nicknames":[],"mitigations":[],"who_affected":"Applications that use Pydantic V1 (versions 1.9.0 through 1.10.26) to validate untrusted input into list fields marked unique_items=True, via conlist() or Field(). Pydantic V2 models are not affected, and lists of scalar types were fixed separately from lists of models or dictionaries, which remain slow unless bounded with max_items.","tier":"C","verified":{},"analyst_revisit_at":null,"fix_status":"available","audience":"developers","plain_action":null,"pkeys":["pydantic"],"ptokens":["pydantic"],"labels":["cwe407","medium","package","pydantic","pydanticv1","pypi","python"],"campaign_type":null,"search_a":"'49g6':12A,17A '96jj':13A,18A 'f7r9':14A,19A 'ghsa':11A,16A 'ghsa-49g6-96jj-f7r9':10A,15A 'item':4A 'list':5A 'pydant':8A 'pydantic':20A 'quadrat':1A 'time':2A 'uniqu':3A 'v1':9A 'valid':6A","search_b":"'-407':22B 'attack':56B 'caus':70B 'check':31B 'constrain':37B 'cost':52B 'cwe':21B 'denial':71B 'follow':44B 'hundr':63B 'item':33B,40B,45B 'kilobyt':64B 'larg':49B 'list':36B,51B,59B 'make':66B 'packag':23B 'pydant':24B,26B,29B 'pydantic-v1':25B 'python':28B 'quadrat':53B 'second':69B 'send':57B 'servic':73B 'take':68B 'time':54B 'true':41B 'uniqu':39B 'untrust':50B 'v1':27B,30B 'valid':47B,67B","search_c":"'/pydantic/pydantic/blob/v1.10.26/pydantic/types.py#l612-l620)':111C '/pydantic/pydantic/blob/v1.10.26/pydantic/types.py))':117C '/pydantic/pydantic/commit/6ec75e57f277a820ec583e601167f25fe8dc7c87.':189C '/pydantic/pydantic/releases/tag/v2.14.2)':206C '1':153C 'affect':218C,297C 'appli':238C 'applic':220C 'aren':278C 'bool':249C 'bound':284C 'builtin':242C 'byte':248C 'case':172C 'check':118C,271C 'classmethod':127C 'cls':132C 'commit':186C 'common':170C 'comparison':180C 'conlist':232C 'constrain':82C,226C 'constrainedlist.unique':106C 'constraint':291C 'def':128C 'denial':210C 'detail':104C 'detect':257C 'dictionari':268C 'duplic':168C,254C 'e.g':265C 'enumer':150C 'errors.listuniqueitemserror':160C 'field':81C,225C,234C 'fix':184C,236C 'float':246C 'follow':124C 'github.com':110C,116C,188C,205C 'github.com/pydantic/pydantic/blob/v1.10.26/pydantic/types.py#l612-l620)':109C 'github.com/pydantic/pydantic/blob/v1.10.26/pydantic/types.py))':115C 'github.com/pydantic/pydantic/commit/6ec75e57f277a820ec583e601167f25fe8dc7c87.':187C 'github.com/pydantic/pydantic/releases/tag/v2.14.2)':204C 'guarante':280C 'hashabl':283C 'hundr':98C 'impact':209C 'input':94C,217C 'int':245C 'issu':182C 'item':85C,107C,120C,130C,229C,277C,290C 'kilobyt':99C 'later':208C 'linear':259C 'list':78C,135C,138C,165C,223C,240C,261C 'max':289C 'model':266C,294C 'namespac':197C 'none':143C,145C,251C 'number':178C 'one':125C 'option':134C,137C 'pydant':88C,199C,292C 'pydantic.v1':196C 'pydantic/types.py':114C 'python':126C 'quadrat':91C,177C,273C 'rais':159C 'reli':193C 'result':174C 'return':144C,161C 'scalar':243C 'second':101C 'servic':212C 'size':286C 'start':152C 'still':270C 'str':247C 'summari':74C 'take':90C,100C 'time':92C,260C,274C 'true':86C,230C 'type':244C,264C 'uniqu':84C,129C,228C 'untrust':77C,216C,222C 'upgrad':201C 'use':231C,287C 'v':133C,141C,151C,157C,162C 'v1':89C 'v2':200C,293C 'v2.14.2':203C 'valid':75C,103C,108C,112C,131C,171C,214C,221C 'valu':148C,155C","cause_entry":null,"cause_path":null,"disclosures_checked_at":null,"first_public_at":"2026-10-29T16:48:03+00:00","kev":null,"cvss_vector":null,"cvss_by":null,"poc_count":0,"sources":[{"source":"ghsa-repo","external_id":"GHSA-49g6-96jj-f7r9","url":"https://github.com/pydantic/pydantic/security/advisories/GHSA-49g6-96jj-f7r9","severity":"medium","published_at":"2026-10-29T16:48:03+00:00","modified_at":"2026-10-29T16:48:03+00:00","first_seen_at":"2026-11-09T22:53:21+00:00","fetched_at":"2026-11-09T22:53:21+00:00","note":null}],"documents":[{"id":110525,"url":"https://github.com/pydantic/pydantic/security/advisories/GHSA-49g6-96jj-f7r9","title":"Maintainer's advisory: github.com","kind":"vendor","source":"ghsa-repo","publisher":"vendor","text":null,"published_at":null,"relation":"advisory","confidence":null,"note":"Maintainer's advisory: github.com"}],"disclosures":[],"exploit_sources":[],"notice":"Free to read, with fair use of about 60 pages a minute. For higher limits or API access: sales@infrastack.ai"}