Skip to content

feat: 供应商页面增加排行榜入口#168

Merged
ding113 merged 11 commits intomainfrom
dev
Nov 21, 2025
Merged

feat: 供应商页面增加排行榜入口#168
ding113 merged 11 commits intomainfrom
dev

Conversation

@ding113
Copy link
Owner

@ding113 ding113 commented Nov 21, 2025

close #115

ding113 and others added 5 commits November 22, 2025 00:51
- Add actual authentication enforcement in createActionRoute handler
  (previously only OpenAPI metadata, no runtime check)
- Add requiredRole: "admin" to all notification management endpoints
- Add SSRF protection to testWebhookAction blocking internal IPs,
  localhost, and dangerous ports

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…ntication errors

- Changed use_commit_signing from true to false in all 17 Claude workflow files

- Fixes GitHub Actions OIDC token exchange 401 error

- Related to anthropics/claude-code-action#522

- Workflow will work without commit signing until OIDC issue is resolved
- Enhanced SSRF protection with IPv6 ULA address checking (fc00::/7, fe80::/10)
- Fixed leaderboard-view.tsx useEffect logic to prevent state reset issues
- Replaced manual Cookie parsing with Hono's getCookie helper for better robustness
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @ding113, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

本次拉取请求主要引入了用户界面和后端安全性的改进。在用户界面方面,为供应商管理页面新增了排行榜入口,方便用户快速查看供应商排名。在安全性方面,通过实现 SSRF 防护机制,显著增强了 Webhook 功能的安全性,防止恶意请求访问内部资源。此外,还改进了排行榜页面的 URL 参数处理,并为 API 路由增加了更细粒度的认证和权限控制,提升了系统的整体健壮性和用户体验。

Highlights

  • 新增供应商排行榜入口: 在供应商管理页面添加了一个新的按钮,可以直接跳转到供应商排行榜页面。
  • 增强 Webhook 安全性 (SSRF 防护): 引入了 isInternalUrl 函数,用于检测并阻止 Webhook URL 指向内部/私有网络地址或危险端口,从而防止服务器端请求伪造 (SSRF) 攻击。
  • 排行榜 URL 参数同步: 优化了排行榜视图,使其能够根据 URL 查询参数 (scopeperiod) 初始化并同步显示内容,支持通过外部链接直接访问特定榜单。
  • API 路由认证与权限控制: 为 createActionRoute 增加了认证和基于角色的权限检查,确保只有经过认证且具有所需角色的用户才能访问特定 API 动作。
