{"record":{"id":"91805716d0ebd700","repo":"netbirdio/netbird","slug":"failed-to-verify-signature-of-artifact-keys","errorCode":null,"errorMessage":"failed to verify signature of artifact keys","messagePattern":"failed to verify signature of artifact keys","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"client/internal/updater/reposign/artifact.go","lineNumber":185,"sourceCode":"\tnow := time.Now().UTC()\n\tif signature.Timestamp.After(now.Add(maxClockSkew)) {\n\t\terr := fmt.Errorf(\"signature timestamp is in the future: %v\", signature.Timestamp)\n\t\tlog.Debugf(\"artifact signature error: %v\", err)\n\t\treturn nil, err\n\t}\n\tif now.Sub(signature.Timestamp) > maxArtifactKeySignatureAge {\n\t\terr := fmt.Errorf(\"signature is too old: %v (created %v)\", now.Sub(signature.Timestamp), signature.Timestamp)\n\t\tlog.Debugf(\"artifact signature error: %v\", err)\n\t\treturn nil, err\n\t}\n\n\t// Reconstruct the signed message: artifact_key_data || timestamp\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(\"failed to verify signature of artifact keys\")\n\t}\n\n\tpubKeys, err := parsePublicKeyBundle(data, tagArtifactPublic)\n\tif err != nil {\n\t\tlog.Debugf(\"failed to parse public keys: %s\", err)\n\t\treturn nil, err\n\t}\n\n\tvalidKeys := make([]PublicKey, 0, len(pubKeys))\n\tfor _, pubKey := range pubKeys {\n\t\t// Filter out expired keys\n\t\tif !pubKey.Metadata.ExpiresAt.IsZero() && now.After(pubKey.Metadata.ExpiresAt) {\n\t\t\tlog.Debugf(\"Key %s is expired at %v (current time %v)\",\n\t\t\t\tpubKey.Metadata.ID, pubKey.Metadata.ExpiresAt, now)\n\t\t\tcontinue\n\t\t}\n\n\t\tif revocationList != nil {","sourceCodeStart":167,"sourceCodeEnd":203,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/internal/updater/reposign/artifact.go#L167-L203","documentation":"Returned by the reposign artifact-key verification path (client/internal/updater/reposign/artifact.go:185) when verifyAny fails to match the bundle signature against any of the trusted public root keys embedded in the client. This is the update supply-chain trust check: the artifact key bundle (data || little-endian timestamp) must verify against at least one root key, otherwise the artifact keys are rejected. The check fails closed by design.","triggerScenarios":"Artifact keys were signed by a root key that postdates the root keys baked into this client build (key rotation); the downloaded keys file is corrupted or truncated so the signed bytes differ; the bundle was tampered with (MITM or compromised mirror); clock skew combined with the maxArtifactKeySignatureAge check is handled separately, so reaching this line means the signature itself is invalid.","commonSituations":"Running an old NetBird release after the project rotated its signing root keys; a proxied or cached download corrupting the artifact; an attacker-controlled update source. Any occurrence means the update must not proceed.","solutions":["Update the NetBird client to the latest release, which embeds the current set of trusted root keys.","Re-download the update artifact from the official source and retry, to rule out corruption.","If it persists on the latest client, treat it as a potential compromise: do not bypass the check, report it to the NetBird maintainers via the security policy."],"exampleFix":"// before: catching the error and continuing with the update anyway\nif _, err := reposign.VerifyArtifactKeys(data, sig); err != nil {\n    log.Warnf(\"signature check failed, continuing: %v\", err) // NEVER do this\n}\n\n// after: fail closed, abort the update\nif _, err := reposign.VerifyArtifactKeys(data, sig); err != nil {\n    return fmt.Errorf(\"refusing update, artifact key signature invalid: %w\", err)\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"keys, err := reposign.VerifyArtifactKeys(data, sig, now)\nif err != nil {\n    // Fail closed: abort the update. Never downgrade this to a warning.\n    return fmt.Errorf(\"update aborted, artifact keys untrusted: %w\", err)\n}","preventionTips":["Keep the client up to date so its embedded root keys match current signings.","Never wrap signature failures in retry-with-ignore logic or cache bypasses.","Download artifacts only from official sources over TLS."],"tags":["go","netbird","security","supply-chain","signature","update"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}