資料庫變更先經主管核准
查詢馬上執行;每項變更要等主管在手機上看過確切的 SQL 和預計影響筆數並核准後,才執行一次。資料庫始終留在你自己的伺服器上。
讓員工、腳本或助理直接查資料庫,能省下好幾個小時;讓他們不經過任何人就改資料,一句「清掉舊購物車」就可能刪掉上週的訂單。Hibira 在兩者之間放了一位主管:查詢馬上執行,每一項變更都要等主管在手機上看過確切的 SQL 和會影響幾筆資料、按下核准後才執行。你的資料庫和連線密碼始終留在自己的伺服器上。
使用者得到什麼
- 唯讀的問題馬上有答案,不用等任何人。
- 每項變更都有一個核准頁面,主管可以從推播通知或 QR code 打開:上面有 SQL、預計影響筆數、執行計畫和指紋,以及「核准」和「拒絕」。
- 主管一做決定,發起人的畫面馬上顯示結果,不用重新整理。被拒絕時會附上主管的理由。
- 保證執行的就是被核准的那一條 SQL,而且只執行一次。

用到的功能
- 審批:
POST /apps/APP_ID/approvals(或 MCP server 的request_approval)會建立一個 Hibira 代管的核准頁面,並在頻道上通知決定結果。 - 私有頻道:每個主控台或每次執行一個,例如
private-sqlgate-<random>,approval事件會送到這裡,不用輪詢。 - 推播通知(選用):把連結送到主管的手機。
- @hibira-io/approvals/sql:Node.js 的
classifySql()、sqlFingerprint()和assertApprovedSql(),Python 也有同樣的工具。
可以在 demos.hibira.io 試試:選一項變更,用手機掃 QR code,然後按核准。
步驟 1:查詢放行,其他一律攔下
classifySql() 只有在確定的時候才把一條 SQL 當成查詢:只有一條語句、開頭是 SELECT、WITH、EXPLAIN 或 SHOW,而且沒有任何會改資料的部分。多條語句、WITH ... DELETE、EXPLAIN ANALYZE、SELECT ... INTO、FOR UPDATE,以及任何無法確定解析的內容,都算變更。查詢還是要放在唯讀交易裡執行,這樣即使判斷錯了,也只會失敗,不會寫入:
import pg from 'pg';
import { classifySql } from '@hibira-io/approvals/sql';
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
export async function runSql(sql, requestedBy) {
if (classifySql(sql) === 'read') return { rows: await readOnly(sql) };
return { approval: await askSupervisor(sql, requestedBy) };
}
async function readOnly(sql) {
const db = await pool.connect();
try {
await db.query('BEGIN READ ONLY');
const { rows } = await db.query(sql);
await db.query('COMMIT');
return rows.slice(0, 200);
} catch (err) {
await db.query('ROLLBACK');
throw err;
} finally {
db.release();
}
}步驟 2:讓主管看到這項變更會做什麼
送出前先估算筆數。EXPLAIN 完全不會執行;在會回滾的交易裡試跑能拿到精確的筆數,但語句確實會跑一次,所以要設逾時。再對 SQL 原文算出指紋:這次核准只對這一條 SQL 有效。
import { sqlFingerprint } from '@hibira-io/approvals/sql';
async function preview(sql) {
const db = await pool.connect();
try {
await db.query('BEGIN');
await db.query("SET LOCAL statement_timeout = '5s'");
const { rowCount } = await db.query(sql);
return { rows: rowCount, fingerprint: await sqlFingerprint(sql) };
} finally {
await db.query('ROLLBACK');
db.release();
}
}步驟 3:送出核准
建立審批時,把 SQL 和預覽放在 details,並指定要通知結果的頻道。SQL 和指紋留在你這邊,用審批 ID 對應。送到 Hibira 的只有審批的文字:沒有連線密碼,也沒有任何資料列。
import Pusher from 'pusher';
const hibira = new Pusher({ appId: process.env.HIBIRA_APP_ID, key: process.env.HIBIRA_KEY, secret: process.env.HIBIRA_SECRET, host: 'ws.hibira.io', useTLS: true });
async function askSupervisor(sql, requestedBy) {
const { rows, fingerprint } = await preview(sql);
const res = await hibira.post({
path: '/approvals',
body: {
title: 'Run this database change?',
details: `${sql}\n\nEstimated rows: ${rows}\n\nSHA-256: ${fingerprint}`,
options: ['Approve', 'Reject'],
channel: `private-sqlgate-${requestedBy.consoleId}`,
push_users: ['supervisor-42'],
expires_in_minutes: 15,
agent: 'SQL console',
},
});
const approval = await res.json();
await pool.query('INSERT INTO pending_changes (approval_id, sql, fingerprint) VALUES ($1, $2, $3)', [approval.approval_id, sql, fingerprint]);
return { id: approval.approval_id, url: approval.url, rows };
}主管會收到帶連結的推播通知(或直接掃發起人畫面上的 QR code),在以自己語言顯示的頁面上回覆。

