Skip to content
Merged
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
39 changes: 36 additions & 3 deletions docs/INSTALLATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,21 @@ apw login https://example.com
### External password manager fallback

When the native app broker cannot return a credential, APW can fall back to a
configured external password manager CLI provider. The fallback executable path
is security-sensitive and is validated before APW invokes it.
configured external password manager CLI provider. The external CLI fallback is
opt-in and only runs when explicitly enabled for the credential request.
Configure it in `~/.apw/config.json` with an absolute executable path:

```json
{
"fallbackProvider": "bitwarden",
"fallbackProviderPath": "/opt/homebrew/bin/bw"
}
```

Supported providers are `bitwarden` and `1password`.

`fallbackProviderPath` must follow these rules:
The fallback executable path is security-sensitive and is validated before APW
invokes it. `fallbackProviderPath` must follow these rules:

- Use an absolute path. Relative paths and `~` expansion are rejected.
- Resolve through `realpath`/canonicalization. Symlinks are followed and the
Expand All @@ -157,6 +168,28 @@ is security-sensitive and is validated before APW invokes it.
- Use `0755` permissions or more restrictive permissions. Group-writable,
world-writable, and special-mode executables are rejected.

External provider executions are bounded by default:

- `fallbackProviderTimeoutMs`: per-process timeout in milliseconds. Default:
`5000`. Values less than `1` fall back to the default. A timed-out provider
process is killed and the credential request fails with a clear timeout
error.
- `fallbackProviderMaxInvocations`: maximum external provider process
invocations per APW session. Default: `10`. Set `0` to block external
provider invocations for the current session. When the limit is exceeded, APW
returns a clear error instead of executing the provider again.

Example with explicit limits:

```json
{
"fallbackProvider": "1password",
"fallbackProviderPath": "/opt/homebrew/bin/op",
"fallbackProviderTimeoutMs": 3000,
"fallbackProviderMaxInvocations": 6
}
```

## Diagnostics

### Machine-readable status
Expand Down
8 changes: 8 additions & 0 deletions rust/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,8 @@ impl ApplePasswordManager {
bridge_last_error: None,
fallback_provider: None,
fallback_provider_path: None,
fallback_provider_timeout_ms: None,
fallback_provider_max_invocations: None,
created_at: Utc::now().timestamp().to_string(),
});

Expand Down Expand Up @@ -1064,6 +1066,8 @@ impl ApplePasswordManager {
bridge_last_error: None,
fallback_provider: None,
fallback_provider_path: None,
fallback_provider_timeout_ms: None,
fallback_provider_max_invocations: None,
created_at: Utc::now().timestamp().to_string(),
});

Expand Down Expand Up @@ -2672,6 +2676,8 @@ mod tests {
secret_source: Some(SecretSource::File),
fallback_provider: None,
fallback_provider_path: None,
fallback_provider_timeout_ms: None,
fallback_provider_max_invocations: None,
created_at: chrono::Utc::now().to_rfc3339(),
runtime_mode: RuntimeMode::Auto,
last_launch_status: None,
Expand Down Expand Up @@ -2764,6 +2770,8 @@ mod tests {
secret_source: Some(SecretSource::File),
fallback_provider: None,
fallback_provider_path: None,
fallback_provider_timeout_ms: None,
fallback_provider_max_invocations: None,
created_at: (chrono::Utc::now() - chrono::Duration::days(45)).to_rfc3339(),
runtime_mode: RuntimeMode::Auto,
last_launch_status: None,
Expand Down
Loading
Loading