KeyHunter parser v10 + 18 scanners + tests (synthetic fixtures only, no keys/db/tokens)
This commit is contained in:
37
tests/test_baipiao.py
Normal file
37
tests/test_baipiao.py
Normal file
@@ -0,0 +1,37 @@
|
||||
"""Fixtures for baipiao_eternal: link extraction, scan_page, dedup, junk filter.
|
||||
No network. Run: python tests/test_baipiao.py
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
os.environ["KF_DB"] = os.path.join(tempfile.mkdtemp(prefix="kf_bp_test_"), "keys.db")
|
||||
HERE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, HERE)
|
||||
import baipiao_eternal as B
|
||||
|
||||
DS = "sk-3f9a1c7e5b2d8460a1c3e5f7091b2d4c"
|
||||
HTML = """<html><body>
|
||||
<a href="/free/api/glm-free-key/">GLM</a>
|
||||
<a href="/free/api/deepseek-credit/">DS</a>
|
||||
<code>DASHSCOPE_API_KEY=%s</code>
|
||||
<code>sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</code>
|
||||
</body></html>""" % DS
|
||||
NOISE = """<code>sk-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</code>"""
|
||||
|
||||
def main():
|
||||
links = B.LINK_RE.findall(HTML)
|
||||
assert sorted(links) == ["/free/api/deepseek-credit/", "/free/api/glm-free-key/"], links
|
||||
n = B.scan_page(HTML, "https://baipiao.org/free/api/x/")
|
||||
assert n >= 1, f"expected key insert, got {n}"
|
||||
rows = B.conn.execute("SELECT val, prov, found FROM keys").fetchall()
|
||||
assert any(r[0] == DS and r[2] == "baipiao" for r in rows), rows
|
||||
assert not any("xxxx" in r[0] for r in rows), "junk leaked"
|
||||
n2 = B.scan_page(HTML, "https://baipiao.org/free/api/x/")
|
||||
assert n2 == 0, f"dedup failed: {n2}"
|
||||
n3 = B.scan_page(NOISE, "noise")
|
||||
assert n3 == 0, f"junk leaked: {n3}"
|
||||
print("PASS: link extraction, scan_page, junk filter, dedup")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
142
tests/test_batch2_patterns.py
Normal file
142
tests/test_batch2_patterns.py
Normal file
@@ -0,0 +1,142 @@
|
||||
"""Batch-2 fixture tests (no network). Run: python tests/test_batch2_patterns.py
|
||||
|
||||
Batch-2 final scope: HEROKU + SUPABASE in canon src/key_patterns.py PATTERNS
|
||||
(fleet_update ships canon; patterns_extra is excluded from wave payload).
|
||||
REJECTED families asserted absent (doctrine: one label per key shape):
|
||||
CLERK — sk_(live|test)_ shape owned by canon STRIPE; second label =
|
||||
mislabel race on every real Stripe live key.
|
||||
BAIDU — bce-v3/ALTAK-{id}/{sig} is a per-request HMAC signature,
|
||||
not a reusable credential.
|
||||
Shape-ownership gate: each pos fixture must match EXACTLY ONE PATTERNS entry.
|
||||
Coupling gate: emitted prov must resolve in eternal_v10 (allowlist/PROVIDERS),
|
||||
else UNKNOWN -> DEAD terminal (KH_AWS graveyard lesson). Static parse only —
|
||||
eternal_v10 has module-level side effects + live HTTP in validate().
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(HERE), "src"))
|
||||
sys.path.insert(0, os.path.dirname(HERE))
|
||||
|
||||
FAILS = []
|
||||
|
||||
|
||||
def check(name, cond, detail=""):
|
||||
if cond:
|
||||
print(f"ok {name}")
|
||||
else:
|
||||
print(f"FAIL {name} {detail}")
|
||||
FAILS.append(name)
|
||||
|
||||
|
||||
import key_patterns as K # noqa: E402 (canon, src tree)
|
||||
import patterns_extra as PE # noqa: E402
|
||||
|
||||
CANON = dict(K.PATTERNS)
|
||||
P = PE.EXTRA_PATTERNS
|
||||
|
||||
# ---- fixtures (synthetic, valid shapes) ----
|
||||
HEROKU_POS = "token=HRKU-AA" + ("aB3xY7zK9mQ2wR5t" * 4)[:58] + " done"
|
||||
HEROKU_NEG1 = "token=HRKU-BB" + ("aB3xY7zK9mQ2wR5t" * 4)[:58]
|
||||
HEROKU_NEG2 = "token=HRKU-AA" + "aB3xY7zK9m" # too short
|
||||
|
||||
SUPABASE_POS = 'SB_TOKEN="sbp-1234567890abcdef1234567890abcdef12345678"'
|
||||
SUPABASE_NEG1 = "sbp-1234567890abcdef1234567890abcdef1234567" # 39 hex
|
||||
SUPABASE_NEG2 = "sbo-1234567890abcdef1234567890abcdef12345678" # wrong prefix
|
||||
|
||||
CLERK_SHAPE = "sk_" + "live_" + "aB3xY7zK9mQ2wR5tN8vJ1cL4hF6dG0sA"
|
||||
BAIDU_SHAPE = "BCE_AK=bce-v3/ALTAK-aB3xY7zK9m/0123456789abcdef0123456789abcdef"
|
||||
|
||||
SPECS = [
|
||||
("HEROKU", HEROKU_POS, [HEROKU_NEG1, HEROKU_NEG2]),
|
||||
("SUPABASE", SUPABASE_POS, [SUPABASE_NEG1, SUPABASE_NEG2]),
|
||||
]
|
||||
|
||||
for name, pos, negs in SPECS:
|
||||
rx = CANON.get(name)
|
||||
if rx is None:
|
||||
check(f"{name}:in-canon", False, "missing from PATTERNS")
|
||||
continue
|
||||
check(f"{name}:pos", rx.search(pos) is not None, f"no match in {pos[:40]}")
|
||||
for i, neg in enumerate(negs):
|
||||
check(f"{name}:neg{i}", rx.search(neg) is None, f"false match in {neg[:40]}")
|
||||
# shape ownership: exactly one canon entry may match the pos fixture
|
||||
owners = [n for n, r in CANON.items() if r.search(pos)]
|
||||
check(f"{name}:shape-owned-once", owners == [name],
|
||||
f"pos fixture matched by {owners} — one label per key shape")
|
||||
|
||||
# rejected families: no canon label, no extra label
|
||||
check("rejected:CLERK-not-in-canon", "CLERK" not in CANON)
|
||||
check("rejected:clerk-not-in-extras", "clerk" not in P)
|
||||
check("rejected:BAIDU-not-in-canon", "BAIDU" not in CANON)
|
||||
check("rejected:baidu-not-in-extras", "baidu" not in P)
|
||||
# clerk shape must stay owned by STRIPE alone
|
||||
stripe_owners = [n for n, r in CANON.items() if r.search(CLERK_SHAPE)]
|
||||
check("rejected:clerk-shape-stripe-only", "STRIPE" in stripe_owners
|
||||
and "CLERK" not in stripe_owners, f"owners={stripe_owners}")
|
||||
# baidu shape must not emit a credential label from canon batch-2
|
||||
baidu_owners = [n for n, r in CANON.items()
|
||||
if n in ("HEROKU", "SUPABASE") and r.search(BAIDU_SHAPE)]
|
||||
check("rejected:baidu-no-batch2-hit", not baidu_owners, f"owners={baidu_owners}")
|
||||
|
||||
# extras regression: kh_* intact, count floor
|
||||
check("regress:kh_google", "kh_google" in P and
|
||||
P["kh_google"].search("AIzaSy" + "A1234567890abcdefghijklmnopqrstuv"[:33]) is not None)
|
||||
check("regress:kh_grok", "kh_grok" in P and
|
||||
P["kh_grok"].search("xai-" + "aB3x" * 12) is not None)
|
||||
check("regress:count", len(P) >= 17, f"EXTRA_PATTERNS shrank: {len(P)}")
|
||||
|
||||
# scan_text end-to-end (junk-фильтр + first-wins dedup): высокоэнтропийные
|
||||
# нециклические фикстуры должны пройти весь тракт и выдать prov-лейблы.
|
||||
import random as _rnd
|
||||
_r = _rnd.Random(20261006)
|
||||
_hex40 = "".join(_r.choice("0123456789abcdef") for _ in range(40))
|
||||
_alnum58 = "".join(_r.choice("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-") for _ in range(58))
|
||||
SB_E2E = 'SB_TOKEN="sbp-' + _hex40 + '"'
|
||||
HK_E2E = "HEROKU_API_KEY=HRKU-AA" + _alnum58
|
||||
for _fix, _want in ((SB_E2E, "SUPABASE"), (HK_E2E, "HEROKU")):
|
||||
_got = K.scan_text(_fix)
|
||||
_provs = {p for p, _ in _got}
|
||||
check(f"scan_text:{_want}", _want in _provs, f"provs={sorted(_provs)}")
|
||||
_v = [v for p, v in _got if p == _want]
|
||||
check(f"scan_text:{_want}-value", bool(_v) and _v[0] in _fix, f"vals={_v}")
|
||||
|
||||
# coupling gate: batch-2 provs resolvable in eternal_v10 (allowlist or PROVIDERS)
|
||||
import ast as _ast
|
||||
import re as _re
|
||||
_allow, _provs = None, set()
|
||||
for _p in (os.path.join(os.path.dirname(HERE), "src", "eternal_v10.py"),
|
||||
os.path.join(os.path.dirname(HERE), "eternal_v10.py")):
|
||||
if os.path.exists(_p):
|
||||
_src = open(_p, encoding="utf-8").read()
|
||||
_m = _re.search(r"if prov_name in \[([^\]]+)\]", _src)
|
||||
if _m:
|
||||
_allow = {s.strip().strip("'\"") for s in _m.group(1).split(",")}
|
||||
try:
|
||||
for _node in _ast.walk(_ast.parse(_src)):
|
||||
if isinstance(_node, (_ast.Assign, _ast.AnnAssign)):
|
||||
_tgt = _node.targets if isinstance(_node, _ast.Assign) else [_node.target]
|
||||
for _t in _tgt:
|
||||
if (isinstance(_t, _ast.Name) and _t.id == "PROVIDERS"
|
||||
and isinstance(_node.value, _ast.Dict)):
|
||||
for _k in _node.value.keys:
|
||||
if isinstance(_k, _ast.Constant) and isinstance(_k.value, str):
|
||||
_provs.add(_k.value)
|
||||
except SyntaxError:
|
||||
pass
|
||||
break
|
||||
if _allow is None and not _provs:
|
||||
check("coupling:allowlist-found", False, "eternal_v10 allowlist not parsed (no PROVIDERS)")
|
||||
else:
|
||||
for _name in ("HEROKU", "SUPABASE"):
|
||||
_resolved = (_allow and _name in _allow) or _name in _provs
|
||||
check(f"coupling:{_name}-resolvable",
|
||||
_resolved,
|
||||
f"{_name} absent -> UNKNOWN->DEAD destroys finds")
|
||||
|
||||
print()
|
||||
if FAILS:
|
||||
print(f"BATCH2 FAILURES: {len(FAILS)}: {FAILS}")
|
||||
sys.exit(1)
|
||||
print("BATCH2 ALL GREEN")
|
||||
154
tests/test_batch3_patterns.py
Normal file
154
tests/test_batch3_patterns.py
Normal file
@@ -0,0 +1,154 @@
|
||||
"""Batch-3 fixture tests (no network). Run: python tests/test_batch3_patterns.py
|
||||
|
||||
Batch-3 scope:
|
||||
AZURE_STORAGE — connection-string family in canon PATTERNS (deliverable:
|
||||
AccountName+AccountKey self-contained; probe = SharedKey-signed list).
|
||||
HTTPS_AUTH anchoring — diff-probe of 68 targets found 0 real creds;
|
||||
reject placeholder creds ($VAR, admin:admin, echo pairs), doc hosts
|
||||
(localhost/RFC1918/example.*) at parse time, not probe time.
|
||||
PRIVATE_KEY structural validation — GCP-SA lesson (broken PEM fragments,
|
||||
ValueError): base64 body must decode; OPENSSH magic or ASN.1 SEQUENCE.
|
||||
Stdlib only (no openssh_key dep — CVE-2022-31124 in <0.0.6).
|
||||
Coupling gate: AZURE_STORAGE must resolve in eternal_v10 allowlist,
|
||||
else UNKNOWN -> DEAD terminal (KH_AWS graveyard lesson). Static parse only.
|
||||
"""
|
||||
import base64
|
||||
import os
|
||||
import random
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(HERE), "src"))
|
||||
sys.path.insert(0, os.path.dirname(HERE))
|
||||
|
||||
FAILS = []
|
||||
|
||||
|
||||
def check(name, cond, detail=""):
|
||||
if cond:
|
||||
print(f"ok {name}")
|
||||
else:
|
||||
print(f"FAIL {name} {detail}")
|
||||
FAILS.append(name)
|
||||
|
||||
|
||||
import key_patterns as K # noqa: E402
|
||||
|
||||
CANON = dict(K.PATTERNS)
|
||||
_r = random.Random(20261006)
|
||||
_B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
|
||||
|
||||
def _b64(n):
|
||||
return "".join(_r.choice(_B64) for _ in range(n))
|
||||
|
||||
|
||||
# ---- AZURE_STORAGE fixtures ----
|
||||
_AZB64 = _b64(86) + "==" # 86 b64 chars + padding = 64-byte key
|
||||
AZ_POS = ("DefaultEndpointsProtocol=https;AccountName=acmefarm01;"
|
||||
"AccountKey=" + _AZB64 + ";EndpointSuffix=core.windows.net")
|
||||
AZ_NEG1 = ("DefaultEndpointsProtocol=https;AccountName=acmefarm01;"
|
||||
"AccountKey=" + _b64(85) + "==;EndpointSuffix=core.windows.net") # 85 short
|
||||
AZ_NEG2 = "AccountName=acmefarm01;AccountKey=" + _AZB64 # no protocol anchor
|
||||
AZ_NEG3 = ("DefaultEndpointsProtocol=http;AccountName=acmefarm01;"
|
||||
"AccountKey=" + _AZB64 + ";EndpointSuffix=core.windows.net") # http not https
|
||||
|
||||
# ---- HTTPS_AUTH anchoring fixtures ----
|
||||
HA_POS = "https://artifactory_reader:Kj8xQ2mZvR4tW9yB@acme.jfrog.io/artifactory/api"
|
||||
HA_JUNK_ECHO = "https://admin:admin@files.acme-cdn.io/dist.zip"
|
||||
HA_JUNK_PH = "https://user:password@nexus.build.corp/repo"
|
||||
HA_JUNK_VAR = "https://${REG_USER}:${REG_TOKEN}@registry-1.docker.io/v2/"
|
||||
HA_JUNK_LOCAL = "https://deploy:Kj8xQ2mZvR4tW9yB@localhost:8443/admin"
|
||||
HA_JUNK_RFC1918 = "https://ci:Kj8xQ2mZvR4tW9yB@192.168.1.50/pkg"
|
||||
HA_JUNK_EXAMPLE = "https://ci:Kj8xQ2mZvR4tW9yB@app.example.org/api"
|
||||
|
||||
# ---- PRIVATE_KEY structural fixtures ----
|
||||
def _pem(label, body_bytes):
|
||||
b64 = base64.b64encode(body_bytes).decode()
|
||||
lines = [b64[i:i + 64] for i in range(0, len(b64), 64)]
|
||||
return (f"-----BEGIN {label} PRIVATE KEY-----\n" + "\n".join(lines) +
|
||||
f"\n-----END {label} PRIVATE KEY-----")
|
||||
|
||||
PK_POS_OPENSSH = _pem("OPENSSH", b"openssh-key-v1\x00" + bytes(range(1, 40)))
|
||||
PK_POS_RSA = _pem("RSA", bytes([0x30, 0x82, 0x01, 0x22]) + bytes(range(2, 44)))
|
||||
PK_NEG_NOMAGIC = _pem("OPENSSH", b"garbage-not-a-key-at-all" + bytes(range(9)))
|
||||
PK_NEG_ENCRYPTED = _pem("ENCRYPTED", bytes([0x30, 0x82, 0x01, 0x22]) + bytes(range(2, 44)))
|
||||
PK_NEG_B64 = ("-----BEGIN RSA PRIVATE KEY-----\n" + "!" * 60 +
|
||||
"\n-----END RSA PRIVATE KEY-----")
|
||||
|
||||
# ================= pattern-level =================
|
||||
rx_az = CANON.get("AZURE_STORAGE")
|
||||
check("AZURE_STORAGE:in-canon", rx_az is not None, "missing from PATTERNS")
|
||||
if rx_az:
|
||||
check("AZURE_STORAGE:pos", rx_az.search(AZ_POS) is not None)
|
||||
check("AZURE_STORAGE:neg1-short-key", rx_az.search(AZ_NEG1) is None)
|
||||
check("AZURE_STORAGE:neg2-no-protocol", rx_az.search(AZ_NEG2) is None)
|
||||
check("AZURE_STORAGE:neg3-http", rx_az.search(AZ_NEG3) is None)
|
||||
owners = [n for n, r in CANON.items() if r.search(AZ_POS)]
|
||||
check("AZURE_STORAGE:shape-owned-once", owners == ["AZURE_STORAGE"],
|
||||
f"owners={owners}")
|
||||
m = rx_az.search(AZ_POS)
|
||||
val = m.group(0)
|
||||
check("AZURE_STORAGE:value-self-contained",
|
||||
"AccountName=" in val and "AccountKey=" in val, f"val={val[:60]}")
|
||||
|
||||
# ================= scan_text end-to-end =================
|
||||
got = {p for p, _ in K.scan_text(AZ_POS)}
|
||||
check("scan_text:AZURE_STORAGE", "AZURE_STORAGE" in got, f"provs={sorted(got)}")
|
||||
|
||||
# HTTPS_AUTH: pos survives, junk shapes rejected
|
||||
hp = [v for p, v in K.scan_text(HA_POS) if p == "HTTPS_AUTH"]
|
||||
check("https_auth:pos-emitted", bool(hp), f"scan={[p for p,_ in K.scan_text(HA_POS)]}")
|
||||
for nm, fix in (("echo", HA_JUNK_ECHO), ("placeholder", HA_JUNK_PH),
|
||||
("var-template", HA_JUNK_VAR), ("localhost", HA_JUNK_LOCAL),
|
||||
("rfc1918", HA_JUNK_RFC1918), ("example-host", HA_JUNK_EXAMPLE)):
|
||||
hj = [v for p, v in K.scan_text(fix) if p == "HTTPS_AUTH"]
|
||||
check(f"https_auth:junk-{nm}", not hj, f"emitted {hj[:1]}")
|
||||
|
||||
# PRIVATE_KEY: structurally valid survive, broken rejected
|
||||
for nm, fix in (("openssh", PK_POS_OPENSSH), ("rsa", PK_POS_RSA)):
|
||||
pk = [v for p, v in K.scan_text(fix) if p == "PRIVATE_KEY"]
|
||||
check(f"private_key:pos-{nm}", bool(pk), f"scan={[p for p,_ in K.scan_text(fix)]}")
|
||||
for nm, fix in (("no-magic", PK_NEG_NOMAGIC), ("bad-b64", PK_NEG_B64),
|
||||
("encrypted-label", PK_NEG_ENCRYPTED)):
|
||||
pk = [v for p, v in K.scan_text(fix) if p == "PRIVATE_KEY"]
|
||||
check(f"private_key:neg-{nm}", not pk, f"emitted len={len(pk)}")
|
||||
|
||||
# ================= coupling gate =================
|
||||
import ast as _ast
|
||||
import re as _re
|
||||
_allow, _provs = None, set()
|
||||
for _p in (os.path.join(os.path.dirname(HERE), "src", "eternal_v10.py"),
|
||||
os.path.join(os.path.dirname(HERE), "eternal_v10.py")):
|
||||
if os.path.exists(_p):
|
||||
_src = open(_p, encoding="utf-8").read()
|
||||
_m = _re.search(r"if prov_name in \[([^\]]+)\]", _src)
|
||||
if _m:
|
||||
_allow = {s.strip().strip("'\"") for s in _m.group(1).split(",")}
|
||||
try:
|
||||
for _node in _ast.walk(_ast.parse(_src)):
|
||||
if isinstance(_node, (_ast.Assign, _ast.AnnAssign)):
|
||||
_tgt = _node.targets if isinstance(_node, _ast.Assign) else [_node.target]
|
||||
for _t in _tgt:
|
||||
if (isinstance(_t, _ast.Name) and _t.id == "PROVIDERS"
|
||||
and isinstance(_node.value, _ast.Dict)):
|
||||
for _k in _node.value.keys:
|
||||
if isinstance(_k, _ast.Constant) and isinstance(_k.value, str):
|
||||
_provs.add(_k.value)
|
||||
except SyntaxError:
|
||||
pass
|
||||
break
|
||||
if _allow is None and not _provs:
|
||||
check("coupling:allowlist-found", False, "eternal_v10 allowlist not parsed (no PROVIDERS)")
|
||||
else:
|
||||
for _name in ("AZURE_STORAGE", "PRIVATE_KEY"):
|
||||
_resolved = (_allow and _name in _allow) or _name in _provs
|
||||
check(f"coupling:{_name}-resolvable",
|
||||
_resolved,
|
||||
f"{_name} absent -> UNKNOWN->DEAD destroys finds")
|
||||
|
||||
print()
|
||||
if FAILS:
|
||||
print(f"BATCH3 FAILURES: {len(FAILS)}: {FAILS}")
|
||||
sys.exit(1)
|
||||
print("BATCH3 ALL GREEN")
|
||||
37
tests/test_gh_realtime.py
Normal file
37
tests/test_gh_realtime.py
Normal file
@@ -0,0 +1,37 @@
|
||||
"""Fixtures for gh_realtime_eternal: diff scanning, junk filter, db dedupe."""
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
os.environ["KF_DB"] = os.path.join(tempfile.mkdtemp(prefix="kf_smoke_ghrt_"), "keys.db")
|
||||
HERE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, HERE)
|
||||
import gh_realtime_eternal as G
|
||||
|
||||
DS = "sk-3f9a1c7e5b2d8460a1c3e5f7091b2d4c"
|
||||
DIFF = f"""diff --git a/.env b/.env
|
||||
index 111..222 100644
|
||||
--- a/.env
|
||||
+++ b/.env
|
||||
@@ -1,2 +1,3 @@
|
||||
-OLD=1
|
||||
+DASHSCOPE_API_KEY={DS}
|
||||
+JUNK=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
"""
|
||||
|
||||
def main():
|
||||
n = G.scan_diff(DIFF, "owner/repo")
|
||||
assert n >= 1, f"expected >=1 key inserted, got {n}"
|
||||
rows = G.conn.execute("SELECT val, prov, found, repo FROM keys").fetchall()
|
||||
assert any(r[0] == DS and r[2] == "ghrealtime" and r[3] == "owner/repo" for r in rows), rows
|
||||
assert not any("xxxx" in r[0] for r in rows), "junk leaked"
|
||||
# dedupe: rescan same diff -> 0 new (rowcount 0 on conflict-no-update)
|
||||
n2 = G.scan_diff(DIFF, "owner/repo")
|
||||
assert n2 == 0, f"dedupe failed: {n2}"
|
||||
# only added lines scanned: '-' removals must not yield keys
|
||||
rem = f"--- a/x\n+++ b/x\n@@ -1 +1 @@\n-{DS}\n+nothing=1\n"
|
||||
assert G.scan_diff(rem, "o/r2") == 0, "scanned removed lines"
|
||||
print(f"PASS: scan_diff found {n}, dedupe ok, junk filtered, removals ignored")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
411
tests/test_key_patterns.py
Normal file
411
tests/test_key_patterns.py
Normal file
@@ -0,0 +1,411 @@
|
||||
"""Fixture tests for the merged key_patterns module (no network).
|
||||
|
||||
Run: python tests/test_key_patterns.py (exit 1 on any failure)
|
||||
|
||||
Covers both public APIs that exist in the two trees:
|
||||
root lanes -> PATTERNS / scan_text
|
||||
src modules -> KEY_PATTERNS / ENV_RE / KIMI_PATTERN / extract_all_keys
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, os.path.dirname(HERE))
|
||||
import key_patterns as K # noqa: E402
|
||||
|
||||
DS = "sk-3f9a1c7e5b2d8460a1c3e5f7091b2d4c" # 32 hex, high entropy
|
||||
DS2 = "sk-7d2f4a6b8c0e1d3f5a7b9c1e3d5f7a9b"
|
||||
KIMI = "sk-" + "Qw3rTy7uIo9pAs2dFg6hJk1lZx5cVb8nMq4wEr0tYu3iOp1a" # 48 alnum
|
||||
ZAI = "9c1f7a3e5b8d2046f1a3c5e7b9d1f3a5.RtY7uI2oP4aS6dF8"
|
||||
LOW_ENTROPY = "sk-aabbccddeeff00112233445566778899" # passes _is_junk today
|
||||
CODING_PLAN = "sk-sp-Kf83Jd92Ls04Mx71Qw63Er25Ty88Ua40"
|
||||
|
||||
FAILS = []
|
||||
|
||||
|
||||
def check(name, cond, detail=""):
|
||||
if cond:
|
||||
print(f"ok {name}")
|
||||
else:
|
||||
print(f"FAIL {name} {detail}")
|
||||
FAILS.append(name)
|
||||
|
||||
|
||||
def providers(text, fn):
|
||||
return {p for p, _ in fn(text)}
|
||||
|
||||
|
||||
def values(text, fn):
|
||||
return {k for _, k in fn(text)}
|
||||
|
||||
|
||||
def test_both_apis_exist():
|
||||
check("api.PATTERNS", isinstance(K.PATTERNS, list) and len(K.PATTERNS) > 100,
|
||||
f"len={len(getattr(K, 'PATTERNS', []))}")
|
||||
check("api.scan_text", callable(getattr(K, "scan_text", None)))
|
||||
check("api.KEY_PATTERNS", isinstance(getattr(K, "KEY_PATTERNS", None), dict)
|
||||
and len(K.KEY_PATTERNS) > 50, f"len={len(getattr(K,'KEY_PATTERNS',[]) or [])}")
|
||||
check("api.extract_all_keys", callable(getattr(K, "extract_all_keys", None)))
|
||||
check("api.ENV_RE", getattr(K, "ENV_RE", None) is not None)
|
||||
check("api.KIMI_PATTERN", getattr(K, "KIMI_PATTERN", None) is not None)
|
||||
|
||||
|
||||
def test_dashscope_env_context():
|
||||
text = f"# .env\nDASHSCOPE_API_KEY={DS}\nOTHER=1\n"
|
||||
check("dashscope.env.scan_text", DS in values(text, K.scan_text),
|
||||
str(sorted(values(text, K.scan_text)))[:120])
|
||||
check("dashscope.env.extract_all", DS in values(text, K.extract_all_keys))
|
||||
check("dashscope.env.prov", "DASHSCOPE" in providers(text, K.scan_text))
|
||||
|
||||
|
||||
def test_dashscope_bare():
|
||||
text = '{"model":"qwen","api_key":"%s"}' % DS2
|
||||
check("dashscope.bare.scan_text", DS2 in values(text, K.scan_text))
|
||||
check("dashscope.bare.extract_all", DS2 in values(text, K.extract_all_keys))
|
||||
|
||||
|
||||
def test_placeholder_and_junk_dropped():
|
||||
text = ("DASHSCOPE_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"
|
||||
"KIMI_API_KEY=sk-your_api_key_here_1234567890abcdefghij\n")
|
||||
v = values(text, K.scan_text) | values(text, K.extract_all_keys)
|
||||
check("junk.placeholder_dropped", not any("xxxx" in x for x in v), str(v)[:120])
|
||||
check("junk.your_key_dropped", not any("your_api_key" in x for x in v))
|
||||
|
||||
|
||||
def test_low_entropy_dropped():
|
||||
text = "DASHSCOPE_API_KEY=%s" % LOW_ENTROPY
|
||||
v = values(text, K.scan_text) | values(text, K.extract_all_keys)
|
||||
check("template.pair_repeat_dropped", LOW_ENTROPY not in v,
|
||||
"low-entropy key accepted (need Shannon prefilter)")
|
||||
|
||||
|
||||
def test_coding_plan_pattern():
|
||||
text = '{"coding_plan_key": "%s"}' % CODING_PLAN
|
||||
v = values(text, K.scan_text) | values(text, K.extract_all_keys)
|
||||
check("pattern.sk-sp_coding_plan", CODING_PLAN in v,
|
||||
"sk-sp- Coding Plan shape not matched")
|
||||
|
||||
|
||||
def test_kimi_and_zai():
|
||||
text = "KIMI_API_KEY=%s\nZHIPU_API_KEY=%s\n" % (KIMI, ZAI)
|
||||
v = values(text, K.scan_text) | values(text, K.extract_all_keys)
|
||||
check("kimi.51char_found", KIMI in v)
|
||||
check("zai.dot_form_found", ZAI in v)
|
||||
|
||||
|
||||
def test_grouped_patterns_do_not_crash():
|
||||
"""A PATTERNS entry with a capture group makes findall() yield tuples;
|
||||
scan_text must flatten them instead of raising on .strip()."""
|
||||
grouped = [(p, rx) for p, rx in K.PATTERNS if getattr(rx, "groups", 0)]
|
||||
if not grouped:
|
||||
print("skip grouped_patterns (no grouped pattern in PATTERNS)")
|
||||
return
|
||||
prov, rx = grouped[0]
|
||||
sample = None
|
||||
for cand in (
|
||||
'SUNO_API_KEY = "sk-Ab3xK9mQ2wE5rT7yU1iO"',
|
||||
'token = "pk-lf-Ab3xK9mQ2wE5rT7yU1iO4pA8sD2fG6hJ"',
|
||||
'sk-proj-Ab3xK9mQ2wE5rT7yU1iO4pA8sD2fG6hJk0Lz9Xc8Vb7Nm6Qa5Ws4Ed3Rf2Tg1Yh0',
|
||||
):
|
||||
try:
|
||||
if rx.search(cand):
|
||||
sample = cand
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
if sample is None:
|
||||
print(f"skip grouped_patterns fixture (no sample matched {prov})")
|
||||
return
|
||||
try:
|
||||
out = K.scan_text(sample)
|
||||
ok = all(isinstance(k, str) for _, k in out)
|
||||
check("grouped.no_crash_and_str_keys", ok, str(out)[:120])
|
||||
except Exception as e:
|
||||
check("grouped.no_crash_and_str_keys", False, f"{type(e).__name__}: {e}")
|
||||
|
||||
|
||||
def test_lowercase_env_attribution():
|
||||
"""IGNORECASE ENV_RE must still attribute the provider, not 'ENV'."""
|
||||
text = "dashscope_api_token=Ab3xK9mQ2wE5rT7yU1iO4pA8sD2fG6hJzX7cV9bN\n"
|
||||
hits = K.scan_text(text)
|
||||
provs = {p for p, _ in hits}
|
||||
check("env.lowercase_provider_not_ENV", "DASHSCOPE" in provs,
|
||||
str(hits)[:120])
|
||||
check("env.lowercase_no_bare_ENV", "ENV" not in provs, str(hits)[:120])
|
||||
|
||||
|
||||
def test_key_patterns_dict_coverage():
|
||||
"""KEY_PATTERNS[prov] must match EVERY alternative of that provider."""
|
||||
check("dict.DASHSCOPE_coding_plan",
|
||||
bool(K.KEY_PATTERNS["DASHSCOPE"].search(CODING_PLAN)))
|
||||
check("dict.DASHSCOPE_hex32", bool(K.KEY_PATTERNS["DASHSCOPE"].search(DS)))
|
||||
kimi_rx = K.KEY_PATTERNS.get("KIMI")
|
||||
check("dict.KIMI_narrow48", kimi_rx is not None
|
||||
and bool(kimi_rx.fullmatch("sk-" + "aB3xK9mQ2wE5rT7yU1iO4pA8sD2fG6hJzX7cV9bNmN2qR4tY"))
|
||||
and not kimi_rx.search("sk-short123"))
|
||||
|
||||
|
||||
def test_one_pair_per_value():
|
||||
"""A value matching two same-shape providers (KIMI/SILICONFLOW sk-48)
|
||||
must come back exactly once — first (specific-first) provider wins."""
|
||||
val = "sk-" + "aB3xK9mQ2wE5rT7yU1iO4pA8sD2fG6hJzX7cV9bNmN2qR4tY"
|
||||
hits = K.scan_text("SOME_CONTEXT '%s'" % val)
|
||||
same = [h for h in hits if h[1] == val]
|
||||
check("dedup.one_pair_per_value", len(same) == 1, str(hits)[:140])
|
||||
|
||||
|
||||
# ── v6.4 placeholder junk fixture tests ─────────────────────────────
|
||||
|
||||
NPM_PLACEHOLDER = "npm_XXXX_XXXX_XXXX_XXXX_XXXX"
|
||||
INSERT_YOUR = "ghp_INSERT_YOUR_TOKEN_HERE_1234567890abcdef"
|
||||
SK_UPPERCASE_X = "sk-" + "X" * 32 # 32 uppercase X
|
||||
REAL_SK = "sk-3f9a1c7e5b2d8460a1c3e5f7091b2d4c" # 32 hex, same as DS
|
||||
REAL_GHP = "ghp_" + "aB3xK9mQ2wE5rT7yU1iO4pA8sD2fG6hJzX7cV9bN" # valid shape
|
||||
|
||||
|
||||
def test_npm_placeholder_rejected():
|
||||
"""npm_XXXX_XXXX_... must be rejected by _is_junk (v6.4)."""
|
||||
text = f'NPM_TOKEN={NPM_PLACEHOLDER}\n'
|
||||
v = values(text, K.scan_text) | values(text, K.extract_all_keys)
|
||||
check("junk.npm_placeholder_rejected",
|
||||
NPM_PLACEHOLDER not in v, str(v)[:120])
|
||||
|
||||
|
||||
def test_insert_your_token_rejected():
|
||||
"""INSERT_YOUR_TOKEN_HERE must be rejected by _is_junk (v6.4)."""
|
||||
text = f'GITHUB_TOKEN={INSERT_YOUR}\n'
|
||||
v = values(text, K.scan_text) | values(text, K.extract_all_keys)
|
||||
check("junk.insert_your_token_rejected",
|
||||
INSERT_YOUR not in v, str(v)[:120])
|
||||
|
||||
|
||||
def test_sk_uppercase_x_rejected():
|
||||
"""sk-XXXX... (all uppercase X) must be rejected by _is_junk (v6.4)."""
|
||||
text = f'DASHSCOPE_API_KEY={SK_UPPERCASE_X}\n'
|
||||
v = values(text, K.scan_text) | values(text, K.extract_all_keys)
|
||||
check("junk.sk_uppercase_x_rejected",
|
||||
SK_UPPERCASE_X not in v, str(v)[:120])
|
||||
|
||||
|
||||
def test_real_sk_accepted():
|
||||
"""Real sk-<32hex> key must NOT be rejected."""
|
||||
text = f'DASHSCOPE_API_KEY={REAL_SK}\n'
|
||||
v = values(text, K.scan_text) | values(text, K.extract_all_keys)
|
||||
check("real.sk_accepted", REAL_SK in v, str(v)[:120])
|
||||
|
||||
|
||||
def test_real_ghp_accepted():
|
||||
"""Real ghp_ token must NOT be rejected."""
|
||||
text = f'GITHUB_TOKEN={REAL_GHP}\n'
|
||||
v = values(text, K.scan_text) | values(text, K.extract_all_keys)
|
||||
check("real.ghp_accepted", REAL_GHP in v, str(v)[:120])
|
||||
|
||||
|
||||
import hashlib as _hl
|
||||
|
||||
def _filler(seed, n, alpha="0123456789abcdef"):
|
||||
out = ""
|
||||
i = 0
|
||||
while len(out) < n:
|
||||
out += _hl.sha256((seed + str(i)).encode()).hexdigest()
|
||||
i += 1
|
||||
return "".join(ch for ch in out if ch in alpha)[:n]
|
||||
|
||||
_BECH32 = "QPZRY9X8GF2TVDW0S3JN54KHCE6MUA7L"
|
||||
def _bech_filler(n):
|
||||
dg = b""
|
||||
i = 0
|
||||
while len(dg) < n:
|
||||
dg += _hl.sha256(b"age" + str(i).encode()).digest(); i += 1
|
||||
return "".join(_BECH32[b % 32] for b in dg[:n])
|
||||
|
||||
NEW_PAT_FIXTURES = [
|
||||
("GROK", "xai-" + _filler("xai", 40, "0123456789abcdefghijklmnopqrstuvwxyz") + "_" + _filler("xai2", 39, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")),
|
||||
("LANGSMITH", "lsv2_pt_" + _filler("lsp1", 32) + "_" + _filler("lsp2", 10)),
|
||||
("OPENAI_ADMIN", "sk-admin-" + _filler("oa1", 58, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-") + "T3BlbkFJ" + _filler("oa2", 58, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-")),
|
||||
("DATABRICKS", "dapi" + _filler("db", 32)),
|
||||
("DOPPLER", "dp.pt." + _filler("dp", 43, "0123456789abcdefghijklmnopqrstuvwxyz")),
|
||||
("ARTIFACTORY", "AKCp" + _filler("art", 69, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")),
|
||||
("ALIBABA_LTAI", "LTAI" + _filler("ltai", 20, "0123456789abcdefghijklmnopqrstuvwxyz")),
|
||||
("AGE_KEY", "AGE-SECRET-KEY-1" + _bech_filler(58)),
|
||||
("ONEPASSWORD", "A3-7K2M9X-4Q8W2E7R1T5-B3N6M-K9P2Q-7X4Z8"),
|
||||
("AZURE_AD", "k7Q" + "4Q~" + _filler("az", 32, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_~.-")),
|
||||
]
|
||||
|
||||
|
||||
def test_v65_merged_patterns():
|
||||
"""v6.5: 10 rules merged from gitleaks v8 + trufflehog detectors."""
|
||||
for prov, sample in NEW_PAT_FIXTURES:
|
||||
text = "export SOME_CTX='%s'\n" % sample
|
||||
v = values(text, K.scan_text) | values(text, K.extract_all_keys)
|
||||
p = providers(text, K.scan_text)
|
||||
check("v65." + prov, sample in v and prov in p,
|
||||
"in_values=%s provs=%s" % (sample in v, sorted(p)[:4]))
|
||||
|
||||
|
||||
def test_elevenlabs_boundary():
|
||||
"""Boundary: XI_API_KEY matches, PERPLEXITY/complexity/maxi_token do not."""
|
||||
import hashlib, string
|
||||
def _body(seed, n, alpha=string.ascii_letters + string.digits):
|
||||
out, i = '', 0
|
||||
while len(out) < n:
|
||||
for b in hashlib.sha256(f'{seed}:{i}'.encode()).digest():
|
||||
out += alpha[b % len(alpha)]
|
||||
if len(out) == n: return out
|
||||
i += 1
|
||||
|
||||
h32 = _body('el', 32, '0123456789abcdef') # pure hex for xi key
|
||||
pplx = _body('pplx', 56, string.ascii_lowercase + string.digits) # exactly 56 lowercase alnum
|
||||
|
||||
# Positive: XI_API_KEY=<hex32> → ELEVENLABS
|
||||
pos_txt = f"XI_API_KEY={h32}"
|
||||
check("EL.xi.positive", any(
|
||||
prov == 'ELEVENLABS' for prov, key in K.scan_text(pos_txt)
|
||||
), f"XI_API_KEY={h32[:6]}...")
|
||||
|
||||
# Negative: PERPLEXITY_API_KEY=<32 hex> → not ELEVENLABS
|
||||
neg1 = f"PERPLEXITY_API_KEY={h32}"
|
||||
el_hits = [prov for prov, key in K.scan_text(neg1) if prov == 'ELEVENLABS']
|
||||
check("EL.perplexity.not_xi", len(el_hits) == 0,
|
||||
f"Got ELEVENLABS hits on PERPLEXITY: {el_hits}")
|
||||
|
||||
# Negative: pplx-<56 lnum> → PERPLEXITY, not ELEVENLABS
|
||||
ppx_txt = f"PERPLEXITY_API_KEY=pplx-{pplx}"
|
||||
hits = list(K.scan_text(ppx_txt))
|
||||
provs = {p for p, _ in hits}
|
||||
check("EL.pplx.is_perplexity", 'PERPLEXITY' in provs,
|
||||
f"PERPLEXITY not in {provs}")
|
||||
check("EL.pplx.not_elevenlabs", 'ELEVENLABS' not in provs,
|
||||
f"ELEVENLABS falsely detected: {provs}")
|
||||
|
||||
# Negative: word-internal xi → no match
|
||||
for word in ('complexity', 'maxi_token'):
|
||||
neg_txt = f"{word}: {h32}"
|
||||
hits = [prov for _, prov, _ in K.scan_text(neg_txt) if prov == 'ELEVENLABS']
|
||||
check(f"EL.{word}.not_xi", len(hits) == 0,
|
||||
f"FP on '{word}': {hits}")
|
||||
|
||||
# Drift guard: key_patterns ELEVENLABS regex == eternal_v10 PROVIDERS regex
|
||||
# Textual grep avoids importing eternal_v10 (which shells out to gh, connects sockets, etc.)
|
||||
EV10 = os.path.join(os.path.dirname(HERE), 'src', 'eternal_v10.py')
|
||||
kp_pat = dict(K.PATTERNS)['ELEVENLABS'].pattern
|
||||
if not os.path.exists(EV10):
|
||||
check("EL.drift_guard", True, f"skip: no {EV10}")
|
||||
else:
|
||||
# Extract the ELEVENLABS regex string from eternal_v10 by finding the
|
||||
# block line-by-line, avoiding the nested-quote escaping problem.
|
||||
lines = open(EV10, encoding='utf-8').readlines()
|
||||
ev_pat = None
|
||||
for i, line in enumerate(lines):
|
||||
if '"ELEVENLABS"' not in line:
|
||||
continue
|
||||
for j in range(i, min(i + 30, len(lines))):
|
||||
m = re.search(r'_re\(\s*r[\'"](.+?)[\'"]\s*,?\s*$', lines[j])
|
||||
if m:
|
||||
ev_pat = m.group(1)
|
||||
break
|
||||
break
|
||||
if ev_pat is None:
|
||||
check("EL.drift_guard", False, "could not extract PROVIDERS['ELEVENLABS']['regex']")
|
||||
else:
|
||||
check("EL.drift_guard", ev_pat == kp_pat,
|
||||
f"PATTERNS={kp_pat!r} PROVIDERS={ev_pat!r}")
|
||||
|
||||
|
||||
def test_dashscope_ws_extraction():
|
||||
"""sk-ws- keys (115-200 chars, dots/dashes) → DASHSCOPE (v11.1)."""
|
||||
import hashlib
|
||||
def _ws_body(n):
|
||||
out, i = '', 0
|
||||
alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_."
|
||||
while len(out) < n:
|
||||
for b in hashlib.sha256(f'ws:{i}'.encode()).digest():
|
||||
out += alpha[b % len(alpha)]
|
||||
if len(out) == n: return out
|
||||
i += 1
|
||||
|
||||
ws_body = _ws_body(108) # 108 chars after sk-ws- prefix (total 115)
|
||||
ws_key = f"sk-ws-{ws_body}"
|
||||
text = f"DASHSCOPE_API_KEY={ws_key}"
|
||||
hits = list(K.scan_text(text))
|
||||
provs = {p for p, _ in hits}
|
||||
vals = {k for _, k in hits}
|
||||
check("DS.ws.extracted", ws_key in vals,
|
||||
f"sk-ws- key not in values, got: {sorted(vals)}")
|
||||
check("DS.ws.attributed", 'DASHSCOPE' in provs,
|
||||
f"DASHSCOPE not in providers, got: {provs}")
|
||||
|
||||
|
||||
def test_dashscope_long_hex_not_truncated():
|
||||
"""40-hex sk- key must NOT be truncated to 32 chars (v11.1 boundary)."""
|
||||
import hashlib, string
|
||||
def _body(seed, n, alpha):
|
||||
out, i = '', 0
|
||||
while len(out) < n:
|
||||
for b in hashlib.sha256(f'{seed}:{i}'.encode()).digest():
|
||||
out += alpha[b % len(alpha)]
|
||||
if len(out) == n: return out
|
||||
i += 1
|
||||
|
||||
long_hex = _body('ds40', 40, '0123456789abcdef')
|
||||
full_key = f"sk-{long_hex}"
|
||||
assert len(full_key) == 43, f"expected 43, got {len(full_key)}" # sk- + 40 hex
|
||||
text = f"DASHSCOPE_API_KEY={full_key}"
|
||||
|
||||
hits = list(K.scan_text(text))
|
||||
vals = {k for _, k in hits}
|
||||
|
||||
# The full 40-hex key should NOT appear (doesn't match 32-hex pattern)
|
||||
# BUT it should NOT produce a truncated 32-char fake
|
||||
truncated = full_key[:35] # sk- + 32 hex
|
||||
fake_hits = [v for v in vals if len(v) == 35 and v.startswith('sk-')]
|
||||
check("DS.notrunc.no32fake", len(fake_hits) == 0,
|
||||
f"Truncated 32-hex fake extracted: {fake_hits}")
|
||||
|
||||
# sk-ws- pattern should NOT match (this is hex, not ws format)
|
||||
ws_hits = [v for v in vals if v.startswith('sk-ws-')]
|
||||
check("DS.notrunc.no_ws_misattrib", len(ws_hits) == 0,
|
||||
f"sk-ws- misattributed on hex key: {ws_hits}")
|
||||
|
||||
# Known limitation: 40-hex key not captured by any DASHSCOPE alt
|
||||
# (newer shapes >32 hex need a separate probe path via endpoint auto-detection)
|
||||
check("DS.notrunc.accepted_limitation", len(vals) == 0,
|
||||
f"40-hex key unexpectedly matched: {vals} (known: >32 hex needs probe path)")
|
||||
|
||||
|
||||
def main():
|
||||
for fn in (test_both_apis_exist, test_dashscope_env_context,
|
||||
test_dashscope_bare, test_placeholder_and_junk_dropped,
|
||||
test_low_entropy_dropped, test_coding_plan_pattern,
|
||||
test_kimi_and_zai, test_grouped_patterns_do_not_crash,
|
||||
test_lowercase_env_attribution,
|
||||
test_key_patterns_dict_coverage,
|
||||
test_one_pair_per_value,
|
||||
test_npm_placeholder_rejected,
|
||||
test_insert_your_token_rejected,
|
||||
test_sk_uppercase_x_rejected,
|
||||
test_real_sk_accepted,
|
||||
test_real_ghp_accepted,
|
||||
test_v65_merged_patterns,
|
||||
test_elevenlabs_boundary,
|
||||
test_dashscope_ws_extraction,
|
||||
test_dashscope_long_hex_not_truncated):
|
||||
fn()
|
||||
_nextra = len(getattr(K, 'EXTRA_PATTERNS', {}))
|
||||
print(f"\nPATTERNS={len(K.PATTERNS)} KEY_PATTERNS={len(K.KEY_PATTERNS)} "
|
||||
f"EXTRA={_nextra}")
|
||||
# loud gate: пустой EXTRA-hook (нет patterns_extra.py рядом с key_patterns)
|
||||
# НЕ должен проходить молча — batch-2 и kh_* семьи исчезнут из lanes.
|
||||
check("extras-loaded", _nextra >= 17,
|
||||
f"EXTRA_PATTERNS={_nextra} — patterns_extra.py не загружается рядом с этим деревом")
|
||||
if FAILS:
|
||||
print(f"RED: {len(FAILS)} failing -> {FAILS}")
|
||||
return 1
|
||||
|
||||
print("GREEN: all key_patterns fixture tests passed")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
278
tests/test_probe_authblind.py
Normal file
278
tests/test_probe_authblind.py
Normal file
@@ -0,0 +1,278 @@
|
||||
"""Regression gate: /models-catalog 200 must never mean WORKING without key
|
||||
attribution (garbage-key negative control). CDN edges serve byte-identical
|
||||
cached public catalogs (observed: beijing models=519 for 161 unrelated keys).
|
||||
Offline: all HTTP faked. Run: python tests/test_probe_authblind.py
|
||||
DB-level contract: an authblind verdict DEMOTES a stale WORKING row to NEW
|
||||
(the insert path only demotes DEAD/ERR, so the branch must write), leaves an
|
||||
already-NEW row untouched, and never writes DEAD/WORKING/LIMITED (LIMITED is
|
||||
terminal for non-ZAI/KIMI families per revalidate_scanner.py). Rows are seeded
|
||||
as WORKING so the demotion assertion is not vacuous; paired reject->DEAD
|
||||
controls prove the write path is live.
|
||||
"""
|
||||
import os, sys, tempfile
|
||||
_HAS_E = False
|
||||
_fd, _TMPDB = tempfile.mkstemp(prefix="kf_authblind_", suffix=".db")
|
||||
os.close(_fd)
|
||||
os.remove(_TMPDB) # sqlite recreates; unique per run = no lock clash
|
||||
os.environ["KF_DB"] = _TMPDB # explicit, BEFORE any hunt-module import
|
||||
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT = os.path.dirname(_HERE) if os.path.basename(_HERE) == "tests" else _HERE
|
||||
sys.path.insert(0, ROOT)
|
||||
sys.path.insert(0, os.path.join(ROOT, "src"))
|
||||
|
||||
# bootstrap: DH/UH import builds PATPool from data/gh_tokens.txt — tree may lack
|
||||
# it (same idiom as test_universal_hunt.py). Dummy file, removed at exit.
|
||||
_DATA = os.path.join(ROOT, "data")
|
||||
os.makedirs(_DATA, exist_ok=True)
|
||||
_TOK = os.path.join(_DATA, "gh_tokens.txt")
|
||||
_CREATED_DUMMY = False
|
||||
if not os.path.exists(_TOK) or os.path.getsize(_TOK) == 0:
|
||||
with open(_TOK, "w") as _f:
|
||||
_f.write("ghp_" + "DUMMYFORTESTS" * 3 + "\n")
|
||||
_CREATED_DUMMY = True
|
||||
import atexit
|
||||
def _cleanup():
|
||||
if _CREATED_DUMMY and os.path.exists(_TOK):
|
||||
os.remove(_TOK)
|
||||
for suffix in ("", "-wal", "-shm"):
|
||||
try:
|
||||
os.remove(_TMPDB + suffix)
|
||||
except OSError:
|
||||
pass
|
||||
atexit.register(_cleanup)
|
||||
|
||||
GARB = "PLACEHOLDER-reassigned-after-import"
|
||||
CATALOG = {"data": [{"id": f"m{i}"} for i in range(11)],
|
||||
"models": [{"name": f"m{i}"} for i in range(11)]}
|
||||
|
||||
class FakeResp:
|
||||
def __init__(self, code, payload=None, text=""):
|
||||
self.status_code = code
|
||||
self.text = text or ("" if payload is None else "json")
|
||||
self._p = payload
|
||||
def json(self):
|
||||
if self._p is None:
|
||||
raise ValueError("no json")
|
||||
return self._p
|
||||
|
||||
FAILS = []
|
||||
def check(name, cond, got=None):
|
||||
if cond:
|
||||
print(f" PASS {name}")
|
||||
else:
|
||||
print(f" FAIL {name} got={got!r}")
|
||||
FAILS.append(name)
|
||||
|
||||
# ---- dashscope_hunt ----
|
||||
import dashscope_hunt as DH
|
||||
GARB = DH.GARBAGE_KEY # single source of truth
|
||||
|
||||
def blind_map(url, hdr, params, method="GET"):
|
||||
if method == "POST" or "/models" not in url:
|
||||
return FakeResp(401, None, "invalid") # chat/balance always auth-check
|
||||
return FakeResp(200, CATALOG) # cached catalog served to EVERYONE
|
||||
|
||||
def authed_map(url, hdr, params, method="GET"):
|
||||
auth = hdr.get("Authorization", "")
|
||||
if GARB in auth:
|
||||
return FakeResp(401, None, '{"error":"invalid"}')
|
||||
return FakeResp(200, CATALOG)
|
||||
|
||||
def dead_map(url, hdr, params, method="GET"):
|
||||
return FakeResp(401, None, '{"error":"invalid"}')
|
||||
|
||||
class FakeRequests:
|
||||
def __init__(self, m): self.m = m
|
||||
def get(self, url, headers=None, timeout=None, **kw):
|
||||
return self.m(url, headers or {}, None, "GET")
|
||||
def post(self, url, headers=None, json=None, timeout=None, **kw):
|
||||
return self.m(url, headers or {}, None, "POST")
|
||||
|
||||
print("== dashscope_hunt ==")
|
||||
DH.requests = FakeRequests(blind_map)
|
||||
st, info = DH.probe_zai("483981f89cf44b66a641bd1c0e87eeee.Ve5Xabc123456")
|
||||
check("probe_zai authblind -> not WORKING", st != "WORKING", (st, info))
|
||||
st, info = DH.probe_bigmodel("483981f89cf44b66a641bd1c0e87eeee.Ve5Xabc123456")
|
||||
check("probe_bigmodel authblind -> not WORKING", st != "WORKING", (st, info))
|
||||
st, info = DH.probe_siliconflow("sk-00000000000000000000000000000000")
|
||||
check("probe_siliconflow authblind -> not WORKING", st != "WORKING", (st, info))
|
||||
st, info = DH.probe_dashscope("sk-00000000000000000000000000000000")
|
||||
check("probe_dashscope authblind -> not WORKING", st != "WORKING", (st, info))
|
||||
|
||||
DH.requests = FakeRequests(authed_map)
|
||||
st, info = DH.probe_zai("483981f89cf44b66a641bd1c0e87eeee.Ve5Xabc123456")
|
||||
check("probe_zai attributed 200 -> WORKING", st == "WORKING", (st, info))
|
||||
st, info = DH.probe_dashscope("sk-00000000000000000000000000000000")
|
||||
check("probe_dashscope attributed 200 -> WORKING", st == "WORKING", (st, info))
|
||||
|
||||
DH.requests = FakeRequests(dead_map)
|
||||
st, info = DH.probe_zai("483981f89cf44b66a641bd1c0e87eeee.Ve5Xabc123456")
|
||||
check("probe_zai 401 -> reject/DEAD path (not WORKING)", st != "WORKING", (st, info))
|
||||
DH.requests = __import__("requests")
|
||||
|
||||
# ---- universal_hunt ----
|
||||
print("== universal_hunt ==")
|
||||
import universal_hunt as UH
|
||||
class FakeS:
|
||||
def __init__(self, m): self.m = m
|
||||
def get(self, url, headers=None, params=None, timeout=None, **kw):
|
||||
return self.m(url, headers or {}, params, "GET")
|
||||
def post(self, url, headers=None, json=None, timeout=None, **kw):
|
||||
return self.m(url, headers or {}, None, "POST")
|
||||
|
||||
def uh_blind(url, hdr, params, method="GET"):
|
||||
if method == "POST" or "/models" not in url:
|
||||
return FakeResp(401, None, "invalid")
|
||||
return FakeResp(200, CATALOG)
|
||||
def uh_authed(url, hdr, params, method="GET"):
|
||||
key = hdr.get("Authorization", "") + hdr.get("x-api-key", "") + str(params or {})
|
||||
if GARB in key or "AUTHBLIND" in key:
|
||||
return FakeResp(401, None, "invalid")
|
||||
return FakeResp(200, CATALOG)
|
||||
|
||||
real_s = UH.S
|
||||
UH.S = FakeS(uh_blind)
|
||||
for name, fn, arg in (("probe_openai", UH.probe_openai, "sk-proj-abc123"),
|
||||
("probe_anthropic", UH.probe_anthropic, "sk-ant-api03-abc"),
|
||||
("probe_anthropic_oat", UH.probe_anthropic_oat, "sk-ant-oat01-abc"),
|
||||
("probe_gemini", UH.probe_gemini, "AIzaSyTest1234567890abcdefghij"),
|
||||
("_models_probe", lambda k: UH._models_probe("test", "https://api.test.dev/v1/models", k), "sk-test-123")):
|
||||
st, info = fn(arg)
|
||||
check(f"{name} authblind -> not WORKING", st != "WORKING", (st, info))
|
||||
UH.S = FakeS(uh_authed)
|
||||
st, info = UH.probe_anthropic("sk-ant-api03-abc")
|
||||
check("probe_anthropic attributed -> WORKING", st == "WORKING", (st, info))
|
||||
st, info = UH.probe_gemini("AIzaSyTest1234567890abcdefghij")
|
||||
check("probe_gemini attributed -> WORKING", st == "WORKING", (st, info))
|
||||
UH.S = real_s
|
||||
|
||||
# ---- eternal_v10 _v_default loose branches (skip if module absent) ----
|
||||
try:
|
||||
import eternal_v10 as E
|
||||
_HAS_E = True
|
||||
except ImportError:
|
||||
pass
|
||||
print("== eternal_v10 ==" if _HAS_E else "== eternal_v10 == SKIP (no module)")
|
||||
if _HAS_E:
|
||||
class FakeHTTP:
|
||||
class _sess:
|
||||
def post(self, url, headers=None, json=None, timeout=None, **kw):
|
||||
return FakeResp(200, None, "<html>WAF intercept page</html>")
|
||||
@staticmethod
|
||||
def session():
|
||||
return FakeHTTP._sess()
|
||||
real_http = E.HTTP
|
||||
E.HTTP = FakeHTTP
|
||||
prov = {"test_url": "https://api.test/v1/chat", "test_model": "m1"}
|
||||
res = E._v_default(prov, "sk-x", "KIMI")
|
||||
check("_v_default non-JSON 200 -> not WORKING", res[0] != "WORKING", res)
|
||||
|
||||
class FakeHTTPAlt:
|
||||
calls = [0]
|
||||
class _sess:
|
||||
def post(self, url, headers=None, json=None, timeout=None, **kw):
|
||||
FakeHTTPAlt.calls[0] += 1
|
||||
if FakeHTTPAlt.calls[0] == 1:
|
||||
return FakeResp(401, None, "invalid")
|
||||
return FakeResp(200, None, "<html>not json</html>")
|
||||
@staticmethod
|
||||
def session():
|
||||
return FakeHTTPAlt._sess()
|
||||
E.HTTP = FakeHTTPAlt
|
||||
prov2 = {"test_url": "https://api.test/v1/chat", "test_model": "m1",
|
||||
"alt_urls": [("ALT", "https://alt.test/v1/chat", "m2")]}
|
||||
res = E._v_default(prov2, "sk-x", "DASHSCOPE")
|
||||
check("_v_default alt non-JSON 200 -> not WORKING", res[0] != "WORKING", res)
|
||||
E.HTTP = real_http
|
||||
|
||||
# ---- DB-level: validate_key must PRESERVE authblind rows (never DEAD/WORKING) ----
|
||||
print("== DB-level validate_key ==")
|
||||
import sqlite3 as _sq
|
||||
for _mod in (UH, DH):
|
||||
_mod.conn = _sq.connect(_TMPDB, timeout=30, check_same_thread=False,
|
||||
isolation_level=None)
|
||||
_dl = _mod.conn.execute("PRAGMA database_list").fetchall()
|
||||
_paths = [str(r[2]).replace("\\", "/") for r in _dl]
|
||||
assert any(_TMPDB.replace("\\", "/") == p for p in _paths), \
|
||||
f"WRONG DB TARGET: {_dl}"
|
||||
print(f" {_mod.__name__} conn -> {_paths}")
|
||||
SCHEMA = ("CREATE TABLE IF NOT EXISTS keys (hash TEXT PRIMARY KEY, key TEXT, "
|
||||
"prov TEXT, status TEXT DEFAULT 'NEW', plan TEXT, remaining TEXT, repo TEXT)")
|
||||
for mod, tag in ((UH, "t_authblind_uh"), (DH, "t_authblind_dh")):
|
||||
mod.conn.execute(SCHEMA)
|
||||
mod.conn.execute("DELETE FROM keys WHERE hash=?", (tag,))
|
||||
uh_key = "sk-test-groq-key-1234567890"
|
||||
UH.conn.execute("INSERT INTO keys (hash, key, prov, status, repo) VALUES "
|
||||
"('t_authblind_uh',?,'GROQ','WORKING','o/r')", (uh_key,))
|
||||
UH.S = FakeS(uh_blind)
|
||||
res = UH.validate_key("t_authblind_uh", uh_key, "GROQ", "o/r")
|
||||
row = UH.conn.execute("SELECT status FROM keys WHERE hash='t_authblind_uh'").fetchone()
|
||||
check("UH authblind DEMOTES stale WORKING -> NEW",
|
||||
res[0] == "NEW" and str(res[1]).startswith("authblind:") and row and row[0] == "NEW",
|
||||
(res, row))
|
||||
res = UH.validate_key("t_authblind_uh", uh_key, "GROQ", "o/r")
|
||||
row = UH.conn.execute("SELECT status FROM keys WHERE hash='t_authblind_uh'").fetchone()
|
||||
check("UH authblind second run: already-NEW row untouched (no-op)",
|
||||
res[0] == "NEW" and row and row[0] == "NEW", (res, row))
|
||||
UH.S = real_s
|
||||
|
||||
dh_key = "sk-00000000000000000000000000000000"
|
||||
DH.conn.execute("INSERT INTO keys (hash, key, prov, status, repo) VALUES "
|
||||
"('t_authblind_dh',?,'DASHSCOPE','WORKING','o/r')", (dh_key,))
|
||||
DH.requests = FakeRequests(blind_map)
|
||||
res = DH.validate_key("t_authblind_dh", dh_key, "DASHSCOPE", "o/r")
|
||||
row = DH.conn.execute("SELECT status FROM keys WHERE hash='t_authblind_dh'").fetchone()
|
||||
check("DH authblind DEMOTES stale WORKING -> NEW",
|
||||
res == ("NEW", "retry:UNVERIFIED_AUTHBLIND") and row and row[0] == "NEW",
|
||||
(res, row))
|
||||
res = DH.validate_key("t_authblind_dh", dh_key, "DASHSCOPE", "o/r")
|
||||
row = DH.conn.execute("SELECT status FROM keys WHERE hash='t_authblind_dh'").fetchone()
|
||||
check("DH authblind second run: already-NEW row untouched (no-op)",
|
||||
res[0] == "NEW" and row and row[0] == "NEW", (res, row))
|
||||
DH.requests = __import__("requests")
|
||||
|
||||
|
||||
|
||||
# ---- balance probes: WORKING only on parsed positive number ----
|
||||
print("== balance probes ==")
|
||||
def bal_map(empty=True, zero=False):
|
||||
def m(url, hdr, params, method="GET"):
|
||||
if "balance" not in url:
|
||||
return FakeResp(401, None, "invalid")
|
||||
if empty:
|
||||
return FakeResp(200, {}, "")
|
||||
v = 0 if zero else 12.5
|
||||
if "moonshot" in url:
|
||||
return FakeResp(200, {"data": {"balance": v}})
|
||||
return FakeResp(200, {"balance_infos": [{"total_balance": v}]})
|
||||
return m
|
||||
for empty, zero, want_dead in ((True, False, True), (False, True, True), (False, False, False)):
|
||||
DH.requests = FakeRequests(bal_map(empty, zero))
|
||||
st1, i1 = DH.probe_moonshot("sk-97e78a69f0d14b68a1d7c77a4e2d1b29")
|
||||
st2, i2 = DH.probe_deepseek("sk-97e78a69f0d14b68a1d7c77a4e2d1b29")
|
||||
label = "unparsed" if empty else ("zero" if zero else "positive")
|
||||
if want_dead:
|
||||
check(f"probe_moonshot {label} -> not WORKING", st1 != "WORKING", (st1, i1))
|
||||
check(f"probe_deepseek {label} -> not WORKING", st2 != "WORKING", (st2, i2))
|
||||
else:
|
||||
check("probe_moonshot positive-balance -> WORKING", st1 == "WORKING", (st1, i1))
|
||||
check("probe_deepseek positive-balance -> WORKING", st2 == "WORKING", (st2, i2))
|
||||
DH.requests = __import__("requests")
|
||||
|
||||
# ---- paired controls: rejected keys must still go DEAD (non-vacuous gate) ----
|
||||
print("== paired DEAD controls ==")
|
||||
UH.S = FakeS(lambda url, hdr, params, method="GET": FakeResp(401, None, "invalid"))
|
||||
res = UH.validate_key("t_authblind_uh", uh_key, "GROQ", "o/r")
|
||||
row = UH.conn.execute("SELECT status FROM keys WHERE hash='t_authblind_uh'").fetchone()
|
||||
check("UH validate_key rejected -> DEAD row (control)",
|
||||
res[0] == "DEAD" and row and row[0] == "DEAD", (res, row))
|
||||
UH.S = real_s
|
||||
DH.requests = FakeRequests(dead_map)
|
||||
res = DH.validate_key("t_authblind_dh", dh_key, "DASHSCOPE", "o/r")
|
||||
row = DH.conn.execute("SELECT status FROM keys WHERE hash='t_authblind_dh'").fetchone()
|
||||
check("DH validate_key rejected -> DEAD row (control)",
|
||||
res[0] == "DEAD" and row and row[0] == "DEAD", (res, row))
|
||||
DH.requests = __import__("requests")
|
||||
|
||||
print("RESULT:", "PASS all" if not FAILS else f"FAIL {len(FAILS)}: {FAILS}")
|
||||
sys.exit(1 if FAILS else 0)
|
||||
9
tests/wp_svn_file_fixture.txt
Normal file
9
tests/wp_svn_file_fixture.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
; Sample plugin config — SYNTHETIC test fixture, no real credentials.
|
||||
; Used by test_wp_svn.py: scan_text must find the injected keys,
|
||||
; GARBAGE/junk entries must never reach the DB.
|
||||
OPENAI_API_KEY=sk-proj-LongKeyBodyAvoidGarbageFilterCheckPassOpenaiTestSkProjValidCharsHereForSixtyPlusMoreNow
|
||||
GOOGLE_API_KEY=AIzaSyD4iE4xmrKKyG3bC4N5v6w7x8y9z0A1b2C3D
|
||||
FAKE_JUNK=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
DB_PASSWORD=placeholder1234567890abcdefghij
|
||||
EMPTY_VALUE=
|
||||
AWS_ACCESS_KEY_ID=AKIAB7QX4M2KJ9RT3WYZ
|
||||
27
tests/wp_svn_trunk_fixture.html
Normal file
27
tests/wp_svn_trunk_fixture.html
Normal file
@@ -0,0 +1,27 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>akismet/trunk - Revision 2891234</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>akismet/trunk</h1>
|
||||
<ul>
|
||||
<li><a href="?C=N;O=D">Name</a></li>
|
||||
<li><a href="?C=M;O=A">Last modified</a></li>
|
||||
<li><a href="?C=S;O=A">Size</a></li>
|
||||
<li><a href="../">..</a></li>
|
||||
<li><a href="_inc/">_inc/</a></li>
|
||||
<li><a href="abilities/">abilities/</a></li>
|
||||
<li><a href="vendor/">vendor/</a></li>
|
||||
<li><a href="languages/">languages/</a></li>
|
||||
<li><a href="views/">views/</a></li>
|
||||
<li><a href="akismet.php">akismet.php</a></li>
|
||||
<li><a href="class.akismet.php">class.akismet.php</a></li>
|
||||
<li><a href="class.akismet-cli.php">class.akismet-cli.php</a></li>
|
||||
<li><a href="index.php">index.php</a></li>
|
||||
<li><a href="wrapper.php">wrapper.php</a></li>
|
||||
<li><a href="readme.txt">readme.txt</a></li>
|
||||
<li><a href="changelog.txt">changelog.txt</a></li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user