Ignored Files
  • Ignored by pattern: .github/workflows/** (17)
    • .github/workflows/claude-ci-autofix.yml
    • .github/workflows/claude-dependency-review.yml
    • .github/workflows/claude-docs-review.yml
    • .github/workflows/claude-issue-auto-response.yml
    • .github/workflows/claude-issue-duplicate-check.yml
    • .github/workflows/claude-issue-oncall-triage.yml
    • .github/workflows/claude-issue-stale-cleanup.yml
    • .github/workflows/claude-issue-triage.yml
    • .github/workflows/claude-mention-responder.yml
    • .github/workflows/claude-pr-changelog.yml
    • .github/workflows/claude-pr-description.yml
    • .github/workflows/claude-pr-label.yml
    • .github/workflows/claude-pr-review.yml
    • .github/workflows/claude-pr-size-check.yml
    • .github/workflows/claude-release-notes.yml
    • .github/workflows/claude-review-responder.yml
    • .github/workflows/claude-security-scan.yml
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

本次 Pull Request 主要实现了两个功能:在供应商管理页面增加了排行榜的入口,并为 Webhook 测试功能增加了 SSRF(服务器端请求伪造)攻击防护。

整体来看,代码质量不错。主要改动点包括:

  • 在多个语言的 i18n 文件中添加了“供应商排行榜”的翻译。
  • 在供应商管理页面 src/app/[locale]/settings/providers/page.tsx 添加了跳转到排行榜的按钮。
  • src/actions/notifications.ts 中新增了 isInternalUrl 函数用于检测内部网络地址,并在 testWebhookAction 中使用它来防止 SSRF 攻击。这是一个重要的安全增强。
  • 在排行榜视图 leaderboard-view.tsx 中,实现了从 URL 查询参数同步 scopeperiod 状态,提升了页面的可链接性。

我提出了一些建议,主要集中在两个方面:

  1. 安全加固:当前的 SSRF 防护措施可以被 DNS 解析绕过,我建议通过增加 DNS 解析步骤来使其更加健壮。
  2. 功能完善:排行榜页面的 URL 同步目前是单向的,我建议实现双向同步,以便在用户切换视图时也能更新 URL。

请查看具体的评论以获取详细信息。修复这些问题后,代码将更加安全和完善。

Comment on lines 15 to 59
function isInternalUrl(urlString: string): boolean {
try {
const url = new URL(urlString);
const hostname = url.hostname.toLowerCase();

// 阻止 localhost 和 IPv6 loopback
if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1") {
return true;
}

// 解析 IPv4 地址
const ipv4Match = hostname.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
if (ipv4Match) {
const [, a, b, c] = ipv4Match.map(Number);
// 私有 IP 范围
if (a === 10) return true; // 10.0.0.0/8
if (a === 172 && b >= 16 && b <= 31) return true; // 172.16.0.0/12
if (a === 192 && b === 168) return true; // 192.168.0.0/16
if (a === 169 && b === 254) return true; // 169.254.0.0/16 (link-local)
if (a === 0) return true; // 0.0.0.0/8
}

// 检查 IPv6 私有地址范围
// 移除方括号(如果存在)用于 IPv6 地址检查
const ipv6Hostname = hostname.replace(/^\[|\]$/g, "");
// ULA (Unique Local Address): fc00::/7
if (ipv6Hostname.startsWith("fc") || ipv6Hostname.startsWith("fd")) {
return true;
}
// Link-local: fe80::/10
if (ipv6Hostname.startsWith("fe80:")) {
return true;
}

// 危险端口
const dangerousPorts = [22, 23, 3306, 5432, 27017, 6379, 11211];
if (url.port && dangerousPorts.includes(parseInt(url.port, 10))) {
return true;
}

return false;
} catch {
return true; // 无效 URL 视为不安全
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

这个 SSRF 防护函数 isInternalUrl 目前的实现存在一个安全隐患。它只检查了 URL 中的主机名是否为私有 IP 地址格式,但没有对域名进行 DNS 解析。攻击者可以通过将一个域名(例如 my-private-service.attacker.com)解析到内部网络地址(例如 127.0.0.110.0.0.1)来绕过此检查。

为了更有效地防止 SSRF 攻击,建议在检查之前,先将主机名解析为 IP 地址,然后对解析出的 IP 地址进行黑名单校验。

你可以使用 Node.js 的 dns.promises.resolve() 方法来实现。这将使函数变为异步函数,调用时需要使用 await

例如:

import { promises as dns } from 'dns';

async function isInternalUrl(urlString: string): Promise<boolean> {
  try {
    const url = new URL(urlString);
    const hostname = url.hostname;

    // 首先获取所有 IP 地址
    const addresses = await dns.resolve(hostname);

    for (const address of addresses) {
      // 接下来对每个 address 进行私有地址检查...
      // ... (现有 IP 检查逻辑)
    }
    // ... (端口检查等)
    return false;
  } catch (error) {
    // DNS 解析失败或 URL 无效等情况,都视为不安全
    return true;
  }
}

考虑到 DNS Rebinding 攻击,最佳实践是仅接受解析出的第一个 IP 地址,或者确保所有解析出的 IP 地址都是安全的。

@ding113 ding113 added the enhancement New feature or request label Nov 21, 2025
CHANGELOG.md Outdated

### Changed

- 合并若干优化 (#162) @ding113
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Vague Changelog Entry: Change description is too generic and uninformative

Problem: The entry "合并若干优化 (#162)" (merge several optimizations) doesn't tell readers what was actually changed in this release. Changelogs should be specific enough for users to understand what improvements or fixes they're getting.

Impact: Users reading the changelog won't know:

  • What features were added
  • What bugs were fixed
  • What performance improvements were made
  • Whether they need to take any action

Suggested Fix:

## [Unreleased]

### Added

- 供应商管理页面新增排行榜入口按钮 (#168)
- Webhook 通知增加 SSRF 防护,阻止访问内部网络地址

### Changed

- 排行榜页面支持通过 URL 参数 \`scope\`\`period\` 直达特定榜单
- 通知管理 API 端点增加管理员权限校验

### Security

- 修复 Webhook 测试接口的 SSRF 漏洞

Reference: Follow Keep a Changelog format with specific categories (Added, Changed, Fixed, Security)

CHANGELOG.md Outdated

### Changed

- 合并若干优化 (#162) @ding113
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Missing Link Formatting: PR reference is not formatted as a clickable link

Problem: The PR reference (#162) is written as plain text instead of a proper markdown link. While GitHub auto-links issue numbers in comments, changelogs should use explicit links for better portability and clarity.

Impact:

  • If the changelog is read outside GitHub (e.g., in release notes, documentation sites), the link won't work
  • Readers can't quickly navigate to the referenced PR
  • Inconsistent with changelog best practices

Suggested Fix:

- 合并若干优化 ([#162](https://github.com/ding113/claude-code-hub/pull/162)) @ding113

Or better yet, replace with specific changes as mentioned in the previous comment:

### Added

- 供应商管理页面新增排行榜入口按钮 ([#168](https://github.com/ding113/claude-code-hub/pull/168))

Reference: Keep a Changelog - Link Conventions

Copy link
Owner Author

@ding113 ding113 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Documentation Review

📊 Issues Summary

  • Critical (🔴): 2 - Must fix before merge
  • High (🟠): 0 - Should fix before merge
  • Medium (🟡): 0 - Consider addressing
  • Low (🟢): 0 - Optional improvements

⚡ Priority Fixes

  1. CHANGELOG.md - Vague Change Description: The changelog entry "合并若干优化" (merge several optimizations) is too generic and doesn't describe what was actually changed. Users need to know specifically what features, fixes, or improvements are included.

  2. CHANGELOG.md - Missing Link Format: The PR reference (#162) should be formatted as a proper markdown link for better portability outside GitHub.

📋 Review Coverage

  • Technical accuracy - 0 issues
  • Completeness - 1 issue (vague description)
  • Code examples - N/A (no code examples in docs)
  • Links and references - 1 issue (missing link format)
  • Clarity and organization - 0 issues

💡 General Observations

The new CHANGELOG.md file follows the correct structure (using Keep a Changelog format), but the content needs to be more specific. Based on the PR changes, this update includes:

  • Added: 供应商管理页面的排行榜入口按钮
  • Changed: 排行榜支持 URL 参数直达特定视图
  • Security: Webhook 测试接口的 SSRF 防护

Consider breaking down the changelog entries by category (Added, Changed, Fixed, Security) and providing specific descriptions instead of generic phrases like "合并若干优化".


🤖 Automated docs review by Claude AI

@ding113
Copy link
Owner Author

ding113 commented Nov 21, 2025

Re-running CI - the formatting fix (commit e75dd91) is already in the dev branch.

@ding113
Copy link
Owner Author

ding113 commented Nov 21, 2025

🔒 Security Scan Results - Inline Finding

🟡 Medium Severity: 输入验证缺失

文件: src/lib/api/action-adapter-openapi.ts (第 196-222 行)

问题描述: requiredRole 参数未进行类型验证

安全影响: 如果 requiredRole 参数被传入非预期值(如 null, undefined, 或其他字符串),权限检查可能被绕过

攻击场景:

  1. 攻击者可能通过修改请求或利用代码错误传入 requiredRole = undefined
  2. 代码中的 if (requiredRole === 'admin' && session.user.role !== 'admin') 条件在 requiredRoleundefined 时不会执行
  3. 导致权限检查被跳过,允许普通用户访问管理员端点

修复建议:

// 在函数开始处验证 requiredRole
if (requiredRole && !['admin', 'user'].includes(requiredRole)) {
  logger.error(`[ActionAPI] Invalid requiredRole: ${requiredRole}`);
  return c.json({ ok: false, error: '配置错误' }, 500);
}

// 在认证检查中使用更严格的逻辑
if (requiresAuth) {
  const authToken = getCookie(c, 'auth-token');
  if (!authToken) {
    return c.json({ ok: false, error: '未认证' }, 401);
  }

  const session = await validateKey(authToken);
  if (!session) {
    return c.json({ ok: false, error: '认证无效或已过期' }, 401);
  }

  // 严格验证角色权限
  if (requiredRole) {
    if (requiredRole === 'admin' && session.user.role !== 'admin') {
      logger.warn(`[ActionAPI] ${fullPath} 权限不足: 需要 admin 角色`, {
        userId: session.user.id,
        userRole: session.user.role,
      });
      return c.json({ ok: false, error: '权限不足' }, 403);
    }
  }
}

参考:


@ding113
Copy link
Owner Author

ding113 commented Nov 21, 2025

🔒 Security Scan Results - Inline Finding #2

🟡 Medium Severity: 不完整的依赖数组(React Hooks 最佳实践)

文件: src/app/[locale]/dashboard/leaderboard/_components/leaderboard-view.tsx (第 34-48 行)

问题描述: useEffect 的依赖数组缺少 scopeperiod 状态变量

安全影响:

  • 可能导致状态不一致,用户看到错误的权限范围数据
  • 潜在的竞态条件:URL 参数更新但组件状态未同步

攻击场景:

  1. 用户通过 URL 参数 ?scope=provider 访问供应商排行榜
  2. 由于依赖数组不完整,状态更新可能不触发
  3. 用户看到的数据可能不匹配当前的 scope 设置
  4. 可能导致权限混淆(显示用户数据但 UI 显示为供应商模式)

修复建议:

// 修改依赖数组,包含所有使用的状态变量
useEffect(() => {
  const urlScope = searchParams.get('scope');
  const normalizedScope = urlScope === 'provider' && isAdmin ? 'provider' : 'user';

  if (normalizedScope !== scope) {
    setScope(normalizedScope);
  }

  const urlPeriod = searchParams.get('period');
  const normalizedPeriod = urlPeriod === 'monthly' ? 'monthly' : 'daily';

  if (normalizedPeriod !== period) {
    setPeriod(normalizedPeriod);
  }
}, [isAdmin, searchParams, scope, period]); // ✅ 添加 scope 和 period

或使用 useRef 避免循环依赖:

const scopeRef = useRef(scope);
const periodRef = useRef(period);

useEffect(() => {
  scopeRef.current = scope;
  periodRef.current = period;
}, [scope, period]);

useEffect(() => {
  const urlScope = searchParams.get('scope');
  const normalizedScope = urlScope === 'provider' && isAdmin ? 'provider' : 'user';

  if (normalizedScope !== scopeRef.current) {
    setScope(normalizedScope);
  }

  const urlPeriod = searchParams.get('period');
  const normalizedPeriod = urlPeriod === 'monthly' ? 'monthly' : 'daily';

  if (normalizedPeriod !== periodRef.current) {
    setPeriod(normalizedPeriod);
  }
}, [isAdmin, searchParams]); // ✅ 正确的依赖数组

参考:


@ding113
Copy link
Owner Author

ding113 commented Nov 21, 2025

🔒 Security Scan Results

🚨 Vulnerabilities Found

  • Critical (🔴): 0 vulnerabilities
  • High (🟠): 0 vulnerabilities
  • Medium (🟡): 2 weaknesses
  • Low (🟢): 0 informational findings

✅ Positive Security Findings

1. ✅ SSRF Protection (src/actions/notifications.ts)

Excellent implementation of SSRF (Server-Side Request Forgery) protection:

  • ✅ Blocks localhost, 127.0.0.1, ::1
  • ✅ Blocks private IP ranges (10.x.x.x, 172.16-31.x.x, 192.168.x.x, 169.254.x.x)
  • ✅ Blocks IPv6 private addresses (fc00::/7, fe80::/10)
  • ✅ Blocks dangerous ports (22, 23, 3306, 5432, 27017, 6379, 11211)
  • ✅ Proper error handling and logging with sanitized URLs

Reference: This is a CRITICAL security control that prevents attackers from accessing internal services. Well done!

2. ✅ Authentication & Authorization (src/lib/api/action-adapter-openapi.ts)

Good implementation of authentication layer:

  • ✅ Cookie-based authentication with validateKey()
  • ✅ Role-based access control (admin/user)
  • ✅ Proper 401/403 status codes
  • ✅ Logging of authentication failures
  • ⚠️ Minor improvement needed: Add input validation for requiredRole (see finding docs: 完善环境配置文档和注释 #1)

3. ✅ Commit Signing Changes (GitHub Workflows)

  • Removed use_commit_signing: true and id-token: write permission
  • Added explicit github_token: ${{ secrets.GH_PAT }}
  • Assessment: This is a configuration change, not a security vulnerability
  • Recommendation: Ensure GH_PAT has minimal required permissions

⚡ Medium Severity Issues (Must Address)

🟡 Finding #1: 输入验证缺失 (Authorization Bypass Risk)

Location: src/lib/api/action-adapter-openapi.ts:196-222

Issue: requiredRole parameter lacks type validation, potentially allowing authorization bypass if undefined or invalid values are passed.

Risk: If requiredRole is undefined, the condition if (requiredRole === 'admin' && ...) will not execute, skipping permission checks.

Fix: Add strict validation:

if (requiredRole && !['admin', 'user'].includes(requiredRole)) {
  return c.json({ ok: false, error: '配置错误' }, 500);
}

Reference: CWE-285: Improper Authorization


🟡 Finding #2: 不完整的依赖数组 (State Synchronization Issue)

Location: src/app/[locale]/dashboard/leaderboard/_components/leaderboard-view.tsx:34-48

Issue: useEffect dependency array is incomplete (missing scope and period), potentially causing state desynchronization.

Risk: Race condition where URL parameters update but component state doesn't sync, leading to users seeing incorrect permission scope data (e.g., user data displayed under provider mode).

Fix: Use useRef to avoid circular dependencies:

const scopeRef = useRef(scope);
useEffect(() => {
  const normalizedScope = /* ... */;
  if (normalizedScope !== scopeRef.current) {
    setScope(normalizedScope);
  }
}, [isAdmin, searchParams]); // Correct dependencies

