[
  {
    "path": ".gitignore",
    "content": "config.json\nresult.html\ngithub\n__pycache__/\n*.pyc\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2019 Macr0phag3\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# github_monitor\n\n## 项目介绍\n由于很多猪队友的存在，公司敏感信息通过 GitHub 泄露出去是很常见的。这个项目主要根据关键字与 hosts 生成的关键词，利用 github 提供的 api，监控 git 泄漏，并在检测到信息泄露的时候发送邮件通知。\n\n## 特性\n1. 对于泄露有对应的泄漏定级，可作为严重性的参考\n2. 简单却完善：利用 api 获取 GitHub 的搜索结果是最简单高效的方式，加上关键词的限定，保证不超过 GitHub 的 api 限制\n3. 注释比较详细，可以很快地进行定制\n4. 自动组合关键字\n\n## 快速开始\n### 依赖\n- pip install PyGithub\n- pip install jinja2\n\n### 配置\n- 在项目文件夹下新建一个 `config.json` 文件，按照 `spider.py` 里的注释配置。config.json 的示例：\n```\n{\n  \"hosts\" : [\n    \"*********.com\",\n    \"*********.com @\",\n],\n\n\"sender_email\":{\n  \"uname\":\"*********@qq.com\",\n  \"smtp\":\"smtp.qq.com\",\n  \"port\":25,\n  \"passwd\":\"*********\"\n},\n\n\"receiver_email\":[\n  \"*********@qq.com\",\n  \"*********@qq.com\"\n],\n\n\"token\":\"*******************\",\n\"admin_email\":\"*********@qq.com\"\n}\n```\nhosts 中，带 `@` 的说明是邮件类型，在代码中会进行特殊处理，详细处理可见代码\n\n- `spider.py` 中的 `file_url` 可能需要修改\n\n### 运行方式\n- crontab 一个小时运行一次 或者直接 python spider.py\n\n## 代码主要逻辑\n![代码主要逻辑](https://github.com/Macr0phag3/GithubMonitor/raw/master/pics/pic2.jpg)\n\n\n## 结果示例\n![结果示例](https://github.com/Macr0phag3/GithubMonitor/raw/master/pics/pic1.jpg)\n\n## 一些想法\n个人认为，github 监控最难的在于如何判断检索到的数据是否含有泄露的敏感信息，这是一个很难的问题。\n\n对于攻击方来说，一般只是为了利用泄露信息，那么对于 github 泄密的判断，只需要有就行。假如一共 100 条信息，能检测到 10 条也是很有价值的。当然，发现的泄露越多越好，为了达到这一目的，甚至可以上机器学习，提高对敏感信息的判断力。误报率比较低（谁都不想兴冲冲地去看泄露信息结果发现 `password: \"********\"` :D ）。\n\n**而我这个代码的作用是监控自身公司的泄露。** 对于防守方（公司）检测自身泄露来说，不小心放过一条都意味着很大的风险。换句话说，100 条泄露必须尽可能达到 100% 的检测率，甚至不惜以误报率换取准确率。所以，让代码去判断泄露是很无力的，需要人眼过一遍。那么问题来了，那么多数据，人眼看不过来怎么办呢。\n\n**提高监控关键字的精确性。** 举个例子，假如你的公司域名/ip 为 qq.com/1.1.1.1，那么最好在监控的关键字附上 qq.com/1.1.1.1 这样。类似的方法有很多（自己公司的文件应该有一些特征的。当然肯定有特殊情况，特殊对待吧），目的是减少搜索结果，能提高精确性，降低人的负担。如果你检测的是 `qiniu.com password` 你会发现每一轮都会有大量的数据，所以别用模糊的关键字。\n\n这一方法还解决了 github api 只能拿到前 1000 个搜索结果（不是页面）的问题，搜索结果少意味着更新的数据也不会多，不会超过 1000 的限制。如果你检测的是 `password` 你会发现每轮更新的数据都不止 1000 条，这样会产生漏报（万一就是第 1001 条泄露的呢）。\n\n如果你能理解我上面说的，就没必要自己写 github 的爬虫解析页面，直接调用 api 就好了。\n\n**信任已有，监控增量**，对于攻击者来说，会认为已有的 github 数据存在泄露，需要去淘一遍（当然也有监控增量的）。而对于公司来说，是假设现在 github 没有泄露，然后去监控它的增量，不会淘一遍已有的 github 数据。增量数据包含 2 种：\n1. 新增泄露：新 push 的文件\n2. 更新泄露：update 的文件\n\n当然，什么都扛不住猪队友呀 :D\n\n## 更新\n2019-01-07, 可以免费在 github 上创建私有仓库了。\n\n**强烈建议需要保密的仓库更改为私有**\n\n**强烈建议需要保密的仓库更改为私有**\n\n**强烈建议需要保密的仓库更改为私有**\n\n## License\nCopyright © 2018 [Macr0phag3](https://github.com/Macr0phag3).\n\nThis project is MIT licensed.\n\n## Others\n<img src=\"https://clean-1252075454.cos.ap-nanjing.myqcloud.com/20200528120800990.png\" width=\"500\">\n\n[![Stargazers over time](https://starchart.cc/Macr0phag3/GithubMonitor.svg)](https://starchart.cc/Macr0phag3/GithubMonitor)\n"
  },
  {
    "path": "leak_test/leak_test",
    "content": "host = http://yin126.com/\npassword = \"test in 2019-03-03 13:53:44\"\n\n"
  },
  {
    "path": "mysqlite.py",
    "content": "# -*- coding: utf-8 -*-\n\n# 2018.11.23 11:07:22 by Tr0y\n\nimport sqlite3\nimport time\n\n\ndef _get_hour():\n    '''\n    返回上个小时的时间戳\n    假如现在是 2018.11.21 19:44:02， 那么返回 '1542794400'\n    即 2018.11.21 18:00:00 的时间戳\n\n    返回值：\n        字符串；上个小时的时间戳\n    '''\n\n    return int(\n        time.mktime(\n            time.strptime(\n                time.strftime(\"%Y-%m-%d %H\"), \"%Y-%m-%d %H\")\n        )\n    )-3600\n\n\nclass MySqlite:\n    def __init__(self, dbname, tablename):\n        '''\n        初始化\n\n        参数：\n            dbname：字符串；数据库名\n            tablename：字符串；表名\n        '''\n\n        self.dbname = dbname\n        self.tablename = tablename\n\n        self.conn = sqlite3.connect(self.dbname)\n\n        self._create()\n\n    def _create(self):  #\n        '''\n        若数据库不存在，则创建数据库\n        '''\n\n        query = \"\"\"create table IF NOT EXISTS {tablename}(\n            url VARCHAR(100),\n            sha VARCHAR(40),\n            repository VARCHAR(100),\n            keyword VARCHAR(100),\n            filename VARCHAR(100),\n            level VARCHAR(5),\n            update_time VARCHAR(10),\n            last_record_time VARCHAR(10),\n            PRIMARY KEY (url, sha)\n        );\"\"\".format(tablename=self.tablename)  # 不存在才新建\n        self.conn.execute(query)\n        self.conn.commit()\n\n    def _select(self, sql):\n        '''\n        查询\n\n        参数：\n            sql：字符串；查询的语句\n\n        返回值：\n            rows：2 维列表；查询的结果\n        '''\n\n        result = self.conn.execute(sql)\n        self.conn.commit()\n        rows = result.fetchall()\n        return rows  # [(, ... ,), (, ... ,)]\n\n    def _insert(self, url, sha, repository, filename, keyword, level, update_time):\n        '''\n        插入数据\n        column 顺序与参数顺序一致\n        **插入的数据类型均为字符串**\n\n        参数：\n            url：        字符串；代码文件的 url\n            sha：        字符串；代码文件的 sha\n            repository： 字符串；代码文件的仓库\n            filename：   字符串；代码文件的文件名\n            keyword：    字符串；代码文件命中的关键字\n            level：      整数；  泄露级别\n            update_time：字符串；数据库中此记录被更新的时间\n        '''\n\n        data = '''INSERT INTO {tablename}(url, sha, repository, keyword, filename, level, update_time, last_record_time) VALUES('{url}','{sha}','{repository}','{keyword}','{filename}','{level}','{update_time}', '∞');\n        '''.format(\n            tablename=self.tablename,\n            url=url,\n            sha=sha,\n            repository=repository,\n            keyword=keyword,\n            filename=filename,\n            level=level,\n            update_time=update_time\n        )\n\n        self.conn.execute(data)\n        self.conn.commit()\n\n    def _update(self, url, sha, repository, filename, keyword, level, update_time, last_record_time):\n        '''\n        更新数据\n\n        参数：\n            url：             字符串；代码文件的 url\n            sha：             字符串；代码文件的 sha\n            repository：      字符串；代码文件的仓库\n            filename：        字符串；代码文件的文件名\n            keyword：         字符串；代码文件命中的关键字\n            level：           整数；  泄露级别\n            update_time：     字符串；数据库中此记录被更新的时间\n            last_record_time：字符串；数据库中此记录上一次被更新的时间\n        '''\n\n        data = '''UPDATE {tablename} SET url='{url}', sha='{sha}', repository='{repository}', keyword='{keyword}', filename='{filename}', level='{level}', update_time='{update_time}', last_record_time='{last_record_time}' where url='{url}';\n        '''.format(\n            tablename=self.tablename,\n            url=url,\n            sha=sha,\n            repository=repository,\n            keyword=keyword,\n            filename=filename,\n            level=level,\n            update_time=update_time,\n            last_record_time=last_record_time\n        )\n\n        self.conn.execute(data)\n        self.conn.commit()\n\n    def Record(self, url, sha, repository, filename, keyword, update_time, negative):\n        '''\n        根据数据库情况，判断新数据记录方式\n\n        参数：\n            url：        字符串；代码文件的 url\n            sha：        字符串；代码文件的 sha\n            repository： 字符串；代码文件的仓库\n            filename：   字符串；代码文件的文件名\n            keyword：    字符串；代码文件命中的关键字\n            update_time：字符串；数据库中此记录被更新的时间\n            negative：   布尔值；是否为误报\n\n        返回值\n            level：整数；泄露级别\n        '''\n\n        result = self._select(\n            '''SELECT url, sha, update_time FROM {tablename} where url='{url}'; '''.format(\n                url=url,\n                tablename=self.tablename\n            ))  # 查询是否存在此 url 的记录\n\n        if result:  # 已存在\n            if result[0][1] != sha:  # 文件 sha 发生变化\n                if negative:\n                    level = 1\n                else:\n                    level = 2\n\n                # 旧的 update_time 作为新的 last_record_time\n                self._update(url, sha, repository, filename, keyword, level, update_time, result[0][2])\n            else:\n                level = 0\n        else:\n            if negative:\n                level = 1\n            else:\n                level = 3\n\n            self._insert(url, sha, repository, filename, keyword, level, update_time)\n\n        return level\n\n    def Get_Data(self, keyword, level):\n        '''\n        获取上一轮的泄露记录\n\n        参数：\n            keyword：字符串；关键字\n            level：字符串；泄露级别\n\n        返回值：\n            result：2 维列表；泄露记录\n        '''\n\n        last_hour_time = _get_hour()\n        result = self._select('''SELECT * FROM {tablename} where keyword='{keyword}' and update_time>='{last_hour_time}' and update_time<'{now_hour_time}' and level='{level}';\n        '''.format(\n            tablename=self.tablename,\n            keyword=keyword,\n            level=level,\n            last_hour_time=last_hour_time,\n            now_hour_time=last_hour_time+3600  # 加个小于当前小时的限制，防止此轮刚更新就报告\n        ))\n\n        for i, r in enumerate(result):\n            result[i] = list(result[i])  # tuple 转 list\n            result[i][-1] = r[-1] if r[-1] == \"∞\" else time.strftime(\n                \"%Y-%m-%d %H:%M:%S\", time.localtime(  # 时间戳转成可读性的时间\n                    int(r[-1])\n                )\n            )\n\n        return result\n"
  },
  {
    "path": "reporter.py",
    "content": "# -*- coding: utf-8 -*-\n\n# 2018.11.23 11:07:07 by nobody\n\nimport smtplib\nfrom email.mime.multipart import MIMEMultipart\nfrom email.mime.text import MIMEText\n\n\nclass Reporter:\n    def __init__(self, email_from, smtp_server, smtp_port, email_username=None, email_password=None):\n        self.email_from = email_from\n        self.smtp_server = smtp_server\n        self.smtp_port = smtp_port\n        self.email_username = email_username\n        self.email_password = email_password\n        self.sent_emails_counter = 0\n\n    def _send_email(self, email, email_to_string):\n        mail_server = smtplib.SMTP(host=self.smtp_server, port=self.smtp_port)\n        if int(self.smtp_port) != 25:\n            mail_server.starttls()\n        if self.email_username is not None:\n            mail_server.login(self.email_username, self.email_password)\n        try:\n            mail_server.sendmail(self.email_from, email_to_string, email.as_string())\n            self.sent_emails_counter += 1\n        finally:\n            mail_server.close()\n\n    def alert(self, content, email_to_string):\n        email = MIMEMultipart('alternative')\n        email['Subject'] = \"Github Monitor\"\n        email['From'] = self.email_from\n        email['To'] = email_to_string\n        part_html = MIMEText(content, 'html', 'utf-8')\n        email.attach(part_html)\n\n        self._send_email(email, email_to_string)\n"
  },
  {
    "path": "spider.py",
    "content": "# -*- coding: utf-8 -*-\n\n# running by py3.x\n# 2018.11.23 11:07:22 by Tr0y\n\nimport json\nimport random\nimport time\nimport traceback\n\nfrom github import Github  # pip install PyGithub\nfrom jinja2 import Template  # pip install jinja2\n\nimport mysqlite\nfrom reporter import Reporter\n\n\ndef GenerateKeywords(hosts):\n    '''\n    hosts * key\n    n*n 种组合的关键字\n    其中 host 带 @ 的还要加上 smtp 关键字\n\n    host 的格式为：\n        www.baidu.com\n        或者\n        www.baidu.com @\n\n    参数：\n        hosts：列表；监控的域名\n    返回值：\n        keywords：列表；生成的关键字\n    '''\n\n    key = [\"password\", \"passwd\", \"密码\"]\n    keywords = []\n\n    for h in hosts:\n        if \"@\" in h:\n            h = h.split(\"@\")[0] + \" smtp\"\n\n        for k in key:\n            keywords.append(h + \" \" + k)\n\n    return keywords\n\n\ndef GenerateHTML(results):\n    '''\n    利用模板生成报告(results)\n\n    参数：\n        results：字典；本轮发现的泄露\n\n    返回值：\n        c：字符串；生成的 HTML 源码\n    '''\n\n    with open(file_url + \"template.html\", \"r\") as fp:\n        template = Template(fp.read())\n        c = template.render(\n            results=results,\n        )\n\n    return c\n\n\nclass GithubMonitor:\n    '''\n    Github 泄露监控\n    '''\n\n    def __init__(self, keywords, token):\n        '''\n        初始化\n\n        参数：\n            keywords：列表；要搜索的关键字\n            token：字符串；用于授权使用 Github 的 api\n        '''\n\n        self.keywords = keywords\n        self.token = token\n\n        self.no_update = 0  # 连续旧记录的数量\n\n        self.github = Github(self.token)\n\n    def _analysis_page(self, result, keyword):\n        '''\n        处理搜索页面\n\n        参数：\n            result：实例；搜索页面返回的结果\n            keyword：字符串；关键字\n        '''\n\n        page_id = 0\n\n        # 0-33 页，每页 30 个结果\n        # 对应 github 的 1000 个结果的限制\n        while page_id < 34:\n            try:\n                items = result.get_page(page_id)  # 获取页面的详细记录\n                ana_result = self._analysis_result(items, keyword)\n                if not ana_result:\n                    print(\"[WARNING] 连续 30 条数据都没有更新\")\n                    print(\"[WARNING] 在第{}页退出\".format(page_id))\n                    break\n\n                elif ana_result is None:\n                    print(\"[WARNING] 搜索页面为空\")\n                    print(\"[WARNING] 在第{}页退出\".format(page_id))\n                    break\n\n            except Exception as e:\n                err = str(e)\n\n                # 速度过快会触发 github 的爬虫检测\n                if \"You have triggered an\" in err:\n                    sleep_time = random.randint(20, 60)\n                    print(\"[WARNING] Too fast! Sleep for {}s\".format(sleep_time))\n                    time.sleep(sleep_time)  # sleep 一会\n                    continue\n\n                elif \"timed out\" in err:\n                    # 出现 time out 则重复运行（page_id 不变）\n                    print(\"[WARNING] Read data time out! Just repeat it\")\n                    continue\n\n                elif \"Server Error\" in err:\n                    print(\"[WARNING] Github Server Error! Just repeat it\")\n                    continue\n\n                elif \"Connection aborted.\" in err:\n                    # Connection aborted 则重复\n                    print(\n                        \"[WARNING] Remote end closed connection without response! Just repeat it\")\n                    continue\n\n                elif \"Unexpected problem\" in err:\n                    print(\"[WARNING] Unexpected problem! Just repeat it\")\n                    continue\n\n                else:\n                    # 其他错误则发邮件报告异常\n                    err = traceback.format_exc()\n                    # 打印出来，以便在日志中看到\n                    print(\"[ERROR] Something went wrong!\\n\" + err)\n                    r.alert(\n                        \"Github Monitor ERROR: Something went wrong!\\n\\n\" + err, admin_email)\n                    raise  # 释放异常，强制停止脚本\n\n            page_id += 1\n\n        print(\"[INFO] 结束关键字: \" + keyword + \"\\n\\n\")\n\n    def _analysis_result(self, items, keyword):\n        '''\n        分析搜索页面\n        '''\n\n        result_id = 0\n\n        result_count = len(items)\n        if not result_count:  # 结果为空\n            return None\n\n        while result_id < result_count:\n            item = items[result_id]\n            try:\n                if all(list([kw in item.decoded_content.decode(\"utf8\") for kw in keyword.split(\" \")])):\n                    negative = False  # 关键字 不 都存在，疑似误报\n                else:\n                    negative = True\n\n                url = \"https://www.github.com/\" + \\\n                    item.repository.full_name + \"/blob/master/\" + item.path\n\n                update_time = str(int(time.time()))\n                record_result = DB.Record(  # 扔给 Record 处理\n                    url,\n                    item.sha,\n                    item.repository.full_name,  # repository\n                    item.path,  # filename\n                    keyword,\n                    update_time,\n                    negative,\n                )\n\n                if record_result == 3:  # 新泄露\n                    self.no_update = 0\n\n                elif record_result == 2:  # 更新泄露\n                    self.no_update = 0\n\n                elif record_result == 1:  # 疑似误报\n                    self.no_update = 0\n\n                else:  # 旧的数据（一个小时之前爬过）\n                    if self.no_update > 30:\n                        # 连续 30 条记录都是旧的数据说明后面的数据也是旧的\n                        return False\n\n                    self.no_update += 1\n\n            except Exception as e:\n                err = str(e)\n\n                # 速度过快触发 github 的爬虫检测就 sleep 一会\n                if \"You have triggered an\" in err:\n                    sleep_time = random.randint(20, 60)\n                    print(\"sleep for {}s\".format(sleep_time))\n                    time.sleep(sleep_time)\n                    continue\n\n                elif \"timed out\" in err:\n                    print(\"[WARNING] Read data time out! Just repeat it\")\n                    continue\n\n                elif \"Server Error\" in err:\n                    print(\"[WARNING] Github Server Error! Just repeat it\")\n                    continue\n\n                elif \"Unexpected problem\" in err:\n                    print(\"[WARNING] Unexpected problem! Just repeat it\")\n                    continue\n\n                elif \"Connection aborted.\" in err:\n                    # Connection aborted 则重复\n                    print(\n                        \"[WARNING] Remote end closed connection without response! Just repeat it\"\n                    )\n                    continue\n\n                elif \"Not Found\" in err:\n                    # 跳过 Not Found\n                    print(\"[WARNING] File not found! Just pass it\")\n\n                else:\n                    # 出现其他错误的时候扔给 analysis_page() 中的异常检测处理\n                    raise\n\n            result_id += 1\n\n        return True\n\n    def search(self):\n        '''\n        根据关键字搜索 Github 上的代码\n        '''\n\n        for keyword in self.keywords:\n            result = self.github.search_code(\n                keyword,  # 关键字\n                sort=\"indexed\",  # 按最新的索引记录排序\n                order=\"desc\",  # 最新的索引放在最前面\n            )\n\n            self._analysis_page(result, keyword)\n\n\n# --------------------- 可能需要修改 ----------------------\nfile_url = \"./\"\nDB = mysqlite.MySqlite(file_url + \"github\", \"leak\")\n# -------------------------------------------------------\n\n# 读取配置\n# 将配置放在单独的 json 文件中\n# 再设置 .gitgnore 防止泄露\nwith open(file_url + \"config.json\", \"r\") as fp:\n    config = json.load(fp)\n\nhosts = config[\"hosts\"]  # 监控的 host\n\nadmin_email = config[\"admin_email\"]  # 管理员邮箱（报错的时候通知）\n\ntoken = config[\"token\"]  # Github token\nr = Reporter(\n    config[\"sender_email\"][\"uname\"],\n    config[\"sender_email\"][\"smtp\"],\n    config[\"sender_email\"][\"port\"],\n    config[\"sender_email\"][\"uname\"],\n    config[\"sender_email\"][\"passwd\"]\n)\n\nkeywords = GenerateKeywords(hosts)\n\nMonitor = GithubMonitor(keywords, token)\nMonitor.search()\n\nsend_flag = 0\nresults = {}\nfor keyword in keywords:\n    results[keyword] = []\n    empty = True\n    for level in range(3, 0, -1):\n        result = DB.Get_Data(keyword, level)  # 获取上一轮的泄漏记录\n        if result:\n            send_flag = 1\n            results[keyword].append(result)\n            empty = False\n        else:\n            results[keyword].append([(None, ) * 7 + (\"∞\",)])\n\n    if empty:  # 不汇报无泄漏的关键字\n        results.pop(keyword)\n\nDB.conn.close()\n\nif send_flag:  # 为 0 时说明 所有关键字都无泄漏\n    print(\"[Info] Send email\")\n    c = GenerateHTML(results)\n\n    for email_addr in config[\"receiver_email\"]:\n        r.alert(c, email_addr)\n\n    with open(file_url + \"result.html\", 'w') as fp:\n        fp.write(c)\nelse:\n    print(\"[Info] Nothing to do\")\n\n'''\nresults 示例：\n\n{'qiniu 密码': [('www.github.com/nicoson/CNR-Video-Audit/blob/master/README.md',\n   '0f00caf3b2bc2828428b568148b1939bdce5f6c6',\n   'nicoson/CNR-Video-Audit',\n   'qiniu 密码',\n   'README.md',\n   '3',\n   '1542811078',\n   '∞'),\n\n  ('www.github.com/Macr0phag3/github_monitor/blob/master/template.html',\n   'e7e35a1fd081e31675a2644fbe91d56356f5e74d',\n   'Macr0phag3/github_monitor',\n   'qiniu 密码',\n   'template.html',\n   '3',\n   '1542811744',\n   '∞'),\n\n  ('www.github.com/Macr0phag3/github_monitor/blob/master/spider.py',\n   '2b3fd456e58eb5dc0ee6d72b98a9494f7dda9423',\n   'Macr0phag3/github_monitor',\n   'qiniu 密码',\n   'spider.py',\n   '2',\n   '1542811745',\n   '∞'),\n\n  ('www.github.com/shuaizhupeiqi/shuaizhupeiqi.github.io/blob/master/page/2/index.html',\n   '413fc90095643fa9e0acc0e5bdb8a6d7c116fc3a',\n   'shuaizhupeiqi/shuaizhupeiqi.github.io',\n   'qiniu 密码',\n   'page/2/index.html',\n   '2',\n   '1542811520',\n   '∞')]}\n'''\n"
  },
  {
    "path": "template.html",
    "content": "<!-- 2018.11.23 11:07:22 by Tr0y -->\n\n<title>Github Leak Report</title>\n\n{% for keyword, value in results.items() %}\n    <h1>命中关键字：{{ keyword }}</h1>\n\n    <h2>新发现泄露(Level 3)</h2>\n    <table border=\"0\" cellpadding=\"5\" cellspacing=\"1\" bgcolor=\"black\">\n        <tr align=\"center\" bgcolor=\"white\">\n          <td>ID</td>\n          <td>File SHA</td>\n          <td>Code Location</td>\n          <td>Last Record Time</td>\n        </tr>\n\n        {% for item in value[0] %}\n          <tr align=\"center\" bgcolor=\"white\">\n              <td>{{ loop.index }}</td>\n              <td>{{ item[1] }}</td>\n              <td><a href={{ item[0] }}> {{ item[2] }} </a></td>\n              <td>{{ item[7] }}</a></td>\n          </tr>\n        {% endfor %}\n    </table>\n\n    <h2>更新泄露(Level 2)</h2>\n    <table border=\"0\" cellpadding=\"5\" cellspacing=\"1\" bgcolor=\"black\">\n      <tr align=\"center\" bgcolor=\"white\">\n          <td>ID</td>\n          <td>File SHA</td>\n          <td>Code Location</td>\n          <td>Last Record Time</td>\n      </tr>\n\n      {% for item in value[1] %}\n      <tr align=\"center\" bgcolor=\"white\">\n          <td>{{ loop.index }}</td>\n          <td>{{ item[1] }}</td>\n          <td><a href={{ item[0] }}> {{ item[2] }} </a></td>\n          <td>{{ item[7] }}</a></td>\n      </tr>\n      {% endfor %}\n    </table>\n\n    <h2>疑似误报(Level 1)</h2>\n    <table border=\"0\" cellpadding=\"5\" cellspacing=\"1\" bgcolor=\"black\">\n      <tr align=\"center\" bgcolor=\"white\">\n          <td>ID</td>\n          <td>File SHA</td>\n          <td>Code Location</td>\n          <td>Last Record Time</td>\n      </tr>\n\n      {% for item in value[2] %}\n      <tr align=\"center\" bgcolor=\"white\">\n          <td>{{ loop.index }}</td>\n          <td>{{ item[1] }}</td>\n          <td><a href={{ item[0] }}> {{ item[2] }} </a></td>\n          <td>{{ item[7] }}</a></td>\n      </tr>\n      {% endfor %}\n    </table>\n\n    <br>\n    <hr size=3 noshade/>\n\n{% endfor %}\n\n<!--\n0: url\n1: sha\n2: repository\n3: keyword\n4: filename\n5: level\n6: update_time\n7: last_record_time\n-->\n"
  }
]