# wasmforge Host API Contract This document is the source of truth for `env.*` imports exposed to WASM. Adding a new export requires adding a row here with rationale, classified under one of categories A/B/C below. Category D ("Unjustified") is a CI failure. The contract is CI-enforced by `internal/hostmod/contract_test.go`. ## Categories - **A. Foundation primitive** — generic dispatch / memory / handle operations that nothing higher up can replicate. Examples: `mod_invoke`, `mem_alloc`, `mod_hread`. WASM cannot do these. - **B. OS proxy** — direct syscall passthrough where the OS API has no equivalent representation inside the WASM sandbox. Examples: `sock_open`, `fd_open`, `os_hostname`, `darwin_call`. WASM cannot reach the kernel on its own. - **C. Atomic composite (host-thread-affinity)** — a multi-step Win32 dance that legitimately needs host thread/process state (SYSTEM impersonation, COM STA-thread affinity, or both). Examples: `lsa_kerbop`. Cannot be replicated by `wf_call` chains in WASM. - **D. Unjustified** — should be migrated to WASM via `wf_call`. CI fails if anything in this category lands. --- ## NativeAOT-WASI active surface (Rubeus / Seatbelt path) Functions registered in `internal/hostmod/nativeaot.go` and exercised by the Rubeus/Seatbelt parity test suite. Probe-verified against Rubeus.wasm. | Canonical (Go-side) | Anonymized (WASM import) | Category | Rationale | |---|---|---|---| | win32_syscalln | mod_invoke | A | Generic wf_call dispatch — foundation | | win32_load_library | mod_load | A | DLL loading — foundation | | win32_get_proc_address | mod_resolve | A | Symbol resolution — foundation | | win32_host_read_bytes | mod_hread | A | Cross-boundary memory read — foundation | | win32_register_funcptr | mod_regptr | A | Callback infrastructure — foundation | | win32_lsa_kerberos_op | lsa_kerbop | C | SYSTEM impersonation + COM STA + LSA on same thread; cannot reproduce via wf_call chains in WASM | | win32_crypto_op | xc_op | C | Generic crypto dispatcher. Runs the iteration loop of MS-PBKDF2 (Windows DPAPI master-key derivation), RFC PBKDF2, HMAC, plain hash, and AES-CBC host-side via BCrypt. The in-bridge alternative (looping `wf_call` per HMAC) costs ~32k wf_calls per master key (~40 min); the dispatcher is one wf_call per derivation. Designed as the **landing pad for future complex/looped crypto** so we don't grow the host export surface per new primitive. Opcode catalog: sha1/sha256/sha512, hmac1/256/512, pbkdf2_1/256/512, mspbkdf2_1/mspbkdf2_512, aescbcdec | | win32_io_op | xi_op | B | Generic IO dispatcher. Sibling to xc_op for filesystem operations. Replaces the 6-8 wf_call CreateFile+ReadFile+CloseHandle chain in `fs_read_all` (~46ms per file) with a single host trip via Go's `os.ReadFile` (~<1ms per file). Opcode catalog: read, stat, list. Same wire format as xc_op (opcode string + length-prefixed packed byte fields). Future filesystem ops add opcodes, not exports. | | win32_reg_search | reg_search | C | Host-side BFS walker for SharpDPAPI's `search` verb. Walks the supplied hive enumerating every value and matches against SharpDPAPI's DPAPI provider GUID + 4 base64/hex string signatures. Doing this in WASM costs ~4 wf_calls per visited key × ~500K HKLM keys → exceeds the lab's 5-minute exec timeout. Host-side BFS uses parent-handle propagation at native Win32 speed (`RegOpenKeyEx(parent, child, …)` — no full-path lookup per key) and completes both hives in seconds. Wire format: NUL-separated UTF-8 records, first record is the "Root:" line matching native FindRegistryBlobs | | win32_dpapi_backupkey | dpapi_bkey | C | Host-side DsGetDcNameW + LsaOpenPolicy + 2× LsaRetrievePrivateData chain for SharpDPAPI's `backupkey` verb. DsGetDcName writes a `DOMAIN_CONTROLLER_INFOW*` (host pointer) to its OUT param; LsaRetrievePrivateData writes a `LSA_UNICODE_STRING*` (host pointer) whose Buffer field is also a host pointer. wasm32 C# would truncate both via Marshal.PtrToStructure on a 32-bit IntPtr → OOB trap. Single wf_call returns the materialised DC FQDN + 16-byte preferred-key GUID + raw key blob; C# assembles the kirbi (PVK wrapping + base64). Wire format: packed (status u32, dc_name length-prefixed, guid length-prefixed, key_blob length-prefixed) | | win32_x509_match | x509_match | C | Host-side X.509 store walker for SharpDPAPI's `machinetriage` cert-matching path. Native walks `CurrentUser\MY` + `LocalMachine\MY` then for each cert calls `cert.PublicKey.Key.ToXmlString` (System.Security.Cryptography PNS on NativeAOT-WASI) to compare against a private-key XML derived from the decrypted blob. The bridge takes raw big-endian RSA modulus bytes (extracted by manual byte slicing on the C# side — no crypto calls), walks both stores via `CertEnumCertificatesInStore`, parses each cert's SubjectPublicKeyInfo via `crypto/x509`, compares moduli, and on match returns packed metadata (thumbprint, issuer, subject, dates, EKUs, cert DER) for C# to format the multi-line output + PEM-wrap. Wire format: status u32, then on match 7 length-prefixed records | | win32_virtual_alloc | mem_alloc | A | Host memory allocation — foundation | | win32_virtual_free | mem_free | A | Host memory free — foundation | | win32_hmem_read | mem_read | A | Cross-boundary memory read — foundation | | win32_hmem_write | mem_write | A | Cross-boundary memory write — foundation | | win32_hmem_write32 | mem_write32 | A | Cross-boundary u32 write — foundation | | win32_hmem_write64 | mem_write64 | A | Cross-boundary u64 write — foundation | | win32_hmem_addr | mem_addr | A | Host VA retrieval — foundation | | win32_wmi_query_restricted | wmi_query_r | C | Atomic WMI query for restricted namespaces (root\SecurityCenter2, ROOT\Subscription) that fire IUnknown auth callbacks during ConnectServer. The host implementation calls CoSetProxyBlanket and runs the entire IWbemServices chain on a COM STA thread so callbacks never cross the WASM FFI boundary. Seatbelt AntiVirus and WMIEventConsumer parity require this; cannot trivially replicate in WASM-side wf_call chains | --- ## Foundation primitives — socket / networking (all targets) Registered in `internal/hostmod/module.go`, `tcp.go`, `udp.go`, `io.go`, `dns.go`, `sockopt.go`. Used by all WASM targets (Go and NativeAOT alike). | Canonical (Go-side) | Anonymized (WASM import) | Category | Rationale | |---|---|---|---| | sock_open | fd_open | B | Socket creation — WASM has no kernel socket syscall | | sock_bind | fd_bind | B | Bind to local address | | sock_listen | fd_listen | B | Mark socket as listening | | sock_connect | fd_connect | B | TCP/UDP connect | | sock_accept | fd_accept | B | Accept incoming connection | | sock_read | fd_read2 | B | Non-blocking socket read | | sock_write | fd_write2 | B | Non-blocking socket write | | sock_close | fd_close2 | B | Socket close | | sock_sendto | fd_sendto | B | UDP datagrams — sendto | | sock_recvfrom | fd_recvfrom | B | UDP datagrams — recvfrom | | sock_shutdown | fd_shutdown | B | Graceful socket shutdown | | sock_setsockopt | fd_setsockopt | B | Socket option set | | sock_getsockopt | fd_getsockopt | B | Socket option get | | sock_getpeername | fd_getpeername | B | Remote address query | | sock_getsockname | fd_getsockname | B | Local address query | | sock_getaddrinfo | addr_resolve | B | DNS resolution — WASM has no getaddrinfo | --- ## Foundation primitives — raw sockets (requires --raw-sockets) Registered in `internal/hostmod/raw.go`. | Canonical (Go-side) | Anonymized (WASM import) | Category | Rationale | |---|---|---|---| | raw_sock_open | fd_raw_open | B | SOCK_RAW creation — requires CAP_NET_RAW, not available in WASM | | raw_sock_send | fd_raw_send | B | Raw packet send | | raw_sock_recv | fd_raw_recv | B | Raw packet receive | --- ## Foundation primitives — OS proxies (all targets) Registered in `internal/hostmod/os_host.go` and `os_exec.go`. | Canonical (Go-side) | Anonymized (WASM import) | Category | Rationale | |---|---|---|---| | os_hostname | sys_hostname | B | gethostname — not available in wasip1 | | os_getwd | sys_getwd | B | getcwd — WASI path mapping doesn't expose host cwd | | os_chdir | sys_chdir | B | chdir — host working directory change | | os_user_current | sys_user | B | getpwuid / GetCurrentUser — not available in WASM | | os_getpid | sys_pid | B | getpid — WASI has no process ID concept | | os_process_list | sys_procs | B | Process enumeration — OS-specific, no WASM equivalent | | os_exec | proc_exec | B | CreateProcess/exec with output capture | | os_start_process | proc_start | B | Non-blocking process start | | os_wait4 | proc_wait | B | Wait for child process completion | | net_interfaces | sys_netifs | B | Network interface enumeration — no WASM equivalent | --- ## Foundation primitives — pipes (all targets) Registered in `internal/hostmod/pipe.go`. | Canonical (Go-side) | Anonymized (WASM import) | Category | Rationale | |---|---|---|---| | os_pipe | fd_pipe | B | Host pipe pair creation — os.Pipe() returns ENOSYS on wasip1 | | pipe_read | fd_pread | B | Read from host pipe | | pipe_write | fd_pwrite | B | Write to host pipe | | pipe_close | fd_pclose | B | Close host pipe | --- ## --win32-apis Go-side surface (non-NativeAOT) Functions used by `wasmforge build --win32-apis` with Go source input (Sliver, goffloader, etc.). Registered via `internal/hostmod/win32.go`. Not exercised by the Rubeus/Seatbelt parity tests, but kept for the broader product surface. ### Dispatch / module loading | Canonical (Go-side) | Anonymized (WASM import) | Category | Rationale | |---|---|---|---| | win32_available | mod_available | B | Feature-gate check — thin flag check | | win32_load_library | mod_load | A | LoadLibraryA — also registered by nativeaot.go | | win32_get_proc_address | mod_resolve | A | GetProcAddress — also registered by nativeaot.go | | win32_call | mod_call | B | Call proc (≤6 uint32 args) — thin DLL wrapper for Go programs | | win32_syscalln | mod_invoke | A | SyscallN (≤15 i64 args) — also registered by nativeaot.go | | win32_free_library | mod_free | B | FreeLibrary | | win32_close_handle | mod_close | B | CloseHandle (generic) | | win32_proc_addr | mod_addr | A | Native address of loaded proc — foundation | | win32_proc_from_hmem | mem_proc | B | Proc from host memory handle — goffloader pattern | | win32_register_funcptr | mod_regptr | A | Callback registration — also registered by nativeaot.go | | win32_host_read_bytes | mod_hread | A | Host memory read — also registered by nativeaot.go | | win32_new_callback | ext_callback | B | NewCallback (function pointer thunk for Go closures) | ### Registry | Canonical (Go-side) | Anonymized (WASM import) | Category | Rationale | |---|---|---|---| | win32_reg_open_key | win32_reg_open_key | B | RegOpenKeyExW — thin wrapper | | win32_reg_close_key | win32_reg_close_key | B | RegCloseKey — thin wrapper | | win32_reg_query_value | reg_query | B | RegQueryValueExW | | win32_reg_set_value | reg_set | B | RegSetValueExW | | win32_reg_delete_value | reg_delete | B | RegDeleteValueW | | win32_reg_enum_key | win32_reg_enum_key | B | RegEnumKeyExW — thin wrapper | ### Filesystem | Canonical (Go-side) | Anonymized (WASM import) | Category | Rationale | |---|---|---|---| | win32_create_file | fs_create | B | CreateFileW | | win32_read_file | fs_read | B | ReadFile | | win32_write_file | fs_write | B | WriteFile | | win32_get_file_attrs | fs_getattr | B | GetFileAttributesW | | win32_set_file_attrs | fs_setattr | B | SetFileAttributesW | | win32_find_files | fs_findfiles | B | FindFirstFileW + FindNextFileW enumeration | ### Process | Canonical (Go-side) | Anonymized (WASM import) | Category | Rationale | |---|---|---|---| | win32_get_computer_name | sys_compname | B | GetComputerNameW | | win32_create_process | proc_create | B | CreateProcessW | | win32_open_process | proc_open | B | OpenProcess | | win32_terminate_process | proc_term | B | TerminateProcess | ### Security / tokens | Canonical (Go-side) | Anonymized (WASM import) | Category | Rationale | |---|---|---|---| | win32_open_process_token | sec_opentoken | B | OpenProcessToken | | win32_get_token_info | sec_tokeninfo | B | GetTokenInformation | | win32_open_sc_manager | svc_open | B | OpenSCManagerW | | win32_query_service_status | svc_status | B | QueryServiceStatus | ### Host memory (VirtualAlloc proxy for goffloader/COFF) | Canonical (Go-side) | Anonymized (WASM import) | Category | Rationale | |---|---|---|---| | win32_virtual_alloc | mem_alloc | A | VirtualAlloc on host — also registered by nativeaot.go | | win32_virtual_protect | mem_protect | B | VirtualProtect — change host memory protection | | win32_virtual_free | mem_free | A | VirtualFree — also registered by nativeaot.go | | win32_hmem_write | mem_write | A | WASM→host memory copy — also registered by nativeaot.go | | win32_hmem_read | mem_read | A | Host→WASM memory copy — also registered by nativeaot.go | | win32_hmem_write32 | mem_write32 | A | u32 write at host offset — also registered by nativeaot.go | | win32_hmem_write64 | mem_write64 | A | u64 write at host offset — also registered by nativeaot.go | | win32_hmem_read32 | mem_read32 | B | u32 read from host offset | | win32_hmem_read64 | mem_read64 | B | u64 read from host offset | | win32_hmem_addr | mem_addr | A | Host VA retrieval — also registered by nativeaot.go | | win32_wmi_query_restricted | wmi_query_r | C | Atomic WMI query for restricted namespaces (root\SecurityCenter2, ROOT\Subscription) that fire IUnknown auth callbacks during ConnectServer. The host implementation calls CoSetProxyBlanket and runs the entire IWbemServices chain on a COM STA thread so callbacks never cross the WASM FFI boundary. Seatbelt AntiVirus and WMIEventConsumer parity require this; cannot trivially replicate in WASM-side wf_call chains | ### Extension API (COFF/BOF callbacks) | Canonical (Go-side) | Anonymized (WASM import) | Category | Rationale | |---|---|---|---| | win32_ext_get_func | ext_getfunc | A | Native address of extension callback — foundation for goffloader | | win32_ext_read_output | ext_readout | B | Read accumulated extension output | | win32_ext_reset_output | ext_resetout | B | Clear extension output buffer | ### Shadow memory (VirtualAlloc interception) | Canonical (Go-side) | Anonymized (WASM import) | Category | Rationale | |---|---|---|---| | shadow_virtual_alloc | shm_alloc | A | Shadow VirtualAlloc — intercepts guest allocations for WASM pointer translation | | shadow_virtual_protect | shm_protect | A | Shadow VirtualProtect — tracks protection changes in shadow map | | shadow_virtual_free | shm_free | A | Shadow VirtualFree — removes allocation from shadow map | --- ## macOS framework bridge (auto-detected from GOOS=darwin) Registered in `internal/hostmod/darwin.go`. Only functional on macOS hosts. | Canonical (Go-side) | Anonymized (WASM import) | Category | Rationale | |---|---|---|---| | darwin_available | fw_available | B | Feature-gate check | | darwin_load | fw_load | B | dlopen — load macOS framework or dylib | | darwin_get_symbol | fw_sym | B | dlsym — get symbol address | | darwin_call | fw_call | B | Call C function via assembly trampoline (SysV ABI) with WASM pointer translation | | darwin_call_masked | fw_call_m | B | Call C function with bitmask-controlled pointer translation | | darwin_call_raw | fw_call_raw | B | Call C function without pointer translation (remote process addresses) | | darwin_mem_read | fw_mem_r | B | Read host memory into WASM linear memory | | darwin_mem_write | fw_mem_w | B | Write WASM data to host memory | | darwin_callback_create | fw_cb_create | A | Create native callback thunk for WASM closure — foundation for macOS delegate patterns | | darwin_callback_addr | fw_cb_addr | A | Get native address of callback thunk | | darwin_callback_wait | fw_cb_wait | A | Block until callback fires (cooperative yield) | | darwin_callback_return | fw_cb_ret | A | Signal callback completion | | darwin_callback_free | fw_cb_free | A | Release callback thunk | | darwin_read_cstring | fw_cstr_r | B | Read null-terminated C string from host memory into WASM | | darwin_block_create | fw_blk_create | A | Create Objective-C Block literal on host — foundation for ObjC API patterns | | darwin_block_release | fw_blk_release | A | Release Objective-C Block | | darwin_block_addr | fw_blk_addr | A | Get native address of Block literal | --- ## Retired / out-of-scope re-migration candidates These functions appeared in nativeaot.go's earlier registration but were removed because the C# side migrated to WASM-side wf_call chains: | Former canonical | Former anonymized | Removed in | Notes | |---|---|---|---| | win32_wmi_query | wmi_query | Phase B | WASM-side stub | | win32_wmi_method | wmi_method | Phase B | WASM-side stub | | win32_get_sddl | (none) | Phase B | WASM-side via wf_call | | win32_enum_user_rights | (none) | Phase B | WASM-side stub | | win32_enum_rpc_endpoints | (none) | Phase B | WASM-side stub | | win32_enum_network_adapters | (none) | Phase B | WASM-side via wf_call | | win32_get_file_version_info | (none) | Phase B | WASM-side via wf_call | | win32_enum_reg_values | (none) | Phase B | WASM-side via wf_call (advapi32) | | win32_parse_sddl_acl | (none) | Phase B | WASM-side passthrough | | win32_reg_enum_key (nativeaot path) | (none) | Phase B | WASM-side via wf_call | If a future feature exercises any of these, the migration follows the Phase 2 pattern: write the WASM-side pinvoke_env_ext.c replacement, remove the `import_module` attribute in `wf_bridge.h`, retire the Go-side Export.