mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-09-07 10:07:15 +00:00
fde04e64f6
# Conflicts: # src/langbot/pkg/api/http/controller/groups/pipelines/pipelines.py # src/langbot/pkg/api/http/service/bot.py # src/langbot/pkg/provider/runners/localagent.py # src/langbot/templates/metadata/pipeline/ai.yaml # tests/unit_tests/api/service/test_bot_service.py # tests/unit_tests/provider/runners/test_difysvapi_runner.py # tests/unit_tests/utils/test_safe_regex.py # web/src/app/infra/entities/adapter-categories.ts # web/src/app/wizard/page.tsx # web/src/i18n/locales/en-US.ts # web/src/i18n/locales/ja-JP.ts # web/src/i18n/locales/zh-Hans.ts # web/tests/e2e/plugin-page-auth.spec.ts
114 lines
4.1 KiB
Python
114 lines
4.1 KiB
Python
"""BanWordFilter regression tests for legacy sensitive-word lists.
|
|
|
|
v4.10.7 introduced a 64-pattern cap in safe_regex. Older installs still carry
|
|
the previous default list (~70 patterns). The filter must keep applying those
|
|
rules instead of blocking every message.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from importlib import import_module
|
|
from unittest.mock import Mock
|
|
|
|
import pytest
|
|
|
|
from tests.factories import FakeApp
|
|
|
|
|
|
_LEGACY_SENSITIVE_WORD_PATTERN_COUNT = 70
|
|
|
|
|
|
def _load_banwords():
|
|
import_module('langbot.pkg.pipeline.pipelinemgr')
|
|
banwords = import_module('langbot.pkg.pipeline.cntfilter.filters.banwords')
|
|
entities = import_module('langbot.pkg.pipeline.cntfilter.entities')
|
|
safe_regex = import_module('langbot.pkg.utils.safe_regex')
|
|
return banwords, entities, safe_regex
|
|
|
|
|
|
def _filter_with_words(words: list[str], *, mask: str = '*', mask_word: str = ''):
|
|
banwords, entities, _ = _load_banwords()
|
|
app = FakeApp()
|
|
app.sensitive_meta = Mock()
|
|
app.sensitive_meta.data = {
|
|
'words': words,
|
|
'mask': mask,
|
|
'mask_word': mask_word,
|
|
}
|
|
return banwords.BanWordFilter(app), entities, app
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_legacy_word_list_over_pattern_cap_does_not_block_clean_message():
|
|
"""A pre-v4.10.7 word list must not fail closed on every message."""
|
|
words = [f'word{i}' for i in range(_LEGACY_SENSITIVE_WORD_PATTERN_COUNT)]
|
|
filt, entities, _ = _filter_with_words(words)
|
|
|
|
result = await filt.process(Mock(), 'hello there, nothing banned')
|
|
|
|
assert result.level == entities.ResultLevel.PASS
|
|
assert result.replacement == 'hello there, nothing banned'
|
|
assert result.user_notice == ''
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_legacy_word_list_still_masks_match_beyond_first_batch():
|
|
"""Words past the first 64-pattern batch must still be applied."""
|
|
words = [f'word{i}' for i in range(_LEGACY_SENSITIVE_WORD_PATTERN_COUNT)] + ['secret-token']
|
|
filt, entities, _ = _filter_with_words(words, mask_word='[hidden]')
|
|
|
|
result = await filt.process(Mock(), 'please hide secret-token now')
|
|
|
|
assert result.level == entities.ResultLevel.MASKED
|
|
assert 'secret-token' not in result.replacement
|
|
assert '[hidden]' in result.replacement
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_legacy_word_list_masks_match_in_first_batch():
|
|
words = ['alpha-secret'] + [f'word{i}' for i in range(_LEGACY_SENSITIVE_WORD_PATTERN_COUNT)]
|
|
filt, entities, _ = _filter_with_words(words, mask_word='[hidden]')
|
|
|
|
result = await filt.process(Mock(), 'alpha-secret is here')
|
|
|
|
assert result.level == entities.ResultLevel.MASKED
|
|
assert result.replacement == '[hidden] is here'
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_invalid_sensitive_word_regex_still_blocks():
|
|
filt, entities, _ = _filter_with_words(['(unclosed'])
|
|
|
|
result = await filt.process(Mock(), 'any message')
|
|
|
|
assert result.level == entities.ResultLevel.BLOCK
|
|
assert result.user_notice == '内容检查规则执行失败,请联系管理员'
|
|
assert 'rejected' in result.console_notice.lower() or 'invalid' in result.console_notice.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_oversized_word_list_is_blocked():
|
|
"""Configured rules must never be silently skipped when the list is oversized."""
|
|
banwords, _, _ = _load_banwords()
|
|
words = [f'word{i}' for i in range(banwords._MAX_SENSITIVE_WORD_PATTERNS + 10)]
|
|
filt, entities, _ = _filter_with_words(words)
|
|
|
|
result = await filt.process(Mock(), 'hello there, nothing banned')
|
|
|
|
assert result.level == entities.ResultLevel.BLOCK
|
|
assert result.replacement == ''
|
|
assert result.user_notice == '内容检查规则执行失败,请联系管理员'
|
|
assert 'at most 256 regex patterns are allowed' in result.console_notice.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_match_beyond_total_cap_cannot_bypass_filter():
|
|
banwords, _, _ = _load_banwords()
|
|
words = [f'word{i}' for i in range(banwords._MAX_SENSITIVE_WORD_PATTERNS)] + ['late-secret']
|
|
filt, entities, _ = _filter_with_words(words, mask_word='[hidden]')
|
|
|
|
result = await filt.process(Mock(), 'please hide late-secret now')
|
|
|
|
assert result.level == entities.ResultLevel.BLOCK
|
|
assert result.replacement == ''
|