joy-cli/scripts/manhuang/notify-guishi-brand.sh
feibisi 2c6281d041 feat(manhuang): supply-chain dashboard, probes, and side-cache data
Expand manhuang collect/UI (tabs, markets, guishi/quote probes), add
configs and launchd helpers, ops handbooks, and scripts for channel
validation and price/threat enrichment. Keys stay in keys.env.example only.
2026-07-13 02:08:29 +08:00

183 lines
6.2 KiB
Bash
Executable file
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# 鬼市品牌撞库 HIT → 通知 konqivm-dm / office inbox
# 只推 threat 级(钓鱼/恶意 feed 命中urlscan 自有域曝光默认不推(--exposure 可开)
# 节流:同 fingerprint 4h 内不重复
#
# 用法:
# bash notify-guishi-brand.sh
# bash notify-guishi-brand.sh --force
# bash notify-guishi-brand.sh --exposure # 也推 urlscan 自有域曝光
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
CACHE="${HOME}/.cache/manhuang"
STATUS="${CACHE}/guishi/status.json"
STAMP="${CACHE}/.guishi-brand-notify.last"
LOG="${HOME}/logs/manhuang-quotes.log"
THROTTLE_S="${GUISHI_BRAND_NOTIFY_THROTTLE_S:-14400}"
FORCE=0
EXPOSURE=0
for a in "$@"; do
case "$a" in
--force) FORCE=1 ;;
--exposure) EXPOSURE=1 ;;
esac
done
mkdir -p "$CACHE" "$(dirname "$LOG")"
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] notify-guishi-brand: $*" | tee -a "$LOG"; }
if [[ ! -f "$STATUS" ]]; then
log "skip: no $STATUS"
exit 0
fi
export STATUS EXPOSURE
STATE=$(python3 - <<'PY'
import json, os, hashlib
from pathlib import Path
p = Path(os.environ["STATUS"])
d = json.loads(p.read_text(encoding="utf-8"))
hits = d.get("brand_hits") or []
exposure_ok = os.environ.get("EXPOSURE") == "1"
threat, exposure = [], []
for h in hits:
if not isinstance(h, dict):
continue
sev = (h.get("severity") or h.get("level") or "").lower()
src = str(h.get("source") or "")
kind = (h.get("kind") or "").lower()
is_threat = (
sev in ("threat", "crit", "high")
or kind == "threat"
or any(x in src for x in ("openphish", "phishing", "maltrail", "urlhaus", "threatfox"))
)
is_exposure = (
sev in ("exposure", "info")
or kind == "exposure"
or src.startswith("urlscan-brand")
)
if is_threat and not (is_exposure and not any(x in src for x in ("openphish", "phishing", "maltrail"))):
# urlscan alone = exposure unless marked threat
if src.startswith("urlscan-brand") and sev not in ("threat", "crit"):
exposure.append(h)
else:
threat.append(h)
elif is_exposure:
exposure.append(h)
else:
# default: treat unknown as exposure (less noise)
exposure.append(h)
notify_hits = list(threat)
if exposure_ok:
notify_hits.extend(exposure)
# fingerprint for throttle
parts = sorted({f"{h.get('brand')}|{h.get('source')}" for h in notify_hits if h.get("brand")})
fp = hashlib.sha1("|".join(parts).encode()).hexdigest()[:16] if parts else ""
# findings crit from probe
findings = [f for f in (d.get("findings") or []) if isinstance(f, dict) and f.get("level") == "crit"]
out = {
"ok": True,
"host": d.get("host") or d.get("role"),
"ts": d.get("ts"),
"threat_n": len(threat),
"exposure_n": len(exposure),
"notify_n": len(notify_hits),
"should_notify": len(notify_hits) > 0,
"fingerprint": fp,
"threat": [
{
"brand": h.get("brand"),
"source": h.get("source"),
"n_lines": h.get("n_lines"),
"sample": (h.get("samples") or [""])[0][:100],
"severity": h.get("severity") or h.get("kind"),
}
for h in notify_hits[:12]
],
"findings_crit": [f.get("msg") for f in findings[:5]],
"circuit_ok": (d.get("socks") or {}).get("circuit_ok"),
"probe_ok": (d.get("counts") or {}).get("ok"),
"probe_fail": (d.get("counts") or {}).get("fail"),
}
print(json.dumps(out, ensure_ascii=False))
PY
)
echo "$STATE" | python3 -c "import sys,json; d=json.load(sys.stdin); print('should', d.get('should_notify'), 'threat', d.get('threat_n'), 'exposure', d.get('exposure_n'), 'fp', d.get('fingerprint'))"
SHOULD=$(echo "$STATE" | python3 -c "import sys,json; print('1' if json.load(sys.stdin).get('should_notify') else '0')")
if [[ "$SHOULD" != "1" ]]; then
log "skip: no threat brand hits (use --exposure for urlscan self-domain)"
exit 0
fi
FP=$(echo "$STATE" | python3 -c "import sys,json; print(json.load(sys.stdin).get('fingerprint') or '')")
if [[ "$FORCE" != "1" && -f "$STAMP" && -n "$FP" ]]; then
now=$(date +%s)
last_epoch=$(python3 -c "import json;print(int(json.load(open('$STAMP')).get('epoch') or 0))" 2>/dev/null || echo 0)
last_fp=$(python3 -c "import json;print(json.load(open('$STAMP')).get('fingerprint') or '')" 2>/dev/null || echo "")
if [[ "$last_fp" == "$FP" && $((now - last_epoch)) -lt $THROTTLE_S ]]; then
log "skip: throttled fp=$FP age=$((now - last_epoch))s"
exit 0
fi
fi
MSG=$(echo "$STATE" | python3 -c '
import json,sys
d=json.load(sys.stdin)
lines=[
"【鬼市品牌撞库】HIT %s 条 (threat=%s exposure=%s)" % (
d.get("notify_n"), d.get("threat_n"), d.get("exposure_n")),
"host=%s probe ok/fail=%s/%s circuit=%s" % (
d.get("host"), d.get("probe_ok"), d.get("probe_fail"), d.get("circuit_ok")),
]
for h in d.get("threat") or []:
lines.append("- %s @ %s · %s" % (h.get("brand"), h.get("source"), (h.get("sample") or "")[:80]))
for f in d.get("findings_crit") or []:
lines.append("! %s" % str(f)[:100])
lines.append("面板: http://localhost:8899/#tab=mh-guishi")
print("\n".join(lines))
')
sent=0
if command -v vm-dm >/dev/null 2>&1; then
if vm-dm konqi "$MSG" --type notification --subject "manhuang-guishi-brand" 2>>"$LOG"; then
sent=1
log "sent via vm-dm konqi"
else
log "vm-dm konqi failed, try outbox"
fi
fi
if [[ "$sent" != "1" ]]; then
# konqi inbox (message-bridge) + Mac office outbox fallbacks
for OUTBOX in "${HOME}/office/konqi/inbox" "${HOME}/office/linuxjoy/outbox"; do
mkdir -p "$OUTBOX"
done
FN="${HOME}/office/konqi/inbox/manhuang-guishi-brand-$(date +%s).json"
python3 - <<PY
import json, time
from pathlib import Path
msg = '''$MSG'''
Path("$FN").write_text(json.dumps({
"from": "manhuang",
"to": "konqi",
"type": "notification",
"subject": "manhuang-guishi-brand",
"content": msg,
"ts": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
}, ensure_ascii=False, indent=2), encoding="utf-8")
print("wrote", "$FN")
PY
log "wrote local inbox $FN"
sent=1
fi
python3 -c "import json,time; open('$STAMP','w').write(json.dumps({'epoch':time.time(),'fingerprint':'$FP','ts':time.strftime('%Y-%m-%dT%H:%M:%SZ',time.gmtime())}))"
log "done notify_n=$(echo "$STATE" | python3 -c 'import sys,json;print(json.load(sys.stdin).get("notify_n"))')"
exit 0