AlistGo/alist · error

failed to obfuscate salt: %w

Error message

failed to obfuscate salt: %w

What it means

Initialization error in the Crypt driver, identical in mechanism to the password case: Init calls updateObfusParm on the Salt field, which runs rclone's obscure.Obscure on the plaintext value. rclone rejects obscuring an empty string, so an empty (non-obfuscated-prefixed) salt makes Init fail with this wrapped message before the storage becomes usable.

Source

Thrown at drivers/crypt/driver.go:52

const obfuscatedPrefix = "___Obfuscated___"

func (d *Crypt) Config() driver.Config {
	return config
}

func (d *Crypt) GetAddition() driver.Additional {
	return &d.Addition
}

func (d *Crypt) Init(ctx context.Context) error {
	//obfuscate credentials if it's updated or just created
	err := d.updateObfusParm(&d.Password)
	if err != nil {
		return fmt.Errorf("failed to obfuscate password: %w", err)
	}
	err = d.updateObfusParm(&d.Salt)
	if err != nil {
		return fmt.Errorf("failed to obfuscate salt: %w", err)
	}

	isCryptExt := regexp.MustCompile(`^[.][A-Za-z0-9-_]{2,}$`).MatchString
	if !isCryptExt(d.EncryptedSuffix) {
		return fmt.Errorf("EncryptedSuffix is Illegal")
	}
	d.FileNameEncoding = utils.GetNoneEmpty(d.FileNameEncoding, "base64")
	d.EncryptedSuffix = utils.GetNoneEmpty(d.EncryptedSuffix, ".bin")

	op.MustSaveDriverStorage(d)

	//need remote storage exist
	storage, err := fs.GetStorage(d.RemotePath, &fs.GetStoragesArgs{})
	if err != nil {
		return fmt.Errorf("can't find remote storage: %w", err)
	}
	d.remoteStorage = storage

View on GitHub (pinned to 843d9dc814)

Solutions

  1. Set a non-empty salt (a random string is typical; it is not secret, only needs to be stable so derived name encryption stays consistent)
  2. Keep the salt identical across re-initializations, or encrypted filenames from before will become undecryptable
  3. Once set, it is stored obfuscated and later Inits skip the failing branch

Example fix

// before
{
  "password": "pass",
  "salt": ""
}

// after
{
  "password": "pass",
  "salt": "aStableSaltValue"
}
Defensive patterns

Strategy: validation

Validate before calling

// before saving: salt must be non-empty (or already obfuscated)
if !strings.HasPrefix(cfg.Salt, "___Obfuscated___") && strings.TrimSpace(cfg.Salt) == "" {
    return errors.New("crypt storage requires a non-empty salt")
}

Type guard

func hasUsableCryptSalt(v string) bool {
    return strings.HasPrefix(v, "___Obfuscated___") || strings.TrimSpace(v) != ""
}

Try / catch

if err := cryptStorage.Init(ctx); err != nil {
    if strings.Contains(err.Error(), "failed to obfuscate salt") {
        return errors.New("crypt salt is empty; set a stable non-empty salt")
    }
}

Prevention

When it happens

Trigger: Creating or updating a Crypt storage with an empty salt field; the salt does not already start with ___Obfuscated___, so the obfuscation path runs and fails on empty input.

Common situations: Optional-looking salt field left blank during setup (note: unlike the suffix fields, no default is filled in before this call); clearing the salt during reconfiguration; scripted provisioning that writes an empty salt.

Related errors


AI-assisted analysis of AlistGo/alist@843d9dc814 (2026-08-15). Data as JSON: /api/errors/12bf64c2eb544e5a. Report an issue: GitHub.