shadow1ng/fscan · error

fscan: target cannot set both Host and URL

Error message

fscan: target cannot set both Host and URL

What it means

validateConfig in pkg/fscan/scanner.go enforces that each scan Target identifies itself either by Host or by URL, never both. Setting both is ambiguous (the scanner cannot decide which form to normalize into fscan's flag variables), so the config is rejected before any scan starts. This is a fail-fast precondition check run by ValidateConfig and scanEach.

Source

Thrown at pkg/fscan/scanner.go:350

func validateConfig(config Config, targets []Target) error {
	if len(targets) == 0 {
		return fmt.Errorf("fscan: at least one target is required")
	}
	for _, name := range normalizePlugins(config.Plugins) {
		if !plugins.Exists(name) {
			return fmt.Errorf("fscan: plugin %q not found", name)
		}
		if !config.AllowUnsafePlugins && !IsSafePlugin(name) {
			return fmt.Errorf("fscan: plugin %q is not enabled for embedded safe mode", name)
		}
	}
	for _, target := range targets {
		if strings.TrimSpace(target.Host) == "" && strings.TrimSpace(target.URL) == "" {
			return fmt.Errorf("fscan: target host or URL is required")
		}
		if strings.TrimSpace(target.Host) != "" && strings.TrimSpace(target.URL) != "" {
			return fmt.Errorf("fscan: target cannot set both Host and URL")
		}
		for _, port := range target.Ports {
			if port < 1 || port > 65535 {
				return fmt.Errorf("fscan: invalid port %d", port)
			}
		}
	}
	for _, port := range config.Ports {
		if port < 1 || port > 65535 {
			return fmt.Errorf("fscan: invalid port %d", port)
		}
	}
	return nil
}

func buildFlagVars(config Config, target Target) *common.FlagVars {
	timeout := secondsOrDefault(config.Timeout, common.DefaultTimeout)
	webTimeout := secondsOrDefault(config.WebTimeout, 5)

View on GitHub (pinned to 95cc12e753)

Solutions

  1. Set only Host (with Ports) for raw host/port scanning; clear URL.
  2. Set only URL when scanning an HTTP endpoint; clear Host and let the scanner parse it.
  3. Add a pre-submit check in your tooling that rejects Targets with both fields set.
  4. If you truly need both host and URL info, derive the URL from Host at call time instead of passing both.

Example fix

// before
targets := []fscan.Target{{Host: "10.0.0.5", URL: "http://10.0.0.5:8080"}}
// after
targets := []fscan.Target{{URL: "http://10.0.0.5:8080"}}
// or for port scanning:
targets := []fscan.Target{{Host: "10.0.0.5", Ports: []int{80, 8080}}}
Defensive patterns

Strategy: validation

Validate before calling

for i, t := range targets {
    if strings.TrimSpace(t.Host) != "" && strings.TrimSpace(t.URL) != "" {
        return fmt.Errorf("target[%d]: set either Host or URL, not both", i)
    }
}

Type guard

func hasAmbiguousTarget(t fscan.Target) bool {
    return strings.TrimSpace(t.Host) != "" && strings.TrimSpace(t.URL) != ""
}

Try / catch

if err := fscan.ValidateConfig(cfg); err != nil {
    if strings.Contains(err.Error(), "cannot set both Host and URL") {
        // fix target definitions before retry
    }
    return err
}

Prevention

When it happens

Trigger: Calling ValidateConfig(config) or Scan/scanEach where targets contains a Target whose Host is a non-empty string AND whose URL is a non-empty string simultaneously.

Common situations: Populating Target structs from YAML/JSON where both fields were copied from a template; programmatically building targets from a host inventory that also recorded a scheme'd URL; reusing a struct filled by a previous code path that set URL and then adding Host for a port sweep.

Related errors


AI-assisted analysis of shadow1ng/fscan@95cc12e753 (2026-09-06). Data as JSON: /api/errors/8fb9b2c7ec82f981. Report an issue: GitHub.