8000 feat: sqlite3 web by sj817 · Pull Request #497 · KarinJS/Karin · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

feat: sqlite3 web #497

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

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft

feat: sqlite3 web #497

wants to merge 1 commit into from

Conversation

sj817
Copy link
Member
@sj817 sj817 commented Jun 30, 2025

#371

好的,这是翻译成中文的 pull request 总结:

Sourcery 总结

实现全栈 SQLite 管理,通过暴露新的服务器处理程序并添加一个专门的前端页面,用于交互式数据库浏览和操作。

新功能:

  • 添加后端 SQLite 数据库操作:表模式检索、数据获取和 SQL 执行端点
  • 添加前端 SQLite 管理器页面,具有用于连接到数据库、浏览表、执行 CRUD 操作和运行自定义 SQL 查询的 UI

增强功能:

  • 注册新的 API 路由和客户端请求方法以进行 SQLite 操作
  • 将 SQLite 管理链接和路由集成到 Web 应用程序导航中
Original summary in English

Summary by Sourcery

Implement full-stack SQLite management by exposing new server handlers and adding a dedicated frontend page for interactive database browsing and manipulation

New Features:

  • Add backend SQLite database operations: table schema retrieval, data fetching, and SQL execution endpoints
  • Add frontend SQLite manager page with UI for connecting to a database, browsing tables, performing CRUD operations, and running custom SQL queries

Enhancements:

  • Register new API routes and client request methods for SQLite operations
  • Integrate a SQLite management link and route into the web app navigation

@sj817 sj817 marked this pull request as draft June 30, 2025 06:40
Copy link
Contributor
sourcery-ai bot commented Jun 30, 2025
## 审查者指南

实现了端到端的 SQLite 数据库管理:注册新的服务器端点,扩展 API 客户端,并添加一个功能齐全的前端 UI,用于浏览、查询和修改 SQLite 表。

#### SQLite API 客户端中新增和更新类型的类图

```mermaid
classDiagram
  class ColumnInfo {
    +string name
    +string type
    +number pk
    +number notnull
    +string dflt_value
    +number cid
  }
  class TableInfo {
    +string name
    +ColumnInfo[] columns
  }
  class SqliteTableData {
    +any[] records
    +number total
    +number page
    +number pageSize
  }
  class SqliteExecuteResult {
    +number changes
    +number lastID
  }
  TableInfo "1" -- "*" ColumnInfo : has

文件级别变更

变更 详情 文件
在服务器路由器中添加 SQLite 管理端点
  • 注册了用于列出表、获取表数据和执行任意 SQL 的 POST 路由
  • 版本化并导出了新的路由常量
  • 使用 sqlite3 实现了处理程序,包含 promise 包装的查询和错误处理
packages/core/src/server/router/index.ts
packages/core/src/server/router/router.ts
packages/core/src/server/database/sqlite.ts
扩展了用于 SQLite 操作的前端 API 层
  • 声明了用于表模式、数据检索和 SQL 执行的新 API 路径
  • 使用 serverPost 编写了请求包装器,包含统一的错误处理
packages/web/src/request/base/api.ts
packages/web/src/request/sqlite/index.ts
集成了 SQLite 管理 UI 页面
  • 在 App.tsx 和站点配置中添加了导航条目和路由
  • 开发了 SqliteManagerPage,包含表列表、分页、搜索和 SQL 编辑器
  • 构建了用于记录编辑/添加的模态框,并连接了 CRUD 操作
packages/web/src/config/site.ts
packages/web/src/App.tsx
packages/web/src/pages/dashboard/sqlite/index.tsx

提示和命令

与 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 以触发新的审查!

自定义您的体验

访问您的 dashboard 以:

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

获取帮助

```
Original review guide in English

Reviewer's Guide

Implements end-to-end SQLite database management: registers new server endpoints, extends the API client, and adds a full-featured frontend UI for browsing, querying, and modifying SQLite tables.

Class diagram for new and updated types in SQLite API client

classDiagram
  class ColumnInfo {
    +string name
    +string type
    +number pk
    +number notnull
    +string dflt_value
    +number cid
  }
  class TableInfo {
    +string name
    +ColumnInfo[] columns
  }
  class SqliteTableData {
    +any[] records
    +number total
    +number page
    +number pageSize
  }
  class SqliteExecuteResult {
    +number changes
    +number lastID
  }
  TableInfo "1" -- "*" ColumnInfo : has
Loading

File-Level Changes

Change Details Files
Added SQLite management endpoints in the server router
  • Registered POST routes for listing tables, fetching table data, and executing arbitrary SQL
  • Versioned and exported new route constants
  • Implemented handlers using sqlite3 with promise-wrapped queries and error handling
