diff --git a/src/lib/components/provider-icons/Bitwarden.svelte b/src/lib/components/provider-icons/Bitwarden.svelte
new file mode 100644
index 0000000..71e5d55
--- /dev/null
+++ b/src/lib/components/provider-icons/Bitwarden.svelte
@@ -0,0 +1,7 @@
+
+
+
diff --git a/src/lib/components/provider-icons/index.ts b/src/lib/components/provider-icons/index.ts
index 86c27f2..55ab428 100644
--- a/src/lib/components/provider-icons/index.ts
+++ b/src/lib/components/provider-icons/index.ts
@@ -4,6 +4,8 @@ import OnePassword from './OnePassword.svelte';
import Infisical from './Infisical.svelte';
import HashiCorpVault from './HashiCorpVault.svelte';
import Doppler from './Doppler.svelte';
+import Bitwarden from './Bitwarden.svelte';
+import ProtonPass from './ProtonPass.svelte';
/** Brand icon for a secret-provider type. Falls back to a generic key icon. */
export function getProviderIcon(type: string): Component {
@@ -17,6 +19,10 @@ export function getProviderIcon(type: string): Component {
return HashiCorpVault;
case 'doppler':
return Doppler;
+ case 'bitwarden':
+ return Bitwarden;
+ case 'proton':
+ return ProtonPass;
default:
return KeyRound; // any future provider until it has a brand icon
}
diff --git a/src/lib/server/secretproviders/bitwarden.ts b/src/lib/server/secretproviders/bitwarden.ts
index 7b35b2a..91f84ca 100644
--- a/src/lib/server/secretproviders/bitwarden.ts
+++ b/src/lib/server/secretproviders/bitwarden.ts
@@ -202,11 +202,10 @@ async function runBws(args: string[], limits: CommandLimits): Promise {
}
return await executeBws(stateDir, args, limits);
} finally {
- try {
- await rm(stateDir, { recursive: true, force: true });
- } catch {
- throw new BwsAdapterError('Bitwarden bws temporary state could not be removed');
- }
+ // Swallow a cleanup failure: throwing here would mask the real error on the
+ // failure path, and turn a successful pull into a thrown error on the happy
+ // path. rm --force rarely fails; a stray temp dir is harmless residue.
+ await rm(stateDir, { recursive: true, force: true }).catch(() => undefined);
}
}
diff --git a/src/lib/server/secretproviders/index.ts b/src/lib/server/secretproviders/index.ts
index b8ce170..53d8591 100644
--- a/src/lib/server/secretproviders/index.ts
+++ b/src/lib/server/secretproviders/index.ts
@@ -19,6 +19,7 @@ import { infisicalProvider } from './infisical';
import { vaultProvider } from './vault';
import { dopplerProvider } from './doppler';
import { bitwardenProvider } from './bitwarden';
+import { protonProvider } from './proton';
// Registered providers. Adding a new backend means dropping a file in this
// directory and registering it here; each implements the SecretProvider
@@ -29,7 +30,8 @@ const providers: Record = {
[infisicalProvider.type]: infisicalProvider as SecretProvider,
[vaultProvider.type]: vaultProvider as SecretProvider,
[dopplerProvider.type]: dopplerProvider as SecretProvider,
- [bitwardenProvider.type]: bitwardenProvider as SecretProvider
+ [bitwardenProvider.type]: bitwardenProvider as SecretProvider,
+ [protonProvider.type]: protonProvider as SecretProvider
};
/** Returns the provider for a stored type, or undefined if unknown. */
diff --git a/src/lib/server/secretproviders/shared.ts b/src/lib/server/secretproviders/shared.ts
index ca48e11..d938fc2 100644
--- a/src/lib/server/secretproviders/shared.ts
+++ b/src/lib/server/secretproviders/shared.ts
@@ -32,6 +32,7 @@ export type SecretProviderType =
| 'vault'
| 'doppler'
| 'bitwarden'
+ | 'proton'
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
| (string & {});
@@ -137,7 +138,12 @@ export interface ConnectConfig {
*/
export interface InfisicalConfig {
host: string;
- token: string;
+ /** Static service/access token. Supply this OR clientId+clientSecret (Universal Auth). */
+ token?: string;
+ /** Universal Auth (Machine Identity) client ID. Paired with clientSecret. */
+ clientId?: string;
+ /** Universal Auth (Machine Identity) client secret. Paired with clientId. */
+ clientSecret?: string;
projectId: string;
environment?: string;
path?: string;
@@ -177,6 +183,15 @@ export interface BitwardenConfig {
serverUrl?: string;
}
+/**
+ * Proton Pass: a Personal Access Token (`pst_...::...`) authenticates an
+ * operator-installed pass-cli client. Bulk pulls one vault (selected by name);
+ * inline `pass://SHARE_ID/ITEM_ID[/FIELD]` references resolve a single field.
+ */
+export interface ProtonConfig {
+ token: string;
+}
+
/** Persisted (encrypted) config, discriminated by the provider `type`. */
export type SecretProviderConfig =
| ServiceAccountConfig
@@ -184,7 +199,8 @@ export type SecretProviderConfig =
| InfisicalConfig
| VaultConfig
| DopplerConfig
- | BitwardenConfig;
+ | BitwardenConfig
+ | ProtonConfig;
/**
* Config keys that hold a SECRET across every provider type. Only these are stripped
@@ -193,7 +209,7 @@ export type SecretProviderConfig =
* a non-secret coordinate the user needs to see and edit. Keep in sync with the
* `type: 'password'` fields in ProviderModal.svelte's PROVIDER_FIELDS.
*/
-export const SECRET_CONFIG_KEYS = new Set(['token']);
+export const SECRET_CONFIG_KEYS = new Set(['token', 'clientSecret']);
/**
* Merges an incoming (edit-form) config OVER the stored one for a write/test: the incoming
diff --git a/src/routes/settings/secrets/ProviderModal.svelte b/src/routes/settings/secrets/ProviderModal.svelte
index 74d5d8a..46b07e0 100644
--- a/src/routes/settings/secrets/ProviderModal.svelte
+++ b/src/routes/settings/secrets/ProviderModal.svelte
@@ -25,6 +25,7 @@
{ value: 'vault', label: 'HashiCorp Vault' },
{ value: 'doppler', label: 'Doppler' },
{ value: 'bitwarden', label: 'Bitwarden Secrets Manager' },
+ { value: 'proton', label: 'Proton Pass' },
];
// Config fields per provider type, matching the config shapes in
@@ -39,7 +40,9 @@
],
infisical: [
{ key: 'host', label: 'API host', type: 'text', required: true, placeholder: 'https://app.infisical.com', hint: 'Infisical Cloud or your self-hosted URL.' },
- { key: 'token', label: 'Access token', type: 'password', required: true, placeholder: 'st...', hint: 'A service token or machine-identity access token.' },
+ { key: 'token', label: 'Access token', type: 'password', required: false, placeholder: 'st...', hint: 'A static service/access token. Leave blank to use Universal Auth (client ID + secret) below instead.' },
+ { key: 'clientId', label: 'Universal Auth client ID', type: 'text', required: false, placeholder: 'machine identity client id', hint: 'A Machine Identity client ID. Pair with the client secret; leave blank if using a static token.' },
+ { key: 'clientSecret', label: 'Universal Auth client secret', type: 'password', required: false, placeholder: 'machine identity client secret', hint: 'The Machine Identity client secret. Exchanged for a short-lived token via Universal Auth.' },
{ key: 'projectId', label: 'Project ID', type: 'text', required: true, placeholder: 'workspace / project id', hint: 'The workspace/project the secrets live in.' },
{ key: 'environment', label: 'Environment', type: 'text', required: true, placeholder: 'prod', hint: 'Environment slug, e.g. prod / staging.' },
{ key: 'path', label: 'Secret path', type: 'text', required: false, placeholder: '/', hint: 'Folder path within the project. Defaults to /.' },
@@ -59,6 +62,9 @@
{ key: 'token', label: 'Machine Account access token', type: 'password', required: true, placeholder: 'Machine Account access token', hint: 'A Bitwarden Secrets Manager Machine Account token with read access to the Project.' },
{ key: 'serverUrl', label: 'Server URL', type: 'text', required: false, placeholder: 'https://vault.bitwarden.com', hint: 'Optional for EU or self-hosted Bitwarden. Leave blank for Bitwarden US cloud.' },
],
+ proton: [
+ { key: 'token', label: 'Personal access token', type: 'password', required: true, placeholder: 'pst_...::...', hint: 'A Proton Pass personal access token (pst_...) used by the operator-installed pass-cli.' },
+ ],
};
export function providerTypeLabel(type: string): string {
@@ -90,6 +96,11 @@
label: 'Project',
placeholder: 'Bitwarden Project UUID',
hint: 'Bulk-load every secret from this Bitwarden Secrets Manager Project.'
+ },
+ 'proton': {
+ label: 'Vault',
+ placeholder: 'Proton Pass vault name',
+ hint: 'Bulk-load every item from this Proton Pass vault. Leave blank to inject only inline pass:// references.'
}
};
@@ -415,6 +426,14 @@
DOCKHAND_BWS_PATH process override).
{/if}
+ {#if formType === 'proton'}
+
+ Proton Pass requires an externally installed or mounted official
+ pass-cli client at /usr/local/bin/pass-cli (or an absolute
+ DOCKHAND_PASS_CLI_PATH process override). Supports both a bulk vault pull
+ and inline pass:// references.
+
+ {/if}
Configuration is stored encrypted.{#if isEditing}
Leave secret fields blank to keep the existing values.{/if}