You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ofbiz.apache.org by as...@apache.org on 2026/06/07 13:05:41 UTC

(ofbiz-plugins) branch trunk updated: Revert "Adding all the newly created Secret Manager Plugins. Currently testing is being done with AWS Secret Manager plugin. For other plugins, I need to create account at all the providers then will update the code if required."

This is an automated email from the ASF dual-hosted git repository.

ashishvijaywargiya pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ofbiz-plugins.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 112fbeab6 Revert "Adding all the newly created Secret Manager Plugins. Currently testing is being done with AWS Secret Manager plugin. For other plugins, I need to create account at all the providers then will update the code if required."
112fbeab6 is described below

commit 112fbeab61f23bc5af2daa039d42f072c295ff19
Author: Ashish Vijaywargiya <as...@apache.org>
AuthorDate: Sun Jun 7 18:35:21 2026 +0530

    Revert "Adding all the newly created Secret Manager Plugins. Currently testing is being done with AWS Secret Manager plugin. For other plugins, I need to create account at all the providers then will update the code if required."
    
    This reverts commit a04675615cd9b7e19ae24970981301c03d6d2b6c.
---
 aws-secrets-provider/build.gradle                  |  30 --
 .../config/aws-secrets-manager.properties          |  53 ---
 aws-secrets-provider/ofbiz-component.xml           |  30 --
 .../awssecrets/AwsSecretsManagerProvider.java      | 207 ---------
 .../org.apache.ofbiz.base.secret.SecretProvider    |   1 -
 .../awssecrets/AwsSecretsManagerProviderTest.java  | 163 --------
 azure-keyvault-secrets-provider/build.gradle       |  29 --
 .../config/azure-keyvault.properties               |  39 --
 .../ofbiz-component.xml                            |  30 --
 .../ofbiz/azurekeyvault/AzureKeyVaultReader.java   |  35 --
 .../AzureKeyVaultSecretsProvider.java              | 180 --------
 .../org.apache.ofbiz.base.secret.SecretProvider    |   1 -
 .../AzureKeyVaultSecretsProviderTest.java          | 131 ------
 bitwarden-secrets-provider/build.gradle            |  27 --
 .../config/bitwarden-secrets.properties            |  59 ---
 bitwarden-secrets-provider/ofbiz-component.xml     |  30 --
 .../ofbiz/bitwarden/BitwardenHttpClient.java       |  49 ---
 .../ofbiz/bitwarden/BitwardenSecretsProvider.java  | 461 ---------------------
 .../org.apache.ofbiz.base.secret.SecretProvider    |   1 -
 .../bitwarden/BitwardenSecretsProviderTest.java    | 220 ----------
 gcp-secretmanager-secrets-provider/build.gradle    |  28 --
 .../config/gcp-secret-manager.properties           |  34 --
 .../ofbiz-component.xml                            |  30 --
 .../GcpSecretManagerSecretsProvider.java           | 201 ---------
 .../ofbiz/gcpsecretmanager/GcpSecretReader.java    |  35 --
 .../org.apache.ofbiz.base.secret.SecretProvider    |   1 -
 .../GcpSecretManagerSecretsProviderTest.java       | 147 -------
 hashicorp-vault-secrets-provider/build.gradle      |  28 --
 .../config/hashicorp-vault-secrets.properties      |  66 ---
 .../ofbiz-component.xml                            |  30 --
 .../ofbiz/hashicorpvault/HashicorpVaultReader.java |  39 --
 .../HashicorpVaultSecretsProvider.java             | 226 ----------
 .../org.apache.ofbiz.base.secret.SecretProvider    |   1 -
 .../HashicorpVaultSecretsProviderTest.java         | 156 -------
 onepassword-secrets-provider/build.gradle          |  23 -
 .../config/onepassword.properties                  |  56 ---
 onepassword-secrets-provider/ofbiz-component.xml   |  30 --
 .../ofbiz/onepassword/OnePasswordHttpClient.java   |  39 --
 .../onepassword/OnePasswordSecretsProvider.java    | 275 ------------
 .../org.apache.ofbiz.base.secret.SecretProvider    |   1 -
 .../OnePasswordSecretsProviderTest.java            | 156 -------
 41 files changed, 3378 deletions(-)

