Description
Version 1.4.1 introduces undocumented telemetry in utils/logger.js that fires automatically on every SDK initialization. There is no opt-out, no disclosure in the README, and no meaningful changelog entry describing the privacy implications.
On every init() call, the SDK silently:
- Sends the user's Flutterwave public API key as a URL query parameter to an external endpoint
- Uses that key to resolve the merchant's real business name
- Transmits the resolved business name to a Flutterwave-controlled telemetry server
- Writes an SDK state file to the system temp directory
This is not a malicious finding- the infrastructure (f4b-flutterwave.com) is Flutterwave-controlled. The concern is that this behavior is entirely undisclosed and users have no way to consent to or disable it.
Steps to Reproduce
- Install
flutterwave-node-v3@1.4.1
- Call any SDK method that triggers
init(publicKey, libraryVersion) — this fires automatically on SDK use
- Observe two outbound HTTPS requests: one to
api.ravepay.co, one to signozservice-prod.f4b-flutterwave.com
- Observe a new file created at
os.tmpdir()/.flw_sdk.json
Expected behaviour
SDK initialization performs only the operations documented in the README. Any telemetry or analytics collection is disclosed upfront and can be disabled.
Actual behaviour
1. Public API key sent as a URL query parameter
The user's Flutterwave public key is embedded directly in the URL of a GET request, where it will appear in server access logs, reverse proxy logs, and any network monitoring in the user's environment:
// utils/logger.js
const query = `PBFPubKey=${encodeURIComponent(publicKey)}`;
const options = {
hostname: 'api.ravepay.co',
path: `/flwv3-pug/getpaidx/api/mercinfo?${query}`, // key visible in URL
method: 'GET',
};
2. Merchant business name resolved and transmitted without consent
The SDK uses the API key to look up the merchant's real business name, then ships it to the telemetry endpoint- going well beyond what SDK analytics would reasonably require:
// utils/logger.js
const name = data?.mn || null; // merchant's real business name
return _post({
name: 'app.created',
data: {
public_key: merchantName, // business name transmitted to f4b-flutterwave.com
library: LIBRARY,
library_version: libraryVersion,
},
});
3. No opt-out mechanism
init() fires unconditionally with no environment variable, configuration flag, or any other way to disable it:
// utils/logger.js
function init(publicKey, libraryVersion = DEFAULT_LIBRARY_VERSION) {
if (_initPromise) return _initPromise; // only prevents double-init, not disabling
_initPromise = _fetchMerchantName(publicKey) // always executes
.then((merchantName) => {
return _post({ name: 'app.created', ... }); // always executes
})
4. Hardcoded telemetry API key committed in source
A fallback API key for the telemetry service is hardcoded in plaintext and is now publicly readable in the npm tarball:
// utils/logger.js
const API_KEY =
process.env.SIGNOZ_API_KEY || 'IuUnO5cwI6Ta1JO/LEFUsMyz1AH3FNzW';
Anyone who reads the source can use this key to post arbitrary events to the telemetry endpoint.
5. Silent disk writes to system temp directory
The SDK writes a persistent state file to os.tmpdir() on every initialization, with no documentation and no cleanup:
// utils/logger.js
const STORE_PATH =
process.env.FLW_SDK_STATE_PATH || path.join(os.tmpdir(), '.flw_sdk.json');
function _saveAppId(publicKey, appId) {
const store = _readStore();
store[_keyHash(publicKey)] = {
app_id: appId,
saved_at: new Date().toISOString(),
};
fs.writeFileSync(STORE_PATH, JSON.stringify(store, null, 2), 'utf8');
}
This will fail silently in read-only container filesystems and persists indefinitely with no documented cleanup path.
Reproduces how often
fires on every fresh SDK initialization where the in-memory and disk caches are empty (i.e. first run per environment).
Configuration
- API Version: v3
- Environment: test mode and live mode (fires in both; key prefix determines which)
- Browser: N/A
- Language: Node.js
Additional Information
The CHANGELOG entry for 1.4.1 reads:
Refactor Signoz integration.
- [UPDATED] Persist merchant name across different event logs.
There is no mention of outbound network calls, identity resolution from the API key, or filesystem writes. Users reading the changelog before upgrading have no way to understand the privacy implications of this version.
Suggested fixes:
- Add an opt-out at the top of
init():
if (process.env.FLW_DISABLE_TELEMETRY === '1') return Promise.resolve();
- Document the telemetry behavior in the README — what is collected, where it is sent, and how to disable it
- Switch the mercinfo call from GET to POST with a JSON body, to keep the API key out of URL logs
- Remove the hardcoded
SIGNOZ_API_KEY fallback value from source
- Document the
os.tmpdir()/.flw_sdk.json file and provide a cleanup path or TTL
Description
Version
1.4.1introduces undocumented telemetry inutils/logger.jsthat fires automatically on every SDK initialization. There is no opt-out, no disclosure in the README, and no meaningful changelog entry describing the privacy implications.On every
init()call, the SDK silently:This is not a malicious finding- the infrastructure (
f4b-flutterwave.com) is Flutterwave-controlled. The concern is that this behavior is entirely undisclosed and users have no way to consent to or disable it.Steps to Reproduce
flutterwave-node-v3@1.4.1init(publicKey, libraryVersion)— this fires automatically on SDK useapi.ravepay.co, one tosignozservice-prod.f4b-flutterwave.comos.tmpdir()/.flw_sdk.jsonExpected behaviour
SDK initialization performs only the operations documented in the README. Any telemetry or analytics collection is disclosed upfront and can be disabled.
Actual behaviour
1. Public API key sent as a URL query parameter
The user's Flutterwave public key is embedded directly in the URL of a GET request, where it will appear in server access logs, reverse proxy logs, and any network monitoring in the user's environment:
2. Merchant business name resolved and transmitted without consent
The SDK uses the API key to look up the merchant's real business name, then ships it to the telemetry endpoint- going well beyond what SDK analytics would reasonably require:
3. No opt-out mechanism
init()fires unconditionally with no environment variable, configuration flag, or any other way to disable it:4. Hardcoded telemetry API key committed in source
A fallback API key for the telemetry service is hardcoded in plaintext and is now publicly readable in the npm tarball:
Anyone who reads the source can use this key to post arbitrary events to the telemetry endpoint.
5. Silent disk writes to system temp directory
The SDK writes a persistent state file to
os.tmpdir()on every initialization, with no documentation and no cleanup:This will fail silently in read-only container filesystems and persists indefinitely with no documented cleanup path.
Reproduces how often
fires on every fresh SDK initialization where the in-memory and disk caches are empty (i.e. first run per environment).
Configuration
Additional Information
The CHANGELOG entry for
1.4.1reads:There is no mention of outbound network calls, identity resolution from the API key, or filesystem writes. Users reading the changelog before upgrading have no way to understand the privacy implications of this version.
Suggested fixes:
init():SIGNOZ_API_KEYfallback value from sourceos.tmpdir()/.flw_sdk.jsonfile and provide a cleanup path or TTL