步驟 4:結果一到,只執行一次
決定會以 approval 事件送到頻道上。把它當成門鈴:先從 API 讀一次結果,確認存下來的 SQL 和指紋仍然相符,再執行。先「認領」這一列,就算兩個事件或兩個 worker 同時進來,也只會執行一次:
import { assertApprovedSql } from '@hibira-io/approvals/sql';
// In the requester's page (pusher-js), or a worker that keeps a connection open:
channel.bind('approval', ({ id, reason }) => {
if (reason === 'decided') fetch(`/api/changes/${id}/run`, { method: 'POST' });
});
// POST /api/changes/:id/run on your server:
export async function runApproved(id) {
const res = await hibira.get({ path: `/approvals/${id}` });
const answer = await res.json();
if (answer.status !== 'approved') return { status: answer.status, comment: answer.comment };
const { rows } = await pool.query('UPDATE pending_changes SET ran_at = now() WHERE approval_id = $1 AND ran_at IS NULL RETURNING sql, fingerprint', [id]);
if (!rows.length) return { status: 'already run' };
await assertApprovedSql(rows[0].sql, rows[0].fingerprint);
const result = await pool.query(rows[0].sql);
return { status: 'ran', rows: result.rowCount };
}
已經用 MCP server 連資料庫了?
如果團隊是在 Claude、Cursor、ChatGPT 或其他 MCP 用戶端裡,透過 DBHub、postgres-mcp、Supabase MCP 這類 server 操作資料庫,只要在前面加上 mcp-approval-gate,一行程式都不用改。查詢直接通過;每一筆新增、更新、刪除或結構變更,都會停在同一個核准頁,主管核准後才照原樣執行一次。
npx -y @hibira-io/mcp-approval-gate --label shop-production --push-interests db-approvals \
-- npx -y @bytebase/dbhub --transport stdio --dsn "$DATABASE_URL"在你用的工具裡做同樣的把關
- Vercel AI SDK、Mastra、LangGraph(JavaScript 和 Python):@hibira-io/approvals 每一種都有範例:把
needsApproval或requireApproval寫成依 SQL 判斷的函式,並在你自己的應用程式裡用審批收件匣顯示每個呼叫的預覽。 - Laravel:filament-approvals 直接在 Filament 後台裡審批,README 裡有給 Laravel AI SDK 用的
RunSql工具。 - n8n、Make、Activepieces 和 Dify:用程式碼步驟判斷 SQL 類型,Hibira 的審批步驟請主管決定並等待結果,資料庫步驟只接在「核准」的分支上。每個的 README 都有完整流程。
- Claude、Cursor 和其他 MCP 用戶端:MCP server 的
request_approval也能在對話中用同樣的方式送審。 - 任何資料庫 MCP server:mcp-approval-gate 不必改程式,就能包住 DBHub、postgres-mcp、Supabase MCP 等 server。