{"record":{"id":"e307e54fc42800e6","repo":"flipped-aurora/gin-vue-admin","slug":"http-https-q","errorCode":null,"errorMessage":"仅允许 http/https, 实际为 %q","messagePattern":"仅允许 http/https, 实际为 %q","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/service/system/sys_timed_task_runner.go","lineNumber":132,"sourceCode":"\tselect {\n\tcase err := <-done:\n\t\tif err != nil && errors.Is(err, context.DeadlineExceeded) {\n\t\t\treturn \"\", errTaskTimeout\n\t\t}\n\t\treturn \"\", err\n\tcase <-ctx.Done():\n\t\treturn \"\", errTaskTimeout\n\t}\n}\n\n// runHTTP 执行 HTTP 回调(SSRF 防护见 sys_timed_task_http.go)\nfunc (s *TimedTaskService) runHTTP(t system.SysTimedTask) (string, error) {\n\tu, err := url.Parse(t.HttpUrl)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"URL 非法: %w\", err)\n\t}\n\tif u.Scheme != \"http\" && u.Scheme != \"https\" {\n\t\treturn \"\", fmt.Errorf(\"仅允许 http/https, 实际为 %q\", u.Scheme)\n\t}\n\tmethod := strings.ToUpper(strings.TrimSpace(t.HttpMethod))\n\tif method == \"\" {\n\t\tmethod = http.MethodGet\n\t}\n\tvar body io.Reader\n\tif t.HttpBody != \"\" {\n\t\tbody = strings.NewReader(t.HttpBody)\n\t}\n\treq, err := http.NewRequest(method, t.HttpUrl, body)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"构造请求失败: %w\", err)\n\t}\n\tif len(t.HttpHeader) > 0 {\n\t\tvar hdr map[string]string\n\t\tif err := json.Unmarshal(t.HttpHeader, &hdr); err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"http_header 必须是 JSON 对象: %w\", err)\n\t\t}","sourceCodeStart":114,"sourceCodeEnd":150,"githubUrl":"https://github.com/flipped-aurora/gin-vue-admin/blob/3136500ef380842b0eb6c4daa87c3f8a47fcf9e0/server/service/system/sys_timed_task_runner.go#L114-L150","documentation":"After parsing, runHTTP enforces a scheme allowlist: only http and https are accepted. Any other scheme (ftp, file, gopher, javascript, empty) is rejected to prevent SSRF/local-file access. The actual scheme is quoted in the error.","triggerScenarios":"t.HttpUrl uses a non-http(s) scheme — e.g. 'file:///etc/passwd', 'ftp://...', or a schemeless 'example.com/hook' (empty scheme).","commonSituations":"Trying to use the HTTP callback for internal file reads; forgetting the 'http://' prefix when entering the URL; probing SSRF protections during security review.","solutions":["Change the task URL to start with http:// or https://","Normalize schemeless URLs by prefixing https:// when saving tasks","Keep the allowlist; do not loosen it for internal schemes"],"exampleFix":"// before\nHttpUrl: \"ftp://backup.example.com/ping\"\n// after\nHttpUrl: \"https://backup.example.com/ping\"","handlingStrategy":"validation","validationCode":"u, err := url.Parse(httpUrl)\nif err != nil || (u.Scheme != \"http\" && u.Scheme != \"https\") {\n    return errors.New(\"callback URL must use http or https\")\n}","typeGuard":"func isHTTPOrHTTPS(s string) bool {\n    u, err := url.Parse(s)\n    return err == nil && (u.Scheme == \"http\" || u.Scheme == \"https\")\n}","tryCatchPattern":"if err := RunTask(t); err != nil {\n    if strings.Contains(err.Error(), \"仅允许 http/https\") {\n        log.Warnf(\"task %d rejected scheme in %q\", t.ID, t.HttpUrl)\n    }\n}","preventionTips":["Always include the scheme when entering callback URLs (default to https://)","Enforce the scheme allowlist in the task-creation API, not only at run time","Never attempt file:// or internal schemes through this executor"],"tags":["ssrf","security","url-validation","timed-task"],"backgroundTag":"unsupported-url-scheme","analyzedSha":"3136500ef380842b0eb6c4daa87c3f8a47fcf9e0","analyzedAt":"2026-08-31T13:50:02.721Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}