{"record":{"id":"f75828e875676845","repo":"Tencent/WeKnora","slug":"errunsafeoutboundurl","errorCode":"ErrUnsafeOutboundURL","errorMessage":"%w: empty URL","messagePattern":"%w: empty URL","errorType":"validation","errorClass":"ErrUnsafeOutboundURL","httpStatus":null,"severity":"error","filePath":"internal/sandbox/url_guard.go","lineNumber":80,"sourceCode":"func SafeDialControl(network string, address string, conn syscall.RawConn) error {\n\treturn DefaultOutboundURLPolicy().DialControl(network, address, conn)\n}\n\nfunc SafeDialControlForPolicy(policy OutboundURLPolicy) func(string, string, syscall.RawConn) error {\n\treturn func(network string, address string, conn syscall.RawConn) error {\n\t\treturn policy.DialControl(network, address, conn)\n\t}\n}\n\n// Validate reports whether raw is an acceptable tenant-supplied endpoint. It\n// rejects non-HTTP schemes and any host that resolves to a forbidden address.\n//\n// Callers must ALSO install DialControl on the dialer they use; Validate alone\n// cannot close the DNS-rebinding window.\nfunc (p OutboundURLPolicy) Validate(raw string) error {\n\traw = strings.TrimSpace(raw)\n\tif raw == \"\" {\n\t\treturn fmt.Errorf(\"%w: empty URL\", ErrUnsafeOutboundURL)\n\t}\n\tparsed, err := url.Parse(raw)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"%w: %v\", ErrUnsafeOutboundURL, err)\n\t}\n\tswitch strings.ToLower(parsed.Scheme) {\n\tcase \"http\", \"https\":\n\tdefault:\n\t\treturn fmt.Errorf(\"%w: scheme %q is not allowed\", ErrUnsafeOutboundURL, parsed.Scheme)\n\t}\n\n\thost := parsed.Hostname()\n\tif host == \"\" {\n\t\treturn fmt.Errorf(\"%w: missing host\", ErrUnsafeOutboundURL)\n\t}\n\t// \".local\" is mDNS; \"localhost\" is only acceptable under the opt-in.\n\tlower := strings.ToLower(host)\n\tif strings.HasSuffix(lower, \".local\") {","sourceCodeStart":62,"sourceCodeEnd":98,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/sandbox/url_guard.go#L62-L98","documentation":"OutboundURLPolicy.Validate is the first-line guard for agent-initiated outbound HTTP. An empty (or whitespace-only) URL cannot be checked, so it is rejected immediately with ErrUnsafeOutboundURL to fail closed rather than allowing an unvalidated request.","triggerScenarios":"Calling Validate or ValidateOutboundURLWithPolicy with \"\" or a string that is only whitespace after TrimSpace — typically an unset config field or a variable that was never populated.","commonSituations":"Missing environment variable / config key for a webhook or API base URL; template placeholder not substituted (\"{{url}}\" would hit scheme check, truly empty hits this); optional URL fields left blank.","solutions":["Supply a non-empty http(s) URL before calling, e.g. from config or flags.","Check for empty input at startup and fail configuration loading early with a named field in the message.","If the URL is legitimately optional, skip the call instead of validating an empty string."],"exampleFix":"// before\nvar endpoint string // never set\nerr := policy.Validate(endpoint)\n// after\nendpoint := os.Getenv(\"WEBHOOK_URL\")\nif endpoint == \"\" {\n    return errors.New(\"WEBHOOK_URL must be set\")\n}\nerr := policy.Validate(endpoint)","handlingStrategy":"validation","validationCode":"if strings.TrimSpace(rawURL) == \"\" {\n    return errors.New(\"outbound URL is required\")\n}","typeGuard":"func nonEmptyURL(s string) bool { return strings.TrimSpace(s) != \"\" }","tryCatchPattern":"err := policy.Validate(raw)\nif errors.Is(err, sandbox.ErrUnsafeOutboundURL) {\n    return fmt.Errorf(\"outbound URL rejected: %w\", err)\n}","preventionTips":["Fail fast at config load when required URL fields are empty.","Distinguish optional vs required URL fields in your config schema.","Trim whitespace from env/config values before use."],"tags":["validation","url","security"],"backgroundTag":"empty-url","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}