Skip to content

fix: enhance WebSocket access token validation method#654

Merged
linyuchen merged 2 commits into
LLOneBot:mainfrom
super1207:main
Dec 12, 2025
Merged

fix: enhance WebSocket access token validation method#654
linyuchen merged 2 commits into
LLOneBot:mainfrom
super1207:main

Conversation

@super1207
Copy link
Copy Markdown
Contributor

@super1207 super1207 commented Dec 12, 2025

Summary by Sourcery

错误修复:

  • 允许通过 Authorization Bearer 头(header)来提供 WebSocket 访问令牌(access token),此外仍支持通过 access_token 查询参数提供。
Original summary in English

Summary by Sourcery

Bug Fixes:

  • Allow WebSocket access tokens to be provided via the Authorization bearer header in addition to the access_token query parameter.

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Dec 12, 2025

审阅者指南(在小型 PR 上默认折叠)

审阅者指南

更新 WebSocket 访问令牌验证逻辑,在原先支持 access_token 查询参数的基础上,新增对 Authorization 头(Bearer 方案)中令牌的支持,并为基于请求头的令牌添加日志记录。

更新后的 WebSocket 访问令牌验证时序图

sequenceDiagram
    actor Client
    participant MilkyHttpHandler
    participant URLParser as URL
    participant Logger

    Client->>MilkyHttpHandler: WebSocket HTTP upgrade request
    activate MilkyHttpHandler

    MilkyHttpHandler->>URLParser: new URL(req.url, host)
    URLParser-->>MilkyHttpHandler: url

    MilkyHttpHandler->>MilkyHttpHandler: read req.headers.authorization
    alt Authorization header present
        MilkyHttpHandler->>MilkyHttpHandler: inputToken = authHeader.split(Bearer ).pop()
        MilkyHttpHandler->>Logger: info(receive ws header token, inputToken)
    else Authorization header missing
        MilkyHttpHandler->>URLParser: url.searchParams.get(access_token)
        URLParser-->>MilkyHttpHandler: inputToken or null
        MilkyHttpHandler->>MilkyHttpHandler: inputToken = value or empty string
    end

    MilkyHttpHandler->>MilkyHttpHandler: compare inputToken with config.accessToken
    alt Token invalid or missing
        MilkyHttpHandler->>Logger: warn(MilkyHttp, remote -> /event (Credentials invalid))
        MilkyHttpHandler-->>Client: Reject WebSocket connection
    else Token valid
        MilkyHttpHandler-->>Client: Proceed with WebSocket upgrade
    end

    deactivate MilkyHttpHandler
Loading

更新后 MilkyHttpHandler WebSocket 令牌验证的类图

classDiagram
    class MilkyHttpHandler {
        - config
        - ctx
        handleRequest(req, res)
    }

    class Config {
        + accessToken : string
    }

    class Context {
        + logger : Logger
    }

    class Logger {
        + info(message, token)
        + warn(scope, message)
    }

    MilkyHttpHandler --> Config : uses
    MilkyHttpHandler --> Context : uses
    Context --> Logger : uses

    %% Updated logic inside handleRequest for WebSocket paths:
    %% 1. Build URL from req.url and req.headers.host
    %% 2. Prefer token from req.headers.authorization (Bearer scheme)
    %% 3. Fallback to access_token query parameter
    %% 4. Log header-based token via logger.info
    %% 5. Validate token against config.accessToken and log failures via logger.warn
Loading

文件级变更

变更 详情 文件
扩展 WebSocket 访问令牌获取方式,支持从 Authorization 头中的 Bearer 令牌读取,并在缺失时回退到查询参数,同时添加日志记录。
  • 引入对 Authorization 请求头的解析,从中提取 WebSocket 请求的 Bearer 令牌。
  • 当 Authorization 请求头不存在时,回退为从查询字符串中的 access_token 读取令牌。
  • 确保 inputToken 始终为非空字符串:当未提供令牌时,默认使用空字符串。
  • 当通过请求头收到 WebSocket 令牌时,添加一条信息级别的日志记录。
  • 保留现有验证逻辑:将提供的令牌与配置中的 accessToken 进行比较,并在凭证无效时记录 warning 日志。
