fix(wechat): a scalar allowed_file_extensions config silently widens back to all 37 defaults by Lesereingrape · Pull Request #6737 · bytedance/deer-flow · GitHub
Skip to content

fix(wechat): a scalar allowed_file_extensions config silently widens back to all 37 defaults - #6737

Merged
WillemJiang merged 4 commits into
bytedance:mainfrom
Lesereingrape:fix/wechat-allowed-file-extensions
Oct 12, 2026
Merged

WillemJiang merged 4 commits into
bytedance:mainfrom
Lesereingrape:fix/wechat-allowed-file-extensions

Conversation

@Lesereingrape

Copy link
Copy Markdown
Contributor

Fixes #6736

Why

While reading the WeCom channel's inbound file gate I noticed that allowed_file_extensions can be configured and silently do nothing. Writing it as a scalar, which is the natural way to express a one-item restriction in YAML:

channels:
  wechat:
    allowed_file_extensions: ".pdf"

makes _coerce_str_set fall into if not isinstance(value, (list, tuple, set, frozenset)): return set(default) — a string is not one of those containers — so the channel ends up admitting all 37 built-in extensions instead of only .pdf. There is no log line saying the configured value was unreadable, so the operator has no way to see that the restriction they wrote was dropped.

The pain is the direction of the failure. This set gates which files the Gateway may read; a restriction that widens instead of narrowing is a posture change that is invisible in the logs.

What changed

  • allowed_file_extensions: ".pdf" now means exactly that — a bare string is the single entry it names, matching what the sibling _coerce_host_suffixes already accepts one method below.
  • When a configured value still cannot be read as an extension allowlist (wrong type, or a list with no usable entries), the channel falls back to the built-in defaults and logs what it ignored and how many extensions it fell back to, so the operator can see the restriction did not take effect.
  • Entries that are not strings are reported and skipped instead of being stringified into members like .123 that no real file can ever match.

Behaviour for a well-formed list config is unchanged, and an unset allowed_file_extensions still resolves to the same 37 defaults.

Surface area

  • Backend API — endpoint / SSE event / request-response shape under backend/app
  • Frontend UI — page / component / setting / interaction under frontend/
  • Agents / LangGraph — agent node, graph wiring, langgraph.json, or prompt change
  • Sandbox — docker/ or sandboxed execution
  • Skills — change under skills/
  • Dependencies — new/upgraded entry in backend/pyproject.toml or frontend/package.json (say what it buys us)
  • Default behavior change — changes existing behavior without the user opting in (default model, default setting, data shape)
  • Docs / tests / CI only — no runtime behavior change

Only an operator who configured allowed_file_extensions as a scalar or with non-string entries notices a difference — and for them the change is that their configured restriction starts applying.

Bug fix verification

  • Test path that reproduces the bug: backend/tests/test_wechat_allowed_file_extensions.py
  • Did it go red on main and green on this branch? yes

On main (c89fed0b) with the fix reverted:

3 failed, 4 passed
  • test_scalar_extension_config_narrows_the_allowlist — .pdf produced 37 extensions
  • test_extension_config_of_the_wrong_type_is_logged_before_falling_back — no log emitted
  • test_unreadable_extension_entry_is_skipped_and_logged — 123 became a .123 member

On this branch:

7 passed

Validation

From backend/ on Windows, using the repo's own venv (make is not available in this environment, so the underlying commands were run directly):

PYTHONPATH=. python -m pytest tests/test_wechat_allowed_file_extensions.py -q   → 7 passed
PYTHONPATH=. python -m pytest tests/test_wechat_channel.py tests/test_wechat_allowed_file_extensions.py -q
                                                                                → 93 passed, 2 skipped
python -m ruff check app/channels/wechat.py tests/test_wechat_allowed_file_extensions.py
                                                                                → All checks passed!
python -m ruff format --check app/channels/wechat.py tests/test_wechat_allowed_file_extensions.py
                                                                                → 2 files already formatted

The 2 skips are pre-existing in tests/test_wechat_channel.py on this host, not introduced here.

AI assistance

Tool(s) used: Qoder

How you used it: Qoder located the coercions and drafted the tests against the observed config shapes; the failing-then-passing test file and the guard were re-run and verified locally, and the accepted shape was matched to the in-file precedent (_coerce_host_suffixes) and to a97772d0 for WeCom's allowed_users.

  • I've read and understand every line of this change and take responsibility for it — it's not unreviewed AI output.

@github-actions github-actions Bot added area:backend Gateway / runtime / core backend under backend/ risk:medium Medium risk: regular code changes size/M PR changes 100-300 lines labels Oct 12, 2026

@willem-bd willem-bd left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed against head 168d0ef. The core fix is correct and well-tested: I ran tests/test_wechat_allowed_file_extensions.py (7 passed) and tests/test_wechat_channel.py (95 passed total) against this head, and the scalar/typing/empty-list behaviors now match the docstring and PR description. One remaining silent-misconfiguration gap in the new scalar path, suggested below.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This scalar path has the same trap its sibling _parse_allowed_users already guards against (it warns when a scalar contains separators). A natural YAML habit like allowed_file_extensions: ".pdf, .zip" now becomes the single bogus entry {".pdf, .zip"}, which matches no filename suffix — verified on this head that even report.pdf is then denied, with no startup log pointing at the config. That is the same invisible-posture-change class this PR fixes, just in the fail-closed direction: every inbound/outbound file gets blocked until the operator happens to notice the per-file "blocked, skipping" warnings. Consider mirroring _parse_allowed_users here: warn when the scalar string contains , or whitespace (or split on commas), so the misconfiguration surfaces at startup.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushed in 9545d80. _coerce_str_set now mirrors the _parse_allowed_users guard: a scalar string containing , or internal whitespace logs

[WeChat] allowed_file_extensions is a scalar string containing separators; treating it as one literal extension. Use a YAML list for multiple extensions

at construction time and the value stays fail-closed ({".pdf, .zip"} still denies report.pdf), so the posture change is visible at startup instead of only through the per-file "blocked, skipping" warnings.

I kept the one-literal semantics rather than splitting on commas: silently reinterpreting the value would make the config mean something the operator did not write, and the sibling guard already sets the warn-don't-split precedent.

Two tests added (test_scalar_extension_config_with_separators_warns, test_space_separated_scalar_extension_config_warns). Measured before the guard existed: 2 failed / 7 passed, both failures on the missing warning. After: 9 passed, ruff check and ruff format --check clean, and -k wechat is 213 passed / 8 skipped. Also normalized the three [wechat] log prefixes inside this function to [WeChat] to match the rest of the file.

…list

A comma- or space-separated scalar string is still one literal extension, so
`allowed_file_extensions: ".pdf, .zip"` blocks every file with nothing in the
startup log pointing at the config. Mirror the `_parse_allowed_users` guard.
@Lesereingrape

Copy link
Copy Markdown
Contributor Author

@WillemJiang
WillemJiang merged commit c53621d into bytedance:main Oct 12, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:backend Gateway / runtime / core backend under backend/ risk:medium Medium risk: regular code changes size/M PR changes 100-300 lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(wechat): a scalar allowed_file_extensions config silently widens back to all 37 default extensions

4 participants