Summary
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:
|
console.debug( |
|
'Available account properties for new institution integration', |
|
{ account: JSON.stringify(account) }, |
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": []
}
}
}
|
console.debug( |
|
'Available (first 10) transactions properties for new integration of institution in sortTransactions function', |
|
{ top10Transactions: JSON.stringify(transactions.slice(0, 10)) }, |
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.
|
default: |
|
console.log('Something went wrong', inspect(error, { depth: null })); |
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
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.
Summary
The GoCardless components in Actualbudget in are logging responses to STDOUT in a parsed format using
console.logandconsole.debug(Which in this version of node is an alias forconsole.log). This is exposing sensitive information in log files including, but not limited to:Details
Whenever GoCardless responds to a request, the payload is printed to the debug log:
actual/packages/sync-server/src/app-gocardless/banks/integration-bank.js
Lines 25 to 27 in 36c40d9
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": [] } } }actual/packages/sync-server/src/app-gocardless/banks/integration-bank.js
Lines 83 to 85 in 36c40d9
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.
actual/packages/sync-server/src/app-gocardless/app-gocardless.js
Lines 263 to 264 in 36c40d9
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:
And quite a few pages more.
PoC
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.