{"record":{"id":"8c8d597d0117e633","repo":"crowdsecurity/crowdsec","slug":"invalid-url-template-s","errorCode":null,"errorMessage":"invalid URL template '%s'","messagePattern":"invalid URL template '(.+?)'","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/cwhub/download.go","lineNumber":39,"sourceCode":"\tBranch      string\n\tURLTemplate string\n}\n\n// IndexProvider retrieves and writes .index.json\ntype IndexProvider interface {\n\tFetchIndex(ctx context.Context, indexFile string, withContent bool, logger *logrus.Logger) (bool, error)\n}\n\n// ContentProvider retrieves and writes the YAML files with the item content.\ntype ContentProvider interface {\n\tFetchContent(ctx context.Context, remotePath, destPath, wantHash string, logger *logrus.Logger) (bool, string, error)\n}\n\n// urlTo builds the URL to download a file from the remote hub.\nfunc (d *Downloader) urlTo(remotePath string) (*url.URL, error) {\n\t// the template must contain two string placeholders\n\tif fmt.Sprintf(d.URLTemplate, \"%s\", \"%s\") != d.URLTemplate {\n\t\treturn nil, fmt.Errorf(\"invalid URL template '%s'\", d.URLTemplate)\n\t}\n\n\traw := fmt.Sprintf(d.URLTemplate, d.Branch, remotePath)\n\n\tparsed, err := url.Parse(raw)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse URL: %w\", err)\n\t}\n\n\treturn parsed, nil\n}\n\n// FetchIndex downloads the index from the hub and writes it to the filesystem.\n// It uses a temporary file to avoid partial downloads, and won't overwrite the original\n// if it has not changed.\n// Return true if the file has been updated, false if already up to date.\nfunc (d *Downloader) FetchIndex(ctx context.Context, destPath string, withContent bool, logger *logrus.Logger) (downloaded bool, err error) {\n\turl, err := d.urlTo(\".index.json\")","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/crowdsecurity/crowdsec/blob/909b5157986a2b2c2163300fdaef5ed01289f7d2/pkg/cwhub/download.go#L21-L57","documentation":"The hub Downloader builds download URLs from a template (URLTemplate) that must contain exactly two '%s' placeholders, filled with the branch and the remote path. urlTo validates this by re-sprintfing with literal '%s' values; if the template doesn't have the two placeholders, it returns 'invalid URL template' and FetchIndex/FetchContent fail.","triggerScenarios":"Constructing a cwhub Downloader with a URLTemplate lacking two '%s' placeholders (e.g. only one placeholder, hard-coded branch, or using %v/%d instead of %s).","commonSituations":"Custom hub configuration (hub_url / api URL in config.yaml) pointing to a mirror whose URL format differs from the expected `<base>/<branch>/<path>` scheme; typos when overriding the hub URL; older config formats after an upgrade.","solutions":["Ensure the template contains exactly two %s placeholders, e.g. 'https://example.com/hub/%s/%s'.","Restore the default hub URL in config.yaml (api.server.hub_url) if you overrode it.","Verify the branch substitution is done by the downloader, not baked into the URL.","After fixing, run `cscli hub update` / `cscli hub upgrade` to re-fetch the index."],"exampleFix":"// before\nURLTemplate = \"https://hub.example.com/crowdsec/master/\" // hard-coded branch\n// after\nURLTemplate = \"https://hub.example.com/crowdsec/%s/%s\"","handlingStrategy":"validation","validationCode":"if strings.Count(template, \"%s\") != 2 {\n    return fmt.Errorf(\"hub URL template must contain exactly two %%s placeholders: %q\", template)\n}","typeGuard":null,"tryCatchPattern":"u, err := d.urlTo(remotePath)\nif err != nil {\n    return fmt.Errorf(\"cannot build hub download URL: %w\", err)\n}","preventionTips":["Keep the default hub URL format `<base>/%s/%s` when overriding hub_url","Never hard-code the branch into the template","Run `cscli hub update` after any hub URL change to validate early"],"tags":["hub","url","config"],"backgroundTag":"invalid-url-format","analyzedSha":"909b5157986a2b2c2163300fdaef5ed01289f7d2","analyzedAt":"2026-09-06T12:27:26.012Z","contentChangedAt":"2026-09-06T12:27:26.012Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}