Back to Blog
critical SEVERITY8 min read

How hardcoded default credentials happen in Node.js database initialization and how to fix it

A critical vulnerability was discovered in `hubcmdui/database/database.js` where the database initialization routine hardcoded the default admin credentials (`root` / `admin@123`) and logged them in plaintext. Because these credentials are visible in the public source code, any attacker who finds the repository can immediately authenticate as an administrator on any unpatched deployment. The fix removes the plaintext credential from the log message, and operators are now prompted to change the d

O
By Orbis AppSec
Published July 23, 2026Reviewed July 23, 2026

Answer Summary

This is a hardcoded default credentials vulnerability (CWE-798) in a Node.js application, specifically in `hubcmdui/database/database.js` at line 228. The initialization routine inserted a default admin user with username `root` and password `admin@123` and then logged those credentials in plaintext via `logger.info`. The fix removes the credential values from the log output and replaces them with a prompt to change the default password, eliminating the information-disclosure path while preserving the initialization logic.

Vulnerability at a Glance

cweCWE-798
fixRemove the plaintext credential from the logger.info call and replace it with a generic prompt to change the default password
riskAny attacker with access to the source code can authenticate as the root administrator on every unpatched deployment
languageJavaScript (Node.js)
root causeThe database initialization function logs the plaintext default password after creating the admin user
vulnerabilityHardcoded Default Credentials with Plaintext Logging

How Hardcoded Default Credentials Happen in Node.js Database Initialization and How to Fix It

Introduction

The hubcmdui/database/database.js file is responsible for bootstrapping the application's SQLite database, including creating the initial administrator account the first time the application runs. That sounds routine — and it is — but a single logger.info call on line 228 turned a standard initialization task into a critical security exposure. The log message printed the default username and password in plaintext:

logger.info('默认管理员用户创建成功: root/admin@123');

Combined with the fact that the username root and password admin@123 are hardcoded in the source code itself, this meant that every operator who deployed the application without immediately changing the default password was running a system that any attacker — armed with nothing more than a GitHub search — could log into as a full administrator.

This post walks through exactly how the vulnerability works, what the fix does, and how to avoid the same pattern in your own Node.js projects.


The Vulnerability Explained

What the Code Does

During database initialization, the application checks whether an admin user already exists. If none is found, it runs an INSERT statement to create one:

// hubcmdui/database/database.js — BEFORE the fix (line ~232)
db.run(
  'INSERT INTO users (username, password, created_at, login_count, last_login) VALUES (?, ?, ?, ?, ?)',
  ['root', hashedPassword, new Date().toISOString(), 0, null]
);
logger.info('默认管理员用户创建成功: root/admin@123');

Two problems exist here, and they compound each other:

  1. The plaintext password is embedded in source code. The string admin@123 lives in the repository. Anyone who reads the code — including anyone who finds the project on GitHub, a code mirror, or a leaked backup — now knows the default password.

  2. The plaintext password is written to the application log. Even if the source code were private, the log line root/admin@123 would appear in every log file, log aggregator, and SIEM that ingests the application's output. Log files are frequently stored with weaker access controls than the application itself, forwarded to third-party services, and included in support bundles.

The Exploitation Path

The exploitation scenario is two steps and requires no special tooling:

Step 1 — Discover the credentials. An attacker searches GitHub (or any code-hosting platform) for the string admin@123 combined with the project name. The plaintext log message confirms both the username and the password in a single line. Alternatively, the attacker reads a log file obtained through an unrelated vulnerability (e.g., a path traversal or misconfigured log endpoint).

Step 2 — Authenticate. The attacker sends a POST request to /api/login:

POST /api/login HTTP/1.1
Content-Type: application/json

{
  "username": "root",
  "password": "admin@123"
}

Because the application created the root account with hashedPassword derived from admin@123, this request succeeds on every deployment where the operator has not manually changed the password. The attacker now has full administrative access.

Why This Is Classified as Critical

The vulnerability is rated critical for three reasons:

  • No attacker skill required. The credentials are public. Exploitation is a copy-paste operation.
  • Every default deployment is affected. The vulnerable code runs on first startup. Any operator who did not change the password immediately after deployment is exposed.
  • Full administrative compromise. The root user has unrestricted access to all application functionality, making lateral movement within the application trivial.

This maps to CWE-798: Use of Hard-coded Credentials and aligns with OWASP A07:2021 — Identification and Authentication Failures.


The Fix

The pull request makes a targeted, surgical change to the log message on line 228:

Before

logger.info('默认管理员用户创建成功: root/admin@123');

After

logger.info('默认管理员用户创建成功,请及时修改默认密码');

