{"id":"CVE-2026-125112","kind":"vulnerability","aliases":["CVE-2026-125112","GHSA-xjw9-38cr-6372"],"related":[],"title":"djust: A template binding inherits a context safety grant it never earned (XSS)","description":"A context safety grant was inherited by a template **binding** that never earned it, so rebinding a name the view had marked safe left the mark attached to the new, attacker-controlled value.\n\ndjust's context safety channel is keyed by **name**, not by value. Every bind copied the value and left the grant in place:\n\n```\nview marks   p = mark_safe('<b>trusted</b>')\ntemplate     {% with p=user_input %}{{ p }}{% endwith %}\n\n  djust   '<img src=x onerror=alert(1)>'          <- executes\n  django  '&lt;img src=x onerror=alert(1)&gt;'\n```\n\n## Affected binds\n\nEight distinct shapes were live. All are fixed in 1.1.2:\n\n| shape | example |\n|---|---|\n| `{% with %}` rebinding a marked name | `{% with p=hostile %}{{ p }}{% endwith %}` |\n| `{% with %}` **descendant** of a rebound name | `{% with p=hostile %}{{ p.a }}{% endwith %}` |\n| `{% with %}` binding over a marked name | `{% with q=hostile %}{{ q }}{% endwith %}` |\n| `{% for %}` loop variable | `{% for p in hostiles %}{{ p }}{% endfor %}` |\n| `{% for %}` descendant | `{% for p in hostiles %}{{ p.a }}{% endfor %}` |\n| `{% for %}` tuple unpacking | `{% for k, v in rows %}{{ v }}{% endfor %}` |\n| `{% include … with %}` | `{% include \"c.html\" with q=hostile %}` |\n| assign tags | `{% … as x %}` |\n\nNo filter chain and no `|safe` anywhere in the template is required. The precondition is that the application marked *some* value safe under a name that a template later rebinds — an ordinary pattern, since a view commonly marks trusted markup safe and templates commonly reuse short names such as `p`, `item` or `row`.\n\n## Impact\n\nStored or reflected XSS in any djust application that combines `mark_safe` (or any framework path that grants a context key safety) with a template that rebinds that name. Exploitation requires no unusual template construct.\n\n## Patches\n\nFixed in **1.1.2**, and in 1.2.0 (`main`).\n\nThe rule is that a bind **replaces** the grant rather than inheriting one. It is stated that way deliberately: the originally reported defects pointed the *opposite* direction — a grant being **lost** across some binds, causing over-escaping — and fixing only the reported direction would have left this leak in place. Both directions are the same rule.\n\n**Not fixed in 1.1.2**, and tracked for 1.2.0: two over-escaping cases where a grant fails to travel with a value that legitimately carries one (`{% with body=post.text|linebreaks %}`, and `mark_safe` reaching a template through `dict.values` / `dict.items`). Both escape *more* than Django rather than less, so neither is a leak.\n\n## Workarounds\n\nBefore upgrading, avoid reusing a context name for both `mark_safe`'d content and untrusted input, and avoid rebinding such a name in `{% with %}`, `{% for %}`, `{% include … with %}` or an assign tag.\n\n## Credit\n\nFound while preparing the 1.1.1 security release: the reproducer published in GHSA-9395-2g46-rj3f was run against the built 1.1.1 artifact before that advisory was published, and proved the class was still live. Publishing the original text would have reported this class as patched when it was not.","summary":"djust carries a 'safe' mark from a context variable into any template binding that reuses the same name, so untrusted input rebound under that name is rendered unescaped. An attacker who controls the bound value can inject HTML or script, giving cross-site scripting in the page.","severity":"high","cvss":null,"epss":null,"exploited":false,"exploited_at":null,"products":[{"name":"djust","fixed":["1.0.10"],"affected":"< 1.0.10","ecosystem":"pypi"},{"name":"djust","fixed":["1.1.2"],"affected":">= 1.1.0, < 1.1.2","ecosystem":"pypi"}],"ecosystems":["pypi"],"tags":["cwe-116","cwe-79","django","djust","package","python","saas","self-hosted"],"not_affected_if":["default-config","no-mark-safe","no-name-rebinding"],"how_to_check":"- Run `pip show djust` or check the installed `djust` version in your lockfile.\n- Search templates for `{% with`, `{% for`, `{% include ... with` and assign tags reusing a name that the view passes through `mark_safe`.\n- Confirm whether the affected version is in use: `djust` below `1.0.10`, or `1.1.0` up to but not including `1.1.2`.","fix":"- `djust` `1.0.10` for the 1.0.x line\n- `djust` `1.1.2` for the 1.1.x line\n- 1.2.0 (main) also carries the fix per the advisory","status":"open","published_at":"2026-09-17T20:31:48+00:00","first_seen_at":"2026-11-09T22:15:30+00:00","triaged_at":"2026-11-09T22:16:27+00:00","updated_at":"2026-11-09T22:16:27+00:00","category":"package","confidence":0.85,"triage":{"at":"2026-11-09T22:16:27Z","model":"claude-haiku-5-5","output":{"fix":"- `djust` `1.0.10` for the 1.0.x line\n- `djust` `1.1.2` for the 1.1.x line\n- 1.2.0 (main) also carries the fix per the advisory","reach":"internet","title":"djust: template rebinding keeps safe mark, letting user input run as HTML","impact":"high","summary":"djust carries a 'safe' mark from a context variable into any template binding that reuses the same name, so untrusted input rebound under that name is rendered unescaped. An attacker who controls the bound value can inject HTML or script, giving cross-site scripting in the page.","audience":"developers","category":"package","nicknames":[],"who_cares":["python","django","djust","self-hosted","saas"],"confidence":0.85,"fix_status":"available","needs_auth":"unknown","worst_case":"attacker script runs in victims' browsers via reflected or stored XSS","analyst_note":"","how_to_check":"- Run `pip show djust` or check the installed `djust` version in your lockfile.\n- Search templates for `{% with`, `{% for`, `{% include ... with` and assign tags reusing a name that the view passes through `mark_safe`.\n- Confirm whether the affected version is in use: `djust` below `1.0.10`, or `1.1.0` up to but not including `1.1.2`.","plain_action":"","product_name":"","needs_analyst":false,"not_affected_if":["default-config","no-mark-safe","no-name-rebinding"],"who_is_affected":"Applications that use djust and combine `mark_safe` (or any grant of safe status to a context key) with a template that later rebinds the same name through `{% with %}`, `{% for %}`, `{% include ... with %}` or an assign tag. No unusual template filter or `|safe` is needed to trigger it. Projects on Django templates alone, or djust projects that never mark values safe or never rebind those names, are not exposed.","user_interaction":"no","product_ecosystem":"","needs_special_setup":"yes","severity_if_unknown":"high"},"cost_usd":0.000921,"input_tokens":5802,"output_tokens":682,"severity_before":"high"},"analyst_status":null,"analyst_note":null,"search":"'-116':28B '-125112':16A,24A '-2':508C '-2026':15A,23A '-79':30B '-9395':507C '1':163C,171C '1.1.1':499C,517C '1.1.2':183C,348C,413C '1.2.0':351C,417C '38cr':20A '6372':21A 'across':384C 'advisori':521C 'affect':172C 'alert':162C,170C 'anywher':262C 'applic':273C,317C 'artifact':518C 'assign':252C,492C 'attach':113C 'attack':68B,118C 'attacker-control':117C 'avoid':465C,480C 'bind':4A,51B,96C,134C,173C,208C,358C,386C 'bodi':437C 'bound':72B 'built':516C 'c.html':248C 'carri':40B,434C 'case':422C 'caus':387C 'chain':258C 'channel':125C 'class':527C,539C 'combin':319C 'common':292C,299C 'construct':344C 'content':475C 'context':7A,46B,88C,123C,329C,468C 'control':70B,119C 'copi':135C 'credit':494C 'cross':81B 'cross-sit':80B 'cve':14A,22A 'cwe':27B,29B 'd':474C 'defect':375C 'deliber':371C 'descend':197C,228C 'dict.items':448C 'dict.values':447C 'direct':379C,396C,405C 'distinct':175C 'django':31B,165C,453C 'djust':1A,25A,26A,32B,39B,121C,157C,316C 'earn':12A,99C 'eight':174C 'endfor':226C,234C,244C 'endwith':156C,195C,206C,217C 'escap':390C,421C,450C 'everi':133C 'exampl':185C 'execut':164C 'exploit':339C 'fail':426C 'filter':257C 'fix':181C,346C,392C,411C 'found':495C 'framework':324C 'g46':510C 'g46-rj3f':509C 'ghsa':18A,506C 'ghsa-xjw9-38cr':17A 'give':79B 'grant':9A,90C,141C,327C,361C,381C,425C 'host':38B 'hostil':193C,204C,215C,224C,232C,251C 'html':76B 'img':158C,166C 'impact':309C 'includ':245C,247C,488C 'inherit':5A,92C,364C 'inject':75B 'input':59B,154C,478C 'item':306C 'k':239C 'key':127C,330C 'later':284C 'leak':401C,461C 'left':110C,139C,399C 'legitim':433C 'less':456C 'linebreak':439C 'live':178C,530C 'loop':219C 'lost':383C 'main':352C 'mark':43B,108C,112C,145C,147C,189C,211C,274C,293C,320C,441C,472C 'markup':295C 'name':56B,63B,104C,129C,190C,201C,212C,280C,302C,338C,469C,484C 'neither':458C 'never':11A,98C 'new':116C 'one':365C,435C 'onerror':161C,169C 'opposit':378C 'ordinari':287C 'origin':373C,533C 'over-escap':388C,419C 'p':146C,152C,155C,192C,194C,203C,222C,225C,230C,305C 'p.a':205C,233C 'packag':33B 'page':86B 'patch':345C,541C 'path':325C 'pattern':288C 'place':143C,403C 'point':376C 'post.text':438C 'precondit':269C 'prepar':497C 'prove':525C 'publish':504C,523C,531C 'python':34B 'q':214C,216C,250C 'rather':362C,454C 'reach':443C 'rebind':102C,187C,285C,336C,481C 'rebound':60B,200C 'reflect':312C 'releas':501C 'render':65B 'replac':359C 'report':374C,395C,537C 'reproduc':503C 'requir':267C,340C 'reus':53B,300C,466C 'rj3f':511C 'row':242C,308C 'rule':354C,409C 'run':513C 'saa':35B 'safe':42B,109C,148C,261C,277C,296C,321C,442C,473C 'safeti':8A,89C,124C,331C 'script':78B,83B 'secur':500C 'self':37B 'self-host':36B 'shape':176C,184C 'short':301C 'sinc':289C 'site':82B 'src':159C,167C 'state':368C 'still':529C 'store':310C 'tag':253C,493C 'templat':3A,50B,95C,150C,265C,283C,298C,334C,343C,445C 'text':534C 'track':415C 'travel':428C 'trust':149C,294C 'tupl':236C 'two':418C 'unescap':66B 'unpack':237C 'untrust':58B,477C 'unusu':342C 'upgrad':464C 'user':153C 'v':240C,243C 'valu':73B,120C,132C,137C,276C,431C 'variabl':47B,220C 'view':106C,144C,291C 'way':370C 'workaround':462C 'would':397C,535C 'x':160C,168C,255C 'xjw9':19A 'xss':13A,313C","nicknames":[],"mitigations":[],"who_affected":"Applications that use djust and combine `mark_safe` (or any grant of safe status to a context key) with a template that later rebinds the same name through `{% with %}`, `{% for %}`, `{% include ... with %}` or an assign tag. No unusual template filter or `|safe` is needed to trigger it. Projects on Django templates alone, or djust projects that never mark values safe or never rebind those names, are not exposed.","tier":"B","verified":{},"analyst_revisit_at":null,"fix_status":"available","audience":"developers","plain_action":null,"pkeys":["djust"],"ptokens":["djust"],"labels":["cwe116","cwe79","django","djust","high","package","pypi","python","saas","selfhosted"],"campaign_type":null,"search_a":"'-125112':16A,24A '-2026':15A,23A '38cr':20A '6372':21A 'bind':4A 'context':7A 'cve':14A,22A 'djust':1A,25A,26A 'earn':12A 'ghsa':18A 'ghsa-xjw9-38cr':17A 'grant':9A 'inherit':5A 'never':11A 'safeti':8A 'templat':3A 'xjw9':19A 'xss':13A","search_b":"'-116':28B '-79':30B 'attack':68B 'bind':51B 'bound':72B 'carri':40B 'context':46B 'control':70B 'cross':81B 'cross-sit':80B 'cwe':27B,29B 'django':31B 'djust':32B,39B 'give':79B 'host':38B 'html':76B 'inject':75B 'input':59B 'mark':43B 'name':56B,63B 'packag':33B 'page':86B 'python':34B 'rebound':60B 'render':65B 'reus':53B 'saa':35B 'safe':42B 'script':78B,83B 'self':37B 'self-host':36B 'site':82B 'templat':50B 'unescap':66B 'untrust':58B 'valu':73B 'variabl':47B","search_c":"'-2':508C '-9395':507C '1':163C,171C '1.1.1':499C,517C '1.1.2':183C,348C,413C '1.2.0':351C,417C 'across':384C 'advisori':521C 'affect':172C 'alert':162C,170C 'anywher':262C 'applic':273C,317C 'artifact':518C 'assign':252C,492C 'attach':113C 'attack':118C 'attacker-control':117C 'avoid':465C,480C 'bind':96C,134C,173C,208C,358C,386C 'bodi':437C 'built':516C 'c.html':248C 'carri':434C 'case':422C 'caus':387C 'chain':258C 'channel':125C 'class':527C,539C 'combin':319C 'common':292C,299C 'construct':344C 'content':475C 'context':88C,123C,329C,468C 'control':119C 'copi':135C 'credit':494C 'd':474C 'defect':375C 'deliber':371C 'descend':197C,228C 'dict.items':448C 'dict.values':447C 'direct':379C,396C,405C 'distinct':175C 'django':165C,453C 'djust':121C,157C,316C 'earn':99C 'eight':174C 'endfor':226C,234C,244C 'endwith':156C,195C,206C,217C 'escap':390C,421C,450C 'everi':133C 'exampl':185C 'execut':164C 'exploit':339C 'fail':426C 'filter':257C 'fix':181C,346C,392C,411C 'found':495C 'framework':324C 'g46':510C 'g46-rj3f':509C 'ghsa':506C 'grant':90C,141C,327C,361C,381C,425C 'hostil':193C,204C,215C,224C,232C,251C 'img':158C,166C 'impact':309C 'includ':245C,247C,488C 'inherit':92C,364C 'input':154C,478C 'item':306C 'k':239C 'key':127C,330C 'later':284C 'leak':401C,461C 'left':110C,139C,399C 'legitim':433C 'less':456C 'linebreak':439C 'live':178C,530C 'loop':219C 'lost':383C 'main':352C 'mark':108C,112C,145C,147C,189C,211C,274C,293C,320C,441C,472C 'markup':295C 'name':104C,129C,190C,201C,212C,280C,302C,338C,469C,484C 'neither':458C 'never':98C 'new':116C 'one':365C,435C 'onerror':161C,169C 'opposit':378C 'ordinari':287C 'origin':373C,533C 'over-escap':388C,419C 'p':146C,152C,155C,192C,194C,203C,222C,225C,230C,305C 'p.a':205C,233C 'patch':345C,541C 'path':325C 'pattern':288C 'place':143C,403C 'point':376C 'post.text':438C 'precondit':269C 'prepar':497C 'prove':525C 'publish':504C,523C,531C 'q':214C,216C,250C 'rather':362C,454C 'reach':443C 'rebind':102C,187C,285C,336C,481C 'rebound':200C 'reflect':312C 'releas':501C 'replac':359C 'report':374C,395C,537C 'reproduc':503C 'requir':267C,340C 'reus':300C,466C 'rj3f':511C 'row':242C,308C 'rule':354C,409C 'run':513C 'safe':109C,148C,261C,277C,296C,321C,442C,473C 'safeti':89C,124C,331C 'secur':500C 'shape':176C,184C 'short':301C 'sinc':289C 'src':159C,167C 'state':368C 'still':529C 'store':310C 'tag':253C,493C 'templat':95C,150C,265C,283C,298C,334C,343C,445C 'text':534C 'track':415C 'travel':428C 'trust':149C,294C 'tupl':236C 'two':418C 'unpack':237C 'untrust':477C 'unusu':342C 'upgrad':464C 'user':153C 'v':240C,243C 'valu':120C,132C,137C,276C,431C 'variabl':220C 'view':106C,144C,291C 'way':370C 'workaround':462C 'would':397C,535C 'x':160C,168C,255C 'xss':313C","cause_entry":null,"cause_path":null,"disclosures_checked_at":null,"first_public_at":"2026-09-17T20:31:48+00:00","kev":null,"cvss_vector":null,"cvss_by":null,"poc_count":0,"sources":[{"source":"ghsa","external_id":"GHSA-xjw9-38cr-6372","url":"https://github.com/advisories/GHSA-xjw9-38cr-6372","severity":"high","published_at":"2026-09-17T20:31:48+00:00","modified_at":"2026-11-09T22:09:15+00:00","first_seen_at":"2026-11-09T22:15:30+00:00","fetched_at":"2026-11-09T22:20:36+00:00","note":null}],"documents":[{"id":110240,"url":"https://api.github.com/repos/djust-org/djust/security-advisories/GHSA-xjw9-38cr-6372","title":"Maintainer's advisory: api.github.com","kind":"vendor","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"advisory","confidence":null,"note":"Maintainer's advisory: api.github.com"},{"id":110241,"url":"https://github.com/djust-org/djust/security/advisories/GHSA-xjw9-38cr-6372","title":"Maintainer's advisory: github.com","kind":"vendor","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"advisory","confidence":null,"note":"Maintainer's advisory: github.com"},{"id":110242,"url":"https://github.com/djust-org/djust/releases/tag/v1.1.2","title":"Release: github.com","kind":"release","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"fix","confidence":null,"note":"Release: github.com"},{"id":110243,"url":"https://github.com/djust-org/djust/pull/2444","title":"Patch: github.com","kind":"other","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"fix","confidence":null,"note":"Patch: github.com"},{"id":110244,"url":"https://github.com/djust-org/djust/commit/06d213dbdd82f5b1c2affe2f869ae88a71809e4a","title":"Patch: github.com","kind":"other","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"fix","confidence":null,"note":"Patch: github.com"},{"id":110245,"url":"https://github.com/djust-org/djust/commit/5288e5dbc8d83d8aee8a22a0a7a3a478461c8965","title":"Patch: github.com","kind":"other","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"fix","confidence":null,"note":"Patch: github.com"},{"id":45542,"url":"https://github.com/djust-org/djust/releases/tag/v1.0.10","title":"Release notes: github.com","kind":"release","source":"nvd","publisher":"vendor","text":null,"published_at":null,"relation":"fix","confidence":null,"note":"Release: 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"}