From 670e6e0323563bf7344dfdff938a6455606b8729 Mon Sep 17 00:00:00 2001 From: J3vb Date: Tue, 7 Apr 2026 07:59:10 +0200 Subject: [PATCH] security: harden plugin sandbox stdin and opaque internal errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - sandbox_wazero.go: explicitly discard plugin stdin via WithStdin to prevent WASM modules from reading the server process's stdin fd - channel_handler.go: replace ErrInternal message pass-through with generic 'an internal error occurred' — full error stays server-side in slog.Error only - invite_handler_test.go: update assertions to expect generic message --- Server/api/channel_handler.go | 3 +-- Server/api/invite_handler_test.go | 12 ++++++------ Server/plugin/sandbox_wazero.go | 2 ++ 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Server/api/channel_handler.go b/Server/api/channel_handler.go index 20a34699..e976516c 100644 --- a/Server/api/channel_handler.go +++ b/Server/api/channel_handler.go @@ -286,8 +286,7 @@ func writeServiceError(w http.ResponseWriter, err error) { writeJSON(w, http.StatusConflict, errorResponse{Error: "CONFLICT", Message: err.Error()}) case errors.Is(err, service.ErrInternal): slog.Error("service error", "err", err) - msg := strings.TrimPrefix(err.Error(), "internal error: ") - writeJSON(w, http.StatusInternalServerError, errorResponse{Error: "INTERNAL_ERROR", Message: msg}) + writeJSON(w, http.StatusInternalServerError, errorResponse{Error: "INTERNAL_ERROR", Message: "an internal error occurred"}) default: slog.Error("service error", "err", err) writeJSON(w, http.StatusInternalServerError, errorResponse{Error: "INTERNAL_ERROR", Message: "internal error"}) diff --git a/Server/api/invite_handler_test.go b/Server/api/invite_handler_test.go index 0927384a..e9cc8a40 100644 --- a/Server/api/invite_handler_test.go +++ b/Server/api/invite_handler_test.go @@ -155,8 +155,8 @@ func TestCreateInvite_CreateInviteFailure(t *testing.T) { if err := json.NewDecoder(rr.Body).Decode(&resp); err != nil { t.Fatalf("decode response: %v", err) } - if resp["message"] != "failed to create invite" { - t.Errorf("message = %v, want failed to create invite", resp["message"]) + if resp["message"] != "an internal error occurred" { + t.Errorf("message = %v, want an internal error occurred", resp["message"]) } } @@ -185,8 +185,8 @@ func TestCreateInvite_GetInviteFailure(t *testing.T) { if err := json.NewDecoder(rr.Body).Decode(&resp); err != nil { t.Fatalf("decode response: %v", err) } - if resp["message"] != "failed to retrieve invite" { - t.Errorf("message = %v, want failed to retrieve invite", resp["message"]) + if resp["message"] != "an internal error occurred" { + t.Errorf("message = %v, want an internal error occurred", resp["message"]) } if _, err := database.Exec(`DROP TRIGGER delete_invite_after_insert`); err != nil { t.Fatalf("drop trigger: %v", err) @@ -387,8 +387,8 @@ func TestRevokeInvite_RevokeFailure(t *testing.T) { if err := json.NewDecoder(rr2.Body).Decode(&resp); err != nil { t.Fatalf("decode revoke failure response: %v", err) } - if resp["message"] != "failed to revoke invite" { - t.Errorf("message = %v, want failed to revoke invite", resp["message"]) + if resp["message"] != "an internal error occurred" { + t.Errorf("message = %v, want an internal error occurred", resp["message"]) } } diff --git a/Server/plugin/sandbox_wazero.go b/Server/plugin/sandbox_wazero.go index 92f26a8e..01c3c46c 100644 --- a/Server/plugin/sandbox_wazero.go +++ b/Server/plugin/sandbox_wazero.go @@ -11,6 +11,7 @@ import ( "fmt" "io" "os" + "strings" "github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero/api" @@ -76,6 +77,7 @@ func (r *Registry) activateWithRuntime(ctx context.Context, inst *Instance) erro modCfg := wazero.NewModuleConfig(). WithName(inst.Manifest.Name). + WithStdin(strings.NewReader("")). // prevent plugins from reading server stdin WithStdout(io.Discard). WithStderr(io.Discard). WithStartFunctions() // suppress _start; exports are called on demand