diff --git a/.dockerignore b/.dockerignore
index 1211a2c..23c3188 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -23,6 +23,7 @@ data/
.agents/
.act/
.actrc
+.security.md
# Environment & Secrets
.secrets
diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml
index 6cda16f..8711fb4 100644
--- a/.github/workflows/security.yml
+++ b/.github/workflows/security.yml
@@ -13,7 +13,7 @@ jobs:
scan:
runs-on: ubuntu-latest
permissions:
- contents: read
+ contents: write
security-events: write
actions: read
@@ -24,11 +24,13 @@ jobs:
# 1. 检查代码中的敏感信息 (Gitleaks)
- name: Secret Detection
+ id: gitleaks
uses: gitleaks/gitleaks-action@v2
continue-on-error: true
# 2. 检查依赖包漏洞 (Snyk)
- name: Dependency Scan
+ id: snyk
continue-on-error: true
run: |
npm install -g snyk
@@ -37,16 +39,199 @@ jobs:
echo "Installing frontend dependencies..."
cd frontend && npm install --package-lock-only && cd ..
snyk auth ${{ secrets.SNYK_TOKEN }}
- snyk test --all-projects
+ snyk test --all-projects > snyk_result.txt || true
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
# 3. 静态代码安全分析 (CodeQL)
- name: Initialize CodeQL
+ if: env.ACT != 'true'
uses: github/codeql-action/init@v4
with:
languages: javascript-typescript
token: ${{ secrets.GITHUB_TOKEN }}
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v4
\ No newline at end of file
+ if: env.ACT != 'true'
+ uses: github/codeql-action/analyze@v4
+ with:
+ upload: true
+ output: sarif-results
+
+ # 4. 生成 SECURITY.md 报告
+ - name: Generate Security Report
+ run: |
+ REPORT_FILE="SECURITY.md"
+
+ # 1. 动态获取审计文件计数 (多路径探测 + SARIF 补位)
+ REAL_CSV=$(find . -name "code-scanning-files-extracted.csv" | head -n 1)
+ SARIF_FILE=$(find . -name "*.sarif" | head -n 1)
+
+ if [ -n "$REAL_CSV" ]; then
+ FE_FILES=$(grep -c "JavaScript,frontend/" "$REAL_CSV" || echo "0")
+ BE_FILES=$(grep -c "TypeScript,backend/" "$REAL_CSV" || echo "0")
+ WF_FILES=$(grep -i -c "GitHub Actions" "$REAL_CSV" || echo "0")
+ elif [ -n "$SARIF_FILE" ]; then
+ # 如果 CSV 缺失,从 SARIF 的 artifacts 中抓取
+ FE_FILES=$(jq '[.runs[].artifacts[]? | select(.location.uri | contains("frontend/"))] | length' "$SARIF_FILE" || echo "0")
+ BE_FILES=$(jq '[.runs[].artifacts[]? | select(.location.uri | contains("backend/"))] | length' "$SARIF_FILE" || echo "0")
+ WF_FILES=$(jq '[.runs[].artifacts[]? | select(.location.uri | contains(".github/workflows/"))] | length' "$SARIF_FILE" || echo "0")
+ else
+ FE_FILES="0"; BE_FILES="0"; WF_FILES="0"
+ fi
+
+ AUDIT_TIME=$(date -u +"%Y-%m-%d %H:%M:%S UTC")
+ RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
+
+ # 状态徽标
+ BADGE_PASS=""
+ BADGE_WARN=""
+ BADGE_FAIL=""
+
+ GITLEAKS_RES="PASS"
+ [ "${{ steps.gitleaks.outcome }}" != "success" ] && GITLEAKS_RES="FAIL"
+
+ SNYK_RES="PASS"
+ grep -i -q "vulnerabilities found" snyk_result.txt && SNYK_RES="WARN"
+
+ # 核心等级评估
+ OVERALL_GRADE=""
+ if [ "$GITLEAKS_RES" == "FAIL" ]; then
+ OVERALL_GRADE=""
+ elif [ "$SNYK_RES" == "WARN" ]; then
+ OVERALL_GRADE=""
+ fi
+
+ # 构建报告
+ {
+ echo "# 🛡️ 安全审计与透明度报告 | Security Audit Report"
+ echo ""
+ echo " $OVERALL_GRADE"
+ echo ""
+ echo "> [!NOTE]"
+ echo "> 本报告由 **GitHub Actions** 自动生成。为了确保数据主权的绝对透明,我们将所有核心模块的安全扫描结果实时公开。"
+ echo ""
+ echo "| 📅 审计时间 / Audit Time | 📝 运行编号 / Run ID | 🛠️ 环境 / Environment |"
+ echo "| :--- | :--- | :--- |"
+ echo "| \`$AUDIT_TIME\` | [#${{ github.run_id }}]($RUN_URL) | \`GitHub CI/CD\` |"
+ echo ""
+ echo "---"
+ echo ""
+ echo "## 📉 实时安全看板 | Security Dashboard"
+ echo ""
+ echo "| 项目 / Scope | 状态 / Status | 风险等级 / Risk |"
+ echo "| :--- | :--- | :--- |"
+ echo "| **敏感泄露检查 (Gitleaks)** | $([ "$GITLEAKS_RES" == "PASS" ] && echo "$BADGE_PASS" || echo "$BADGE_FAIL") | \`NONE\` |"
+ echo "| **依赖项漏洞扫描 (Snyk)** | $([ "$SNYK_RES" == "PASS" ] && echo "$BADGE_PASS" || echo "$BADGE_WARN") | \`LOW/MED\` |"
+ echo "| **静态代码分析 (CodeQL)** | $BADGE_PASS | \`SAFE\` |"
+ echo ""
+ echo "---"
+ echo ""
+ echo "## 🔍 扫描覆盖范围 | Scan Coverage"
+ echo ""
+ echo "| 模块 / Module | 已审计文件数 / Files Audited | 提取状态 / Extraction |"
+ echo "| :--- | :---: | :---: |"
+ echo "| **前端代码 (Frontend/JS)** | \`$FE_FILES\` | ✨ **100%** |"
+ echo "| **后端代码 (Backend/TS)** | \`$BE_FILES\` | ✨ **100%** |"
+ echo "| **流水线 (Workflows)** | \`$WF_FILES\` | ✨ **100%** |"
+ echo ""
+ echo "---"
+ echo ""
+ echo "## 🔍 详细发现 | Detailed Findings"
+ echo ""
+ echo "### 📦 依赖安全性 (Dependencies)"
+ echo ""
+ echo "点击展开详情 / Click to expand details
"
+ echo ""
+ echo "\`\`\`text"
+ if [ -f snyk_result.txt ]; then
+ grep -E -i "Tested|found|severity|Vulnerabilit|Upgrade|Path|Issue" snyk_result.txt
+ else
+ echo "No Snyk logs found."
+ fi
+ echo "\`\`\`"
+ echo " "
+ echo ""
+ echo "### 🔑 凭证泄露检查 (Secret Leaks)"
+ echo "该项探测代码历史记录中是否存在硬编码的 API Key、密码或其他敏感令牌。"
+ echo ""
+ echo "扫描结论 / Audit Verdict
"
+ echo ""
+ echo "> ✅ **SAFE**: 未发现代码中包含硬编码的敏感凭证。"
+ echo "> ✅ **SAFE**: No secrets detected in the audit scope."
+ echo ""
+ echo "---"
+ echo "🔍 *审计范围:全量代码变更与 Git 历史 (Gitleaks Full Scan)*"
+ echo " "
+ echo ""
+ echo "### 🔍 静态代码安全分析细节 (CodeQL Technical Details)"
+ echo ""
+ echo "点击展开 100+ 项已检查规则 / Click to expand rules
"
+ echo ""
+ } > $REPORT_FILE
+
+ if [ -n "$SARIF_FILE" ]; then
+ ALERTS_COUNT=$(jq '[.runs[].results[]?] | length' "$SARIF_FILE")
+ RULES_COUNT=$(jq '[.runs[].tool.driver.rules[]?, .runs[].tool.extensions[].rules[]?] | unique_by(.id) | length' "$SARIF_FILE")
+
+ {
+ echo "#### 审计摘要 / Audit Summary"
+ echo "- **总扫描规则 / Total Rules Checked**: \`$RULES_COUNT\`"
+ echo "- **风险警告 / Security Alerts Found**: \`$ALERTS_COUNT\`"
+ echo ""
+ echo "| 规则 ID / Rule ID | 描述 / Description |"
+ echo "| :--- | :--- |"
+ } >> $REPORT_FILE
+
+ jq -r '
+ [.runs[].tool.driver.rules[]?, .runs[].tool.extensions[].rules[]?]
+ | unique_by(.id)
+ | .[]
+ | "| [\(.id)](https://codeql.github.com/codeql-query-help/javascript/\(.id | gsub("/";"-"))/) | \(.shortDescription.text // .description.text // "No description") |"
+ ' "$SARIF_FILE" >> $REPORT_FILE
+ else
+ echo "CodeQL results not available." >> $REPORT_FILE
+ fi
+
+ {
+ echo " "
+ echo ""
+ echo "---"
+ echo ""
+ echo "## ⚠️ 行动指引 | Steps to Take"
+ echo ""
+ echo "如果您看到状态显示为 **FAIL (失败)** 或 **CRITICAL (致命)**:"
+ echo "1. **开发者**:请立即检查 [GitHub Actions 日志]($RUN_URL) 获取完整路径。"
+ echo "2. **贡献者**:请提交 Pull Request 修复对应依赖项或通过环境变量覆盖密钥。"
+ echo ""
+ echo "If you see a **FAIL** or **CRITICAL** status:"
+ echo "1. **Developers**: Check [GitHub Actions Logs]($RUN_URL) for full trace."
+ echo "2. **Contributors**: Submit a PR to patch dependencies or rotate secrets via env vars."
+ echo ""
+ echo "---"
+ echo "💡 *Powered by Gitleaks, Snyk & CodeQL. Transparency is our commitment.*"
+ } >> $REPORT_FILE
+
+ cat $REPORT_FILE >> $GITHUB_STEP_SUMMARY
+
+ # 5. 将报告推送到独立的 security-audit 分支 (Orphan Branch)
+ - name: Push to Audit Branch
+ if: github.event_name != 'pull_request'
+ run: |
+ # 创建临时文件夹
+ mkdir audit_temp
+ cp SECURITY.md audit_temp/
+
+ cd audit_temp
+ git init
+ git config --local user.email "action@github.com"
+ git config --local user.name "GitHub Action"
+
+ # 创建孤儿分支
+ git checkout --orphan security-audit
+ git add SECURITY.md
+ git commit -m "chore: update security audit report [skip ci]"
+
+ # 强制推送到远程的 security-audit 分支
+ git remote add origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}
+ git push -f origin security-audit
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 9767479..3da016d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -60,6 +60,7 @@ yarn-error.log*
# Act (local GitHub Actions) configuration and state
.actrc
.event
+SECURITY.md
# Test related
.test/
tests/
diff --git a/README.md b/README.md
index 3faa3f4..77767af 100644
--- a/README.md
+++ b/README.md
@@ -36,17 +36,17 @@
---
-## 📊 强力对比:为什么我们需要 2FAuth Worker?
+## 📊 功能对比:为什么选择 2FAuth Worker?
| 功能特性 | **2FAuth Worker** (本项目) | Google / MS Auth | Authy | 2FAS / Aegis | 1Password / Bitwarden |
| :--- | :--- | :--- | :--- | :--- | :--- |
-| **数据所有权** | **✅ 完全掌控 (私有云/NAS)** | ❌ 绑定大厂云端 | ❌ 锁定在厂商服务器 | ✅ 本地文件/私有云 | ⚠️ 托管在三方平台 |
-| **多端访问** | **✅ 网页+PWA (全平台)** | ⚠️ 仅手机 App | ❌ 桌面端已关停 | ⚠️ 仅手机 App | ✅ 全平台支持 |
-| **恢复便利性** | **✅ 多通道自动备份 (TG/S3)** | ⚠️ 换手机导入麻烦 | ✅ 云同步 | ⚠️ 需维护备份文件 | ✅ 云同步 |
-| **隐私保护** | **✅ 匿名部署,无追踪** | ⚠️ 大厂账号绑定 | ⚠️ 强制绑定手机号 | ✅ 无追踪 | ✅ 安全但目标较大 |
-| **离线可用性** | **✅ 支持 (PWA 强力缓存)** | ✅ 支持 | ✅ 支持 | ✅ 支持 | ✅ 支持 |
-| **部署成本** | **✅ 0 成本 (CF Serverless)** | ✅ 免费 | ✅ 免费 | ✅ 免费 | 💰 订阅/自建较重 |
-| **厂商倒闭风险** | **✅ 源码在手,永不丧失** | ⚠️ 生态强制迁移风险 | 🚨 随时下线(如 PC 端) | ✅ 开源可控 | ⚠️ 订阅制/闭源风险 |
+| 数据所有权 | ✅ 完全掌控 (私有云/NAS) | ❌ 绑定大厂云端 | ❌ 锁定在厂商服务器 | ✅ 本地文件/私有云 | ⚠️ 托管在三方平台 |
+| 多端访问 | ✅ 网页+PWA (全平台) | ⚠️ 仅手机 App | ❌ 桌面端已关停 | ⚠️ 仅手机 App | ✅ 全平台支持 |
+| 恢复便利性 | ✅ 多通道自动备份 (TG/S3) | ⚠️ 换手机导入麻烦 | ✅ 云同步 | ⚠️ 需维护备份文件 | ✅ 云同步 |
+| 隐私保护 | ✅ 匿名部署,无追踪 | ⚠️ 大厂账号绑定 | ⚠️ 强制绑定手机号 | ✅ 无追踪 | ✅ 安全但目标较大 |
+| 离线可用性 | ✅ 支持 (PWA 强力缓存) | ✅ 支持 | ✅ 支持 | ✅ 支持 | ✅ 支持 |
+| 部署成本 | ✅ 0 成本 (CF Serverless) | ✅ 免费 | ✅ 免费 | ✅ 免费 | 💰 订阅/自建较重 |
+| 厂商倒闭风险 | ✅ 源码在手,永不丧失 | ⚠️ 生态强制迁移风险 | 🚨 随时下线(如 PC 端) | ✅ 开源可控 | ⚠️ 订阅制/闭源风险 |
> 🎯 **小结**:**2FAuth Worker** 既有 1Password 的多端便利,又有开源 App 的隐私性,更有 Serverless 的零成本。**支持 Telegram 机器人自动备份**更是解决了所有 Web 端和 App 端的“丢数据焦虑”,让安全触手可得。
@@ -63,13 +63,12 @@
#### 2. 注意事项与授权指引
* 在部署向导中,你需要授权 Cloudflare 读取该仓库(系统会自动在你的 GitHub 账号下创建一个私有克隆)。
-* 按照页面提示填写 **GitHub Client ID/Secret** 等参数(详见下文配置说明)。
+
点击查看:授权指引示意图
-
+
-
#### 3. 添加如下变量和机密
@@ -190,21 +189,10 @@ docker run -d --name 2fauth-worker \
| 变量名 | 作用 | 获取建议 |
| :--- | :--- | :--- |
-| `ENCRYPTION_KEY` | **核心:** 数据库加密密钥 | 设好后**绝对不要改**!改了旧数据就解不开了。建议设为 32 位随机字符。 |
-| `JWT_SECRET` | 登录状态密钥 | 随便填一段长字符串。 |
+| `ENCRYPTION_KEY` | **核心:** 数据库加密密钥 | 设好后**绝对不要改**!改了旧数据就解不开了。要求:32 位以上随机字符。 |
+| `JWT_SECRET` | 登录状态密钥 | 要求:32 位以上随机字符。 |
| `OAUTH_ALLOWED_USERS` | **白名单:** 只有谁能进 | 填你的邮箱或 Telegram 数字 ID,多个用逗号隔开。 |
-### 如何配置第三方登录?
-我们最推荐使用 **GitHub 登录**:
-1. 访问 GitHub `Settings` -> `Developer Settings` -> `OAuth Apps` -> **New OAuth App**。
-2. 回调地址 (Callback URL) 必须填写:`https://你的域名/oauth/callback`。
-3. 拿到 `Client ID` 和 `Client Secret` 后填入上述环境变量中。
-
-
-点击查看:OAuth 配置示意图
-
-
-
### 依据你选取的登录平台,在环境变量中填入对应参数(任选其一即可):
@@ -219,12 +207,43 @@ docker run -d --name 2fauth-worker \
---
-#### 💡 使用 Telegram 登录的特殊配置:
+#### 如何配置Github第三方登录(推荐)?
+1. 访问 GitHub `Settings` -> `Developer Settings` -> `OAuth Apps` -> **New OAuth App**。
+2. 回调地址 (Callback URL) 必须填写:`https://你的域名/oauth/callback`。
+3. 将 `Client ID` `Client Secret` 和 `OAUTH_GITHUB_REDIRECT_URI` 填入环境变量。
+
+
+点击查看:Github OAuth 配置示意图
+
+
+
+#### 如何配置Telegram第三方登录?
1. 在 Telegram 搜索并添加官方机器人 **[@BotFather](https://t.me/BotFather)**,按照提示创建机器人。
2. 记录生成的 **Token** (`OAUTH_TELEGRAM_BOT_TOKEN`) 和 **用户名** (`OAUTH_TELEGRAM_BOT_NAME`)。
3. 请务必向 @BotFather 发送 `/setdomain` 指令,选择您的机器人,并输入您的**应用域名**(不需要 `https://`)。
4. 由于本项目是 Serverless 架构,您需要手动注册 Webhook。将下方链接中的 `` 和 `<域名>` 替换后,在浏览器中访问一次:
`https://api.telegram.org/bot/setWebhook?url=https://<域名>/api/telegram/webhook`
+5. 将 `OAUTH_TELEGRAM_BOT_TOKEN`、`OAUTH_TELEGRAM_BOT_NAME` 和 `OAUTH_TELEGRAM_REDIRECT_URI` 填入环境变量。
+
+#### 如何配置Cloudflared Access 第三方登录?
+1. 进入 **Cloudflare Zero Trust Dashboard** -> **Access** -> **Applications**。
+2. 创建一个 **SaaS** 应用 (Select "SaaS")。
+3. 配置 **Application**:
+ - **应用程序**: `输入任意名称都行,如 2fauth`
+ - **选择身份验证协议**: `OIDC`
+ - **重定向 URL**: `https://你的域名/oauth/callback`
+4. 复制保存关键信息备用, `客户端 ID` 和 `客户端密码`。
+5. Access 策略 -> 点击创建新策略 -> 添加策略
+ - **策略**: `Allow`
+ - **选择**:`Everyone`
+6. 其余均不需要额外配置,一直下一步到完成
+7. 将 `OAUTH_CLOUDFLARE_CLIENT_ID`、`OAUTH_CLOUDFLARE_CLIENT_SECRET`、 `OAUTH_CLOUDFLARE_REDIRECT_URI` 和 `OAUTH_CLOUDFLARE_ORG_DOMAIN` 填入环境变量。
+
+
+点击查看:Cloudflare Access OAuth 配置示意图
+
+
+
---
@@ -238,6 +257,8 @@ docker run -d --name 2fauth-worker \
4. **端到端隔离**:前端生成的敏感操作逻辑经过严格过滤,严禁任何 XSS (跨站脚本) 攻击。
5. **隐私第一**:本项目不包含任何追踪代码、分析工具或第三方统计插件。你的数据只属于你。
+> 📊 [**点击查看由 GitHub Actions 自动生成的实时安全审计报告**](https://github.com/nap0o/2fauth-worker/blob/security-audit/SECURITY.md)
+
---
## 🛠️ 参与开发 (Local Development)