I suspect that WebAssembly is actually the wrong abstraction here, and you might want something a little closer to BPF for non-process-isolated serverless compute, but also not BPF because BPF can do arbitrary memory reads. WebAssembly appears to be too permissive for this case, and BPF too restrictive to be useful.
Ideally, if you could have a language-level VM that guarantees that you only ever read or write data inside memory allocated to you and calls a limited set of syscalls (which you intercept/rewrite at compile time), you probably don't need any other protection.
You still need a way to protect the data inside the VM's memory from getting trampled, since the data could contain the spiritual equivalent of function pointers and those get passed out to the syscalls. Or at a simpler level, if gmail is running in a wasm sandbox, I still don't want an attacker corrupting gmail's heap and causing it to forward my entire inbox to the attacker even if my OS is safe.
There are ways to have a verifier check to make sure that this kind of code modification doesn't happen. It's a very hard problem, though, and maybe not suitable for things like Javascript.
What I'm referring to is not code modification, but heap corruption. The simplest example would be that if the to: and bcc: list for your email live in a wasm heap, any bug in that wasm application could be used to redirect your email. The same is true for JavaScript, C#, etc but the key difference is that those are memory-safe languages under normal circumstances, while WASM is only "memory safe" in that the memory accesses are contained to its sandbox (and part of the stack is outside of the heap and protected from stack overflows, like the return address). Everything else is still wide open for corruption and is easier to manipulate due to predictable function pointers.
The wasm shadow stack is a great idea for mitigating various attacks against C but since you can only put ints and floats on that stack and you can't pass values by reference, the vast majority of applications have a bunch of critical stack data stored in the heap at a fixed address next to non-critical data and a stray memcpy/strcpy can trample a function pointer. Function #37 will always be the same function and its arguments are probably being loaded from nearby locals on the stack.
If support for putting more complex types in arguments and locals ever ships in wasm, it would make it possible to mitigate a lot of this. I'm not sure whether that's in the cards, though.
Ideally, if you could have a language-level VM that guarantees that you only ever read or write data inside memory allocated to you and calls a limited set of syscalls (which you intercept/rewrite at compile time), you probably don't need any other protection.