security: structure safe sqlite wrapper errors
ober
5d51c1e314af5c1a61b164f1c66989758eb393a3
--- a/docs/kimi3-security-recommmendations.md +++ b/docs/kimi3-security-recommmendations.md @@ -472,6 +472,11 @@ eval-shaped channel; authentication shrinks who can even reach the parser. Today "always use `(jerboa prelude safe)`" is advice in docs. Advice is what a 23-minute adversary counts on users skipping. +- **Status:** import-conflict cleanup landed 2026-07-27, and the safe SQLite + wrappers now validate SQL before handle work and map invalid handles/raw + backend failures to structured DB conditions. `tests/test-safe-prelude.ss` + passes 24/24. The default-prelude switch, explicit unsafe escape hatch, + application import-audit enforcement, and broader user docs remain open. - **Do:** (a) `jerboa run file.ss` and the script path default to resolving `(jerboa prelude)` as `(jerboa prelude safe)`; opting into the raw prelude requires an explicit `(jerboa prelude unsafe)` (rename with --- a/lib/std/safe.ss +++ b/lib/std/safe.ss @@ -213,15 +213,27 @@ (make-message-condition (format #f "~a: SQLite not available — vendored jsqlite could not be loaded" who)))))) + (def (raise-sqlite-contract-error who expected val) + (raise (condition + (make-db-error 'db 'sqlite) + (make-message-condition + (format #f "~a: expected sqlite ~a, got ~a" who expected val))))) + + (def (raise-sqlite-query-error who sql) + (raise (condition + (make-db-query-error 'db 'sqlite sql) + (make-message-condition + (format #f "~a: SQLite operation failed" who))))) + (def (check-sqlite-db! who val) (when-checking (unless (and raw-sqlite-db? (raw-sqlite-db? val)) - (error who "expected sqlite database, got ~a" val)))) + (raise-sqlite-contract-error who "database" val)))) (def (check-sqlite-stmt! who val) (when-checking (unless (and raw-sqlite-stmt? (raw-sqlite-stmt? val)) - (error who "expected sqlite statement, got ~a" val)))) + (raise-sqlite-contract-error who "statement" val)))) ;; ---- SQL injection heuristic detection ---- ;; Reject SQL strings that look like they were built by concatenation. @@ -305,12 +317,13 @@ (def (safe-sqlite-exec db sql) ;; Pre: db is sqlite database, sql is string, not tainted ;; Post: returns 0 on success + (check-string! 'safe-sqlite-exec sql) (check-untainted! sql 'sqlite-exec) + (check-sql-safety! 'safe-sqlite-exec sql) (ensure-sqlite! 'safe-sqlite-exec) (check-sqlite-db! 'safe-sqlite-exec db) - (check-string! 'safe-sqlite-exec sql) - (check-sql-safety! 'safe-sqlite-exec sql) - (let ([rc (raw-sqlite-exec db sql)]) + (let ([rc (try (raw-sqlite-exec db sql) + (catch (exn) (raise-sqlite-query-error 'safe-sqlite-exec sql)))]) (when-checking (unless (and (fixnum? rc) (fx= rc 0)) (raise (condition @@ -324,22 +337,24 @@ (def (safe-sqlite-execute db sql . params) ;; Pre: db is sqlite database, sql is string, params is list - (ensure-sqlite! 'safe-sqlite-execute) - (check-sqlite-db! 'safe-sqlite-execute db) (check-string! 'safe-sqlite-execute sql) - (check-untainted! 'safe-sqlite-execute sql) + (check-untainted! sql 'safe-sqlite-execute) (check-sql-safety! 'safe-sqlite-execute sql) - (apply raw-sqlite-execute db sql params)) + (ensure-sqlite! 'safe-sqlite-execute) + (check-sqlite-db! 'safe-sqlite-execute db) + (try (apply raw-sqlite-execute db sql params) + (catch (exn) (raise-sqlite-query-error 'safe-sqlite-execute sql)))) (def (safe-sqlite-query db sql . params) ;; Pre: db is sqlite database, sql is string ;; Post: returns a list of rows - (ensure-sqlite! 'safe-sqlite-query) - (check-sqlite-db! 'safe-sqlite-query db) (check-string! 'safe-sqlite-query sql) - (check-untainted! 'safe-sqlite-query sql) + (check-untainted! sql 'safe-sqlite-query) (check-sql-safety! 'safe-sqlite-query sql) - (let ([result (apply raw-sqlite-query db sql params)]) + (ensure-sqlite! 'safe-sqlite-query) + (check-sqlite-db! 'safe-sqlite-query db) + (let ([result (try (apply raw-sqlite-query db sql params) + (catch (exn) (raise-sqlite-query-error 'safe-sqlite-query sql)))]) (when-checking (unless (list? result) (raise (condition @@ -348,12 +363,13 @@ result)) (def (safe-sqlite-prepare db sql) - (ensure-sqlite! 'safe-sqlite-prepare) - (check-sqlite-db! 'safe-sqlite-prepare db) (check-string! 'safe-sqlite-prepare sql) - (check-untainted! 'safe-sqlite-prepare sql) + (check-untainted! sql 'safe-sqlite-prepare) (check-sql-safety! 'safe-sqlite-prepare sql) - (let ([stmt (raw-sqlite-prepare db sql)]) + (ensure-sqlite! 'safe-sqlite-prepare) + (check-sqlite-db! 'safe-sqlite-prepare db) + (let ([stmt (try (raw-sqlite-prepare db sql) + (catch (exn) (raise-sqlite-query-error 'safe-sqlite-prepare sql)))]) (when-checking (unless (and raw-sqlite-stmt? (raw-sqlite-stmt? stmt)) (raise (condition