Skip to content

Releases: stellar/anchor-platform

4.8.0

Choose a tag to compare

@ceciliaromao ceciliaromao released this 11 Sep 01:01
1f3188f

What's Changed

Full Changelog: 4.7.1...4.8.0

4.7.1

Choose a tag to compare

@amandagonsalves amandagonsalves released this 26 Aug 18:59
3ebdffa

What's Changed

Full Changelog: 4.7.0...4.7.1

4.7.0

Choose a tag to compare

@ceciliaromao ceciliaromao released this 20 Aug 17:38
b1cbb5a

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_domain pre-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

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.type must be postgres or aurora.
    The in-memory h2 default won't persist client rows across restarts, and sqlite doesn't
    support Flyway's foreign-key migrations.
  • Flyway enabled. data.flyway_enabled=true. On first startup against that database, Flyway
    applies V32__client_config.sql, which creates four tables: client_config, client_domain,
    client_signing_key, and client_destination_account. No manual migration step is needed
    beyond having Flyway on.
  • Platform API auth already configured. The /clients endpoints live on the platform server
    and are protected the same way as the rest of the Platform API (platform_api.auth.type: JWT
    or API_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.yaml

clients.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/clients

Confirm 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 with 400.
  • A signing key or domain can only belong to one client at a time — client_domain.domain and
    client_signing_key.signing_key are both unique across all clients. Reusing one on a different
    client returns 400.
  • You can't remove the last signing key from a custodial client via DELETE /clients/{name}/signing-keys/{signingKey} — that also returns 400.
  • Callback URLs, when set, must parse as valid http/https URLs.

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

Choose a tag to compare

@amandagonsalves amandagonsalves released this 03 Aug 14:14
4234dd3

What's Changed

Full Changelog: 4.6.1...4.6.2

4.6.1

Choose a tag to compare

@amandagonsalves amandagonsalves released this 20 Jul 15:52
3992f90

What's Changed

Full Changelog: 4.6.0...4.6.1

4.6.0

Choose a tag to compare

@amandagonsalves amandagonsalves released this 16 Jul 16:46
5b7b98a

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

Choose a tag to compare

@amandagonsalves amandagonsalves released this 01 Jul 19:03
f64c7c7

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

Choose a tag to compare

@amandagonsalves amandagonsalves released this 08 Jun 16:20
c4a2595

What's Changed

Full Changelog: 4.3.0...4.4.0

4.3.0

Choose a tag to compare

@JiahuiWho JiahuiWho released this 06 May 12:39
e3b99d8

⚠️ 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/transactions or GET /sep6/transactions when called with a muxed JWT (their stored web_auth_account doesn't start with M).
  • They are still accessible via GET /sep24/transaction or GET /sep6/transaction (single-row, by id / 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.

  1. Back up sep24_transaction and sep6_transaction.

  2. Build a mapping CSV with two columns: id (the SEP transaction UUID) and muxed_account (the M-address that should be set on the row), one row per pre-upgrade transaction created by a muxed customer.

  3. 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.                                                                                                 
    
  4. 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%';
    
    
  5. 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

Choose a tag to compare

@JiahuiWho JiahuiWho released this 29 Apr 18:05
29ed8bd

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