diff --git a/aws-secrets-provider/build.gradle b/aws-secrets-provider/build.gradle
deleted file mode 100644
index 48c5c3dd7..000000000
--- a/aws-secrets-provider/build.gradle
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-// AWS SDK for Java v2 — Apache License 2.0
-// https://github.com/aws/aws-sdk-java-v2
-dependencies {
-    pluginLibsCompile 'software.amazon.awssdk:secretsmanager:2.26.31'
-    pluginLibsCompile 'software.amazon.awssdk:url-connection-client:2.26.31'
-}
-
-// AWS SDK v2 bundles several items that conflict with OFBiz's global exclusions
-configurations.all {
-    exclude group: 'commons-logging', module: 'commons-logging'
-}
diff --git a/aws-secrets-provider/config/aws-secrets-manager.properties b/aws-secrets-provider/config/aws-secrets-manager.properties
deleted file mode 100644
index ba521ee71..000000000
--- a/aws-secrets-provider/config/aws-secrets-manager.properties
+++ /dev/null
@@ -1,53 +0,0 @@
-###############################################################################
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-###############################################################################
-
-####
-# AWS Secrets Manager — SecretProvider configuration
-#
-# Authentication uses the AWS Default Credential Provider Chain:
-#   1. Environment variables: AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY
-#   2. Java system properties: aws.accessKeyId / aws.secretAccessKey
-#   3. AWS credential profiles file (~/.aws/credentials)
-#   4. EC2 / ECS instance profile / IAM role (recommended for production)
-#   5. AWS SSO
-#
-# No credentials should ever be stored in this file.
-####
-
-# AWS region for the Secrets Manager endpoint.
-# Leave empty to let the SDK resolve from the environment (recommended for EC2/ECS).
-aws.secretsmanager.region=us-east-1
-
-# How long (in seconds) to cache a resolved secret value in memory before re-fetching.
-# Default: 3600 (1 hour). Set to 0 to disable caching (not recommended in production).
-aws.secretsmanager.cache.ttl.seconds=3600
-
-# Optional prefix prepended to every OFBiz secret key before the AWS lookup.
-# Example: with prefix "myapp/prod/" the key "jdbc-password.ofbiz" becomes
-#          "myapp/prod/jdbc-password.ofbiz" in AWS Secrets Manager.
-aws.secretsmanager.secret.name.prefix=
-
-# If the secret in AWS is a JSON object (e.g. {"password":"s3cr3t","username":"dbuser"}),
-# set this to the field name that holds the actual secret value (e.g. "password").
-# Leave empty to use the raw secret string as-is.
-aws.secretsmanager.json.field=
-
-# Optional endpoint override. Useful for local testing with LocalStack.
-# Example: http://localhost:4566
-aws.secretsmanager.endpoint.override=
diff --git a/aws-secrets-provider/ofbiz-component.xml b/aws-secrets-provider/ofbiz-component.xml
deleted file mode 100644
index 445cfb3ae..000000000
--- a/aws-secrets-provider/ofbiz-component.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
--->
-
-<ofbiz-component name="aws-secrets-provider" enabled="true"
-        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:noNamespaceSchemaLocation="https://ofbiz.apache.org/dtds/ofbiz-component.xsd">
-
-    <resource-loader name="main" type="component"/>
-
-    <!-- Puts config/ on the classpath so aws-secrets-manager.properties is found by UtilProperties -->
-    <classpath type="dir" location="config"/>
-
-</ofbiz-component>
diff --git a/aws-secrets-provider/src/main/java/org/apache/ofbiz/awssecrets/AwsSecretsManagerProvider.java b/aws-secrets-provider/src/main/java/org/apache/ofbiz/awssecrets/AwsSecretsManagerProvider.java
deleted file mode 100644
index 83c2fbb83..000000000
--- a/aws-secrets-provider/src/main/java/org/apache/ofbiz/awssecrets/AwsSecretsManagerProvider.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*******************************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- *******************************************************************************/
-package org.apache.ofbiz.awssecrets;
-
-import java.net.URI;
-import java.util.concurrent.ConcurrentHashMap;
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-import org.apache.ofbiz.base.lang.ThreadSafe;
-import org.apache.ofbiz.base.secret.SecretProvider;
-import org.apache.ofbiz.base.util.Debug;
-import org.apache.ofbiz.base.util.GeneralException;
-import org.apache.ofbiz.base.util.UtilProperties;
-
-import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient;
-import software.amazon.awssdk.regions.Region;
-import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
-import software.amazon.awssdk.services.secretsmanager.SecretsManagerClientBuilder;
-import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest;
-import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueResponse;
-import software.amazon.awssdk.services.secretsmanager.model.ResourceNotFoundException;
-import software.amazon.awssdk.services.secretsmanager.model.SecretsManagerException;
-
-/**
- * {@link SecretProvider} implementation backed by AWS Secrets Manager.
- *
- * <p>Authentication is handled by the AWS Default Credential Provider Chain —
- * no credentials are stored in this file or in properties files. The preferred
- * approach for EC2/ECS/EKS deployments is an IAM instance role or task role,
- * which requires zero credential configuration on the server.</p>
- *
- * <p>Resolved secret values are cached in memory for the duration configured by
- * {@code aws.secretsmanager.cache.ttl.seconds} (default 1 hour) to avoid
- * per-request API calls. Call {@link #invalidateCache()} to force an immediate
- * re-fetch, for example after a manual secret rotation.</p>
- *
- * <p>Configure via {@code plugins/aws-secrets-provider/config/aws-secrets-manager.properties}.</p>
- */
-@ThreadSafe
-public final class AwsSecretsManagerProvider implements SecretProvider {
-
-    private static final String MODULE = AwsSecretsManagerProvider.class.getName();
-    private static final String CONFIG_RESOURCE = "aws-secrets-manager";
-
-    private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
-
-    private final SecretsManagerClient client;
-    private final long cacheTtlMs;
-    private final String secretNamePrefix;
-    private final String jsonField;
-
-    private final ConcurrentHashMap<String, CacheEntry> cache = new ConcurrentHashMap<>();
-
-    private static final class CacheEntry {
-        private final String value;
-        private final long expiresAt;
-
-        CacheEntry(String value, long ttlMs) {
-            this.value = value;
-            this.expiresAt = System.currentTimeMillis() + ttlMs;
-        }
-
-        String getValue() {
-            return value;
-        }
-
-        boolean isExpired() {
-            return System.currentTimeMillis() >= expiresAt;
-        }
-    }
-
-    /** Public no-arg constructor required by {@link java.util.ServiceLoader}. */
-    public AwsSecretsManagerProvider() {
-        this(buildClient(), readTtlMs(),
-                UtilProperties.getPropertyValue(CONFIG_RESOURCE, "aws.secretsmanager.secret.name.prefix", ""),
-                UtilProperties.getPropertyValue(CONFIG_RESOURCE, "aws.secretsmanager.json.field", ""));
-    }
-
-    /** Package-private constructor used by unit tests to inject a mock client. */
-    AwsSecretsManagerProvider(SecretsManagerClient client, long cacheTtlMs,
-            String secretNamePrefix, String jsonField) {
-        this.client = client;
-        this.cacheTtlMs = cacheTtlMs;
-        this.secretNamePrefix = secretNamePrefix;
-        this.jsonField = jsonField;
-    }
-
-    @Override
-    public String getSecret(String key) throws GeneralException {
-        CacheEntry cached = cache.get(key);
-        if (cached != null && !cached.isExpired()) {
-            return cached.getValue();
-        }
-
-        String secretName = secretNamePrefix + key;
-        String secretValue = fetchFromAws(secretName);
-
-        if (!jsonField.isEmpty()) {
-            secretValue = extractJsonField(secretValue, secretName);
-        }
-
-        if (secretValue == null || secretValue.isEmpty()) {
-            throw new GeneralException("Secret '" + secretName + "' resolved to an empty value");
-        }
-
-        cache.put(key, new CacheEntry(secretValue, cacheTtlMs));
-        return secretValue;
-    }
-
-    /**
-     * Clears the in-memory cache, forcing the next {@link #getSecret(String)} call
-     * for each key to re-fetch from AWS Secrets Manager. Useful after a manual
-     * secret rotation to pick up the new value without restarting OFBiz.
-     */
-    public void invalidateCache() {
-        cache.clear();
-        Debug.logInfo("AwsSecretsManagerProvider: secret cache invalidated", MODULE);
-    }
-
-    // -- private helpers --
-
-    private String fetchFromAws(String secretName) throws GeneralException {
-        try {
-            GetSecretValueResponse response = client.getSecretValue(
-                    GetSecretValueRequest.builder().secretId(secretName).build());
-            String value = response.secretString();
-            if (value == null) {
-                throw new GeneralException("Secret '" + secretName
-                        + "' contains binary data; only string secrets are supported");
-            }
-            return value;
-        } catch (ResourceNotFoundException e) {
-            throw new GeneralException("Secret '" + secretName + "' not found in AWS Secrets Manager", e);
-        } catch (SecretsManagerException e) {
-            throw new GeneralException("AWS Secrets Manager error for '" + secretName + "': " + e.getMessage(), e);
-        }
-    }
-
-    /**
-     * Extracts a single string field from a flat JSON secret value.
-     * AWS typically stores database credentials as {"username":"u","password":"p"}.
-     */
-    private String extractJsonField(String json, String secretName) throws GeneralException {
-        try {
-            JsonNode root = JSON_MAPPER.readTree(json);
-            JsonNode node = root.get(jsonField);
-            if (node == null || node.isNull()) {
-                throw new GeneralException(
-                        "JSON field '" + jsonField + "' not found in secret '" + secretName + "'");
-            }
-            return node.asText();
-        } catch (JsonProcessingException e) {
-            throw new GeneralException("Failed to parse JSON for secret '" + secretName + "': " + e.getMessage(), e);
-        }
-    }
-
-    private static SecretsManagerClient buildClient() {
-        String region = UtilProperties.getPropertyValue(CONFIG_RESOURCE, "aws.secretsmanager.region", "");
-        String endpointOverride = UtilProperties.getPropertyValue(CONFIG_RESOURCE,
-                "aws.secretsmanager.endpoint.override", "");
-
-        SecretsManagerClientBuilder builder = SecretsManagerClient.builder()
-                .httpClient(UrlConnectionHttpClient.builder().build());
-
-        if (!region.isEmpty()) {
-            builder.region(Region.of(region));
-        }
-        if (!endpointOverride.isEmpty()) {
-            builder.endpointOverride(URI.create(endpointOverride));
-        }
-
-        Debug.logInfo("AwsSecretsManagerProvider: initialized"
-                + (region.isEmpty() ? " (region from environment)" : " region=" + region), MODULE);
-        return builder.build();
-    }
-
-    private static long readTtlMs() {
-        String raw = UtilProperties.getPropertyValue(CONFIG_RESOURCE,
-                "aws.secretsmanager.cache.ttl.seconds", "3600");
-        try {
-            return Long.parseLong(raw.trim()) * 1000L;
-        } catch (NumberFormatException e) {
-            Debug.logWarning("Invalid aws.secretsmanager.cache.ttl.seconds '" + raw
-                    + "', defaulting to 3600s", MODULE);
-            return 3_600_000L;
-        }
-    }
-}
diff --git a/aws-secrets-provider/src/main/resources/META-INF/services/org.apache.ofbiz.base.secret.SecretProvider b/aws-secrets-provider/src/main/resources/META-INF/services/org.apache.ofbiz.base.secret.SecretProvider
deleted file mode 100644
index 8d42eba0c..000000000
--- a/aws-secrets-provider/src/main/resources/META-INF/services/org.apache.ofbiz.base.secret.SecretProvider
+++ /dev/null
@@ -1 +0,0 @@
-org.apache.ofbiz.awssecrets.AwsSecretsManagerProvider
diff --git a/aws-secrets-provider/src/test/java/org/apache/ofbiz/awssecrets/AwsSecretsManagerProviderTest.java b/aws-secrets-provider/src/test/java/org/apache/ofbiz/awssecrets/AwsSecretsManagerProviderTest.java
deleted file mode 100644
index fb6f64b69..000000000
--- a/aws-secrets-provider/src/test/java/org/apache/ofbiz/awssecrets/AwsSecretsManagerProviderTest.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*******************************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- *******************************************************************************/
-package org.apache.ofbiz.awssecrets;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import org.apache.ofbiz.base.util.GeneralException;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-
-import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
-import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest;
-import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueResponse;
-import software.amazon.awssdk.services.secretsmanager.model.ResourceNotFoundException;
-
-public class AwsSecretsManagerProviderTest {
-
-    private static final long ONE_HOUR_MS = 3_600_000L;
-
-    // -- Happy path --
-
-    @Test
-    public void getSecretReturnsSecretString() throws GeneralException {
-        SecretsManagerClient client = clientReturning("jdbc-password.mydb", "s3cr3t");
-        AwsSecretsManagerProvider provider = new AwsSecretsManagerProvider(client, ONE_HOUR_MS, "", "");
-
-        assertEquals("s3cr3t", provider.getSecret("jdbc-password.mydb"));
-    }
-
-    @Test
-    public void getSecretCachesPreventsSecondAwsCall() throws GeneralException {
-        SecretsManagerClient client = clientReturning("jdbc-password.mydb", "s3cr3t");
-        AwsSecretsManagerProvider provider = new AwsSecretsManagerProvider(client, ONE_HOUR_MS, "", "");
-
-        provider.getSecret("jdbc-password.mydb");
-        provider.getSecret("jdbc-password.mydb"); // second call — should hit cache
-
-        verify(client, times(1)).getSecretValue(any(GetSecretValueRequest.class));
-    }
-
-    @Test
-    public void getSecretAppliesSecretNamePrefix() throws GeneralException {
-        SecretsManagerClient client = mock(SecretsManagerClient.class);
-        ArgumentCaptor<GetSecretValueRequest> captor = ArgumentCaptor.forClass(GetSecretValueRequest.class);
-        when(client.getSecretValue(captor.capture()))
-                .thenReturn(response("val"));
-
-        AwsSecretsManagerProvider provider = new AwsSecretsManagerProvider(client, ONE_HOUR_MS, "myapp/prod/", "");
-        provider.getSecret("jdbc-password.ofbiz");
-
-        assertEquals("myapp/prod/jdbc-password.ofbiz", captor.getValue().secretId());
-    }
-
-    @Test
-    public void getSecretExtractsJsonFieldWhenConfigured() throws GeneralException {
-        String json = "{\"username\":\"dbuser\",\"password\":\"dbpass\"}";
-        SecretsManagerClient client = clientReturning("jdbc-password.mydb", json);
-        AwsSecretsManagerProvider provider = new AwsSecretsManagerProvider(client, ONE_HOUR_MS, "", "password");
-
-        assertEquals("dbpass", provider.getSecret("jdbc-password.mydb"));
-    }
-
-    @Test
-    public void getSecretUsesRawStringWhenNoJsonFieldConfigured() throws GeneralException {
-        String rawPassword = "plain-text-password";
-        SecretsManagerClient client = clientReturning("jdbc-password.mydb", rawPassword);
-        AwsSecretsManagerProvider provider = new AwsSecretsManagerProvider(client, ONE_HOUR_MS, "", "");
-
-        assertEquals("plain-text-password", provider.getSecret("jdbc-password.mydb"));
-    }
-
-    // -- Cache invalidation --
-
-    @Test
-    public void invalidateCacheForcesRefetchOnNextCall() throws GeneralException {
-        SecretsManagerClient client = clientReturning("jdbc-password.mydb", "val");
-        AwsSecretsManagerProvider provider = new AwsSecretsManagerProvider(client, ONE_HOUR_MS, "", "");
-
-        provider.getSecret("jdbc-password.mydb");
-        provider.invalidateCache();
-        provider.getSecret("jdbc-password.mydb");
-
-        verify(client, times(2)).getSecretValue(any(GetSecretValueRequest.class));
-    }
-
-    @Test
-    public void getSecretExpiredCacheEntryTriggersRefetch() throws GeneralException {
-        SecretsManagerClient client = clientReturning("jdbc-password.mydb", "val");
-        // TTL of -1 ms means the entry expires immediately (expiresAt is in the past)
-        AwsSecretsManagerProvider provider = new AwsSecretsManagerProvider(client, -1L, "", "");
-
-        provider.getSecret("jdbc-password.mydb");
-        provider.getSecret("jdbc-password.mydb"); // cache entry is expired — must re-fetch
-
-        verify(client, times(2)).getSecretValue(any(GetSecretValueRequest.class));
-    }
-
-    // -- Error handling --
-
-    @Test(expected = GeneralException.class)
-    public void getSecretThrowsWhenSecretNotFound() throws GeneralException {
-        SecretsManagerClient client = mock(SecretsManagerClient.class);
-        when(client.getSecretValue(any(GetSecretValueRequest.class)))
-                .thenThrow(ResourceNotFoundException.builder().message("not found").build());
-
-        new AwsSecretsManagerProvider(client, ONE_HOUR_MS, "", "")
-                .getSecret("jdbc-password.missing");
-    }
-
-    @Test(expected = GeneralException.class)
-    public void getSecretThrowsWhenJsonFieldMissing() throws GeneralException {
-        String json = "{\"username\":\"dbuser\"}"; // no "password" field
-        SecretsManagerClient client = clientReturning("jdbc-password.mydb", json);
-
-        new AwsSecretsManagerProvider(client, ONE_HOUR_MS, "", "password")
-                .getSecret("jdbc-password.mydb");
-    }
-
-    @Test(expected = GeneralException.class)
-    public void getSecretThrowsWhenSecretStringIsNull() throws GeneralException {
-        SecretsManagerClient client = mock(SecretsManagerClient.class);
-        // secretString() returns null — this is a binary secret
-        when(client.getSecretValue(any(GetSecretValueRequest.class)))
-                .thenReturn(GetSecretValueResponse.builder().build());
-
-        new AwsSecretsManagerProvider(client, ONE_HOUR_MS, "", "")
-                .getSecret("binary-secret");
-    }
-
-    // -- helpers --
-
-    private static SecretsManagerClient clientReturning(String secretId, String secretValue) {
-        SecretsManagerClient client = mock(SecretsManagerClient.class);
-        when(client.getSecretValue(any(GetSecretValueRequest.class)))
-                .thenReturn(response(secretValue));
-        return client;
-    }
-
-    private static GetSecretValueResponse response(String secretValue) {
-        return GetSecretValueResponse.builder().secretString(secretValue).build();
-    }
-}
diff --git a/azure-keyvault-secrets-provider/build.gradle b/azure-keyvault-secrets-provider/build.gradle
deleted file mode 100644
index 9f36ae2e2..000000000
--- a/azure-keyvault-secrets-provider/build.gradle
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-// Azure SDK for Java — Key Vault Secrets (MIT License)
-// https://github.com/Azure/azure-sdk-for-java
-dependencies {
-    pluginLibsCompile 'com.azure:azure-security-keyvault-secrets:4.8.0'
-    pluginLibsCompile 'com.azure:azure-identity:1.12.0'
-}
-
-configurations.all {
-    exclude group: 'commons-logging', module: 'commons-logging'
-}
diff --git a/azure-keyvault-secrets-provider/config/azure-keyvault.properties b/azure-keyvault-secrets-provider/config/azure-keyvault.properties
deleted file mode 100644
index fd8f262a6..000000000
--- a/azure-keyvault-secrets-provider/config/azure-keyvault.properties
+++ /dev/null
@@ -1,39 +0,0 @@
-###############################################################################
-# Azure Key Vault — SecretProvider configuration
-#
-# To activate this provider:
-#   1. Set enabled="true" in azure-keyvault-secrets-provider/ofbiz-component.xml
-#   2. Set enabled="false" on every other *-secrets-provider plugin
-###############################################################################
-
-# Full URL of your Azure Key Vault (required)
-# Example: https://my-vault.vault.azure.net
-azure.keyvault.url=
-
-# Authentication method:
-#   default       — Uses DefaultAzureCredential: tries Managed Identity, env vars,
-#                   Azure CLI, Visual Studio Code, etc. in order. Recommended for
-#                   Azure-hosted deployments (AKS, App Service, VMs with Managed Identity).
-#   client_secret — Authenticates as a service principal using tenant + client credentials.
-#                   Use for on-premise or non-Azure deployments.
-azure.auth.method=default
-
-# Required only when azure.auth.method=client_secret
-azure.tenant.id=
-azure.client.id=
-azure.client.secret=
-
-# Optional prefix prepended to every secret name before lookup.
-# Example: "prod-" makes key "db" → "prod-db"
-azure.secret.name.prefix=
-
-# Azure Key Vault secret names may only contain letters, digits and hyphens.
-# OFBiz keys like "jdbc-password.mysql-ofbiz" contain a dot, which is invalid.
-# Set this to the replacement character (default: -) so the dot is substituted.
-# Example: "jdbc-password.mysql-ofbiz" → "jdbc-password-mysql-ofbiz"
-# Set to empty to disable replacement (only do this if your keys have no dots).
-azure.secret.name.dot.replacement=-
-
-# In-memory cache TTL in seconds (default: 3600 = 1 hour).
-# Set to 0 to disable caching (fetches from Azure on every call).
-azure.cache.ttl.seconds=3600
diff --git a/azure-keyvault-secrets-provider/ofbiz-component.xml b/azure-keyvault-secrets-provider/ofbiz-component.xml
deleted file mode 100644
index 65ed6fdfd..000000000
--- a/azure-keyvault-secrets-provider/ofbiz-component.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
--->
-
-<ofbiz-component name="azure-keyvault-secrets-provider" enabled="false"
-        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:noNamespaceSchemaLocation="https://ofbiz.apache.org/dtds/ofbiz-component.xsd">
-
-    <resource-loader name="main" type="component"/>
-
-    <!-- Puts config/ on the classpath so azure-keyvault.properties is found by UtilProperties -->
-    <classpath type="dir" location="config"/>
-
-</ofbiz-component>
diff --git a/azure-keyvault-secrets-provider/src/main/java/org/apache/ofbiz/azurekeyvault/AzureKeyVaultReader.java b/azure-keyvault-secrets-provider/src/main/java/org/apache/ofbiz/azurekeyvault/AzureKeyVaultReader.java
deleted file mode 100644
index a268df676..000000000
--- a/azure-keyvault-secrets-provider/src/main/java/org/apache/ofbiz/azurekeyvault/AzureKeyVaultReader.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*******************************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- *******************************************************************************/
-package org.apache.ofbiz.azurekeyvault;
-
-/**
- * Thin seam over Azure Key Vault reads used by {@link AzureKeyVaultSecretsProvider}.
- * Kept package-private so tests can substitute a lambda without connecting to Azure.
- */
-@FunctionalInterface
-interface AzureKeyVaultReader {
-    /**
-     * Retrieves the current value of the named secret.
-     *
-     * @param secretName the Key Vault secret name (letters, digits and hyphens only)
-     * @return the plaintext secret value; never {@code null}
-     * @throws Exception if the read fails or the secret does not exist
-     */
-    String read(String secretName) throws Exception;
-}
diff --git a/azure-keyvault-secrets-provider/src/main/java/org/apache/ofbiz/azurekeyvault/AzureKeyVaultSecretsProvider.java b/azure-keyvault-secrets-provider/src/main/java/org/apache/ofbiz/azurekeyvault/AzureKeyVaultSecretsProvider.java
deleted file mode 100644
index b32a8b2d3..000000000
--- a/azure-keyvault-secrets-provider/src/main/java/org/apache/ofbiz/azurekeyvault/AzureKeyVaultSecretsProvider.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*******************************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- *******************************************************************************/
-package org.apache.ofbiz.azurekeyvault;
-
-import java.util.concurrent.ConcurrentHashMap;
-
-import com.azure.core.credential.TokenCredential;
-import com.azure.identity.ClientSecretCredentialBuilder;
-import com.azure.identity.DefaultAzureCredentialBuilder;
-import com.azure.security.keyvault.secrets.SecretClient;
-import com.azure.security.keyvault.secrets.SecretClientBuilder;
-
-import org.apache.ofbiz.base.lang.ThreadSafe;
-import org.apache.ofbiz.base.secret.SecretProvider;
-import org.apache.ofbiz.base.util.Debug;
-import org.apache.ofbiz.base.util.GeneralException;
-import org.apache.ofbiz.base.util.UtilProperties;
-
-/**
- * {@link SecretProvider} implementation backed by Azure Key Vault.
- *
- * <h2>Authentication</h2>
- * <p>Controlled by {@code azure.auth.method}:</p>
- * <ul>
- *   <li><strong>default</strong> — Uses {@code DefaultAzureCredential}, which tries
- *       Managed Identity, environment variables, Azure CLI, and more in sequence.
- *       Recommended for Azure-hosted deployments (AKS, App Service, VMs with MI).</li>
- *   <li><strong>client_secret</strong> — Authenticates as a service principal using
- *       {@code azure.tenant.id}, {@code azure.client.id}, and {@code azure.client.secret}.
- *       Suitable for on-premise or multi-cloud deployments.</li>
- * </ul>
- *
- * <h2>Secret name mapping</h2>
- * <p>Azure Key Vault secret names may only contain letters, digits and hyphens.
- * OFBiz keys contain dots (e.g. {@code jdbc-password.mysql-ofbiz}), which are invalid.
- * Set {@code azure.secret.name.dot.replacement=-} (the default) to replace dots with
- * hyphens, so the key maps to {@code jdbc-password-mysql-ofbiz} in the vault.</p>
- *
- * <p>Configure via {@code plugins/azure-keyvault-secrets-provider/config/azure-keyvault.properties}.</p>
- */
-@ThreadSafe
-public final class AzureKeyVaultSecretsProvider implements SecretProvider {
-
-    private static final String MODULE = AzureKeyVaultSecretsProvider.class.getName();
-    private static final String CONFIG_RESOURCE = "azure-keyvault";
-
-    private final AzureKeyVaultReader vaultReader;
-    private final String secretNamePrefix;
-    private final String dotReplacement;
-    private final long cacheTtlMs;
-
-    private final ConcurrentHashMap<String, CacheEntry> cache = new ConcurrentHashMap<>();
-
-    private static final class CacheEntry {
-        final String value;
-        final long expiresAt;
-
-        CacheEntry(String value, long ttlMs) {
-            this.value = value;
-            this.expiresAt = System.currentTimeMillis() + ttlMs;
-        }
-
-        boolean isExpired() {
-            return System.currentTimeMillis() >= expiresAt;
-        }
-    }
-
-    /** Public no-arg constructor required by {@link java.util.ServiceLoader}. */
-    public AzureKeyVaultSecretsProvider() {
-        this(readerFrom(buildClient()),
-                prop("azure.secret.name.prefix", ""),
-                prop("azure.secret.name.dot.replacement", "-"),
-                readTtlMs());
-    }
-
-    /** Package-private constructor used by unit tests to inject an {@link AzureKeyVaultReader} lambda. */
-    AzureKeyVaultSecretsProvider(AzureKeyVaultReader vaultReader, String secretNamePrefix,
-            String dotReplacement, long cacheTtlMs) {
-        this.vaultReader = vaultReader;
-        this.secretNamePrefix = secretNamePrefix;
-        this.dotReplacement = dotReplacement;
-        this.cacheTtlMs = cacheTtlMs;
-    }
-
-    @Override
-    public String getSecret(String key) throws GeneralException {
-        CacheEntry cached = cache.get(key);
-        if (cached != null && !cached.isExpired()) {
-            return cached.value;
-        }
-
-        String sanitizedKey = dotReplacement.isEmpty() ? key : key.replace(".", dotReplacement);
-        String secretName = secretNamePrefix + sanitizedKey;
-
-        String value;
-        try {
-            value = vaultReader.read(secretName);
-        } catch (Exception e) {
-            throw new GeneralException(
-                    "Azure Key Vault read failed for secret '" + secretName + "': " + e.getMessage(), e);
-        }
-
-        if (value == null || value.isEmpty()) {
-            throw new GeneralException(
-                    "Azure Key Vault returned empty value for secret '" + secretName + "'");
-        }
-
-        cache.put(key, new CacheEntry(value, cacheTtlMs));
-        return value;
-    }
-
-    /**
-     * Clears the in-memory cache, forcing the next {@link #getSecret(String)} call
-     * to re-fetch from Azure Key Vault. Useful after a secret rotation.
-     */
-    public void invalidateCache() {
-        cache.clear();
-        Debug.logInfo("AzureKeyVaultSecretsProvider: secret cache invalidated", MODULE);
-    }
-
-    // -- private helpers --
-
-    private static AzureKeyVaultReader readerFrom(SecretClient client) {
-        return secretName -> client.getSecret(secretName).getValue();
-    }
-
-    private static SecretClient buildClient() {
-        String vaultUrl = prop("azure.keyvault.url", "");
-        String authMethod = prop("azure.auth.method", "default");
-
-        TokenCredential credential;
-        if ("client_secret".equalsIgnoreCase(authMethod)) {
-            credential = new ClientSecretCredentialBuilder()
-                    .tenantId(prop("azure.tenant.id", ""))
-                    .clientId(prop("azure.client.id", ""))
-                    .clientSecret(prop("azure.client.secret", ""))
-                    .build();
-        } else {
-            credential = new DefaultAzureCredentialBuilder().build();
-        }
-
-        Debug.logInfo("AzureKeyVaultSecretsProvider: initialized vault=" + vaultUrl
-                + " auth=" + authMethod, MODULE);
-
-        return new SecretClientBuilder()
-                .vaultUrl(vaultUrl)
-                .credential(credential)
-                .buildClient();
-    }
-
-    private static long readTtlMs() {
-        String raw = prop("azure.cache.ttl.seconds", "3600");
-        try {
-            return Long.parseLong(raw.trim()) * 1000L;
-        } catch (NumberFormatException e) {
-            Debug.logWarning("Invalid azure.cache.ttl.seconds '" + raw + "', defaulting to 3600s", MODULE);
-            return 3_600_000L;
-        }
-    }
-
-    private static String prop(String key, String defaultValue) {
-        return UtilProperties.getPropertyValue(CONFIG_RESOURCE, key, defaultValue);
-    }
-}
diff --git a/azure-keyvault-secrets-provider/src/main/resources/META-INF/services/org.apache.ofbiz.base.secret.SecretProvider b/azure-keyvault-secrets-provider/src/main/resources/META-INF/services/org.apache.ofbiz.base.secret.SecretProvider
deleted file mode 100644
index 3aac1713e..000000000
--- a/azure-keyvault-secrets-provider/src/main/resources/META-INF/services/org.apache.ofbiz.base.secret.SecretProvider
+++ /dev/null
@@ -1 +0,0 @@
-org.apache.ofbiz.azurekeyvault.AzureKeyVaultSecretsProvider
diff --git a/azure-keyvault-secrets-provider/src/test/java/org/apache/ofbiz/azurekeyvault/AzureKeyVaultSecretsProviderTest.java b/azure-keyvault-secrets-provider/src/test/java/org/apache/ofbiz/azurekeyvault/AzureKeyVaultSecretsProviderTest.java
deleted file mode 100644
index 9f036f2dc..000000000
--- a/azure-keyvault-secrets-provider/src/test/java/org/apache/ofbiz/azurekeyvault/AzureKeyVaultSecretsProviderTest.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*******************************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- *******************************************************************************/
-package org.apache.ofbiz.azurekeyvault;
-
-import static org.junit.Assert.assertEquals;
-
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.apache.ofbiz.base.util.GeneralException;
-import org.junit.Test;
-
-public class AzureKeyVaultSecretsProviderTest {
-
-    private static final long ONE_HOUR_MS = 3_600_000L;
-
-    // -- Happy path --
-
-    @Test
-    public void getSecret_returnsSecretValue() throws GeneralException {
-        AzureKeyVaultReader reader = fixedReader("mykey", "s3cr3t");
-        assertEquals("s3cr3t", provider(reader, "", "-").getSecret("mykey"));
-    }
-
-    @Test
-    public void getSecret_sanitizesDotInKey() throws GeneralException {
-        // OFBiz key "jdbc-password.mysql-ofbiz" → Azure name "jdbc-password-mysql-ofbiz"
-        AzureKeyVaultReader reader = fixedReader("jdbc-password-mysql-ofbiz", "dbpass");
-        assertEquals("dbpass", provider(reader, "", "-").getSecret("jdbc-password.mysql-ofbiz"));
-    }
-
-    @Test
-    public void getSecret_appliesPrefixAfterSanitizing() throws GeneralException {
-        AzureKeyVaultReader reader = fixedReader("prod-jdbc-password-mydb", "prodpass");
-        assertEquals("prodpass", provider(reader, "prod-", "-").getSecret("jdbc-password.mydb"));
-    }
-
-    @Test
-    public void getSecret_cachePreventsDuplicateReaderCall() throws GeneralException {
-        AtomicInteger calls = new AtomicInteger();
-        AzureKeyVaultReader reader = secretName -> {
-            calls.incrementAndGet();
-            return "val";
-        };
-        AzureKeyVaultSecretsProvider p = provider(reader, "", "-");
-
-        p.getSecret("mykey");
-        p.getSecret("mykey"); // cache hit
-
-        assertEquals(1, calls.get());
-    }
-
-    @Test
-    public void invalidateCache_forcesRefetchOnNextCall() throws GeneralException {
-        AtomicInteger calls = new AtomicInteger();
-        AzureKeyVaultReader reader = secretName -> {
-            calls.incrementAndGet();
-            return "val";
-        };
-        AzureKeyVaultSecretsProvider p = provider(reader, "", "-");
-
-        p.getSecret("mykey");
-        p.invalidateCache();
-        p.getSecret("mykey");
-
-        assertEquals(2, calls.get());
-    }
-
-    @Test
-    public void getSecret_expiredCacheTriggersRefetch() throws GeneralException {
-        AtomicInteger calls = new AtomicInteger();
-        AzureKeyVaultReader reader = secretName -> {
-            calls.incrementAndGet();
-            return "val";
-        };
-        // TTL of -1 ms — entries expire immediately
-        AzureKeyVaultSecretsProvider p = new AzureKeyVaultSecretsProvider(
-                reader, "", "-", -1L);
-
-        p.getSecret("mykey");
-        p.getSecret("mykey");
-
-        assertEquals(2, calls.get());
-    }
-
-    @Test
-    public void getSecret_dotReplacementDisabled_keepsDotsInName() throws GeneralException {
-        AzureKeyVaultReader reader = fixedReader("my.key", "val");
-        assertEquals("val", provider(reader, "", "").getSecret("my.key"));
-    }
-
-    // -- Error handling --
-
-    @Test(expected = GeneralException.class)
-    public void getSecret_throwsOnReaderException() throws GeneralException {
-        AzureKeyVaultReader reader = secretName -> { throw new RuntimeException("SecretNotFound"); };
-        provider(reader, "", "-").getSecret("missing");
-    }
-
-    @Test(expected = GeneralException.class)
-    public void getSecret_throwsOnEmptyValue() throws GeneralException {
-        AzureKeyVaultReader reader = secretName -> "";
-        provider(reader, "", "-").getSecret("mykey");
-    }
-
-    // -- helpers --
-
-    private static AzureKeyVaultSecretsProvider provider(AzureKeyVaultReader reader,
-            String prefix, String dotReplacement) {
-        return new AzureKeyVaultSecretsProvider(reader, prefix, dotReplacement, ONE_HOUR_MS);
-    }
-
-    private static AzureKeyVaultReader fixedReader(String expectedName, String value) {
-        return secretName -> expectedName.equals(secretName) ? value : null;
-    }
-}
diff --git a/bitwarden-secrets-provider/build.gradle b/bitwarden-secrets-provider/build.gradle
deleted file mode 100644
index be905aee6..000000000
--- a/bitwarden-secrets-provider/build.gradle
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-// No external SDK required.
-// Uses Java's built-in java.net.http.HttpClient (Java 11+), javax.crypto for
-// AES-256-CBC + HMAC-SHA256 decryption, and OFBiz's bundled jackson-databind.
-//
-// Bitwarden Secrets Manager encrypts all data end-to-end. The symmetric key
-// embedded in the machine account access token is used to decrypt secret names
-// and values client-side.
-dependencies {}
diff --git a/bitwarden-secrets-provider/config/bitwarden-secrets.properties b/bitwarden-secrets-provider/config/bitwarden-secrets.properties
deleted file mode 100644
index 6552fa9c3..000000000
--- a/bitwarden-secrets-provider/config/bitwarden-secrets.properties
+++ /dev/null
@@ -1,59 +0,0 @@
-###############################################################################
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-###############################################################################
-
-####
-# Bitwarden Secrets Manager — SecretProvider configuration
-#
-# Requires a Bitwarden Secrets Manager machine account access token.
-# Create one in the Bitwarden SM console under Machine Accounts.
-#
-# The access token embeds an AES-256 symmetric key used to decrypt
-# secret names and values client-side (end-to-end encryption).
-#
-# NOTE: Only ONE SecretProvider plugin may be active at a time.
-# Deploy only the plugin that matches your environment.
-####
-
-# Bitwarden Secrets Manager REST API base URL.
-# Use https://api.bitwarden.eu for EU cloud, or your self-hosted URL.
-bitwarden.api.url=https://api.bitwarden.com
-
-# Bitwarden Identity Service URL (used for machine account OAuth token exchange).
-bitwarden.identity.url=https://identity.bitwarden.com
-
-# Machine account access token.
-# Format:  0.<serviceAccountId>.<clientSecret>:<base64EncKey>
-# Inject at deploy time — do not commit a real token.
-bitwarden.access.token=
-
-# UUID of the organization that owns the secrets.
-bitwarden.organization.id=
-
-# Optional prefix prepended to the OFBiz key when matching the Bitwarden secret key (name).
-# Example: with prefix "myapp/" the OFBiz key "jdbc-password.ofbiz" matches a
-#          Bitwarden secret whose decrypted key is "myapp/jdbc-password.ofbiz".
-bitwarden.secret.name.prefix=
-
-# How long (in seconds) to cache resolved secret values before re-fetching.
-# Default: 3600 (1 hour). Set to 0 to disable caching.
-bitwarden.cache.ttl.seconds=3600
-
-# HTTP connect and read timeouts in seconds.
-bitwarden.connect.timeout.seconds=5
-bitwarden.read.timeout.seconds=10
diff --git a/bitwarden-secrets-provider/ofbiz-component.xml b/bitwarden-secrets-provider/ofbiz-component.xml
deleted file mode 100644
index c5c3a8aec..000000000
--- a/bitwarden-secrets-provider/ofbiz-component.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
--->
-
-<ofbiz-component name="bitwarden-secrets-provider" enabled="false"
-        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:noNamespaceSchemaLocation="https://ofbiz.apache.org/dtds/ofbiz-component.xsd">
-
-    <resource-loader name="main" type="component"/>
-
-    <!-- Puts config/ on the classpath so bitwarden-secrets.properties is found by UtilProperties -->
-    <classpath type="dir" location="config"/>
-
-</ofbiz-component>
diff --git a/bitwarden-secrets-provider/src/main/java/org/apache/ofbiz/bitwarden/BitwardenHttpClient.java b/bitwarden-secrets-provider/src/main/java/org/apache/ofbiz/bitwarden/BitwardenHttpClient.java
deleted file mode 100644
index 64260a519..000000000
--- a/bitwarden-secrets-provider/src/main/java/org/apache/ofbiz/bitwarden/BitwardenHttpClient.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*******************************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- *******************************************************************************/
-package org.apache.ofbiz.bitwarden;
-
-import java.io.IOException;
-
-/**
- * Thin seam over HTTP calls used by {@link BitwardenSecretsProvider}.
- * Kept package-private so tests can substitute a mock without making real
- * network calls.
- */
-interface BitwardenHttpClient {
-
-    /**
-     * Performs an authenticated GET request.
-     *
-     * @param url         the full request URL
-     * @param bearerToken the OAuth bearer token
-     * @return the response body as a UTF-8 string
-     * @throws IOException if the request fails or the server returns a non-2xx status
-     */
-    String get(String url, String bearerToken) throws IOException;
-
-    /**
-     * Performs a form-encoded POST request (no auth header — used for token exchange).
-     *
-     * @param url      the full request URL
-     * @param formBody {@code application/x-www-form-urlencoded} encoded body
-     * @return the response body as a UTF-8 string
-     * @throws IOException if the request fails or the server returns a non-2xx status
-     */
-    String post(String url, String formBody) throws IOException;
-}
diff --git a/bitwarden-secrets-provider/src/main/java/org/apache/ofbiz/bitwarden/BitwardenSecretsProvider.java b/bitwarden-secrets-provider/src/main/java/org/apache/ofbiz/bitwarden/BitwardenSecretsProvider.java
deleted file mode 100644
index ed94ea7a8..000000000
--- a/bitwarden-secrets-provider/src/main/java/org/apache/ofbiz/bitwarden/BitwardenSecretsProvider.java
+++ /dev/null
@@ -1,461 +0,0 @@
-/*******************************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- *******************************************************************************/
-package org.apache.ofbiz.bitwarden;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.http.HttpClient;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
-import java.nio.charset.StandardCharsets;
-import java.security.MessageDigest;
-import java.time.Duration;
-import java.util.Arrays;
-import java.util.Base64;
-import java.util.concurrent.ConcurrentHashMap;
-
-import javax.crypto.Cipher;
-import javax.crypto.Mac;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-import org.apache.ofbiz.base.lang.ThreadSafe;
-import org.apache.ofbiz.base.secret.SecretProvider;
-import org.apache.ofbiz.base.util.Debug;
-import org.apache.ofbiz.base.util.GeneralException;
-import org.apache.ofbiz.base.util.UtilProperties;
-
-/**
- * {@link SecretProvider} implementation backed by Bitwarden Secrets Manager.
- *
- * <h2>Authentication</h2>
- * <p>Uses a machine account <em>access token</em> (created in the Bitwarden SM console).
- * The token has the format {@code 0.<serviceAccountId>.<clientSecret>:<base64EncKey>}.
- * The provider parses the token to extract OAuth credentials for the identity service
- * and the 64-byte symmetric key for client-side decryption.</p>
- *
- * <h2>End-to-end encryption</h2>
- * <p>Bitwarden SM encrypts all data at rest and in transit. Secret <em>keys</em> (names)
- * and <em>values</em> are returned from the API in encrypted form. This provider decrypts
- * them using AES-256-CBC with HMAC-SHA256 integrity verification before returning the
- * plaintext to OFBiz. No plaintext ever leaves the JVM.</p>
- *
- * <h2>Cipher format</h2>
- * <p>Encrypted strings use Bitwarden's type-2 cipher format:
- * {@code 2.<iv_base64>|<ciphertext_base64>|<hmac_base64>}</p>
- * <ul>
- *   <li>MAC key (bytes 32–63 of the 64-byte symmetric key) verifies integrity via
- *       HMAC-SHA256 over {@code IV || ciphertext}.</li>
- *   <li>Enc key (bytes 0–31) decrypts with AES-256-CBC after MAC is verified.</li>
- * </ul>
- *
- * <h2>API flow per lookup</h2>
- * <ol>
- *   <li>POST to identity service to exchange client credentials for a bearer token.</li>
- *   <li>GET the organization's secret list; decrypt each secret key to find the match.</li>
- *   <li>GET the matched secret by ID; decrypt and return the value.</li>
- * </ol>
- *
- * <p>Configure via {@code plugins/bitwarden-secrets-provider/config/bitwarden-secrets.properties}.</p>
- */
-@ThreadSafe
-public final class BitwardenSecretsProvider implements SecretProvider {
-
-    private static final String MODULE = BitwardenSecretsProvider.class.getName();
-    private static final String CONFIG_RESOURCE = "bitwarden-secrets";
-    private static final ObjectMapper JSON = new ObjectMapper();
-
-    private final BitwardenHttpClient httpClient;
-    private final String apiUrl;
-    private final String identityUrl;
-    private final String organizationId;
-    private final String secretNamePrefix;
-    private final long cacheTtlMs;
-
-    // Parsed from the access token — never stored in config
-    private final String oauthClientId;
-    private final String oauthClientSecret;
-    private final byte[] encKey; // bytes  0-31 of the 64-byte symmetric key
-    private final byte[] macKey; // bytes 32-63
-
-    // Cached OAuth bearer token + its expiry
-    private volatile String bearerToken = null;
-    private volatile long bearerTokenExpiresAt = 0L;
-
-    private final ConcurrentHashMap<String, CacheEntry> cache = new ConcurrentHashMap<>();
-
-    private static final class CacheEntry {
-        final String value;
-        final long expiresAt;
-
-        CacheEntry(String value, long ttlMs) {
-            this.value = value;
-            this.expiresAt = System.currentTimeMillis() + ttlMs;
-        }
-
-        boolean isExpired() {
-            return System.currentTimeMillis() >= expiresAt;
-        }
-    }
-
-    /** Public no-arg constructor required by {@link java.util.ServiceLoader}. */
-    public BitwardenSecretsProvider() throws GeneralException {
-        this(buildHttpClient(),
-                prop("bitwarden.api.url", "https://api.bitwarden.com").replaceAll("/+$", ""),
-                prop("bitwarden.identity.url", "https://identity.bitwarden.com").replaceAll("/+$", ""),
-                prop("bitwarden.organization.id", ""),
-                prop("bitwarden.secret.name.prefix", ""),
-                readTtlMs(),
-                prop("bitwarden.access.token", ""));
-    }
-
-    /**
-     * Package-private constructor used by unit tests to inject mock HTTP client and
-     * pre-parsed key material.
-     */
-    BitwardenSecretsProvider(BitwardenHttpClient httpClient, String apiUrl, String identityUrl,
-            String organizationId, String secretNamePrefix, long cacheTtlMs,
-            String clientId, String clientSecret, byte[] encKey, byte[] macKey) {
-        this.httpClient = httpClient;
-        this.apiUrl = apiUrl;
-        this.identityUrl = identityUrl;
-        this.organizationId = organizationId;
-        this.secretNamePrefix = secretNamePrefix;
-        this.cacheTtlMs = cacheTtlMs;
-        this.oauthClientId = clientId;
-        this.oauthClientSecret = clientSecret;
-        this.encKey = encKey;
-        this.macKey = macKey;
-    }
-
-    /** Full constructor called by the public no-arg constructor after parsing the token. */
-    private BitwardenSecretsProvider(BitwardenHttpClient httpClient, String apiUrl, String identityUrl,
-            String organizationId, String secretNamePrefix, long cacheTtlMs,
-            String rawAccessToken) throws GeneralException {
-        this.httpClient = httpClient;
-        this.apiUrl = apiUrl;
-        this.identityUrl = identityUrl;
-        this.organizationId = organizationId;
-        this.secretNamePrefix = secretNamePrefix;
-        this.cacheTtlMs = cacheTtlMs;
-
-        // Parse: "0.<serviceAccountId>.<clientSecret>:<base64EncKey>"
-        int colonIdx = rawAccessToken.lastIndexOf(':');
-        if (colonIdx < 0) {
-            throw new GeneralException(
-                    "Invalid Bitwarden access token format — missing ':' separator");
-        }
-        String identityPart = rawAccessToken.substring(0, colonIdx);
-        String encKeyBase64 = rawAccessToken.substring(colonIdx + 1);
-
-        String[] dotParts = identityPart.split("\\.");
-        if (dotParts.length != 3 || !"0".equals(dotParts[0])) {
-            throw new GeneralException(
-                    "Invalid Bitwarden access token format — expected '0.<id>.<secret>:<key>'");
-        }
-        String serviceAccountId = dotParts[1];
-        String clientSecretPart = dotParts[2];
-
-        this.oauthClientId = "service-account." + serviceAccountId;
-        this.oauthClientSecret = clientSecretPart;
-
-        byte[] keyBytes;
-        try {
-            keyBytes = Base64.getDecoder().decode(encKeyBase64);
-        } catch (IllegalArgumentException e) {
-            throw new GeneralException("Invalid Bitwarden access token — enc key is not valid base64", e);
-        }
-        if (keyBytes.length != 64) {
-            throw new GeneralException("Invalid Bitwarden access token — enc key must be 64 bytes, got "
-                    + keyBytes.length);
-        }
-        this.encKey = Arrays.copyOfRange(keyBytes, 0, 32);
-        this.macKey = Arrays.copyOfRange(keyBytes, 32, 64);
-
-        Debug.logInfo("BitwardenSecretsProvider: initialized api=" + apiUrl
-                + " org=" + organizationId, MODULE);
-    }
-
-    @Override
-    public String getSecret(String key) throws GeneralException {
-        CacheEntry cached = cache.get(key);
-        if (cached != null && !cached.isExpired()) {
-            return cached.value;
-        }
-
-        String secretName = secretNamePrefix + key;
-        String value = fetchFromBitwarden(secretName);
-
-        cache.put(key, new CacheEntry(value, cacheTtlMs));
-        return value;
-    }
-
-    /**
-     * Clears the in-memory cache, forcing the next {@link #getSecret(String)} call
-     * to re-fetch and re-decrypt from Bitwarden SM.
-     */
-    public void invalidateCache() {
-        cache.clear();
-        Debug.logInfo("BitwardenSecretsProvider: secret cache invalidated", MODULE);
-    }
-
-    // -- private helpers --
-
-    private String fetchFromBitwarden(String secretName) throws GeneralException {
-        String bearer = ensureBearerToken();
-        String secretId = findSecretId(secretName, bearer);
-        return fetchAndDecryptValue(secretId, secretName, bearer);
-    }
-
-    /** Returns a valid OAuth bearer token, refreshing it if expired. */
-    private synchronized String ensureBearerToken() throws GeneralException {
-        if (bearerToken != null && System.currentTimeMillis() < bearerTokenExpiresAt) {
-            return bearerToken;
-        }
-
-        String body = "grant_type=client_credentials"
-                + "&scope=api.secrets"
-                + "&client_id=" + oauthClientId
-                + "&client_secret=" + oauthClientSecret;
-
-        String responseJson;
-        try {
-            responseJson = httpClient.post(identityUrl + "/connect/token", body);
-        } catch (IOException e) {
-            throw new GeneralException("Bitwarden identity token request failed: " + e.getMessage(), e);
-        }
-
-        try {
-            JsonNode root = JSON.readTree(responseJson);
-            JsonNode tokenNode = root.get("access_token");
-            JsonNode expiresNode = root.get("expires_in");
-            if (tokenNode == null || tokenNode.isNull()) {
-                throw new GeneralException("Bitwarden identity response missing 'access_token'");
-            }
-            bearerToken = tokenNode.asText();
-            long expiresIn = expiresNode != null ? expiresNode.asLong(3600L) : 3600L;
-            // Subtract 60 s to renew slightly before expiry
-            bearerTokenExpiresAt = System.currentTimeMillis() + (expiresIn - 60L) * 1000L;
-            return bearerToken;
-        } catch (GeneralException e) {
-            throw e;
-        } catch (Exception e) {
-            throw new GeneralException("Failed to parse Bitwarden identity response: " + e.getMessage(), e);
-        }
-    }
-
-    /** Lists the organization's secrets and returns the ID of the one matching secretName. */
-    private String findSecretId(String secretName, String bearer) throws GeneralException {
-        String listUrl = apiUrl + "/organizations/" + organizationId + "/secrets";
-        String responseJson;
-        try {
-            responseJson = httpClient.get(listUrl, bearer);
-        } catch (IOException e) {
-            throw new GeneralException("Bitwarden secrets list request failed: " + e.getMessage(), e);
-        }
-
-        try {
-            JsonNode root = JSON.readTree(responseJson);
-            JsonNode data = root.get("data");
-            if (data == null || !data.isArray()) {
-                throw new GeneralException("Bitwarden secrets list response missing 'data' array");
-            }
-
-            for (JsonNode item : data) {
-                JsonNode encKey = item.get("key");
-                if (encKey == null) continue;
-
-                String decryptedKey = decrypt(encKey.asText());
-                if (secretName.equals(decryptedKey)) {
-                    JsonNode idNode = item.get("id");
-                    if (idNode == null || idNode.isNull()) {
-                        throw new GeneralException("Bitwarden secret '" + secretName + "' has no id field");
-                    }
-                    return idNode.asText();
-                }
-            }
-            throw new GeneralException("No Bitwarden secret found with key '" + secretName + "'");
-        } catch (GeneralException e) {
-            throw e;
-        } catch (Exception e) {
-            throw new GeneralException("Failed to parse Bitwarden secrets list: " + e.getMessage(), e);
-        }
-    }
-
-    /** Fetches the secret by ID and decrypts its value. */
-    private String fetchAndDecryptValue(String secretId, String secretName, String bearer)
-            throws GeneralException {
-        String secretUrl = apiUrl + "/secrets/" + secretId;
-        String responseJson;
-        try {
-            responseJson = httpClient.get(secretUrl, bearer);
-        } catch (IOException e) {
-            throw new GeneralException("Bitwarden secret fetch failed for '" + secretName
-                    + "': " + e.getMessage(), e);
-        }
-
-        try {
-            JsonNode root = JSON.readTree(responseJson);
-            JsonNode encValueNode = root.get("value");
-            if (encValueNode == null || encValueNode.isNull()) {
-                throw new GeneralException("Bitwarden secret '" + secretName + "' has no value field");
-            }
-            String plaintext = decrypt(encValueNode.asText());
-            if (plaintext.isEmpty()) {
-                throw new GeneralException("Bitwarden secret '" + secretName + "' decrypted to empty string");
-            }
-            return plaintext;
-        } catch (GeneralException e) {
-            throw e;
-        } catch (Exception e) {
-            throw new GeneralException("Failed to parse Bitwarden secret response: " + e.getMessage(), e);
-        }
-    }
-
-    /**
-     * Decrypts a Bitwarden type-2 cipher string.
-     *
-     * <p>Format: {@code 2.<iv_b64>|<ciphertext_b64>|<hmac_b64>}</p>
-     * <ul>
-     *   <li>HMAC-SHA256 is verified over {@code IV || ciphertext} using {@code macKey}.</li>
-     *   <li>AES-256-CBC decryption uses {@code encKey} and the extracted IV.</li>
-     * </ul>
-     */
-    String decrypt(String cipherString) throws GeneralException {
-        if (!cipherString.startsWith("2.")) {
-            throw new GeneralException(
-                    "Unsupported Bitwarden cipher type — expected type 2 (AES-CBC-256-HMAC-SHA256)");
-        }
-        String[] parts = cipherString.substring(2).split("\\|");
-        if (parts.length != 3) {
-            throw new GeneralException(
-                    "Invalid Bitwarden cipher string — expected '2.<iv>|<ct>|<hmac>'");
-        }
-
-        byte[] iv, ciphertext, expectedHmac;
-        try {
-            iv = Base64.getDecoder().decode(parts[0]);
-            ciphertext = Base64.getDecoder().decode(parts[1]);
-            expectedHmac = Base64.getDecoder().decode(parts[2]);
-        } catch (IllegalArgumentException e) {
-            throw new GeneralException("Invalid base64 in Bitwarden cipher string", e);
-        }
-
-        try {
-            // Verify integrity: HMAC-SHA256(iv || ciphertext, macKey)
-            Mac mac = Mac.getInstance("HmacSHA256");
-            mac.init(new SecretKeySpec(macKey, "HmacSHA256"));
-            mac.update(iv);
-            byte[] computedHmac = mac.doFinal(ciphertext);
-            if (!MessageDigest.isEqual(computedHmac, expectedHmac)) {
-                throw new GeneralException(
-                        "Bitwarden HMAC verification failed — wrong key or tampered ciphertext");
-            }
-
-            // Decrypt: AES-256-CBC
-            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
-            cipher.init(Cipher.DECRYPT_MODE,
-                    new SecretKeySpec(encKey, "AES"),
-                    new IvParameterSpec(iv));
-            return new String(cipher.doFinal(ciphertext), StandardCharsets.UTF_8);
-
-        } catch (GeneralException e) {
-            throw e;
-        } catch (Exception e) {
-            throw new GeneralException("Bitwarden decryption failed: " + e.getMessage(), e);
-        }
-    }
-
-    private static BitwardenHttpClient buildHttpClient() {
-        int connectTimeout = parseSeconds(prop("bitwarden.connect.timeout.seconds", "5"));
-        int readTimeout = parseSeconds(prop("bitwarden.read.timeout.seconds", "10"));
-
-        HttpClient javaClient = HttpClient.newBuilder()
-                .connectTimeout(Duration.ofSeconds(connectTimeout))
-                .build();
-
-        return new BitwardenHttpClient() {
-            @Override
-            public String get(String url, String bearerToken) throws IOException {
-                HttpRequest request = HttpRequest.newBuilder()
-                        .uri(URI.create(url))
-                        .header("Authorization", "Bearer " + bearerToken)
-                        .header("Accept", "application/json")
-                        .timeout(Duration.ofSeconds(readTimeout))
-                        .GET()
-                        .build();
-                return send(request);
-            }
-
-            @Override
-            public String post(String url, String formBody) throws IOException {
-                HttpRequest request = HttpRequest.newBuilder()
-                        .uri(URI.create(url))
-                        .header("Content-Type", "application/x-www-form-urlencoded")
-                        .header("Accept", "application/json")
-                        .timeout(Duration.ofSeconds(readTimeout))
-                        .POST(HttpRequest.BodyPublishers.ofString(formBody, StandardCharsets.UTF_8))
-                        .build();
-                return send(request);
-            }
-
-            private String send(HttpRequest request) throws IOException {
-                HttpResponse<String> response;
-                try {
-                    response = javaClient.send(request,
-                            HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
-                } catch (InterruptedException e) {
-                    Thread.currentThread().interrupt();
-                    throw new IOException("HTTP request interrupted", e);
-                }
-                int status = response.statusCode();
-                if (status < 200 || status >= 300) {
-                    throw new IOException("Bitwarden API returned HTTP " + status
-                            + " for " + request.uri());
-                }
-                return response.body();
-            }
-        };
-    }
-
-    private static int parseSeconds(String raw) {
-        try {
-            return Integer.parseInt(raw.trim());
-        } catch (NumberFormatException e) {
-            return 10;
-        }
-    }
-
-    private static long readTtlMs() {
-        String raw = prop("bitwarden.cache.ttl.seconds", "3600");
-        try {
-            return Long.parseLong(raw.trim()) * 1000L;
-        } catch (NumberFormatException e) {
-            Debug.logWarning("Invalid bitwarden.cache.ttl.seconds '" + raw + "', defaulting to 3600s", MODULE);
-            return 3_600_000L;
-        }
-    }
-
-    private static String prop(String key, String defaultValue) {
-        return UtilProperties.getPropertyValue(CONFIG_RESOURCE, key, defaultValue);
-    }
-}
diff --git a/bitwarden-secrets-provider/src/main/resources/META-INF/services/org.apache.ofbiz.base.secret.SecretProvider b/bitwarden-secrets-provider/src/main/resources/META-INF/services/org.apache.ofbiz.base.secret.SecretProvider
deleted file mode 100644
index 8f6a30013..000000000
--- a/bitwarden-secrets-provider/src/main/resources/META-INF/services/org.apache.ofbiz.base.secret.SecretProvider
+++ /dev/null
@@ -1 +0,0 @@
-org.apache.ofbiz.bitwarden.BitwardenSecretsProvider
diff --git a/bitwarden-secrets-provider/src/test/java/org/apache/ofbiz/bitwarden/BitwardenSecretsProviderTest.java b/bitwarden-secrets-provider/src/test/java/org/apache/ofbiz/bitwarden/BitwardenSecretsProviderTest.java
deleted file mode 100644
index 70e6e0f7b..000000000
--- a/bitwarden-secrets-provider/src/test/java/org/apache/ofbiz/bitwarden/BitwardenSecretsProviderTest.java
+++ /dev/null
@@ -1,220 +0,0 @@
-/*******************************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- *******************************************************************************/
-package org.apache.ofbiz.bitwarden;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.contains;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.security.SecureRandom;
-import java.util.Arrays;
-import java.util.Base64;
-
-import javax.crypto.Cipher;
-import javax.crypto.Mac;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-
-import org.apache.ofbiz.base.util.GeneralException;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-/**
- * Tests for {@link BitwardenSecretsProvider}.
- *
- * <p>A stable 64-byte test key is generated once for the test class.
- * Helper methods produce properly-encrypted cipher strings so the decryption
- * tests exercise the real AES-256-CBC + HMAC-SHA256 path, not just mocked output.</p>
- */
-public class BitwardenSecretsProviderTest {
-
-    private static final long ONE_HOUR_MS = 3_600_000L;
-    private static final String API_URL = "https://api.bitwarden.com";
-    private static final String IDENTITY_URL = "https://identity.bitwarden.com";
-    private static final String ORG_ID = "org-abc";
-    private static final String BEARER_TOKEN_RESPONSE =
-            "{\"access_token\":\"test-bearer\",\"expires_in\":3600}";
-
-    private static byte[] TEST_ENC_KEY;
-    private static byte[] TEST_MAC_KEY;
-
-    @BeforeClass
-    public static void generateTestKey() {
-        byte[] keyBytes = new byte[64];
-        new SecureRandom().nextBytes(keyBytes);
-        TEST_ENC_KEY = Arrays.copyOfRange(keyBytes, 0, 32);
-        TEST_MAC_KEY = Arrays.copyOfRange(keyBytes, 32, 64);
-    }
-
-    // -- Decryption unit tests (no HTTP, pure crypto) --
-
-    @Test
-    public void decrypt_roundTrip() throws Exception {
-        String plaintext = "my-secret-password";
-        String cipherString = encrypt(plaintext);
-
-        BitwardenSecretsProvider provider = provider(mockHttpForSecret("mykey", plaintext));
-        assertEquals(plaintext, provider.decrypt(cipherString));
-    }
-
-    @Test(expected = GeneralException.class)
-    public void decrypt_throwsOnWrongCipherType() throws Exception {
-        BitwardenSecretsProvider provider = provider(mock(BitwardenHttpClient.class));
-        provider.decrypt("1.abc|def|ghi"); // type 1, not 2
-    }
-
-    @Test(expected = GeneralException.class)
-    public void decrypt_throwsOnTamperedCiphertext() throws Exception {
-        String cipherString = encrypt("secret");
-        // Flip a byte in the ciphertext part
-        String[] parts = cipherString.substring(2).split("\\|");
-        byte[] ct = Base64.getDecoder().decode(parts[1]);
-        ct[0] ^= 0xFF;
-        String tampered = "2." + parts[0] + "|" + Base64.getEncoder().encodeToString(ct) + "|" + parts[2];
-
-        BitwardenSecretsProvider provider = provider(mock(BitwardenHttpClient.class));
-        provider.decrypt(tampered);
-    }
-
-    // -- Full flow tests (with mock HTTP) --
-
-    @Test
-    public void getSecret_fetchesAndDecryptsSecret() throws Exception {
-        String secretValue = "db-password-123";
-        BitwardenHttpClient client = mockHttpForSecret("jdbc-password.mydb", secretValue);
-        BitwardenSecretsProvider provider = provider(client);
-
-        assertEquals(secretValue, provider.getSecret("jdbc-password.mydb"));
-    }
-
-    @Test
-    public void getSecret_appliesSecretNamePrefix() throws Exception {
-        String secretValue = "dbpass";
-        BitwardenHttpClient client = mockHttpForSecret("prod/jdbc-password.mydb", secretValue);
-        BitwardenSecretsProvider provider = providerWithPrefix(client, "prod/");
-
-        assertEquals(secretValue, provider.getSecret("jdbc-password.mydb"));
-    }
-
-    @Test
-    public void getSecret_cachePreventsSecondApiCall() throws Exception {
-        BitwardenHttpClient client = mockHttpForSecret("mykey", "val");
-        BitwardenSecretsProvider provider = provider(client);
-
-        provider.getSecret("mykey");
-        provider.getSecret("mykey");
-
-        // Only 1 identity POST + 1 list GET + 1 secret GET = 3 calls for the first fetch
-        verify(client, times(1)).post(anyString(), anyString());
-        verify(client, times(1)).get(contains("/organizations/"), anyString());
-        verify(client, times(1)).get(contains("/secrets/"), anyString());
-    }
-
-    @Test
-    public void invalidateCache_forcesRefetch() throws Exception {
-        BitwardenHttpClient client = mockHttpForSecret("mykey", "val");
-        BitwardenSecretsProvider provider = provider(client);
-
-        provider.getSecret("mykey");
-        provider.invalidateCache();
-        provider.getSecret("mykey");
-
-        // Two full fetches — bearer token is still valid so POST called once
-        verify(client, times(2)).get(contains("/organizations/"), anyString());
-        verify(client, times(2)).get(contains("/secrets/"), anyString());
-    }
-
-    @Test(expected = GeneralException.class)
-    public void getSecret_throwsWhenSecretNotFound() throws Exception {
-        BitwardenHttpClient client = mock(BitwardenHttpClient.class);
-        when(client.post(anyString(), anyString())).thenReturn(BEARER_TOKEN_RESPONSE);
-        when(client.get(contains("/organizations/"), anyString()))
-                .thenReturn("{\"data\":[]}"); // empty list
-
-        provider(client).getSecret("missing");
-    }
-
-    @Test(expected = GeneralException.class)
-    public void getSecret_throwsOnHttpError() throws Exception {
-        BitwardenHttpClient client = mock(BitwardenHttpClient.class);
-        when(client.post(anyString(), anyString())).thenThrow(new IOException("connection refused"));
-
-        provider(client).getSecret("mykey");
-    }
-
-    // -- helpers --
-
-    private BitwardenSecretsProvider provider(BitwardenHttpClient client) throws GeneralException {
-        return providerWithPrefix(client, "");
-    }
-
-    private BitwardenSecretsProvider providerWithPrefix(BitwardenHttpClient client, String prefix)
-            throws GeneralException {
-        return new BitwardenSecretsProvider(client, API_URL, IDENTITY_URL,
-                ORG_ID, prefix, ONE_HOUR_MS,
-                "service-account.test-id", "test-secret", TEST_ENC_KEY, TEST_MAC_KEY);
-    }
-
-    /**
-     * Builds a mock HTTP client that returns a bearer token on POST and serves a
-     * secrets list + secret detail using properly encrypted cipher strings.
-     */
-    private BitwardenHttpClient mockHttpForSecret(String secretKey, String secretValue) throws Exception {
-        String encryptedKey = encrypt(secretKey);
-        String encryptedValue = encrypt(secretValue);
-        String secretId = "secret-id-001";
-
-        BitwardenHttpClient client = mock(BitwardenHttpClient.class);
-        when(client.post(anyString(), anyString())).thenReturn(BEARER_TOKEN_RESPONSE);
-        when(client.get(contains("/organizations/"), eq("test-bearer")))
-                .thenReturn("{\"data\":[{\"id\":\"" + secretId + "\",\"key\":\""
-                        + encryptedKey + "\"}]}");
-        when(client.get(contains("/secrets/" + secretId), eq("test-bearer")))
-                .thenReturn("{\"id\":\"" + secretId + "\",\"key\":\"" + encryptedKey
-                        + "\",\"value\":\"" + encryptedValue + "\"}");
-        return client;
-    }
-
-    /** Produces a valid Bitwarden type-2 cipher string using the test key material. */
-    private String encrypt(String plaintext) throws Exception {
-        byte[] iv = new byte[16];
-        new SecureRandom().nextBytes(iv);
-
-        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
-        cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(TEST_ENC_KEY, "AES"), new IvParameterSpec(iv));
-        byte[] ciphertext = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
-
-        Mac mac = Mac.getInstance("HmacSHA256");
-        mac.init(new SecretKeySpec(TEST_MAC_KEY, "HmacSHA256"));
-        mac.update(iv);
-        byte[] hmac = mac.doFinal(ciphertext);
-
-        Base64.Encoder b64 = Base64.getEncoder();
-        return "2." + b64.encodeToString(iv) + "|"
-                + b64.encodeToString(ciphertext) + "|"
-                + b64.encodeToString(hmac);
-    }
-}
diff --git a/gcp-secretmanager-secrets-provider/build.gradle b/gcp-secretmanager-secrets-provider/build.gradle
deleted file mode 100644
index 7980275c3..000000000
--- a/gcp-secretmanager-secrets-provider/build.gradle
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-// Google Cloud Secret Manager Java client (Apache License 2.0)
-// https://github.com/googleapis/java-secretmanager
-dependencies {
-    pluginLibsCompile 'com.google.cloud:google-cloud-secretmanager:2.36.0'
-}
-
-configurations.all {
-    exclude group: 'commons-logging', module: 'commons-logging'
-}
diff --git a/gcp-secretmanager-secrets-provider/config/gcp-secret-manager.properties b/gcp-secretmanager-secrets-provider/config/gcp-secret-manager.properties
deleted file mode 100644
index ccff5dd36..000000000
--- a/gcp-secretmanager-secrets-provider/config/gcp-secret-manager.properties
+++ /dev/null
@@ -1,34 +0,0 @@
-###############################################################################
-# GCP Secret Manager — SecretProvider configuration
-#
-# To activate this provider:
-#   1. Set enabled="true" in gcp-secretmanager-secrets-provider/ofbiz-component.xml
-#   2. Set enabled="false" on every other *-secrets-provider plugin
-###############################################################################
-
-# GCP project ID that owns the secrets (required)
-# Example: my-gcp-project
-gcp.project.id=
-
-# Path to a service account JSON key file.
-# Leave empty to use Application Default Credentials (ADC) — recommended when
-# running on GCP (GKE, GCE, Cloud Run, App Engine) or with gcloud auth set up.
-gcp.credentials.file=
-
-# Optional prefix prepended to every secret name before lookup.
-# Useful for environment namespacing, e.g. "prod/" makes key "db" → "prod/db".
-gcp.secret.name.prefix=
-
-# GCP Secret Manager secret version to access (default: latest)
-gcp.secret.version=latest
-
-# GCP secret names may only contain letters, digits, hyphens and underscores.
-# OFBiz keys like "jdbc-password.mysql-ofbiz" contain a dot, which is invalid.
-# Set this to the replacement character (default: -) so the dot is substituted.
-# Example: "jdbc-password.mysql-ofbiz" → "jdbc-password-mysql-ofbiz"
-# Set to empty to disable replacement (only do this if your keys have no dots).
-gcp.secret.name.dot.replacement=-
-
-# In-memory cache TTL in seconds (default: 3600 = 1 hour).
-# Set to 0 to disable caching (fetches from GCP on every call).
-gcp.cache.ttl.seconds=3600
diff --git a/gcp-secretmanager-secrets-provider/ofbiz-component.xml b/gcp-secretmanager-secrets-provider/ofbiz-component.xml
deleted file mode 100644
index 7608b2281..000000000
--- a/gcp-secretmanager-secrets-provider/ofbiz-component.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
--->
-
-<ofbiz-component name="gcp-secretmanager-secrets-provider" enabled="false"
-        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:noNamespaceSchemaLocation="https://ofbiz.apache.org/dtds/ofbiz-component.xsd">
-
-    <resource-loader name="main" type="component"/>
-
-    <!-- Puts config/ on the classpath so gcp-secret-manager.properties is found by UtilProperties -->
-    <classpath type="dir" location="config"/>
-
-</ofbiz-component>
diff --git a/gcp-secretmanager-secrets-provider/src/main/java/org/apache/ofbiz/gcpsecretmanager/GcpSecretManagerSecretsProvider.java b/gcp-secretmanager-secrets-provider/src/main/java/org/apache/ofbiz/gcpsecretmanager/GcpSecretManagerSecretsProvider.java
deleted file mode 100644
index 5a3e3a368..000000000
--- a/gcp-secretmanager-secrets-provider/src/main/java/org/apache/ofbiz/gcpsecretmanager/GcpSecretManagerSecretsProvider.java
+++ /dev/null
@@ -1,201 +0,0 @@
-/*******************************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- *******************************************************************************/
-package org.apache.ofbiz.gcpsecretmanager;
-
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.concurrent.ConcurrentHashMap;
-
-import com.google.api.gax.core.FixedCredentialsProvider;
-import com.google.auth.oauth2.GoogleCredentials;
-import com.google.cloud.secretmanager.v1.SecretManagerServiceClient;
-import com.google.cloud.secretmanager.v1.SecretManagerServiceSettings;
-
-import org.apache.ofbiz.base.lang.ThreadSafe;
-import org.apache.ofbiz.base.secret.SecretProvider;
-import org.apache.ofbiz.base.util.Debug;
-import org.apache.ofbiz.base.util.GeneralException;
-import org.apache.ofbiz.base.util.UtilProperties;
-
-/**
- * {@link SecretProvider} implementation backed by Google Cloud Secret Manager.
- *
- * <h2>Authentication</h2>
- * <p>Two modes are supported via {@code gcp.credentials.file}:</p>
- * <ul>
- *   <li><strong>Application Default Credentials (ADC)</strong> — leave the property empty.
- *       GCP automatically provides credentials when running on GKE, GCE, Cloud Run, or
- *       after {@code gcloud auth application-default login} locally.</li>
- *   <li><strong>Service Account key file</strong> — set {@code gcp.credentials.file} to the
- *       path of a downloaded JSON key file. Suitable for on-premise deployments.</li>
- * </ul>
- *
- * <h2>Secret name mapping</h2>
- * <p>GCP secret names may only contain letters, digits, hyphens and underscores.
- * OFBiz keys contain dots (e.g. {@code jdbc-password.mysql-ofbiz}), which are invalid.
- * Set {@code gcp.secret.name.dot.replacement=-} (the default) to replace dots with
- * hyphens, so the key maps to {@code jdbc-password-mysql-ofbiz} in the vault.</p>
- *
- * <h2>Secret resource name</h2>
- * <p>Built as:
- * {@code projects/{projectId}/secrets/{prefix}{sanitizedKey}/versions/{version}}</p>
- *
- * <p>Configure via {@code plugins/gcp-secretmanager-secrets-provider/config/gcp-secret-manager.properties}.</p>
- */
-@ThreadSafe
-public final class GcpSecretManagerSecretsProvider implements SecretProvider {
-
-    private static final String MODULE = GcpSecretManagerSecretsProvider.class.getName();
-    private static final String CONFIG_RESOURCE = "gcp-secret-manager";
-
-    private final GcpSecretReader secretReader;
-    private final String projectId;
-    private final String secretNamePrefix;
-    private final String version;
-    private final String dotReplacement;
-    private final long cacheTtlMs;
-
-    private final ConcurrentHashMap<String, CacheEntry> cache = new ConcurrentHashMap<>();
-
-    private static final class CacheEntry {
-        final String value;
-        final long expiresAt;
-
-        CacheEntry(String value, long ttlMs) {
-            this.value = value;
-            this.expiresAt = System.currentTimeMillis() + ttlMs;
-        }
-
-        boolean isExpired() {
-            return System.currentTimeMillis() >= expiresAt;
-        }
-    }
-
-    /** Public no-arg constructor required by {@link java.util.ServiceLoader}. */
-    public GcpSecretManagerSecretsProvider() throws GeneralException {
-        this(readerFrom(buildClient()),
-                prop("gcp.project.id", ""),
-                prop("gcp.secret.name.prefix", ""),
-                prop("gcp.secret.version", "latest"),
-                prop("gcp.secret.name.dot.replacement", "-"),
-                readTtlMs());
-    }
-
-    /** Package-private constructor used by unit tests to inject a {@link GcpSecretReader} lambda. */
-    GcpSecretManagerSecretsProvider(GcpSecretReader secretReader, String projectId,
-            String secretNamePrefix, String version, String dotReplacement, long cacheTtlMs) {
-        this.secretReader = secretReader;
-        this.projectId = projectId;
-        this.secretNamePrefix = secretNamePrefix;
-        this.version = version;
-        this.dotReplacement = dotReplacement;
-        this.cacheTtlMs = cacheTtlMs;
-    }
-
-    @Override
-    public String getSecret(String key) throws GeneralException {
-        CacheEntry cached = cache.get(key);
-        if (cached != null && !cached.isExpired()) {
-            return cached.value;
-        }
-
-        String sanitizedKey = dotReplacement.isEmpty() ? key : key.replace(".", dotReplacement);
-        String secretName = secretNamePrefix + sanitizedKey;
-        String resourceName = "projects/" + projectId + "/secrets/" + secretName + "/versions/" + version;
-
-        String value;
-        try {
-            value = secretReader.read(resourceName);
-        } catch (Exception e) {
-            throw new GeneralException(
-                    "GCP Secret Manager read failed for '" + resourceName + "': " + e.getMessage(), e);
-        }
-
-        if (value == null || value.isEmpty()) {
-            throw new GeneralException(
-                    "GCP Secret Manager returned empty value for '" + resourceName + "'");
-        }
-
-        cache.put(key, new CacheEntry(value, cacheTtlMs));
-        return value;
-    }
-
-    /**
-     * Clears the in-memory cache, forcing the next {@link #getSecret(String)} call
-     * to re-fetch from GCP. Useful after a secret rotation.
-     */
-    public void invalidateCache() {
-        cache.clear();
-        Debug.logInfo("GcpSecretManagerSecretsProvider: secret cache invalidated", MODULE);
-    }
-
-    // -- private helpers --
-
-    private static GcpSecretReader readerFrom(SecretManagerServiceClient client) {
-        return resourceName -> {
-            try {
-                return client.accessSecretVersion(resourceName)
-                        .getPayload()
-                        .getData()
-                        .toStringUtf8();
-            } catch (Exception e) {
-                throw new IOException(e.getMessage(), e);
-            }
-        };
-    }
-
-    private static SecretManagerServiceClient buildClient() throws GeneralException {
-        String credentialsFile = prop("gcp.credentials.file", "");
-        try {
-            SecretManagerServiceSettings.Builder builder = SecretManagerServiceSettings.newBuilder();
-
-            if (!credentialsFile.isEmpty()) {
-                try (FileInputStream fis = new FileInputStream(credentialsFile)) {
-                    GoogleCredentials credentials = GoogleCredentials
-                            .fromStream(fis)
-                            .createScoped("https://www.googleapis.com/auth/cloud-platform");
-                    builder.setCredentialsProvider(FixedCredentialsProvider.create(credentials));
-                }
-            }
-            // else: Application Default Credentials are used automatically
-
-            Debug.logInfo("GcpSecretManagerSecretsProvider: initialized project="
-                    + prop("gcp.project.id", "") + " version=" + prop("gcp.secret.version", "latest"), MODULE);
-            return SecretManagerServiceClient.create(builder.build());
-
-        } catch (IOException e) {
-            throw new GeneralException(
-                    "GcpSecretManagerSecretsProvider: initialization failed: " + e.getMessage(), e);
-        }
-    }
-
-    private static long readTtlMs() {
-        String raw = prop("gcp.cache.ttl.seconds", "3600");
-        try {
-            return Long.parseLong(raw.trim()) * 1000L;
-        } catch (NumberFormatException e) {
-            Debug.logWarning("Invalid gcp.cache.ttl.seconds '" + raw + "', defaulting to 3600s", MODULE);
-            return 3_600_000L;
-        }
-    }
-
-    private static String prop(String key, String defaultValue) {
-        return UtilProperties.getPropertyValue(CONFIG_RESOURCE, key, defaultValue);
-    }
-}
diff --git a/gcp-secretmanager-secrets-provider/src/main/java/org/apache/ofbiz/gcpsecretmanager/GcpSecretReader.java b/gcp-secretmanager-secrets-provider/src/main/java/org/apache/ofbiz/gcpsecretmanager/GcpSecretReader.java
deleted file mode 100644
index 411cfaa7f..000000000
--- a/gcp-secretmanager-secrets-provider/src/main/java/org/apache/ofbiz/gcpsecretmanager/GcpSecretReader.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*******************************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- *******************************************************************************/
-package org.apache.ofbiz.gcpsecretmanager;
-
-/**
- * Thin seam over GCP Secret Manager reads used by {@link GcpSecretManagerSecretsProvider}.
- * Kept package-private so tests can substitute a lambda without connecting to GCP.
- */
-@FunctionalInterface
-interface GcpSecretReader {
-    /**
-     * Accesses the secret version at the given fully-qualified resource name.
-     *
-     * @param resourceName e.g. {@code "projects/my-project/secrets/my-secret/versions/latest"}
-     * @return the plaintext secret value; never {@code null}
-     * @throws Exception if the access fails or the secret does not exist
-     */
-    String read(String resourceName) throws Exception;
-}
diff --git a/gcp-secretmanager-secrets-provider/src/main/resources/META-INF/services/org.apache.ofbiz.base.secret.SecretProvider b/gcp-secretmanager-secrets-provider/src/main/resources/META-INF/services/org.apache.ofbiz.base.secret.SecretProvider
deleted file mode 100644
index 7421bf9dd..000000000
--- a/gcp-secretmanager-secrets-provider/src/main/resources/META-INF/services/org.apache.ofbiz.base.secret.SecretProvider
+++ /dev/null
@@ -1 +0,0 @@
-org.apache.ofbiz.gcpsecretmanager.GcpSecretManagerSecretsProvider
diff --git a/gcp-secretmanager-secrets-provider/src/test/java/org/apache/ofbiz/gcpsecretmanager/GcpSecretManagerSecretsProviderTest.java b/gcp-secretmanager-secrets-provider/src/test/java/org/apache/ofbiz/gcpsecretmanager/GcpSecretManagerSecretsProviderTest.java
deleted file mode 100644
index 053fc3ddf..000000000
--- a/gcp-secretmanager-secrets-provider/src/test/java/org/apache/ofbiz/gcpsecretmanager/GcpSecretManagerSecretsProviderTest.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*******************************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- *******************************************************************************/
-package org.apache.ofbiz.gcpsecretmanager;
-
-import static org.junit.Assert.assertEquals;
-
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.apache.ofbiz.base.util.GeneralException;
-import org.junit.Test;
-
-public class GcpSecretManagerSecretsProviderTest {
-
-    private static final long ONE_HOUR_MS = 3_600_000L;
-    private static final String PROJECT = "my-project";
-
-    // -- Happy path --
-
-    @Test
-    public void getSecret_returnsSecretValue() throws GeneralException {
-        GcpSecretReader reader = fixedReader(
-                "projects/my-project/secrets/mykey/versions/latest", "s3cr3t");
-        assertEquals("s3cr3t", provider(reader, "", "-").getSecret("mykey"));
-    }
-
-    @Test
-    public void getSecret_sanitizesDotInKey() throws GeneralException {
-        // OFBiz key "jdbc-password.mysql-ofbiz" → GCP name "jdbc-password-mysql-ofbiz"
-        GcpSecretReader reader = fixedReader(
-                "projects/my-project/secrets/jdbc-password-mysql-ofbiz/versions/latest", "dbpass");
-        assertEquals("dbpass", provider(reader, "", "-").getSecret("jdbc-password.mysql-ofbiz"));
-    }
-
-    @Test
-    public void getSecret_appliesPrefixAfterSanitizing() throws GeneralException {
-        GcpSecretReader reader = fixedReader(
-                "projects/my-project/secrets/prod-jdbc-password-mydb/versions/latest", "prodpass");
-        assertEquals("prodpass", provider(reader, "prod-", "-").getSecret("jdbc-password.mydb"));
-    }
-
-    @Test
-    public void getSecret_usesConfiguredVersion() throws GeneralException {
-        GcpSecretReader reader = fixedReader(
-                "projects/my-project/secrets/mykey/versions/3", "v3val");
-        GcpSecretManagerSecretsProvider p = new GcpSecretManagerSecretsProvider(
-                reader, PROJECT, "", "3", "-", ONE_HOUR_MS);
-        assertEquals("v3val", p.getSecret("mykey"));
-    }
-
-    @Test
-    public void getSecret_cachePreventsDuplicateReaderCall() throws GeneralException {
-        AtomicInteger calls = new AtomicInteger();
-        GcpSecretReader reader = resourceName -> {
-            calls.incrementAndGet();
-            return "val";
-        };
-        GcpSecretManagerSecretsProvider p = provider(reader, "", "-");
-
-        p.getSecret("mykey");
-        p.getSecret("mykey"); // cache hit
-
-        assertEquals(1, calls.get());
-    }
-
-    @Test
-    public void invalidateCache_forcesRefetchOnNextCall() throws GeneralException {
-        AtomicInteger calls = new AtomicInteger();
-        GcpSecretReader reader = resourceName -> {
-            calls.incrementAndGet();
-            return "val";
-        };
-        GcpSecretManagerSecretsProvider p = provider(reader, "", "-");
-
-        p.getSecret("mykey");
-        p.invalidateCache();
-        p.getSecret("mykey");
-
-        assertEquals(2, calls.get());
-    }
-
-    @Test
-    public void getSecret_expiredCacheTriggersRefetch() throws GeneralException {
-        AtomicInteger calls = new AtomicInteger();
-        GcpSecretReader reader = resourceName -> {
-            calls.incrementAndGet();
-            return "val";
-        };
-        // TTL of -1 ms — entries expire immediately
-        GcpSecretManagerSecretsProvider p = new GcpSecretManagerSecretsProvider(
-                reader, PROJECT, "", "latest", "-", -1L);
-
-        p.getSecret("mykey");
-        p.getSecret("mykey");
-
-        assertEquals(2, calls.get());
-    }
-
-    @Test
-    public void getSecret_dotReplacementDisabled_keepsDotsInName() throws GeneralException {
-        // dot.replacement="" → no sanitization
-        GcpSecretReader reader = fixedReader(
-                "projects/my-project/secrets/my.key/versions/latest", "val");
-        assertEquals("val", provider(reader, "", "").getSecret("my.key"));
-    }
-
-    // -- Error handling --
-
-    @Test(expected = GeneralException.class)
-    public void getSecret_throwsOnReaderException() throws GeneralException {
-        GcpSecretReader reader = resourceName -> { throw new Exception("NOT_FOUND"); };
-        provider(reader, "", "-").getSecret("missing");
-    }
-
-    @Test(expected = GeneralException.class)
-    public void getSecret_throwsOnEmptyValue() throws GeneralException {
-        GcpSecretReader reader = resourceName -> "";
-        provider(reader, "", "-").getSecret("mykey");
-    }
-
-    // -- helpers --
-
-    private static GcpSecretManagerSecretsProvider provider(GcpSecretReader reader,
-            String prefix, String dotReplacement) {
-        return new GcpSecretManagerSecretsProvider(reader, PROJECT, prefix, "latest",
-                dotReplacement, ONE_HOUR_MS);
-    }
-
-    private static GcpSecretReader fixedReader(String expectedResource, String value) {
-        return resourceName -> expectedResource.equals(resourceName) ? value : null;
-    }
-}
diff --git a/hashicorp-vault-secrets-provider/build.gradle b/hashicorp-vault-secrets-provider/build.gradle
deleted file mode 100644
index ae4c90ea9..000000000
--- a/hashicorp-vault-secrets-provider/build.gradle
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-// HashiCorp Vault Java Driver — Apache License 2.0
-// https://github.com/jopenlibs/vault-java-driver
-dependencies {
-    pluginLibsCompile 'io.github.jopenlibs:vault-java-driver:5.4.0'
-}
-
-configurations.all {
-    exclude group: 'commons-logging', module: 'commons-logging'
-}
diff --git a/hashicorp-vault-secrets-provider/config/hashicorp-vault-secrets.properties b/hashicorp-vault-secrets-provider/config/hashicorp-vault-secrets.properties
deleted file mode 100644
index 9829048e5..000000000
--- a/hashicorp-vault-secrets-provider/config/hashicorp-vault-secrets.properties
+++ /dev/null
@@ -1,66 +0,0 @@
-###############################################################################
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-###############################################################################
-
-####
-# HashiCorp Vault — SecretProvider configuration
-#
-# NOTE: Only ONE SecretProvider plugin may be active at a time.
-# If multiple provider plugins are on the classpath, the first one
-# discovered by java.util.ServiceLoader is used (order is undefined).
-# Deploy only the plugin that matches your environment.
-####
-
-# Vault server address.
-vault.address=http://127.0.0.1:8200
-
-# Authentication method: token | approle
-vault.auth.method=token
-
-# Token auth: set vault.token (leave empty when using AppRole).
-# Never commit real tokens — use an environment variable or a secrets manager
-# to inject this value at deploy time.
-vault.token=
-
-# AppRole auth: role_id is non-sensitive; secret_id should be injected at runtime.
-vault.approle.role_id=
-vault.approle.secret_id=
-
-# KV secrets engine mount path (the path you used when enabling the engine).
-vault.kv.mount=secret
-
-# KV engine version: 1 or 2 (Vault default is 2 for new mounts).
-vault.kv.version=2
-
-# Optional prefix prepended to every OFBiz secret key before the Vault lookup.
-# Example: with prefix "myapp/prod/" the key "jdbc-password.ofbiz" becomes
-#          "secret/myapp/prod/jdbc-password.ofbiz" in Vault.
-vault.secret.name.prefix=
-
-# If the secret in Vault holds multiple fields (e.g. {"username":"u","password":"p"}),
-# set this to the field name that holds the actual secret value (e.g. "password").
-# Leave empty only when the secret has exactly one field — its value is returned as-is.
-vault.field=password
-
-# How long (in seconds) to cache a resolved secret value before re-fetching.
-# Default: 3600 (1 hour). Set to 0 to disable caching.
-vault.cache.ttl.seconds=3600
-
-# Whether to verify the Vault server's TLS certificate.
-# Set to false only for local development — never in production.
-vault.ssl.verify=true
diff --git a/hashicorp-vault-secrets-provider/ofbiz-component.xml b/hashicorp-vault-secrets-provider/ofbiz-component.xml
deleted file mode 100644
index 37ea052c7..000000000
--- a/hashicorp-vault-secrets-provider/ofbiz-component.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
--->
-
-<ofbiz-component name="hashicorp-vault-secrets-provider" enabled="false"
-        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:noNamespaceSchemaLocation="https://ofbiz.apache.org/dtds/ofbiz-component.xsd">
-
-    <resource-loader name="main" type="component"/>
-
-    <!-- Puts config/ on the classpath so hashicorp-vault-secrets.properties is found by UtilProperties -->
-    <classpath type="dir" location="config"/>
-
-</ofbiz-component>
diff --git a/hashicorp-vault-secrets-provider/src/main/java/org/apache/ofbiz/hashicorpvault/HashicorpVaultReader.java b/hashicorp-vault-secrets-provider/src/main/java/org/apache/ofbiz/hashicorpvault/HashicorpVaultReader.java
deleted file mode 100644
index 0818488f5..000000000
--- a/hashicorp-vault-secrets-provider/src/main/java/org/apache/ofbiz/hashicorpvault/HashicorpVaultReader.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*******************************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- *******************************************************************************/
-package org.apache.ofbiz.hashicorpvault;
-
-import java.util.Map;
-
-import io.github.jopenlibs.vault.VaultException;
-
-/**
- * Thin seam over Vault KV reads used by {@link HashicorpVaultSecretsProvider}.
- * Kept package-private so tests can substitute a lambda without launching a real Vault server.
- */
-@FunctionalInterface
-interface HashicorpVaultReader {
-    /**
-     * Reads the KV secret at the given path.
-     *
-     * @param path the full KV path (e.g. {@code "secret/myapp/jdbc-password"})
-     * @return the data fields; never {@code null}
-     * @throws VaultException if the read fails or the path does not exist
-     */
-    Map<String, String> read(String path) throws VaultException;
-}
diff --git a/hashicorp-vault-secrets-provider/src/main/java/org/apache/ofbiz/hashicorpvault/HashicorpVaultSecretsProvider.java b/hashicorp-vault-secrets-provider/src/main/java/org/apache/ofbiz/hashicorpvault/HashicorpVaultSecretsProvider.java
deleted file mode 100644
index ab16ca8e6..000000000
--- a/hashicorp-vault-secrets-provider/src/main/java/org/apache/ofbiz/hashicorpvault/HashicorpVaultSecretsProvider.java
+++ /dev/null
@@ -1,226 +0,0 @@
-/*******************************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- *******************************************************************************/
-package org.apache.ofbiz.hashicorpvault;
-
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-import java.util.Collections;
-
-import io.github.jopenlibs.vault.SslConfig;
-import io.github.jopenlibs.vault.Vault;
-import io.github.jopenlibs.vault.VaultConfig;
-import io.github.jopenlibs.vault.VaultException;
-
-import org.apache.ofbiz.base.lang.ThreadSafe;
-import org.apache.ofbiz.base.secret.SecretProvider;
-import org.apache.ofbiz.base.util.Debug;
-import org.apache.ofbiz.base.util.GeneralException;
-import org.apache.ofbiz.base.util.UtilProperties;
-
-/**
- * {@link SecretProvider} implementation backed by HashiCorp Vault.
- *
- * <p>Supports both Token and AppRole authentication methods. The preferred
- * production approach is AppRole — the role_id is non-sensitive and can live
- * in config, while the secret_id is injected at deploy time via an environment
- * variable or a secrets bootstrap mechanism.</p>
- *
- * <p>Both KV v1 and KV v2 engines are supported via the {@code vault.kv.version}
- * property. The driver handles the {@code /data/} path rewrite for KV v2
- * automatically when {@code engineVersion(2)} is configured.</p>
- *
- * <p>Resolved secret values are cached in memory for the TTL configured by
- * {@code vault.cache.ttl.seconds} (default 1 hour).</p>
- *
- * <p>Configure via {@code plugins/hashicorp-vault-secrets-provider/config/hashicorp-vault-secrets.properties}.</p>
- */
-@ThreadSafe
-public final class HashicorpVaultSecretsProvider implements SecretProvider {
-
-    private static final String MODULE = HashicorpVaultSecretsProvider.class.getName();
-    private static final String CONFIG_RESOURCE = "hashicorp-vault-secrets";
-
-    private final HashicorpVaultReader vaultReader;
-    private final String kvMount;
-    private final String secretNamePrefix;
-    private final String field;
-    private final long cacheTtlMs;
-
-    private final ConcurrentHashMap<String, CacheEntry> cache = new ConcurrentHashMap<>();
-
-    private static final class CacheEntry {
-        final String value;
-        final long expiresAt;
-
-        CacheEntry(String value, long ttlMs) {
-            this.value = value;
-            this.expiresAt = System.currentTimeMillis() + ttlMs;
-        }
-
-        boolean isExpired() {
-            return System.currentTimeMillis() >= expiresAt;
-        }
-    }
-
-    /** Public no-arg constructor required by {@link java.util.ServiceLoader}. */
-    public HashicorpVaultSecretsProvider() {
-        this(readerFrom(buildVault()),
-                prop("vault.kv.mount", "secret"),
-                prop("vault.secret.name.prefix", ""),
-                prop("vault.field", "password"),
-                readTtlMs());
-    }
-
-    /** Package-private constructor used by unit tests to inject a {@link HashicorpVaultReader} lambda. */
-    HashicorpVaultSecretsProvider(HashicorpVaultReader vaultReader, String kvMount, String secretNamePrefix,
-            String field, long cacheTtlMs) {
-        this.vaultReader = vaultReader;
-        this.kvMount = kvMount;
-        this.secretNamePrefix = secretNamePrefix;
-        this.field = field;
-        this.cacheTtlMs = cacheTtlMs;
-    }
-
-    @Override
-    public String getSecret(String key) throws GeneralException {
-        CacheEntry cached = cache.get(key);
-        if (cached != null && !cached.isExpired()) {
-            return cached.value;
-        }
-
-        String path = kvMount + "/" + secretNamePrefix + key;
-        String value = readFromVault(path);
-
-        cache.put(key, new CacheEntry(value, cacheTtlMs));
-        return value;
-    }
-
-    /**
-     * Clears the in-memory cache, forcing the next {@link #getSecret(String)} call
-     * for each key to re-fetch from Vault. Useful after a secret rotation.
-     */
-    public void invalidateCache() {
-        cache.clear();
-        Debug.logInfo("HashicorpVaultSecretsProvider: secret cache invalidated", MODULE);
-    }
-
-    // -- private helpers --
-
-    private String readFromVault(String path) throws GeneralException {
-        Map<String, String> data;
-        try {
-            data = vaultReader.read(path);
-        } catch (VaultException e) {
-            throw new GeneralException("Vault error reading path '" + path + "': " + e.getMessage(), e);
-        }
-
-        if (data == null || data.isEmpty()) {
-            throw new GeneralException("No data found at Vault path '" + path + "' — check the path and auth policy");
-        }
-
-        if (!field.isEmpty()) {
-            String value = data.get(field);
-            if (value == null) {
-                throw new GeneralException(
-                        "Field '" + field + "' not found in Vault secret at '" + path + "'");
-            }
-            return value;
-        }
-
-        if (data.size() == 1) {
-            return data.values().iterator().next();
-        }
-
-        throw new GeneralException("Secret at '" + path + "' has " + data.size()
-                + " fields — set vault.field to select one");
-    }
-
-    private static HashicorpVaultReader readerFrom(Vault vault) {
-        return path -> {
-            Map<String, String> data = vault.logical().read(path).getData();
-            return data != null ? data : Collections.emptyMap();
-        };
-    }
-
-    private static Vault buildVault() {
-        String address = prop("vault.address", "http://127.0.0.1:8200");
-        String authMethod = prop("vault.auth.method", "token");
-        int kvVersion = parseKvVersion(prop("vault.kv.version", "2"));
-        boolean sslVerify = Boolean.parseBoolean(prop("vault.ssl.verify", "true"));
-
-        try {
-            SslConfig ssl = new SslConfig().verify(sslVerify).build();
-            String token;
-
-            if ("approle".equalsIgnoreCase(authMethod)) {
-                String roleId = prop("vault.approle.role_id", "");
-                String secretId = prop("vault.approle.secret_id", "");
-                VaultConfig bootConfig = new VaultConfig()
-                        .address(address)
-                        .engineVersion(kvVersion)
-                        .sslConfig(ssl)
-                        .build();
-                token = new Vault(bootConfig)
-                        .auth()
-                        .loginByAppRole(roleId, secretId)
-                        .getAuthClientToken();
-            } else {
-                token = prop("vault.token", "");
-            }
-
-            VaultConfig config = new VaultConfig()
-                    .address(address)
-                    .token(token)
-                    .engineVersion(kvVersion)
-                    .sslConfig(ssl)
-                    .build();
-
-            Debug.logInfo("HashicorpVaultSecretsProvider: initialized address=" + address
-                    + " auth=" + authMethod + " kv-version=" + kvVersion, MODULE);
-            return new Vault(config);
-
-        } catch (VaultException e) {
-            throw new RuntimeException("HashicorpVaultSecretsProvider: initialization failed: " + e.getMessage(), e);
-        }
-    }
-
-    private static int parseKvVersion(String raw) {
-        try {
-            int v = Integer.parseInt(raw.trim());
-            if (v == 1 || v == 2) return v;
-        } catch (NumberFormatException ignored) { }
-        Debug.logWarning("Invalid vault.kv.version '" + raw + "', defaulting to 2", MODULE);
-        return 2;
-    }
-
-    private static long readTtlMs() {
-        String raw = prop("vault.cache.ttl.seconds", "3600");
-        try {
-            return Long.parseLong(raw.trim()) * 1000L;
-        } catch (NumberFormatException e) {
-            Debug.logWarning("Invalid vault.cache.ttl.seconds '" + raw + "', defaulting to 3600s", MODULE);
-            return 3_600_000L;
-        }
-    }
-
-    private static String prop(String key, String defaultValue) {
-        return UtilProperties.getPropertyValue(CONFIG_RESOURCE, key, defaultValue);
-    }
-}
diff --git a/hashicorp-vault-secrets-provider/src/main/resources/META-INF/services/org.apache.ofbiz.base.secret.SecretProvider b/hashicorp-vault-secrets-provider/src/main/resources/META-INF/services/org.apache.ofbiz.base.secret.SecretProvider
deleted file mode 100644
index 2e36cff5e..000000000
--- a/hashicorp-vault-secrets-provider/src/main/resources/META-INF/services/org.apache.ofbiz.base.secret.SecretProvider
+++ /dev/null
@@ -1 +0,0 @@
-org.apache.ofbiz.hashicorpvault.HashicorpVaultSecretsProvider
diff --git a/hashicorp-vault-secrets-provider/src/test/java/org/apache/ofbiz/hashicorpvault/HashicorpVaultSecretsProviderTest.java b/hashicorp-vault-secrets-provider/src/test/java/org/apache/ofbiz/hashicorpvault/HashicorpVaultSecretsProviderTest.java
deleted file mode 100644
index 22afb7cf9..000000000
--- a/hashicorp-vault-secrets-provider/src/test/java/org/apache/ofbiz/hashicorpvault/HashicorpVaultSecretsProviderTest.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*******************************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- *******************************************************************************/
-package org.apache.ofbiz.hashicorpvault;
-
-import static org.junit.Assert.assertEquals;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import io.github.jopenlibs.vault.VaultException;
-
-import org.apache.ofbiz.base.util.GeneralException;
-import org.junit.Test;
-
-public class HashicorpVaultSecretsProviderTest {
-
-    private static final long ONE_HOUR_MS = 3_600_000L;
-
-    // -- Happy path --
-
-    @Test
-    public void getSecret_returnsSingleFieldSecret() throws GeneralException {
-        HashicorpVaultReader reader = fixedReader("secret/mykey", singleEntry("value", "s3cr3t"));
-        HashicorpVaultSecretsProvider provider = provider(reader, "", "");
-
-        assertEquals("s3cr3t", provider.getSecret("mykey"));
-    }
-
-    @Test
-    public void getSecret_extractsNamedField() throws GeneralException {
-        HashicorpVaultReader reader = fixedReader("secret/mykey", twoEntry("username", "user", "password", "s3cr3t"));
-        HashicorpVaultSecretsProvider provider = provider(reader, "", "password");
-
-        assertEquals("s3cr3t", provider.getSecret("mykey"));
-    }
-
-    @Test
-    public void getSecret_appliesKvMountAndPrefix() throws GeneralException {
-        HashicorpVaultReader reader = fixedReader("kv/prod/jdbc-password.ofbiz", singleEntry("value", "dbpass"));
-        HashicorpVaultSecretsProvider provider = new HashicorpVaultSecretsProvider(reader, "kv", "prod/", "", ONE_HOUR_MS);
-
-        assertEquals("dbpass", provider.getSecret("jdbc-password.ofbiz"));
-    }
-
-    @Test
-    public void getSecret_cachePreventsDuplicateReaderCall() throws GeneralException {
-        AtomicInteger calls = new AtomicInteger();
-        HashicorpVaultReader reader = path -> {
-            calls.incrementAndGet();
-            return singleEntry("value", "s3cr3t");
-        };
-        HashicorpVaultSecretsProvider provider = provider(reader, "", "");
-
-        provider.getSecret("mykey");
-        provider.getSecret("mykey"); // second call — cache hit
-
-        assertEquals(1, calls.get());
-    }
-
-    @Test
-    public void invalidateCache_forcesRefetchOnNextCall() throws GeneralException {
-        AtomicInteger calls = new AtomicInteger();
-        HashicorpVaultReader reader = path -> {
-            calls.incrementAndGet();
-            return singleEntry("value", "val");
-        };
-        HashicorpVaultSecretsProvider provider = provider(reader, "", "");
-
-        provider.getSecret("mykey");
-        provider.invalidateCache();
-        provider.getSecret("mykey");
-
-        assertEquals(2, calls.get());
-    }
-
-    @Test
-    public void getSecret_expiredCacheEntryTriggersRefetch() throws GeneralException {
-        AtomicInteger calls = new AtomicInteger();
-        HashicorpVaultReader reader = path -> {
-            calls.incrementAndGet();
-            return singleEntry("value", "val");
-        };
-        // TTL of -1 ms means entries expire immediately
-        HashicorpVaultSecretsProvider provider = new HashicorpVaultSecretsProvider(reader, "secret", "", "", -1L);
-
-        provider.getSecret("mykey");
-        provider.getSecret("mykey");
-
-        assertEquals(2, calls.get());
-    }
-
-    // -- Error handling --
-
-    @Test(expected = GeneralException.class)
-    public void getSecret_throwsWhenDataIsEmpty() throws GeneralException {
-        HashicorpVaultReader reader = path -> Collections.emptyMap();
-        provider(reader, "", "").getSecret("missing");
-    }
-
-    @Test(expected = GeneralException.class)
-    public void getSecret_throwsWhenFieldMissing() throws GeneralException {
-        HashicorpVaultReader reader = fixedReader("secret/mykey", singleEntry("username", "dbuser"));
-        provider(reader, "", "password").getSecret("mykey"); // "password" field not present
-    }
-
-    @Test(expected = GeneralException.class)
-    public void getSecret_throwsWhenMultiFieldAndNoFieldConfigured() throws GeneralException {
-        HashicorpVaultReader reader = fixedReader("secret/mykey", twoEntry("username", "u", "password", "p"));
-        provider(reader, "", "").getSecret("mykey"); // ambiguous — 2 fields, no field config
-    }
-
-    @Test(expected = GeneralException.class)
-    public void getSecret_throwsOnVaultException() throws GeneralException {
-        HashicorpVaultReader reader = path -> { throw new VaultException("connection refused", 503); };
-        provider(reader, "", "").getSecret("mykey");
-    }
-
-    // -- helpers --
-
-    private static HashicorpVaultSecretsProvider provider(HashicorpVaultReader reader, String prefix, String field) {
-        return new HashicorpVaultSecretsProvider(reader, "secret", prefix, field, ONE_HOUR_MS);
-    }
-
-    private static HashicorpVaultReader fixedReader(String expectedPath, Map<String, String> data) {
-        return path -> expectedPath.equals(path) ? data : Collections.emptyMap();
-    }
-
-    private static Map<String, String> singleEntry(String k, String v) {
-        return Collections.singletonMap(k, v);
-    }
-
-    private static Map<String, String> twoEntry(String k1, String v1, String k2, String v2) {
-        Map<String, String> m = new HashMap<>();
-        m.put(k1, v1);
-        m.put(k2, v2);
-        return m;
-    }
-}
diff --git a/onepassword-secrets-provider/build.gradle b/onepassword-secrets-provider/build.gradle
deleted file mode 100644
index b2ca70694..000000000
--- a/onepassword-secrets-provider/build.gradle
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-// No external SDK required.
-// Uses Java's built-in java.net.http.HttpClient (Java 11+) and OFBiz's
-// bundled jackson-databind for JSON parsing.
-dependencies {}
diff --git a/onepassword-secrets-provider/config/onepassword.properties b/onepassword-secrets-provider/config/onepassword.properties
deleted file mode 100644
index 6a46799df..000000000
--- a/onepassword-secrets-provider/config/onepassword.properties
+++ /dev/null
@@ -1,56 +0,0 @@
-###############################################################################
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-###############################################################################
-
-####
-# 1Password Connect Server — SecretProvider configuration
-#
-# Requires a self-hosted 1Password Connect Server.
-# See: https://developer.1password.com/docs/connect/
-#
-# NOTE: Only ONE SecretProvider plugin may be active at a time.
-# Deploy only the plugin that matches your environment.
-####
-
-# URL of your 1Password Connect Server (no trailing slash).
-op.connect.url=http://localhost:8080
-
-# Connect Server access token.
-# Inject at deploy time — do not commit a real token.
-op.connect.token=
-
-# UUID of the vault to search for secrets.
-# Find it in the 1Password app or via: GET /v1/vaults
-op.vault.id=
-
-# The item field label whose value is returned as the secret.
-# Default is "password" — matches the standard Login and Password item templates.
-op.field=password
-
-# Optional prefix prepended to the OFBiz key when searching for the 1Password item title.
-# Example: with prefix "myapp/" the key "jdbc-password.ofbiz" matches an item titled
-#          "myapp/jdbc-password.ofbiz" in 1Password.
-op.secret.name.prefix=
-
-# How long (in seconds) to cache a resolved secret value before re-fetching.
-# Default: 3600 (1 hour). Set to 0 to disable caching.
-op.cache.ttl.seconds=3600
-
-# HTTP connect and read timeouts in seconds.
-op.connect.timeout.seconds=5
-op.read.timeout.seconds=10
diff --git a/onepassword-secrets-provider/ofbiz-component.xml b/onepassword-secrets-provider/ofbiz-component.xml
deleted file mode 100644
index 10f7cafe5..000000000
--- a/onepassword-secrets-provider/ofbiz-component.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
--->
-
-<ofbiz-component name="onepassword-secrets-provider" enabled="false"
-        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:noNamespaceSchemaLocation="https://ofbiz.apache.org/dtds/ofbiz-component.xsd">
-
-    <resource-loader name="main" type="component"/>
-
-    <!-- Puts config/ on the classpath so onepassword.properties is found by UtilProperties -->
-    <classpath type="dir" location="config"/>
-
-</ofbiz-component>
diff --git a/onepassword-secrets-provider/src/main/java/org/apache/ofbiz/onepassword/OnePasswordHttpClient.java b/onepassword-secrets-provider/src/main/java/org/apache/ofbiz/onepassword/OnePasswordHttpClient.java
deleted file mode 100644
index fc91c2d9d..000000000
--- a/onepassword-secrets-provider/src/main/java/org/apache/ofbiz/onepassword/OnePasswordHttpClient.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*******************************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- *******************************************************************************/
-package org.apache.ofbiz.onepassword;
-
-import java.io.IOException;
-
-/**
- * Thin seam over HTTP GET calls used by {@link OnePasswordSecretsProvider}.
- * Kept package-private so tests can substitute a mock without launching a real
- * Connect Server.
- */
-@FunctionalInterface
-interface OnePasswordHttpClient {
-    /**
-     * Performs an authenticated GET request.
-     *
-     * @param url         the full request URL
-     * @param bearerToken the Connect Server access token
-     * @return the response body as a UTF-8 string
-     * @throws IOException if the request fails or the server returns a non-2xx status
-     */
-    String get(String url, String bearerToken) throws IOException;
-}
diff --git a/onepassword-secrets-provider/src/main/java/org/apache/ofbiz/onepassword/OnePasswordSecretsProvider.java b/onepassword-secrets-provider/src/main/java/org/apache/ofbiz/onepassword/OnePasswordSecretsProvider.java
deleted file mode 100644
index 88cddd79e..000000000
--- a/onepassword-secrets-provider/src/main/java/org/apache/ofbiz/onepassword/OnePasswordSecretsProvider.java
+++ /dev/null
@@ -1,275 +0,0 @@
-/*******************************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- *******************************************************************************/
-package org.apache.ofbiz.onepassword;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.URLEncoder;
-import java.net.http.HttpClient;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
-import java.nio.charset.StandardCharsets;
-import java.time.Duration;
-import java.util.concurrent.ConcurrentHashMap;
-
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-import org.apache.ofbiz.base.lang.ThreadSafe;
-import org.apache.ofbiz.base.secret.SecretProvider;
-import org.apache.ofbiz.base.util.Debug;
-import org.apache.ofbiz.base.util.GeneralException;
-import org.apache.ofbiz.base.util.UtilProperties;
-
-/**
- * {@link SecretProvider} implementation backed by a 1Password Connect Server.
- *
- * <p>Looks up items in a designated 1Password vault by matching the item title
- * to the OFBiz secret key (with an optional prefix). The value of the configured
- * field (default {@code "password"}) is returned as the secret.</p>
- *
- * <p>Authentication uses a static Connect Server access token configured in
- * {@code op.connect.token}. Inject this token at deploy time — do not commit it.</p>
- *
- * <p>API flow per lookup:</p>
- * <ol>
- *   <li>{@code GET /v1/vaults/{vaultId}/items?filter=title eq "{title}"} — find the item UUID</li>
- *   <li>{@code GET /v1/vaults/{vaultId}/items/{itemId}} — fetch the full item with field values</li>
- *   <li>Scan {@code fields[]} for the entry whose {@code label} matches {@code op.field}</li>
- * </ol>
- *
- * <p>Configure via {@code plugins/onepassword-secrets-provider/config/onepassword.properties}.</p>
- */
-@ThreadSafe
-public final class OnePasswordSecretsProvider implements SecretProvider {
-
-    private static final String MODULE = OnePasswordSecretsProvider.class.getName();
-    private static final String CONFIG_RESOURCE = "onepassword";
-    private static final ObjectMapper JSON = new ObjectMapper();
-
-    private final OnePasswordHttpClient httpClient;
-    private final String connectUrl;
-    private final String token;
-    private final String vaultId;
-    private final String field;
-    private final String secretNamePrefix;
-    private final long cacheTtlMs;
-
-    private final ConcurrentHashMap<String, CacheEntry> cache = new ConcurrentHashMap<>();
-
-    private static final class CacheEntry {
-        final String value;
-        final long expiresAt;
-
-        CacheEntry(String value, long ttlMs) {
-            this.value = value;
-            this.expiresAt = System.currentTimeMillis() + ttlMs;
-        }
-
-        boolean isExpired() {
-            return System.currentTimeMillis() >= expiresAt;
-        }
-    }
-
-    /** Public no-arg constructor required by {@link java.util.ServiceLoader}. */
-    public OnePasswordSecretsProvider() {
-        this(buildHttpClient(),
-                prop("op.connect.url", "http://localhost:8080").replaceAll("/+$", ""),
-                prop("op.connect.token", ""),
-                prop("op.vault.id", ""),
-                prop("op.field", "password"),
-                prop("op.secret.name.prefix", ""),
-                readTtlMs());
-    }
-
-    /** Package-private constructor used by unit tests to inject a mock HTTP client. */
-    OnePasswordSecretsProvider(OnePasswordHttpClient httpClient, String connectUrl, String token,
-            String vaultId, String field, String secretNamePrefix, long cacheTtlMs) {
-        this.httpClient = httpClient;
-        this.connectUrl = connectUrl;
-        this.token = token;
-        this.vaultId = vaultId;
-        this.field = field;
-        this.secretNamePrefix = secretNamePrefix;
-        this.cacheTtlMs = cacheTtlMs;
-    }
-
-    @Override
-    public String getSecret(String key) throws GeneralException {
-        CacheEntry cached = cache.get(key);
-        if (cached != null && !cached.isExpired()) {
-            return cached.value;
-        }
-
-        String title = secretNamePrefix + key;
-        String value = fetchFromConnect(title);
-
-        cache.put(key, new CacheEntry(value, cacheTtlMs));
-        return value;
-    }
-
-    /**
-     * Clears the in-memory cache, forcing the next {@link #getSecret(String)} call
-     * to re-fetch from the Connect Server. Useful after a secret update in 1Password.
-     */
-    public void invalidateCache() {
-        cache.clear();
-        Debug.logInfo("OnePasswordSecretsProvider: secret cache invalidated", MODULE);
-    }
-
-    // -- private helpers --
-
-    private String fetchFromConnect(String title) throws GeneralException {
-        String itemId = findItemId(title);
-        return fetchFieldValue(itemId, title);
-    }
-
-    private String findItemId(String title) throws GeneralException {
-        String filter = "title eq \"" + title + "\"";
-        String encodedFilter;
-        try {
-            encodedFilter = URLEncoder.encode(filter, StandardCharsets.UTF_8);
-        } catch (Exception e) {
-            throw new GeneralException("Failed to encode filter for title '" + title + "'", e);
-        }
-
-        String searchUrl = connectUrl + "/v1/vaults/" + vaultId + "/items?filter=" + encodedFilter;
-        String responseBody;
-        try {
-            responseBody = httpClient.get(searchUrl, token);
-        } catch (IOException e) {
-            throw new GeneralException("1Password Connect request failed: " + e.getMessage(), e);
-        }
-
-        try {
-            JsonNode items = JSON.readTree(responseBody);
-            if (!items.isArray() || items.size() == 0) {
-                throw new GeneralException("No 1Password item found with title '" + title + "'");
-            }
-            JsonNode idNode = items.get(0).get("id");
-            if (idNode == null || idNode.isNull()) {
-                throw new GeneralException("1Password item for '" + title + "' has no id field");
-            }
-            return idNode.asText();
-        } catch (GeneralException e) {
-            throw e;
-        } catch (Exception e) {
-            throw new GeneralException("Failed to parse 1Password item search response: " + e.getMessage(), e);
-        }
-    }
-
-    private String fetchFieldValue(String itemId, String title) throws GeneralException {
-        String itemUrl = connectUrl + "/v1/vaults/" + vaultId + "/items/" + itemId;
-        String responseBody;
-        try {
-            responseBody = httpClient.get(itemUrl, token);
-        } catch (IOException e) {
-            throw new GeneralException("1Password Connect request failed for item '" + itemId + "': " + e.getMessage(), e);
-        }
-
-        try {
-            JsonNode item = JSON.readTree(responseBody);
-            JsonNode fields = item.get("fields");
-            if (fields == null || !fields.isArray()) {
-                throw new GeneralException("1Password item '" + title + "' has no fields array");
-            }
-
-            for (JsonNode f : fields) {
-                JsonNode labelNode = f.get("label");
-                if (labelNode != null && field.equalsIgnoreCase(labelNode.asText())) {
-                    JsonNode valueNode = f.get("value");
-                    if (valueNode == null || valueNode.isNull()) {
-                        throw new GeneralException(
-                                "Field '" + field + "' in 1Password item '" + title + "' has a null value");
-                    }
-                    String value = valueNode.asText();
-                    if (value.isEmpty()) {
-                        throw new GeneralException(
-                                "Field '" + field + "' in 1Password item '" + title + "' is empty");
-                    }
-                    return value;
-                }
-            }
-            throw new GeneralException(
-                    "Field '" + field + "' not found in 1Password item '" + title + "'");
-        } catch (GeneralException e) {
-            throw e;
-        } catch (Exception e) {
-            throw new GeneralException("Failed to parse 1Password item response: " + e.getMessage(), e);
-        }
-    }
-
-    private static OnePasswordHttpClient buildHttpClient() {
-        int connectTimeout = parseSeconds(prop("op.connect.timeout.seconds", "5"));
-        int readTimeout = parseSeconds(prop("op.read.timeout.seconds", "10"));
-
-        HttpClient javaClient = HttpClient.newBuilder()
-                .connectTimeout(Duration.ofSeconds(connectTimeout))
-                .build();
-
-        Debug.logInfo("OnePasswordSecretsProvider: initialized connect-url=" + prop("op.connect.url", ""),
-                MODULE);
-
-        return (url, bearerToken) -> {
-            HttpRequest request = HttpRequest.newBuilder()
-                    .uri(URI.create(url))
-                    .header("Authorization", "Bearer " + bearerToken)
-                    .header("Accept", "application/json")
-                    .timeout(Duration.ofSeconds(readTimeout))
-                    .GET()
-                    .build();
-
-            HttpResponse<String> response;
-            try {
-                response = javaClient.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
-            } catch (InterruptedException e) {
-                Thread.currentThread().interrupt();
-                throw new IOException("HTTP request interrupted", e);
-            }
-
-            int status = response.statusCode();
-            if (status < 200 || status >= 300) {
-                throw new IOException("1Password Connect returned HTTP " + status + " for " + url);
-            }
-            return response.body();
-        };
-    }
-
-    private static int parseSeconds(String raw) {
-        try {
-            return Integer.parseInt(raw.trim());
-        } catch (NumberFormatException e) {
-            return 10;
-        }
-    }
-
-    private static long readTtlMs() {
-        String raw = prop("op.cache.ttl.seconds", "3600");
-        try {
-            return Long.parseLong(raw.trim()) * 1000L;
-        } catch (NumberFormatException e) {
-            Debug.logWarning("Invalid op.cache.ttl.seconds '" + raw + "', defaulting to 3600s", MODULE);
-            return 3_600_000L;
-        }
-    }
-
-    private static String prop(String key, String defaultValue) {
-        return UtilProperties.getPropertyValue(CONFIG_RESOURCE, key, defaultValue);
-    }
-}
diff --git a/onepassword-secrets-provider/src/main/resources/META-INF/services/org.apache.ofbiz.base.secret.SecretProvider b/onepassword-secrets-provider/src/main/resources/META-INF/services/org.apache.ofbiz.base.secret.SecretProvider
deleted file mode 100644
index 128ee9165..000000000
--- a/onepassword-secrets-provider/src/main/resources/META-INF/services/org.apache.ofbiz.base.secret.SecretProvider
+++ /dev/null
@@ -1 +0,0 @@
-org.apache.ofbiz.onepassword.OnePasswordSecretsProvider
diff --git a/onepassword-secrets-provider/src/test/java/org/apache/ofbiz/onepassword/OnePasswordSecretsProviderTest.java b/onepassword-secrets-provider/src/test/java/org/apache/ofbiz/onepassword/OnePasswordSecretsProviderTest.java
deleted file mode 100644
index 1cb1dc519..000000000
--- a/onepassword-secrets-provider/src/test/java/org/apache/ofbiz/onepassword/OnePasswordSecretsProviderTest.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*******************************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- *******************************************************************************/
-package org.apache.ofbiz.onepassword;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import java.io.IOException;
-
-import org.apache.ofbiz.base.util.GeneralException;
-import org.junit.Test;
-
-public class OnePasswordSecretsProviderTest {
-
-    private static final long ONE_HOUR_MS = 3_600_000L;
-    private static final String BASE_URL = "http://localhost:8080";
-    private static final String TOKEN = "test-token";
-    private static final String VAULT_ID = "vault-abc";
-    private static final String ITEM_ID = "item-1";
-
-    // -- Happy path --
-
-    @Test
-    public void getSecret_returnsFieldValue() throws Exception {
-        OnePasswordHttpClient client = buildMockClient("jdbc-password.mydb", ITEM_ID, "s3cr3t");
-        assertEquals("s3cr3t", provider(client, "password", "").getSecret("jdbc-password.mydb"));
-    }
-
-    @Test
-    public void getSecret_appliesSecretNamePrefix() throws Exception {
-        OnePasswordHttpClient client = buildMockClient("prod/jdbc-password.mydb", ITEM_ID, "dbpass");
-        assertEquals("dbpass", provider(client, "password", "prod/").getSecret("jdbc-password.mydb"));
-    }
-
-    @Test
-    public void getSecret_cachePreventsSecondHttpCall() throws Exception {
-        OnePasswordHttpClient client = buildMockClient("mykey", ITEM_ID, "val");
-        OnePasswordSecretsProvider p = provider(client, "password", "");
-
-        p.getSecret("mykey");
-        p.getSecret("mykey"); // cache hit — no HTTP calls
-
-        // First getSecret = 2 calls (1 search + 1 fetch); second = 0 (cache)
-        verify(client, times(2)).get(anyString(), anyString());
-    }
-
-    @Test
-    public void invalidateCache_forcesRefetch() throws Exception {
-        OnePasswordHttpClient client = buildMockClient("mykey", ITEM_ID, "val");
-        OnePasswordSecretsProvider p = provider(client, "password", "");
-
-        p.getSecret("mykey");
-        p.invalidateCache();
-        p.getSecret("mykey"); // cache cleared — full re-fetch
-
-        // Two full fetches × 2 HTTP calls each = 4 total
-        verify(client, times(4)).get(anyString(), anyString());
-    }
-
-    @Test
-    public void getSecret_expiredCacheEntryTriggersRefetch() throws Exception {
-        OnePasswordHttpClient client = buildMockClient("mykey", ITEM_ID, "val");
-        OnePasswordSecretsProvider p = provider(client, "password", "", -1L); // instant expiry
-
-        p.getSecret("mykey");
-        p.getSecret("mykey"); // expired — must re-fetch
-
-        verify(client, times(4)).get(anyString(), anyString());
-    }
-
-    // -- Error handling --
-
-    @Test(expected = GeneralException.class)
-    public void getSecret_throwsWhenItemNotFound() throws Exception {
-        OnePasswordHttpClient client = mock(OnePasswordHttpClient.class);
-        when(client.get(anyString(), anyString())).thenReturn("[]");
-
-        provider(client, "password", "").getSecret("missing");
-    }
-
-    @Test(expected = GeneralException.class)
-    public void getSecret_throwsWhenFieldNotPresent() throws Exception {
-        // Item exists but has no "password" field — only "username"
-        OnePasswordHttpClient client = buildMockClient("mykey", ITEM_ID, "username", "dbuser", "password");
-        provider(client, "password", "").getSecret("mykey");
-    }
-
-    @Test(expected = GeneralException.class)
-    public void getSecret_throwsOnHttpError() throws Exception {
-        OnePasswordHttpClient client = mock(OnePasswordHttpClient.class);
-        when(client.get(anyString(), anyString())).thenThrow(new IOException("connection refused"));
-
-        provider(client, "password", "").getSecret("mykey");
-    }
-
-    // -- helpers --
-
-    private static OnePasswordSecretsProvider provider(OnePasswordHttpClient client, String field, String prefix) {
-        return provider(client, field, prefix, ONE_HOUR_MS);
-    }
-
-    private static OnePasswordSecretsProvider provider(OnePasswordHttpClient client, String field,
-            String prefix, long ttlMs) {
-        return new OnePasswordSecretsProvider(client, BASE_URL, TOKEN, VAULT_ID, field, prefix, ttlMs);
-    }
-
-    /**
-     * Builds a mock that routes by URL: search requests get the item list,
-     * item-fetch requests get the full item with a "password" field.
-     */
-    private static OnePasswordHttpClient buildMockClient(String title, String itemId, String fieldValue)
-            throws IOException {
-        return buildMockClient(title, itemId, "password", fieldValue, "password");
-    }
-
-    /**
-     * Builds a mock that exposes an item with the given fieldLabel/fieldValue,
-     * regardless of which field the provider is configured to look for.
-     */
-    private static OnePasswordHttpClient buildMockClient(String title, String itemId,
-            String fieldLabel, String fieldValue, String ignoredField) throws IOException {
-        OnePasswordHttpClient client = mock(OnePasswordHttpClient.class);
-        when(client.get(anyString(), eq(TOKEN))).thenAnswer(inv -> {
-            String url = inv.getArgument(0, String.class);
-            if (url.contains("filter=")) {
-                return "[{\"id\":\"" + itemId + "\",\"title\":\"" + title + "\"}]";
-            }
-            // item detail
-            return "{\"id\":\"" + itemId + "\",\"fields\":["
-                    + "{\"label\":\"" + fieldLabel + "\",\"value\":\"" + fieldValue + "\"}"
-                    + "]}";
-        });
-        return client;
-    }
-}