Skip to content

fix(env): preserve '=' in complex dict cast values (#565)#618

Open
jbbqqf wants to merge 1 commit into
joke2k:developfrom
jbbqqf:fix/565-complex-dict-equal-sign
Open

fix(env): preserve '=' in complex dict cast values (#565)#618
jbbqqf wants to merge 1 commit into
joke2k:developfrom
jbbqqf:fix/565-complex-dict-equal-sign

Conversation

@jbbqqf
Copy link
Copy Markdown

@jbbqqf jbbqqf commented May 22, 2026

Summary

The complex-dict cast path (cast={'value': ...}) used val.split('=') with no maxsplit, so a value that itself contains = (e.g. base64 padding, query strings, JWT segments) was silently truncated at the second =. The simple-dict cast path (cast=dict) already used split('=', 1). This PR aligns the two paths.

Fixes #565.

Context

Issue #565 noticed the inconsistency in environ/environ.py:

  • Complex dict (line 554, before fix): val.split('=') — drops the rest of the value after the first internal =
  • Simple dict (line 557): v.split('=', 1) — preserves it

Changes

  • environ/environ.py — single-line change: val.split('=')val.split('=', 1) in the complex-dict branch of parse_value. Added a code comment pointing at the simple-dict line and at issue Complex dict value cannot have equal sign, but simple dict can. #565 so a reviewer doesn't have to re-derive the reasoning.
  • tests/test_env.py — extends the existing parametrised test_dict_parsing matrix with a dict_value_contains_equal_sign case ('a=1;b=v=more' with dict(value=str)).

Reproduce BEFORE/AFTER yourself (copy-paste)

# --- one-time setup ---
git clone https://github.com/joke2k/django-environ.git /tmp/dj-environ-565 \
  && cd /tmp/dj-environ-565
python3 -m venv .venv && . .venv/bin/activate
pip install -e ".[testing]" >/dev/null

# --- BEFORE: origin/develop, expect the bug ---
git checkout origin/develop -- environ/ tests/
python3 -c "
import environ
e = environ.Env()
print(e.parse_value('a=1;b=v=more', {'value': str}))
"
# Expected: {'a': '1', 'b': 'v'}            <-- truncated, BUG

# --- AFTER: this branch, expect the fix ---
git fetch https://github.com/jbbqqf/django-environ.git fix/565-complex-dict-equal-sign
git checkout FETCH_HEAD -- environ/ tests/
python3 -c "
import environ
e = environ.Env()
print(e.parse_value('a=1;b=v=more', {'value': str}))
"
# Expected: {'a': '1', 'b': 'v=more'}       <-- preserved, FIXED

# Regression test:
pytest tests/test_env.py -k dict_value_contains_equal_sign -v
# Expected: PASSED in TestEnv / TestFileEnv / TestSubClass (3 cases)

What I ran locally

$ pytest tests/test_env.py -k test_dict_parsing -v
... 24 passed, 267 deselected in 0.08s

$ pytest
... 458 passed in 0.26s

The new test was also verified to FAIL on develop (only the environ.py change reverted): assert {'a': '1', 'b': 'v'} == {'a': '1', 'b': 'v=more'}.

Edge cases

# Scenario Input Expected Verified by
1 Existing simple key=value 'a=1' with dict(value=int) {'a': 1} dict_int (unchanged)
2 Existing list value 'a=1,2,3' with dict(value=[int]) {'a': [1, 2, 3]} dict_int_list (unchanged)
3 Value contains a single = 'a=1;b=v=more' with dict(value=str) {'a': '1', 'b': 'v=more'} new dict_value_contains_equal_sign
4 Multiple = in value 'k=a=b=c' with dict(value=str) {'k': 'a=b=c'} covered by same path (split with maxsplit=1)

Risk / blast radius

The change touches a single line in Env.parse_value's complex-dict branch. The previous behaviour silently truncated values at the first internal =, so any user actively relying on that truncation would have been doing so unintentionally — they would have been getting partial values without warning. All existing test_dict_parsing cases pass unchanged (none of them placed = inside a value), and the broader test suite (458 tests) is green.


PR drafted with assistance from Claude Code (Anthropic). The change was reviewed manually against django-environ's source. The reproducer block above is the one I used during development; reviewers can paste it verbatim.

The complex-dict cast path used ``val.split('=')`` (no maxsplit), so a
value containing an additional ``=`` (e.g. base64 padding, query strings,
JWT segments) was silently truncated. The simple-dict cast path already
used ``split('=', 1)``; align the complex path with the same single
split.

Adds a parametrised regression test covering the
``a=1;b=v=more`` shape that fails on develop and passes after the fix.

Fixes joke2k#565.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Complex dict value cannot have equal sign, but simple dict can.

1 participant