CWE-219
AllowedStorage of File with Sensitive Data Under Web Root
Abstraction: Variant · Status: Draft
The product stores sensitive data under the web document root with insufficient access control, which might make it accessible to untrusted parties.
11 vulnerabilities reference this CWE, most recent first.
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"
}
Mitigation
Avoid storing information under the web root directory.
Mitigation
Access control permissions should be set to prevent reading/writing of sensitive files inside/outside of the web directory.
No CAPEC attack patterns related to this CWE.