Commit 11114e655e
Verified · cmc
Layout: unified · split
internal/control/ghimport.go +4
| @@ -173,6 +173,10 @@ func runImportIssues(c *Ctx, args []string) int { | ||
| 173 | 173 | given := apiBase != "" |
| 174 | 174 | if !given { |
| 175 | 175 | apiBase = "https://api.github.com" |
| 176 | } else if strings.Contains(apiBase, "@") || strings.ContainsAny(apiBase, "?#") { | |
| 177 | // Same rule as repo import: the URL is echoed and becomes the | |
| 178 | // site prefix, so credentials and queries stay out of it. | |
| 179 | return c.fail(protocol.ExitUsage, "--api-base: use the plain API URL, without credentials, a query or a fragment; a token goes on stdin with --token-stdin") | |
| 176 | 180 | } |
| 177 | 181 | // A writer-supplied API base is the same SSRF surface as a webhook |
| 178 | 182 | // target. Resolve and check it once here; the client connects only |
internal/control/ghimport_test.go +13
| @@ -176,3 +176,16 @@ func TestImportIssuesRefusesAPrivateAPIBase(t *testing.T) { | ||
| 176 | 176 | t.Fatalf("looked up %v", *asked) |
| 177 | 177 | } |
| 178 | 178 | } |
| 179 | ||
| 180 | // --api-base takes a plain http or https URL: no credentials, query, | |
| 181 | // fragment or other scheme, and nothing of a refused one is echoed. | |
| 182 | func TestImportIssuesRefusesAnAPIBaseShape(t *testing.T) { | |
| 183 | for _, base := range []string{"https://abc@api.test", "https://api.test/?abc", "https://api.test/#abc", "ftp://api.test/abc"} { | |
| 184 | c, errOut, _, _ := importCtx(t, true) | |
| 185 | asked := stubLookup(t, "127.0.0.1") | |
| 186 | code := Dispatch(c, []string{"repo", "import-issues", "alice/app", "--from", "o/r", "--api-base", base}) | |
| 187 | if code != protocol.ExitUsage || len(*asked) != 0 || strings.Contains(errOut.String(), "abc") { | |
| 188 | t.Fatalf("%s: exit %d, looked up %v: %s", base, code, *asked, errOut.String()) | |
| 189 | } | |
| 190 | } | |
| 191 | } | |