siyuan-note/siyuan · error

invalid encrypted asset original name

Error message

invalid encrypted asset original name

What it means

decryptAssetMetadata validates metadata.OriginalName: it must be non-empty, not ".", equal to its own filepath.Base (no directory components), and contain no '/' or '\\'. This prevents path traversal and directory escapes when the original name is restored to disk.

Source

Thrown at kernel/model/crypto.go:2348

	if err := json.Unmarshal(data, metadata); err != nil {
		return nil, err
	}
	var version struct {
		Spec        json.RawMessage `json:"spec"`
		ContainerID json.RawMessage `json:"containerID"`
	}
	if err := json.Unmarshal(data, &version); err != nil {
		return nil, err
	}
	// 仅认证元数据同时缺少两个版本字段时按旧容器读取,显式空值或不完整的新格式不能降级。
	if len(version.Spec) == 0 && len(version.ContainerID) == 0 {
		metadata.Spec = encryptedAssetLegacySpec
	} else if metadata.Spec != encryptedAssetSpec || len(metadata.ContainerID) != encryptedAssetContainerIDSize {
		return nil, errors.New("unsupported encrypted asset container version")
	}
	if metadata.OriginalName == "" || metadata.OriginalName == "." ||
		filepath.Base(metadata.OriginalName) != metadata.OriginalName || strings.ContainsAny(metadata.OriginalName, `/\`) {
		return nil, errors.New("invalid encrypted asset original name")
	}
	if metadata.Size < 0 {
		return nil, errors.New("invalid encrypted asset content metadata")
	}
	chunks := uint64(metadata.Size) / encryptedAssetChunkSize
	if metadata.Size%encryptedAssetChunkSize != 0 || metadata.Size == 0 {
		chunks++
	}
	if metadata.Chunks != chunks {
		return nil, errors.New("invalid encrypted asset chunk count")
	}
	return metadata, nil
}

// DecryptAssetWithName 解密资源内容并返回原始名称。
func DecryptAssetWithName(boxID, diskName string, dek, ciphertext []byte) (plaintext []byte, originalName string, err error) {
	var output bytes.Buffer
	originalName, err = DecryptAssetToWriter(boxID, diskName, dek, bytes.NewReader(ciphertext), &output)

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Re-encrypt the asset with a sanitized original name (base name only, no separators)
  2. If the container is untrusted, do not decrypt-and-restore it; treat it as hostile input
  3. Check the encryption-side code path that built encryptedAssetMetadata to ensure it passes filepath.Base of the real name
  4. Restore the asset from a trusted backup if metadata was corrupted

Example fix

// before: encrypt with a user-supplied path as name
meta.OriginalName = userPath
// after: store only the base name
meta.OriginalName = filepath.Base(userPath)
Defensive patterns

Strategy: validation

Validate before calling

func safeAssetName(name string) bool {
    return name != "" && name != "." && filepath.Base(name) == name && !strings.ContainsAny(name, `/\`)
}

Try / catch

if err != nil && strings.Contains(err.Error(), "original name") {
    // reject the container as untrusted/tampered
}

Prevention

When it happens

Trigger: Decrypting an asset whose metadata original name was crafted with '../', absolute paths, embedded separators, or is empty — typically a tampered container or a bug in code that populated metadata before encryption.

Common situations: Assets imported by third-party tooling that wrote unsafe names into metadata, malicious containers crafted to overwrite files outside the assets dir, corruption flipping bytes in the name field.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/dcb5d4cdc77b5b79. Report an issue: GitHub.