{"record":{"id":"750453610c9d4dc6","repo":"slackhq/nebula","slug":"entry-v-in-tun-routes-is-invalid","errorCode":null,"errorMessage":"entry %v in tun.routes is invalid","messagePattern":"entry (.+?) in tun\\.routes is invalid","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"overlay/route.go","lineNumber":91,"sourceCode":"\tr := c.Get(\"tun.routes\")\n\tif r == nil {\n\t\treturn []Route{}, nil\n\t}\n\n\trawRoutes, ok := r.([]any)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"tun.routes is not an array\")\n\t}\n\n\tif len(rawRoutes) < 1 {\n\t\treturn []Route{}, nil\n\t}\n\n\troutes := make([]Route, len(rawRoutes))\n\tfor i, r := range rawRoutes {\n\t\tm, ok := r.(map[string]any)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"entry %v in tun.routes is invalid\", i+1)\n\t\t}\n\n\t\trMtu, ok := m[\"mtu\"]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"entry %v.mtu in tun.routes is not present\", i+1)\n\t\t}\n\n\t\tmtu, ok := rMtu.(int)\n\t\tif !ok {\n\t\t\tmtu, err = strconv.Atoi(rMtu.(string))\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"entry %v.mtu in tun.routes is not an integer: %v\", i+1, err)\n\t\t\t}\n\t\t}\n\n\t\tif mtu < 500 {\n\t\t\treturn nil, fmt.Errorf(\"entry %v.mtu in tun.routes is below 500: %v\", i+1, mtu)\n\t\t}","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/slackhq/nebula/blob/dd8f660c0ac37903ec4080ca4d3c861ba9342ceb/overlay/route.go#L73-L109","documentation":"One of the elements inside tun.routes is not an object (map). parseRoutes iterates each array entry and asserts it is map[string]any before reading its mtu/route keys; the 1-based entry index is reported. The library requires each route entry to be a structured object.","triggerScenarios":"Calling getAllRoutesFromConfig when tun.routes contains a bare string, number, or nested array instead of an object, e.g. tun.routes: [\"10.0.0.0/24\"] or mixed-type entries.","commonSituations":"Users familiar with other VPN tools writing routes as plain CIDR strings; templating errors that inject scalars into the list; copy-paste from documentation showing comma-separated route strings.","solutions":["Make every tun.routes entry an object with mtu and route keys","Convert string entries to objects: replace \"10.0.0.0/24\" with {mtu: 1300, route: \"10.0.0.0/24\"}","Use the reported entry index (entry %v) to locate the offending item in the array","Validate the config against the expected routes schema before applying"],"exampleFix":"// before\ntun:\n  routes:\n    - 10.0.0.0/24\n// after\ntun:\n  routes:\n    - mtu: 1300\n      route: 10.0.0.0/24","handlingStrategy":"validation","validationCode":"for i, e := range routes.([]any) {\n    if _, ok := e.(map[string]any); !ok {\n        return fmt.Errorf(\"tun.routes entry %d must be an object with mtu and route\", i+1)\n    }\n}","typeGuard":"func isRouteEntry(v any) bool {\n    _, ok := v.(map[string]any)\n    return ok\n}","tryCatchPattern":null,"preventionTips":["Never put bare CIDR strings in tun.routes","Each entry must be {mtu: int, route: cidr}","Validate array element types with a schema linter","Use the 1-based entry index in the error to find the bad item"],"tags":["config","validation"],"backgroundTag":"schema-validation-failed","analyzedSha":"dd8f660c0ac37903ec4080ca4d3c861ba9342ceb","analyzedAt":"2026-09-03T11:13:55.444Z","contentChangedAt":"2026-09-03T11:13:55.444Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}