packages/core/src/server/router/index.ts
packages/core/src/server/router/router.ts
packages/core/src/server/database/sqlite.ts
Extended frontend API layer for SQLite operations
  • Declared new API paths for table schema, data retrieval, and SQL execution
  • Authored request wrappers using serverPost with unified error handling
packages/web/src/request/base/api.ts
packages/web/src/request/sqlite/index.ts
Integrated a SQLite management UI page
  • Added navigation entry and route to App.tsx and site config
  • Developed SqliteManagerPage with table list, pagination, search, and SQL editor
  • Built modals for record editing/adding and wired CRUD actions
pa 8000 ckages/web/src/config/site.ts
packages/web/src/App.tsx
packages/web/src/pages/dashboard/sqlite/index.tsx

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
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.

@sj817 - 我已经查看了你的更改,发现了一些需要解决的问题。

阻塞问题:

  • 检测到用于手动构造 SQL 字符串的用户输入。这通常是不好的做法,因为手动构造可能会意外导致 SQL 注入。攻击者可以使用 SQL 注入来窃取或修改数据库的内容。请改用参数化查询,大多数数据库引擎默认提供该查询。或者,考虑使用对象关系映射器 (ORM),例如 Sequelize,它将保护您的查询。(link)
  • 检测到用于手动构造 SQL 字符串的用户输入。这通常是不好的做法,因为手动构造可能会意外导致 SQL 注入。攻击者可以使用 SQL 注入来窃取或修改数据库的内容。请改用参数化查询,大多数数据库引擎默认提供该查询。或者,考虑使用对象关系映射器 (ORM),例如 Sequelize,它将保护您的查询。(link)
  • PRAGMA table_info 使用中存在潜在的 SQL 注入风险。(link)
  • 直接内插 tableName 和 where 子句会引入 SQL 注入风险。(link)
  • SELECT 语句中存在 SQL 注入风险,用于表数据。(link)
  • 执行任意 SQL 而不进行验证存在安全风险。(link)
  • 直接字符串内插 SQL 值可能导致 SQL 注入或数据损坏。(link)
  • 在 INSERT 语句中直接进行值内插是不安全的。(link)
  • 在 DELETE 语句中直接内插表名和列名是不安全的。(link)

一般评论:

  • 服务器和客户端代码正在通过连接原始输入(例如表名、where 子句、编辑的值)来构建 SQL 语句,这很容易受到 SQL 注入的攻击——考虑使用参数化查询或适当的转义。
  • 新的 SqliteManagerPage 组件超过 500 行,并且混合了获取逻辑、模态框、表渲染和 SQL 执行——考虑将其重构为更小的钩子和子组件,以提高清晰度和可维护性。
  • 三个 Express 处理程序重复打开和关闭数据库,逻辑几乎相同——将常见的数据库初始化、错误处理和响应格式化提取到共享实用程序中,以减少重复。
Prompt for AI Agents
请解决此代码审查中的评论:
## 总体评论
- 服务器和客户端代码正在通过连接原始输入(例如表名、where 子句、编辑的值)来构建 SQL 语句,这很容易受到 SQL 注入的攻击——考虑使用参数化查询或适当的转义。
- 新的 SqliteManagerPage 组件超过 500 行,并且混合了获取逻辑、模态框、表渲染和 SQL 执行——考虑将其重构为更小的钩子和子组件,以提高清晰度和可维护性。
- 三个 Express 处理程序重复打开和关闭数据库,逻辑几乎相同——将常见的数据库初始化、错误处理和响应格式化提取到共享实用程序中,以减少重复。

## 单独评论

