Security¶
This guide covers secure coding practices for Kaappi applications: input validation, SQL injection prevention, safe FFI, TLS, sandbox mode, and the overall security model.
SQL injection prevention¶
Always use parameterized queries. Never build SQL by concatenating user input:
;; WRONG — vulnerable to SQL injection
(pg-query conn
(string-append "SELECT * FROM users WHERE name = '" name "'"))
;; CORRECT — parameterized query
(pg-query conn "SELECT * FROM users WHERE name = $1" name)
PostgreSQL uses $1, $2 placeholders. SQLite uses ?:
Parameters are sent separately from the SQL text and are never interpreted as SQL. This prevents injection regardless of what the user provides.
Input validation¶
Validate user input at the boundary — before it reaches your application logic:
(define (validate-email s)
(and (string? s)
(> (string-length s) 0)
(string-contains s "@")))
(define (validate-positive-integer s)
(let ((n (string->number s)))
(and n (exact-integer? n) (positive? n))))
In a web handler:
(POST "/users"
(lambda (req params)
(let ((body (request-json req)))
(cond
((not (assoc "name" body))
(json-response '(("error" . "name is required")) 400))
((not (assoc "email" body))
(json-response '(("error" . "email is required")) 400))
((not (validate-email (cdr (assoc "email" body))))
(json-response '(("error" . "invalid email")) 400))
(else
(create-user db body)
(json-response '(("created" . #t)) 201))))))
XSS prevention¶
When serving HTML, use template-render-html (not template-render) to
auto-escape user data:
(import (kaappi template))
;; SAFE — auto-escapes <, >, &, ", '
(template-render-html "<p>{{.name}}</p>"
`(("name" . ,user-input)))
;; UNSAFE — user input rendered as raw HTML
(template-render "<p>{{.name}}</p>"
`(("name" . ,user-input)))
template-render-html escapes these characters:
| Character | Escape |
|---|---|
< |
< |
> |
> |
& |
& |
" |
" |
' |
' |
You can also use html-escape directly:
(html-escape "<script>alert('xss')</script>")
;=> "<script>alert('xss')</script>"
TLS and HTTPS¶
The HTTP client uses TLS with certificate verification by default for
https:// URLs:
TLS is provided by OpenSSL with SNI (Server Name Indication) and system certificate verification. There is no way to disable certificate verification — this is intentional.
For the low-level TLS API:
(import (kaappi net))
(define ssl (tls-connect "api.example.com" 443))
;; SNI and certificate verification are automatic
Safe FFI practices¶
FFI gives native code full access to the host process. Treat it as a trust boundary:
Only load trusted libraries¶
;; Load a system library — OK
(define libm (ffi-open "libm.dylib"))
;; Load a user-provided path — DANGEROUS
;; Never pass user input to ffi-open
(define lib (ffi-open user-provided-path)) ;; DON'T DO THIS
Type safety¶
Kaappi validates argument types before marshaling to C. Passing a string
where an int is expected raises a type error instead of silently passing
garbage to the C function:
(define c-abs (ffi-fn libm "abs" '(int) 'int))
(c-abs "hello") ;=> error: type error in 'abs'
(c-abs 42) ;=> 42
Fixnums passed to double parameters are automatically coerced. For
semantic validation (e.g., non-negative values), add your own checks:
(define c-sqrt (ffi-fn libm "sqrt" '(double) 'double))
(define (safe-sqrt x)
(unless (and (number? x) (>= x 0))
(error "sqrt: expected non-negative number" x))
(c-sqrt x))
Release resources¶
(define lib (ffi-open "libfoo.dylib"))
;; ... use lib ...
(ffi-close lib)
;; For callbacks:
(define cb (ffi-callback my-proc '(int) 'int))
;; ... pass cb to C ...
(ffi-callback-release cb)
Sandbox mode¶
The --sandbox flag runs code with restricted capabilities — designed for
untrusted input:
What sandbox blocks¶
| Category | Blocked procedures |
|---|---|
| File I/O | open-input-file, open-output-file, call-with-input-file, call-with-output-file, with-input-from-file, with-output-to-file, file-exists?, delete-file |
| FFI | ffi-open, ffi-fn, ffi-close, ffi-callback, ffi-bytevector-ptr |
| Code loading | eval, load |
| Process context | get-environment-variable, get-environment-variables, command-line, exit |
| Filesystem | All SRFI-170 operations |
| OS threads | SRFI-18 (thread-start!, mutexes, etc.) |
What sandbox allows¶
Arithmetic, strings, lists, vectors, hash tables, standard output
(display, write), green fibers, and all pure-computation libraries.
Resource limits¶
Combine sandbox with resource limits to prevent infinite loops and memory exhaustion:
| Flag | Description |
|---|---|
--timeout N |
Kill execution after N milliseconds |
--max-memory N |
Cap heap allocation at N bytes (GC runs first) |
Both flags work independently of --sandbox.
Sandbox escape testing¶
The sandbox boundary is verified by a test suite of 36 escape tests that confirm every gated capability is blocked. These tests run in CI on every commit.
Secrets management¶
Never hardcode secrets in source files:
;; WRONG
(define api-key "sk-live-abc123...")
;; CORRECT — read from environment
(define api-key (get-environment-variable "API_KEY"))
For database credentials:
;; Use environment variables
(define db
(pg-connect
(string-append "host=" (get-environment-variable "DB_HOST")
" dbname=" (get-environment-variable "DB_NAME")
" user=" (get-environment-variable "DB_USER")
" password=" (get-environment-variable "DB_PASS"))))
Security model summary¶
| Component | Trust level | Notes |
|---|---|---|
| R7RS Scheme code | Sandboxable | Use --sandbox for untrusted code |
| FFI (native code) | Full trust | Same privileges as host process |
.sbc bytecode |
Trusted input | Don't load from untrusted sources |
thottam packages |
Trusted | Native packages have full access |
| HTTPS | Certificate verified | SNI + system CA store |
| Parameterized queries | SQL-injection safe | Use $1 / ? parameters |
Reporting vulnerabilities¶
See SECURITY.md. Use GitHub's private security advisory feature — do not open a public issue.
Next: Deployment