security: document spidermonkey wasm unsafe invariants

ober

c617c8a412bb6769dd1c12048864c563d713e300

diff --git a/docs/ffi-audit.md b/docs/ffi-audit.md
index 293cca8..1d78a91 100644
--- a/docs/ffi-audit.md
+++ b/docs/ffi-audit.md
@@ -41,9 +41,9 @@ Latest summary from `tools/ffi-audit-report.ss`:
     (c-file-count 5)
     (no-mangle-exports 196)
     (unsafe-sites 369)
-    (unsafe-sites-with-nearby-safety-comment 303)
-    (unsafe-sites-without-nearby-safety-comment 66)
-    (unsafe-comment-review-sites 66)
+    (unsafe-sites-with-nearby-safety-comment 321)
+    (unsafe-sites-without-nearby-safety-comment 48)
+    (unsafe-comment-review-sites 48)
     (export-review-sites 196))
   (vendor
     (jsqlite (path "vendor/jsqlite") (status accepted-risk-cve-gated)))
@@ -138,7 +138,8 @@ documents atomic file-publication syscall invariants, and has regressions for
 null/nonzero input and output-slot failures. `wasm_sm.rs` now compiles under
 the crate-wide `unsafe_op_in_unsafe_fn` denial for the SpiderMonkey feature,
 checks host-memory offset arithmetic, bounds C ABI module/function/argument/log
-buffers, converts poisoned handle-store locks into normal FFI errors, and has
+buffers, converts poisoned handle-store locks into normal FFI errors, documents
+SpiderMonkey native callback, realm, and WASM memory-copy invariants, and has
 helper regressions for null/nonzero inputs and signed offset conversion.
 `sqlite.rs` now funnels path, SQL, text, and blob inputs through checked C ABI
 slice helpers, rejects oversized SQLite `int` lengths, centralizes
@@ -154,7 +155,7 @@ funnels hostname, path, PEM, pin, and read/write buffers through checked C ABI
 helpers, rejects null/nonzero and oversized buffers before slicing, rejects
 negative accepted fds before adopting them as `TcpStream`, and documents socket
 shutdown invariants used to break blocked TLS I/O. The generated inventory now
-reports 303 annotated native unsafe sites and 66 remaining unsafe review sites.
+reports 321 annotated native unsafe sites and 48 remaining unsafe review sites.
 
 Remaining work before closing K3-P1-01:
 
