@carllee1983/dbcli 9.1.0 → 9.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/CHANGELOG.md +44 -0
- package/dist/cli-runtime.mjs +14 -14
- package/dist/cli.mjs +1 -1
- package/dist/core.mjs +3 -3
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/plugins/dbcli-agent/.codex-plugin/plugin.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,50 @@ All notable changes to dbcli are documented here.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [9.1.1] - 2026-09-15
|
|
9
|
+
|
|
10
|
+
一個使用者回報的查詢缺陷(#197),一條會把人導向無效權限階的拒絕訊息,以及兩個
|
|
11
|
+
會對同一個 commit 給出兩種答案、或只會叫「東西變了」的測試門檻。
|
|
12
|
+
|
|
13
|
+
版本為 **9.1.1**。沒有新增指令、旗標或 engine;沒有任何一處拒絕先前被接受的輸入。
|
|
14
|
+
唯一改變輸出的是拒絕訊息裡的 `required:` 欄位與它的句子——那個值先前是錯的,照它
|
|
15
|
+
去要權限會被再拒一次,所以修正它不是相容性變更,是讓它開始為真。
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- **snippet 裡重複使用同一個具名參數,在 MySQL / MariaDB / SQLite 上不再炸。**
|
|
20
|
+
`rewriteToBind()` 對每一次 `:name` 出現輸出一個 placeholder,但 bind values 是由
|
|
21
|
+
去重後的名稱清單映射出來的。PostgreSQL 重用 `$1`,一個值對一個索引本來就對;`?`
|
|
22
|
+
沒有索引可以重用,所以 placeholder 數量跟著出現次數走、values 數量跟著相異名稱數
|
|
23
|
+
走,兩者在有重複參數時必然對不上,MariaDB 以 `ER_WRONG_ARGUMENTS` 拒絕整條
|
|
24
|
+
statement。
|
|
25
|
+
|
|
26
|
+
實務影響是「參數為 0 就查全部、否則查單一」這類條件在 snippet 裡寫不出來,只能
|
|
27
|
+
繞道 CTE 讓參數只出現一次。`--dry-run` 看得到 SQL 與 bind values 不匹配,但不會
|
|
28
|
+
報錯,所以問題要到真的執行才炸。非 PostgreSQL 引擎現在每一次出現送一個值,
|
|
29
|
+
PostgreSQL 的去重行為不變。(#197)
|
|
30
|
+
|
|
31
|
+
- **拒絕訊息指向的權限階,現在是真的會通過的那一階。** 無法分類的語句(`REPLACE
|
|
32
|
+
INTO`、`PRAGMA`、`VACUUM`、`REINDEX` 等)在 `query-only` 下被拒時宣稱需要
|
|
33
|
+
`read-write`,但沒有任何一階授予未知語句,取得 read-write 之後會再被拒一次。訊息
|
|
34
|
+
與 `required:` 欄位現在都說 `admin`,en 與 zh-TW 同步。判決完全沒變:本來拒絕的仍
|
|
35
|
+
然拒絕,本來允許的仍然允許。(DBCLI-037)
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
|
|
39
|
+
- **capability catalog 的輸出改由契約守住,不再由 sha256 守住。** 三份渲染
|
|
40
|
+
(JSON/text/Markdown)的凍結雜湊換成結構性斷言:文件在釘住的 schema 版本下
|
|
41
|
+
parse 得過、三份渲染列出同一組 capability、每個 capability 指的 command 是 CLI 真的
|
|
42
|
+
帶得動的。catalog 是從 engine 矩陣推導出來的(ADR-0022),所以每次矩陣變動都會
|
|
43
|
+
移動那三個雜湊,而修法永遠是把現在的值抄回去——連續三次之後,它報告的是 diff
|
|
44
|
+
本來就說了的事。決定與被否決的選項記在 ADR-0039。使用者可見的輸出沒有變。
|
|
45
|
+
(DBCLI-038)
|
|
46
|
+
|
|
47
|
+
- **驗證 artifact 的秘密洩漏檢查改成逐欄檢查。** 原本在整份文件裡搜尋連線 port 的
|
|
48
|
+
四位數字,而 artifact 帶一個隨機後綴的 id,後綴湊出那四位數字時測試會紅——什麼都
|
|
49
|
+
沒洩漏,重跑就過。改為解析後逐欄走訪並指名帶著 port 的欄位;判準仍是包含而非
|
|
50
|
+
相等,藏在連線字串裡的 port 照樣抓得到。(DBCLI-039)
|
|
51
|
+
|
|
8
52
|
## [9.1.0] - 2026-09-11
|
|
9
53
|
|
|
10
54
|
ForgeFlow Story DBCLI-014 到 DBCLI-036 在 `v9.0.0` 之後交付。其中只有最後三張
|
package/dist/cli-runtime.mjs
CHANGED
|
@@ -2087,7 +2087,7 @@ var package_default;
|
|
|
2087
2087
|
var init_package = __esm(() => {
|
|
2088
2088
|
package_default = {
|
|
2089
2089
|
name: "@carllee1983/dbcli",
|
|
2090
|
-
version: "9.1.
|
|
2090
|
+
version: "9.1.1",
|
|
2091
2091
|
description: "Database CLI for AI agents",
|
|
2092
2092
|
type: "module",
|
|
2093
2093
|
publishConfig: {
|
|
@@ -8997,7 +8997,7 @@ var init_messages = __esm(() => {
|
|
|
8997
8997
|
elasticsearch_requires_level: "Elasticsearch {type} operation requires {minimum} permission or higher (current level: {permission})",
|
|
8998
8998
|
escalated_write_requires_admin: "This statement opens as a read but contains an executable {keyword}. A write hidden inside a read statement requires admin permission (current level: {permission}).",
|
|
8999
8999
|
multiple_statements_refused: "SQL containing multiple statements is refused below admin permission, because only the first statement determines the permission check. Run each statement separately.",
|
|
9000
|
-
unknown_statement_query_only: "Unrecognised SQL statement (current level: query-only). Security policy requires
|
|
9000
|
+
unknown_statement_query_only: "Unrecognised SQL statement (current level: query-only). Security policy requires admin for unknown statements. If this is a legitimate read-only statement, please open an issue at https://github.com/CarlLee1983/dbcli/issues.",
|
|
9001
9001
|
permission_denied_reason: "Permission denied: {reason}",
|
|
9002
9002
|
table_not_found: "Table not found: {table}",
|
|
9003
9003
|
unsupported_lang: "Language '{lang}' not supported, using English",
|
|
@@ -9333,7 +9333,7 @@ var init_messages2 = __esm(() => {
|
|
|
9333
9333
|
elasticsearch_requires_level: "Elasticsearch {type} \u64CD\u4F5C\u9700\u8981 {minimum} \u4EE5\u4E0A\u7684\u6B0A\u9650\uFF08\u76EE\u524D\u5C64\u7D1A\uFF1A{permission}\uFF09",
|
|
9334
9334
|
escalated_write_requires_admin: "\u6B64\u8A9E\u53E5\u8868\u9762\u4E0A\u662F\u8B80\u53D6\uFF0C\u4F46\u5BE6\u969B\u5305\u542B\u53EF\u57F7\u884C\u7684 {keyword}\u3002\u8B80\u53D6\u8A9E\u53E5\u4E2D\u96B1\u85CF\u5BEB\u5165\u64CD\u4F5C\u9700\u8981 admin \u6B0A\u9650\uFF08\u76EE\u524D\u5C64\u7D1A\uFF1A{permission}\uFF09\u3002",
|
|
9335
9335
|
multiple_statements_refused: "\u4F4E\u65BC admin \u6B0A\u9650\u6642\u62D2\u7D55\u57F7\u884C\u5305\u542B\u591A\u500B\u8A9E\u53E5\u7684 SQL\uFF0C\u56E0\u70BA\u6B0A\u9650\u6AA2\u67E5\u53EA\u6703\u4F9D\u64DA\u7B2C\u4E00\u500B\u8A9E\u53E5\u5224\u65B7\u3002\u8ACB\u5C07\u5404\u8A9E\u53E5\u5206\u958B\u57F7\u884C\u3002",
|
|
9336
|
-
unknown_statement_query_only: "\u7121\u6CD5\u8FA8\u8B58\u7684 SQL \u8A9E\u53E5\uFF08\u76EE\u524D\u5C64\u7D1A\uFF1Aquery-only\uFF09\u3002\u5B89\u5168\u653F\u7B56\u8981\u6C42\u672A\u77E5\u8A9E\u53E5\
|
|
9336
|
+
unknown_statement_query_only: "\u7121\u6CD5\u8FA8\u8B58\u7684 SQL \u8A9E\u53E5\uFF08\u76EE\u524D\u5C64\u7D1A\uFF1Aquery-only\uFF09\u3002\u5B89\u5168\u653F\u7B56\u8981\u6C42\u672A\u77E5\u8A9E\u53E5\u9700\u8981 admin \u6B0A\u9650\u3002\u82E5\u9019\u5176\u5BE6\u662F\u5408\u6CD5\u7684\u552F\u8B80\u8A9E\u53E5\uFF0C\u8ACB\u81F3 https://github.com/CarlLee1983/dbcli/issues \u63D0\u51FA\u8B70\u984C\u3002",
|
|
9337
9337
|
permission_denied_reason: "\u6B0A\u9650\u88AB\u62D2\uFF1A{reason}",
|
|
9338
9338
|
table_not_found: "\u627E\u4E0D\u5230\u8868\u683C\uFF1A{table}",
|
|
9339
9339
|
unsupported_lang: "\u4E0D\u652F\u63F4\u7684\u8A9E\u8A00 '{lang}'\uFF0C\u4F7F\u7528\u82F1\u6587",
|
|
@@ -18719,6 +18719,7 @@ function paramErr(spec, raw, expected) {
|
|
|
18719
18719
|
function rewriteToBind(sqlBody, params, engine) {
|
|
18720
18720
|
const masked = stripCommentsAndStrings(sqlBody);
|
|
18721
18721
|
const order = [];
|
|
18722
|
+
const occurrences = [];
|
|
18722
18723
|
const indexByName = new Map;
|
|
18723
18724
|
let m;
|
|
18724
18725
|
NAME_RE.lastIndex = 0;
|
|
@@ -18737,19 +18738,18 @@ function rewriteToBind(sqlBody, params, engine) {
|
|
|
18737
18738
|
const start = m.index;
|
|
18738
18739
|
const end = start + m[0].length;
|
|
18739
18740
|
outSql += sqlBody.slice(cursor, start);
|
|
18740
|
-
|
|
18741
|
-
|
|
18741
|
+
if (engine === "postgres") {
|
|
18742
|
+
outSql += `$${indexByName.get(name)}`;
|
|
18743
|
+
} else {
|
|
18744
|
+
outSql += "?";
|
|
18745
|
+
occurrences.push(name);
|
|
18746
|
+
}
|
|
18742
18747
|
cursor = end;
|
|
18743
18748
|
}
|
|
18744
18749
|
outSql += sqlBody.slice(cursor);
|
|
18745
|
-
const undeclared =
|
|
18746
|
-
const
|
|
18747
|
-
|
|
18748
|
-
undeclared.push(n);
|
|
18749
|
-
return null;
|
|
18750
|
-
}
|
|
18751
|
-
return params[n];
|
|
18752
|
-
});
|
|
18750
|
+
const undeclared = order.filter((n) => !Object.prototype.hasOwnProperty.call(params, n));
|
|
18751
|
+
const bindOrder = engine === "postgres" ? order : occurrences;
|
|
18752
|
+
const values = bindOrder.map((n) => Object.prototype.hasOwnProperty.call(params, n) ? params[n] : null);
|
|
18753
18753
|
return { sql: outSql, values, undeclared };
|
|
18754
18754
|
}
|
|
18755
18755
|
var NAME_RE;
|
|
@@ -19548,7 +19548,7 @@ function checkPermissionForClassification(classification, permission) {
|
|
|
19548
19548
|
allowed: false,
|
|
19549
19549
|
reason: isUnknown ? t("errors.unknown_statement_query_only") : refusalReason(classification.type, permission),
|
|
19550
19550
|
classification,
|
|
19551
|
-
requiredPermission:
|
|
19551
|
+
requiredPermission: minimumPermissionFor(classification.type)
|
|
19552
19552
|
};
|
|
19553
19553
|
}
|
|
19554
19554
|
return {
|
package/dist/cli.mjs
CHANGED
package/dist/core.mjs
CHANGED
|
@@ -1800,7 +1800,7 @@ var messages_default = {
|
|
|
1800
1800
|
elasticsearch_requires_level: "Elasticsearch {type} operation requires {minimum} permission or higher (current level: {permission})",
|
|
1801
1801
|
escalated_write_requires_admin: "This statement opens as a read but contains an executable {keyword}. A write hidden inside a read statement requires admin permission (current level: {permission}).",
|
|
1802
1802
|
multiple_statements_refused: "SQL containing multiple statements is refused below admin permission, because only the first statement determines the permission check. Run each statement separately.",
|
|
1803
|
-
unknown_statement_query_only: "Unrecognised SQL statement (current level: query-only). Security policy requires
|
|
1803
|
+
unknown_statement_query_only: "Unrecognised SQL statement (current level: query-only). Security policy requires admin for unknown statements. If this is a legitimate read-only statement, please open an issue at https://github.com/CarlLee1983/dbcli/issues.",
|
|
1804
1804
|
permission_denied_reason: "Permission denied: {reason}",
|
|
1805
1805
|
table_not_found: "Table not found: {table}",
|
|
1806
1806
|
unsupported_lang: "Language '{lang}' not supported, using English",
|
|
@@ -2132,7 +2132,7 @@ var messages_default2 = {
|
|
|
2132
2132
|
elasticsearch_requires_level: "Elasticsearch {type} \u64CD\u4F5C\u9700\u8981 {minimum} \u4EE5\u4E0A\u7684\u6B0A\u9650\uFF08\u76EE\u524D\u5C64\u7D1A\uFF1A{permission}\uFF09",
|
|
2133
2133
|
escalated_write_requires_admin: "\u6B64\u8A9E\u53E5\u8868\u9762\u4E0A\u662F\u8B80\u53D6\uFF0C\u4F46\u5BE6\u969B\u5305\u542B\u53EF\u57F7\u884C\u7684 {keyword}\u3002\u8B80\u53D6\u8A9E\u53E5\u4E2D\u96B1\u85CF\u5BEB\u5165\u64CD\u4F5C\u9700\u8981 admin \u6B0A\u9650\uFF08\u76EE\u524D\u5C64\u7D1A\uFF1A{permission}\uFF09\u3002",
|
|
2134
2134
|
multiple_statements_refused: "\u4F4E\u65BC admin \u6B0A\u9650\u6642\u62D2\u7D55\u57F7\u884C\u5305\u542B\u591A\u500B\u8A9E\u53E5\u7684 SQL\uFF0C\u56E0\u70BA\u6B0A\u9650\u6AA2\u67E5\u53EA\u6703\u4F9D\u64DA\u7B2C\u4E00\u500B\u8A9E\u53E5\u5224\u65B7\u3002\u8ACB\u5C07\u5404\u8A9E\u53E5\u5206\u958B\u57F7\u884C\u3002",
|
|
2135
|
-
unknown_statement_query_only: "\u7121\u6CD5\u8FA8\u8B58\u7684 SQL \u8A9E\u53E5\uFF08\u76EE\u524D\u5C64\u7D1A\uFF1Aquery-only\uFF09\u3002\u5B89\u5168\u653F\u7B56\u8981\u6C42\u672A\u77E5\u8A9E\u53E5\
|
|
2135
|
+
unknown_statement_query_only: "\u7121\u6CD5\u8FA8\u8B58\u7684 SQL \u8A9E\u53E5\uFF08\u76EE\u524D\u5C64\u7D1A\uFF1Aquery-only\uFF09\u3002\u5B89\u5168\u653F\u7B56\u8981\u6C42\u672A\u77E5\u8A9E\u53E5\u9700\u8981 admin \u6B0A\u9650\u3002\u82E5\u9019\u5176\u5BE6\u662F\u5408\u6CD5\u7684\u552F\u8B80\u8A9E\u53E5\uFF0C\u8ACB\u81F3 https://github.com/CarlLee1983/dbcli/issues \u63D0\u51FA\u8B70\u984C\u3002",
|
|
2136
2136
|
permission_denied_reason: "\u6B0A\u9650\u88AB\u62D2\uFF1A{reason}",
|
|
2137
2137
|
table_not_found: "\u627E\u4E0D\u5230\u8868\u683C\uFF1A{table}",
|
|
2138
2138
|
unsupported_lang: "\u4E0D\u652F\u63F4\u7684\u8A9E\u8A00 '{lang}'\uFF0C\u4F7F\u7528\u82F1\u6587",
|
|
@@ -4359,7 +4359,7 @@ function checkPermissionForClassification(classification, permission) {
|
|
|
4359
4359
|
allowed: false,
|
|
4360
4360
|
reason: isUnknown ? t("errors.unknown_statement_query_only") : refusalReason(classification.type, permission),
|
|
4361
4361
|
classification,
|
|
4362
|
-
requiredPermission:
|
|
4362
|
+
requiredPermission: minimumPermissionFor(classification.type)
|
|
4363
4363
|
};
|
|
4364
4364
|
}
|
|
4365
4365
|
return {
|
package/gemini-extension.json
CHANGED
package/package.json
CHANGED