Files
praetorian-inc-wasmforge/docs/internals/HOST-API-CONTRACT.md
T
michaelweber a983ca221e removing references to projects we haven't open sourced yet (#3)
* removing references to projects we haven't open sourced yet

* remove last Tribunus reference from test/.gitignore (reviewer nit)
2026-06-16 16:17:10 -04:00

17 KiB
Raw Blame History

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.