Skip to content

Commit c39f499

Browse files
committed
Refactor Capabilities structure and enforce read-only mode
- Removed write and DDL capability flags from Capabilities struct. - Updated methods to create capabilities to focus solely on read-only operations. - Adjusted tests and integration points to reflect the new capabilities structure. - Modified error messages to clarify that Plenum is strictly read-only. - Ensured all database engines reject write and DDL operations uniformly. - Cleaned up main.rs and MCP tool documentation to remove references to write/DDL capabilities.
1 parent 08d4bc3 commit c39f499

18 files changed

Lines changed: 437 additions & 730 deletions

File tree

.plenum/config.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
{
22
"connections": {
3-
"test-sqlite": {
3+
"default": {
44
"engine": "sqlite",
55
"file": "/tmp/test.db"
66
}
77
},
8-
"default": "test-sqlite"
8+
"default": "default"
99
}

CLAUDE.md

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -200,38 +200,43 @@ Stdout MUST NOT include logs or diagnostic text.
200200

201201
---
202202

203-
## Capability Model
203+
## Read-Only Mode
204204

205-
Read-only is the default mode (SELECT queries only).
205+
**Plenum is strictly read-only.** All write and DDL operations are prohibited.
206206

207-
Operations requiring higher privileges are gated by explicit capability flags:
208-
- allow_write (enables INSERT, UPDATE, DELETE)
209-
- allow_ddl (enables CREATE, DROP, ALTER, etc.)
210-
- max_rows (limits result set size)
211-
- timeout_ms (limits execution time)
207+
Permitted operations:
208+
- `SELECT` queries
209+
- `SHOW`, `DESCRIBE`, `PRAGMA` statements (database-specific introspection)
210+
- `EXPLAIN` and `EXPLAIN ANALYZE`
211+
- Transaction control statements (BEGIN, COMMIT, ROLLBACK, SAVEPOINT, RELEASE)
212212

213-
Violations MUST fail before query execution.
213+
Rejected operations:
214+
- `INSERT`, `UPDATE`, `DELETE` (write operations)
215+
- `CREATE`, `DROP`, `ALTER`, `TRUNCATE` (DDL operations)
216+
- Any other statement that modifies data or schema
214217

215-
Capabilities are NEVER inferred.
218+
### Safety Constraints
216219

217-
### Capability Hierarchy
220+
While Plenum does not allow write operations, it provides safety constraints for read operations:
221+
- `max_rows` (limits result set size to prevent overwhelming MCP token limits)
222+
- `timeout_ms` (limits execution time to prevent long-running queries)
218223

219-
Capabilities follow a strict hierarchy:
220-
- **Read-only** (default): No flags needed, SELECT queries only
221-
- **Write**: Requires `--allow-write` flag, enables INSERT, UPDATE, DELETE
222-
- **DDL**: Requires `--allow-ddl` flag, enables DDL operations AND write operations
224+
**Examples:**
225+
- `plenum query --sql "SELECT * FROM users" --max-rows 100` → allowed
226+
- `plenum query --sql "INSERT INTO users ..." ` → DENIED (Plenum is read-only)
227+
- `plenum query --sql "CREATE TABLE ..." ` → DENIED (Plenum is read-only)
228+
- `plenum query --sql "SHOW TABLES"` → allowed
229+
- `plenum query --sql "EXPLAIN SELECT * FROM users"` → allowed
223230

224-
**Important rules:**
225-
- `--allow-ddl` implicitly grants write permissions (DDL is a superset of write)
226-
- `--allow-write` does NOT enable DDL operations (DDL requires explicit flag)
227-
- Agents must explicitly request `--allow-ddl` even if write is already enabled
231+
### Agent Workflow for Write Operations
228232

229-
**Examples:**
230-
- `plenum query --sql "SELECT ..."` → allowed (read-only default)
231-
- `plenum query --sql "INSERT ..." --allow-write` → allowed
232-
- `plenum query --sql "CREATE TABLE ..." --allow-write` → DENIED (needs --allow-ddl)
233-
- `plenum query --sql "CREATE TABLE ..." --allow-ddl` → allowed (DDL implies write)
234-
- `plenum query --sql "INSERT ..." --allow-ddl` → allowed (DDL implies write)
233+
When an agent determines that a write or DDL operation is needed:
234+
1. Use `plenum introspect` to understand the schema
235+
2. Use `plenum query` to read current data if needed
236+
3. Construct the appropriate SQL statement
237+
4. **Present the SQL to the user** in the response for manual execution
238+
239+
Plenum will never execute write operations - this ensures all data modifications remain under human control.
235240

236241
---
237242

@@ -288,25 +293,27 @@ Engine quirks stay inside engine modules.
288293

289294
## Security Model
290295

291-
Plenum's security boundary is **capability enforcement**, not SQL validation.
296+
Plenum's security boundary is **read-only enforcement**, not SQL validation.
292297

293298
### Plenum Enforces:
294-
- Operation type restrictions (read-only, write, DDL)
295-
- Row limits and timeouts
299+
- Strict read-only operation (rejects all write/DDL operations)
300+
- Row limits and timeouts (for safety constraints)
296301
- Credential security (no logging/persistence in error messages or logs)
297302

298303
### Plenum Does NOT Enforce:
299304
- SQL injection prevention
300305
- Query semantic correctness
301306
- Business logic constraints
307+
- Row-level security or access control
302308

303309
### Agent Responsibility:
304310
The calling agent MUST:
305311
- Sanitize user inputs before constructing SQL
306312
- Validate queries for safety before passing to Plenum
307313
- Implement application-level security controls
314+
- Present write operations to users instead of attempting to execute them
308315

309-
**Plenum assumes SQL passed to it is safe.** It provides capability constraints, not query validation.
316+
**Plenum assumes SQL passed to it is safe for reading.** It provides read-only enforcement and safety constraints, not query validation.
310317

311318
---
312319

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "plenum"
3-
version = "1.0.2"
3+
version = "1.5.0"
44
edition = "2021"
55
authors = ["Plenum Contributors"]
66
license = "MIT OR Apache-2.0"

EXAMPLES.md

Lines changed: 42 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -321,98 +321,57 @@ plenum query \
321321

322322
Sets a 5-second timeout for query execution.
323323

324-
### Write Query (INSERT)
324+
### Attempted Write Query (REJECTED)
325+
326+
Plenum is strictly read-only. Write operations are rejected:
325327

326328
```bash
327329
plenum query \
328330
--name prod-db \
329-
--sql "INSERT INTO logs (level, message) VALUES ('INFO', 'Application started')" \
330-
--allow-write
331+
--sql "INSERT INTO logs (level, message) VALUES ('INFO', 'Application started')"
331332
```
332333

333-
**Success Output:**
334+
**Error Output:**
334335
```json
335336
{
336-
"ok": true,
337+
"ok": false,
337338
"engine": "postgres",
338339
"command": "query",
339-
"data": {
340-
"columns": [],
341-
"rows": [],
342-
"rows_affected": 1
343-
},
344-
"meta": {
345-
"execution_ms": 45,
346-
"rows_returned": 0
340+
"error": {
341+
"code": "CAPABILITY_VIOLATION",
342+
"message": "Plenum is read-only and cannot execute this query. Please run this query manually:\n\nINSERT INTO logs (level, message) VALUES ('INFO', 'Application started')"
347343
}
348344
}
349345
```
350346

351-
### Write Query (UPDATE)
347+
**Agent Workflow:** When a write operation is needed:
348+
1. Use Plenum to introspect schema and read current data
349+
2. Construct the SQL statement
350+
3. Present it to the user for manual execution
351+
352+
### Attempted DDL Query (REJECTED)
353+
354+
DDL operations are also rejected:
352355

353356
```bash
354357
plenum query \
355358
--name prod-db \
356-
--sql "UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE id = 42" \
357-
--allow-write
359+
--sql "CREATE TABLE temp_results (id SERIAL PRIMARY KEY, value TEXT)"
358360
```
359361

360-
**Success Output:**
362+
**Error Output:**
361363
```json
362364
{
363-
"ok": true,
365+
"ok": false,
364366
"engine": "postgres",
365367
"command": "query",
366-
"data": {
367-
"columns": [],
368-
"rows": [],
369-
"rows_affected": 1
370-
},
371-
"meta": {
372-
"execution_ms": 52,
373-
"rows_returned": 0
368+
"error": {
369+
"code": "CAPABILITY_VIOLATION",
370+
"message": "Plenum is read-only and cannot execute this query. Please run this query manually:\n\nCREATE TABLE temp_results (id SERIAL PRIMARY KEY, value TEXT)"
374371
}
375372
}
376373
```
377374

378-
### Write Query (DELETE)
379-
380-
```bash
381-
plenum query \
382-
--name prod-db \
383-
--sql "DELETE FROM temp_cache WHERE created_at < NOW() - INTERVAL '1 hour'" \
384-
--allow-write
385-
```
386-
387-
### DDL Query (CREATE TABLE)
388-
389-
```bash
390-
plenum query \
391-
--name prod-db \
392-
--sql "CREATE TABLE temp_results (id SERIAL PRIMARY KEY, value TEXT)" \
393-
--allow-ddl
394-
```
395-
396-
**Note:** `--allow-ddl` implicitly grants write permissions.
397-
398-
### DDL Query (CREATE INDEX)
399-
400-
```bash
401-
plenum query \
402-
--name prod-db \
403-
--sql "CREATE INDEX idx_users_created_at ON users(created_at)" \
404-
--allow-ddl
405-
```
406-
407-
### DDL Query (DROP TABLE)
408-
409-
```bash
410-
plenum query \
411-
--name prod-db \
412-
--sql "DROP TABLE IF EXISTS temp_results" \
413-
--allow-ddl
414-
```
415-
416375
### Query from File
417376

418377
```bash
@@ -493,24 +452,21 @@ plenum query --name dest-db \
493452
--sql "SELECT id, email FROM users WHERE id NOT IN (SELECT id FROM source_users)"
494453
```
495454

496-
### Workflow 4: Temporary Table Workflow
455+
### Workflow 4: Read-Only Analysis
497456

498457
```bash
499-
# Create temporary table
500-
plenum query --name work-db \
501-
--sql "CREATE TEMPORARY TABLE analysis_tmp (id INT, score REAL)" \
502-
--allow-ddl
458+
# Plenum is read-only - complex queries should be constructed and run manually
459+
# Use Plenum to understand the schema first
460+
plenum introspect --name work-db > schema.json
503461

504-
# Populate it
505-
plenum query --name work-db \
506-
--sql "INSERT INTO analysis_tmp SELECT user_id, AVG(rating) FROM reviews GROUP BY user_id" \
507-
--allow-write
462+
# Then construct your analysis query
463+
# Present this to the user for manual execution:
464+
# CREATE TEMPORARY TABLE analysis_tmp (id INT, score REAL);
465+
# INSERT INTO analysis_tmp SELECT user_id, AVG(rating) FROM reviews GROUP BY user_id;
508466

509-
# Query results
467+
# After the user runs it manually, use Plenum to query results
510468
plenum query --name work-db \
511469
--sql "SELECT * FROM analysis_tmp WHERE score > 4.5"
512-
513-
# No cleanup needed - temporary table drops when connection closes
514470
```
515471

516472
### Workflow 5: Database Discovery (Wildcard Mode)
@@ -682,7 +638,9 @@ plenum introspect --name mysql-discovery
682638

683639
## Error Handling
684640

685-
### Capability Violation (Missing --allow-write)
641+
### Read-Only Violation (Write Operation)
642+
643+
Plenum rejects all write operations:
686644

687645
```bash
688646
plenum query --name prod-db \
@@ -697,17 +655,18 @@ plenum query --name prod-db \
697655
"command": "query",
698656
"error": {
699657
"code": "CAPABILITY_VIOLATION",
700-
"message": "Write operations require --allow-write flag"
658+
"message": "Plenum is read-only and cannot execute this query. Please run this query manually:\n\nUPDATE users SET email = 'test@example.com' WHERE id = 1"
701659
}
702660
}
703661
```
704662

705-
### Capability Violation (Missing --allow-ddl)
663+
### Read-Only Violation (DDL Operation)
664+
665+
Plenum rejects all DDL operations:
706666

707667
```bash
708668
plenum query --name prod-db \
709-
--sql "DROP TABLE users" \
710-
--allow-write
669+
--sql "DROP TABLE users"
711670
```
712671

713672
**Error Output:**
@@ -718,7 +677,7 @@ plenum query --name prod-db \
718677
"command": "query",
719678
"error": {
720679
"code": "CAPABILITY_VIOLATION",
721-
"message": "DDL operations require --allow-ddl flag"
680+
"message": "Plenum is read-only and cannot execute this query. Please run this query manually:\n\nDROP TABLE users"
722681
}
723682
}
724683
```
@@ -880,7 +839,7 @@ diff source-schema.json dest-schema.json
880839
3. **Parse JSON output** - All Plenum output is valid JSON
881840
4. **Sanitize user inputs** - Plenum passes SQL verbatim to the database
882841
5. **Use named connections** - Avoid repeating connection parameters
883-
6. **Start read-only** - Only add `--allow-write` or `--allow-ddl` when necessary
842+
6. **Plenum is read-only** - Use it to introspect and query, then present write/DDL SQL to users for manual execution
884843
7. **Set max-rows for unknown queries** - Prevent accidentally fetching millions of rows
885844
8. **Use timeouts for expensive queries** - Protect against long-running operations
886845

0 commit comments

Comments
 (0)