{"record":{"id":"0f047a0f5cb3b591","repo":"getsops/sops","slug":"gnupghome-has-invalid-permissions-got-o-wanted","errorCode":null,"errorMessage":"GNUPGHOME has invalid permissions: got %#o wanted %#o","messagePattern":"GNUPGHOME has invalid permissions: got %#o wanted %#o","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pgp/keysource.go","lineNumber":209,"sourceCode":"func (d GnuPGHome) Validate() error {\n\tif d == \"\" {\n\t\treturn fmt.Errorf(\"empty GNUPGHOME path\")\n\t}\n\tif !filepath.IsAbs(d.String()) {\n\t\treturn fmt.Errorf(\"GNUPGHOME must be an absolute path\")\n\t}\n\tfi, err := os.Lstat(d.String())\n\tif err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\treturn fmt.Errorf(\"GNUPGHOME does not exist\")\n\t\t}\n\t\treturn fmt.Errorf(\"cannot stat GNUPGHOME: %w\", err)\n\t}\n\tif !fi.IsDir() {\n\t\treturn fmt.Errorf(\"GNUGPHOME is not a directory\")\n\t}\n\tif perm := fi.Mode().Perm(); perm != 0o700 {\n\t\treturn fmt.Errorf(\"GNUPGHOME has invalid permissions: got %#o wanted %#o\", perm, 0o700)\n\t}\n\treturn nil\n}\n\n// String returns the GnuPGHome as a string. It does not Validate.\nfunc (d GnuPGHome) String() string {\n\treturn string(d)\n}\n\n// ApplyToMasterKey configures the GnuPGHome on the provided key if it passes\n// Validate.\nfunc (d GnuPGHome) ApplyToMasterKey(key *MasterKey) {\n\tif err := d.Validate(); err == nil {\n\t\tkey.gnuPGHomeDir = d.String()\n\t}\n}\n\n// DisableOpenPGP disables encrypt and decrypt operations using OpenPGP.","sourceCodeStart":191,"sourceCodeEnd":227,"githubUrl":"https://github.com/getsops/sops/blob/13442bb98183887d7a9ac09ec8ab0564673a59d8/pgp/keysource.go#L191-L227","documentation":"GnuPGHome.Validate() enforces that the GNUPGHOME directory has exactly permission bits 0700 (owner read/write/execute, no group/other access). If the mode differs, it returns this error with the got/wanted octal values. gpg itself refuses to operate on a permissive home directory, so sops rejects it up front.","triggerScenarios":"ImportContext, Cleanup, or ApplyToMasterKey on a GnuPGHome whose directory mode is anything other than 0700 (e.g. 0755 after mkdir without chmod, or 0777 from a shared/tmpfs location).","commonSituations":"Created the directory with umask 022 leaving 0755; copied a home dir with cp preserving lax perms; running on Windows/CI mounts where perms translate oddly; group-shared GNUPGHOME.","solutions":["chmod 700 on the directory: chmod 700 \"$GNUPGHOME\"","Recreate the directory with strict perms: rm -rf and mkdir -p + chmod 700","In CI, avoid mounting GNUPGHOME from a volume that forces group/other bits; copy into a fresh 0700 dir instead","Call pgp.GnuPGHome(path).Validate() before use and surface the got/wanted message to the operator"],"exampleFix":"// before\nos.MkdirAll(gnupgHome, 0o755)\n// after\nos.MkdirAll(gnupgHome, 0o700) // and chmod 700 if it pre-existed with lax perms","handlingStrategy":"validation","validationCode":"if err := pgp.GnuPGHome(path).Validate(); err != nil {\n    // message includes got/wanted octal perms; surface it\n    log.Fatal(err)\n}\n// proactively repair:\nif fi, err := os.Stat(path); err == nil && fi.IsDir() && fi.Mode().Perm() != 0o700 {\n    os.Chmod(path, 0o700)\n}","typeGuard":"func hasSafeGnuPGPerms(h pgp.GnuPGHome) bool {\n    fi, err := os.Stat(string(h))\n    return err == nil && fi.IsDir() && fi.Mode().Perm() == 0o700\n}","tryCatchPattern":null,"preventionTips":["chmod 700 after any mkdir of GNUPGHOME, including provisioning scripts","Watch for volumes/mounts (tmpfs, bind mounts, Windows shares) that alter permission bits","Never share one GNUPGHOME between users; give each service its own 0700 home","Include a perms check in CI setup steps before running sops"],"tags":["gnupg","permissions","security"],"backgroundTag":"gnupghome-invalid","analyzedSha":"13442bb98183887d7a9ac09ec8ab0564673a59d8","analyzedAt":"2026-09-01T03:53:00.447Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}