argo-search 2.9.8 → 2.9.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +30 -20
- package/README.es.md +30 -20
- package/README.ja.md +30 -20
- package/README.ko.md +30 -20
- package/README.md +32 -22
- package/SKILL.md +1 -1
- package/bin/argo +4 -0
- package/bin/argo.js +1 -1
- package/config.yaml +52 -5
- package/docs/ARGO_INTRO.md +5 -3
- package/docs/RELEASE_NOTES_v2.9.9.md +78 -0
- package/package.json +3 -2
- package/packages/dsh-plugin/README.md +3 -2
- package/packages/dsh-plugin/dsh/client.js +98 -32
- package/packages/dsh-plugin/dsh/diagnostics.js +43 -0
- package/packages/dsh-plugin/dsh/index.js +23 -1
- package/packages/dsh-plugin/package.json +2 -2
- package/packages/dsh-plugin/test/client.test.js +2 -1
- package/scripts/adaptive.py +102 -42
- package/scripts/bench_tail_ab.py +281 -0
- package/scripts/bounded_run.py +91 -49
- package/scripts/clock_bridge.py +22 -0
- package/scripts/content_security.py +119 -4
- package/scripts/dispatch_result.py +9 -2
- package/scripts/engine_add.py +602 -0
- package/scripts/engine_dispatch.py +9 -0
- package/scripts/evidence_loop.py +4 -0
- package/scripts/fetch_quality.py +13 -0
- package/scripts/fetch_v3.py +3 -3
- package/scripts/http_client.py +24 -38
- package/scripts/image_vision/vision_probe +0 -0
- package/scripts/local_engines.py +321 -0
- package/scripts/matrix_search_eval.py +111 -1
- package/scripts/mcp_transport.py +1 -1
- package/scripts/net_proxy.py +27 -0
- package/scripts/recovery.py +7 -3
- package/scripts/route_combo.py +19 -5
- package/scripts/route_domains.py +138 -12
- package/scripts/safe_conn.py +52 -0
- package/scripts/search_entry.py +1 -0
- package/scripts/search_pipeline.py +20 -2
- package/scripts/search_rank.py +6 -0
- package/scripts/serp_guard.py +23 -0
- package/scripts/tail_completion.py +212 -0
- package/scripts/url_safety.py +126 -3
- package/scripts/usage_log.py +45 -0
|
@@ -35,6 +35,7 @@ THREAT_NAMES = {
|
|
|
35
35
|
"payload_smuggling": "载荷走私",
|
|
36
36
|
"xss_injection": "XSS 注入",
|
|
37
37
|
"recursive_injection": "递归注入",
|
|
38
|
+
"credential_exposure": "凭证暴露",
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
|
|
@@ -181,11 +182,53 @@ INJECTION_PATTERNS_EL = [
|
|
|
181
182
|
r"υποδύσου\s+(?:τον\s+)?(?:άνθρωπο|χρήστη|διαχειριστή|σύστημα)",
|
|
182
183
|
]
|
|
183
184
|
|
|
185
|
+
# 西班牙语注入
|
|
186
|
+
INJECTION_PATTERNS_ES = [
|
|
187
|
+
r"(?i)ignora\s+(?:todas\s+)?(?:las\s+)?(?:instrucciones|órdenes|ordenes|reglas|indicaciones)\s+(?:anteriores|previas)",
|
|
188
|
+
r"(?i)olvida\s+(?:todas\s+)?(?:las\s+)?(?:instrucciones|órdenes|reglas)",
|
|
189
|
+
r"(?i)(?:muéstrame|muestrame|enséñame|ensename|dime|revela|imprime|exhibe)\s+(?:tu\s+)?(?:prompt\s+del\s+sistema|prompt|instrucciones|configuración|configuracion)",
|
|
190
|
+
r"(?i)ya\s+no\s+(?:eres|es)\s+(?:un\s+)?(?:asistente|bot|ia|agente)",
|
|
191
|
+
r"(?i)(?:eres|ahora\s+eres)\s+(?:una\s+)?(?:persona|humano|usuario|administrador|sistema)",
|
|
192
|
+
r"(?i)(?:actúa|actua|comporta|finge|simula)\s+(?:como|de\s+que)\s+(?:una\s+)?(?:persona|humano|usuario|administrador|sistema)",
|
|
193
|
+
r"(?i)(?:elude|evita|ignora)\s+(?:todas\s+)?(?:las\s+)?(?:restricciones|limitaciones|protecciones|reglas)",
|
|
194
|
+
r"(?i)te\s+llamas\s+(?:ChatGPT|Claude|GPT|asistente\s+de\s+ia)",
|
|
195
|
+
]
|
|
196
|
+
|
|
197
|
+
# 法语注入
|
|
198
|
+
INJECTION_PATTERNS_FR = [
|
|
199
|
+
r"(?i)ignore\s+(?:toutes\s+)?(?:les\s+)?(?:instructions|consignes|règles|regles|ordres)\s+(?:précédentes|precedentes)",
|
|
200
|
+
r"(?i)oublie\s+(?:toutes\s+)?(?:les\s+)?(?:instructions|consignes|règles|regles)",
|
|
201
|
+
r"(?i)(?:montre|affiche|dis|révèle|revel|imprime)\s+(?:ton|votre|ta)?\s*(?:prompt\s+système|prompt|instructions|configuration)",
|
|
202
|
+
r"(?i)tu\s+n'es\s+plus\s+(?:un\s+)?(?:assistant|bot|ia|agent)",
|
|
203
|
+
r"(?i)tu\s+es\s+(?:désormais|desormais\s+)?(?:une\s+)?(?:personne|humain|utilisateur|administrateur|système|systeme)",
|
|
204
|
+
r"(?i)(?:fais\s+semblant|joue|agis)\s+(?:comme|en\s+tant\s+que)\s+(?:une\s+)?(?:personne|humain|utilisateur|administrateur|système|systeme)",
|
|
205
|
+
r"(?i)(?:contourne|évite|evite)\s+(?:toutes\s+)?(?:les\s+)?(?:restrictions|limitations|protections|règles|regles)",
|
|
206
|
+
r"(?i)ton\s+nom\s+est\s+(?:ChatGPT|Claude|GPT|assistant\s+ia)",
|
|
207
|
+
]
|
|
208
|
+
|
|
209
|
+
# 德语注入
|
|
210
|
+
INJECTION_PATTERNS_DE = [
|
|
211
|
+
r"(?i)ignoriere\s+(?:alle\s+)?(?:vorherigen\s+)?(?:Anweisungen|Regeln|Instruktionen|Befehle)",
|
|
212
|
+
r"(?i)vergiss\s+(?:alle\s+)?(?:vorherigen\s+)?(?:Anweisungen|Regeln|Instruktionen)",
|
|
213
|
+
r"(?i)(?:zeig|zeige|erzähl|erzahl|gib\s+preis|offenbare|drucke)\s+(?:mir\s+)?(?:deinen\s+)?(?:System-?Prompt|Systemprompt|Anweisungen|Einstellungen)",
|
|
214
|
+
r"(?i)du\s+bist\s+(?:nicht\s+mehr|kein\s+mehr)\s+(?:ein\s+)?(?:Assistent|Bot|KI|Agent)",
|
|
215
|
+
r"(?i)du\s+bist\s+(?:nun\s+)?(?:ein\s+)?(?:Mensch|Benutzer|Administrator|System)",
|
|
216
|
+
r"(?i)(?:tu|gib\s+vor|verhalte\s+dich)\s+(?:als|wie)\s+(?:ein\s+)?(?:Mensch|Benutzer|Administrator|System)",
|
|
217
|
+
r"(?i)(?:umgehe|ignoriere)\s+(?:alle\s+)?(?:Einschränkungen|Einschrankungen|Beschränkungen|Beschrankungen|Schutzmaßnahmen|Schutzmassnahmen|Regeln)",
|
|
218
|
+
r"(?i)dein\s+Name\s+ist\s+(?:ChatGPT|Claude|GPT|KI-Assistent)",
|
|
219
|
+
]
|
|
220
|
+
|
|
221
|
+
# 拉丁语系专用表(不含英语通用,避免 en/latin 两条路重复匹配)
|
|
222
|
+
INJECTION_PATTERNS_LATIN = (
|
|
223
|
+
INJECTION_PATTERNS_ES + INJECTION_PATTERNS_FR + INJECTION_PATTERNS_DE
|
|
224
|
+
)
|
|
225
|
+
|
|
184
226
|
# 按语系分类的注入模式表(lang_detect 主语言标签 → 模式列表)
|
|
185
227
|
LANG_INJECTION_PATTERNS: dict[str, list[str]] = {
|
|
186
228
|
"zh": INJECTION_PATTERNS_ZH,
|
|
187
229
|
"ja": INJECTION_PATTERNS_JA,
|
|
188
230
|
"ko": INJECTION_PATTERNS_KO,
|
|
231
|
+
"latin": INJECTION_PATTERNS_LATIN,
|
|
189
232
|
"cyrillic": INJECTION_PATTERNS_RU,
|
|
190
233
|
"arabic": INJECTION_PATTERNS_AR,
|
|
191
234
|
"hebrew": INJECTION_PATTERNS_HE,
|
|
@@ -259,6 +302,21 @@ XSS_PATTERNS = [
|
|
|
259
302
|
r"(?i)window\.location",
|
|
260
303
|
]
|
|
261
304
|
|
|
305
|
+
# 凭证形态:出现在抓取正文里的密钥形状串——可能是页面自身的示例,也可能是
|
|
306
|
+
# 被注入/外泄的凭证。标 credential_exposure(权重 0.5,单独命中不触发隔离,
|
|
307
|
+
# 但进 threat_types 供上层判断)。
|
|
308
|
+
SECRET_FORM_PATTERNS = [
|
|
309
|
+
r"sk-[A-Za-z0-9_\-]{16,}",
|
|
310
|
+
r"sk-ant-[A-Za-z0-9_\-]{16,}",
|
|
311
|
+
r"ghp_[A-Za-z0-9]{20,}",
|
|
312
|
+
r"github_pat_[A-Za-z0-9_]{20,}",
|
|
313
|
+
r"AKIA[0-9A-Z]{16}",
|
|
314
|
+
r"ASIA[0-9A-Z]{16}",
|
|
315
|
+
r"xox[baprs]-[A-Za-z0-9\-]{10,}",
|
|
316
|
+
r"AIza[0-9A-Za-z_\-]{35}",
|
|
317
|
+
r"-----BEGIN (?:RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----",
|
|
318
|
+
]
|
|
319
|
+
|
|
262
320
|
# 检测但不过度脱敏的文件路径类模式
|
|
263
321
|
_NO_REDACT_PATTERNS: frozenset = frozenset({
|
|
264
322
|
r"(?i)/etc/passwd",
|
|
@@ -317,6 +375,7 @@ WEIGHTS = {
|
|
|
317
375
|
"payload_smuggling": 0.9,
|
|
318
376
|
"xss_injection": 0.9,
|
|
319
377
|
"recursive_injection": 1.0,
|
|
378
|
+
"credential_exposure": 0.5,
|
|
320
379
|
}
|
|
321
380
|
|
|
322
381
|
|
|
@@ -348,6 +407,7 @@ class ContentScrubber:
|
|
|
348
407
|
threats.extend(self._detect_exfiltration(normalized))
|
|
349
408
|
threats.extend(self._detect_impersonation(normalized))
|
|
350
409
|
threats.extend(self._detect_xss(normalized))
|
|
410
|
+
threats.extend(self._detect_secret_forms(normalized))
|
|
351
411
|
|
|
352
412
|
# 阶段 3:语义意图分析
|
|
353
413
|
threats.extend(self._semantic_analysis(normalized))
|
|
@@ -467,9 +527,10 @@ class ContentScrubber:
|
|
|
467
527
|
if lang == "zh" or lang == "mixed":
|
|
468
528
|
patterns.extend(INJECTION_PATTERNS_ZH)
|
|
469
529
|
for key, plist in LANG_INJECTION_PATTERNS.items():
|
|
470
|
-
|
|
530
|
+
# en 与 latin 共享拉丁模式:ES/FR/DE 注入多为 ASCII 或带扩展字符,
|
|
531
|
+
# 纯 ASCII 时 lang_detect 判 en,漏掉它们等于给拉丁注入留门
|
|
532
|
+
if lang == key or (lang == "en" and key == "latin"):
|
|
471
533
|
patterns.extend(plist)
|
|
472
|
-
# en/latin 落到英文通用(已有)
|
|
473
534
|
return tuple(patterns)
|
|
474
535
|
|
|
475
536
|
def _detect_injections(self, text: str, lang: str = "other") -> list[ThreatDetection]:
|
|
@@ -485,6 +546,11 @@ class ContentScrubber:
|
|
|
485
546
|
def _detect_xss(self, text: str) -> list[ThreatDetection]:
|
|
486
547
|
return self._match_patterns(text, XSS_PATTERNS, "xss_injection", 0.9)
|
|
487
548
|
|
|
549
|
+
def _detect_secret_forms(self, text: str) -> list[ThreatDetection]:
|
|
550
|
+
"""凭证形态检测(输出侧防护):正文里的密钥形状串。"""
|
|
551
|
+
return self._match_patterns(text, SECRET_FORM_PATTERNS,
|
|
552
|
+
"credential_exposure", 0.7)
|
|
553
|
+
|
|
488
554
|
@staticmethod
|
|
489
555
|
def _match_patterns(text: str, patterns: list[str],
|
|
490
556
|
threat_type: str, confidence: float) -> list[ThreatDetection]:
|
|
@@ -629,10 +695,51 @@ def scrub_content(content: str) -> ScrubResult:
|
|
|
629
695
|
return get_scrubber().scrub(content)
|
|
630
696
|
|
|
631
697
|
|
|
698
|
+
# ── 风险门禁(v2.9.8)──────────────────────────────────────────────────────
|
|
699
|
+
#
|
|
700
|
+
# 此前 scrub_to_dict 只回传元数据(risk_score 等),**清洗后的正文被丢弃**——
|
|
701
|
+
# 安全引擎只「评分」不「门禁」,高风险内容照样进证据流水线与 Agent 上下文。
|
|
702
|
+
# 门禁把「评分」变成「动作」:risk_score ≥ 阈值时,fetch 层用清洗后正文替换
|
|
703
|
+
# 原始正文并标 content_quarantined,使被注入污染的正文不进证据分、不进上下文。
|
|
704
|
+
#
|
|
705
|
+
# 开关与阈值都可配(见 references/usage.md 功能开关总表):
|
|
706
|
+
# ARGO_CONTENT_RISK_GATE 默认开;关掉 = 放行高风险内容,仅应急用
|
|
707
|
+
# ARGO_CONTENT_RISK_THRESHOLD 默认 0.5,与历史 content_clean 阈值一致
|
|
708
|
+
RISK_GATE_THRESHOLD = 0.5
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
def risk_gate_enabled() -> bool:
|
|
712
|
+
"""内容风险门禁总开关(默认开)。"""
|
|
713
|
+
try:
|
|
714
|
+
from engine_env import env_flag
|
|
715
|
+
return env_flag("ARGO_CONTENT_RISK_GATE", default=True)
|
|
716
|
+
except Exception:
|
|
717
|
+
return True
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
def risk_gate_threshold() -> float:
|
|
721
|
+
"""门禁阈值:risk_score ≥ 此值即隔离。默认 0.5,越界回落默认。"""
|
|
722
|
+
try:
|
|
723
|
+
from engine_env import get_env
|
|
724
|
+
val = float(str(get_env(["ARGO_CONTENT_RISK_THRESHOLD"])).strip())
|
|
725
|
+
except (TypeError, ValueError):
|
|
726
|
+
return RISK_GATE_THRESHOLD
|
|
727
|
+
if val <= 0.0 or val > 1.0:
|
|
728
|
+
return RISK_GATE_THRESHOLD
|
|
729
|
+
return val
|
|
730
|
+
|
|
731
|
+
|
|
632
732
|
def scrub_to_dict(content: str) -> dict:
|
|
633
|
-
"""清洗入口(dict 形态,供 fetch 层直接合并)。
|
|
733
|
+
"""清洗入口(dict 形态,供 fetch 层直接合并)。
|
|
734
|
+
|
|
735
|
+
除历史字段外,回传门禁三件套:gate_applied(门禁是否生效)、
|
|
736
|
+
quarantined(是否隔离)、cleaned_content(仅隔离时回传,供替换原始正文)。
|
|
737
|
+
"""
|
|
634
738
|
r = scrub_content(content)
|
|
635
|
-
|
|
739
|
+
gate_on = risk_gate_enabled()
|
|
740
|
+
threshold = risk_gate_threshold()
|
|
741
|
+
quarantined = gate_on and r.risk_score >= threshold
|
|
742
|
+
out = {
|
|
636
743
|
"content_clean": r.clean,
|
|
637
744
|
"risk_score": r.risk_score,
|
|
638
745
|
"threat_count": len(r.threats),
|
|
@@ -640,7 +747,15 @@ def scrub_to_dict(content: str) -> dict:
|
|
|
640
747
|
"redactions": r.redactions,
|
|
641
748
|
"encoding_layers_stripped": r.encoding_layers_stripped,
|
|
642
749
|
"content_lang": r.content_lang,
|
|
750
|
+
"gate_applied": gate_on,
|
|
751
|
+
"gate_threshold": threshold,
|
|
752
|
+
"quarantined": quarantined,
|
|
643
753
|
}
|
|
754
|
+
# 只有被隔离时才回传清洗后正文:它替换原始正文,是门禁的「动作」部分;
|
|
755
|
+
# 未命中时多带一份正文副本只是白占内存。
|
|
756
|
+
if quarantined:
|
|
757
|
+
out["cleaned_content"] = r.content
|
|
758
|
+
return out
|
|
644
759
|
|
|
645
760
|
|
|
646
761
|
# ── CLI ────────────────────────────────────────────────────────────────────
|
|
@@ -39,20 +39,26 @@ class DispatchResult:
|
|
|
39
39
|
(deep 等无预算模式 total 为 None)。搜索输出把它挂进 timing.budget,
|
|
40
40
|
让「这次离预算上限还有多远」可观测,而不必反推 process_ms。
|
|
41
41
|
|
|
42
|
+
`stranded`:早停弃置且仍存活的引擎 ``[(engine, holder, now_fn), ...]``,
|
|
43
|
+
交 tail_completion 后台收割(学习器记真实成绩 + 合并写 L2 缓存)。主响应
|
|
44
|
+
与 cancelled 记账不受它影响——它改的是「交付被丢弃」这一件事。
|
|
45
|
+
|
|
42
46
|
记账字段的语义与历史见模块 docstring;`wasted_ms` 仅为兼容别名。
|
|
43
47
|
"""
|
|
44
48
|
|
|
45
49
|
__slots__ = ("raw_results", "engine_outcomes", "engine_latency",
|
|
46
50
|
"wasted_ms", "early_stopped", "run_one", "ingest",
|
|
47
51
|
"budget_used_ms", "budget_total_ms", "useful_ms",
|
|
48
|
-
"first_sufficient_ms", "tail_wait_ms", "abandoned_engine_ms"
|
|
52
|
+
"first_sufficient_ms", "tail_wait_ms", "abandoned_engine_ms",
|
|
53
|
+
"stranded")
|
|
49
54
|
|
|
50
55
|
def __init__(self, raw_results: Any, engine_outcomes: Any,
|
|
51
56
|
engine_latency: Any, wasted_ms: int, early_stopped: bool,
|
|
52
57
|
run_one: Any, ingest: Any, budget_used_ms: int | None = None,
|
|
53
58
|
budget_total_ms: int | None = None, useful_ms: int = 0,
|
|
54
59
|
first_sufficient_ms: int = 0, tail_wait_ms: int = 0,
|
|
55
|
-
abandoned_engine_ms: int | None = None
|
|
60
|
+
abandoned_engine_ms: int | None = None,
|
|
61
|
+
stranded: Any = ()) -> None:
|
|
56
62
|
self.raw_results = raw_results
|
|
57
63
|
self.engine_outcomes = engine_outcomes
|
|
58
64
|
self.engine_latency = engine_latency
|
|
@@ -68,6 +74,7 @@ class DispatchResult:
|
|
|
68
74
|
# 旧调用方只传 wasted_ms:别名取同一值,不引入第二个真源。
|
|
69
75
|
self.abandoned_engine_ms = (wasted_ms if abandoned_engine_ms is None
|
|
70
76
|
else abandoned_engine_ms)
|
|
77
|
+
self.stranded = tuple(stranded or ())
|
|
71
78
|
|
|
72
79
|
|
|
73
80
|
def summarize_dispatch(*, wall_ms: int, contributed_ms: list[float],
|