mirror of
https://gh-proxy.org/https://github.com/klinsmaya/multica.git
synced 2026-11-08 17:36:45 +08:00
* fix(api): stop returning raw error text in 500 responses 39 handlers answered a 500 with the message followed by err.Error(), sending database and store errors (constraint names, SQL states, Redis errors) to API clients, while the other 720 500 responses in the handler package send a fixed message. These now send their fixed message too and log the error with the request's attributes, as the handlers that already logged it do. * fix(api): drop raw error text from the task callbacks and skill import 500s completeTask and failTask sent err.Error() as the whole 500 message, and the structured skill import put it in the result's Reason on its three 500s. They now send fixed messages and log the error. The task callbacks keep their 500, so the daemon still retries them: isTransientError reads the status code, never the body. * fix(api): close remaining raw error leaks Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Yushen <[email protected]> Co-authored-by: multica-agent <[email protected]>
68 lines
1.8 KiB
Go
68 lines
1.8 KiB
Go
package handler
|
|
|
|
import (
|
|
"go/ast"
|
|
"go/parser"
|
|
"go/token"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// TestInternalServerErrorsDoNotExposeCause keeps raw infrastructure errors on
|
|
// the server side. It walks complete call expressions, so multiline payloads
|
|
// and error variables with names other than err are covered too.
|
|
func TestInternalServerErrorsDoNotExposeCause(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
entries, err := os.ReadDir(".")
|
|
if err != nil {
|
|
t.Fatalf("read handler package: %v", err)
|
|
}
|
|
fset := token.NewFileSet()
|
|
for _, entry := range entries {
|
|
name := entry.Name()
|
|
if entry.IsDir() || !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") {
|
|
continue
|
|
}
|
|
file, err := parser.ParseFile(fset, filepath.Clean(name), nil, 0)
|
|
if err != nil {
|
|
t.Fatalf("parse %s: %v", name, err)
|
|
}
|
|
ast.Inspect(file, func(node ast.Node) bool {
|
|
call, ok := node.(*ast.CallExpr)
|
|
if !ok || !callUsesInternalServerError(call) {
|
|
return true
|
|
}
|
|
ast.Inspect(call, func(child ast.Node) bool {
|
|
causeCall, ok := child.(*ast.CallExpr)
|
|
if !ok {
|
|
return true
|
|
}
|
|
selector, ok := causeCall.Fun.(*ast.SelectorExpr)
|
|
if ok && selector.Sel.Name == "Error" && len(causeCall.Args) == 0 {
|
|
pos := fset.Position(causeCall.Pos())
|
|
t.Errorf("%s:%d returns a raw error from an internal-server-error path; send stable public copy and log the cause", name, pos.Line)
|
|
}
|
|
return true
|
|
})
|
|
return false
|
|
})
|
|
}
|
|
}
|
|
|
|
func callUsesInternalServerError(call *ast.CallExpr) bool {
|
|
for _, arg := range call.Args {
|
|
selector, ok := arg.(*ast.SelectorExpr)
|
|
if !ok || selector.Sel.Name != "StatusInternalServerError" {
|
|
continue
|
|
}
|
|
pkg, ok := selector.X.(*ast.Ident)
|
|
if ok && pkg.Name == "http" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|