src/milky/network/http.ts

提示与命令

与 Sourcery 交互

  • 触发新审阅: 在 Pull Request 中评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的审阅评论。
  • 从审阅评论生成 GitHub Issue: 在某条审阅评论下回复,请求 Sourcery 从该评论创建一个 issue。你也可以直接回复 @sourcery-ai issue 来从该评论创建 issue。
  • 生成 Pull Request 标题: 在 Pull Request 标题的任意位置写上 @sourcery-ai,即可随时生成标题。你也可以在 Pull Request 中评论 @sourcery-ai title 来(重新)生成标题。
  • 生成 Pull Request 摘要: 在 Pull Request 正文的任意位置写上 @sourcery-ai summary,即可在对应位置生成 PR 摘要。你也可以在 Pull Request 中评论 @sourcery-ai summary 来随时(重新)生成摘要。
  • 生成审阅者指南: 在 Pull Request 中评论 @sourcery-ai guide,即可随时(重新)生成审阅者指南。
  • 标记所有 Sourcery 评论为已解决: 在 Pull Request 中评论 @sourcery-ai resolve,即可将所有 Sourcery 评论标记为已解决。适用于你已经处理完所有评论且不再希望看到它们的场景。
  • 撤销所有 Sourcery 审阅: 在 Pull Request 中评论 @sourcery-ai dismiss,即可撤销所有现有的 Sourcery 审阅。特别适用于你希望从一次全新的审阅开始——别忘了再评论 @sourcery-ai review 来触发新的审阅!

自定义你的体验

访问你的 控制面板 可以:

  • 启用或禁用审阅功能,例如 Sourcery 生成的 Pull Request 摘要、审阅者指南等。
  • 修改审阅语言。
  • 添加、删除或编辑自定义审阅说明。
  • 调整其他审阅设置。

获取帮助

Original review guide in English
Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Updates WebSocket access token validation to accept tokens from the Authorization header (Bearer scheme) in addition to the access_token query parameter, with added logging for header-based tokens.

Sequence diagram for updated WebSocket access token validation

sequenceDiagram
    actor Client
    participant MilkyHttpHandler
    participant URLParser as URL
    participant Logger

    Client->>MilkyHttpHandler: WebSocket HTTP upgrade request
    activate MilkyHttpHandler

    MilkyHttpHandler->>URLParser: new URL(req.url, host)
    URLParser-->>MilkyHttpHandler: url

    MilkyHttpHandler->>MilkyHttpHandler: read req.headers.authorization
    alt Authorization header present
        MilkyHttpHandler->>MilkyHttpHandler: inputToken = authHeader.split(Bearer ).pop()
        MilkyHttpHandler->>Logger: info(receive ws header token, inputToken)
    else Authorization header missing
        MilkyHttpHandler->>URLParser: url.searchParams.get(access_token)
        URLParser-->>MilkyHttpHandler: inputToken or null
        MilkyHttpHandler->>MilkyHttpHandler: inputToken = value or empty string
    end

    MilkyHttpHandler->>MilkyHttpHandler: compare inputToken with config.accessToken
    alt Token invalid or missing
        MilkyHttpHandler->>Logger: warn(MilkyHttp, remote -> /event (Credentials invalid))
        MilkyHttpHandler-->>Client: Reject WebSocket connection
    else Token valid
        MilkyHttpHandler-->>Client: Proceed with WebSocket upgrade
    end

    deactivate MilkyHttpHandler
Loading

Class diagram for updated MilkyHttpHandler WebSocket token validation

classDiagram
    class MilkyHttpHandler {
        - config
        - ctx
        handleRequest(req, res)
    }

    class Config {
        + accessToken : string
    }

    class Context {
        + logger : Logger
    }

    class Logger {
        + info(message, token)
        + warn(scope, message)
    }

    MilkyHttpHandler --> Config : uses
    MilkyHttpHandler --> Context : uses
    Context --> Logger : uses

    %% Updated logic inside handleRequest for WebSocket paths:
    %% 1. Build URL from req.url and req.headers.host
    %% 2. Prefer token from req.headers.authorization (Bearer scheme)
    %% 3. Fallback to access_token query parameter
    %% 4. Log header-based token via logger.info
    %% 5. Validate token against config.accessToken and log failures via logger.warn
