{"record":{"id":"9a3edab736e838d9","repo":"semaphoreui/semaphore","slug":"http-requests-forbidden","errorCode":null,"errorMessage":"http requests forbidden","messagePattern":"http requests forbidden","errorType":"http","errorClass":null,"httpStatus":403,"severity":"error","filePath":"cli/cmd/root.go","lineNumber":385,"sourceCode":"\n\t\t\t\t\tif util.Config.WebHost != \"\" {\n\t\t\t\t\t\twebHost, err2 := url.Parse(util.Config.WebHost)\n\t\t\t\t\t\tif err2 != nil {\n\t\t\t\t\t\t\tlog.Panic(err2)\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttarget += webHost.Host + r.URL.Path\n\t\t\t\t\t} else {\n\t\t\t\t\t\thostParts := strings.Split(r.Host, \":\")\n\t\t\t\t\t\thost := hostParts[0]\n\t\t\t\t\t\ttarget += host + port + r.URL.Path\n\t\t\t\t\t}\n\n\t\t\t\t\tif len(r.URL.RawQuery) > 0 {\n\t\t\t\t\t\ttarget += \"?\" + r.URL.RawQuery\n\t\t\t\t\t}\n\n\t\t\t\t\tif r.Method != \"GET\" && r.Method != \"HEAD\" && r.Method != \"OPTIONS\" {\n\t\t\t\t\t\thttp.Error(w, \"http requests forbidden\", http.StatusForbidden)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\thttp.Redirect(w, r, target, http.StatusTemporaryRedirect)\n\t\t\t\t}))\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Panic(err)\n\t\t\t\t}\n\t\t\t}()\n\t\t}\n\n\t\terr = http.ListenAndServeTLS(util.Config.Interface+port, util.Config.TLS.CertFile, util.Config.TLS.KeyFile, cropTrailingSlashMiddleware(router))\n\n\t\tif err != nil {\n\t\t\tlog.Panic(err)\n\t\t}\n\n\t} else {","sourceCodeStart":367,"sourceCodeEnd":403,"githubUrl":"https://github.com/semaphoreui/semaphore/blob/1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa/cli/cmd/root.go#L367-L403","documentation":"This is not a thrown panic but an HTTP 403 response from the TLS HTTP-redirect handler: any request whose method is not GET, HEAD, or OPTIONS is rejected with 'http requests forbidden' because only navigation requests should ever hit the insecure redirect listener.","triggerScenarios":"Sending POST/PUT/DELETE/PATCH (or any non-GET/HEAD/OPTIONS method) to the plain-HTTP redirect port while TLS is enabled — e.g. an API client or webhook posting to http://host:80 instead of https://host:443.","commonSituations":"CI jobs or integrations hardcoded to http:// that POST to the redirect port; health probes or webhooks pointing at the wrong port/scheme; browsers/clients not upgraded after enabling TLS.","solutions":["Point the client to the HTTPS endpoint (https://host) and method-appropriate API paths instead of the HTTP redirect port","Change the request to GET/HEAD if it is only meant to test reachability of the redirect","Remove the HTTP redirect listener entirely if the plain-HTTP port should not be exposed"],"exampleFix":"// before\ncurl -X POST http://semaphore.example/api/external/... \n// after\ncurl -X POST https://semaphore.example/api/external/...","handlingStrategy":"validation","validationCode":"const url = new URL(endpoint)\nif (url.protocol !== \"https:\" && [\"POST\",\"PUT\",\"DELETE\",\"PATCH\"].includes(method)) {\n  throw new Error(\"non-GET requests must use the HTTPS endpoint\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Point API clients and webhooks at https:// URLs, never the HTTP redirect port","Use GET/HEAD only when probing the redirect listener","Update integrations after enabling TLS","Document that the plain-HTTP port is redirect-only"],"tags":["http","tls","redirect","http-403","forbidden"],"backgroundTag":"http-error-response","analyzedSha":"1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa","analyzedAt":"2026-09-07T11:00:33.293Z","contentChangedAt":"2026-09-07T11:00:33.293Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}