GHSA-XVP7-8VM8-XFXX
Vulnerability from github – Published: 2025-10-20 17:55 – Updated: 2026-06-08 23:28Summary
The GoCardless components in Actualbudget in are logging responses to STDOUT in a parsed format using console.logand console.debug (Which in this version of node is an alias for console.log). This is exposing sensitive information in log files including, but not limited to:
- Gocardless bearer tokens.
- Account IBAN and Bank Account numbers.
- PII of the account holder.
- Transaction details (Payee bank information, Recipient account numbers, Transaction IDs)...
Details
Whenever GoCardless responds to a request, the payload is printed to the debug log: https://github.com/actualbudget/actual/blob/36c40d90d2fe09eb1f25a6e2f77f6dd40638b267/packages/sync-server/src/app-gocardless/banks/integration-bank.js#L25-L27
This in turn logs the following information to Docker (all values removed here. These fields are possibly dependent on what is returned by each institution so may differ):
{
"account": {
"resourceId": "",
"iban": "",
"bban": "",
"currency": "",
"name": "<full legal name in the bank>",
"product": "",
"status": "",
"bic": "",
"usage": "",
"id": "",
"created": "",
"last_accessed": "",
"institution_id": "",
"owner_name": "",
"institution": {
"id": "",
"name": "",
"bic": "",
"transaction_total_days": "",
"countries": [
""
],
"logo": "",
"max_access_valid_for_days": "",
"supported_features": [
"",
"",
""
],
"identification_codes": []
}
}
}
https://github.com/actualbudget/actual/blob/36c40d90d2fe09eb1f25a6e2f77f6dd40638b267/packages/sync-server/src/app-gocardless/banks/integration-bank.js#L83-L85
This is the first of the 10 transactions:
{
"top10Transactions": [{
"transactionId": "",
"entryReference": "",
"bookingDate": "",
"valueDate": "",
"transactionAmount": {
"amount": "",
"currency": ""
},
"creditorName": "",
"creditorAccount": {
"bban": ""
},
"debtorName": "",
"debtorAccount": {
"bban": ""
},
"remittanceInformationUnstructured": "",
"remittanceInformationStructuredArray": [
{"reference": "", "referenceType": ""}
],
"additionalInformation": "",
"proprietaryBankTransactionCode": "",
"debtorAgent": "",
"internalTransactionId": "",
"payeeName": "",
"date": ""
}]
}
Additionally, in the error handling for GoCardless, there is a catch all for unclassified errors that prints the entire stack trace to the console.
https://github.com/actualbudget/actual/blob/36c40d90d2fe09eb1f25a6e2f77f6dd40638b267/packages/sync-server/src/app-gocardless/app-gocardless.js#L263-L264
Our bank was offline today for maintenance which threw a 503 error from Gocardless. The entire response payload was dumped to console, which includes the Bearer tokens for accessing GoCardless:
Something went wrong ServiceError: Institution service unavailable
at handleGoCardlessError (file:///app/src/app-gocardless/services/gocardless-service.js:59:13)
at Object.getTransactions (file:///app/src/app-gocardless/services/gocardless-service.js:530:7)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async Object.getNormalizedTransactions (file:///app/src/app-gocardless/services/gocardless-service.js:267:26)
at async file:///app/src/app-gocardless/app-gocardless.js:186:13 {
details: h [AxiosError]: Request failed with status code 503
at te (file:///app/node_modules/nordigen-node/dist/index.esm.js:13:914)
at IncomingMessage.<anonymous> (file:///app/node_modules/nordigen-node/dist/index.esm.js:17:16315)
at IncomingMessage.emit (node:events:529:35)
at endReadableNT (node:internal/streams/readable:1400:12)
at process.processTicksAndRejections (node:internal/process/task_queues:82:21) {
code: 'ERR_BAD_RESPONSE',
config: {
transitional: {
silentJSONParsing: true,
forcedJSONParsing: true,
clarifyTimeoutError: false
},
adapter: [ 'xhr', 'http' ],
transformRequest: [ [Function (anonymous)] ],
transformResponse: [ [Function (anonymous)] ],
timeout: 0,
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1,
maxBodyLength: -1,
env: {
FormData: [Function: _] {
LINE_BREAK: '\r\n',
DEFAULT_CONTENT_TYPE: 'application/octet-stream'
},
Blob: [class Blob]
},
validateStatus: [Function: validateStatus],
headers: T [AxiosHeaders] {
Accept: 'application/json',
'Content-Type': 'application/json',
'User-Agent': 'Nordigen-Node-v2',
'Authorization': 'Bearer eyJ0eXAi... (the full token is in the response)',
'Accept-Encoding': 'gzip, compress, deflate, br'
},
method: 'get',
url: URL {
href: 'https://bankaccountdata.gocardless.com/api/v2/accounts/<Account id Was Here>?date_from=2024-12-22',
origin: 'https://bankaccountdata.gocardless.com',
protocol: 'https:',
username: '',
password: '',
host: 'bankaccountdata.gocardless.com',
hostname: 'bankaccountdata.gocardless.com',
port: '',
pathname: '/api/v2/accounts/<Account id Was Here>/transactions',
search: '?date_from=2024-12-22',
searchParams: URLSearchParams { 'date_from' => '2024-12-22' },
hash: ''
},
data: undefined
},
And quite a few pages more.
PoC
- Setup an Actualbudget server inside of Docker. In this instance I was using the Docker Compose script posted in the repository: https://github.com/actualbudget/actual/blob/master/packages/sync-server/docker-compose.yml
- Link a gocardless account to Actualbudget and sync a bank account
- Observe in the container using
docker logs actual-actual_server-1 -fthat sensitive details are logged to the console and ingested by docker.
Impact
Information disclosure. The services are available both on-premises and in environments that are not under the control of the end user, such as third-party providers who offer this application as a managed solution.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 25.10.0"
},
"package": {
"ecosystem": "npm",
"name": "@actual-app/sync-server"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "25.11.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-209",
"CWE-215",
"CWE-219"
],
"github_reviewed": true,
"github_reviewed_at": "2025-10-20T17:55:59Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "### Summary\nThe GoCardless components in Actualbudget in are logging responses to STDOUT in a parsed format using `console.log`and `console.debug` (Which in this version of node is an alias for `console.log`). This is exposing sensitive information in log files including, but not limited to:\n\n- Gocardless bearer tokens.\n- Account IBAN and Bank Account numbers.\n- PII of the account holder.\n- Transaction details (Payee bank information, Recipient account numbers, Transaction IDs)...\n\n### Details\n\nWhenever GoCardless responds to a request, the payload is printed to the debug log: \nhttps://github.com/actualbudget/actual/blob/36c40d90d2fe09eb1f25a6e2f77f6dd40638b267/packages/sync-server/src/app-gocardless/banks/integration-bank.js#L25-L27\n\nThis in turn logs the following information to Docker (all values removed here. These fields are possibly dependent on what is returned by each institution so may differ):\n\n```json\n{\n \"account\": {\n \"resourceId\": \"\",\n \"iban\": \"\",\n \"bban\": \"\",\n \"currency\": \"\",\n \"name\": \"\u003cfull legal name in the bank\u003e\",\n \"product\": \"\",\n \"status\": \"\",\n \"bic\": \"\",\n \"usage\": \"\",\n \"id\": \"\",\n \"created\": \"\",\n \"last_accessed\": \"\",\n \"institution_id\": \"\",\n \"owner_name\": \"\",\n \"institution\": {\n \"id\": \"\",\n \"name\": \"\",\n \"bic\": \"\",\n \"transaction_total_days\": \"\",\n \"countries\": [\n \"\"\n ],\n \"logo\": \"\",\n \"max_access_valid_for_days\": \"\",\n \"supported_features\": [\n \"\",\n \"\",\n \"\"\n ],\n \"identification_codes\": []\n }\n }\n}\n```\n\nhttps://github.com/actualbudget/actual/blob/36c40d90d2fe09eb1f25a6e2f77f6dd40638b267/packages/sync-server/src/app-gocardless/banks/integration-bank.js#L83-L85\n\nThis is the first of the 10 transactions:\n```json\n{\n \"top10Transactions\": [{\n \"transactionId\": \"\",\n \"entryReference\": \"\",\n \"bookingDate\": \"\",\n \"valueDate\": \"\",\n \"transactionAmount\": {\n \"amount\": \"\",\n \"currency\": \"\"\n },\n \"creditorName\": \"\",\n \"creditorAccount\": {\n \"bban\": \"\"\n },\n \"debtorName\": \"\",\n \"debtorAccount\": {\n \"bban\": \"\"\n },\n \"remittanceInformationUnstructured\": \"\",\n \"remittanceInformationStructuredArray\": [\n {\"reference\": \"\", \"referenceType\": \"\"}\n ],\n \"additionalInformation\": \"\",\n \"proprietaryBankTransactionCode\": \"\",\n \"debtorAgent\": \"\",\n \"internalTransactionId\": \"\",\n \"payeeName\": \"\",\n \"date\": \"\"\n }]\n}\n```\n\nAdditionally, in the error handling for GoCardless, there is a catch all for unclassified errors that prints the entire stack trace to the console.\n\nhttps://github.com/actualbudget/actual/blob/36c40d90d2fe09eb1f25a6e2f77f6dd40638b267/packages/sync-server/src/app-gocardless/app-gocardless.js#L263-L264\n\nOur bank was offline today for maintenance which threw a 503 error from Gocardless. The entire response payload was dumped to console, which includes the Bearer tokens for accessing GoCardless:\n\n```java\nSomething went wrong ServiceError: Institution service unavailable\n at handleGoCardlessError (file:///app/src/app-gocardless/services/gocardless-service.js:59:13)\n at Object.getTransactions (file:///app/src/app-gocardless/services/gocardless-service.js:530:7)\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)\n at async Object.getNormalizedTransactions (file:///app/src/app-gocardless/services/gocardless-service.js:267:26)\n at async file:///app/src/app-gocardless/app-gocardless.js:186:13 {\n details: h [AxiosError]: Request failed with status code 503\n at te (file:///app/node_modules/nordigen-node/dist/index.esm.js:13:914)\n at IncomingMessage.\u003canonymous\u003e (file:///app/node_modules/nordigen-node/dist/index.esm.js:17:16315)\n at IncomingMessage.emit (node:events:529:35)\n at endReadableNT (node:internal/streams/readable:1400:12)\n at process.processTicksAndRejections (node:internal/process/task_queues:82:21) {\n code: \u0027ERR_BAD_RESPONSE\u0027,\n config: {\n transitional: {\n silentJSONParsing: true,\n forcedJSONParsing: true,\n clarifyTimeoutError: false\n },\n adapter: [ \u0027xhr\u0027, \u0027http\u0027 ],\n transformRequest: [ [Function (anonymous)] ],\n transformResponse: [ [Function (anonymous)] ],\n timeout: 0,\n xsrfCookieName: \u0027XSRF-TOKEN\u0027,\n xsrfHeaderName: \u0027X-XSRF-TOKEN\u0027,\n maxContentLength: -1,\n maxBodyLength: -1,\n env: {\n FormData: [Function: _] {\n LINE_BREAK: \u0027\\r\\n\u0027,\n DEFAULT_CONTENT_TYPE: \u0027application/octet-stream\u0027\n },\n Blob: [class Blob]\n },\n validateStatus: [Function: validateStatus],\n headers: T [AxiosHeaders] {\n Accept: \u0027application/json\u0027,\n \u0027Content-Type\u0027: \u0027application/json\u0027,\n \u0027User-Agent\u0027: \u0027Nordigen-Node-v2\u0027,\n \u0027Authorization\u0027: \u0027Bearer eyJ0eXAi... (the full token is in the response)\u0027,\n \u0027Accept-Encoding\u0027: \u0027gzip, compress, deflate, br\u0027\n },\n method: \u0027get\u0027,\n url: URL {\n href: \u0027https://bankaccountdata.gocardless.com/api/v2/accounts/\u003cAccount id Was Here\u003e?date_from=2024-12-22\u0027,\n origin: \u0027https://bankaccountdata.gocardless.com\u0027,\n protocol: \u0027https:\u0027,\n username: \u0027\u0027,\n password: \u0027\u0027,\n host: \u0027bankaccountdata.gocardless.com\u0027,\n hostname: \u0027bankaccountdata.gocardless.com\u0027,\n port: \u0027\u0027,\n pathname: \u0027/api/v2/accounts/\u003cAccount id Was Here\u003e/transactions\u0027,\n search: \u0027?date_from=2024-12-22\u0027,\n searchParams: URLSearchParams { \u0027date_from\u0027 =\u003e \u00272024-12-22\u0027 },\n hash: \u0027\u0027\n },\n data: undefined\n },\n```\nAnd quite a few pages more.\n\n### PoC\n- Setup an Actualbudget server inside of Docker. In this instance I was using the Docker Compose script posted in the repository: https://github.com/actualbudget/actual/blob/master/packages/sync-server/docker-compose.yml\n- Link a gocardless account to Actualbudget and sync a bank account\n- Observe in the container using `docker logs actual-actual_server-1 -f` that sensitive details are logged to the console and ingested by docker. \n\n### Impact\nInformation disclosure. The services are available both on-premises and in environments that are not under the control of the end user, such as third-party providers who offer this application as a managed solution.",
"id": "GHSA-xvp7-8vm8-xfxx",
"modified": "2026-06-08T23:28:41Z",
"published": "2025-10-20T17:55:59Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/actualbudget/actual/security/advisories/GHSA-xvp7-8vm8-xfxx"
},
{
"type": "WEB",
"url": "https://github.com/actualbudget/actual/commit/97482a082d502887ef22514b93e35e4c67f4f30f"
},
{
"type": "PACKAGE",
"url": "https://github.com/actualbudget/actual"
},
{
"type": "WEB",
"url": "https://github.com/actualbudget/actual/blob/36c40d90d2fe09eb1f25a6e2f77f6dd40638b267/packages/sync-server/src/app-gocardless/app-gocardless.js#L263-L264"
},
{
"type": "WEB",
"url": "https://github.com/actualbudget/actual/blob/36c40d90d2fe09eb1f25a6e2f77f6dd40638b267/packages/sync-server/src/app-gocardless/banks/integration-bank.js#L25-L27"
},
{
"type": "WEB",
"url": "https://github.com/actualbudget/actual/blob/36c40d90d2fe09eb1f25a6e2f77f6dd40638b267/packages/sync-server/src/app-gocardless/banks/integration-bank.js#L83-L85"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:L/PR:H/UI:R/S:U/C:H/I:N/A:N",
"type": "CVSS_V3"
}
],
"summary": "Actual Sync-server Gocardless service is logging sensitive data including bearer tokens and account numbers"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.