Releases: stellar/anchor-platform
Release list
4.8.0
What's Changed
- chore(release): merge main to develop by @amandagonsalves in #2001
- [ANCHOR-1295]: SEP-31
customer_info_neededimplementation by @ceciliaromao in #2004 - add socket tier 1 reachability analysis by @kanwalpreetd in #1952
- [ANCHOR-1281]: SEP-12 customer-id ownership bypass in anchor-platform by @amandagonsalves in #1997
- [ANCHOR-1292]: Unvalidated signing key via Platform API /clients bricks the SEP server by @amandagonsalves in #2005
- [ANCHOR-1295]: SEP-31 refund memo by @ceciliaromao in #2006
- [ANCHOR-1302]: Observe destination accounts in SEP6/24 by @amandagonsalves in #2010
- chore(release): bump version to 4.8.0 by @ceciliaromao in #2014
- chore(release): merge release/4.8.0 to main by @ceciliaromao in #2015
Full Changelog: 4.7.1...4.8.0
4.7.1
What's Changed
- chore(release): merge main to develop by @ceciliaromao in #1996
- [ANCHOR-1289]: SEP-24 Reference UI broken by @amandagonsalves in #1998
- chore(release): bump version to 4.7.1 by @amandagonsalves in #1999
- chore(release): merge release/4.7.1 to main by @amandagonsalves in #2000
Full Changelog: 4.7.0...4.7.1
4.7.0
What's Changed
- chore(release): merge main to develop by @amandagonsalves in #1989
- [ANCHOR-1268]: SEP-10 authentication bypass and owner lockout via sign-extended threshold/weight parsing on the RPC ledger backend by @amandagonsalves in #1987
- [ANCHOR-1267]: Unauthenticated
client_domainpre-auth thread exhaustion in SEP-10/SEP-45 takes the whole anchor SEP API offline from one host by @amandagonsalves in #1990 - [ANCHOR-1236]: SEP-10 client_domain parameter causes server-side HTTP requests to arbitrary attacker-controlled hosts (SSRF) by @amandagonsalves in #1991
- [ANCHOR-1270]: Move client (wallet) config from file to database, with a REST management API and JWT-carried client attribution by @amandagonsalves in #1992
- chore(release): bump version to 4.7.0 by @ceciliaromao in #1994
- Chore/merge release 4.7.0 to main by @ceciliaromao in #1995
Full Changelog: 4.6.2...4.7.0
For #1992 - Migrating clients.type to db
This is the guide for anchors moving their client configuration (custodial and non-custodial
wallets, signing keys, domains, callback URLs, destination accounts) off a static file/yaml/
json config and onto the new db-backed option, where clients are managed live through the
/clients REST API instead of a redeploy.
- Why move to
db - Prerequisites
- 1. Point
clientsatdb - 2. Start the platform server once to import
- 3. Stop re-importing on every restart
- 4. Verify the import
- Managing clients going forward
- Validation rules and constraints
- Rolling back
- Troubleshooting
Why move to db
With clients.type set to file, yaml, or json, the client list is fixed at deploy time —
onboarding or updating a wallet means editing the config and restarting the platform server. With
clients.type=db, the same client records live in the database and are managed through
PUT/GET/DELETE calls on /clients, so onboarding a new wallet doesn't require a deploy.
Prerequisites
- A relational database, not the default
h2.data.typemust bepostgresoraurora.
The in-memoryh2default won't persist client rows across restarts, andsqlitedoesn't
support Flyway's foreign-key migrations. - Flyway enabled.
data.flyway_enabled=true. On first startup against that database, Flyway
appliesV32__client_config.sql, which creates four tables:client_config,client_domain,
client_signing_key, andclient_destination_account. No manual migration step is needed
beyond having Flyway on. - Platform API auth already configured. The
/clientsendpoints live on the platform server
and are protected the same way as the rest of the Platform API (platform_api.auth.type:JWT
orAPI_KEY). If you can already call other Platform API endpoints (e.g.PATCH /transactions), you can call/clients.
1. Point clients at db
clients.type=db
clients.value=path/to/your/existing/clients.yamlclients.value can stay pointed at whatever file (or inline YAML/JSON string) you were already
using with type=file/yaml/json — the format is unchanged. On db, that value is no longer
read by the running server; it's only used once, on startup, to seed the database.
2. Start the platform server once to import
On startup, if clients.type=db, a one-time CommandLineRunner reads every client out of
clients.value and upserts it into the database by name. Check the startup logs for:
Imported {N} of {N} clients into the database
A per-client failure (e.g. a duplicate signing key) is logged and skipped — it doesn't stop the
other clients from importing or block startup.
3. Stop re-importing on every restart
This import isn't a one-shot migration flag — it runs on every startup where
clients.type=db and clients.value is non-empty, and it re-upserts every client from that file
each time, overwriting whatever is currently in the database for those client names. Once you've
confirmed the import succeeded, clear the value so subsequent restarts don't clobber changes made
through the API afterward:
clients.type=db
clients.value=4. Verify the import
curl -H "<your platform auth header>" http://localhost:8085/clientsConfirm the count and names match what was in your old config file.
Managing clients going forward
| Method | Path | Purpose |
|---|---|---|
PUT |
/clients/{name} |
Create or fully replace a client |
GET |
/clients/{name} |
Fetch one client |
GET |
/clients |
List all clients |
GET |
/clients/custodial |
List custodial clients only |
GET |
/clients/non-custodial |
List non-custodial clients only |
DELETE |
/clients/{name} |
Delete a client |
POST |
/clients/{name}/signing-keys/{signingKey} |
Add a signing key |
DELETE |
/clients/{name}/signing-keys/{signingKey} |
Remove a signing key |
POST |
/clients/{name}/destination-accounts/{account} |
Add a destination account |
DELETE |
/clients/{name}/destination-accounts/{account} |
Remove a destination account |
PUT example (custodial client):
curl -X PUT -H "<your platform auth header>" -H "Content-Type: application/json" \
http://localhost:8085/clients/my-wallet \
-d '{"type":"custodial","signingKeys":["GABC...XYZ"]}'Validation rules and constraints
- A custodial client needs at least one signing key; a non-custodial client needs at least one
domain. Requests violating this are rejected with400. - A signing key or domain can only belong to one client at a time —
client_domain.domainand
client_signing_key.signing_keyare both unique across all clients. Reusing one on a different
client returns400. - You can't remove the last signing key from a custodial client via
DELETE /clients/{name}/signing-keys/{signingKey}— that also returns400. - Callback URLs, when set, must parse as valid
http/httpsURLs.
Rolling back
Deleting client rows isn't required to roll back. Setting clients.type back to file, yaml,
or json (with value pointed at your old config) reverts to reading clients from that source;
the database rows are simply unused while type isn't db.
Troubleshooting
Startup log shows fewer imported than expected, with no per-client error. Check for a
duplicate signing key or domain across clients in your source file — the import calls the same
uniqueness-checked upsert the API uses, so a collision there fails the same way a PUT would.
Clients you edited through the API reverted after a restart. clients.value is still set —
see Stop re-importing on every restart.
/clients returns 404 Not Found on a client you're sure exists. Client lookups are by
name and are case-sensitive; confirm the exact name used in the original PUT.
4.6.2
What's Changed
- chore(release): merge release/4.6.1 to main by @amandagonsalves in #1981
- chore(release): merge main to develop by @amandagonsalves in #1982
- [ANCHOR-1251]: Permanent DoS of Stellar-RPC Payment Observer via Broken Fault-Recovery by @amandagonsalves in #1983
- [ANCHOR-1236]: SEP-10
client_domainparameter causes server-side HTTP requests to arbitrary attacker-controlled hosts (SSRF) by @amandagonsalves in #1984 - [ANCHOR-1261]: SEP-6/24/31-only deployments fail to start due to missing bean dependencies by @amandagonsalves in #1985
- chore(release): bump version to 4.6.2 by @amandagonsalves in #1986
- chore(release): merge release/4.6.2 to main by @amandagonsalves in #1988
Full Changelog: 4.6.1...4.6.2
4.6.1
What's Changed
- chore(release): merge main to develop by @amandagonsalves in #1978
- fix(migration): revert V29 to its released checksum, move SEP-31 owner backfill to V31 by @amandagonsalves in #1979
- chore(release): bump version to 4.6.1 by @amandagonsalves in #1980
Full Changelog: 4.6.0...4.6.1
4.6.0
What's Changed
- chore(release): merge main to develop by @amandagonsalves in #1964
- [ANCHOR-1224]: Missing JWT audience/type binding in JwtService → SEP-10 user token usable as Platform-API admin credential under JWT secret reuse by @amandagonsalves in #1965
- [ANCHOR-1233]: SEP-24 withdraw skips the destination-account check its SEP-24 deposit sibling enforces by @amandagonsalves in #1968
- [ANCHOR-1237]: Attacker-chosen SEP-31 receiver_id bypasses the SEP-12 transaction_id ownership check by @amandagonsalves in #1966
- [ANCHOR-1225]: Strict-send sendAmount mis-credited as received USDC lets any wallet user drain a full withdrawal payout for ~$0 on-chain by @amandagonsalves in #1967
- [ANCHOR-1226]: SEP-6 deposit/withdraw-exchange skips the quote buy-asset check SEP-24 enforces, binding a quote to a mismatched destination asset by @amandagonsalves in #1969
- [ANCHOR-1228]: SEP-24 per-asset transaction limit (min/max_amount) bypassable via quote_id with omitted amount by @amandagonsalves in #1970
- [ANCHOR-1248]: Legacy SEP-31 customer ids are attacker-claimable, reopening the KYC IDOR for pre-upgrade customers by @amandagonsalves in #1972
- [ANCHOR-1249]: Payment observer as asset check warn only, non-native withdrawal is credited when the user sends native XLM by @amandagonsalves in #1973
- [ANCHOR-1252]: SEP-6 deposit-exchange lets a quote_id bypass the per-asset deposit limit by @amandagonsalves in #1974
- [ANCHOR-1254]: V30 backfill builds an owner row from two transactions, locking out legacy owners and enabling a co-tenant KYC claim by @amandagonsalves in #1975
- chore(release): bump version to 4.6.0 by @amandagonsalves in #1976
Full Changelog: 4.5.0...4.6.0
4.5.0
What's Changed
- chore(release): merge main to develop by @amandagonsalves in #1950
- [ANCHOR-1221]: NPE in notify_onchain_funds_received (hardcoded op index 0 + unsafe getPaymentOperation) strands received funds by @amandagonsalves in #1955
- [ANCHOR-1218]: StellarRpcPaymentObserver indexes filtered op list with full-tx operationIndex → payments never credited by @amandagonsalves in #1953
- [ANCHOR-1220]: SEP-10 multisig bypass: getAccount exception fails open to single master-key auth, skipping medium-threshold check by @amandagonsalves in #1954
- [ANCHOR-1212]: SEP-6 deposit persists unapproved account as to_account without enforcing destinationAccounts by @amandagonsalves in #1960
- [ANCHOR-1231]: Race condition between RPC events and transaction index by @amandagonsalves in #1962
- [ANCHOR-1016]: Move testnet reset scripts into Anchor Platform repo by @amandagonsalves in #1957
- chore(release): bump version to 4.5.0 by @amandagonsalves in #1961
- chore(release): merge release/4.5.0 to main by @amandagonsalves in #1963
Full Changelog: 4.4.0...4.5.0
4.4.0
What's Changed
- [Chore] Merge
maintodevby @JiahuiWho in #1933 - chore(soroban): bump soroban-sdk from v22 to v26 by @amandagonsalves in #1937
- [ANCHOR-1206] Add Gateway API support to helm charts by @JiahuiWho in #1936
- [ANCHOR-1206] Carry forward security headers for Gateway API migration by @JiahuiWho in #1940
- [ANCHOR-1215]: Anchor Platform SEP-12 IDOR via unvalidated customer id allows cross-customer KYC disclosure and payout destination overwrite by @amandagonsalves in #1946
- chore(release): bump version to 4.4.0 by @amandagonsalves in #1948
- chore(release): merge release/4.4.0 to main by @amandagonsalves in #1949
Full Changelog: 4.3.0...4.4.0
4.3.0
⚠️ Pre-upgrade muxed transaction migration (optional)
What's changed
SEP-24 and SEP-6 transactions are now stored with the muxed M-address as web_auth_account instead of the underlying G-address. New transactions created after the upgrade are isolated per muxed customer, closing a cross-customer authorization issue.
For transactions created before the upgrade by muxed customers:
- They will not appear in
GET /sep24/transactionsorGET /sep6/transactionswhen called with a muxed JWT (their storedweb_auth_accountdoesn't start withM). - They are still accessible via
GET /sep24/transactionorGET /sep6/transaction(single-row, byid/stellar_transaction_id/external_transaction_id), but the platform cannot tell pre-upgrade rows apart by muxed sub-ID — any muxed JWT on the same shared G-account will be allowed to read them. The muxed sub-ID for those rows was discarded at storage time and cannot be reconstructed from the platform's data.
Who should migrate
Not affected: if you only use plain G-accounts or G:memo legacy memo form for SEP-10 auth, skip this section.
Affected: if you issue muxed (M...) SEP-10 JWTs to multiple customers under one shared G-account, consider migrating if you want either:
- Pre-upgrade transactions to show up in muxed customers' listings (
GET /sep24/transactions,GET /sep6/transactions). - Strict per-customer isolation on legacy rows. Without migration, any muxed JWT on the same G can still read pre-upgrade rows via the single-row endpoints.
Migration steps
The platform does not contain the muxed-sub-ID-to-transaction mapping for pre-upgrade rows. You'll need to assemble it from your business server's records — typically your webhook/callback log customer-to-transaction mapping table, or KYC system, all of which received the muxed identity at the time the transaction was created.
-
Back up
sep24_transactionandsep6_transaction. -
Build a mapping CSV with two columns:
id(the SEP transaction UUID) andmuxed_account(the M-address that should be set on the row), one row per pre-upgrade transaction created by a muxed customer. -
Pre-flight: preview the rows using your CSV's IDs. Row count and timestamps should match what you expect.
SELECT id, web_auth_account, started_at, kind FROM sep24_transaction WHERE web_auth_account NOT LIKE 'M%' AND id IN (<list of IDs from your CSV>); Repeat against sep6_transaction.
-
Apply the migration using a temporary mapping table:
CREATE TEMP TABLE muxed_mapping (id varchar, muxed_account varchar); \copy muxed_mapping FROM 'mapping.csv' WITH CSV HEADER UPDATE sep24_transaction t SET web_auth_account = m.muxed_account FROM muxed_mapping m WHERE t.id = m.id AND t.web_auth_account NOT LIKE 'M%'; UPDATE sep6_transaction t SET web_auth_account = m.muxed_account FROM muxed_mapping m WHERE t.id = m.id AND t.web_auth_account NOT LIKE 'M%';
-
Verify by calling
GET /sep24/transaction?id=<migrated-id>with the correct muxed JWT (expect 200) and with a different muxed JWT on the same G (expect 404).
What if some rows can't be mapped
If your business server doesn't have records for some pre-upgrade muxed transactions, those row can't be safely migrated — there's no way to know which M-address originally created them. Your options:
- Leave them as-is. They remain accessible to any muxed JWT on the shared G via the single-row endpoints, and not enumerable via the multi-row endpoints.
- Delete them, if compliance permits and they are in terminal states (completed, refunded, expired).
Rollback
Restore the backup from step 1 if anything looks wrong. The migration is UPDATE-only, no rows are added or deleted.
Full Changelog: 4.2.2...4.3.0
4.2.2
What's Changed
- [ANCHOR-1190] Fix Docker release to publish multi-arch images by @JiahuiWho in #1922
- [ANCHOR-1190] Fix multi-arch image build by @JiahuiWho in #1923
- [ANCHOR -1195] Fix error serialization when Horizon look up fails by @JiahuiWho in #1924
- [ANCHOR-1196] Fix discarded return value in muxed account address encoding by @JiahuiWho in #1925
Full Changelog: 4.2.1...4.2.2