feat: validate Worker protocol and verify process crash recovery

This commit is contained in:
2026-09-17 15:17:09 +08:00
parent 4d4b2e9f7d
commit 9e3d4be5cc
23 changed files with 1878 additions and 28 deletions

View File

@@ -7,6 +7,7 @@ import (
"errors"
"net/http"
"reflect"
"strconv"
"strings"
"sync"
"time"
@@ -142,9 +143,9 @@ func (h *WorkerHub) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err != nil {
return
}
var hello workerFrame
hello, decodeErr := decodeWorkerRequest(raw, true)
var input worker.ConnectInput
if kind != websocket.MessageText || decodeWorkerJSON(raw, &hello) != nil || hello.Version != "1" || hello.Type != "worker.hello" || hello.MessageID.Validate() != nil || hello.SessionID != "" || hello.ReplyTo != "" || hello.Error != nil || decodeWorkerJSON(hello.Body, &input) != nil {
if kind != websocket.MessageText || decodeErr != nil || decodeWorkerJSON(hello.Body, &input) != nil {
_ = conn.Close(websocket.StatusPolicyViolation, "invalid worker hello")
return
}
@@ -177,8 +178,8 @@ func (h *WorkerHub) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err != nil {
return
}
var frame workerFrame
if kind != websocket.MessageText || decodeWorkerJSON(raw, &frame) != nil || frame.Version != "1" || frame.MessageID.Validate() != nil || frame.SessionID != binding.SessionID || frame.ReplyTo != "" || frame.Error != nil {
frame, decodeErr := decodeWorkerRequest(raw, false)
if kind != websocket.MessageText || decodeErr != nil || frame.SessionID != binding.SessionID {
_ = conn.Close(websocket.StatusPolicyViolation, "invalid worker frame")
return
}
@@ -393,6 +394,9 @@ func decodeWorkerJSON(raw []byte, out any) error {
if err := exactFields(raw, reflect.TypeOf(out).Elem()); err != nil {
return fault.Invalid("unknown or invalid worker field")
}
if err := requiredWorkerFields(raw, reflect.TypeOf(out).Elem()); err != nil {
return err
}
decoder := json.NewDecoder(bytes.NewReader(raw))
decoder.DisallowUnknownFields()
if err := decoder.Decode(out); err != nil {
@@ -400,3 +404,89 @@ func decodeWorkerJSON(raw []byte, out any) error {
}
return nil
}
// decodeWorkerRequest 拒绝请求中伪装为响应的字段;空字符串/null 不能绕过方向约束。
func decodeWorkerRequest(raw []byte, hello bool) (workerFrame, error) {
var frame workerFrame
if err := decodeWorkerJSON(raw, &frame); err != nil {
return frame, err
}
if frame.Version != "1" || frame.MessageID.Validate() != nil || len(frame.Body) == 0 {
return frame, fault.Invalid("invalid worker envelope")
}
var fields map[string]json.RawMessage
_ = json.Unmarshal(raw, &fields)
if fields["error"] != nil || fields["reply_to"] != nil {
return frame, fault.Invalid("response fields are not allowed in worker requests")
}
if hello {
if frame.Type != "worker.hello" || fields["session_id"] != nil {
return frame, fault.Invalid("invalid worker hello envelope")
}
} else if frame.SessionID.Validate() != nil {
return frame, fault.Invalid("worker session is required")
}
return frame, nil
}
// requiredWorkerFields 校验必填字段及规范 uint64 字符串,不把省略布尔值等同于显式控制意图。
func requiredWorkerFields(raw []byte, t reflect.Type) error {
if t == reflect.TypeOf(json.RawMessage{}) || t == reflect.TypeOf(time.Time{}) {
return nil
}
if t.Kind() == reflect.Pointer {
return nil
}
if t.Kind() == reflect.Array || t.Kind() == reflect.Slice {
var values []json.RawMessage
if err := json.Unmarshal(raw, &values); err != nil {
return fault.Invalid("invalid worker array")
}
for _, value := range values {
if err := requiredWorkerFields(value, t.Elem()); err != nil {
return err
}
}
return nil
}
if t.Kind() != reflect.Struct {
return nil
}
var fields map[string]json.RawMessage
if json.Unmarshal(raw, &fields) != nil {
return fault.Invalid("invalid worker object")
}
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
tag := strings.Split(field.Tag.Get("json"), ",")
if tag[0] == "" || tag[0] == "-" {
continue
}
value, found := fields[tag[0]]
optional, numberString := false, false
for _, flag := range tag[1:] {
optional = optional || flag == "omitempty"
numberString = numberString || flag == "string"
}
if !found {
if optional {
continue
}
return fault.Invalid("missing required worker field")
}
if field.Type.Kind() == reflect.Uint64 && numberString {
var text string
if json.Unmarshal(value, &text) != nil {
return fault.Invalid("worker counter must be a decimal string")
}
n, err := strconv.ParseUint(text, 10, 64)
if err != nil || n == 0 || strconv.FormatUint(n, 10) != text {
return fault.Invalid("invalid worker counter")
}
}
if err := requiredWorkerFields(value, field.Type); err != nil {
return err
}
}
return nil
}