Translation: The new message reads "Default admin user created successfully. Please change the default password promptly."

Why This Change Is Sufficient (and What It Does Not Change)

The fix removes the information-disclosure path. The INSERT statement itself is unchanged — the application still creates the root account with hashedPassword on first run. The difference is that the log no longer broadcasts the plaintext password to every system that consumes the application's log output.

The fix also shifts the message from confirmation to action: instead of telling operators "here are your credentials," it tells them "you need to change your credentials." This is a small UX nudge, but it matters operationally — operators who see the new message are more likely to act on it.

What a Deeper Fix Would Look Like

The PR addresses the immediate information-disclosure risk. A more comprehensive remediation would include:

  1. Force a password change on first login. Set a must_change_password flag in the users table and redirect the root user to a password-change screen before granting access to any other functionality.

  2. Generate a random initial password. Instead of hardcoding admin@123, generate a cryptographically random password at first run, print it once to stdout (not to a persistent log), and require the operator to record it:

const crypto = require('crypto');
const initialPassword = crypto.randomBytes(16).toString('hex');
const hashedPassword = await bcrypt.hash(initialPassword, 12);

db.run(
  'INSERT INTO users (username, password, created_at, login_count, last_login) VALUES (?, ?, ?, ?, ?)',
  ['root', hashedPassword, new Date().toISOString(), 0, null]
);

// Print ONCE to stdout, never to a persistent log
process.stdout.write(`\n[SETUP] Initial admin password: ${initialPassword}\nChange this immediately.\n\n`);
  1. Accept credentials via environment variables. Allow operators to set ADMIN_USERNAME and ADMIN_PASSWORD before first run, so the initial account is created with operator-chosen credentials rather than a default.

Prevention & Best Practices

Never Log Credential Values

Treat any string that contains a password, token, or secret as toxic to your logging pipeline. Use structured logging and explicitly exclude sensitive fields:

// BAD — logs the secret
logger.info(`User created: ${username}/${password}`);

// GOOD — logs only non-sensitive metadata
logger.info('Admin user initialized', { username, action: 'first_run_setup' });

Use Environment Variables or Secret Management

Credentials that must exist at startup should come from the environment, not from source code:

const adminPassword = process.env.ADMIN_PASSWORD;
if (!adminPassword) {
  throw new Error('ADMIN_PASSWORD environment variable is required');
}

For production deployments, use a secrets manager (AWS Secrets Manager, HashiCorp Vault, Azure Key Vault) rather than environment variables in .env files, which are frequently committed to version control by accident.

Scan for Secrets in CI/CD

Add secret-scanning tools to your pipeline so that hardcoded credentials are caught before they reach the main branch:

  • Gitleaks — scans git history for secrets
  • Semgrep — rule-based static analysis including hardcoded credential patterns
  • truffleHog — entropy-based and pattern-based secret detection

Enforce Password Change on First Login

Any application that ships with a default credential — even a randomly generated one — should enforce a password change before the account becomes fully functional. This is a defense-in-depth measure that limits the window of exposure.

Security Standards

  • OWASP A07:2021 — Identification and Authentication Failures: https://owasp.org/Top10/A07_2021-Identification_and_Authentication_Failures/
  • OWASP Authentication Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html
  • CWE-798: https://cwe.mitre.org/data/definitions/798.html
  • CWE-259 (Use of Hard-coded Password): https://cwe.mitre.org/data/definitions/259.html

Key Takeaways

  • The logger.info call in database.js was the direct disclosure vector. The password was hashed in the database, but the log message printed the plaintext value on every fresh deployment — a distinction that is easy to overlook during code review.
  • Hardcoded credentials in initialization code are especially dangerous because they affect every deployment by default, not just misconfigured ones.
  • The string admin@123 in source code is permanently public once it has been pushed to any repository, even a private one, because it may have been cloned, forked, or indexed before deletion.
  • Removing the credential from the log is necessary but not sufficient — a complete fix also randomizes the initial password and forces a change on first login.
  • Static analysis tools can catch this pattern automatically. The Orbis AppSec scanner flagged the logger.info call containing credential-like strings adjacent to an INSERT INTO users statement, demonstrating that this class of vulnerability is reliably detectable before it reaches production.

How Orbis AppSec Detected This

  • Source: The hardcoded string literal 'admin@123' embedded in the database initialization routine in hubcmdui/database/database.js
  • Sink: logger.info('默认管理员用户创建成功: root/admin@123') at database.js:228, which writes the plaintext credential to the application log
  • Missing control: No secret-scanning gate in CI/CD; no requirement to supply credentials via environment variables; no enforcement of password change on first login
  • CWE: CWE-798 — Use of Hard-coded Credentials
  • Fix: The logger.info call was updated to remove the plaintext credential and replaced with a prompt instructing operators to change the default password

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

