{"record":{"id":"841553d1a7c0d0de","repo":"netbirdio/netbird","slug":"revocation-list-verification-failed","errorCode":null,"errorMessage":"revocation list verification failed","messagePattern":"revocation list verification failed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"client/internal/updater/reposign/revocation.go","lineNumber":150,"sourceCode":"\t}\n\n\t// Validate signature timestamp is close to LastUpdated\n\t// (prevents signing old lists with new timestamps)\n\ttimeDiff := signature.Timestamp.Sub(revoList.LastUpdated).Abs()\n\tif timeDiff > maxClockSkew {\n\t\terr := fmt.Errorf(\"signature timestamp %v differs too much from list LastUpdated %v (diff: %v)\",\n\t\t\tsignature.Timestamp, revoList.LastUpdated, timeDiff)\n\t\tlog.Errorf(\"timestamp mismatch in revocation list: %v\", err)\n\t\treturn nil, err\n\t}\n\n\t// Reconstruct the signed message: revocation_list_data || timestamp || version\n\tmsg := make([]byte, 0, len(data)+8)\n\tmsg = append(msg, data...)\n\tmsg = binary.LittleEndian.AppendUint64(msg, uint64(signature.Timestamp.Unix()))\n\n\tif !verifyAny(publicRootKeys, msg, signature.Signature) {\n\t\treturn nil, errors.New(\"revocation list verification failed\")\n\t}\n\treturn revoList, nil\n}\n\nfunc CreateRevocationList(privateRootKey RootKey, expiration time.Duration) ([]byte, []byte, error) {\n\tnow := time.Now()\n\trl := RevocationList{\n\t\tRevoked:     make(map[KeyID]time.Time),\n\t\tLastUpdated: now.UTC(),\n\t\tExpiresAt:   now.Add(expiration).UTC(),\n\t}\n\n\tsignature, err := signRevocationList(privateRootKey, rl)\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"failed to sign revocation list: %w\", err)\n\t}\n\n\trlData, err := json.Marshal(&rl)","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/internal/updater/reposign/revocation.go#L132-L168","documentation":"Returned by the revocation-list verification path (client/internal/updater/reposign/revocation.go:150) when verifyAny fails to verify the reconstructed signed message (revocation list data || little-endian timestamp) against any trusted public root key. The revocation list is what rejects compromised updater keys, so an unverifiable list is rejected outright rather than treated as empty; this check fails closed by design.","triggerScenarios":"The revocation list was signed by a rotated root key unknown to this client build; the list file is corrupted or truncated so the signed bytes differ; the list was tampered with (attacker stripping revocations). Note the timestamp-vs-LastUpdated skew check runs earlier, so reaching this line means the signature itself does not verify.","commonSituations":"Old client builds after root-key rotation; cached/stale revocation list from a mirror; intercepted or corrupted downloads. Because accepting an unverified list would let revoked keys through, the updater refuses to continue with it.","solutions":["Update the NetBird client to the latest release so its embedded root keys can verify the current revocation list.","Clear any cached revocation list artifact and re-download from the official source, then retry.","If it persists, do not skip revocation checking; report it to the NetBird maintainers via the security policy."],"exampleFix":"// before: ignoring revocation verification failure and proceeding\nlist, err := reposign.VerifyRevocationList(data, sig)\nif err != nil {\n    log.Warnf(\"revocation check failed, assuming no revocations\") // NEVER\n}\n\n// after: fail closed\nlist, err := reposign.VerifyRevocationList(data, sig)\nif err != nil {\n    return fmt.Errorf(\"refusing update, revocation list unverifiable: %w\", err)\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"list, err := reposign.VerifyRevocationList(data, sig)\nif err != nil {\n    // Fail closed: an unverifiable revocation list must not be treated as\n    // 'no revocations'. Abort the update and surface the error.\n    return fmt.Errorf(\"update aborted, revocation list untrusted: %w\", err)\n}","preventionTips":["Update the agent promptly after root-key rotations so revocation lists verify.","Never skip revocation checks on failure or fall back to an empty list.","Clear caches only from trusted sources; do not hand-edit cached list files."],"tags":["go","netbird","security","supply-chain","signature","revocation","update"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}