netbirdio/netbird · critical
artifact verification failed: %w
Error message
artifact verification failed: %w
What it means
reposign.ValidateArtifact rejected the artifact; this is the security decision of verify-artifact and must never be bypassed. Concrete refusals in reposign/artifact.go:221-265: signature timestamp more than 5 minutes in the future (clock skew), signature older than 10 years, the signing key had expired at signature time, Ed25519 verification of the BLAKE2s hash/length/timestamp message failed (artifact bytes changed or wrong key), or no key with the signature's KeyID is in the supplied key list.
Source
Thrown at client/cmd/signer/artifactsign.go:192
artifactData, err := os.ReadFile(artifactFile)
if err != nil {
return fmt.Errorf("read artifact file: %w", err)
}
// Read signature
sigBytes, err := os.ReadFile(signatureFile)
if err != nil {
return fmt.Errorf("read signature file: %w", err)
}
signature, err := reposign.ParseSignature(sigBytes)
if err != nil {
return fmt.Errorf("failed to parse signature: %w", err)
}
// Validate artifact
if err := reposign.ValidateArtifact([]reposign.PublicKey{publicKey}, artifactData, *signature); err != nil {
return fmt.Errorf("artifact verification failed: %w", err)
}
cmd.Println("✅ Artifact signature is valid")
cmd.Printf("Artifact: %s\n", artifactFile)
cmd.Printf("Signed by key: %s\n", signature.KeyID)
cmd.Printf("Signature timestamp: %s\n", signature.Timestamp.Format("2006-01-02 15:04:05 MST"))
return nil
}
func handleVerifyArtifactKey(cmd *cobra.Command, artifactKeyFile, rootKeyFile, signatureFile, revocationFile string) error {
cmd.Println("🔍 Verifying artifact key...")
// Read artifact key data
artifactKeyData, err := os.ReadFile(artifactKeyFile)
if err != nil {
return fmt.Errorf("read artifact key file: %w", err)
}
View on GitHub (pinned to 93e97f4bf1)
Solutions
- Confirm the artifact is byte-identical to what was signed: compare sha256sum against the value at signing time
- Check the key pair matches: the KeyID printed on successful signing must equal the ID embedded in --artifact-public-key-file
- Sync the system clock (max allowed skew is 5 minutes) and re-verify
- If the signing key expired, re-sign with a current artifact key and republish
Defensive patterns
Strategy: try-catch
Try / catch
if err := reposign.ValidateArtifact([]reposign.PublicKey{publicKey}, artifactData, *signature); err != nil {
switch {
case strings.Contains(err.Error(), "no signing Key found"):
log.Fatalf("key file does not match signer KeyID %s", signature.KeyID)
case strings.Contains(err.Error(), "timestamp is in the future"), strings.Contains(err.Error(), "too old"):
log.Fatalf("clock/timestamp problem: %v", err)
default:
log.Fatalf("VERIFICATION FAILED, do not ship: %v", err)
}
} Prevention
- Treat any verification failure as fatal; never fall back to shipping unverified artifacts
- Log the artifact sha256 and signature KeyID at signing time so mismatches are diagnosable
- Keep verifier clocks NTP-synced (5-minute skew budget)
- Rotate artifact keys before their ExpiresAt and republish the root-signed bundle
When it happens
Trigger: Artifact rebuilt but not re-signed (hash mismatch); --artifact-public-key-file is a different key than the signer used ("no signing Key found with ID ..."); host clock more than 5 minutes fast; signing key expired between signing and verifying; artifact or .sig truncated during transfer.
Common situations: Release pipeline re-builds the binary after signing; verifier uses an old/new key rotation mismatch; VM clock drift in CI; partially downloaded artifact.
Related errors
- artifact key verification failed: %w
- failed to parse artifact private key: %w
- sign artifact: %w
- failed to parse artifact public key: %w
- failed to parse root public key(s): %w
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/4f0e449b18c71e9f.
Report an issue: GitHub.