-
Notifications
You must be signed in to change notification settings - Fork 548
refactor: use oxlint #3393
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
refactor: use oxlint #3393
Conversation
| } catch (e) { | ||
| console.error('Invalid regex pattern in secureDomains:', domain, e); | ||
| } catch (err) { | ||
| console.error('Invalid regex pattern in secureDomains:', domain, err); |
Check failure
Code scanning / CodeQL
Clear-text logging of sensitive information High
an access to oauthServices
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 days ago
In general, to fix clear-text logging of sensitive or untrusted data, you either (a) stop logging the sensitive value entirely, (b) redact or mask it, or (c) log only high-level metadata (e.g., that an error occurred, without including the raw data). The goal is to preserve useful diagnostics without exposing potentially sensitive content.
Here, the problematic log is:
console.error('Invalid regex pattern in secureDomains:', domain, err);domain may originate from this.ctx.state.oauthServices, which is untrusted. We should avoid logging the raw domain value. The least intrusive change, preserving functionality, is to log that there was an invalid regex pattern and optionally log non-sensitive metadata (e.g., the index in the list or a coarse type), but omit the raw domain string. Since we do not see the rest of the file and must minimize assumptions, the safest fix is to remove domain from the console.error call while keeping the error object err, which already has its own message/stack.
Concretely, in packages/server/src/logic/base.js, around line 54–57, replace the console.error invocation with one that does not interpolate or pass domain. No new imports, methods, or definitions are required; we only adjust the log message to drop the sensitive value.
-
Copy modified line R56
| @@ -53,7 +53,7 @@ | ||
| try { | ||
| return new RegExp(domain.slice(1, -1)); // 去掉斜杠并创建 RegExp 对象 | ||
| } catch (err) { | ||
| console.error('Invalid regex pattern in secureDomains:', domain, err); | ||
| console.error('Invalid regex pattern in secureDomains.', err); | ||
|
|
||
| return null; | ||
| } |
| const comment = self.comment | ||
| .replace(/<a href="(.*?)">(.*?)<\/a>/g, '\n[$2] $1\n') | ||
| .replace(/<[^>]+>/g, ''); | ||
| .replaceAll(/<a href="(.*?)">(.*?)<\/a>/g, '\n[$2] $1\n') | ||
| .replaceAll(/<[^>]+>/g, ''); | ||
| const postName = self.url; |
Check failure
Code scanning / CodeQL
Incomplete multi-character sanitization High
<script
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 days ago
In general, the problem is that self.comment is sanitized using multi-character regexes that try to remove HTML tags, but carefully crafted input can re-form dangerous sequences (like <script) after only a single pass. To fix this, we should avoid complex tag-stripping regexes and instead normalize the comment into a clearly non-HTML form—e.g., by HTML-escaping < and > or by using a library that safely sanitizes HTML. Since this field is going into a notification message (not an HTML page we’re styling), converting the comment into plain, escaped text is acceptable and avoids tag-based injection.
The best minimal change without altering existing high-level functionality is to treat the comment as plain text by escaping < and > after we’ve transformed anchor tags into the desired Markdown-like form. That means: keep the first replaceAll that converts <a href="..."> links into \n[$2] $1\n, then drop the fragile /<[^>]+>/g sanitizer and instead escape any remaining < and > characters so the downstream consumer will not interpret them as HTML. This preserves link transformation, keeps other characters as-is, and guarantees that literal <script cannot be treated as an HTML tag.
Concretely, in packages/server/src/service/notify.js around line 111, replace:
const comment = self.comment
.replaceAll(/<a href="(.*?)">(.*?)<\/a>/g, '\n[$2] $1\n')
.replaceAll(/<[^>]+>/g, '');with a version that first does the anchor transformation, then escapes < and >:
const comment = self.comment
.replaceAll(/<a href="(.*?)">(.*?)<\/a>/g, '\n[$2] $1\n')
.replaceAll(/</g, '<')
.replaceAll(/>/g, '>');No new imports are required, and we stay within standard JavaScript string operations. Functionality-wise, users still see their comment text and links represented, but any HTML-like syntax is displayed literally instead of being interpreted as markup, which removes the HTML element injection risk flagged by CodeQL.
-
Copy modified lines R113-R114
| @@ -110,7 +110,8 @@ | ||
| const QYWX_AM_AY = QYWX_AM.split(','); | ||
| const comment = self.comment | ||
| .replaceAll(/<a href="(.*?)">(.*?)<\/a>/g, '\n[$2] $1\n') | ||
| .replaceAll(/<[^>]+>/g, ''); | ||
| .replaceAll(/</g, '<') | ||
| .replaceAll(/>/g, '>'); | ||
| const postName = self.url; | ||
|
|
||
| const data = { |
| .replaceAll(/<a href="(.*?)">(.*?)<\/a>/g, '') | ||
| .replaceAll(/<[^>]+>/g, ''); | ||
|
|
Check failure
Code scanning / CodeQL
Incomplete multi-character sanitization High
<script
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 days ago
In general, to fix incomplete multi-character sanitization you either (a) use a robust sanitization library that correctly handles HTML and script tags, or (b) if you must use regex replacement, apply it repeatedly until no more changes occur so that patterns that reappear due to earlier replacements are also removed. Here, we will keep the existing intended behavior—removing <a> tags and then stripping all remaining HTML tags—but make the sanitization robust against overlapping/multi-step patterns by looping until the string stops changing.
Concretely, in packages/server/src/service/notify.js within the qq(self, parent) method, we’ll replace the current single-pass chain:
const comment = self.comment
.replaceAll(/<a href="(.*?)">(.*?)<\/a>/g, '')
.replaceAll(/<[^>]+>/g, '');with a small sanitization loop that repeatedly applies these two regexes until no further modifications are made. This stays entirely within the shown file, does not add dependencies, and preserves the intent: remove anchor tags and all HTML tags. No imports are needed; we only use built-in String.prototype.replace in a loop. We’ll implement it inline with a let variable so only the comment computation changes.
-
Copy modified line R192 -
Copy modified lines R194-R205
| @@ -189,10 +189,20 @@ | ||
| return false; | ||
| } | ||
|
|
||
| const comment = self.comment | ||
| .replaceAll(/<a href="(.*?)">(.*?)<\/a>/g, '') | ||
| .replaceAll(/<[^>]+>/g, ''); | ||
| let comment = self.comment; | ||
|
|
||
| // Repeatedly strip anchor tags and remaining HTML tags to avoid | ||
| // incomplete multi-character sanitization issues (e.g. leftover "<script"). | ||
| while (true) { | ||
| const previous = comment; | ||
| comment = comment | ||
| .replace(/<a href="(.*?)">(.*?)<\/a>/g, '') | ||
| .replace(/<[^>]+>/g, ''); | ||
| if (comment === previous) { | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| const data = { | ||
| self: { | ||
| ...self, |
| const comment = self.comment | ||
| .replace(/<a href="(.*?)">(.*?)<\/a>/g, '[Link:$2]') | ||
| .replace(/<[^>]+>/g, ''); | ||
| .replaceAll(/<a href="(.*?)">(.*?)<\/a>/g, '[Link:$2]') | ||
| .replaceAll(/<[^>]+>/g, ''); | ||
|
|
Check failure
Code scanning / CodeQL
Incomplete multi-character sanitization High
<script
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 days ago
In general, the best fix is to avoid hand-written regex-based HTML sanitization and instead either (1) properly escape the user content for the target format, or (2) use a well-tested sanitization library that neutralizes HTML/script content, rather than trying to remove tags with /\<[^>]+>/g.
For this specific code, the safest minimal change without altering higher-level behavior is:
- Stop trying to “sanitize”
self.commentby stripping tags via regex. - Instead, transform
self.commentinto a plain-text / Markdown-safe string by HTML-escaping special characters (&,<,>,",') before composing the Telegram message. This guarantees that any<script>-like sequences become harmless text and cannot be reconstituted by partial regex operations. - Preserve the existing link-extraction logic for
commentLink, which is used to build separate Markdown links from<a>tags, but ensure that the basecommentused inside the code block is fully escaped, not semi-sanitized with regex.
Concretely, in packages/server/src/service/notify.js within the telegram method:
- Add a small helper function (local to this file) to escape HTML special characters.
- Replace the current computation of
comment:
const comment = self.comment
.replaceAll(/<a href="(.*?)">(.*?)<\/a>/g, '[Link:$2]')
.replaceAll(/<[^>]+>/g, '');with a safer version that:
- Removes/rewrites anchors to
[Link:...]as currently done, but - Then escapes special characters instead of stripping tags by
<[^>]+>:
const comment = escapeHtml(
self.comment.replaceAll(/<a href="(.*?)">(.*?)<\/a>/g, '[Link:$2]')
);- Define
escapeHtmlin the same file (abovemodule.exportsor above thetelegrammethod) using straightforward character replacement; no new dependencies are needed.
This preserves behavior (links still become [Link:...], comment is still presented as text) while ensuring that <script> and other HTML cannot be interpreted, eliminating the incomplete multi-character sanitization issue.
-
Copy modified lines R7-R18 -
Copy modified lines R262-R264
| @@ -4,6 +4,18 @@ | ||
| const nodemailer = require('nodemailer'); | ||
| const nunjucks = require('nunjucks'); | ||
|
|
||
| function escapeHtml(str) { | ||
| if (typeof str !== 'string') { | ||
| return str; | ||
| } | ||
| return str | ||
| .replace(/&/g, '&') | ||
| .replace(/</g, '<') | ||
| .replace(/>/g, '>') | ||
| .replace(/"/g, '"') | ||
| .replace(/'/g, '''); | ||
| } | ||
|
|
||
| module.exports = class extends think.Service { | ||
| constructor(controller) { | ||
| super(controller); | ||
| @@ -247,9 +259,9 @@ | ||
| if (commentLink !== '') { | ||
| commentLink = `\n${commentLink}\n`; | ||
| } | ||
| const comment = self.comment | ||
| .replaceAll(/<a href="(.*?)">(.*?)<\/a>/g, '[Link:$2]') | ||
| .replaceAll(/<[^>]+>/g, ''); | ||
| const comment = escapeHtml( | ||
| self.comment.replaceAll(/<a href="(.*?)">(.*?)<\/a>/g, '[Link:$2]') | ||
| ); | ||
|
|
||
| const contentTG = | ||
| think.config('TGTemplate') || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR refactors the codebase to use oxlint instead of ESLint. Oxlint is a high-performance linter written in Rust that aims to be faster and more efficient than ESLint. The changes include:
Changes:
- Added oxlint configuration files (.oxlintrc.json and .oxlintrc.base.json) with comprehensive linting rules
- Refactored code throughout the project to address oxlint warnings and improve code quality
- Updated VS Code settings to use oxc-vscode as the default formatter
Reviewed changes
Copilot reviewed 71 out of 71 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| .oxlintrc.json | Main oxlint configuration with project-specific rules and overrides |
| .oxlintrc.base.json | Base oxlint configuration defining core linting rules |
| .vscode/settings.json | Updated default formatter to oxc-vscode |
| tsconfig.base.json | Removed unused baseUrl and added DOM.Iterable to lib |
| scripts/workspace-publish.js | Converted function to arrow function, added oxlint-disable for console.log, improved string concatenation |
| scripts/thinkjs-mock.js | Changed global to globalThis, simplified return statement |
| packages/server/src/**/*.js | Multiple refactorings: replaced .replace() with .replaceAll(), added radix to parseInt, renamed error variables to 'err', added braces to switch cases, converted arrays to Sets for lookups, improved string concatenation |
| packages/client/src/**/*.{ts,vue} | Similar refactorings: replaced Array.from() with spread, improved array checks, updated documentation, better string handling |
| packages/admin/src/**/*.{js,jsx} | Converted async functions to arrow functions where appropriate, improved event handler naming, removed unnecessary hrefs, added function names |
| packages/hexo-next/index.js | Replaced substr with slice |
| packages/cloudbase/index.js | Added oxlint-disable for func-names |
| eslint.config.js | Updated to use String.raw for regex |
| docs/src/.vuepress/**/*.{ts,vue} | Minor refactorings for oxlint compatibility |
Comments suppressed due to low confidence (1)
packages/admin/src/pages/profile/index.jsx:112
- This anchor element has
target="_blank"andrel="noreferrer"attributes but nohref. These attributes have no effect without anhref. Since this is meant to be a clickable element for changing the avatar (with anonClickhandler), consider either removing these attributes or converting this to a<button>element for better semantics and accessibility.
<a
title={t('change avatar')}
target="_blank"
rel="noreferrer"
onClick={changeAvatar}
>
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (items.length > 0 && !isNaN(parseInt(items[0].objectId))) { | ||
| lastKey = parseInt(items[0].objectId); |
Copilot
AI
Jan 28, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing radix parameter in parseInt() calls. It's recommended to always specify the radix (typically 10) to avoid potential issues. Consider changing to parseInt(items[0].objectId, 10) for consistency with other parseInt calls in this PR.
| <a key={key} className={`operate-${key}`} onClick={action}> | ||
| {name} | ||
| </a> |
Copilot
AI
Jan 28, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The removal of href="javascript:void(0)" from anchor elements that only have onClick handlers may impact keyboard accessibility. Anchor elements without an href attribute are not keyboard-focusable by default. Consider adding role="button" and tabIndex={0} to these elements, or use <button> elements styled as links instead for better accessibility.
| <a key={key} className={`operate-${key}`} onClick={action}> | |
| {name} | |
| </a> | |
| <button | |
| key={key} | |
| type="button" | |
| className={`operate-${key}`} | |
| onClick={action} | |
| > | |
| {name} | |
| </button> |
| DOMPurify.sanitize(content, { | ||
| FORBID_TAGS: ['form', 'input', 'style'], | ||
| FORBID_ATTR: ['autoplay', 'style'], | ||
| ...think.config('domPurify'), |
Copilot
AI
Jan 28, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The spread operator ...think.config('domPurify') will throw an error if think.config('domPurify') returns undefined. The original code used Object.assign with think.config('domPurify') || {} which safely handled undefined values. Consider changing this to ...(think.config('domPurify') || {}) to maintain backward compatibility and prevent runtime errors.
|
@lizheming Please continue this work when you are spare. I reuse another preset of mine, which enables more rules then the original eslint one. You should take care of the useCallback warnings. |
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
No description provided.