### 评论 1
<location> `packages/core/src/server/database/sqlite.ts:16` </location>
<code_context>
+      return res.status(400).json({ code: 400, message: '数据库路径不能为空' })
+    }
+
+    const db = new sqlite3.Database(dbPath, (err) => {
+      if (err) {
+        console.error('连接数据库失败', err)
</code_context>

<issue_to_address>
数据库连接未在早期错误时关闭。

请在错误分支中显式关闭数据库连接,以确保即使构造函数部分成功,也能释放资源。
</issue_to_address>

### 评论 2
<location> `packages/core/src/server/database/sqlite.ts:35` </location>
<code_context>
+
+    const getColumns = (tableName: string): Promise<any[]> => {
+      return new Promise((resolve, reject) =>
8000
 {
+        db.all(`PRAGMA table_info(${tableName})`, (err, rows) => {
+          if (err) reject(err)
+          else resolve(rows)
</code_context>

<issue_to_address>
PRAGMA table_info 使用中存在潜在的 SQL 注入风险。

在使用 PRAGMA 语句之前,请验证 `tableName` 是否为安全标识符,因为此处不支持参数化查询。
</issue_to_address>

### 评论 3
<location> `packages/core/src/server/database/sqlite.ts:94` </location>
<code_context>
+
+    const getCount = (): Promise<number> => {
+      return new Promise((resolve, reject) => {
+        db.get(`SELECT COUNT(*) as count FROM ${tableName} ${where ? 'WHERE ' + where : ''}`, (err, row: { count: number }) => {
+          if (err) reject(err)
+          else resolve(row.count)
</code_context>

<issue_to_address>
直接内插 tableName 和 where 子句会引入 SQL 注入风险。

验证 `tableName` 是否为标识符,并对 `where` 子句使用参数化查询,以防止 SQL 注入。
</issue_to_address>

### 评论 4
<location> `packages/core/src/server/database/sqlite.ts:104` </location>
<code_context>
+    const getData = (offset: number): Promise<any[]> => {
+      return new Promise((resolve, reject) => {
+        db.all(
+          `SELECT * FROM ${tableName} ${where ? 'WHERE ' + where : ''} LIMIT ${pageSize} OFFSET ${offset}`,
+          (err, rows) => {
+            if (err) reject(err)
</code_context>

<issue_to_address>
SELECT 语句中存在 SQL 注入风险,用于表数据。

直接内插 `tableName``where` 允许 SQL 注入。验证 `tableName` 并对 `where` 使用参数化查询。
</issue_to_address>

### 评论 5
<location> `packages/core/src/server/database/sqlite.ts:160` </location>
<code_context>
+      }
+    })
+
+    const statement = sql.trim().toLowerCase()
+
+    // 判断是否为查询语句
</code_context>

<issue_to_address>
SQL 语句类型检测区分大小写,但不够健壮。

当前的检测可能因前导注释、空格或以 `WITH` 等构造开头的查询而失败。考虑删除输入中的空格并处理注释,或使用更健壮的 SQL 解析器。
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
    const statement = sql.trim().toLowerCase()

    // 判断是否为查询语句
    const isQuery = statement.startsWith('select')
=======
    // 更健壮的SQL类型检测:去除前导空白和注释,检测SELECT/WITH等查询语句
    function getFirstKeyword(sql: string): string | null {
      // 去除前导空白
      let s = sql.trim()
      // 去除前导多行注释
      while (s.startsWith('/*')) {
        const end = s.indexOf('*/')
        if (end === -1) break
        s = s.slice(end + 2).trim()
      }
      // 去除前导单行注释
      while (s.startsWith('--')) {
        const end = s.indexOf('\n')
        if (end === -1) return null
        s = s.slice(end + 1).trim()
      }
      // 匹配第一个单词
      const match = s.match(/^([a-zA-Z]+)/)
      return match ? match[1].toLowerCase() : null
    }

    const firstKeyword = getFirstKeyword(sql)
    // 判断是否为查询语句(SELECT 或 WITH 开头)
    const isQuery = firstKeyword === 'select' || firstKeyword === 'with'
>>>>>>> REPLACE

</suggested_fix>

### 评论 6
<location> `packages/core/src/server/database/sqlite.ts:189` </location>
<code_context>
+        // 非查询语句
+        const executeUpdate = (): Promise<{ changes: number, lastID: number }> => {
+          return new Promise((resolve, reject) => {
+            db.run(sql, function (err) {
+              if (err) reject(err)
+              else resolve({ changes: this.changes, lastID: this.lastID })
</code_context>

<issue_to_address>
执行任意 SQL 而不进行验证存在安全风险。

实施诸如语句白名单、严格身份验证或沙盒之类的安全措施来降低风险。
</issue_to_address>

### 评论 7
<location> `packages/web/src/pages/dashboard/sqlite/index.tsx:125` </location>
<code_context>
+      const pkValue = editingRecord[pkColumn.name]
+
+      // 构建SET部分
+      const setClause = Object.entries(editedValues)
+        .map(([column, value]) => {
+          if (value === null) return `${column} = NULL`
+          return typeof value === 'string' ? `${column} = '${value}'` : `${column} = ${value}`
+        })
+        .join(', ')
+
+      // 构建SQL更新语句
</code_context>

<issue_to_address>
直接字符串内插 SQL 值可能导致 SQL 注入或数据损坏。

使用参数化查询或转义单引号以防止 SQL 注入并安全地处理特殊字符。
</issue_to_address>

### 评论 8
<location> `packages/web/src/pages/dashboard/sqlite/index.tsx:160` </location>
<code_context>
+
+      // 构建列和值
+      const columns = Object.keys(newRecord).join(', ')
+      const values = Object.values(newRecord).map(v =>
+        v === null ? 'NULL' : (typeof v === 'string' ? `'${v}'` : v)
+      ).join(', ')
+
+      // 构建SQL插入语句
</code_context>

<issue_to_address>
在 INSERT 语句中直接进行值内插是不安全的。

这种方法会将代码暴露于 SQL 注入以及特殊字符问题。请改用参数化查询。
</issue_to_address>

### 评论 9
<location> `packages/web/src/pages/dashboard/sqlite/index.tsx:209` </location>
<code_context>
+      }
+
+      // 构建SQL删除语句
+      const sql = `DELETE FROM ${selectedTable} WHERE ${pkColumn.name} = ${typeof pkValue === 'string' ? `'${pkValue}'` : pkValue}`
+
+      // 执行删除操作
</code_context>

<issue_to_address>
在 DELETE 语句中直接内插表名和列名是不安全的。

使用前验证表名和列名,并始终对值使用参数化查询,以防止 SQL 注入。
</issue_to_address>

### 评论 10
<location> `packages/web/src/pages/dashboard/sqlite/index.tsx:24` </location>
<code_context>
+  const [currentPage, setCurrentPage] = useState<number>(1)
+  const [searchValue, setSearchValue] = useState<string>('')
+  const [sqlQuery, setSqlQuery] = useState<string>('')
+  const [queryResult, setQueryResult] = useState<any[] | null>(null)
+
+  // 编辑相关状态
</code_context>

<issue_to_address>
切换表时未清除查询结果状态。

当所选表更改时,清除 `queryResult` 以防止显示过时的结果。
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH

  // 编辑相关状态
  const { isOpen, onOpen, onClose } = useDisclosure()
=======

  useEffect(() => {
    setQueryResult(null)
  }, [selectedTable])

  // 编辑相关状态
  const { isOpen, onOpen, onClose } = useDisclosure()
>>>>>>> REPLACE

</suggested_fix>

## 安全问题

### 问题 1
<location> `packages/core/src/server/database/sqlite.ts:94` </location>

<issue_to_address>
**security (opengrep-rules.javascript.express.security.injection.tainted-sql-string):** 检测到用于手动构造 SQL 字符串的用户输入。这通常是不好的做法,因为手动构造可能会意外导致 SQL 注入。攻击者可以使用 SQL 注入来窃取或修改数据库的内容。请改用参数化查询,大多数数据库引擎默认提供该查询。或者,考虑使用对象关系映射器 (ORM),例如 Sequelize,它将保护您的查询。

*Source: opengrep*
</issue_to_address>

### 问题 2
<location> `packages/core/src/server/database/sqlite.ts:104` </location>

<issue_to_address>
**security (opengrep-rules.javascript.express.security.injection.tainted-sql-string):** 检测到用于手动构造 SQL 字符串的用户输入。这通常是不好的做法,因为手动构造可能会意外导致 SQL 注入。攻击者可以使用 SQL 注入来窃取或修改数据库的内容。请改用参数化查询,大多数数据库引擎默认提供该查询。或者,考虑使用对象关系映射器 (ORM),例如 Sequelize,它将保护您的查询。

*Source: opengrep*
</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.
Original comment in English

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

Blocking issues:

  • Detected user input used to manually construct a SQL string. This is usually bad practice because manual construction could accidentally result in a SQL injection. An attacker could use a SQL injection to steal or modify contents of the database. Instead, use a parameterized query which is available by default in most database engines. Alternatively, consider using an object-relational mapper (ORM) such as Sequelize which will protect your queries. (link)
  • Detected user input used to manually construct a SQL string. This is usually bad practice because manual construction could accidentally result in a SQL injection. An attacker could use a SQL injection to steal or modify contents of the database. Instead, use a parameterized query which is available by default in most database engines. Alternatively, consider using an object-relational mapper (ORM) such as Sequelize which will protect your queries. (link)
  • Potential SQL injection risk in PRAGMA table_info usage. (link)
  • Direct interpolation of tableName and where clause introduces SQL injection risk. (link)
  • SQL injection risk in SELECT statement for table data. (link)
  • Executing arbitrary SQL without validation is a security risk. (link)
  • Direct string interpolation for SQL values can cause SQL injection or data corruption. (link)
  • Direct value interpolation in INSERT statement is unsafe. (link)
  • Direct interpolation of table and column names in DELETE statement is unsafe. (link)

General comments:

  • The server and client code are building SQL statements by concatenating raw input (e.g. table names, where clauses, edited values), which is vulnerable to SQL injection—consider using parameterized queries or proper escaping.
  • The new SqliteManagerPage component is over 500 lines long and mixes fetching logic, modals, table rendering, and SQL execution—consider refactoring it into smaller hooks and child components for clarity and maintainability.
  • The three Express handlers repeatedly open and close the database with nearly identical logic—extract the common DB initialization, error handling, and response formatting into a shared utility to reduce duplication.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The server and client code are building SQL statements by concatenating raw input (e.g. table names, where clauses, edited values), which is vulnerable to SQL injection—consider using parameterized queries or proper escaping.
- The new SqliteManagerPage component is over 500 lines long and mixes fetching logic, modals, table rendering, and SQL execution—consider refactoring it into smaller hooks and child components for clarity and maintainability.
- The three Express handlers repeatedly open and close the database with nearly identical logic—extract the common DB initialization, error handling, and response formatting into a shared utility to reduce duplication.

## Individual Comments

### Comment 1
<location> `packages/core/src/server/database/sqlite.ts:16` </location>
<code_context>
+      return res.status(400).json({ code: 400, message: '数据库路径不能为空' })
+    }
+
+    const db = new sqlite3.Database(dbPath, (err) => {
+      if (err) {
+        console.error('连接数据库失败', err)
</code_context>

<issue_to_address>
Database connection is not closed on early error.

Explicitly close the database connection in the error branch to ensure resources are released, even if the constructor partially succeeds.
</issue_to_address>

### Comment 2
<location> `packages/core/src/server/database/sqlite.ts:35` </location>
<code_context>
+
+    const getColumns = (tableName: string): Promise<any[]> => {
+      return new Promise((resolve, reject) => {
+        db.all(`PRAGMA table_info(${tableName})`, (err, rows) => {
+          if (err) reject(err)
+          else resolve(rows)
</code_context>

<issue_to_address>
Potential SQL injection risk in PRAGMA table_info usage.

Validate that `tableName` is a safe identifier before using it in the PRAGMA statement, as parameterized queries are not supported here.
</issue_to_address>

### Comment 3
<location> `packages/core/src/server/database/sqlite.ts:94` </location>
<code_context>
+
+    const getCount = (): Promise<number> => {
+      return new Promise((resolve, reject) => {
+        db.get(`SELECT COUNT(*) as count FROM ${tableName} ${where ? 'WHERE ' + where : ''}`, (err, row: { count: number }) => {
+          if (err) reject(err)
+          else resolve(row.count)
</code_context>

<issue_to_address>
Direct interpolation of tableName and where clause introduces SQL injection risk.

Validate `tableName` as an identifier and use parameterized queries for the `where` clause to prevent SQL injection.
</issue_to_address>

### Comment 4
<location> `packages/core/src/server/database/sqlite.ts:104` </location>
<code_context>
+    const getData = (offset: number): Promise<any[]> => {
+      return new Promise((resolve, reject) => {
+        db.all(
+          `SELECT * FROM ${tableName} ${where ? 'WHERE ' + where : ''} LIMIT ${pageSize} OFFSET ${offset}`,
+          (err, rows) => {
+            if (err) reject(err)
</code_context>

<issue_to_address>
SQL injection risk in SELECT statement for table data.

Directly interpolating `tableName` and `where` allows SQL injection. Validate `tableName` and use parameterized queries for `where`.
</issue_to_address>

### Comment 5
<location> `packages/core/src/server/database/sqlite.ts:160` </location>
<code_context>
+      }
+    })
+
+    const statement = sql.trim().toLowerCase()
+
+    // 判断是否为查询语句
</code_context>

<issue_to_address>
SQL statement type detection is case-insensitive but not robust.

Current detection may fail with leading comments, whitespace, or queries starting with constructs like `WITH`. Consider trimming input and handling comments, or use a more robust SQL parser.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
    const statement = sql.trim().toLowerCase()

    // 判断是否为查询语句
    const isQuery = statement.startsWith('select')
=======
    // 更健壮的SQL类型检测:去除前导空白和注释,检测SELECT/WITH等查询语句
    function getFirstKeyword(sql: string): string | null {
      // 去除前导空白
      let s = sql.trim()
      // 去除前导多行注释
      while (s.startsWith('/*')) {
        const end = s.indexOf('*/')
        if (end === -1) break
        s = s.slice(end + 2).trim()
      }
      // 去除前导单行注释
      while (s.startsWith('--')) {
        const end = s.indexOf('\n')
        if (end === -1) return null
        s = s.slice(end + 1).trim()
      }
      // 匹配第一个单词
      const match = s.match(/^([a-zA-Z]+)/)
      return match ? match[1].toLowerCase() : null
    }

    const firstKeyword = getFirstKeyword(sql)
    // 判断是否为查询语句(SELECT 或 WITH 开头)
    const isQuery = firstKeyword === 'select' || firstKeyword === 'with'
>>>>>>> REPLACE

</suggested_fix>

### Comment 6
<location> `packages/core/src/server/database/sqlite.ts:189` </location>
<code_context>
+        // 非查询语句
+        const executeUpdate = (): Promise<{ changes: number, lastID: number }> => {
+          return new Promise((resolve, reject) => {
+            db.run(sql, function (err) {
+              if (err) reject(err)
+              else resolve({ changes: this.changes, lastID: this.lastID })
</code_context>

<issue_to_address>
Executing arbitrary SQL without validation is a security risk.

Implement safeguards such as statement whitelisting, strict authentication, or sandboxing to mitigate risks.
</issue_to_address>

### Comment 7
<location> `packages/web/src/pages/dashboard/sqlite/index.tsx:125` </location>
<code_context>
+      const pkValue = editingRecord[pkColumn.name]
+
+      // 构建SET部分
+      const setClause = Object.entries(editedValues)
+        .map(([column, value]) => {
+          if (value === null) return `${column} = NULL`
+          return typeof value === 'string' ? `${column} = '${value}'` : `${column} = ${value}`
+        })
+        .join(', ')
+
+      // 构建SQL更新语句
</code_context>

<issue_to_address>
Direct string interpolation for SQL values can cause SQL injection or data corruption.

Use parameterized queries or escape single quotes to prevent SQL injection and handle special characters safely.
</issue_to_address>

### Comment 8
<location> `packages/web/src/pages/dashboard/sqlite/index.tsx:160` </location>
<code_context>
+
+      // 构建列和值
+      const columns = Object.keys(newRecord).join(', ')
+      const values = Object.values(newRecord).map(v =>
+        v === null ? 'NULL' : (typeof v === 'string' ? `'${v}'` : v)
+      ).join(', ')
+
+      // 构建SQL插入语句
</code_context>

<issue_to_address>
Direct value interpolation in INSERT statement is unsafe.

This approach exposes the code to SQL injection and issues with special characters. Use parameterized queries instead.
</issue_to_address>

### Comment 9
<location> `packages/web/src/pages/dashboard/sqlite/index.tsx:209` </location>
<code_context>
+      }
+
+      // 构建SQL删除语句
+      const sql = `DELETE FROM ${selectedTable} WHERE ${pkColumn.name} = ${typeof pkValue === 'string' ? `'${pkValue}'` : pkValue}`
+
+      // 执行删除操作
</code_context>

<issue_to_address>
Direct interpolation of table and column names in DELETE statement is unsafe.

Validate table and column names before use, and always use parameterized queries for values to prevent SQL injection.
</issue_to_address>

### Comment 10
<location> `packages/web/src/pages/dashboard/sqlite/index.tsx:24` </location>
<code_context>
+  const [currentPage, setCurrentPage] = useState<number>(1)
+  const [searchValue, setSearchValue] = useState<string>('')
+  const [sqlQuery, setSqlQuery] = useState<string>('')
+  const [queryResult, setQueryResult] = useState<any[] | null>(null)
+
+  // 编辑相关状态
</code_context>

<issue_to_address>
Query result state is not cleared when switching tables.

Clear `queryResult` when the selected table changes to prevent displaying outdated results.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH

  // 编辑相关状态
  const { isOpen, onOpen, onClose } = useDisclosure()
=======

  useEffect(() => {
    setQueryResult(null)
  }, [selectedTable])

  // 编辑相关状态
  const { isOpen, onOpen, onClose } = useDisclosure()
>>>>>>> REPLACE

</suggested_fix>

## Security Issues

### Issue 1
<location> `packages/core/src/server/database/sqlite.ts:94` </location>

<issue_to_address>
**security (opengrep-rules.javascript.express.security.injection.tainted-sql-string):** Detected user input used to manually construct a SQL string. This is usually bad practice because manual construction could accidentally result in a SQL injection. An attacker could use a SQL injection to steal or modify contents of the database. Instead, use a parameterized query which is available by default in most database engines. Alternatively, consider using an object-relational mapper (ORM) such as Sequelize which will protect your queries.

*Source: opengrep*
</issue_to_address>

### Issue 2
<location> `packages/core/src/server/database/sqlite.ts:104` </location>

<issue_to_address>
**security (opengrep-rules.javascript.express.security.injection.tainted-sql-string):** Detected user input used to manually construct a SQL string. This is usually bad practice because manual construction could accidentally result in a SQL injection. An attacker could use a SQL injection to steal or modify contents of the database. Instead, use a parameterized query which is available by default in most database engines. Alternatively, consider using an object-relational mapper (ORM) such as Sequelize which will protect your queries.

*Source: opengrep*
</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.

return res.status(400).json({ code: 400, message: '数据库路径不能为空' })
}

const db = new sqlite3.Database(dbPath, (err) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (bug_risk): 数据库连接未在早期错误时关闭。

请在错误分支中显式关闭数据库连接,以确保即使构造函数部分成功,也能释放资源。

Original comment in English

issue (bug_risk): Database connection is not closed on early error.

Explicitly close the database connection in the error branch to ensure resources are released, even if the constructor partially succeeds.


const getColumns = (tableName: string): Promise<any[]> => {
return new Promise((resolve, reject) => {
db.all(`PRAGMA table_info(${tableName})`, (err, rows) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

🚨 issue (security): PRAGMA table_info 使用中存在潜在的 SQL 注入风险。

在使用 PRAGMA 语句之前,请验证 tableName 是否为安全标识符,因为此处不支持参数化查询。

Original comment in English

🚨 issue (security): Potential SQL injection risk in PRAGMA table_info usage.

Validate that tableName is a safe identifier before using it in the PRAGMA statement, as parameterized queries are not supported here.


const getCount = (): Promise<number> => {
return new Promise((resolve, reject) => {
db.get(`SELECT COUNT(*) as count FROM ${tableName} ${where ? 'WHERE ' + where : ''}`, (err, row: { count: number }) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

🚨 issue (security): 直接内插 tableName 和 where 子句会引入 SQL 注入风险。

验证 tableName 是否为标识符,并对 where 子句使用参数化查询,以防止 SQL 注入。

Original comment in English

🚨 issue (security): Direct interpolation of tableName and where clause introduces SQL injection risk.

Validate tableName as an identifier and use parameterized queries for the where clause to prevent SQL injection.

const getData = (offset: number): Promise<any[]> => {
return new Promise((resolve, reject) => {
db.all(
`SELECT * FROM ${tableName} ${where ? 'WHERE ' + where : ''} LIMIT ${pageSize} OFFSET ${offset}`,
Copy link
Contributor

Choose a reason for hiding this comment

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

🚨 issue (security): SELECT 语句中存在 SQL 注入风险,用于表数据。

直接内插 tableNamewhere 允许 SQL 注入。验证 tableName 并对 where 使用参数化查询。

Original comment in English

🚨 issue (security): SQL injection risk in SELECT statement for table data.

Directly interpolating tableName and where allows SQL injection. Validate tableName and use parameterized queries for where.

Comment on lines +160 to +163
const statement = sql.trim().toLowerCase()

// 判断是否为查询语句
const isQuery = statement.startsWith('select')
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (bug_risk): SQL 语句类型检测区分大小写,但不够健壮。

当前的检测可能因前导注释、空格或以 WITH 等构造开头的查询而失败。考虑删除输入中的空格并处理注释,或使用更健壮的 SQL 解析器。

Suggested change
const statement = sql.trim().toLowerCase()
// 判断是否为查询语句
const isQuery = statement.startsWith('select')
// 更健壮的SQL类型检测:去除前导空白和注释,检测SELECT/WITH等查询语句
function getFirstKeyword(sql: string): string | null {
// 去除前导空白
let s = sql.trim()
// 去除前导多行注释
while (s.startsWith('/*')) {
const end = s.indexOf('*/')
if (end === -1) break
s = s.slice(end + 2).trim()
}
// 去除前导单行注释
while (s.startsWith('--')) {
const end = s.indexOf('\n')
if (end === -1) return null
s = s.slice(end + 1).trim()
}
// 匹配第一个单词
const match = s.match(/^([a-zA-Z]+)/)
return match ? match[1].toLowerCase() : null
}
const firstKeyword = getFirstKeyword(sql)
// 判断是否为查询语句(SELECT 或 WITH 开头)
const isQuery = firstKeyword === 'select' || firstKeyword === 'with'
Original comment in English

suggestion (bug_risk): SQL statement type detection is case-insensitive but not robust.

Current detection may fail with leading comments, whitespace, or queries starting with constructs like WITH. Consider trimming input and handling comments, or use a more robust SQL parser.

Suggested change
const statement = sql.trim().toLowerCase()
// 判断是否为查询语句
const isQuery = statement.startsWith('select')
// 更健壮的SQL类型检测:去除前导空白和注释,检测SELECT/WITH等查询语句
function getFirstKeyword(sql: string): string | null {
// 去除前导空白
let s = sql.trim()
// 去除前导多行注释
while (s.startsWith('/*')) {
const end = s.indexOf('*/')
if (end === -1) break
s = s.slice(end + 2).trim()
}
// 去除前导单行注释
while (s.startsWith('--')) {
const end = s.indexOf('\n')
if (end === -1) return null
s = s.slice(end + 1).trim()
}
// 匹配第一个单词
const match = s.match(/^([a-zA-Z]+)/)
return match ? match[1].toLowerCase() : null
}
const firstKeyword = getFirstKeyword(sql)
// 判断是否为查询语句(SELECT 或 WITH 开头)
const isQuery = firstKeyword === 'select' || firstKeyword === 'with'

Comment on lines +160 to +162
const values = Object.values(newRecord).map(v =>
v === null ? 'NULL' : (typeof v === 'string' ? `'${v}'` : v)
).join(', ')
Copy link
Contributor

Choose a reason for hiding this comment

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

🚨 issue (security): 在 INSERT 语句中直接进行值内插是不安全的。

这种方法会将代码暴露于 SQL 注入以及特殊字符问题。请改用参数化查询。

Original comment in English

🚨 issue (security): Direct value interpolation in INSERT statement is unsafe.

This approach exposes the code to SQL injection and issues with special characters. Use parameterized queries instead.

}

// 构建SQL删除语句
const sql = `DELETE FROM ${selectedTable} WHERE ${pkColumn.name} = ${typeof pkValue === 'string' ? `'${pkValue}'` : pkValue}`
Copy link
Contributor

Choose a reason for hiding this comment

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

🚨 issue (security): 在 DELETE 语句中直接内插表名和列名是不安全的。

使用前验证表名和列名,并始终对值使用参数化查询,以防止 SQL 注入。

Original comment in English

🚨 issue (security): Direct interpolation of table and column names in DELETE statement is unsafe.

Validate table and column names before use, and always use parameterized queries for values to prevent SQL injection.

Comment on lines +25 to +27

// 编辑相关状态
const { isOpen, onOpen, onClose } = useDisclosure()
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (bug_risk): 切换表时未清除查询结果状态。

当所选表更改时,清除 queryResult 以防止显示过时的结果。

Suggested change
// 编辑相关状态
const { isOpen, onOpen, onClose } = useDisclosure()
useEffect(() => {
setQueryResult(null)
}, [selectedTable])
// 编辑相关状态
const { isOpen, onOpen, onClose } = useDisclosure()
Original comment in English

suggestion (bug_risk): Query result state is not cleared when switching tables.

Clear queryResult when the selected table changes to prevent displaying outdated results.

Suggested change
// 编辑相关状态
const { isOpen, onOpen, onClose } = useDisclosure()
useEffect(() => {
setQueryResult(null)
}, [selectedTable])
// 编辑相关状态
const { isOpen, onOpen, onClose } = useDisclosure()


const getCount = (): Promise<number> => {
return new Promise((resolve, reject) => {
db.get(`SELECT COUNT(*) as count FROM ${tableName} ${where ? 'WHERE ' + where : ''}`, (err, row: { count: number }) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

security (opengrep-rules.javascript.express.security.injection.tainted-sql-string): 检测到用于手动构造 SQL 字符串的用户输入。这通常是不好的做法,因为手动构造可能会意外导致 SQL 注入。攻击者可以使用 SQL 注入来窃取或修改数据库的内容。请改用参数化查询,大多数数据库引擎默认提供该查询。或者,考虑使用对象关系映射器 (ORM),例如 Sequelize,它将保护您的查询。

Source: opengrep

Original comment in English

security (opengrep-rules.javascript.express.security.injection.tainted-sql-string): Detected user input used to manually construct a SQL string. This is usually bad practice because manual construction could accidentally result in a SQL injection. An attacker could use a SQL injection to steal or modify contents of the database. Instead, use a parameterized query which is available by default in most database engines. Alternatively, consider using an object-relational mapper (ORM) such as Sequelize which will protect your queries.

Source: opengrep

const getData = (offset: number): Promise<any[]> => {
return new Promise((resolve, reject) => {
db.all(
`SELECT * FROM ${tableName} ${where ? 'WHERE ' + where : ''} LIMIT ${pageSize} OFFSET ${offset}`,
Copy link
Contributor

Choose a reason for hiding this comment

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

security (opengrep-rules.javascript.express.security.injection.tainted-sql-string): 检测到用于手动构造 SQL 字符串的用户输入。这通常是不好的做法,因为手动构造可能会意外导致 SQL 注入。攻击者可以使用 SQL 注入来窃取或修改数据库的内容。请改用参数化查询,大多数数据库引擎默认提供该查询。或者,考虑使用对象关系映射器 (ORM),例如 Sequelize,它将保护您的查询。

Source: opengrep

Original comment in English

security (opengrep-rules.javascript.express.security.injection.tainted-sql-string): Detected user input used to manually construct a SQL string. This is usually bad practice because manual construction could accidentally result in a SQL injection. An attacker could use a SQL injection to steal or modify contents of the database. Instead, use a parameterized query which is available by default in most database engines. Alternatively, consider using an object-relational mapper (ORM) such as Sequelize which will protect your queries.

Source: opengrep

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.

1 participant
0