From 9b11c04e4f4811665fb259b55d124694a352d729 Mon Sep 17 00:00:00 2001 From: Juan Cernadas Date: Thu, 5 Nov 2026 20:08:13 -0300 Subject: [PATCH 1/3] fix(ci): read integration secrets from repository secrets The integration-test steps now read their credentials directly from repository secrets, so CI no longer needs a long-lived service token or the third-party CLI action. Each step receives only the four values it uses, and GitHub masks them automatically. PLUGGY_RSA_PUBLIC_KEY is now passed as well. Without it, the encrypted-parameters integration test silently sent plaintext parameters and never exercised EncryptedParametersInterceptor. --- .github/workflows/maven-publish.yml | 23 +++++------------------ .github/workflows/maven.yml | 23 +++++------------------ CLAUDE.md | 14 +++++++------- 3 files changed, 17 insertions(+), 43 deletions(-) diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml index c0a04b5..86c963e 100644 --- a/.github/workflows/maven-publish.yml +++ b/.github/workflows/maven-publish.yml @@ -69,26 +69,13 @@ jobs: java-version: '8' distribution: temurin - - name: Install Doppler CLI - uses: dopplerhq/cli-action@4819d808ab99e5cde19a0637a16536a4038fad73 # v4 - - name: Integration Tests env: - DOPPLER_TOKEN: ${{ secrets.DOPPLER_TOKEN_PROD }} - run: | - client_id="$(doppler secrets get PLUGGY_CLIENT_ID --plain)" - client_secret="$(doppler secrets get PLUGGY_CLIENT_SECRET --plain)" - base_url="$(doppler secrets get PLUGGY_BASE_URL --plain)" - - printf '::add-mask::%s\n' "$client_id" - printf '::add-mask::%s\n' "$client_secret" - printf '::add-mask::%s\n' "$base_url" - unset DOPPLER_TOKEN - - PLUGGY_CLIENT_ID="$client_id" \ - PLUGGY_CLIENT_SECRET="$client_secret" \ - PLUGGY_BASE_URL="$base_url" \ - mvn -B verify --file pom.xml + PLUGGY_CLIENT_ID: ${{ secrets.PLUGGY_CLIENT_ID }} + PLUGGY_CLIENT_SECRET: ${{ secrets.PLUGGY_CLIENT_SECRET }} + PLUGGY_BASE_URL: ${{ secrets.PLUGGY_BASE_URL }} + PLUGGY_RSA_PUBLIC_KEY: ${{ secrets.PLUGGY_RSA_PUBLIC_KEY }} + run: mvn -B verify --file pom.xml deploy: runs-on: ubuntu-latest needs: [validate-ref, build, test] diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 38ad9c4..a74783d 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -46,23 +46,10 @@ jobs: java-version: '8' distribution: temurin - - name: Install Doppler CLI - uses: dopplerhq/cli-action@4819d808ab99e5cde19a0637a16536a4038fad73 # v4 - - name: Integration Tests env: - DOPPLER_TOKEN: ${{ secrets.DOPPLER_TOKEN_PROD }} - run: | - client_id="$(doppler secrets get PLUGGY_CLIENT_ID --plain)" - client_secret="$(doppler secrets get PLUGGY_CLIENT_SECRET --plain)" - base_url="$(doppler secrets get PLUGGY_BASE_URL --plain)" - - printf '::add-mask::%s\n' "$client_id" - printf '::add-mask::%s\n' "$client_secret" - printf '::add-mask::%s\n' "$base_url" - unset DOPPLER_TOKEN - - PLUGGY_CLIENT_ID="$client_id" \ - PLUGGY_CLIENT_SECRET="$client_secret" \ - PLUGGY_BASE_URL="$base_url" \ - mvn -B verify --file pom.xml + PLUGGY_CLIENT_ID: ${{ secrets.PLUGGY_CLIENT_ID }} + PLUGGY_CLIENT_SECRET: ${{ secrets.PLUGGY_CLIENT_SECRET }} + PLUGGY_BASE_URL: ${{ secrets.PLUGGY_BASE_URL }} + PLUGGY_RSA_PUBLIC_KEY: ${{ secrets.PLUGGY_RSA_PUBLIC_KEY }} + run: mvn -B verify --file pom.xml diff --git a/CLAUDE.md b/CLAUDE.md index 84d779e..836e0c2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -37,12 +37,12 @@ Three slash commands under `.claude/commands/` codify the recurring SDK ↔ API - `add-model-fields` — Java/Lombok templates for adding fields to existing DTOs, with the Gson `IDENTITY` casing gotcha called out. - `add-endpoint` — templates for adding a new endpoint method to `PluggyApiService` with the right Retrofit annotations and DTO naming conventions. -Integration tests require the following env vars (loaded from Doppler in CI, see `.github/workflows/maven.yml`): +Integration tests require the following env vars. In CI they are repository secrets synced from Doppler (change values in Doppler, not in GitHub: the sync overwrites them) and passed only to the integration-test step, see `.github/workflows/maven.yml`: - `PLUGGY_CLIENT_ID` - `PLUGGY_CLIENT_SECRET` - `PLUGGY_BASE_URL` -- `PLUGGY_RSA_PUBLIC_KEY` (optional; only needed when exercising `EncryptedParametersInterceptor`) +- `PLUGGY_RSA_PUBLIC_KEY` (optional; only needed when exercising `EncryptedParametersInterceptor`. Without it, `CreateItemTest#createItem_withEncryptedParameters_responseOk` still passes but sends the parameters unencrypted) Missing any of the first three causes `BaseApiIntegrationTest` to fail-fast in `@BeforeEach`. Each test that creates items appends the new id to `itemsIdCreated`; the base class's `@AfterEach` deletes them, so integration tests are responsible for tracking what they create. @@ -86,9 +86,9 @@ The version in `pom.xml` (`` on line 7) is the **single source of truth bump in pom.xml ──► merge to master │ ▼ -release.yml (on: push to master; also workflow_dispatch w/ `force`) +release.yml (on: push to master; also workflow_dispatch) reads project.version → TAG = v - if TAG already exists (and not forced): NO-OP, nothing is published + if TAG already exists: NO-OP, nothing is published else: creates annotated tag v, generates notes from git log ..HEAD, publishes a GitHub Release │ ⚠️ the "created" event does NOT auto-run maven-publish (see gotchas) @@ -96,7 +96,7 @@ release.yml (on: push to master; also workflow_dispatch w/ `force`) maven-publish.yml (declares `on: release created`, but in practice must be run MANUALLY via workflow_dispatch w/ `tag_version`) build : mvn -B package - test : mvn -B verify (integration tests, creds from Doppler) + test : mvn -B verify (integration tests, creds from repo secrets) deploy : mvn deploy -Dmaven.test.skip.exec -s settings.xml → GitHub Packages (maven.pkg.github.com/pluggyai/pluggy-java) (deploy job needs `packages: write`) @@ -112,10 +112,10 @@ maven-publish.yml (declares `on: release created`, but in practice must be **Gotchas:** - **The GitHub Release does NOT auto-publish to Packages — you must dispatch `maven-publish.yml` yourself.** `release.yml` creates the Release using the default `GITHUB_TOKEN`, and GitHub deliberately does not fire workflow triggers (including `maven-publish`'s `release: created`) for events raised by `GITHUB_TOKEN`, to prevent recursive runs. So the deploy step never triggers automatically — every real publish has been a manual `workflow_dispatch`. After tagging, run `gh workflow run maven-publish.yml -f tag_version=v`. A tagged release with nothing in Packages is this bug, not a build failure. *Permanent fix (not yet applied): create the Release with a PAT instead of `GITHUB_TOKEN`, or add a step at the end of `release.yml` that dispatches `maven-publish.yml`.* -- **CI uses a GitHub Actions allowlist (`allowed_actions: selected`).** Only GitHub-owned actions plus a short pinned list (currently `dopplerhq/cli-action@v1`, `softprops/action-gh-release@v1`) are permitted. Any non-allowlisted action — or a *bumped version* of an allowlisted one (e.g. `dopplerhq/cli-action@v4`) — makes the whole workflow fail at **`startup_failure` with no jobs**. This is why `dependabot.yml` ignores `semver-major`; if a workflow suddenly won't start, check `gh api repos/pluggyai/pluggy-java/actions/permissions/selected-actions` and either pin back or add the action (repo admin: Settings → Actions → Allowed actions). +- **CI uses a GitHub Actions allowlist (`allowed_actions: selected`).** The workflows use only GitHub-owned actions, pinned to full commit SHAs. Any other action must be allowlisted at that exact SHA; otherwise the whole workflow fails at **`startup_failure` with no jobs**. If a workflow suddenly won't start, check `gh api repos/pluggyai/pluggy-java/actions/permissions/selected-actions` and either drop the action or allowlist its exact pin (repo admin: Settings → Actions → Allowed actions). - **Forgetting the bump is a silent no-op.** If the pom version already has a tag, the push to master publishes nothing. Don't bump the version unless you intend to cut a release, and *do* bump it when you do. - The `maven-release-plugin` is configured in `pom.xml` (`tagNameFormat = v@{project.version}`) but is **not** used by CI — tagging is done by `release.yml`'s shell, not `mvn release:*`. -- Manual escape hatches: run `release.yml` via `workflow_dispatch` with `force=true` to re-release an existing tag; run `maven-publish.yml` via `workflow_dispatch` with a `tag_version` input to re-deploy an existing tag without re-tagging. +- Manual escape hatch: run `maven-publish.yml` via `workflow_dispatch` with a `tag_version` input (validated as a `v` tag) to re-deploy an existing tag without re-tagging. Published tags are immutable: `release.yml` has no way to move or re-create one. ## Style From 86df10f1eb29b8a6f335e1ab563aa7b066858a46 Mon Sep 17 00:00:00 2001 From: Juan Cernadas Date: Thu, 5 Nov 2026 20:08:13 -0300 Subject: [PATCH 2/3] fix(deps): align converter-gson and gson with retrofit 2.12.0 converter-gson was left at 2.9.0 when retrofit moved to 2.12.0, and it still declared gson 2.8.5, which is affected by GHSA-4jrv-ppp4-jm57 (CVE-2022-25647). Maven already resolved the direct gson 2.11.0, so the vulnerable version was not on the classpath, but the declared graph kept the alert open and the Retrofit modules were out of sync. Move converter-gson to 2.12.0 and gson to 2.13.1, the version that converter-gson 2.12.0 is built against. --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 8c5f6a3..35fcf20 100644 --- a/pom.xml +++ b/pom.xml @@ -60,14 +60,14 @@ com.squareup.retrofit2 converter-gson - 2.9.0 + 2.12.0 com.google.code.gson gson - 2.11.0 + 2.13.1 From 2af1ca36d84d974d71543e140b58444f33aad425 Mon Sep 17 00:00:00 2001 From: Juan Cernadas Date: Thu, 5 Nov 2026 20:08:13 -0300 Subject: [PATCH 3/3] chore(release): bump version to 1.17.2 Ships the Retrofit, OkHttp, log4j and Gson dependency updates merged since v1.17.1. --- CLAUDE.md | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 836e0c2..55400e1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -62,7 +62,7 @@ The payments surface is Smart Transfers (`/smart-transfers/*`) and Pix por biome **3. Two interceptors handle cross-cutting concerns** -- `ApiKeyAuthInterceptor` (`client/auth/`): transparently fetches the `x-api-key` JWT via `POST /auth` on first use, caches it in `TokenProvider`, decodes the JWT `exp` claim to detect expiry, and on a 401/403 with an expired key refreshes once and retries the original request. An `x-api-key` already on the request is the caller's credential: it is sent as-is and never refreshed or replaced with the team key. Also sets `User-Agent: PluggyJava/` — note this string is hardcoded in two places (`PluggyClient.authenticate` and `ApiKeyAuthInterceptor.requestWithAuth`) and is not auto-derived from `pom.xml`. +- `ApiKeyAuthInterceptor` (`client/auth/`): transparently fetches the `x-api-key` JWT via `POST /auth` on first use, caches it in `TokenProvider`, decodes the JWT `exp` claim to detect expiry, and on a 401/403 with an expired key refreshes once and retries the original request. An `x-api-key` already on the request is the caller's credential: it is sent as-is and never refreshed or replaced with the team key. Also sets `User-Agent: PluggyJava/ (Java )` via `ai.pluggy.utils.Version`, which reads `project.version` from the filtered `pluggy.properties`, so bumping `pom.xml` is all a release needs. - `EncryptedParametersInterceptor` (`client/auth/`): only attached when `rsaPublicKey(...)` is set on the builder. RSA/ECB/OAEPPadding-encrypts the `parameters` JSON field on POST/PATCH `/items` requests before they leave OkHttp. All three conditions are required: Smart Transfer preauthorizations also send a `parameters` field, which must go out as plain JSON, and item requests without one (MFA) must pass through. ### JSON / Gson conventions — important gotcha diff --git a/pom.xml b/pom.xml index 35fcf20..157a93e 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ ai.pluggy pluggy-java - 1.17.1 + 1.17.2 jar