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

  1. Check the artifact is non-empty (stat its size) before signing — an empty file almost always means an upstream build step failed.
  2. Derive len exclusively from the same PackageHash used for the hash: h.Len() after io.Copy(h, file).
  3. If empty artifacts are legitimately possible in your flow, skip signing them explicitly rather than working around the guard.
  4. 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

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


AI-assisted analysis of tailscale/tailscale@6e0912f979 (2026-08-18). Data as JSON: /api/errors/3aa2436d3ae06be3. Report an issue: GitHub.