Reference: CWE-362: Race Condition


📋 OWASP Top 10 Coverage

  • A01: Injection - Clean (No SQL/Command/LDAP injection found)
  • A02: Broken Authentication - Mostly Clean (Minor improvement needed, see Finding docs: 完善环境配置文档和注释 #1)
  • A03: Sensitive Data Exposure - Clean (No secrets or PII logged)
  • A04: XML External Entities - N/A (No XML parsing in PR)
  • A05: Broken Access Control - Mostly Clean (Minor improvement needed, see Finding docs: 完善环境配置文档和注释 #1)
  • A06: Security Misconfiguration - Clean (No debug mode or verbose errors)
  • A07: XSS - Clean (No dangerouslySetInnerHTML or DOM manipulation)
  • A08: Insecure Deserialization - Clean (No unsafe deserialization)
  • A09: Known Vulnerabilities - Not Scanned (No dependency changes in PR)
  • A10: Logging & Monitoring - Good (Proper security logging implemented)

Additional Checks:

  • SSRF - ✅ Excellent (Strong SSRF protection implemented)
  • Path Traversal - N/A (No file operations)
  • Cryptographic Issues - N/A (No crypto code)
  • Race Conditions - ⚠️ Minor Issue (See Finding Welcome to claude-code-hub Discussions! #2)

🛡️ Security Posture: Strong with Minor Improvements Needed

Overall Assessment:
This PR introduces a significant security improvement with the SSRF protection implementation. The authentication layer is well-designed with proper role-based access control.

Recommendations:

  1. Keep the SSRF protection - This is critical
  2. ⚠️ Address Finding docs: 完善环境配置文档和注释 #1 - Add requiredRole validation to prevent potential authorization bypass
  3. ⚠️ Address Finding Welcome to claude-code-hub Discussions! #2 - Fix React Hook dependencies to prevent state desynchronization
  4. ✅ The GitHub workflow changes are acceptable but ensure GH_PAT has minimal permissions

Approval Recommendation: ✅ Safe to merge after addressing Finding #1 (critical for production security)


🤖 Automated security scan by Claude AI - OWASP Top 10 & CWE coverage

@ding113 ding113 added the size/S Small PR (< 200 lines) label Nov 21, 2025
Copy link
Owner Author

@ding113 ding113 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📋 Code Review Summary

This PR adds a provider leaderboard entry to the settings page and includes several GitHub workflow configuration changes (switching from OIDC to GH_PAT authentication). The PR also includes SSRF protection for webhook testing and adds authentication checks to notification API endpoints. While the feature implementation is straightforward, there are security concerns with the SSRF protection implementation.

🔍 Issues Found

  • Critical (🔴): 0 issues
  • High (🟠): 1 issue
  • Medium (🟡): 2 issues
  • Low (🟢): 0 issues

🎯 Priority Actions

  1. SSRF Protection Bypass (🟠 High) - src/actions/notifications.ts:30: The isInternalUrl function is missing check for the full 127.0.0.0/8 loopback range. Currently only checks 127.0.0.1 as a string, but 127.0.0.2, 127.1.1.1, etc. would bypass validation. Add if (a === 127) return true; in the IPv4 validation section.

  2. Missing useEffect Dependencies (🟡 Medium) - src/app/[locale]/dashboard/leaderboard/_components/leaderboard-view.tsx:47: The useEffect that syncs URL params with state references scope and period in comparison logic but doesn't include them in the dependency array [isAdmin, searchParams]. This can cause stale closure bugs. Add scope and period to the dependency array.

  3. IPv6-mapped IPv4 Bypass (🟡 Medium) - src/actions/notifications.ts:41-45: The IPv6 checks don't account for IPv6-mapped IPv4 addresses like ::ffff:127.0.0.1 which could bypass the internal URL check. Add: if (hostname.startsWith("::ffff:")) return true;

💡 General Observations

  • The workflow changes switching from id-token: write permission to github_token: ${{ secrets.GH_PAT }} and disabling commit signing appear consistent across all workflow files.
  • The authentication additions to the notification management APIs (requiredRole: "admin") are a good security practice.
  • The i18n additions for the leaderboard link are consistent across all supported locales.
  • Consider adding network-level egress filtering as a defense-in-depth measure since client-side URL validation alone cannot fully prevent SSRF (DNS rebinding, redirects, etc.).

🤖 Automated review by Claude AI - focused on identifying issues for improvement

Copy link
Owner Author

@ding113 ding113 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📋 Code Review Summary

This PR adds a provider leaderboard entry to the settings page and includes several GitHub workflow configuration changes (switching from OIDC to GH_PAT authentication). The PR also includes SSRF protection for webhook testing and adds authentication checks to notification API endpoints. While the feature implementation is straightforward, there are security concerns with the SSRF protection implementation.

🔍 Issues Found

  • Critical (🔴): 0 issues
  • High (🟠): 1 issue
  • Medium (🟡): 2 issues
  • Low (🟢): 0 issues

🎯 Priority Actions

  1. SSRF Protection Bypass (🟠 High) - src/actions/notifications.ts:30: The isInternalUrl function is missing check for the full 127.0.0.0/8 loopback range. Currently only checks 127.0.0.1 as a string, but 127.0.0.2, 127.1.1.1, etc. would bypass validation. Add if (a === 127) return true; in the IPv4 validation section.

  2. Missing useEffect Dependencies (🟡 Medium) - src/app/[locale]/dashboard/leaderboard/_components/leaderboard-view.tsx:47: The useEffect that syncs URL params with state references scope and period in comparison logic but doesn't include them in the dependency array [isAdmin, searchParams]. This can cause stale closure bugs. Add scope and period to the dependency array.

  3. IPv6-mapped IPv4 Bypass (🟡 Medium) - src/actions/notifications.ts:41-45: The IPv6 checks don't account for IPv6-mapped IPv4 addresses like ::ffff:127.0.0.1 which could bypass the internal URL check. Add: if (hostname.startsWith("::ffff:")) return true;

💡 General Observations

  • The workflow changes switching from id-token: write permission to github_token: ${{ secrets.GH_PAT }} and disabling commit signing appear consistent across all workflow files.
  • The authentication additions to the notification management APIs (requiredRole: "admin") are a good security practice.
  • The i18n additions for the leaderboard link are consistent across all supported locales.
  • Consider adding network-level egress filtering as a defense-in-depth measure since client-side URL validation alone cannot fully prevent SSRF (DNS rebinding, redirects, etc.).

🤖 Automated review by Claude AI - focused on identifying issues for improvement

@ding113
Copy link
Owner Author

ding113 commented Nov 21, 2025

🔒 Security Scan Results

No security vulnerabilities detected

This PR has been scanned against OWASP Top 10, CWE Top 25, and common security anti-patterns. No security issues were identified in the code changes.

🛡️ Security Improvements Detected

This PR actually enhances the application's security posture with the following fixes:

✅ Authentication Enforcement (src/lib/api/action-adapter-openapi.ts)

  • Added: Runtime authentication checks in createActionRoute handler
  • Impact: Previously only documented in OpenAPI metadata, now enforced at runtime
  • Coverage: All API endpoints now require valid auth-token cookie
  • Role-based Access: Admin-only endpoints properly enforce requiredRole check

✅ SSRF Protection (src/actions/notifications.ts)

  • Added: Comprehensive SSRF protection in testWebhookAction
  • Blocked: localhost, 127.0.0.1, ::1, internal IP ranges (10.x, 172.16-31.x, 192.168.x)
  • IPv6 Protection: ULA addresses (fc00::/7, fd00::/8) and link-local (fe80::/10) blocked
  • Port Filtering: Dangerous ports (SSH 22, MySQL 3306, PostgreSQL 5432, Redis 6379, etc.) blocked
  • Logging: URL validation failures logged with sanitized URLs (passwords redacted)

✅ Authorization Enhancement

  • Added: requiredRole: "admin" to all notification management endpoints:
    • /notifications/getNotificationSettings
    • /notifications/updateNotificationSettings
    • /notifications/testWebhook
  • Impact: Non-admin users cannot access sensitive notification configuration

📋 OWASP Top 10 Coverage

  • A01: Injection - No SQL/NoSQL/Command injection vectors found
  • A02: Broken Authentication - Authentication strengthened (runtime enforcement added)
  • A03: Sensitive Data Exposure - No secrets or sensitive data in code changes
  • A04: XML External Entities - N/A (no XML processing)
  • A05: Broken Access Control - RBAC improved (admin-only endpoints enforced)
  • A06: Security Misconfiguration - No misconfigurations detected
  • A07: XSS - No XSS vectors (changes are backend-only)
  • A08: Insecure Deserialization - N/A (no deserialization changes)
  • A09: Known Vulnerabilities - Workflow configuration changes only
  • A10: Logging & Monitoring - Security events properly logged (SSRF attempts)

🔍 Additional Security Checks

  • SSRF (Server-Side Request Forgery) - FIXED with comprehensive protection
  • Path Traversal - N/A (no file operations)
  • Race Conditions - No concurrent access issues
  • Cryptographic Issues - No crypto changes
  • Input Validation - URL validation added (SSRF protection)
  • Information Disclosure - Sensitive data sanitized in logs

📝 Non-Security Changes Reviewed

The following changes are configuration/UX improvements with no security impact:

  1. GitHub Actions Workflows: Disabled commit signing and removed OIDC token permission (fixes v1: App token exchange failed anthropics/claude-code-action#522)
  2. Leaderboard Navigation: Added provider leaderboard link in settings UI
  3. URL State Management: Query parameter synchronization in leaderboard view
  4. Internationalization: Added translation keys for new UI elements

🎯 Security Assessment

Security Posture: ✅ Strong - This PR significantly improves security

Recommendation: ✅ Safe to merge - All changes enhance security without introducing new risks


🤖 Automated security scan by Claude AI - OWASP Top 10 & CWE coverage
📅 Scan Date: 2025-11-21 17:48:30 UTC
🔬 Analysis Depth: Comprehensive (OWASP Top 10, CWE Top 25, SSRF, Auth, RBAC)

if (ipv4Match) {
const [, a, b, c] = ipv4Match.map(Number);
// 私有 IP 范围
if (a === 10) return true; // 10.0.0.0/8
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High: Missing 127.0.0.0/8 loopback range check

Why this is a problem: The IPv4 validation checks for 127.0.0.1 as a string at the beginning, but does not check the entire 127.0.0.0/8 loopback range when parsing IPv4 addresses. An attacker could use 127.0.0.2 or 127.1.1.1 to bypass the check and access internal services.

Suggested fix:

    // 解析 IPv4 地址
    const ipv4Match = hostname.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
    if (ipv4Match) {
      const [, a, b, c] = ipv4Match.map(Number);
      // 私有 IP 范围
      if (a === 10) return true; // 10.0.0.0/8
      if (a === 127) return true; // 127.0.0.0/8 (loopback range) - ADD THIS
      if (a === 172 && b >= 16 && b <= 31) return true; // 172.16.0.0/12
      if (a === 192 && b === 168) return true; // 192.168.0.0/16
      if (a === 169 && b === 254) return true; // 169.254.0.0/16 (link-local)
      if (a === 0) return true; // 0.0.0.0/8
    }

// 移除方括号(如果存在)用于 IPv6 地址检查
const ipv6Hostname = hostname.replace(/^\[|\]$/g, "");
// ULA (Unique Local Address): fc00::/7
if (ipv6Hostname.startsWith("fc") || ipv6Hostname.startsWith("fd")) {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium: IPv6-mapped IPv4 addresses can bypass SSRF protection

Why this is a problem: The IPv6 checks don't account for IPv6-mapped IPv4 addresses like ::ffff:127.0.0.1 which represent IPv4 addresses in IPv6 format. An attacker could use this representation to bypass the internal URL check.

Suggested fix:

    // 检查 IPv6 私有地址范围
    // 移除方括号(如果存在)用于 IPv6 地址检查
    const ipv6Hostname = hostname.replace(/^\[|\]$/g, "");
    
    // 阻止 IPv6-mapped IPv4 addresses (::ffff:x.x.x.x)
    if (ipv6Hostname.startsWith("::ffff:")) {
      return true;
    }
    
    // ULA (Unique Local Address): fc00::/7
    if (ipv6Hostname.startsWith("fc") || ipv6Hostname.startsWith("fd")) {
      return true;
    }


if (normalizedPeriod !== period) {
setPeriod(normalizedPeriod);
}
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium: Missing dependencies in useEffect hook causes stale closure

Why this is a problem: The useEffect that syncs URL params with state references scope and period in its comparison logic but does not include them in the dependency array. This can cause the effect to use stale values when comparing, potentially leading to unexpected behavior or the state not updating correctly when URL params change while state was modified programmatically.

Suggested fix:

  }, [isAdmin, searchParams, scope, period]);

- 修改编辑密钥弹窗(edit-key-form.tsx)使用 FormGrid 双栏布局
- 修改添加密钥弹窗(add-key-form.tsx)使用 FormGrid 双栏布局
- 与编辑用户弹窗的限高、双栏设计保持一致
- 改善长表单的可读性和空间利用率
@ding113 ding113 merged commit 72e1730 into main Nov 21, 2025
5 of 7 checks passed
ding113 pushed a commit that referenced this pull request Nov 21, 2025
ding113 pushed a commit that referenced this pull request Nov 22, 2025
- 从 useEffect 依赖数组移除 scope 和 period,避免状态更新触发循环
- 添加 eslint-disable 注释说明原因
- 修复由 PR #168 (commit 281ed80) 引入的 bug
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request size/S Small PR (< 200 lines)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: 供应商页面增加排行榜入口

1 participant

Comments