Loading

File-Level Changes

Change Details Files
Extend WebSocket access token retrieval to support Bearer tokens from the Authorization header with fallback to query parameter and add logging.
  • Introduce parsing of the Authorization header to extract a Bearer token for WebSocket requests.
  • Fallback to reading the access_token from the query string when the Authorization header is absent.
  • Ensure a non-null string is always used for inputToken by defaulting to an empty string when no token is provided.
  • Add an informational log entry when a WebSocket token is received via header.
  • Keep existing validation logic that compares the provided token with the configured accessToken and logs a warning on invalid credentials.
src/milky/network/http.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey there - 我已经查看了你的改动,发现有一些需要处理的问题。

  • 请避免记录原始的访问令牌(this.ctx.logger.info('receive ws header token', inputToken)),因为这会将敏感凭据泄露到日志中;建议只记录令牌是否存在,或记录哈希/脱敏后的值。
  • 使用 authHeader.split('Bearer ').pop()! 解析 Authorization 头的方式比较脆弱;建议不区分大小写地校验前缀并去除空白(例如使用正则,或者检查 authHeader.toLowerCase().startsWith('bearer ') 然后再切片),以更安全地处理格式异常或空格异常的请求头。
给 AI Agent 的提示词
Please address the comments from this code review:

## Overall Comments
- Avoid logging the raw access token (`this.ctx.logger.info('receive ws header token', inputToken)`) as this can leak sensitive credentials into logs; log only the presence of a token or a hashed/obfuscated value instead.
- The Authorization header parsing with `authHeader.split('Bearer ').pop()!` is brittle; consider validating the prefix case-insensitively and trimming whitespace (e.g., using a regex or checking `authHeader.toLowerCase().startsWith('bearer ')` and then slicing) to handle malformed or oddly spaced headers safely.

## Individual Comments

