OpenNHP/opennhp · error

failed to parse ztdo header

Error message

failed to parse ztdo header:%s

What it means

ztdo.ParseHeader failed on the downloaded file, meaning the file at ztdoPath is not a valid ztdo container or its header is corrupt/unrecognized. ParseHeader validates the ztdo magic/header fields (version, ECC mode, object id, etc.) before any decryption happens.

Solutions

  1. Inspect the downloaded temp file (file type, first bytes) — if it is HTML/XML it is an error page, not data.
  2. Re-download the ztdo to rule out truncation; verify Content-Length matches file size.
  3. Confirm provider and agent use compatible ztdo format versions (upgrade endpoints together).
  4. Re-encrypt and re-upload the object at the provider if the stored blob is corrupt.
  5. Check proxy/middlebox stripping or altering the response body.

Example fix

// before: header parsed blind
if parseErr := ztdo.ParseHeader(ztdoPath); parseErr != nil {
    return "", fmt.Errorf("failed to parse ztdo header:%s", parseErr)
}

// after: sanity-check size first
if fi, err := os.Stat(ztdoPath); err != nil || fi.Size() < minZtdoHeaderSize {
    return "", fmt.Errorf("downloaded file too small/corrupt, not a valid ztdo")
}
if parseErr := ztdo.ParseHeader(ztdoPath); parseErr != nil {
    return "", fmt.Errorf("failed to parse ztdo header:%s", parseErr)
}
Defensive patterns

Strategy: type-guard

Validate before calling

fi, err := os.Stat(ztdoPath)
if err != nil || fi.Size() < minZtdoHeaderSize {
    return fmt.Errorf("downloaded payload is not a valid ztdo container")
}

Type guard

func looksLikeZtdo(path string) bool {
    f, err := os.Open(path); if err != nil { return false }
    defer f.Close()
    magic := make([]byte, 4)
    _, _ = io.ReadFull(f, magic)
    return bytes.Equal(magic, ztdoMagicBytes)
}

Try / catch

if err := ztdo.ParseHeader(ztdoPath); err != nil {
    // quarantine file, re-download once, then report
    return fmt.Errorf("invalid ztdo payload from provider: %w", err)
}

Prevention

When it happens

Trigger: The URL served an error page (HTML/JSON) or truncated file instead of a ztdo binary; the file was modified/corrupted in transit or in storage; the provider wrote an incompatible ztdo version header.

Common situations: Pre-signed URL returning an XML S3 error document; partial download due to interrupted connection that the downloader didn't surface; provider upgraded the ztdo format while the agent ships an older parser; manual tampering with the stored object.

Understand the failure class

Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.

Related errors


AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07). Data as JSON: /api/errors/7749ba291baed2de. Report an issue: GitHub.

Appendix: source

Thrown at endpoints/agent/udpagent.go:1379

			}

			providerPbk, _ := base64.StdEncoding.DecodeString(dataPrkWrapping.ProviderPublicKeyBase64)

			if dagMsg.AccessUrl == "" {
				log.Error("access url is empty, please check with data provider")
				return "", fmt.Errorf("access url is empty, please check with data provider")
			}

			var err error
			ztdoPath, err := utils.DownloadFileToTemp(dagMsg.AccessUrl, "ztdo-")
			if err != nil {
				log.Error("failed to download ztdo: %v\n", err)
				return "", fmt.Errorf("failed to download ztdo: %v", err)
			}

			if parseErr := ztdo.ParseHeader(ztdoPath); parseErr != nil {
				fmt.Printf("Error: failed to parse ztdo header:%s\n", parseErr)
				return "", fmt.Errorf("failed to parse ztdo header:%s", parseErr)
			}

			if ztdoId != ztdo.GetObjectID() {
				fmt.Printf("Error: ztdo id mismatch, please check with data provider\n")
				return "", fmt.Errorf("ztdo id mismatch, please check with data provider")
			}

			// decrypt data private key
			saDataPrk := ztdolib.NewSymmetricAgreement(ztdo.GetECCMode(), false)
			saDataPrk.SetMessagePatterns(ztdolib.DataPrivateKeyWrappingPatterns)
			saDataPrk.SetPsk([]byte(ztdolib.InitialDHPKeyWrappingString))
			saDataPrk.SetStaticKeyPair(teeEcdh)
			saDataPrk.SetEphemeralKeyPair(consumerEphemeralEcdh)
			saDataPrk.SetRemoteStaticPublicKey(providerPbk)

			gcmKey, ad := saDataPrk.AgreeSymmetricKey()

			dataPrkBase64, err := dataPrkWrapping.Unwrap(gcmKey[:], ad)

View on GitHub (pinned to 6e04ca5ff0)