Files
First/src/js_analyzer.py
T
2026-04-02 21:38:29 +08:00

1120 lines
40 KiB
Python
Raw 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.
"""
敏感信息提取引擎 — 纯 Python 实现
完整移植 HeartK-1.1.1/js/background.js 的全部匹配规则。
"""
import re
import json
import os
import time
from datetime import datetime
# ══════════════════════════════════════════
# 信息提取正则 (1:1 移植自 HeartK background.js extract_info)
# ══════════════════════════════════════════
_TLD = (r'xin|com|cn|net|com\.cn|vip|top|cc|shop|club|wang|xyz|luxe|site|'
r'news|pub|fun|online|win|red|loan|ren|mom|net\.cn|org|link|biz|bid|'
r'help|tech|date|mobi|so|me|tv|co|vc|pw|video|party|pics|website|'
r'store|ltd|ink|trade|live|wiki|space|gift|lol|work|band|info|click|'
r'photo|market|tel|social|press|game|kim|org\.cn|games|pro|men|love|'
r'studio|rocks|asia|group|science|design|software|engineer|lawyer|'
r'fit|beer|tw|我爱你|中国|公司|网络|在线|网址|网店|集团|中文网')
_PATTERNS = {
"sfz": re.compile(
r"""['"]((\d{8}(0\d|10|11|12)([0-2]\d|30|31)\d{3}$)|"""
r"""(\d{6}(18|19|20)\d{2}(0[1-9]|10|11|12)([0-2]\d|30|31)\d{3}(\d|X|x)))['"]"""),
"mobile": re.compile(
r"""['"](1(3([0-35-9]\d|4[1-8])|4[14-9]\d|5([\d]\d|7[1-79])|"""
r"""66\d|7[2-35-8]\d|8\d{2}|9[89]\d)\d{7})['"]"""),
"mail": re.compile(
r"""['"][a-zA-Z0-9._\-]*@[a-zA-Z0-9._\-]{1,63}"""
r"""\.((?!js|css|jpg|jpeg|png|ico)[a-zA-Z]{2,})['"]"""),
"ip": re.compile(
r"""['"](([a-zA-Z0-9]+:)?//)?\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(/.*?)?['"]"""),
"ip_port": re.compile(
r"""['"](([a-zA-Z0-9]+:)?//)?\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{1,5}(/.*?)?['"]"""),
"domain": re.compile(
r"""['"](([a-zA-Z0-9]+:)?//)?[a-zA-Z0-9\-\.]*?\.(""" + _TLD +
r""")(:\d{1,5})?(/)?['"]"""),
"path": re.compile(
r"""(?:"|')"""
r"""("""
r"""(?:[a-zA-Z]{1,10}://|//)[^"'/]{1,}\.[a-zA-Z]{2,}[^"']{0,}"""
r"""|(?:/|\.\./|\./)[^"'><,;|*()(%%$^/\\\[\]][^"'><,;|()]{1,}"""
r"""|[a-zA-Z0-9_\-/]{1,}/[a-zA-Z0-9_\-/]{1,}\.(?:[a-zA-Z]{1,4}|action)(?:[\?|#][^"|']{0,}|)"""
r"""|[a-zA-Z0-9_\-/]{1,}/[a-zA-Z0-9_\-/]{3,}(?:[\?|#][^"|']{0,}|)"""
r"""|[a-zA-Z0-9_\-]{1,}\.(?:\w)(?:[\?|#][^"|']{0,}|)"""
r""")"""
r"""(?:"|')"""),
"jwt": re.compile(
r"""['"](ey[A-Za-z0-9_-]{10,}\.[A-Za-z0-9._-]{10,}|"""
r"""ey[A-Za-z0-9_/+-]{10,}\.[A-Za-z0-9._/+-]{10,})['"]"""),
"algorithm": re.compile(
r"""\W(Base64\.encode|Base64\.decode|btoa|atob|"""
r"""CryptoJS\.AES|CryptoJS\.DES|JSEncrypt|rsa|KJUR|"""
r"""\$\.md5|md5|sha1|sha256|sha512)[(.]""", re.IGNORECASE),
}
# ══════════════════════════════════════════
# URL 提取正则 (8 patterns: 4 core × 2 quote styles)
# ══════════════════════════════════════════
_DANGEROUS_EXT = (
r'php|asp|aspx|jsp|do|action|cgi|ashx|asmx|py|pl|go|rb|class|jar|war|ear|phar|'
r'html|htm|exe|msi|bat|cmd|sh|bash|zsh|vbs|ps1|psm1|ps2|msh|msh1|msh2|mshxml|'
r'msh1xml|msh2xml|dll|so|dylib|ocx|sys|drv|com|scr|pif|hta|vb|bas|frm|vbx|vbp|'
r'service|desktop|application|command|tool|jspx|asa|cer|cdx|rhtml|tcl|tk|pm|rpm|'
r'deb|apk|ipa|app|bin|elf|run|img|pkg|iso|dmg|nexe|node|wasm|clj|scala|groovy|'
r'a|o|lib|ex_|inf|reg|lnk|scf'
)
_HOST_RE = r"""https?://[^\s'"/?#]+(?::\d+)?"""
def _build_url_patterns():
cores = [
# 1. single segment, no extension
_HOST_RE + r"""/*[^'"\s./?]+/*(?:\?[^\s'"]*)?""",
# 2. single segment, dangerous extension
_HOST_RE + r"""/*[^'"\s/?]+\.(?:""" + _DANGEROUS_EXT + r""")/*(?:\?[^\s'"]*)?""",
# 3. multi-segment, no extension
_HOST_RE + r"""/*(?:/+[^'"\s/?]*)+/+[^'"\s./?]+/*(?:\?[^\s'"]*)?""",
# 4. multi-segment, dangerous extension
_HOST_RE + r"""/*(?:/+[^'"\s/?]*)+/+[^'"\s/?]+\.(?:""" + _DANGEROUS_EXT + r""")/*(?:\?[^\s'"]*)?""",
]
pats = []
for core in cores:
# normal quotes
pats.append(re.compile(r"""(?:^|[^\\])["'](""" + core + r""")["']""", re.IGNORECASE | re.MULTILINE))
# escaped quotes
pats.append(re.compile(r"""\\["'](""" + core + r""")\\["']""", re.IGNORECASE))
return pats
_URL_PATTERNS = _build_url_patterns()
# ══════════════════════════════════════════
# 静态资源提取正则 (8 patterns: 4 core × 2 quote styles)
# ══════════════════════════════════════════
_STATIC_EXT = (
r'css|js|ts|json|config|xml|vue|jsx|tsx|txt|csv|'
r'png|jpg|jpeg|gif|webp|svg|ico|bmp|'
r'mp3|wav|ogg|mp4|webm|mov|avi|mkv|'
r'pdf|doc|docx|xls|xlsx|ppt|pptx|rtf|'
r'woff|woff2|ttf|eot|svelte|'
r'zip|rar|7z|tar|gz|bz2|xz|tgz|tbz|lz|lz4|zst|iso'
)
def _build_static_patterns():
ext_group = r"""\.(?:""" + _STATIC_EXT + r""")/*(?:\?[^\s'"]*)?"""
cores = [
# 1. local path, single segment
r"""/+[^'"\s/?]+""" + ext_group,
# 2. local path, multi-segment
r"""(?:/+[^'"\s/?]*)+/+[^'"\s/?]+""" + ext_group,
# 3. full URL, single segment
r"""https?://[^\s'"/?#]+(?::\d+)?/*[^'"\s/?]+""" + ext_group,
# 4. full URL, multi-segment
r"""https?://[^\s'"/?#]+(?::\d+)?/*(?:/+[^'"\s/?]*)+/+[^'"\s/?]+""" + ext_group,
]
pats = []
for core in cores:
# normal quotes
pats.append(re.compile(r"""(?:^|[^\\])["'](""" + core + r""")["']""", re.IGNORECASE | re.MULTILINE))
# escaped quotes
pats.append(re.compile(r"""\\["'](""" + core + r""")\\["']""", re.IGNORECASE))
return pats
_STATIC_PATTERNS = _build_static_patterns()
# ══════════════════════════════════════════
# OSS 云存储域名检测
# ══════════════════════════════════════════
_OSS_DOMAIN_RE = re.compile(
r'(?:'
r'[\w.-]+\.oss[\w-]*\.aliyuncs\.com|' # 阿里云 OSS
r'[\w.-]+\.cos\.[\w-]+\.myqcloud\.com|' # 腾讯云 COS
r'[\w.-]+\.file\.myqcloud\.com|' # 腾讯云 COS legacy
r'(?:[\w.-]+\.)?s3[\w.-]*\.amazonaws\.com|' # AWS S3
r'[\w.-]+\.obs\.[\w-]+\.myhuaweicloud\.com|' # 华为云 OBS
r'[\w.-]+\.(?:qiniucdn|qnssl)\.com|' # 七牛 CDN
r'[\w.-]+\.bkt\.clouddn\.com|' # 七牛旧域名
r'[\w.-]+\.blob\.core\.windows\.net|' # Azure Blob
r'[\w.-]+\.azureedge\.net|' # Azure CDN
r'storage\.googleapis\.com(?:/[\w.-]+)?|' # Google Cloud Storage
r'[\w.-]+\.storage\.googleapis\.com|' # GCS alt
r'[\w.-]+\.cdn\.bcebos\.com|' # 百度 BOS
r'[\w.-]+\.vod[\w-]*\.aliyuncs\.com|' # 阿里云 VOD
r'[\w.-]+\.cdn\.aliyuncs\.com|' # 阿里云 CDN
r'[\w.-]+\.ucloud\.cn|' # UCloud
r'[\w.-]+\.ks3[\w-]*\.ksyun\.com' # 金山云 KS3
r')',
re.IGNORECASE
)
# 静态资源过滤: .js 不算(排除 .jsp)
_STATIC_FILTER_EXTS = ('.ts', '.tsx', '.less', '.scss', '.sass', '.map',
'.d.ts', '.spec.ts', '.test.ts')
# ══════════════════════════════════════════
# Nuclei 凭证泄露检测 — 完整移植 HeartK 的 702+24 条规则
# ══════════════════════════════════════════
# 702 个 nuclei key name (每个 key 用标准模板匹配: ["']?KEY["']? [=:] ["']?VALUE["']?)
# 直接取自 HeartK-1.1.1/js/background.js nuclei_regex 数组
_NUCLEI_KEY_NAMES = [
r'zopim[_-]?account[_-]?key',
r'zhuliang[_-]?gh[_-]?token',
r'zensonatypepassword',
r'zendesk[_-]?travis[_-]?github',
r'yt[_-]?server[_-]?api[_-]?key',
r'yt[_-]?partner[_-]?refresh[_-]?token',
r'yt[_-]?partner[_-]?client[_-]?secret',
r'yt[_-]?client[_-]?secret',
r'yt[_-]?api[_-]?key',
r'yt[_-]?account[_-]?refresh[_-]?token',
r'yt[_-]?account[_-]?client[_-]?secret',
r'yangshun[_-]?gh[_-]?token',
r'yangshun[_-]?gh[_-]?password',
r'www[_-]?googleapis[_-]?com',
r'wpt[_-]?ssh[_-]?private[_-]?key[_-]?base64',
r'wpt[_-]?ssh[_-]?connect',
r'wpt[_-]?report[_-]?api[_-]?key',
r'wpt[_-]?prepare[_-]?dir',
r'wpt[_-]?db[_-]?user',
r'wpt[_-]?db[_-]?password',
r'wporg[_-]?password',
r'wpjm[_-]?phpunit[_-]?google[_-]?geocode[_-]?api[_-]?key',
r'wordpress[_-]?db[_-]?user',
r'wordpress[_-]?db[_-]?password',
r'wincert[_-]?password',
r'widget[_-]?test[_-]?server',
r'widget[_-]?fb[_-]?password[_-]?3',
r'widget[_-]?fb[_-]?password[_-]?2',
r'widget[_-]?fb[_-]?password',
r'widget[_-]?basic[_-]?password[_-]?5',
r'widget[_-]?basic[_-]?password[_-]?4',
r'widget[_-]?basic[_-]?password[_-]?3',
r'widget[_-]?basic[_-]?password[_-]?2',
r'widget[_-]?basic[_-]?password',
r'watson[_-]?password',
r'watson[_-]?device[_-]?password',
r'watson[_-]?conversation[_-]?password',
r'wakatime[_-]?api[_-]?key',
r'vscetoken',
r'visual[_-]?recognition[_-]?api[_-]?key',
r'virustotal[_-]?apikey',
r'vip[_-]?github[_-]?deploy[_-]?key[_-]?pass',
r'vip[_-]?github[_-]?deploy[_-]?key',
r'vip[_-]?github[_-]?build[_-]?repo[_-]?deploy[_-]?key',
r'v[_-]?sfdc[_-]?password',
r'v[_-]?sfdc[_-]?client[_-]?secret',
r'usertravis',
r'user[_-]?assets[_-]?secret[_-]?access[_-]?key',
r'user[_-]?assets[_-]?access[_-]?key[_-]?id',
r'use[_-]?ssh',
r'us[_-]?east[_-]?1[_-]?elb[_-]?amazonaws[_-]?com',
r'urban[_-]?secret',
r'urban[_-]?master[_-]?secret',
r'urban[_-]?key',
r'unity[_-]?serial',
r'unity[_-]?password',
r'twitteroauthaccesstoken',
r'twitteroauthaccesssecret',
r'twitter[_-]?consumer[_-]?secret',
r'twitter[_-]?consumer[_-]?key',
r'twine[_-]?password',
r'twilio[_-]?token',
r'twilio[_-]?sid',
r'twilio[_-]?configuration[_-]?sid',
r'twilio[_-]?chat[_-]?account[_-]?api[_-]?service',
r'twilio[_-]?api[_-]?secret',
r'twilio[_-]?api[_-]?key',
r'trex[_-]?okta[_-]?client[_-]?token',
r'trex[_-]?client[_-]?token',
r'travis[_-]?token',
r'travis[_-]?secure[_-]?env[_-]?vars',
r'travis[_-]?pull[_-]?request',
r'travis[_-]?gh[_-]?token',
r'travis[_-]?e2e[_-]?token',
r'travis[_-]?com[_-]?token',
r'travis[_-]?branch',
r'travis[_-]?api[_-]?token',
r'travis[_-]?access[_-]?token',
r'token[_-]?core[_-]?java',
r'thera[_-]?oss[_-]?access[_-]?key',
r'tester[_-]?keys[_-]?password',
r'test[_-]?test',
r'test[_-]?github[_-]?token',
r'tesco[_-]?api[_-]?key',
r'svn[_-]?pass',
r'surge[_-]?token',
r'surge[_-]?login',
r'stripe[_-]?public',
r'stripe[_-]?private',
r'strip[_-]?secret[_-]?key',
r'strip[_-]?publishable[_-]?key',
r'stormpath[_-]?api[_-]?key[_-]?secret',
r'stormpath[_-]?api[_-]?key[_-]?id',
r'starship[_-]?auth[_-]?token',
r'starship[_-]?account[_-]?sid',
r'star[_-]?test[_-]?secret[_-]?access[_-]?key',
r'star[_-]?test[_-]?location',
r'star[_-]?test[_-]?bucket',
r'star[_-]?test[_-]?aws[_-]?access[_-]?key[_-]?id',
r'staging[_-]?base[_-]?url[_-]?runscope',
r'ssmtp[_-]?config',
r'sshpass',
r'srcclr[_-]?api[_-]?token',
r'square[_-]?reader[_-]?sdk[_-]?repository[_-]?password',
r'sqssecretkey',
r'sqsaccesskey',
r'spring[_-]?mail[_-]?password',
r'spotify[_-]?api[_-]?client[_-]?secret',
r'spotify[_-]?api[_-]?access[_-]?token',
r'spaces[_-]?secret[_-]?access[_-]?key',
r'spaces[_-]?access[_-]?key[_-]?id',
r'soundcloud[_-]?password',
r'soundcloud[_-]?client[_-]?secret',
r'sonatypepassword',
r'sonatype[_-]?token[_-]?user',
r'sonatype[_-]?token[_-]?password',
r'sonatype[_-]?password',
r'sonatype[_-]?pass',
r'sonatype[_-]?nexus[_-]?password',
r'sonatype[_-]?gpg[_-]?passphrase',
r'sonatype[_-]?gpg[_-]?key[_-]?name',
r'sonar[_-]?token',
r'sonar[_-]?project[_-]?key',
r'sonar[_-]?organization[_-]?key',
r'socrata[_-]?password',
r'socrata[_-]?app[_-]?token',
r'snyk[_-]?token',
r'snyk[_-]?api[_-]?token',
r'snoowrap[_-]?refresh[_-]?token',
r'snoowrap[_-]?password',
r'snoowrap[_-]?client[_-]?secret',
r'slate[_-]?user[_-]?email',
r'slash[_-]?developer[_-]?space[_-]?key',
r'slash[_-]?developer[_-]?space',
r'signing[_-]?key[_-]?sid',
r'signing[_-]?key[_-]?secret',
r'signing[_-]?key[_-]?password',
r'signing[_-]?key',
r'setsecretkey',
r'setdstsecretkey',
r'setdstaccesskey',
r'ses[_-]?secret[_-]?key',
r'ses[_-]?access[_-]?key',
r'service[_-]?account[_-]?secret',
r'sentry[_-]?key',
r'sentry[_-]?secret',
r'sentry[_-]?endpoint',
r'sentry[_-]?default[_-]?org',
r'sentry[_-]?auth[_-]?token',
r'sendwithus[_-]?key',
r'sendgrid[_-]?username',
r'sendgrid[_-]?user',
r'sendgrid[_-]?password',
r'sendgrid[_-]?key',
r'sendgrid[_-]?api[_-]?key',
r'sendgrid',
r'selion[_-]?selenium[_-]?host',
r'selion[_-]?log[_-]?level[_-]?dev',
r'segment[_-]?api[_-]?key',
r'secretid',
r'secretkey',
r'secretaccesskey',
r'secret[_-]?key[_-]?base',
r'secret[_-]?9',
r'secret[_-]?8',
r'secret[_-]?7',
r'secret[_-]?6',
r'secret[_-]?5',
r'secret[_-]?4',
r'secret[_-]?3',
r'secret[_-]?2',
r'secret[_-]?11',
r'secret[_-]?10',
r'secret[_-]?1',
r'secret[_-]?0',
r'sdr[_-]?token',
r'scrutinizer[_-]?token',
r'sauce[_-]?access[_-]?key',
r'sandbox[_-]?aws[_-]?secret[_-]?access[_-]?key',
r'sandbox[_-]?aws[_-]?access[_-]?key[_-]?id',
r'sandbox[_-]?access[_-]?token',
r'salesforce[_-]?bulk[_-]?test[_-]?security[_-]?token',
r'salesforce[_-]?bulk[_-]?test[_-]?password',
r'sacloud[_-]?api',
r'sacloud[_-]?access[_-]?token[_-]?secret',
r'sacloud[_-]?access[_-]?token',
r's3[_-]?user[_-]?secret',
r's3[_-]?secret[_-]?key',
r's3[_-]?secret[_-]?assets',
r's3[_-]?secret[_-]?app[_-]?logs',
r's3[_-]?key[_-]?assets',
r's3[_-]?key[_-]?app[_-]?logs',
r's3[_-]?key',
r's3[_-]?external[_-]?3[_-]?amazonaws[_-]?com',
r's3[_-]?bucket[_-]?name[_-]?assets',
r's3[_-]?bucket[_-]?name[_-]?app[_-]?logs',
r's3[_-]?access[_-]?key[_-]?id',
r's3[_-]?access[_-]?key',
r'rubygems[_-]?auth[_-]?token',
r'rtd[_-]?store[_-]?pass',
r'rtd[_-]?key[_-]?pass',
r'route53[_-]?access[_-]?key[_-]?id',
r'ropsten[_-]?private[_-]?key',
r'rinkeby[_-]?private[_-]?key',
r'rest[_-]?api[_-]?key',
r'repotoken',
r'reporting[_-]?webdav[_-]?url',
r'reporting[_-]?webdav[_-]?pwd',
r'release[_-]?token',
r'release[_-]?gh[_-]?token',
r'registry[_-]?secure',
r'registry[_-]?pass',
r'refresh[_-]?token',
r'rediscloud[_-]?url',
r'redis[_-]?stunnel[_-]?urls',
r'randrmusicapiaccesstoken',
r'rabbitmq[_-]?password',
r'quip[_-]?token',
r'qiita[_-]?token',
r'pypi[_-]?passowrd',
r'pushover[_-]?token',
r'publish[_-]?secret',
r'publish[_-]?key',
r'publish[_-]?access',
r'project[_-]?config',
r'prod[_-]?secret[_-]?key',
r'prod[_-]?password',
r'prod[_-]?access[_-]?key[_-]?id',
r'private[_-]?signing[_-]?password',
r'private[_-]?key[_-]?(id)?',
r'pring[_-]?mail[_-]?username',
r'preferred[_-]?username',
r'prebuild[_-]?auth',
r'postgresql[_-]?pass',
r'postgresql[_-]?db',
r'postgres[_-]?env[_-]?postgres[_-]?password',
r'postgres[_-]?env[_-]?postgres[_-]?db',
r'plugin[_-]?password',
r'plotly[_-]?apikey',
r'places[_-]?apikey',
r'places[_-]?api[_-]?key',
r'pg[_-]?host',
r'pg[_-]?database',
r'personal[_-]?secret',
r'personal[_-]?key',
r'percy[_-]?token',
r'percy[_-]?project',
r'paypal[_-]?client[_-]?secret',
r'passwordtravis',
r'parse[_-]?js[_-]?key',
r'pagerduty[_-]?apikey',
r'packagecloud[_-]?token',
r'ossrh[_-]?username',
r'ossrh[_-]?secret',
r'ossrh[_-]?password',
r'ossrh[_-]?pass',
r'ossrh[_-]?jira[_-]?password',
r'os[_-]?password',
r'os[_-]?auth[_-]?url',
r'org[_-]?project[_-]?gradle[_-]?sonatype[_-]?nexus[_-]?password',
r'org[_-]?gradle[_-]?project[_-]?sonatype[_-]?nexus[_-]?password',
r'openwhisk[_-]?key',
r'open[_-]?whisk[_-]?key',
r'onesignal[_-]?user[_-]?auth[_-]?key',
r'onesignal[_-]?api[_-]?key',
r'omise[_-]?skey',
r'omise[_-]?pubkey',
r'omise[_-]?pkey',
r'omise[_-]?key',
r'okta[_-]?oauth2[_-]?clientsecret',
r'okta[_-]?oauth2[_-]?client[_-]?secret',
r'okta[_-]?client[_-]?token',
r'ofta[_-]?secret',
r'ofta[_-]?region',
r'ofta[_-]?key',
r'octest[_-]?password',
r'octest[_-]?app[_-]?username',
r'octest[_-]?app[_-]?password',
r'oc[_-]?pass',
r'object[_-]?store[_-]?creds',
r'object[_-]?store[_-]?bucket',
r'object[_-]?storage[_-]?region[_-]?name',
r'object[_-]?storage[_-]?password',
r'oauth[_-]?token',
r'numbers[_-]?service[_-]?pass',
r'nuget[_-]?key',
r'nuget[_-]?apikey',
r'nuget[_-]?api[_-]?key',
r'npm[_-]?token',
r'npm[_-]?secret[_-]?key',
r'npm[_-]?password',
r'npm[_-]?email',
r'npm[_-]?auth[_-]?token',
r'npm[_-]?api[_-]?token',
r'npm[_-]?api[_-]?key',
r'now[_-]?token',
r'non[_-]?token',
r'node[_-]?pre[_-]?gyp[_-]?secretaccesskey',
r'node[_-]?pre[_-]?gyp[_-]?github[_-]?token',
r'node[_-]?pre[_-]?gyp[_-]?accesskeyid',
r'node[_-]?env',
r'ngrok[_-]?token',
r'ngrok[_-]?auth[_-]?token',
r'nexuspassword',
r'nexus[_-]?password',
r'new[_-]?relic[_-]?beta[_-]?token',
r'netlify[_-]?api[_-]?key',
r'nativeevents',
r'mysqlsecret',
r'mysqlmasteruser',
r'mysql[_-]?username',
r'mysql[_-]?user',
r'mysql[_-]?root[_-]?password',
r'mysql[_-]?password',
r'mysql[_-]?hostname',
r'mysql[_-]?database',
r'my[_-]?secret[_-]?env',
r'multi[_-]?workspace[_-]?sid',
r'multi[_-]?workflow[_-]?sid',
r'multi[_-]?disconnect[_-]?sid',
r'multi[_-]?connect[_-]?sid',
r'multi[_-]?bob[_-]?sid',
r'minio[_-]?secret[_-]?key',
r'minio[_-]?access[_-]?key',
r'mile[_-]?zero[_-]?key',
r'mh[_-]?password',
r'mh[_-]?apikey',
r'mg[_-]?public[_-]?api[_-]?key',
r'mg[_-]?api[_-]?key',
r'mapboxaccesstoken',
r'mapbox[_-]?aws[_-]?secret[_-]?access[_-]?key',
r'mapbox[_-]?aws[_-]?access[_-]?key[_-]?id',
r'mapbox[_-]?api[_-]?token',
r'mapbox[_-]?access[_-]?token',
r'manifest[_-]?app[_-]?url',
r'manifest[_-]?app[_-]?token',
r'mandrill[_-]?api[_-]?key',
r'managementapiaccesstoken',
r'management[_-]?token',
r'manage[_-]?secret',
r'manage[_-]?key',
r'mailgun[_-]?secret[_-]?api[_-]?key',
r'mailgun[_-]?pub[_-]?key',
r'mailgun[_-]?pub[_-]?apikey',
r'mailgun[_-]?priv[_-]?key',
r'mailgun[_-]?password',
r'mailgun[_-]?apikey',
r'mailgun[_-]?api[_-]?key',
r'mailer[_-]?password',
r'mailchimp[_-]?key',
r'mailchimp[_-]?api[_-]?key',
r'mail[_-]?password',
r'magento[_-]?password',
r'magento[_-]?auth[_-]?username',
r'magento[_-]?auth[_-]?password',
r'lottie[_-]?upload[_-]?cert[_-]?key[_-]?store[_-]?password',
r'lottie[_-]?upload[_-]?cert[_-]?key[_-]?password',
r'lottie[_-]?s3[_-]?secret[_-]?key',
r'lottie[_-]?s3[_-]?api[_-]?key',
r'lottie[_-]?happo[_-]?secret[_-]?key',
r'lottie[_-]?happo[_-]?api[_-]?key',
r'looker[_-]?test[_-]?runner[_-]?client[_-]?secret',
r'll[_-]?shared[_-]?key',
r'll[_-]?publish[_-]?url',
r'linux[_-]?signing[_-]?key',
r'linkedin[_-]?client[_-]?secret',
r'lighthouse[_-]?api[_-]?key',
r'lektor[_-]?deploy[_-]?username',
r'lektor[_-]?deploy[_-]?password',
r'leanplum[_-]?key',
r'kxoltsn3vogdop92m',
r'kubeconfig',
r'kubecfg[_-]?s3[_-]?path',
r'kovan[_-]?private[_-]?key',
r'keystore[_-]?pass',
r'kafka[_-]?rest[_-]?url',
r'kafka[_-]?instance[_-]?name',
r'kafka[_-]?admin[_-]?url',
r'jwt[_-]?secret',
r'jdbc:mysql',
r'jdbc[_-]?host',
r'jdbc[_-]?databaseurl',
r'itest[_-]?gh[_-]?token',
r'ios[_-]?docs[_-]?deploy[_-]?token',
r'internal[_-]?secrets',
r'integration[_-]?test[_-]?appid',
r'integration[_-]?test[_-]?api[_-]?key',
r'index[_-]?name',
r'ij[_-]?repo[_-]?username',
r'ij[_-]?repo[_-]?password',
r'hub[_-]?dxia2[_-]?password',
r'homebrew[_-]?github[_-]?api[_-]?token',
r'hockeyapp[_-]?token',
r'heroku[_-]?token',
r'heroku[_-]?email',
r'heroku[_-]?api[_-]?key',
r'hb[_-]?codesign[_-]?key[_-]?pass',
r'hb[_-]?codesign[_-]?gpg[_-]?pass',
r'hab[_-]?key',
r'hab[_-]?auth[_-]?token',
r'grgit[_-]?user',
r'gren[_-]?github[_-]?token',
r'gradle[_-]?signing[_-]?password',
r'gradle[_-]?signing[_-]?key[_-]?id',
r'gradle[_-]?publish[_-]?secret',
r'gradle[_-]?publish[_-]?key',
r'gpg[_-]?secret[_-]?keys',
r'gpg[_-]?private[_-]?key',
r'gpg[_-]?passphrase',
r'gpg[_-]?ownertrust',
r'gpg[_-]?keyname',
r'gpg[_-]?key[_-]?name',
r'google[_-]?private[_-]?key[_-]?(id)?',
r'google[_-]?maps[_-]?api[_-]?key',
r'google[_-]?client[_-]?secret',
r'google[_-]?client[_-]?id',
r'google[_-]?client[_-]?email',
r'google[_-]?account[_-]?type',
r'gogs[_-]?password',
r'gitlab[_-]?user[_-]?email',
r'github[_-]?tokens',
r'github[_-]?token',
r'github[_-]?repo',
r'github[_-]?release[_-]?token',
r'github[_-]?pwd',
r'github[_-]?password',
r'github[_-]?oauth[_-]?token',
r'github[_-]?oauth',
r'github[_-]?key',
r'github[_-]?hunter[_-]?username',
r'github[_-]?hunter[_-]?token',
r'github[_-]?deployment[_-]?token',
r'github[_-]?deploy[_-]?hb[_-]?doc[_-]?pass',
r'github[_-]?client[_-]?secret',
r'github[_-]?auth[_-]?token',
r'github[_-]?auth',
r'github[_-]?api[_-]?token',
r'github[_-]?api[_-]?key',
r'github[_-]?access[_-]?token',
r'git[_-]?token',
r'git[_-]?name',
r'git[_-]?email',
r'git[_-]?committer[_-]?name',
r'git[_-]?committer[_-]?email',
r'git[_-]?author[_-]?name',
r'git[_-]?author[_-]?email',
r'ghost[_-]?api[_-]?key',
r'ghb[_-]?token',
r'gh[_-]?unstable[_-]?oauth[_-]?client[_-]?secret',
r'gh[_-]?token',
r'gh[_-]?repo[_-]?token',
r'gh[_-]?oauth[_-]?token',
r'gh[_-]?oauth[_-]?client[_-]?secret',
r'gh[_-]?next[_-]?unstable[_-]?oauth[_-]?client[_-]?secret',
r'gh[_-]?next[_-]?unstable[_-]?oauth[_-]?client[_-]?id',
r'gh[_-]?next[_-]?oauth[_-]?client[_-]?secret',
r'gh[_-]?email',
r'gh[_-]?api[_-]?key',
r'gcs[_-]?bucket',
r'gcr[_-]?password',
r'gcloud[_-]?service[_-]?key',
r'gcloud[_-]?project',
r'gcloud[_-]?bucket',
r'ftp[_-]?username',
r'ftp[_-]?user',
r'ftp[_-]?pw',
r'ftp[_-]?password',
r'ftp[_-]?login',
r'ftp[_-]?host',
r'fossa[_-]?api[_-]?key',
r'flickr[_-]?api[_-]?secret',
r'flickr[_-]?api[_-]?key',
r'flask[_-]?secret[_-]?key',
r'firefox[_-]?secret',
r'firebase[_-]?token',
r'firebase[_-]?project[_-]?develop',
r'firebase[_-]?key',
r'firebase[_-]?api[_-]?token',
r'firebase[_-]?api[_-]?json',
r'file[_-]?password',
r'exp[_-]?password',
r'eureka[_-]?awssecretkey',
r'env[_-]?sonatype[_-]?password',
r'env[_-]?secret[_-]?access[_-]?key',
r'env[_-]?secret',
r'env[_-]?key',
r'env[_-]?heroku[_-]?api[_-]?key',
r'env[_-]?github[_-]?oauth[_-]?token',
r'end[_-]?user[_-]?password',
r'encryption[_-]?password',
r'elasticsearch[_-]?password',
r'elastic[_-]?cloud[_-]?auth',
r'dsonar[_-]?projectkey',
r'dsonar[_-]?login',
r'droplet[_-]?travis[_-]?password',
r'dropbox[_-]?oauth[_-]?bearer',
r'doordash[_-]?auth[_-]?token',
r'dockerhubpassword',
r'dockerhub[_-]?password',
r'docker[_-]?token',
r'docker[_-]?postgres[_-]?url',
r'docker[_-]?password',
r'docker[_-]?passwd',
r'docker[_-]?pass',
r'docker[_-]?key',
r'docker[_-]?hub[_-]?password',
r'digitalocean[_-]?ssh[_-]?key[_-]?ids',
r'digitalocean[_-]?ssh[_-]?key[_-]?body',
r'digitalocean[_-]?access[_-]?token',
r'dgpg[_-]?passphrase',
r'deploy[_-]?user',
r'deploy[_-]?token',
r'deploy[_-]?secure',
r'deploy[_-]?password',
r'ddgc[_-]?github[_-]?token',
r'ddg[_-]?test[_-]?email[_-]?pw',
r'ddg[_-]?test[_-]?email',
r'db[_-]?username',
r'db[_-]?user',
r'db[_-]?pw',
r'db[_-]?password',
r'db[_-]?host',
r'db[_-]?database',
r'db[_-]?connection',
r'datadog[_-]?app[_-]?key',
r'datadog[_-]?api[_-]?key',
r'database[_-]?username',
r'database[_-]?user',
r'database[_-]?port',
r'database[_-]?password',
r'database[_-]?name',
r'database[_-]?host',
r'danger[_-]?github[_-]?api[_-]?token',
r'cypress[_-]?record[_-]?key',
r'coverity[_-]?scan[_-]?token',
r'coveralls[_-]?token',
r'coveralls[_-]?repo[_-]?token',
r'coveralls[_-]?api[_-]?token',
r'cos[_-]?secrets',
r'conversation[_-]?username',
r'conversation[_-]?password',
r'contentful[_-]?v2[_-]?access[_-]?token',
r'contentful[_-]?test[_-]?org[_-]?cma[_-]?token',
r'contentful[_-]?php[_-]?management[_-]?test[_-]?token',
r'contentful[_-]?management[_-]?api[_-]?access[_-]?token[_-]?new',
r'contentful[_-]?management[_-]?api[_-]?access[_-]?token',
r'contentful[_-]?integration[_-]?management[_-]?token',
r'contentful[_-]?cma[_-]?test[_-]?token',
r'contentful[_-]?access[_-]?token',
r'consumerkey',
r'consumer[_-]?key',
r'conekta[_-]?apikey',
r'coding[_-]?token',
r'codecov[_-]?token',
r'codeclimate[_-]?repo[_-]?token',
r'codacy[_-]?project[_-]?token',
r'cocoapods[_-]?trunk[_-]?token',
r'cocoapods[_-]?trunk[_-]?email',
r'cn[_-]?secret[_-]?access[_-]?key',
r'cn[_-]?access[_-]?key[_-]?id',
r'clu[_-]?ssh[_-]?private[_-]?key[_-]?base64',
r'clu[_-]?repo[_-]?url',
r'cloudinary[_-]?url[_-]?staging',
r'cloudinary[_-]?url',
r'cloudflare[_-]?email',
r'cloudflare[_-]?auth[_-]?key',
r'cloudflare[_-]?auth[_-]?email',
r'cloudflare[_-]?api[_-]?key',
r'cloudant[_-]?service[_-]?database',
r'cloudant[_-]?processed[_-]?database',
r'cloudant[_-]?password',
r'cloudant[_-]?parsed[_-]?database',
r'cloudant[_-]?order[_-]?database',
r'cloudant[_-]?instance',
r'cloudant[_-]?database',
r'cloudant[_-]?audited[_-]?database',
r'cloudant[_-]?archived[_-]?database',
r'cloud[_-]?api[_-]?key',
r'clojars[_-]?password',
r'client[_-]?secret',
r'cli[_-]?e2e[_-]?cma[_-]?token',
r'claimr[_-]?token',
r'claimr[_-]?superuser',
r'claimr[_-]?db',
r'claimr[_-]?database',
r'ci[_-]?user[_-]?token',
r'ci[_-]?server[_-]?name',
r'ci[_-]?registry[_-]?user',
r'ci[_-]?project[_-]?url',
r'ci[_-]?deploy[_-]?password',
r'chrome[_-]?refresh[_-]?token',
r'chrome[_-]?client[_-]?secret',
r'cheverny[_-]?token',
r'cf[_-]?password',
r'certificate[_-]?password',
r'censys[_-]?secret',
r'cattle[_-]?secret[_-]?key',
r'cattle[_-]?agent[_-]?instance[_-]?auth',
r'cattle[_-]?access[_-]?key',
r'cargo[_-]?token',
r'cache[_-]?s3[_-]?secret[_-]?key',
r'bx[_-]?username',
r'bx[_-]?password',
r'bundlesize[_-]?github[_-]?token',
r'built[_-]?branch[_-]?deploy[_-]?key',
r'bucketeer[_-]?aws[_-]?secret[_-]?access[_-]?key',
r'bucketeer[_-]?aws[_-]?access[_-]?key[_-]?id',
r'browserstack[_-]?access[_-]?key',
r'browser[_-]?stack[_-]?access[_-]?key',
r'brackets[_-]?repo[_-]?oauth[_-]?token',
r'bluemix[_-]?username',
r'bluemix[_-]?pwd',
r'bluemix[_-]?password',
r'bluemix[_-]?pass[_-]?prod',
r'bluemix[_-]?pass',
r'bluemix[_-]?auth',
r'bluemix[_-]?api[_-]?key',
r'bintraykey',
r'bintray[_-]?token',
r'bintray[_-]?key',
r'bintray[_-]?gpg[_-]?password',
r'bintray[_-]?apikey',
r'bintray[_-]?api[_-]?key',
r'b2[_-]?bucket',
r'b2[_-]?app[_-]?key',
r'awssecretkey',
r'awscn[_-]?secret[_-]?access[_-]?key',
r'awscn[_-]?access[_-]?key[_-]?id',
r'awsaccesskeyid',
r'aws[_-]?ses[_-]?secret[_-]?access[_-]?key',
r'aws[_-]?ses[_-]?access[_-]?key[_-]?id',
r'aws[_-]?secrets',
r'aws[_-]?secret[_-]?key',
r'aws[_-]?secret[_-]?access[_-]?key',
r'aws[_-]?secret',
r'aws[_-]?key',
r'aws[_-]?config[_-]?secretaccesskey',
r'aws[_-]?config[_-]?accesskeyid',
r'aws[_-]?access[_-]?key[_-]?id',
r'aws[_-]?access[_-]?key',
r'aws[_-]?access',
r'author[_-]?npm[_-]?api[_-]?key',
r'author[_-]?email[_-]?addr',
r'auth0[_-]?client[_-]?secret',
r'auth0[_-]?api[_-]?clientsecret',
r'auth[_-]?token',
r'assistant[_-]?iam[_-]?apikey',
r'artifacts[_-]?secret',
r'artifacts[_-]?key',
r'artifacts[_-]?bucket',
r'artifacts[_-]?aws[_-]?secret[_-]?access[_-]?key',
r'artifacts[_-]?aws[_-]?access[_-]?key[_-]?id',
r'artifactory[_-]?key',
r'argos[_-]?token',
r'apple[_-]?id[_-]?password',
r'appclientsecret',
r'app[_-]?token',
r'app[_-]?secrete',
r'app[_-]?report[_-]?token[_-]?key',
r'app[_-]?bucket[_-]?perm',
r'apigw[_-]?access[_-]?token',
r'apiary[_-]?api[_-]?key',
r'api[_-]?secret',
r'api[_-]?key[_-]?sid',
r'api[_-]?key[_-]?secret',
r'api[_-]?key',
r'aos[_-]?sec',
r'aos[_-]?key',
r'ansible[_-]?vault[_-]?password',
r'android[_-]?docs[_-]?deploy[_-]?token',
r'anaconda[_-]?token',
r'amazon[_-]?secret[_-]?access[_-]?key',
r'amazon[_-]?bucket[_-]?name',
r'alicloud[_-]?secret[_-]?key',
r'alicloud[_-]?access[_-]?key',
r'alias[_-]?pass',
r'algolia[_-]?search[_-]?key[_-]?1',
r'algolia[_-]?search[_-]?key',
r'algolia[_-]?search[_-]?api[_-]?key',
r'algolia[_-]?api[_-]?key[_-]?search',
r'algolia[_-]?api[_-]?key[_-]?mcm',
r'algolia[_-]?api[_-]?key',
r'algolia[_-]?admin[_-]?key[_-]?mcm',
r'algolia[_-]?admin[_-]?key[_-]?2',
r'algolia[_-]?admin[_-]?key[_-]?1',
r'air[-_]?table[-_]?api[-_]?key',
r'adzerk[_-]?api[_-]?key',
r'admin[_-]?email',
r'account[_-]?sid',
r'access[_-]?token',
r'access[_-]?secret',
r'access[_-]?key[_-]?secret',
r'access[_-]?key',
r'account[_-]?(name|key)?',
r'password',
r'username',
# 6 个通配符模式 (HeartK lines 703-708)
r'[\w_-]*?password[\w_-]*?',
r'[\w_-]*?username[\w_-]*?',
r'[\w_-]*?accesskey[\w_-]*?',
r'[\w_-]*?secret[\w_-]*?',
r'[\w_-]*?bucket[\w_-]*?',
r'[\w_-]*?token[\w_-]*?',
# 华为 OSS (HeartK line 710)
r'huawei\.oss\.(ak|sk|bucket\.name|endpoint|local\.path)',
]
# 编译: 把 702 个 key name 合并为一个大正则 (性能远优于逐个匹配)
# 模板: ["']?KEY["']?[^\S\r\n]*[=:][^\S\r\n]*["']?[\w-]+["']?
_NUCLEI_COMPILED = re.compile(
r"""["']?(?:""" + '|'.join(_NUCLEI_KEY_NAMES) + r""")["']?[^\S\r\n]*[=:][^\S\r\n]*["']?[\w\-]+["']?""",
re.IGNORECASE
)
# 24 个特殊格式凭证正则 (1:1 移植自 HeartK background.js lines 709-737)
_SPECIAL_SECRETS = [
# 私钥头 (line 709)
re.compile(r"""["']?[-]+BEGIN \w+ PRIVATE KEY[-]+""", re.IGNORECASE),
# 阿里云 AK (line 713)
re.compile(r"""LTAI[A-Za-z\d]{12,30}"""),
# 腾讯云 SecretId (line 714)
re.compile(r"""AKID[A-Za-z\d]{13,40}"""),
# 京东云 AK (line 715)
re.compile(r"""JDC_[0-9A-Z]{25,40}"""),
# AWS AK (line 716)
re.compile(r"""(?:A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}"""),
# 火山引擎 AK (line 717)
re.compile(r"""(?:AKLT|AKTP)[a-zA-Z0-9]{35,50}"""),
# 火山引擎 AKLT 变体 (line 718)
re.compile(r"""AKLT[a-zA-Z0-9\-_]{16,28}"""),
# Google API key (line 719)
re.compile(r"""AIza[0-9A-Za-z_\-]{35}"""),
# Bearer token (line 720)
re.compile(r"""[Bb]earer\s+[a-zA-Z0-9\-=._+/\\]{20,500}"""),
# Basic auth (line 721)
re.compile(r"""[Bb]asic\s+[A-Za-z0-9+/]{18,}={0,2}"""),
# Authorization header (line 722)
re.compile(r"""["'\[]*[Aa]uthorization["'\]]*\s*[:=]\s*['"]?\b(?:[Tt]oken\s+)?[a-zA-Z0-9\-_+/]{20,500}['"]?"""),
# GitLab PAT (line 723)
re.compile(r"""glpat-[a-zA-Z0-9\-=_]{20,22}"""),
# GitHub tokens (line 724)
re.compile(r"""(?:ghp|gho|ghu|ghs|ghr|github_pat)_[a-zA-Z0-9_]{36,255}"""),
# APID (line 725)
re.compile(r"""APID[a-zA-Z0-9]{32,42}"""),
# 微信 AppID (line 726)
re.compile(r"""["'](wx[a-z0-9]{15,18})["']"""),
# 企业微信 CorpID (line 727)
re.compile(r"""["'](ww[a-z0-9]{15,18})["']"""),
# 微信公众号 GH ID (line 728)
re.compile(r"""["'](gh_[a-z0-9]{11,13})["']"""),
# 硬编码密码 (line 729)
re.compile(
r"""(?:admin_?pass|password|[a-z]{3,15}_?password|user_?pass|user_?pwd|admin_?pwd)"""
r"""\\?['"]*\s*[:=]\s*\\?['"][a-z0-9!@#$%&*]{5,20}\\?['"]""", re.IGNORECASE),
# 企业微信 webhook (line 730)
re.compile(r"""https://qyapi\.weixin\.qq\.com/cgi-bin/webhook/send\?key=[a-zA-Z0-9\-]{25,50}""", re.IGNORECASE),
# 钉钉 webhook (line 731)
re.compile(r"""https://oapi\.dingtalk\.com/robot/send\?access_token=[a-z0-9]{50,80}""", re.IGNORECASE),
# 飞书 webhook (line 732)
re.compile(r"""https://open\.feishu\.cn/open-apis/bot/v2/hook/[a-z0-9\-]{25,50}""", re.IGNORECASE),
# Slack webhook (line 733)
re.compile(r"""https://hooks\.slack\.com/services/[a-zA-Z0-9\-_]{6,12}/[a-zA-Z0-9\-_]{6,12}/[a-zA-Z0-9\-_]{15,24}""", re.IGNORECASE),
# Grafana API key (line 734)
re.compile(r"""eyJrIjoi[a-zA-Z0-9\-_+/]{50,100}={0,2}"""),
# Grafana Cloud token (line 735)
re.compile(r"""glc_[A-Za-z0-9\-_+/]{32,200}={0,2}"""),
# Grafana SA token (line 736)
re.compile(r"""glsa_[A-Za-z0-9]{32}_[A-Fa-f0-9]{8}"""),
]
# ══════════════════════════════════════════
# 分析函数
# ══════════════════════════════════════════
def _strip_quotes(items):
"""去除匹配结果前后的引号"""
out = []
for s in items:
if isinstance(s, tuple):
s = s[0]
s = str(s)
if s and s[0] in ("'", '"'):
s = s[1:]
if s and s[-1] in ("'", '"'):
s = s[:-1]
if s:
out.append(s)
return out
def analyze_js(js_content, max_len=2_000_000):
"""
分析单个 JS 文件内容,提取敏感信息。
"""
if len(js_content) > max_len:
js_content = js_content[:max_len]
result = {}
# 1) 提取基础信息 (sfz, mobile, mail, ip, ip_port, domain, jwt, algorithm)
for key, pat in _PATTERNS.items():
if key == "algorithm":
cleaned = list({m.group(1) for m in pat.finditer(js_content)})
else:
matches = pat.findall(js_content)
cleaned = list(set(_strip_quotes(matches))) if matches else []
result[key] = sorted(cleaned) if cleaned else []
# 2) URL 提取 — 8 组新规则 (独立于 domain)
url_set = set()
for pat in _URL_PATTERNS:
for m in pat.finditer(js_content):
url_set.add(m.group(1).strip())
# 3) 静态资源提取 — 8 组新规则 (独立提取)
static_set = set()
for pat in _STATIC_PATTERNS:
for m in pat.finditer(js_content):
v = m.group(1).strip()
# 过滤源码文件后缀
if not any(v.endswith(ext) for ext in _STATIC_FILTER_EXTS):
static_set.add(v)
# 4) 从 URL 结果中去除已匹配为静态资源的项
url_set -= static_set
# 5) 路径后处理: 去除 static/url 重复项、过滤噪音后缀、去除 http(s):// 开头的
if result.get("path"):
result["path"] = [
p for p in result["path"]
if p not in static_set
and p not in url_set
and not any(p.endswith(ext) for ext in _STATIC_FILTER_EXTS)
and not p.startswith(("http://", "https://", "//"))
]
# 6) 合并 domain 到 url
if result["domain"]:
url_set.update(result["domain"])
result["url"] = sorted(url_set)
result["static"] = sorted(static_set)
# 6) secret 检测 — nuclei 规则
secrets = set()
for m in _NUCLEI_COMPILED.finditer(js_content):
secrets.add(m.group(0).strip())
for pat in _SPECIAL_SECRETS:
for m in pat.finditer(js_content):
secrets.add(m.group(0).strip())
result["secret"] = sorted(secrets)
# 7) OSS 云存储检测 — 从所有 URL 类结果中提取
oss_set = set()
for item in list(url_set) + list(static_set) + result.get("domain", []):
if _OSS_DOMAIN_RE.search(item):
oss_set.add(item)
# 也直接扫描原文中的 OSS 域名
for m in _OSS_DOMAIN_RE.finditer(js_content):
oss_set.add(m.group(0))
result["oss"] = sorted(oss_set)
return result
def merge_results(results_list):
"""合并多个 analyze_js 结果。"""
all_keys = list(CATEGORY_INFO.keys())
merged = {k: [] for k in all_keys}
for r in results_list:
for k in all_keys:
vals = r.get(k, [])
if vals:
merged[k].extend(vals)
for k in all_keys:
merged[k] = sorted(set(merged[k]))
return merged
# ══════════════════════════════════════════
# 类别中文名 & 图标
# ══════════════════════════════════════════
CATEGORY_INFO = {
"sfz": ("身份证号", "\u2b24"),
"mobile": ("手机号码", "\u260e"),
"mail": ("邮箱地址", "\u2709"),
"ip": ("IP 地址", "\u2316"),
"ip_port": ("IP:端口", "\u2316"),
"domain": ("域名", "\u2b21"),
"path": ("路径", "\u2192"),
"url": ("URL 链接", "\u26d3"),
"jwt": ("JWT Token", "\u2b24"),
"algorithm": ("加密算法", "\u2638"),
"secret": ("凭证泄露", "\u26a0"),
"static": ("静态资源", "\u25cb"),
"oss": ("OSS 云存储", "\u2601"),
}
# ══════════════════════════════════════════
# 历史记录
# ══════════════════════════════════════════
def _reports_dir(base_dir):
d = os.path.join(base_dir, "scan_reports")
os.makedirs(d, exist_ok=True)
return d
def save_report(base_dir, appid, result, js_count=0, total_size=0, name=""):
"""保存分析报告到 scan_reports/ 目录"""
report = {
"appid": appid,
"name": name,
"time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"timestamp": int(time.time()),
"js_count": js_count,
"total_size": total_size,
"result": result,
"summary": {k: len(v) for k, v in result.items()},
}
d = _reports_dir(base_dir)
fname = f"{appid}_{int(time.time())}.json"
path = os.path.join(d, fname)
with open(path, "w", encoding="utf-8") as f:
json.dump(report, f, indent=2, ensure_ascii=False)
return path
def load_reports(base_dir):
"""加载所有历史报告,按时间降序"""
d = _reports_dir(base_dir)
reports = []
for f in os.listdir(d):
if not f.endswith(".json"):
continue
try:
with open(os.path.join(d, f), "r", encoding="utf-8") as fp:
r = json.load(fp)
r["_filename"] = f
reports.append(r)
except Exception:
pass
reports.sort(key=lambda x: x.get("timestamp", 0), reverse=True)
return reports
def delete_report(base_dir, filename):
"""删除指定报告"""
path = os.path.join(_reports_dir(base_dir), filename)
if os.path.exists(path):
os.remove(path)