crowdsecurity/crowdsec · error
invalid URL template '%s'
Error message
invalid URL template '%s'
What it means
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.
Source
Thrown at pkg/cwhub/download.go:39
Branch string
URLTemplate string
}
// IndexProvider retrieves and writes .index.json
type IndexProvider interface {
FetchIndex(ctx context.Context, indexFile string, withContent bool, logger *logrus.Logger) (bool, error)
}
// ContentProvider retrieves and writes the YAML files with the item content.
type ContentProvider interface {
FetchContent(ctx context.Context, remotePath, destPath, wantHash string, logger *logrus.Logger) (bool, string, error)
}
// urlTo builds the URL to download a file from the remote hub.
func (d *Downloader) urlTo(remotePath string) (*url.URL, error) {
// the template must contain two string placeholders
if fmt.Sprintf(d.URLTemplate, "%s", "%s") != d.URLTemplate {
return nil, fmt.Errorf("invalid URL template '%s'", d.URLTemplate)
}
raw := fmt.Sprintf(d.URLTemplate, d.Branch, remotePath)
parsed, err := url.Parse(raw)
if err != nil {
return nil, fmt.Errorf("failed to parse URL: %w", err)
}
return parsed, nil
}
// FetchIndex downloads the index from the hub and writes it to the filesystem.
// It uses a temporary file to avoid partial downloads, and won't overwrite the original
// if it has not changed.
// Return true if the file has been updated, false if already up to date.
func (d *Downloader) FetchIndex(ctx context.Context, destPath string, withContent bool, logger *logrus.Logger) (downloaded bool, err error) {
url, err := d.urlTo(".index.json")View on GitHub (pinned to 909b515798)
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.
Example fix
// before URLTemplate = "https://hub.example.com/crowdsec/master/" // hard-coded branch // after URLTemplate = "https://hub.example.com/crowdsec/%s/%s"
Defensive patterns
Strategy: validation
Validate before calling
if strings.Count(template, "%s") != 2 {
return fmt.Errorf("hub URL template must contain exactly two %%s placeholders: %q", template)
} Try / catch
u, err := d.urlTo(remotePath)
if err != nil {
return fmt.Errorf("cannot build hub download URL: %w", err)
} Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Understand the failure class
Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.
Related errors
- failed to build hub index request: %w
- failed to build request: %w
- empty loki host
- no hub configuration provided
- failed to parse DSN %s: %w
AI-assisted analysis of crowdsecurity/crowdsec@909b515798 (2026-09-06).
Data as JSON: /api/errors/8c8d597d0117e633.
Report an issue: GitHub.