moonD4rk/HackBrowserData · error

keychain: empty secret for %s

Error message

keychain: empty secret for %s

What it means

After `security find-generic-password` succeeds with no stderr, the library reads the printed password from stdout. If stdout is empty (or whitespace only), there is no safe-storage secret to derive the V10 key from, so it fails naming the storage label that had no secret.

Source

Thrown at masterkey/retriever_darwin.go:156

	if err := cmd.Run(); err != nil {
		if errors.Is(ctx.Err(), context.DeadlineExceeded) {
			return nil, fmt.Errorf("security command timed out after %s", securityCmdTimeout)
		}
		// `security` exits non-zero with empty stderr when the user denies the prompt or mistypes;
		// surface that instead of the cryptic "exit status 128 ()".
		stderrStr := strings.TrimSpace(stderr.String())
		if stderrStr == "" {
			return nil, fmt.Errorf("security command: %w (likely keychain access denied or wrong password)", err)
		}
		return nil, fmt.Errorf("security command: %w (%s)", err, stderrStr)
	}
	if stderr.Len() > 0 {
		return nil, fmt.Errorf("keychain: %s", strings.TrimSpace(stderr.String()))
	}

	secret := bytes.TrimSpace(stdout.Bytes())
	if len(secret) == 0 {
		return nil, fmt.Errorf("keychain: empty secret for %s", storage)
	}

	return darwinParams.deriveKey(secret), nil
}

// DefaultRetrievers wires the macOS V10 chain (the only tier Chromium uses here), first success wins:
//  1. GcoredumpRetriever        — CVE-2025-24204 exploit (root only)
//  2. KeychainPasswordRetriever — direct unlock, skipped when password is empty
//  3. SecurityCmdRetriever      — `security` CLI fallback (may prompt)
func DefaultRetrievers(keychainPassword string) Retrievers {
	chain := []Retriever{&GcoredumpRetriever{}}
	if keychainPassword != "" {
		chain = append(chain, &KeychainPasswordRetriever{Password: keychainPassword})
	}
	chain = append(chain, &SecurityCmdRetriever{cache: make(map[string]securityResult)})
	return Retrievers{V10: NewChain(chain...)}
}

View on GitHub (pinned to 0503d04d7a)

Solutions

  1. Open Keychain Access, find the '<Browser> Safe Storage' item, and confirm its password attribute is non-empty (click 'Show password')
  2. Re-save the secret: use the browser once (visit a site, save a password/cookie) so Chromium repopulates Safe Storage, then retry
  3. Manually create the entry: security add-generic-password -a 'Chrome Safe Storage' -s 'Chrome Safe Storage' -w '<secret>'
  4. Rely on a different tier: supply the login keychain password (KeychainPasswordRetriever) or run as root (GcoredumpRetriever)
Defensive patterns

Strategy: validation

Validate before calling

out, err := exec.Command("security", "find-generic-password", "-wa", "Chrome Safe Storage").Output()
if err == nil && len(bytes.TrimSpace(out)) == 0 {
    // empty secret — Safe Storage entry has no password; skip this tier
}

Prevention

When it happens

Trigger: `security find-generic-password -wa <storage>` exits 0, prints nothing to stderr, but also no password data — typically when the generic-password item exists but has an empty password attribute, or `-w` matching yields no value.

Common situations: Chromium installed but never saved cookies/passwords so Safe Storage was created empty; user deleted the secret body from Keychain Access; a third-party keychain entry with the right label but blank password.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of moonD4rk/HackBrowserData@0503d04d7a (2026-09-06). Data as JSON: /api/errors/0995fa3c67cdc128. Report an issue: GitHub.