resolve secret references pasted with surrounding quotes

This commit is contained in:
Jarek Krochmalski committed 2026-09-06 17:03:47 +05:30
1 parent 07f29f5afc
commit a22ed6b809
7 files changed
+129 -7

No files matched your search

+2 -2
View File
@@ -13,7 +13,7 @@
import { request } from 'undici';
import type { AzureKvConfig, SecretProvider, TestConnectionResult } from './shared';
import { assertSafeProviderHost, isJsonResponse } from './shared';
import { assertSafeProviderHost, isJsonResponse, stripSurroundingQuotes } from './shared';
const API_VERSION = '7.4';
const AZURE_REF_PREFIX = 'azurekv://';
@@ -140,7 +140,7 @@ export const azureKvProvider: SecretProvider<AzureKvConfig> = {
supportsBulk: true,
isReference(value: unknown): value is string {
return typeof value === 'string' && AZURE_REF_RE.test(value.trim());
return typeof value === 'string' && AZURE_REF_RE.test(stripSurroundingQuotes(value));
},
async testConnection(config: AzureKvConfig): Promise<TestConnectionResult> {
+2 -2
View File
@@ -15,7 +15,7 @@
import { request } from 'undici';
import type { ConnectConfig, SecretProvider, TestConnectionResult } from './shared';
import { assertSafeProviderHost, parseProviderError, isJsonResponse } from './shared';
import { assertSafeProviderHost, parseProviderError, isJsonResponse, stripSurroundingQuotes } from './shared';
import { UnsupportedOperationError } from './shared';
/** A vault as returned by GET /v1/vaults. */
@@ -228,7 +228,7 @@ export const connectProvider: SecretProvider<ConnectConfig> = {
supportsBulk: false,
isReference(value: unknown): value is string {
return typeof value === 'string' && value.trim().startsWith('op://');
return typeof value === 'string' && stripSurroundingQuotes(value).startsWith('op://');
},
async testConnection(config: ConnectConfig): Promise<TestConnectionResult> {
+2 -1
View File
@@ -20,6 +20,7 @@
import { spawn } from 'node:child_process';
import { isAbsolute } from 'node:path';
import type { KeePassConfig, SecretProvider, TestConnectionResult } from './shared';
import { stripSurroundingQuotes } from './shared';
const DEFAULT_CLI_PATH = '/usr/bin/keepassxc-cli';
const COMMAND_TIMEOUT_MS = 30_000;
@@ -260,7 +261,7 @@ export const keepassProvider: SecretProvider<KeePassConfig> = {
supportsBulk: true,
isReference(value: unknown): value is string {
return typeof value === 'string' && value.trim().startsWith(KEEPASS_PREFIX);
return typeof value === 'string' && stripSurroundingQuotes(value).startsWith(KEEPASS_PREFIX);
},
async testConnection(config: KeePassConfig): Promise<TestConnectionResult> {
+2 -1
View File
@@ -28,6 +28,7 @@ import { chmod, mkdtemp, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { isAbsolute, join } from 'node:path';
import type { ProtonConfig, SecretProvider, TestConnectionResult } from './shared';
import { stripSurroundingQuotes } from './shared';
const DEFAULT_PASS_CLI_PATH = '/usr/local/bin/pass-cli';
const LOGIN_TIMEOUT_MS = 30_000;
@@ -368,7 +369,7 @@ export const protonProvider: SecretProvider<ProtonConfig> = {
supportsBulk: true,
isReference(value: unknown): value is string {
return typeof value === 'string' && value.trim().startsWith('pass://');
return typeof value === 'string' && stripSurroundingQuotes(value).startsWith('pass://');
},
async testConnection(config: ProtonConfig): Promise<TestConnectionResult> {
@@ -15,6 +15,7 @@
import { createClient } from '@1password/sdk';
import type { SecretProvider, ServiceAccountConfig, TestConnectionResult } from './shared';
import { stripSurroundingQuotes } from './shared';
const INTEGRATION_NAME = 'Dockhand';
const INTEGRATION_VERSION = '1.0.0';
@@ -49,7 +50,7 @@ export const serviceAccountProvider: SecretProvider<ServiceAccountConfig> = {
supportsBulk: true,
isReference(value: unknown): value is string {
return typeof value === 'string' && value.trim().startsWith('op://');
return typeof value === 'string' && stripSurroundingQuotes(value).startsWith('op://');
},
async testConnection({ token }: ServiceAccountConfig): Promise<TestConnectionResult> {
+14
View File
@@ -48,6 +48,20 @@ export type SecretProviderType =
* self-hosted Vault/Infisical/Connect on the local network still works. Throws on
* an unsafe host; call it before the first request in every REST provider.
*/
/**
* Strip ONE layer of matching surrounding quotes for reference DETECTION only.
* 1Password's "Copy Secret Reference" puts `"op://Vault/Item/field"` on the clipboard
* (quotes included), so a pasted value fails a bare `startsWith('op://')` test and the
* reference is silently skipped (#1521). Providers normalize with this before the prefix
* test, and the resolver uses it to key the reference - the STORED value is left untouched
* (so #1086's "stop stripping quotes on save" still holds). Only strips when both ends are
* the same quote char; leaves a value with mismatched/one-sided quotes as-is.
*/
export function stripSurroundingQuotes(value: string): string {
// \1 backreference: strip only when the SAME quote char wraps both ends.
return value.trim().replace(/^(["'])(.*)\1$/s, '$2');
}
export function assertSafeProviderHost(rawUrl: string, label: string): void {
const safe = isSafeNotificationUrl(rawUrl);
if (!safe.ok) {