mcli 20260901 发布
发布日期: 2026-09-01 · 版本: RELEASE.2026-09-01T00-00-00Z
距离 mcli 20260806 二十六天,本次发布汇总了客户端此后积累的全部变更。工具链升级到 Go 1.27.0,共享依赖栈与 SILO 服务端对齐,SILO 包以自有模块路径导入,go.mod 终于如实描述二进制的构建来源。fork 以来第一个真正的新命令 —— mcli checksum verify —— 到来了,它对端点实际返回的字节只读地核验存储的 S3 附加校验和。让 --json admin prometheus metrics 无法使用的 panic 已修复。而每一条可能打印凭据的路径 —— --debug trace、admin trace、错误消息、JSON 错误文档、checksum 报告 —— 都在三轮对抗性审查之后按 fail-closed 重写。发布流程本身的变化不亚于代码:产物只从 main 精确顶端、且该提交 CI 全绿的 GPG 签名标签构建,每种格式都带有 LICENSE、NOTICE 与生成的 CREDITS,携带 Sigstore 构建溯源证明,并落入不可变的 GitHub release。
本次发布构建自 ddac5d58,即 pgsty/mc#24 的合并提交:自 RELEASE.2026-08-06T00-00-00Z 以来 49 个提交、262 个文件、+12,487 / −6,089 行。
行为变更
- macOS 最低版本升至 13。 Go 1.27 链接 darwin 二进制时的最低系统版本为 13.0,darwin 归档包在 macOS 12 及更早版本上无法运行。
mcli --json admin prometheus metrics重新可用,并按名称确定顺序输出 metric family。纯文本 metrics 输出不变。pipe的标准输入为空时,现在执行普通的零字节 PUT,而非单分片的 multipart 上传。生成的 ETag 是d41d8cd98f00b204e9800998ecf8427e,而非<md5>-1形式。非空输入不受影响。- 策略写入在客户端严格校验 ——
admin policy create、admin user svcacct add/set、idp ldap accesskey create/edit。裸 ARN(如arn:aws:s3:::)被拒绝(表示该前缀下的全部资源请写arn:aws:s3:::*);命名策略缺少Version被拒绝;空策略被拒绝;同一 statement 内混用 S3 与 admin action 被拒绝。后三种情形服务端本来就会拒绝,因此只有裸 ARN 改变了"什么能被存入"。读取既有策略仍然宽松;服务账号的会话策略仍可省略Version,admin user svcacct set --policy也仍接受空文档 —— 这是清除内联策略、让账号回到继承策略的方式。 - 写在命令名之前的全局参数格式错误现在会报错。
mcli -H "bad" ls,以及在 app 层无法解析的--resolve、--limit-*取值,此前会被静默忽略、命令照常运行;现在与命令层形式一样以同样的消息失败,且只报告一次、输出到 stderr(--json下为 JSON 错误文档);此前 CLI 库会先把该文本回显到 stdout。 - 错误消息不再回显密钥。
alias set对错误的 secret key 只报告长度要求;MC_CONFIG_ENV_FILE的解析错误给出行号而不重复该行;SSE-C 密钥错误不再回显密钥;admin config set对携带密钥的key=value打码;远端复制目标打印时不含凭据;格式错误的--custom-header按位置报告,从不按值。 - 支持产物改为私有。
support diag、support profile、support perf、support inspect、admin cluster bucket export与admin cluster iam export写出的文件以0600创建,包括对上一个文件的轮转备份。此前是0644或0666。 --debug显示得更少。Authorization头只保留 scheme(AWS4-HMAC-SHA256 **REDACTED**);token、cookie、API key、SSE-C 密钥以及每一个--custom-header的值都被打码;2xx 响应的 body 不再 dump。admin trace的输出在默认与详细两种渲染下以同样方式脱敏:按名字打码的 header 值、查询串、body、错误文本与注记。- User-Agent 报告
minio-go/v7.3.0(SDK 自身的版本常量),而非minio-go/v7.0.98。
主要变更
- Go 1.27.0:模块、容器构建阶段、Linux/macOS/Windows CI、交叉编译、tidy 检查与漏洞分析全部使用 1.27.0。Go 官方维护的模块族同步更新:
x/net0.58.0、x/sys0.47.0、x/term0.45.0、x/text0.41.0、x/crypto0.55.0、x/mod0.40.0、x/sync0.22.0、x/tools0.49.0。golangci-lint移出产品模块图,按钉死的 v2.13.1 独立安装。 - 以自有模块路径导入 SILO 包。
go.mod现在直接 requiregithub.com/pgsty/silo-pkg/v3v3.13.0。客户端的策略校验依赖只有那里才有的辅助函数 ——policy.ParseConfigStrict与policy.Resource.IsBareARN—— 而对上游minio/pkg做replace不会被任何把本模块当作依赖引用的项目继承(pgsty/mc#12),因此改为如实声明依赖。上游minio/pkg仅作为colorjson的间接依赖存在,由依赖下限检查钉在 v3.6.1,gomodguard则阻止上游 import 路径回流。minio-go从 v7.0.99 升到silo-pkg自身构建所依赖的上游伪版本v7.3.1-0.20260828014306-0e78d3f18efe;过渡期的pgsty/silo-gofork 已退役,它唯一的功能性改动已合入上游。共享依赖栈的其余部分跟随服务端:etcd 客户端模块从 3.6.9 升到 3.7.1,包含 GO-2026-6107 / CVE-2026-73500 的修复;grpc-gateway2.29.0、zap1.28.0 与go.yaml.in/yaml/v33.0.5 跟随。go-systemd通过 SILO 共享的可移植性钉子有意停留在 22.6.0 —— 22.7.0 在 NetBSD 上无法编译。 - 运行时版本下限受到保护。 第一轮依赖调整曾因移除开发工具而悄然拉低了十余个运行时模块的选定版本 —— 其中包括
shoenig/go-m1cpu,其旧版本在启用 CGO 的新款 Apple Silicon 上会在初始化阶段崩溃 —— 该候选版本被否决。buildscripts/check-dependency-floors现在把go.mod中的每一条依赖与上一个RELEASE.*标签比较,出现回退即构建失败;唯一允许的例外是go-systemd钉子。 mcli checksum verify(设计文档)。将存储的 S3 附加校验和 —— CRC32、CRC32C、CRC64NVME、SHA1、SHA256,类型为FULL_OBJECT—— 与对象 API 返回的逻辑字节逐一核验,支持单个对象、--recursive前缀、--versions全部版本或精确的--version-id,以及 JSON Lines 候选清单。对未版本化对象以If-Match读取并在读后重新 stat,与核验并发的写入会被报告为UNKNOWN_OBJECT_CHANGED而非误判为不匹配。每个对象得到MATCH、MISMATCH、NO_CHECKSUM、WOULD_VERIFY(dry run)、十种UNKNOWN_*分类之一,或三种SKIPPED_*之一;汇总包含objects、verified计数(MATCH+MISMATCH,即真正重算过校验和的结果)、各状态计数与incomplete。--fail-on接受mismatch、unknown、no-checksum(任一对象缺少校验和、或根本没有核验任何对象时失败)、any(默认:不匹配、未知,以及被--max-size跳过的对象)或none。--report把同样的记录写入新建的0600JSON Lines 文件。无论 stdout 是否为终端,结果都写到 stdout;--json在非终端下输出紧凑的 JSON Lines —— pgsty/mc#5 报告的非 TTY 静默问题已修复。支持 dry run、--max-workers、--max-size、时间过滤,以及通过--enc-c的 SSE-C。该命令在 CI 中针对真实 SILO 服务器验证。- 凭据脱敏,fail-closed。 Codex 的三轮对抗性审查发现,最初的脱敏实现 —— 以及它的前两次重写 —— 只识别预期中的形态、其余原样放行:含斜杠的 access key、首值为空以掩盖第二个值的
Authorization、非十六进制签名、藏进 credential scope 或SignedHeaders的密钥、Proxy-Authorization、cookie、X-Api-Key、响应 trailer、带 userinfo 的跳转Location、把请求头反射进错误 body 的服务端、--json错误输出中被 JSON 转义的密钥、重复出现两次的短 token。最终设计只有三条规则、没有例外:Authorization类的值只保留 scheme;名字看起来像密钥、或由调用者通过--custom-header提供的头一律打码、不问内容;客户端自己没有生成的文本 —— 响应 body、服务端错误消息、trace 注记 —— 先按凭据形态扫描,再按进程学到的每一个密钥字面值擦除。最后这一层由进程级的密钥登记表支撑:别名 secret key 与会话令牌、MC_HOST_*取值、各种编码形态的 SSE-C 密钥、tier 与 service principal 密钥、GCS 凭据文件、LDAP 密码及其返回的 STS 凭据、SUBNET 令牌、--secret-key/--account-key/--api-key参数,以及携带密钥的key=value参数,都在读入时、校验或任何网络调用之前登记。出现位置先收集为区间并合并再替换;JSON 错误文档在解码后的值树上只清洗字符串叶子,因此 key 与非终端下的紧凑格式都不受影响。四十余条对抗用例、一个让真实客户端对着"反射所有收到内容"的端点运行的黑盒测试,以及对 SILO 服务器的实时抓包,共同钉住了结果。 admin trace脱敏。 服务端为其他客户端请求提供的 header map 走的是 S3 tracer 从未触及的路径。人类可读与 JSON 两种渲染、默认与详细两种形式,现在都基于脱敏后的副本 —— 事件对象在两者间共享、永不被修改。查询串(预签名 URL 的签名、位于第一个参数的会话令牌)、body、错误文本与注记都会按事件自身的 header 值擦除,因此其他客户端发送的 token 不可能进入特权 trace 输出。header 按名字打码:其他客户端以不像密钥的名字发送的--custom-header,会按服务端看到的样子显示。
缺陷修复
- Metrics JSON 不再 panic。 20260806 客户端的
mcli --json admin prometheus metrics在 v2 与 v3 两种 metrics API 下都会以panic: Invalid name validation scheme requested: unset失败。madmin-go构造了 Prometheus 文本解析器的零值,而prometheus/common0.66 起拒绝这种用法。客户端现在以 UTF-8 校验模式构造解析器,遇到问题返回普通解析错误而非 panic,并按名称顺序输出 family。 pipe空输入改为普通 PUT;此前对零字节发起的单分片 multipart 上传既无必要,又会生成 multipart 形式的 ETag。- RPM 载荷校验与签名脚本对齐:内容与发布元数据不符的 RPM 会在签名之前被拒绝。
sql打印完结果行后不再 panic。 SDK 的 select 读取器会在每个终止事件上自行关闭 HTTP 响应,而客户端又关闭了一次;在 zstd 压缩的响应上两次排空相互竞争,对 SILO 服务器的查询约五分之二会在结果行已经写出之后以panic: invalid memory address、退出码2收场(pgsty/mc#23)。客户端现在改为取消请求并排空结果管道,而不是再次关闭响应;在真实服务器上连续 820 次查询零 panic,而旧客户端 50 次中失败 21 次。
安全加固
- 运维人员产出的文件不再对全局可读。
support diag一边警告归档可能包含敏感环境信息,一边以0666写出;共用的moveFile帮助函数在把每个0600临时文件搬到最终名字的途中放宽为0644,而即便保留源文件权限,早期客户端留下的0644仍会被带进轮转备份。现在每个支持产物都以0600创建,并在写入第一个字节之前 chmod。 Test_FullSuite遇 panic 即失败。 功能套件此前会从 panic 中 recover 以执行清理、然后报告成功;套件中任何位置的 panic(包括二进制构建)在 CI 里都不可见。- 新命令帮助中残留的
play/别名 —— 仓库里唯一仍在宣传 MinIO 运营演示别名的文件 —— 已替换,品牌门禁现在除主机名外也拒绝命令示例中的裸别名。 - 补齐 AGPL 声明:六个 fork 自有文件此前只带许可证文本的前半段。
工程与交付
- 每种产物都带第三方声明。
CREDITS由buildscripts/gen-credits.sh从链接进六个 GoReleaser 目标的运行时模块并集生成,并在 CI 中比对,依赖变更不可能让它悄然过期。LICENSE、NOTICE与CREDITS随 GoReleaser 归档包、DEB/RPM/APK 软件包以及每一个容器镜像(/licenses/目录)一同交付。 - CI 在
main上运行。Go与Crosscompile此前只在 pull request 上运行,merge commit —— 真正被打标签的提交 —— 从未被测试;现在每次推送到main都会运行。它们的并发组以github.head_ref为键,而它在 pull request 之外为空,于是每次手动触发都会取消其他所有运行;现在改以 pull request 编号为键、ref 作为回退。VulnCheck也改为每周运行,因为归档上游的 fork 可能数周没有提交。CI 中的功能套件现在针对 SILORELEASE.2026-08-06T00-00-00Z运行。 - 只从签名标签发布。 产物工作流没有手动触发;产物来自推送的
RELEASE.*标签,事件 SHA、标签指向的提交与检出三者必须一致。verify-release-tag.sh要求 annotated 标签带有来自预期密钥的有效 OpenPGP 签名 —— 指纹与公钥位置来自仓库变量,而非标签指向的树本身,且导入的密钥必须与预期指纹一致。check-release-commit.sh要求被打标签的提交是origin/main的精确顶端,并且四个工作流对该 SHA 都有成功的 push-to-main 运行、按工作流文件路径匹配;pull request 与手动触发的运行不算证据。check-release-state.sh(pgsty/mc#6)按标签串行运行、从头替换恰好一个既有 Draft、拒绝已发布的 release,并在上传软件包前再次确认只有一个 Draft。每一个判定都住在带 fixture 覆盖的 shell 脚本里;测试中的假gh既钉住判决,也逐字核对传入参数。 - 溯源与不可变。 归档包、checksums、DEB 与 APK 软件包携带 Sigstore 构建溯源证明(
gh attestation verify <file> --repo pgsty/mc)。RPM 有意排除:sign-release-rpms.sh会在 release 仍为 Draft 时用密钥9592A7BC7A682E7333376E09E7935D8DB9BD8B20的分离式 GPG 签名改写其字节,早先做的证明会指向没有任何已发布 RPM 拥有的摘要。GitHub release 一经发布即不可变;修复资产意味着新标签。pgsty/mc容器镜像只在gh attestation verify把每个归档绑定到release.yml@refs/tags/<tag>、发布提交摘要、GitHub 托管 runner、其自身文件名与 SHA-256 之后才从经证明的归档构建;解包出的二进制的 ELF 机器类型必须与归档架构一致;每个架构的镜像先拉取、运行并检查标签,然后才合并 manifest,latest只从双架构 manifest 写入。UBI 基础镜像按摘要钉住,由 Dependabot 保持更新。 - 仓库治理。
main要求 pull request 与全部八项精确检查(DCO、Linux/macOS/Windows 的 Go、vetchecks、Crosscompile、VulnCheck、Test Release —— 后者现在不带路径过滤地在每个 pull request 与推送上运行);RELEASE.*标签只有管理员能创建,且永远不能更新或删除;secret scanning、push protection、Dependabot 告警与安全更新已开启。本项目由单人维护,可强制执行的政策是"通过必需检查门禁的 pull request"而非独立人工评审;README 已如实说明。 - 打标签前的验证。 tidy/verify、依赖下限、
go vet、golangci-lint、actionlint、品牌政策、govulncheck零可达漏洞;Apple Silicon 原生 CGO 执行与完整 race 套件;交叉编译矩阵外加 FreeBSD、OpenBSD、NetBSD 与 Windows ARM64;以及针对已发布 SILORELEASE.2026-08-06T00-00-00Z的 HTTP 与 HTTPS 完整功能套件,涵盖 SSE-C、分片上传、watch、mirror、预签名请求、用户与策略。在此之上是 Codex(GPT-5.6)三轮审查与 Claude Code 的独立验证,以及打标签前由六位独立审查者对 20260806 以来全部差异的最后一轮检查 —— 凭据脱敏、checksum verify、行为变更、发布链、模块路径迁移,以及候选二进制与 20260806 二进制在真实服务器上的黑盒对比。49 个提交中有 34 个直接回应这些审查发现的具体问题,每一条在修复前先固化为回归测试。除凭据工作之外,审查还发现并由本次发布修复了:即便没有核验任何对象也可能 exit0的--fail-on no-checksum;会接受"从未真正构建过发布提交"的运行的发布门禁;本会让第一次真实容器发布失败的证明 subject 检查;latest曾短暂指向仅 amd64 manifest 的时刻;默认admin trace渲染不脱敏地打印查询串;把错误消息中的普通文字改写掉的凭据形态;无法再清除策略的svcacct set;以及由来已久的sqlpanic。
兼容性
本版本的完整 --help 树与 20260806 二进制做了 diff:唯一差异是新增的 checksum 命令。在同一服务器上并排对比,head、cat、get、cp、mirror、find、share、sql、stat 与 ls 的输出与 JSON 字段集完全一致;二十份真实形态的策略除裸 ARN 外接受/拒绝结果一致;admin policy info --json 逐字节相同。mc 命令名与 mcli 软件包名;~/.mc / ~/.mcli 配置目录;github.com/minio/mc 模块路径及全部 import 路径;MC_* 环境变量;协议头(x-minio-*)与 minio-go User-Agent 前缀;minio tier 类型;.part.minio 传输后缀;minio-job 抓取任务名;以及软件包格式、产物命名与 YYYYMMDDHHMMSS.0.0 版本方案均未改变。客户端继续兼容 MinIO 服务器及其他 S3 兼容端点。把本模块当作 Go 依赖引用的项目现在直接解析到 github.com/pgsty/silo-pkg/v3,不再需要自己为它写 replace。
已知问题与边界
- 当服务端自身的完整性校验拒绝读取时,
checksum verify报告的是UNKNOWN_READ_ERROR而非MISMATCH—— SILO 的 bitrot 保护会在对象 API 返回之前拒绝损坏的分片。MISMATCH意味着存储的校验和描述的字节与对象 API 返回的不同,这正是该命令要找的历史写入路径缺陷;UNKNOWN永远不会被报告为MATCH。 - Access key ID 只要出现在凭据结构内就会被打码,但把裸 access key 反射进错误消息的服务端仍会让它被打印:它在合法消息中同时充当用户标识。短于三个字符的密钥不登记。当 secret key 与 access key 相同时 —— 默认的
minioadmin:minioadmin—— 错误文本中的这个标识也会被打码,因为这个字符串就是密钥。 --json错误文档中cause.error的 key 按字母序而非结构体顺序排列;key 与值本身不变。mcli sql在 stderr 报告查询失败后仍以0退出(pgsty/mc#25):逐对象的错误只报告而不中止运行,以便递归查询继续。与上游一致。govulncheck追踪到间接依赖x/crypto/openpgp中的 GO-2026-5932;不存在修复版本,客户端也不调用它。- minio/mc#5139 继续适用:
mirror --remove --watch在源端删除对象的 非当前版本 时,可能误删目标端的现存对象。 - 不阻断发布的质量债 —— 归档可复现性、测试工具链可移植性、KMS / SSE-S3 / 多站点复制的延后覆盖 —— 记录在 pgsty/mc#21。
关联提交
- 6686b64: chore(deps): align the SILO Go dependency stack
- 423490d: fix: stop JSON metrics from panicking
- 14b05e6: build: generate and package third-party notices
- e938cea: fix: align RPM payload signing validation
- 8158605: release: adopt Silo Go SDK v7.3.1
- d6ba236: ci: allow manual release workflow runs
- b6b2655: ci: release an existing tag on dispatch
- 70a2950: fix: preserve embeddable pkg compatibility
- aa07b1c: feat: add read-only object checksum verification
- 1b39423: fix: preserve checksum output in pipelines
- a31a42b: docs: record checksum verify output contract
- 2121081: fix: validate policy writes strictly
- 712bf3a: fix: use regular PUT for empty pipe input
- 9a3805d: ci: make release retries tag-idempotent
- 7bc9843: test: make report permissions portable
- 2d5abc2: chore: complete the AGPL notice on fork-authored files
- f5c5dc9: build: import the SILO package under its own module path
- a8e7eb4: ci: test main on push and stop dispatch runs cancelling each other
- b75d28b: fix: stop checksum verify help from advertising the play alias
- c05da7b: chore: drop configuration for bots that are not installed
- a29b455: fix: stop –debug from printing credentials
- 08244df: fix: keep secrets out of error messages and traces
- 30a7ea0: fix: stop writing support artifacts world-readable
- 14bd724: test: fail the full suite when it panics
- 4abbb3a: feat: expose how much checksum verify actually verified
- f484d0d: ci: make a release prove its commit, and pin what it builds from
- 2c33a4e: chore: ignore the local consumer-module review scratch directory
- bdc742a: fix: close the remaining –debug credential leaks
- 3998392: fix: withhold SSE-C keys and credential URLs the parser rejects
- c1358c4: fix: force 0600 on rotated support artifacts too
- 45e5144: fix: fail –fail-on no-checksum when nothing was verified
- 3674933: ci: make the release gate provable and its claims true
- 8b7b2e9: test: exercise checksum verify against a real server
- 4b36b0a: fix: make credential redaction fail-closed and scrub final output
- a6c14da: ci: verify build provenance before repackaging archives into images
- 915cfd9: fix: reject a malformed app-level global flag instead of ignoring it
- 81e2767: fix: rebuild SigV4 traces from known fields and widen secret scrubbing
- 625713c: fix: keep only the scheme of a signed header and scrub JSON structurally
- ec12b64: fix: redact admin trace output and register every credential ingress
- 1445e88: ci: release only from a signed tag at the tip of main, verify exact provenance
- f188939: fix: close final credential and release verification gaps
- f5b88dd: fix: redact the default admin trace and stop the shape sweep mangling prose
- 59989f8: fix: let svcacct set clear a policy again, report bad global flags once
- d38412a: fix: classify an SSE-C refusal with a key as a read error, keep policy and size messages clear
- b291b53: fix: register a URL password as written, keep listing parameters visible in traces
- 9dfca8f: test: drive the SSE-C refusal classification through the verify path
- 76b2e89: fix: keep an unquoted DSN password whole and match auth schemes case-insensitively
- 328efee: fix: register the password before the first separator of an unquoted DSN payload too
- 7057705: fix: avoid double-closing S3 Select responses