Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
[chore] Follow up to PR 3383
Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>
  • Loading branch information
dominikschulz committed May 3, 2026
commit 52ef38284dd47752f1e5d17ed70f4d645b3c19ca
8 changes: 4 additions & 4 deletions docs/commands/generate.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ Note: If you only want generate a password without storing it in the store, use

## Synopsis

```
$ gopass generate entry [length]
$ gopass generate entry key [length]
```sh
gopass generate entry [length]
gopass generate entry key [length]
```

## Modes of operation
Expand Down Expand Up @@ -41,7 +41,7 @@ Use `--generator` to select one of the available password generators:
| Generator | Description |
|-------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `cryptic` | The default generator yields cryptic passwords that should work with most sites. Use `--symbols` and `--strict` if the site has specific requirements. Please note that we auto-detect the correct rules for some sites. The length argument specifies the number of characters. |
| `--xkcd` | Use an [XKCD#936](https://xkcd.com/936/) style password. Use `--xkcd-lang` and `--xkcd-sep` to refine its behaviour. The length argument specifies the number of words. |
| `xkcd` | Use an [XKCD#936](https://xkcd.com/936/) style password. Use `--xkcd-lang` and `--xkcd-sep` to refine its behaviour. The length argument specifies the number of words. |
| `memorable` | Generate a memorable password. The length argument specifies the minimum lenght of characters. Please note that the password might be longer if not all necessary rules were satisfied by the minimum length solution. |
| `external` | Use the external generator from `$GOPASS_EXTERNAL_PWGEN` |

Expand Down
277 changes: 256 additions & 21 deletions docs/exit-codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,63 +38,298 @@ Run `gopass --exit-codes` to print this table at any time.

## Per-Command Summary

### `show`
### `audit`

| Code | When |
|-----:|------|
| 0 | Secret displayed successfully |
| 10 | Secret not found |
| 11 | Secret could not be decrypted |
| 0 | No issues found |
| 1 | Audit run itself failed |
| 13 | Store contents could not be listed |
| 14 | One or more weak passwords or issues detected |
| 18 | Report file could not be written |

### `insert`
### `cat`

| Code | When |
|-----:|------|
| 0 | Secret inserted successfully |
| 11 | Existing secret could not be read for append/key-insert |
| 12 | Secret could not be encrypted and saved |
| 0 | Content read or written successfully |
| 9 | No secret name provided |
| 11 | Secret could not be decrypted for output |
| 18 | I/O error reading from stdin or writing content |

### `generate`
### `clone`

| Code | When |
|-----:|------|
| 0 | Password generated and stored successfully |
| 12 | Generated secret could not be encrypted and saved |
| 0 | Store cloned successfully |
| 2 | No repository URL provided |
| 5 | Root store is already initialized (cannot clone over it) |
| 6 | Root store not initialized when trying to add a mount |
| 7 | Git clone operation failed |
| 8 | Adding the cloned store as a mount failed |
| 18 | Could not read repository URL or mount point interactively |

### `find`
### `config`

| Code | When |
|-----:|------|
| 0 | Matches found (or single match displayed) |
| 10 | No matching secret found |
| 0 | Config value displayed or set successfully |
| 2 | Wrong number of arguments |
| 1 | Config value could not be set |

### `delete`
### `convert`

| Code | When |
|-----:|------|
| 0 | Store converted successfully |
| 2 | Unknown backend name given for `--storage` or `--crypto` |
| 10 | Named store not found |
| 1 | Conversion failed |

### `copy` / `cp`

| Code | When |
|-----:|------|
| 0 | Secret or directory copied successfully |
| 2 | Not enough arguments |
| 3 | Destination exists and user declined overwrite |
| 10 | Source path does not exist |
| 13 | Could not list source subtree |
| 18 | Copy operation failed |

### `create`

| Code | When |
|-----:|------|
| 0 | Secret created successfully |
| 1 | Interactive create wizard failed to initialize |
| 3 | User cancelled the create wizard |
| 18 | Generated password could not be copied to clipboard |

### `delete` / `rm`

| Code | When |
|-----:|------|
| 0 | Secret deleted successfully |
| 2 | No name provided; or multiple names with `-r`; or target is a directory without `-r` |
| 4 | `--key` value conflicts with an existing secret name |
| 10 | Secret not found |
| 18 | Delete or YAML-key removal failed |
| 20 | Post-delete hook execution failed |

### `audit`
### `doctor`

| Code | When |
|-----:|------|
| 0 | No issues found |
| 14 | One or more weak passwords or issues detected |
| 0 | All checks passed |
| 21 | One or more checks failed |

### `edit`

| Code | When |
|-----:|------|
| 0 | Secret saved successfully |
| 2 | No name provided |
| 11 | Existing secret could not be decrypted before editing |
| 12 | Edited secret could not be encrypted and saved |
| 17 | Recipients for the secret are invalid |
| 20 | Pre-edit hook execution failed |

### `env`

| Code | When |
|-----:|------|
| 0 | Program executed successfully with secrets in environment |
| 2 | No program to execute; conflicting input-mode flags; non-secret path used with `--stdin` |
| 10 | Named secret not found |
| 13 | Store contents could not be listed |

### `find`

| Code | When |
|-----:|------|
| 0 | Matches found (or single match displayed) |
| 2 | No search pattern provided; or invalid regular expression |
| 3 | User aborted interactive selection |
| 10 | No matching secret found |
| 13 | Store contents could not be listed |

### `fsck`

| Code | When |
|-----:|------|
| 0 | Store integrity OK |
| 10 | Specified filter path not found |
| 15 | One or more integrity errors found |

### `doctor`
### `generate`

| Code | When |
|-----:|------|
| 0 | All checks passed |
| 21 | One or more checks failed |
| 0 | Password generated and stored successfully |
| 2 | Length argument is not a valid positive integer |
| 3 | User declined to overwrite existing secret |
| 9 | No secret name provided |
| 12 | Generated secret could not be encrypted and saved |
| 18 | Generated password could not be copied to clipboard |

### `git`

| Code | When |
|-----:|------|
| 0 | Git operation completed successfully |
| 2 | Not enough arguments for `git remote add` or `git remote rm` |
| 7 | VCS init or remote push operation failed |

### `grep`

| Code | When |
|-----:|------|
| 0 | Search completed (results printed or nothing matched) |
| 2 | No search argument provided; or invalid regular expression |
| 13 | Store contents could not be listed |

### `history`

| Code | When |
|-----:|------|
| 0 | Revision history displayed successfully |
| 2 | No secret name provided |
| 10 | Secret does not exist |
| 1 | Revision list could not be retrieved |

### `init`

| Code | When |
|-----:|------|
| 0 | Store initialized successfully |
| 1 | Store initialization failed |
| 6 | Store is not initialized (checked via `IsInitialized`) |

### `insert`

| Code | When |
|-----:|------|
| 0 | Secret inserted successfully |
| 1 | Editor could not be launched for buffer-based insert |
| 2 | YAML key could not be parsed |
| 3 | Secret exists and user declined overwrite |
| 9 | No secret name provided |
| 11 | Existing secret could not be read for append/key-insert |
| 12 | Secret could not be encrypted and saved |
| 18 | I/O error reading from stdin or prompting for password |

### `link`

| Code | When |
|-----:|------|
| 0 | Link created successfully |
| 2 | Not enough arguments |

### `list` / `ls`

| Code | When |
|-----:|------|
| 0 | Store contents listed successfully |
| 10 | Specified filter path not found |
| 13 | Store tree could not be built |

### `merge`

| Code | When |
|-----:|------|
| 0 | Secrets merged successfully |
| 2 | Missing source or destination argument |
| 11 | Source secret could not be decrypted |
| 12 | Merged secret could not be encrypted and saved |

### `mounts`

| Code | When |
|-----:|------|
| 0 | Mount added or removed successfully |
| 2 | No alias provided for `mounts remove`; or wrong argument count for `mounts add` |
| 8 | Mount operation failed |

### `move` / `mv`

| Code | When |
|-----:|------|
| 0 | Secret or directory moved successfully |
| 2 | Not exactly two arguments provided |
| 3 | Destination exists and user declined overwrite |
| 1 | Move operation failed |

### `otp`

| Code | When |
|-----:|------|
| 0 | OTP token generated successfully |
| 2 | No secret name provided |
| 10 | Secret contains no OTP key |
| 1 | OTP URI not found or token calculation failed |
| 18 | Token could not be copied to clipboard |

### `process`

| Code | When |
|-----:|------|
| 0 | Template processed and output written successfully |
| 2 | No file argument provided |
| 18 | Template file could not be read or processed |

### `recipients`

| Code | When |
|-----:|------|
| 0 | Recipient operation completed successfully |
| 3 | User aborted interactive key selection |
| 13 | Recipient list could not be retrieved |
| 17 | Recipient could not be added or removed |

### `reorg`

| Code | When |
|-----:|------|
| 0 | Store reorganized successfully |
| 2 | Secret count changed in editor; or invalid move in editor |
| 3 | User aborted confirmation |
| 4 | Running in non-interactive mode |
| 7 | Git commit after reorganization failed |
| 13 | Store contents could not be listed |

### `show`

| Code | When |
|-----:|------|
| 0 | Secret displayed successfully |
| 1 | Revision list could not be retrieved; or QR encoding failed |
| 2 | No name provided |
| 10 | Secret not found; or requested YAML key, line, or password field not found |
| 11 | Secret could not be decrypted |

### `sync`

| Code | When |
|-----:|------|
| 0 | Synchronization completed (sync does not emit specific non-zero codes; errors are logged as warnings) |

### `templates`

| Code | When |
|-----:|------|
| 0 | Template operation completed successfully |
| 2 | No template name provided for `templates rm` |
| 10 | Template not found for `templates rm` |
| 13 | Template list could not be retrieved |
| 18 | Template could not be read or written |

### `update`

| Code | When |
|-----:|------|
| 0 | gopass is up to date or update applied successfully |
| 1 | Update check or download failed |

## Scripting Example

Expand Down
37 changes: 37 additions & 0 deletions docs/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,43 @@ checks for expiration, minimum trust level, and the presence of an encryption
sub-capability. Expired or untrusted keys are rejected, preventing silent
encryption to keys that can no longer decrypt.

### Recipient File Integrity (Hash Pinning)

The recipients file (`.gpg-id`) lives inside the git repository and lists the
public-key fingerprints/IDs that secrets are encrypted to. Because the file is
git-tracked, a collaborator — or an attacker with write access to the remote
repository — could push a commit that silently adds their own key, causing
future encrypt operations to include them as a recipient.

gopass defends against this with a hash-pinning mechanism:

1. **Hash computation.** Every time gopass writes the recipients file (via
`gopass recipients add`, `gopass recipients rm`, or `gopass init`), it
computes the SHA-256 digest of the serialised recipients list.

2. **Local storage.** The digest is written to the **global** (machine-local)
gopass config file, stored outside the git repository, under the key
`recipients.hash` (or `recipients.<alias>.hash` for mounted sub-stores).
Because the global config file is never committed to git, a remote attacker
cannot alter the expected value.

3. **Verification on load.** When `recipients.check` is set to `true` (in the
global config or per-mount config), every load of the recipients file
recomputes the digest and compares it to the stored value. If they differ,
`ErrInvalidHash` is returned and the operation is aborted, alerting the
operator that the recipients file was modified outside of gopass.

4. **Explicit acknowledgement.** A legitimate change — for example, pulling an
update that a teammate pushed after running `gopass recipients add` on their
machine — can be accepted with `gopass recipients ack`. This command
verifies the new recipient list interactively and then updates the stored
hash, re-pinning to the new value.

The combination of out-of-band hash storage and explicit acknowledgement ensures
that the recipients list can only grow silently if the attacker also has
write access to the operator's local machine — at which point the entire
threat model has already collapsed.

### OpenBSD Pledge

On OpenBSD, gopass calls `protect.Pledge("stdio rpath wpath cpath tty proc
Expand Down
2 changes: 1 addition & 1 deletion internal/action/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ func ShowFlags() []cli.Flag {
},
&cli.BoolFlag{
Name: "unsafe",
Aliases: []string{"u"},
Aliases: []string{"u", "f"}, // -f is deprecated, but we keep it for backward compatibility.
Usage: "Display unsafe content (e.g. the password) even if safecontent is enabled",
},
&cli.BoolFlag{
Expand Down
Loading
Loading