tailscale/tailscale · error
package length must be positive, got %d
Error message
package length must be positive, got %d
What it means
distsign guards the signature input invariant: SignPackageHash(hash, len) builds the message hash||uint64(len) before Ed25519 signing, and refuses to sign anything with a non-positive length, because a zero- or negative-length package is meaningless and would produce a signature nothing can reproduce (the verifier always derives length from bytes written). The error prints the offending len.
Source
Thrown at clientupdate/distsign/distsign.go:150
Bytes: []byte(pub),
}), nil
}
// ParseSigningKey parses the PEM-encoded private signing key. The key must be
// in the same format as returned by GenerateSigningKey.
func ParseSigningKey(privKey []byte) (*SigningKey, error) {
k, err := parsePrivateKey(privKey, pemTypeSigningPrivate)
if err != nil {
return nil, fmt.Errorf("failed to parse root key: %w", err)
}
return &SigningKey{k: k}, nil
}
// SignPackageHash signs the hash and the length of a package. Use PackageHash
// to compute the inputs.
func (s *SigningKey) SignPackageHash(hash []byte, len int64) ([]byte, error) {
if len <= 0 {
return nil, fmt.Errorf("package length must be positive, got %d", len)
}
msg := binary.LittleEndian.AppendUint64(hash, uint64(len))
return ed25519.Sign(s.k, msg), nil
}
// PackageHash is a hash.Hash that counts the number of bytes written. Use it
// to get the hash and length inputs to SigningKey.SignPackageHash.
type PackageHash struct {
hash.Hash
len int64
}
// NewPackageHash returns an initialized PackageHash using BLAKE2s.
func NewPackageHash() *PackageHash {
h, err := blake2s.New256(nil)
if err != nil {
// Should never happen with a nil key passed to blake2s.
panic(err)View on GitHub (pinned to 6e0912f979)
Solutions
- Check the artifact is non-empty (stat its size) before signing — an empty file almost always means an upstream build step failed.
- Derive len exclusively from the same PackageHash used for the hash: h.Len() after io.Copy(h, file).
- If empty artifacts are legitimately possible in your flow, skip signing them explicitly rather than working around the guard.
- Audit call order: write data into PackageHash, then Sum() and Len(), then SignPackageHash.
Example fix
// before: signing before hashing, len never populated h := distsign.NewPackageHash() sig, err := sk.SignPackageHash(h.Sum(nil), 0) // package length must be positive, got 0 // after: hash the file first, then sign with the tracked length h := distsign.NewPackageHash() f, _ := os.Open(artifact) io.Copy(h, f) sig, err := sk.SignPackageHash(h.Sum(nil), h.Len())
Defensive patterns
Strategy: validation
Validate before calling
// guard before signing
fi, err := os.Stat(artifact)
if err != nil || fi.Size() <= 0 {
return fmt.Errorf("refusing to sign empty or missing artifact %s", artifact)
}
h := distsign.NewPackageHash()
io.Copy(h, mustOpen(artifact))
sig, err := sk.SignPackageHash(h.Sum(nil), h.Len()) Try / catch
sig, err := sk.SignPackageHash(hash, n)
if err != nil && strings.Contains(err.Error(), "package length must be positive") {
return fmt.Errorf("build produced a %d-byte artifact; fix the build step", n)
} Prevention
- Always source both hash and length from the same PackageHash instance after writing the file.
- Fail builds that emit zero-byte artifacts before they reach the signing step.
When it happens
Trigger: Signing an empty (zero-byte) artifact: PackageHash with nothing written yields Len()==0. Also passing a literal 0 or negative number instead of PackageHash().Len(), or computing the hash before writing the file contents into it.
Common situations: Build pipelines that sign placeholder/empty output files before the real artifact is produced; ordering bugs where SignPackageHash runs before the hash is fed; passing a byte-count variable that was never set.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- failed to parse root key: %w
- invalid pkgsAddr %q: %w
- signature %q for file %q does not validate with the current
- failed to move %q to %q after signature validation
- signature %q for key %q does not validate with any known roo
AI-assisted analysis of tailscale/tailscale@6e0912f979 (2026-08-18).
Data as JSON: /api/errors/3aa2436d3ae06be3.
Report an issue: GitHub.