Hardcoded default credentials are one of the oldest and most reliably exploited vulnerability classes in application security, yet they continue to appear in production codebases because they feel harmless during development — the password is "just for testing," or "operators will change it." They rarely do, and the consequences are immediate full administrative compromise for anyone who reads the source code.

The fix in database.js eliminates the plaintext credential from the log output, which closes the most direct information-disclosure path. Developers working on similar initialization routines should go further: generate random initial passwords, deliver them securely to operators exactly once, and enforce a password change before the account can be used. A single logger.info call should never be the reason an attacker gains admin access.


References

Frequently Asked Questions

What is a hardcoded default credentials vulnerability?

It occurs when an application ships with a fixed, well-known username and password baked into its source code or binary. Because the credentials are the same on every installation and are often publicly visible, attackers can use them to gain unauthorized access without any brute-forcing.

How do you prevent hardcoded default credentials in Node.js?

Never embed passwords in source code. Generate a random password at first run, require the operator to set credentials via environment variables or a secure setup wizard, and never log credential values at any log level.

What CWE is hardcoded default credentials?

CWE-798 — Use of Hard-coded Credentials. A related entry is CWE-259 (Use of Hard-coded Password), which specifically covers passwords embedded in source code.

Is hashing the password enough to prevent this vulnerability?

No. Hashing the password before storing it is good practice, but it does not eliminate the risk when the plaintext password is still logged or when the same plaintext value is used on every deployment. Attackers only need the plaintext value, which is visible in the source code and the log file.

Can static analysis detect hardcoded default credentials?

Yes. Tools such as Semgrep, Gitleaks, and Orbis AppSec can flag patterns where string literals that look like passwords appear in logger calls, INSERT statements, or variable assignments adjacent to usernames. The Orbis AppSec multi-agent AI scanner identified this exact pattern in database.js.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #90

Related Articles

critical

How supply chain code injection happens in Node.js build scripts and how to fix it

A critical supply chain vulnerability in the chatgpt-auto-continue extension's build utility allowed arbitrary code execution by fetching JavaScript from a CDN without integrity verification. The fix implements SHA-256 hash validation before executing the downloaded code, preventing potential supply chain attacks through compromised CDN content or man-in-the-middle attacks.

high

How command injection happens in Node.js child_process and how to fix it

A high-severity command injection vulnerability was discovered in `bootstrap.js` at line 34, where the `exec()` function was used to run shell commands constructed from a `folder` variable. By replacing `exec()` with `execFile()` and passing arguments as an array, the fix eliminates the shell interpolation entirely, closing the door on command injection attacks that could have affected all downstream consumers of this Node.js library.

high

How SSRF and Credential Leakage via Absolute URLs happens in axios and how to fix it

A high-severity vulnerability (CVE-2025-27152) in axios versions prior to 1.8.2 allowed Server-Side Request Forgery (SSRF) attacks and credential leakage when making HTTP requests with absolute URLs. This vulnerability was fixed by upgrading from axios 1.7.4 to 1.8.2 in both package.json and package-lock.json, eliminating the attack vector that could have exposed authentication tokens and enabled unauthorized server-side requests.

high

How Client-Side Denial of Service Happens in Node.js FTP Clients and How to Fix It

CVE-2026-44240 is a client-side denial of service vulnerability in the basic-ftp Node.js library that allows attackers to crash FTP clients by sending malformed, unterminated multiline FTP responses. Upgrading from version 5.0.5 to 5.3.1 patches this critical flaw that could have disrupted any application using the vulnerable library for FTP operations.

critical

How WebSocket protocol length header abuse happens in Node.js and how to fix it

A critical vulnerability (CVE-2026-54466) in websocket-driver versions prior to 0.7.5 allows attackers to corrupt WebSocket messages by manipulating protocol length headers. This can lead to data integrity issues, denial of service, or potentially arbitrary code execution in affected Node.js applications. The fix involves upgrading the websocket-driver dependency to version 0.7.5.

critical

How arbitrary code execution via injected protobuf definition type fields happens in Node.js and how to fix it

A critical arbitrary code execution vulnerability (CVE-2026-41242) was discovered in protobufjs versions prior to 7.5.5 and 8.0.1, allowing attackers to inject malicious code through crafted protobuf definition type fields. The fix upgrades the dependency from the vulnerable version 6.11.4 to 7.5.5, which properly sanitizes type field inputs during protobuf parsing. This vulnerability is especially dangerous because protobufjs is widely used in Node.js applications for serialization, meaning a s