diff --git a/docs/kimi3-security-recommmendations.md b/docs/kimi3-security-recommmendations.md
index d068e22..53a2cb2 100644
--- a/docs/kimi3-security-recommmendations.md
+++ b/docs/kimi3-security-recommmendations.md
@@ -686,7 +686,8 @@ the current inventory and remediation trail now live in [ffi-audit.md](ffi-audit
   fingerprint output writes, and documents atomic file-publication syscalls.
   `wasm_sm.rs` now compiles under `unsafe_op_in_unsafe_fn` for the
   SpiderMonkey feature, bounds C ABI buffers, validates host-memory offsets,
-  and converts poisoned handle-store locks into normal FFI errors. The
+  converts poisoned handle-store locks into normal FFI errors, and documents
+  SpiderMonkey native callback, realm, and WASM memory-copy invariants. The
   `sqlite.rs` native wrapper now validates path, SQL, text, blob, result
   output, and error output buffers through shared helpers, rejects oversized
   SQLite lengths, converts poisoned handle-store locks into normal FFI errors,
@@ -696,8 +697,8 @@ the current inventory and remediation trail now live in [ffi-audit.md](ffi-audit
   publication, and test-owned fd conversion invariants. `tls.rs` now validates
   hostname, path, PEM, pin, and read/write buffers through shared helpers,
   rejects invalid accepted fds before ownership transfer, and documents socket
-  shutdown invariants for blocked TLS I/O. The generated report now shows 303
-  annotated native unsafe sites and 66 remaining unsafe review sites.
+  shutdown invariants for blocked TLS I/O. The generated report now shows 321
+  annotated native unsafe sites and 48 remaining unsafe review sites.
   Remaining work: continue unsafe invariant comments across the rest of
   `jerboa-native-rs`.
 
@@ -1187,7 +1188,7 @@ Track these in `docs/status.md` per release:
 | Confined worker exists | facade, audit lifecycle, output caps, deadline, process-group kill, memory rlimit pre-exec path, explicit sandbox-axis refusal, egress proxy env wiring, and Linux syscall/ptrace seccomp pre-exec setup landed; Landlock path/net and Seatbelt/Capsicum parity pending | yes, tested (P0-02) |
 | Unclassified raw `read`/FASL/`load` sites | scanner-driven manifest classification gate closed 2026-07-27; trusted build, compiler, REPL/dev, and test surfaces are centrally justified by exact source line | 0 |
 | FFI bindings audited | scanner inventory and provisional verdicts in [ffi-audit.md](ffi-audit.md); native exports gated and justified | 100% with final verdicts |
-| Un-annotated Rust `unsafe` blocks | 66 unsafe review sites without nearby `SAFETY:` comments in [ffi-audit.md](ffi-audit.md) | 0 |
+| Un-annotated Rust `unsafe` blocks | 48 unsafe review sites without nearby `SAFETY:` comments in [ffi-audit.md](ffi-audit.md) | 0 |
 | Fuzz corpora / crash regressions | 0 / 0 | per-parser corpora + every crash a test |
 | Exploit-shaped regression tests | ~0 | ≥ 1 per historical finding |
 | Sandboxed-by-default app entry | no | yes (P0-06) |
diff --git a/docs/reviews/2026-07-27-native-export-review.sexp b/docs/reviews/2026-07-27-native-export-review.sexp
index cca9e07..d1af475 100644
--- a/docs/reviews/2026-07-27-native-export-review.sexp
+++ b/docs/reviews/2026-07-27-native-export-review.sexp
@@ -132,14 +132,14 @@
     (export (symbol "jerboa_sha256") (file "jerboa-native-rs/src/crypto.rs") (line 87) (scheme-callers 1 (caller (file "lib/std/crypto/native-rust.ss") (line 69))))
     (export (symbol "jerboa_sha384") (file "jerboa-native-rs/src/crypto.rs") (line 184) (scheme-callers 1 (caller (file "lib/std/crypto/native-rust.ss") (line 78))))
     (export (symbol "jerboa_sha512") (file "jerboa-native-rs/src/crypto.rs") (line 194) (scheme-callers 1 (caller (file "lib/std/crypto/native-rust.ss") (line 87))))
-    (export (symbol "jerboa_sm_add_fuel") (file "jerboa-native-rs/src/wasm_sm.rs") (line 1149) (scheme-callers 1 (caller (file "lib/std/wasm/sandbox.ss") (line 186))))
-    (export (symbol "jerboa_sm_call") (file "jerboa-native-rs/src/wasm_sm.rs") (line 1005) (scheme-callers 1 (caller (file "lib/std/wasm/sandbox.ss") (line 168))))
-    (export (symbol "jerboa_sm_get_log") (file "jerboa-native-rs/src/wasm_sm.rs") (line 1162) (scheme-callers 1 (caller (file "lib/std/wasm/sandbox.ss") (line 180))))
-    (export (symbol "jerboa_sm_instance_free") (file "jerboa-native-rs/src/wasm_sm.rs") (line 994) (scheme-callers 1 (caller (file "lib/std/wasm/sandbox.ss") (line 162))))
-    (export (symbol "jerboa_sm_instance_new") (file "jerboa-native-rs/src/wasm_sm.rs") (line 984) (scheme-callers 2 (caller (file "lib/std/wasm/sandbox.ss") (line 156)) (caller (file "lib/std/wasm/sandbox.ss") (line 174))))
-    (export (symbol "jerboa_sm_instance_new_hosted") (file "jerboa-native-rs/src/wasm_sm.rs") (line 989) (scheme-callers 1 (caller (file "lib/std/wasm/sandbox.ss") (line 174))))
-    (export (symbol "jerboa_sm_module_free") (file "jerboa-native-rs/src/wasm_sm.rs") (line 934) (scheme-callers 1 (caller (file "lib/std/wasm/sandbox.ss") (line 150))))
-    (export (symbol "jerboa_sm_module_new") (file "jerboa-native-rs/src/wasm_sm.rs") (line 847) (scheme-callers 1 (caller (file "lib/std/wasm/sandbox.ss") (line 144))))
+    (export (symbol "jerboa_sm_add_fuel") (file "jerboa-native-rs/src/wasm_sm.rs") (line 1152) (scheme-callers 1 (caller (file "lib/std/wasm/sandbox.ss") (line 186))))
+    (export (symbol "jerboa_sm_call") (file "jerboa-native-rs/src/wasm_sm.rs") (line 1008) (scheme-callers 1 (caller (file "lib/std/wasm/sandbox.ss") (line 168))))
+    (export (symbol "jerboa_sm_get_log") (file "jerboa-native-rs/src/wasm_sm.rs") (line 1165) (scheme-callers 1 (caller (file "lib/std/wasm/sandbox.ss") (line 180))))
+    (export (symbol "jerboa_sm_instance_free") (file "jerboa-native-rs/src/wasm_sm.rs") (line 997) (scheme-callers 1 (caller (file "lib/std/wasm/sandbox.ss") (line 162))))
+    (export (symbol "jerboa_sm_instance_new") (file "jerboa-native-rs/src/wasm_sm.rs") (line 987) (scheme-callers 2 (caller (file "lib/std/wasm/sandbox.ss") (line 156)) (caller (file "lib/std/wasm/sandbox.ss") (line 174))))
+    (export (symbol "jerboa_sm_instance_new_hosted") (file "jerboa-native-rs/src/wasm_sm.rs") (line 992) (scheme-callers 1 (caller (file "lib/std/wasm/sandbox.ss") (line 174))))
+    (export (symbol "jerboa_sm_module_free") (file "jerboa-native-rs/src/wasm_sm.rs") (line 937) (scheme-callers 1 (caller (file "lib/std/wasm/sandbox.ss") (line 150))))
+    (export (symbol "jerboa_sm_module_new") (file "jerboa-native-rs/src/wasm_sm.rs") (line 849) (scheme-callers 1 (caller (file "lib/std/wasm/sandbox.ss") (line 144))))
     (export (symbol "jerboa_sqlite_bind_blob") (file "jerboa-native-rs/src/sqlite.rs") (line 442) (scheme-callers 1 (caller (file "lib/std/db/sqlite-native.ss") (line 63))))
     (export (symbol "jerboa_sqlite_bind_double") (file "jerboa-native-rs/src/sqlite.rs") (line 372) (scheme-callers 1 (caller (file "lib/std/db/sqlite-native.ss") (line 59))))
     (export (symbol "jerboa_sqlite_bind_int") (file "jerboa-native-rs/src/sqlite.rs") (line 348) (scheme-callers 1 (caller (file "lib/std/db/sqlite-native.ss") (line 57))))
diff --git a/docs/status.md b/docs/status.md
index 206517a..9aa2e4b 100644
--- a/docs/status.md
+++ b/docs/status.md
@@ -25,7 +25,7 @@ release artifacts are built as Jerboa multicall binaries with `jerboa`,
 | Area | Current state | Remaining work |
 |---|---|---|
 | Kimi security handoff | [kimi3-security-recommmendations.md](kimi3-security-recommmendations.md) is the backlog. Dated evidence and review manifests live under [reviews/](reviews/). | Keep new security evidence in dated review records and summarize the current release state here. |
-| FFI audit phase 5 | [ffi-audit.md](ffi-audit.md) records the scanner output, provisional Scheme binding verdicts, and native Rust export inventory. `make native-export-review-check` gates native export decisions. The worker-launch native path in `aproc.rs`/`seccomp.rs`, crypto FFI buffer path in `crypto.rs`, secure-memory region lifecycle in `secure_mem.rs`, secure filesystem capability boundary in `secure_fs.rs`, TLS native ABI in `tls.rs`, Ed25519/X25519 key-agreement buffers, compression buffers, HTTP parse/writev boundary, embed-crypto ABI, integrity ABI, regex-native ABI, process-control ABI, pcap ABI, inotify ABI, epoll/eventfd ABI, antidebug ABI, SOCKS5 server ABI, PostgreSQL native ABI, Landlock native ABI, wasmi sandbox ABI, SpiderMonkey WASM ABI, DuckDB native ABI, X.509 native ABI, and SQLite native ABI now have nearby `SAFETY:` comments or equivalent checked FFI invariants, with the generated unannotated unsafe-site count at 66. | Continue adding `SAFETY:` invariant comments near the remaining Rust unsafe sites. |
+| FFI audit phase 5 | [ffi-audit.md](ffi-audit.md) records the scanner output, provisional Scheme binding verdicts, and native Rust export inventory. `make native-export-review-check` gates native export decisions. The worker-launch native path in `aproc.rs`/`seccomp.rs`, crypto FFI buffer path in `crypto.rs`, secure-memory region lifecycle in `secure_mem.rs`, secure filesystem capability boundary in `secure_fs.rs`, TLS native ABI in `tls.rs`, Ed25519/X25519 key-agreement buffers, compression buffers, HTTP parse/writev boundary, embed-crypto ABI, integrity ABI, regex-native ABI, process-control ABI, pcap ABI, inotify ABI, epoll/eventfd ABI, antidebug ABI, SOCKS5 server ABI, PostgreSQL native ABI, Landlock native ABI, wasmi sandbox ABI, SpiderMonkey WASM ABI, DuckDB native ABI, X.509 native ABI, and SQLite native ABI now have nearby `SAFETY:` comments or equivalent checked FFI invariants, with the generated unannotated unsafe-site count at 48. | Continue adding `SAFETY:` invariant comments near the remaining Rust unsafe sites. |
 | Native Rust exports | The native export review now has 190 exported functions: 183 tracked Scheme references and 7 retained standalone C/binary helpers. The previous 35 no-Scheme-reference removal candidates no longer have C ABI export markers. | Re-run `make native-export-review-check` whenever adding or removing native exports. |
 | Confined worker | `(std security worker)` provides the facade, audit lifecycle, output caps, deadlines, process-group kill, memory rlimit pre-exec setup, Linux syscall/ptrace seccomp pre-exec setup for requested axes, explicit sandbox-axis refusal, and egress proxy env wiring. | Install native Landlock path/net rules in the worker pre-exec path and keep Linux/macOS/FreeBSD parity tests current. |
 | Safe surface | Direct scripts default to the safe prelude; raw access requires `--unsafe-prelude` or `(jerboa prelude unsafe)`. | Continue moving risky APIs behind explicit unsafe imports as new modules land. |
diff --git a/jerboa-native-rs/src/wasm_sm.rs b/jerboa-native-rs/src/wasm_sm.rs
index b474264..3145a42 100644
--- a/jerboa-native-rs/src/wasm_sm.rs
+++ b/jerboa-native-rs/src/wasm_sm.rs
@@ -108,9 +108,9 @@ fn ffi_bytes<'a>(ptr: *const u8, len: usize, label: &str) -> ::std::result::Resu
     if len == 0 {
         return Ok(&[]);
     }
-    // SAFETY: `len` is bounded to `isize::MAX`, and null pointers paired with
-    // nonzero length were rejected above. The returned slice is copied or
-    // decoded before control returns across the C ABI.
+    // `len` is bounded to `isize::MAX`, and null pointers paired with nonzero
+    // length were rejected above. The returned slice is copied or decoded.
+    // SAFETY: the caller-provided readable C ABI buffer is valid for this call.
     Ok(unsafe { ::std::slice::from_raw_parts(ptr, len) })
 }
 
@@ -231,9 +231,9 @@ fn read_wasm_memory(offset: usize, len: usize) -> Option<Vec<u8>> {
             return None;
         }
         let mut buf = vec![0u8; len];
-        // SAFETY: guest-memory bounds are checked with overflow-safe addition,
-        // the source pointer is non-null, and the destination Vec owns `len`
-        // initialized bytes.
+        // Guest-memory bounds are checked with overflow-safe addition, the
+        // source pointer is non-null, and the Vec owns initialized output.
+        // SAFETY: both source and destination cover exactly `len` bytes.
         unsafe {
             ::std::ptr::copy_nonoverlapping(call_ctx.memory_ptr.add(offset), buf.as_mut_ptr(), len);
         }
@@ -259,9 +259,9 @@ fn write_wasm_memory(offset: usize, data: &[u8]) -> bool {
         {
             return false;
         }
-        // SAFETY: guest-memory bounds are checked with overflow-safe addition,
-        // the destination pointer is non-null, and `data` provides at least
-        // `data.len()` initialized source bytes.
+        // Guest-memory bounds are checked with overflow-safe addition and the
+        // destination pointer is non-null.
+        // SAFETY: `data` provides at least `data.len()` initialized bytes.
         unsafe {
             ::std::ptr::copy_nonoverlapping(
                 data.as_ptr(),
@@ -274,9 +274,9 @@ fn write_wasm_memory(offset: usize, data: &[u8]) -> bool {
 }
 
 // ---- log_message(level, msg_ptr, msg_len) -> 0 ----
+// SAFETY: SpiderMonkey calls this only as a native callback with valid vp/argc.
 unsafe extern "C" fn host_log_message(_cx: *mut JSContext, argc: u32, vp: *mut Value) -> bool {
-    // SAFETY: SpiderMonkey invokes native callbacks with a valid `(argc, vp)`
-    // pair for the duration of the callback.
+    // SAFETY: `vp` points to a SpiderMonkey CallArgs frame for this callback.
     let args = unsafe { CallArgs::from_vp(vp, argc) };
     let level = if argc > 0 && args.get(0).get().is_int32() {
         args.get(0).get().to_int32()
@@ -330,9 +330,9 @@ unsafe extern "C" fn host_log_message(_cx: *mut JSContext, argc: u32, vp: *mut V
 }
 
 // ---- get_time_ms() -> i32 ----
+// SAFETY: SpiderMonkey calls this only as a native callback with valid vp/argc.
 unsafe extern "C" fn host_get_time_ms(_cx: *mut JSContext, argc: u32, vp: *mut Value) -> bool {
-    // SAFETY: SpiderMonkey invokes native callbacks with a valid `(argc, vp)`
-    // pair for the duration of the callback.
+    // SAFETY: `vp` points to a SpiderMonkey CallArgs frame for this callback.
     let args = unsafe { CallArgs::from_vp(vp, argc) };
     let ms = CALL_CTX.with(|ctx| {
         if let Some(ref call_ctx) = *ctx.borrow() {
@@ -349,9 +349,9 @@ unsafe extern "C" fn host_get_time_ms(_cx: *mut JSContext, argc: u32, vp: *mut V
 }
 
 // ---- random_get(buf_ptr, buf_len) -> errno ----
+// SAFETY: SpiderMonkey calls this only as a native callback with valid vp/argc.
 unsafe extern "C" fn host_random_get(_cx: *mut JSContext, argc: u32, vp: *mut Value) -> bool {
-    // SAFETY: SpiderMonkey invokes native callbacks with a valid `(argc, vp)`
-    // pair for the duration of the callback.
+    // SAFETY: `vp` points to a SpiderMonkey CallArgs frame for this callback.
     let args = unsafe { CallArgs::from_vp(vp, argc) };
     let buf_ptr = if argc > 0 && args.get(0).get().is_int32() {
         js_i32_arg_to_usize(args.get(0).get().to_int32()).unwrap_or(0)
@@ -390,9 +390,9 @@ unsafe extern "C" fn host_random_get(_cx: *mut JSContext, argc: u32, vp: *mut Va
 }
 
 // ---- fd_write(fd, iovs_ptr, iovs_len, nwritten_ptr) -> errno ----
+// SAFETY: SpiderMonkey calls this only as a native callback with valid vp/argc.
 unsafe extern "C" fn host_fd_write(_cx: *mut JSContext, argc: u32, vp: *mut Value) -> bool {
-    // SAFETY: SpiderMonkey invokes native callbacks with a valid `(argc, vp)`
-    // pair for the duration of the callback.
+    // SAFETY: `vp` points to a SpiderMonkey CallArgs frame for this callback.
     let args = unsafe { CallArgs::from_vp(vp, argc) };
     let _fd = if argc > 0 && args.get(0).get().is_int32() {
         args.get(0).get().to_int32()
@@ -450,18 +450,18 @@ unsafe extern "C" fn host_fd_write(_cx: *mut JSContext, argc: u32, vp: *mut Valu
 }
 
 // ---- fd_read(fd, iovs_ptr, iovs_len, nread_ptr) -> errno ----
+// SAFETY: SpiderMonkey calls this only as a native callback with valid vp/argc.
 unsafe extern "C" fn host_fd_read(_cx: *mut JSContext, argc: u32, vp: *mut Value) -> bool {
-    // SAFETY: SpiderMonkey invokes native callbacks with a valid `(argc, vp)`
-    // pair for the duration of the callback.
+    // SAFETY: `vp` points to a SpiderMonkey CallArgs frame for this callback.
     let args = unsafe { CallArgs::from_vp(vp, argc) };
     args.rval().set(Int32Value(8)); // EBADF — stdin blocked
     true
 }
 
 // ---- clock_time_get(clock_id, precision, time_ptr) -> errno ----
+// SAFETY: SpiderMonkey calls this only as a native callback with valid vp/argc.
 unsafe extern "C" fn host_clock_time_get(_cx: *mut JSContext, argc: u32, vp: *mut Value) -> bool {
-    // SAFETY: SpiderMonkey invokes native callbacks with a valid `(argc, vp)`
-    // pair for the duration of the callback.
+    // SAFETY: `vp` points to a SpiderMonkey CallArgs frame for this callback.
     let args = unsafe { CallArgs::from_vp(vp, argc) };
     let _clock_id = if argc > 0 && args.get(0).get().is_int32() {
         args.get(0).get().to_int32()
@@ -487,18 +487,18 @@ unsafe extern "C" fn host_clock_time_get(_cx: *mut JSContext, argc: u32, vp: *mu
 }
 
 // ---- proc_exit(code) ----
+// SAFETY: SpiderMonkey calls this only as a native callback with valid vp/argc.
 unsafe extern "C" fn host_proc_exit(_cx: *mut JSContext, argc: u32, vp: *mut Value) -> bool {
-    // SAFETY: SpiderMonkey invokes native callbacks with a valid `(argc, vp)`
-    // pair for the duration of the callback.
+    // SAFETY: `vp` points to a SpiderMonkey CallArgs frame for this callback.
     let args = unsafe { CallArgs::from_vp(vp, argc) };
     args.rval().set(UndefinedValue());
     true
 }
 
 // ---- recv_packet / send_packet / cdb_open / cdb_find / cdb_close stubs ----
+// SAFETY: SpiderMonkey calls this only as a native callback with valid vp/argc.
 unsafe extern "C" fn host_stub_i32(_cx: *mut JSContext, argc: u32, vp: *mut Value) -> bool {
-    // SAFETY: SpiderMonkey invokes native callbacks with a valid `(argc, vp)`
-    // pair for the duration of the callback.
+    // SAFETY: `vp` points to a SpiderMonkey CallArgs frame for this callback.
     let args = unsafe { CallArgs::from_vp(vp, argc) };
     args.rval().set(Int32Value(-1));
     true
@@ -509,10 +509,11 @@ unsafe extern "C" fn host_stub_i32(_cx: *mut JSContext, argc: u32, vp: *mut Valu
 // ============================================================
 
 /// Build the JS import object: { wasi_snapshot_preview1: {...}, dns: {...} }
+// SAFETY: caller must pass an active SpiderMonkey realm for all JSAPI calls.
 unsafe fn build_hosted_imports(cx: &mut AutoRealm) -> *mut JSObject {
-    // SAFETY: `cx` is an active SpiderMonkey realm supplied by the caller; all
-    // created objects and values are rooted for the duration of this helper,
+    // Created objects and values are rooted for the duration of this helper,
     // and native callback pointers have the ABI SpiderMonkey expects.
+    // SAFETY: all JSAPI calls below use the active realm provided by `cx`.
     unsafe {
         rooted!(&in(cx) let mut imports = JS_NewPlainObject(cx));
         if imports.is_null() {
@@ -650,6 +651,7 @@ unsafe fn build_hosted_imports(cx: &mut AutoRealm) -> *mut JSObject {
 
 /// Core function: compile WASM bytes, optionally attach host imports,
 /// call an exported function, return its i32 result.
+// SAFETY: caller must pass an active realm and a global from that realm.
 unsafe fn sm_compile_and_call(
     cx: &mut AutoRealm,
     global_ptr: *mut JSObject,
@@ -658,9 +660,9 @@ unsafe fn sm_compile_and_call(
     func_name: &str,
     args: &[i32],
 ) -> ::std::result::Result<i32, String> {
-    // SAFETY: `cx` is an active SpiderMonkey realm, `global_ptr` belongs to
-    // that realm, all JS values are rooted, and borrowed slices live through
-    // the synchronous SpiderMonkey compile/instantiate/call sequence.
+    // `cx` is an active realm, `global_ptr` belongs to it, all JS values are
+    // rooted, and borrowed slices live through this synchronous sequence.
+    // SAFETY: all JSAPI calls below occur inside that active SpiderMonkey realm.
     unsafe {
         // Re-root the global in this scope
         rooted!(&in(cx) let global = global_ptr);
@@ -860,9 +862,10 @@ pub extern "C" fn jerboa_sm_module_new(bytes: *const u8, bytes_len: usize) -> u6
         let options = RealmOptions::default();
         let cx = rt.cx();
 
-        // SAFETY: all SpiderMonkey values are rooted in the active realm, the
-        // module bytes were copied from a validated C ABI slice, and the
-        // temporary aligned buffer lives until `Construct1` returns.
+        // All SpiderMonkey values are rooted in the active realm, the module
+        // bytes were copied from a validated C ABI slice, and the temporary
+        // aligned buffer lives until `Construct1` returns.
+        // SAFETY: all JSAPI calls below occur inside the temporary realm.
         let valid = unsafe {
             rooted!(&in(cx) let global = JS_NewGlobalObject(
                 cx, &SIMPLE_GLOBAL_CLASS, ptr::null_mut(),
@@ -1071,9 +1074,9 @@ pub extern "C" fn jerboa_sm_call(
         let options = RealmOptions::default();
         let cx = rt.cx();
 
-        // SAFETY: SpiderMonkey calls are confined to the fresh runtime/realm
-        // for this invocation, all JS values are rooted, and the call context
-        // is cleared before returning to the C ABI.
+        // SpiderMonkey calls are confined to the fresh runtime/realm for this
+        // invocation, all JS values are rooted, and call context is cleared.
+        // SAFETY: all JSAPI calls below occur inside the fresh runtime/realm.
         let result = unsafe {
             rooted!(&in(cx) let global = JS_NewGlobalObject(
                 cx, &SIMPLE_GLOBAL_CLASS, ptr::null_mut(),
@@ -1093,9 +1096,9 @@ pub extern "C" fn jerboa_sm_call(
         match result {
             Ok(val) => {
                 if results_count > 0 && !results_ptr.is_null() {
-                    // SAFETY: non-null result output was checked above when
-                    // `results_count` is nonzero; only one i64 result is
-                    // written to the caller-owned slot.
+                    // Non-null result output was checked above when
+                    // `results_count` is nonzero.
+                    // SAFETY: only one i64 result is written to the caller slot.
                     unsafe {
                         *results_ptr = val as i64;
                     }