{"record":{"id":"d173be7d0ecab4ef","repo":"tailscale/tailscale","slug":"no-signing-keys-found-in-the-bundle","errorCode":null,"errorMessage":"no signing keys found in the bundle","messagePattern":"no signing keys found in the bundle","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"clientupdate/distsign/distsign.go","lineNumber":437,"sourceCode":"}\n\n// ParseRootKeyBundle parses the bundle of PEM-encoded public root keys.\nfunc ParseRootKeyBundle(bundle []byte) ([]ed25519.PublicKey, error) {\n\treturn parsePublicKeyBundle(bundle, pemTypeRootPublic)\n}\n\nfunc parsePublicKeyBundle(bundle []byte, typeTag string) ([]ed25519.PublicKey, error) {\n\tvar keys []ed25519.PublicKey\n\tfor len(bundle) > 0 {\n\t\tpub, rest, err := parsePublicKey(bundle, typeTag)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tkeys = append(keys, pub)\n\t\tbundle = rest\n\t}\n\tif len(keys) == 0 {\n\t\treturn nil, errors.New(\"no signing keys found in the bundle\")\n\t}\n\treturn keys, nil\n}\n\nfunc parseSinglePublicKey(data []byte, typeTag string) (ed25519.PublicKey, error) {\n\tpub, rest, err := parsePublicKey(data, typeTag)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(rest) > 0 {\n\t\treturn nil, errors.New(\"trailing PEM data\")\n\t}\n\treturn pub, err\n}\n\nfunc parsePublicKey(data []byte, typeTag string) (pub ed25519.PublicKey, rest []byte, retErr error) {\n\tb, rest := pem.Decode(data)\n\tif b == nil {","sourceCodeStart":419,"sourceCodeEnd":455,"githubUrl":"https://github.com/tailscale/tailscale/blob/cfe32b8be6a33f8e24fbc369cbfbf7c729d9e042/clientupdate/distsign/distsign.go#L419-L455","documentation":"Thrown by parsePublicKeyBundle when the input bundle contains zero PEM blocks of the expected type. The distsign package (used by tailscale update to verify downloadable binaries) expects a concatenation of PEM-encoded Ed25519 public keys, e.g. the distsign.pub bundle fetched from the download server or validated by RootKey.SignSigningKeys. An empty or whitespace-only input parses to zero keys and is rejected because there would be nothing to trust.","triggerScenarios":"Calling distsign.ParseSigningKeyBundle / ParseRootKeyBundle (or RootKey.SignSigningKeys, which internally validates the bundle) with an empty []byte or data containing no PEM blocks; serving an empty distsign.pub file from the download server that the client fetches before verifying a download.","commonSituations":"Download server returns HTTP 200 with an empty body for the key-bundle URL; the bundle file was truncated or created empty during a release-pipeline failure; a wrong URL/path serves blank content; CI passes an unset environment variable holding the bundle.","solutions":["Print/inspect the bytes passed to the parser (len and first lines) to confirm the bundle is empty","Fix the source: point the client at the correct distsign.pub URL or regenerate the bundle by concatenating the PEM outputs of GenerateSigningKey for each signing key","If publishing manually, verify the file on the server is non-empty and starts with '-----BEGIN' before shipping","Re-run the download/verification once the server serves a valid bundle"],"exampleFix":"// before\nbundle, err := os.ReadFile(\"distsign.pub\")\nif err != nil { return err }\nkeys, err := distsign.ParseSigningKeyBundle(bundle) // errors: no signing keys found\n\n// after\nbundle, err := os.ReadFile(\"distsign.pub\")\nif err != nil { return err }\nif len(bytes.TrimSpace(bundle)) == 0 {\n    return fmt.Errorf(\"distsign.pub is empty; rebuild/republish the key bundle\")\n}\nkeys, err := distsign.ParseSigningKeyBundle(bundle)","handlingStrategy":"validation","validationCode":"// Pre-check a key bundle before handing it to distsign.\nfunc validBundle(bundle []byte, tag string) bool {\n    rest := bundle\n    n := 0\n    for {\n        var b *pem.Block\n        b, rest = pem.Decode(rest)\n        if b == nil {\n            return n > 0 && len(rest) == 0\n        }\n        if b.Type != tag {\n            return false\n        }\n        n++\n    }\n}","typeGuard":null,"tryCatchPattern":"keys, err := distsign.ParseSigningKeyBundle(data)\nif err != nil {\n    if strings.Contains(err.Error(), \"no signing keys found\") {\n        // empty/blank bundle: fix the source file or URL, do not retry blindly\n    }\n    return fmt.Errorf(\"parsing signing key bundle: %w\", err)\n}","preventionTips":["Never serve key bundles from endpoints that can return 200 with an empty body; fail HTTP >= 400 explicitly","Add a CI step asserting distsign.pub is non-empty and contains at least one BEGIN/END block before publish","Log bundle length and a hash alongside parse errors to distinguish empty vs corrupt input"],"tags":["distsign","pem","ed25519","key-bundle","supply-chain"],"backgroundTag":null,"analyzedSha":"cfe32b8be6a33f8e24fbc369cbfbf7c729d9e042","analyzedAt":"2026-08-15T19:58:31.583Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}