diff --git a/server/tracker/services/validation.py b/server/tracker/services/validation.py index cffdff5..bd30de7 100644 --- a/server/tracker/services/validation.py +++ b/server/tracker/services/validation.py @@ -12,9 +12,10 @@ _URL_DROP_KEY = re.compile("|".join(C.URL_PARAM_DROP_PATTERNS), re.I) _BIZ_CHANNEL = {"whatsapp", "phone", "email"} _BIZ_RESULT = {"mail_sent", "mail_failed"} _BIZ_ERROR = {"invalid", "spam", "other"} -_SUBMIT_RE = re.compile(r"^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$|^$") # uuid4,可空 -_FORM_RE = re.compile(r"^cf7-\d{1,10}$") # CF7 表单 DOM id(f{n}-p{m} → 服务端规范化为 cf7-{form_id}) -_PROD_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$") # slug ≤100 +# 锚点一律 \Z 而非 $:Python `$` 匹配串尾换行前,"cf7-156\n" 等脏值会被放行(collect 端点公开) +_SUBMIT_RE = re.compile(r"^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\Z|\Z") # uuid4,可空 +_FORM_RE = re.compile(r"^cf7-\d{1,10}\Z") # CF7 表单 DOM id(f{n}-p{m} → 服务端规范化为 cf7-{form_id}) +_PROD_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0,99}\Z") # slug ≤100 def _biz_props_ok(name, props) -> bool: """业务事件属性逐键校验(spec §2 枚举/格式表;全部非自由文本)。 diff --git a/server/tracker/tests/test_validation.py b/server/tracker/tests/test_validation.py index 86ae9bc..e5f1d6d 100644 --- a/server/tracker/tests/test_validation.py +++ b/server/tracker/tests/test_validation.py @@ -124,6 +124,9 @@ def _biz(name, **props): (_biz("form_submit_result", form_id="cf7-156", submission_id="", result_code="ok"), "property_not_allowed"), (_biz("product_view", product_id="byd-equation-leopard-5", inferred=True, rule_version="v1"), None), (_biz("product_view", product_id="x"*101, inferred=True, rule_version="v1"), "property_not_allowed"), + # 尾换行脏值:$ 锚匹配串尾换行前,\Z 才是真正串尾——collect 端点公开,须拒 + (_biz("form_start", form_id="cf7-156\n"), "property_not_allowed"), + (_biz("form_submit", form_id="cf7-156", submission_id="9c8b7a6d-1111-4222-8333-444455556666\n"), "property_not_allowed"), ]) def test_biz_event_validation(ev, expect_reason): assert validate_event(ev, v2=True, batch_site_id=SITE_ID, batch_visitor_id="v1") == expect_reason