mirror of
https://github.com/BaaRai/ARTEX.git
synced 2026-11-13 11:40:53 +08:00
Comments, doc comments, UI labels, log and error messages, LLM prompt strings, markdown docs, test data and assertions. Identifiers, enum keys, routes, placeholders and code structure are unchanged. Notes: - docs/漏洞流量证据.md renamed to docs/vulnerability-traffic-evidence.md - tests that exercised CJK specific handling (GBK zip names, Chinese full text search) now use ASCII equivalents; go build passes and the test suite matches the pre translation baseline (remaining failures need a PostgreSQL DSN and fail identically on the original) - frontend mention syntax now uses English kinds (finding, asset, ...) matching the backend, chat mentions tests pass
113 lines
4.9 KiB
Go
113 lines
4.9 KiB
Go
package notify
|
|
|
|
import (
|
|
"context"
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// This file locks down the **capability boundary** of the generic Webhook template.
|
|
//
|
|
// This is the only place in the whole package where "a user-provided string is
|
|
// evaluated as code", so we must spell out what it can and cannot do, and pin those
|
|
// properties down with tests — otherwise one day someone will casually add a method
|
|
// to the template context, or a readFile to the FuncMap, and the capability surface
|
|
// silently expands while the diff looks like a harmless little function.
|
|
|
|
// TestTemplateContextHasNoMethods is the most important one.
|
|
//
|
|
// text/template invokes exported methods ({{.Foo}} can read a field or call a
|
|
// method). So as long as the template context can reach **any** type with exported
|
|
// methods, those methods are effectively exposed to template authors.
|
|
// This feature's context is deliberately pure data (exported fields only, zero
|
|
// methods).
|
|
//
|
|
// If this test fails: someone added a method to webhookTemplateData / webhookItem.
|
|
// Before deciding to allow it, think carefully about whether that method could be
|
|
// used by a template to read things that should not be exposed.
|
|
func TestTemplateContextHasNoMethods(t *testing.T) {
|
|
for _, v := range []any{webhookTemplateData{}, webhookItem{}} {
|
|
typ := reflect.TypeOf(v)
|
|
if n := typ.NumMethod(); n != 0 {
|
|
var names []string
|
|
for i := 0; i < n; i++ {
|
|
names = append(names, typ.Method(i).Name)
|
|
}
|
|
t.Fatalf("%s exposes %d methods (%s): text/template can call them, "+
|
|
"which hands those methods' capabilities to template authors", typ.Name(), n, strings.Join(names, ", "))
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestTemplateFuncsAreMinimal locks down the set of functions exposed to templates.
|
|
//
|
|
// Every extra function in the FuncMap is one more capability. Currently there are
|
|
// only json / jsons, whose job is to serialize a value into a JSON fragment — they
|
|
// cannot read files, make requests, or execute commands.
|
|
func TestTemplateFuncsAreMinimal(t *testing.T) {
|
|
var got []string
|
|
for name := range webhookTemplateFuncs {
|
|
got = append(got, name)
|
|
}
|
|
sort.Strings(got)
|
|
want := []string{"json", "jsons"}
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("template function set changed: got %v, want %v. Before adding a function, confirm it does not expand the capability surface "+
|
|
"(no file reads/writes, no network requests, no command execution)", got, want)
|
|
}
|
|
}
|
|
|
|
// TestTemplateCannotReachUnknownData covers out-of-bounds access in templates:
|
|
// accessing something that does not exist must fail, not echo something back;
|
|
// and the failure message must not carry internal data out.
|
|
func TestTemplateCannotReachUnknownData(t *testing.T) {
|
|
_, err := renderWebhookBody(`{"x": {{.Environment}}, "y": {{.Env}}}`, singleMsg())
|
|
if err == nil {
|
|
t.Fatal("accessing a non-existent field should error")
|
|
}
|
|
// The error must not contain real content from the template context (vulnerability title/summary).
|
|
for _, leak := range []string{"SQL injection", "param id"} {
|
|
if strings.Contains(err.Error(), leak) {
|
|
t.Errorf("template error leaked message content %q: %v", leak, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestTemplateRenderFailsPermanently: a broken template is a configuration error; retries will not fix it.
|
|
// If it were classified as retryable, one bad template would make every delivery waste three rounds of backoff.
|
|
func TestTemplateRenderFailsPermanently(t *testing.T) {
|
|
cfg := map[string]any{
|
|
"url": "https://example.com/hook",
|
|
"body_template": `{{.Items.`,
|
|
}
|
|
if err := (webhookChannel{}).Validate(cfg); err == nil {
|
|
t.Fatal("template syntax errors should be caught at save time")
|
|
}
|
|
// Even if validation is bypassed and delivery is attempted directly, it must be judged a permanent failure rather than retried over and over.
|
|
_, err := (webhookChannel{}).Send(context.Background(), cfg, singleMsg())
|
|
if err == nil || !IsPermanent(err) {
|
|
t.Fatalf("a bad template should be a permanent failure, got %v", err)
|
|
}
|
|
}
|
|
|
|
// TestTemplateCanOnlyProduceJSON covers the constraint that "the template render result must be valid JSON".
|
|
// It also blocks uses like "using the template to generate plain text to trigger other protocols".
|
|
func TestTemplateCanOnlyProduceJSON(t *testing.T) {
|
|
// A valid template passes.
|
|
ok := map[string]any{"url": "https://example.com/hook", "body_template": `{"t":{{json .Title}}}`}
|
|
if err := (webhookChannel{}).Validate(ok); err != nil {
|
|
t.Fatalf("a valid template should pass validation: %v", err)
|
|
}
|
|
// When the render result is not JSON it must be rejected (rather than sent out as-is).
|
|
bad := map[string]any{"url": "http://127.0.0.1:1/hook", "body_template": `not json {{.Count}}`}
|
|
_, err := (webhookChannel{}).Send(context.Background(), bad, singleMsg())
|
|
if err == nil || !IsPermanent(err) {
|
|
t.Fatalf("rendering non-JSON should be a permanent failure, got %v", err)
|
|
}
|
|
if !strings.Contains(err.Error(), "valid JSON") {
|
|
t.Errorf("the error message should indicate a JSON problem, got %v", err)
|
|
}
|
|
}
|