How Plaintext Credential Storage Happens in Node-RED Custom Nodes and How to Fix It
Introduction
In the telegrambot/99-telegrambot.html file at line 67, we discovered a critical credential storage vulnerability where the Telegram bot token was declared as a Node-RED credential of type 'text'. This single configuration choice — token: { type: 'text' } inside the RED.nodes.registerType() call — meant that the bot's API token was treated as non-sensitive data by the entire Node-RED platform.
This matters because the Telegram bot token is the single authentication factor for controlling a bot. Anyone who obtains it can read messages, send messages as the bot, access group chats, and potentially exfiltrate data from all conversations the bot participates in. For developers building Node-RED custom nodes, this vulnerability demonstrates how a seemingly minor type declaration can have catastrophic security implications.
The Vulnerability Explained
Node-RED has a built-in credential management system that differentiates between two types of stored values:
type: 'text'— Treated as non-sensitive. Stored inflows_cred.json, returned in full via the admin REST API, and visible in the editor.type: 'password'— Treated as a secret. Encrypted at rest, masked in the editor UI, and never sent back to the client after initial storage.
Here's the vulnerable code from telegrambot/99-telegrambot.html:
credentials: {
token: { type: 'text' },
},
By declaring the token as 'text', the node inadvertently opted out of Node-RED's security protections. This created two concrete attack vectors:
Attack Vector 1: Filesystem Access
An attacker who gains read access to the Node-RED user directory (via a path traversal vulnerability, misconfigured file permissions, a backup exposure, or container escape) can read flows_cred.json directly. With type: 'text', the token is stored without the encryption that Node-RED applies to 'password' type credentials.
Attack Vector 2: Admin API Exposure
Node-RED's admin API endpoint (GET /credentials/:type/:id) returns credential values to the editor. For 'text' type credentials, the full value is returned. If the admin API is exposed without authentication (a common misconfiguration in development or IoT deployments), any network-adjacent attacker can query the API and retrieve the bot token in cleartext.
Additionally, the PR description notes that a prior version (pre-v17.3.0) leaked the token in verbose logs, indicating a pattern of historical exposure that compounds the risk.
Real-world exploitation scenario:
- Attacker discovers a Node-RED instance with the Telegram bot node deployed (common in home automation, IoT, and notification systems).
- Attacker accesses
flows_cred.json(via exposed.node-reddirectory, Docker volume misconfiguration, or backup file). - Attacker extracts the Telegram bot token directly from the JSON file.
- Attacker calls
https://api.telegram.org/bot<TOKEN>/getUpdatesto read all recent messages. - Attacker uses the token to impersonate the bot, send phishing messages to group members, or exfiltrate sensitive data.
The Fix
The fix is a single-character type change on line 67 of telegrambot/99-telegrambot.html:
Before (vulnerable):
credentials: {
token: { type: 'text' },
},
After (fixed):
credentials: {
token: { type: 'password' },
},
This change instructs Node-RED to:
- Encrypt the token at rest — When stored in
flows_cred.json, the value is encrypted using Node-RED's credential secret key (configured viacredentialSecretinsettings.js). - Mask the token in the editor — The UI displays the field as a password input, preventing shoulder-surfing.
- Never return the token via the admin API — Once stored, the API returns only a boolean
has_token: trueindicator, not the actual value. This eliminates the network-based exfiltration vector entirely. - Prevent logging exposure — Since the platform knows this is a secret, it avoids including it in debug output or verbose logs.
The fix is minimal and behavior-preserving: the bot still receives and uses its token for Telegram API authentication, but the token is now handled as a secret throughout its lifecycle within Node-RED.
Prevention & Best Practices
For Node-RED custom node developers:
-
Always use
type: 'password'for secrets — Any credential field containing API keys, tokens, passwords, or other authentication material must be declared as'password', never'text'. -
Audit existing nodes — Search your node HTML files for
type: 'text'in credential declarations and evaluate whether each field contains sensitive data. -
Set a strong
credentialSecret— Node-RED's encryption of password-type credentials depends on thecredentialSecretinsettings.js. Ensure this is set to a strong, unique value in production. -
Restrict admin API access — Enable authentication on the Node-RED admin interface using
adminAuthin settings. Never expose the admin API to untrusted networks. -
Use environment variables for tokens — Consider using
${ENV_VAR}syntax in Node-RED to inject tokens from environment variables rather than storing them in flows at all.
Relevant standards:
- CWE-256: Plaintext Storage of a Password
- CWE-522: Insufficiently Protected Credentials
- OWASP: Sensitive Data Exposure (A02:2021 – Cryptographic Failures)
Key Takeaways
- Node-RED's credential type declaration is a security boundary — The difference between
'text'and'password'is not cosmetic; it determines whether encryption, masking, and API protection are applied. - A Telegram bot token in
flows_cred.jsonwithtype: 'text'is recoverable by any attacker with filesystem read access — no decryption needed. - The admin API returns full values for
'text'credentials — meaning unauthenticated admin API access (common in dev/IoT) directly exposes the token over the network. - Historical log leakage (pre-v17.3.0) compounds the risk — tokens that were stored as plaintext may have already been written to log files, backups, or monitoring systems.
- One-line fixes can eliminate critical vulnerabilities — this demonstrates why security-focused code review of configuration declarations is as important as reviewing logic.
How Orbis AppSec Detected This
- Source: The Telegram bot token entered the system via the Node-RED editor credential input form and was persisted via the
RED.nodes.registerType()credential system. - Sink: The
flows_cred.jsonfile on disk and the Node-RED admin REST API endpoint (GET /credentials/telegram-bot/:id), both intelegrambot/99-telegrambot.html:67. - Missing control: The credential was declared as
type: 'text'instead oftype: 'password', bypassing Node-RED's built-in encryption-at-rest and API masking for secrets. - CWE: CWE-256 (Plaintext Storage of a Password)
- Fix: Changed the credential type declaration from
'text'to'password'to enable Node-RED's native credential encryption and prevent token exposure via the admin API.
Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.
Conclusion
This vulnerability illustrates a subtle but critical class of security issues in Node-RED custom node development: the credential type declaration acts as a security policy decision, not merely a UI hint. By declaring the Telegram bot token as type: 'text', the node author inadvertently disabled encryption at rest, enabled plaintext API retrieval, and exposed the most sensitive piece of data in the entire node's configuration.
The fix — changing a single string from 'text' to 'password' — activates Node-RED's full credential protection stack. For any developer building Node-RED nodes that handle API keys, tokens, or passwords, this should serve as a clear reminder: always declare secrets as type: 'password' in your credential definitions.