Skip to content

v2.0.1

Choose a tag to compare

@rzlco666 rzlco666 released this 05 Nov 17:45
805d18f

Catatan Rilis v2.0.1

Basis: main, merge commit 805d18f (PR #8 fix/server-json-description; PR #7 fix/mcp-v2-test-findings).
Tanggal rilis: 2026-11-06.

Ringkasan

v2.0.1 adalah patch rilis dari temuan uji V2. Tidak ada perubahan nama tool
atau bentuk parameter yang disengaja; yang berubah adalah penolakan argumen
tak dikenal, kejujuran metadata limit, pembatasan page_size, efisiensi
schema_context, kelonggaran drop_column, saran advisor, allowlist database
opt-in, dan ukuran payload tools/list.

Verifikasi yang mendasari draf

Gate Hasil
Conformance (scripts/conformance/runner.py) 54 check, 0 fail
stdio.tools.list.default_bytes 2643 B (cap 3072)
stdio.tools.list.full_bytes 10853 B (cap 12000)
http.tools.list.full_bytes 10853 B (cap 12000)
cargo clippy -D warnings (5 crate, all-targets) bersih
Lib tests guard 108, core 69, tools 92, server 8
Root tests http_integration 46 (5 ignored), param_strictness 7, sqlite_integration 12, table_policy 7, unit 14

Catatan: conformance dijalankan terhadap binary debug hasil source commit ini;
payload JSON-RPC tidak bergantung profil build. Release binary tetap dibangun
oleh CI.

V1 — Allowlist database opt-in

--allow-databases db1,db2 / STRUT_STACK_SQL_ALLOW_DATABASES menolak
statement yang qualifier tabelnya menunjuk database lain:

  • MySQL/ClickHouse: prefix database di luar daftar ditolak;
  • SQLite: database hasil ATTACH di luar daftar ditolak;
  • SQL Server: nama three-part di luar daftar ditolak;
  • SHOW ditolak selama allowlist aktif karena outputnya tidak bisa difilter;
  • list_schemas hanya menampilkan database yang diizinkan; tool katalog
    menolak schema: "*" dan nama schema yang tidak terdaftar;
  • allowlist bersifat process-wide; grant ber-scope tidak bisa memperlebarnya.

Bukti: guard lib 108 test (4 test allowlist baru), core lib 69,
table_policy 7 (1 test baru database_allowlist_filters_list_schemas),
tools lib 92.

Batas: nama tabel tanpa qualifier tetap resolve ke database koneksi dan
karena itu tetap terjangkau — isolasi keras harus datang dari kredensial
database ber-scope. Qualifier PostgreSQL/DuckDB adalah schema/catalog, bukan
database, dan tidak dibatasi flag ini.

V2 — Argumen tool tak dikenal ditolak

Semua struct parameter tool memakai serde(deny_unknown_fields), termasuk
opsi summary dan entri batch queries[]. Salah ketik seperti keywords,
page_size pada list_tables, atau focuss kini gagal dengan
invalid_params yang menyebut field tak dikenal dan daftar field valid;
sebelumnya argumen itu dibuang diam-diam. get_relationship_graph tetap
menerima focus sebagai alias table.

Bukti: param_strictness 7/7 lulus (7 test baru, termasuk alias
focus dan penolakan typo), tools lib 92, conformance memvalidasi schema 20
tool tanpa error.

V3 — Truncation dilaporkan jujur

Guard menambahkan LIMIT max_rows + 1; lapisan eksekusi membuang baris probe
dan menetapkan meta.truncated = true hanya bila baris itu ada. Tabel dengan
tepat max_rows baris kini melaporkan truncated: false, bukan hasil yang
tidak bisa diverifikasi. meta.limit_injected tidak berubah.

Bukti: 2 test baru di sqlite_integration
(injected_limit_marks_truncated_when_probe_row_exists,
injected_limit_does_not_mark_truncated_at_exactly_max_rows);
sqlite_integration 12/12, unit 14/14.

V4 — page_size dibatasi max_rows di semua engine

Sebelumnya hanya SQL Server yang meng-clamp page_size. page_size: 500
dengan max_rows: 100 pada tabel 142 baris mengembalikan seluruh baris dan
has_more: false. Kini guard meng-clamp pagination server dan lapisan eksekusi
meng-clamp page klien untuk semua engine: 100 baris, has_more: true.
Clamping LIMIT eksplisit (meta.limit_clamped) tidak berubah.

Bukti: 1 test baru
(oversized_page_size_is_capped_by_max_rows), sqlite_integration 12/12.

V5 — get_schema_context memakai cache schema

Packer tidak lagi menjalankan dua scan semua-tabel plus introspection index
langsung pada setiap panggilan. Ia membaca schema overview dan daftar index
yang sudah di-cache; panggilan warm tidak melakukan introspection driver.

Bukti (benchmark commit, SQLite in-memory 300 tabel × 8 kolom):

Jalur Waktu
Urutan lima panggilan lama 25.1 ms
Panggilan cold baru (4 cache miss) 28.4 ms
Panggilan warm baru (4 cache hit, 0 miss) 3.4 ms

Caveat: angka ini bukan pengukuran jaringan MySQL. Laporan 1.2 detik
sebelumnya berasal dari schema MySQL berjaringan, tetapi belum ada pengukuran
terkontrol MySQL-network untuk rilis ini. Jangan mengklaim percepatan jaringan
dari angka di atas.

V6 — drop_column cukup dengan nama kolom

column.type opsional dan diabaikan untuk drop_column; add_column dan
alter_column tetap mewajibkannya dan mengembalikan error yang bisa
ditindaklanjuti bila hilang. Validasi tipe kolom menerima dan membuang
qualifier NULL / NOT NULL di akhir (nullability punya field sendiri),
tetapi tetap menolak upaya injeksi. ColumnDef juga menolak field tak dikenal.

Bukti: 4 test unit baru di ddl.rs (tools lib 92/92) mencakup
pembuangan qualifier, penolakan injeksi, drop_column hanya nama, dan error
add_column tanpa tipe; param_strictness 7/7.

V7 — Saran advisor menyebut kolom sebenarnya

Saran full-scan menyebut kolom yang benar-benar dibandingkan plan dan
merekomendasikan index komposit untuk filter multi-kolom. Scanner filter
mem-tokenize identifier sehingga string literal, placeholder, dan nama tipe
cast tidak lagi dianggap kolom. Saran nested-loop besar menyebut equality join
key bila plan mengeksposnya. Lookup primary key SQLite mempertahankan plan node
dan tidak menghasilkan temuan.

Bukti: 5 test unit baru di explain.rs (tools lib 92/92) mencakup
filter satu kolom, index komposit, join key, lookup PK SQLite, dan pengabaian
literal/cast/placeholder.

Advisory konfigurasi P0 (belum diterapkan)

Perintah berikut adalah contoh untuk operator. Draf ini tidak menjalankan
perintah apa pun dan tidak mengubah database atau konfigurasi.

1. User database least-privilege (hanya SELECT)

PostgreSQL:

CREATE ROLE strut_ro LOGIN PASSWORD '<secret>';
GRANT CONNECT ON DATABASE app TO strut_ro;
GRANT USAGE ON SCHEMA public TO strut_ro;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO strut_ro;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO strut_ro;

MySQL/MariaDB:

CREATE USER 'strut_ro'@'%' IDENTIFIED BY '<secret>';
GRANT SELECT, SHOW VIEW ON app.* TO 'strut_ro'@'%';
-- jangan beri PROCESS, SHOW DATABASES, INSERT/UPDATE/DELETE, atau DDL

SQL Server:

CREATE LOGIN strut_ro WITH PASSWORD = '<secret>';
CREATE USER strut_ro FOR LOGIN strut_ro;
GRANT SELECT ON SCHEMA::dbo TO strut_ro;

Jalankan server dengan kredensial tersebut (default read-only; jangan tambah
--allow-writes/--allow-ddl untuk peran ini):

export DATABASE_URL='postgres://strut_ro:<secret>@db.internal:5432/app'
strut-stack-sql --url-env DATABASE_URL --deny-columns password,salt

2. Redaksi kolom sensitif

strut-stack-sql --url-env DATABASE_URL --deny-columns password,salt

--deny-columns adalah redaksi, bukan kontrol akses: kolom dengan nama
yang cocok persis (case-insensitive) diganti "[redacted]", nama kolom tetap
terlihat, dan alias (SELECT password AS p) melewati pencocokan. Isolasi
sebenarnya tetap berasal dari user database least-privilege dan view yang tidak
memilih kolom rahasia.

What's Changed

  • docs: README overhaul + one-command AI setup in install.sh by @rzlco666 in #6
  • fix: MCP v2.0.0 test findings — patch release 2.0.1 by @rzlco666 in #7
  • docs: list all six engines in server.json description by @rzlco666 in #8

Full Changelog: v2.0.0...v2.0.1