### Comment 1
<location> `src/milky/network/http.ts:93-100` </location>
<code_context>
-        const inputToken = url.searchParams.get('access_token')
+
+        let inputToken = ''
+        const authHeader = req.headers['authorization']
+        if (authHeader) {
+          inputToken = authHeader.split('Bearer ').pop()!
+          this.ctx.logger.info('receive ws header token', inputToken)
+        } else {
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Authorization header parsing is brittle and can break with minor format variations.

The code assumes a single, exact `'Bearer '` substring, which can fail with lowercase `'bearer'`, extra spaces, or multiple auth schemes. Consider normalizing and checking the prefix instead, e.g.:

```ts
const authHeader = req.headers['authorization']
if (authHeader?.toLowerCase().startsWith('bearer ')) {
  inputToken = authHeader.slice(7).trim()
}
```

This is more robust and removes the need for `.split(...).pop()!` and the non-null assertion.

```suggestion
        let inputToken = ''
        const authHeader = req.headers['authorization']
        if (authHeader?.toLowerCase().startsWith('bearer ')) {
          inputToken = authHeader.slice(7).trim()
          this.ctx.logger.info('receive ws header token', inputToken)
        } else {
          inputToken = url.searchParams.get('access_token') ?? ''
        }
```
</issue_to_address>

### Comment 2
<location> `src/milky/network/http.ts:96` </location>
<code_context>
+        let inputToken = ''
+        const authHeader = req.headers['authorization']
+        if (authHeader) {
+          inputToken = authHeader.split('Bearer ').pop()!
+          this.ctx.logger.info('receive ws header token', inputToken)
+        } else {
</code_context>

<issue_to_address>
**🚨 issue (security):** Logging the raw access token is a security/privacy risk.

`this.ctx.logger.info('receive ws header token', inputToken)` logs the full access token, which is sensitive and should not be written to logs. Please remove the token from the log, mask it (e.g., only last few chars), or log only that a token was present along with non-sensitive context like remote address/path.
</issue_to_address>

Sourcery 对开源项目是免费的 —— 如果你觉得我们的代码审查有帮助,欢迎分享 ✨
帮我变得更有用!请对每条评论点 👍 或 👎,我会根据你的反馈改进后续的审查。
Original comment in English

Hey there - I've reviewed your changes and found some issues that need to be addressed.

  • Avoid logging the raw access token (this.ctx.logger.info('receive ws header token', inputToken)) as this can leak sensitive credentials into logs; log only the presence of a token or a hashed/obfuscated value instead.
  • The Authorization header parsing with authHeader.split('Bearer ').pop()! is brittle; consider validating the prefix case-insensitively and trimming whitespace (e.g., using a regex or checking authHeader.toLowerCase().startsWith('bearer ') and then slicing) to handle malformed or oddly spaced headers safely.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Avoid logging the raw access token (`this.ctx.logger.info('receive ws header token', inputToken)`) as this can leak sensitive credentials into logs; log only the presence of a token or a hashed/obfuscated value instead.
- The Authorization header parsing with `authHeader.split('Bearer ').pop()!` is brittle; consider validating the prefix case-insensitively and trimming whitespace (e.g., using a regex or checking `authHeader.toLowerCase().startsWith('bearer ')` and then slicing) to handle malformed or oddly spaced headers safely.

## Individual Comments

### Comment 1
<location> `src/milky/network/http.ts:93-100` </location>
<code_context>
-        const inputToken = url.searchParams.get('access_token')
+
+        let inputToken = ''
+        const authHeader = req.headers['authorization']
+        if (authHeader) {
+          inputToken = authHeader.split('Bearer ').pop()!
+          this.ctx.logger.info('receive ws header token', inputToken)
+        } else {
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Authorization header parsing is brittle and can break with minor format variations.

The code assumes a single, exact `'Bearer '` substring, which can fail with lowercase `'bearer'`, extra spaces, or multiple auth schemes. Consider normalizing and checking the prefix instead, e.g.:

```ts
const authHeader = req.headers['authorization']
if (authHeader?.toLowerCase().startsWith('bearer ')) {
  inputToken = authHeader.slice(7).trim()
}
```

This is more robust and removes the need for `.split(...).pop()!` and the non-null assertion.

```suggestion
        let inputToken = ''
        const authHeader = req.headers['authorization']
        if (authHeader?.toLowerCase().startsWith('bearer ')) {
          inputToken = authHeader.slice(7).trim()
          this.ctx.logger.info('receive ws header token', inputToken)
        } else {
          inputToken = url.searchParams.get('access_token') ?? ''
        }
```
</issue_to_address>

### Comment 2
<location> `src/milky/network/http.ts:96` </location>
<code_context>
+        let inputToken = ''
+        const authHeader = req.headers['authorization']
+        if (authHeader) {
+          inputToken = authHeader.split('Bearer ').pop()!
+          this.ctx.logger.info('receive ws header token', inputToken)
+        } else {
</code_context>

<issue_to_address>
**🚨 issue (security):** Logging the raw access token is a security/privacy risk.

`this.ctx.logger.info('receive ws header token', inputToken)` logs the full access token, which is sensitive and should not be written to logs. Please remove the token from the log, mask it (e.g., only last few chars), or log only that a token was present along with non-sensitive context like remote address/path.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/milky/network/http.ts
Comment thread src/milky/network/http.ts Outdated
let inputToken = ''
const authHeader = req.headers['authorization']
if (authHeader) {
inputToken = authHeader.split('Bearer ').pop()!
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚨 issue (security): 记录原始访问令牌是一个安全/隐私风险。

this.ctx.logger.info('receive ws header token', inputToken) 会把完整的访问令牌写进日志,而访问令牌是敏感信息,不应写入日志。请从日志中移除令牌本身,或对其做掩码处理(例如只保留最后几位),或者仅记录“存在令牌”这一事实以及非敏感的上下文信息(如远程地址/请求路径)。

Original comment in English

🚨 issue (security): Logging the raw access token is a security/privacy risk.

this.ctx.logger.info('receive ws header token', inputToken) logs the full access token, which is sensitive and should not be written to logs. Please remove the token from the log, mask it (e.g., only last few chars), or log only that a token was present along with non-sensitive context like remote address/path.

Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
@linyuchen linyuchen merged commit 3a50c58 into LLOneBot:main Dec 12, 2025
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants