From 9feb188d9373a8e23ee84028e5ba80451c75046c Mon Sep 17 00:00:00 2001 From: wangyunlong <3080433063@qq.com> Date: Wed, 14 Oct 2026 18:38:03 +0800 Subject: [PATCH] =?UTF-8?q?fix(cookie-gate):=20=E5=BF=85=E9=9C=80=20cookie?= =?UTF-8?q?=20=E6=94=B9=E5=85=A8=E9=87=8F=E6=A0=A1=E9=AA=8C=E5=B9=B6?= =?UTF-8?q?=E6=94=B6=E7=B4=A7=E5=90=84=E8=B4=A6=E5=8F=B7=E5=BF=85=E9=9C=80?= =?UTF-8?q?=E5=90=8D=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit required_cookie_names 原语义是「任一必需名命中即有效」,单个匿名 cookie (如小红书 a1)就能把游客会话判为有效;改为全部必需名都存在、存活且域名 匹配才算通过。随之按实际登录产物收紧名单:jd 去 pin、erp321 留 u_id+u_co_id、小红书改 customer-sso-sid+solar.beaker.session.id、 星图仅 sid_guard。cookie_gate 用例冻结时钟改相对时钟防过期漂移。 --- config/runtime-bindings.json | 24 ++++++------- src/gyxx_flow/adapters/integration.py | 50 +++++++++++++++++++-------- tests/test_cookie_gate.py | 2 +- 3 files changed, 46 insertions(+), 30 deletions(-) diff --git a/config/runtime-bindings.json b/config/runtime-bindings.json index 0845234..3c83f0f 100644 --- a/config/runtime-bindings.json +++ b/config/runtime-bindings.json @@ -88,7 +88,6 @@ "jd.com" ], "required_cookie_names": [ - "pin", "thor" ], "credential_env_names": [ @@ -116,7 +115,6 @@ "jd.com" ], "required_cookie_names": [ - "pin", "thor" ], "credential_env_names": [ @@ -145,10 +143,7 @@ ], "required_cookie_names": [ "u_id", - "u_co_id", - "ASP.NET_SessionId", - ".ASPXAUTH", - "token" + "u_co_id" ], "credential_env_names": [ "ERP_USERNAME", @@ -299,7 +294,8 @@ "xiaohongshu.com" ], "required_cookie_names": [ - "web_session", + "customer-sso-sid", + "solar.beaker.session.id", "a1" ], "keepalive": { @@ -351,9 +347,6 @@ "xingtu.cn" ], "required_cookie_names": [ - "sessionid", - "sessionid_ss", - "sid_tt", "sid_guard" ], "keepalive": { @@ -365,8 +358,7 @@ "headless": true, "real_chrome": true }, - "members": [ - ] + "members": [] }, "chanmama": { "cdp_port": 22207, @@ -702,8 +694,12 @@ ], "cdp_port": 22165, "login_mode": "C", - "required_cookie_domains": ["xiaohongshu.com"], - "credential_env_names": ["XHS_ARK_ACCOUNT"] + "required_cookie_domains": [ + "xiaohongshu.com" + ], + "credential_env_names": [ + "XHS_ARK_ACCOUNT" + ] }, "content_marketing:data/tools/sync_style_categories.py": { "script_id": "content_marketing:data/tools/sync_style_categories.py", diff --git a/src/gyxx_flow/adapters/integration.py b/src/gyxx_flow/adapters/integration.py index 2e5def1..2dafec4 100644 --- a/src/gyxx_flow/adapters/integration.py +++ b/src/gyxx_flow/adapters/integration.py @@ -1210,29 +1210,49 @@ def _merge_storage_state( def _account_cookie_valid(account: AccountBinding, cookies: object) -> bool: - """A vault cookie set is valid when a live account cookie is present.""" + """A vault cookie set is valid when a live account cookie is present. + + With ``required_cookie_names`` configured, every required name must be + present as a live, domain-matched cookie; a single anonymous cookie that + happens to share a required name (e.g. ``a1``) must not validate a guest + session. Without required names, any live cookie on a required domain + keeps accepting the set. + """ if not isinstance(cookies, list): return False required_names = account.required_cookie_names required_domains = account.required_cookie_domains - for item in cookies: - if not isinstance(item, dict): - continue - if _cookie_expired(item): - continue - if not str(item.get("value", "")): - continue - name = str(item.get("name", "")) - domain = str(item.get("domain", "")).casefold().lstrip(".") - name_matches = not required_names or name in required_names - domain_matches = not required_domains or any( + + def _domain_matches(domain: str) -> bool: + return not required_domains or any( domain == required.casefold() or domain.endswith("." + required.casefold()) for required in required_domains ) - if name_matches and domain_matches: - return True - return False + + def _live(item: object) -> bool: + return ( + isinstance(item, dict) + and not _cookie_expired(item) + and bool(str(item.get("value", ""))) + ) + + if not required_names: + return any( + _live(item) + and _domain_matches( + str(item.get("domain", "")).casefold().lstrip(".") + ) + for item in cookies + ) + + outstanding = set(required_names) + for item in cookies: + if not _live(item): + continue + if _domain_matches(str(item.get("domain", "")).casefold().lstrip(".")): + outstanding.discard(str(item.get("name", ""))) + return not outstanding def _cookie_expired(cookie: Mapping[str, object], *, now: float | None = None) -> bool: diff --git a/tests/test_cookie_gate.py b/tests/test_cookie_gate.py index 43d9c2f..511b379 100644 --- a/tests/test_cookie_gate.py +++ b/tests/test_cookie_gate.py @@ -28,7 +28,7 @@ from gyxx_flow.workflow.engine import WorkflowEngine from gyxx_flow.workflow.model import StepDefinition, WorkflowDefinition from gyxx_flow.workflow.steps import StepExecution -_NOW = datetime(2026, 10, 8, 12, 0, 0, tzinfo=timezone.utc) +_NOW = datetime.now(timezone.utc).replace(microsecond=0) def _settings(tmp_path: Path) -> Settings: