# semantic-release@25.0.9 security report (npm)

- Verdict: **Needs review** (risk level: medium)
- Scanned: 2026-10-06T14:24:34.000Z
- Files reviewed: 27
- Findings: 9 medium, 11 low severity findings
- Report: https://security.togoder.click/npm/semantic-release
- Source: Togoder Security (https://security.togoder.click), AI source-code review

## Summary

Togoder Security scanned the npm package semantic-release@25.0.9 on Oct 6, 2026. An AI review of 27 source files produced 9 medium, 11 low severity findings. The overall verdict is medium: the findings flag risky but common patterns (dynamic code, unsafe defaults, broad file or network access) rather than confirmed malware.

## Findings

### [medium] Runtime code execution via plugin loading

Finding ID: `NPS-BECFD8FF2C33`

File: `cli.js:25`

The application is designed to load and execute plugins based on the 'plugins', 'extends', and other plugin-related options parsed from the command line. These options are coerced from user input and passed to the main module. This constitutes a dynamic module loading mechanism where user-controlled input determines what code gets executed. If an attacker can control the CLI arguments or configuration, they may achieve arbitrary code execution.

### [medium] Dynamic import based on configuration

Finding ID: `NPS-76667BCCEF20`

File: `cli.js:65`

The CLI dynamically imports './index.js' at runtime after parsing user-controlled options from process.argv. While the module path is static, the options parsed from CLI arguments are passed directly into the imported module. In the context of semantic-release, these options include plugin names and shareable configurations which are resolved and executed as code. An attacker who can influence the CLI arguments (e.g., via CI/CD configuration or a malicious .releaserc file) could cause arbitrary plugin code to be loaded and executed.

### [medium] Dynamic module loading from external input

Finding ID: `NPS-C1CB37E99ED9`

File: `lib/get-config.js:35`

The `extends` config option allows loading arbitrary modules via `importFrom.silent(__dirname, extendPath)` and `importFrom(cwd, extendPath)`, where `extendPath` comes directly from the configuration. This enables loading and executing code from any resolvable module path, which could be exploited if an attacker can control the config (e.g., a malicious shared config). This is a legitimate feature of semantic-release, but it represents a code execution vector.

### [medium] Environment variable and credential harvesting

Finding ID: `NPS-20F3FD813224`

File: `lib/get-git-auth-url.js:66`

The code reads multiple sensitive environment variables (GH_TOKEN, GITHUB_TOKEN, GL_TOKEN, GITLAB_TOKEN, BB_TOKEN, BITBUCKET_TOKEN, GIT_CREDENTIALS) to extract git authentication credentials. While this is the intended functionality for semantic-release to authenticate with git providers, it demonstrates credential harvesting behavior that could be abused if the package were compromised or if the collected credentials were sent elsewhere.

### [medium] Command injection risk

Finding ID: `NPS-96EDF25CA61D`

File: `lib/git.js:56`

getCommits uses gitLogParser.parse with a format string constructed from user-supplied `from` and `to` values: `{ _: `${from ? from + '..' : ''}${to}` }`. These values are passed to git-log-parser, which may construct a git log command. If the parser does not safely shell-escape these values, an attacker controlling `from` or `to` (e.g., via branch/tag names in a CI context) could inject git options or shell metacharacters.

### [medium] Dynamic module loading from external input

Finding ID: `NPS-451FE1491FF2`

File: `lib/plugins/normalize.js:14`

The code calls loadPlugin(context, name, pluginsPath) where 'name' and 'pluginsPath' are derived from plugin configuration. This function likely dynamically imports or requires modules based on user-supplied or configuration-supplied names, which could be abused to load arbitrary code if the plugin name is not properly validated. While this is typical for a plugin system (semantic-release), it represents a dynamic import risk.

### [medium] Code execution via plugin function invocation

Finding ID: `NPS-6937C56AD7CE`

File: `lib/plugins/normalize.js:35`

The plugin function (or method) retrieved from the loaded module is invoked with configuration and context data. This is expected behavior for a plugin framework but means arbitrary code from third-party plugins will execute. If a malicious plugin is installed, it would run with access to environment, file system, and network.

### [medium] Plugin loading mechanism

Finding ID: `NPS-5C076302324C`

File: `lib/plugins/utils.js:49`

The code loads external plugins from the filesystem based on names provided in configuration. While this is expected behavior for a plugin system, it represents a code execution vector if the configuration is not properly validated or if an attacker can influence plugin paths.

### [medium] Dynamic import with computed path

Finding ID: `NPS-2009962D3AAB`

File: `lib/plugins/utils.js:62`

The loadPlugin function uses dynamic import() with a computed file path derived from user-controlled plugin names and configuration. This could potentially load and execute arbitrary modules if an attacker can control the plugin name or path, though resolve-from is used to resolve paths relative to known directories.

### [low] dynamic module loading

Finding ID: `NPS-83A904B1DE67`

File: `bin/semantic-release.js:10`

Uses createRequire to load package.json for engine constraints. This is standard practice for ESM modules needing to read package metadata.

### [low] process execution

Finding ID: `NPS-A8F98F710643`

File: `bin/semantic-release.js:27`

This is the CLI entry point for semantic-release. It checks Node.js and Git versions before invoking the main CLI. The `execa('git', ['--version'])` call is a legitimate version check, not a security concern.

### [low] Debug information exposure

Finding ID: `NPS-12F760EBEC7F`

File: `cli.js:60`

When the 'debug' flag is set, debug logging is enabled for the 'semantic-release:*' namespace. Debug logs may contain sensitive information such as tokens or credentials used during the release process. If debug output is captured in logs (common in CI environments), this could lead to credential leakage.

### [low] Error output potentially containing sensitive data

Finding ID: `NPS-348A390496F6`

File: `cli.js:68`

On error, the code writes the inspected error object to stderr. It uses a 'hideSensitive' utility to redact sensitive environment variables, which is a good practice. However, if the redaction is incomplete or if sensitive data exists outside of environment variables (e.g., passed as CLI arguments), it could still be exposed in error output.

### [low] Environment variable access

Finding ID: `NPS-1EEEB922A411`

File: `lib/get-config.js:76`

The `repoUrl({ cwd, env })` call passes the environment object into the git URL resolution logic. While this file itself does not read secrets directly, environment access is delegated to `./git.js`. This is normal for CI tools but warrants review of the imported module.

### [low] Potential credential/secret exposure in debug output

Finding ID: `NPS-BE16A7779AF3`

File: `lib/get-config.js:88`

`debug("options values: %O", options)` logs the full resolved options object, which may include tokens, repository credentials, or environment-derived secrets passed via CLI options or config. Debug output could leak sensitive values if logs are captured.

### [low] Sensitive credential handling in URLs

Finding ID: `NPS-DCC7C6F4446E`

File: `lib/get-git-auth-url.js:28`

Function formatAuthUrl embeds raw credentials into URLs (auth: gitCredentials) and these URLs are then passed to git.verifyAuth. If these URLs were logged, leaked, or exfiltrated, credentials could be exposed. Debug logging is present but appears to log URLs in some cases.

### [low] Spawning processes or shell commands

Finding ID: `NPS-A6F60E09211D`

File: `lib/get-git-auth-url.js:40`

The code calls verifyAuth (imported from ./git.js) which invokes git operations. Although the actual command execution is in the imported module, this file orchestrates spawning git processes to verify authentication URLs, passing user-controlled credentials as part of the URL.

### [low] Spawning external processes

Finding ID: `NPS-7A392B01E66F`

File: `lib/git.js`

The module extensively spawns the `git` binary via execa with arguments derived from parameters such as repositoryUrl, branch, tagName, ref, and note. Several of these are used with `--` separators or passed as distinct argv entries (mitigating shell injection), but ref/branch/tag names flow into git subcommands (rev-list, tag, check-ref-format, show-ref, ls-remote). If callers do not validate these inputs, malicious values could influence git behavior (e.g., option injection via leading dashes).

### [low] JSON parsing of git note content

Finding ID: `NPS-5A53B2D6DB13`

File: `lib/git.js`

getTagsNotes executes `git log` with `--notes=refs/notes/semantic-release*` and JSON.parse()s the resulting note payloads. Git notes are repository-controlled data; a malicious commit/note could contain a crafted JSON payload. JSON.parse itself is safe from code execution, but the parsed object is later used (e.g., channels) and could influence release behavior.

### [low] Environment variable access

Finding ID: `NPS-46DAE188FA0A`

File: `lib/git.js:57`

getCommits reads process.env and merges it with execaOptions.env when invoking git-log-parser. This propagates all environment variables (including credentials like GITHUB_TOKEN, NPM_TOKEN) into the child git process. While this is common for git operations, it expands the exposure surface if any downstream logging or error path leaks the env.

## Files reviewed

- `cli.js` (medium): The CLI is part of semantic-release and dynamically loads plugins and configurations based on user-controlled input, which is an inherent risk of arbitrary code execution if input is not trusted, though no direct malicious intent or obfuscation is present.
- `lib/get-config.js` (medium): This appears to be legitimate semantic-release configuration loading code, but it dynamically imports user-specified 'extends' modules (code execution vector) and logs full options which may contain credentials.
- `lib/get-git-auth-url.js` (medium): This is the legitimate semantic-release get-git-auth-url module; it harvests git tokens from environment variables to build authenticated repository URLs, which is expected behavior but involves sensitive credential handling and process spawning.
- `lib/git.js` (medium): This is legitimate semantic-release git utility code that shells out to git; the main concerns are potential command/option injection through unvalidated refs and propagation of process.env to child git processes, but no data exfiltration, credential harvesting, obfuscation, or backdoor patterns were found.
- `lib/plugins/normalize.js` (medium): No direct malicious patterns (exfiltration, credential theft, obfuscation, backdoors) were found, but the code implements a dynamic plugin loader that executes third-party code, which is a potential security risk if plugin sources are not trusted.
- `lib/plugins/utils.js` (medium): The code implements a plugin loading system with dynamic imports and filesystem resolution; while no obvious malicious patterns are present, the dynamic module loading from computed paths warrants caution.
- `bin/semantic-release.js` (safe): The code is a standard CLI entry point for semantic-release that performs version checks and delegates to the main CLI; no malicious patterns were found.
- `index.js` (safe): This is the legitimate entry point for the semantic-release package, with no malicious patterns such as data exfiltration, credential harvesting, obfuscation, or unauthorized process execution detected.
- `lib/branches/expand.js` (safe): No malicious patterns detected
- `lib/branches/get-tags.js` (safe): No malicious patterns detected
- `lib/branches/index.js` (safe): No malicious patterns detected; the code is a standard branch validation module using expected dependencies and no suspicious operations.
- `lib/branches/normalize.js` (safe): Cleared by Jev triage; no further analysis needed
- `lib/definitions/branches.js` (safe): Cleared by Jev triage; no further analysis needed
- `lib/definitions/constants.js` (safe): Cleared by Jev triage; no further analysis needed
- `lib/definitions/errors.js` (safe): Cleared by Jev triage; no further analysis needed
- `lib/definitions/plugins.js` (safe): No malicious patterns detected; the code is a legitimate plugin configuration module for semantic-release and exhibits no data exfiltration, credential harvesting, obfuscation, process spawning, or other suspicious behavior.
- `lib/get-commits.js` (safe): No malicious patterns detected; the file simply retrieves git commits using the debug logger and a local getCommits helper.
- `lib/get-error.js` (safe): Cleared by Jev triage; no further analysis needed
- `lib/get-last-release.js` (safe): Cleared by Jev triage; no further analysis needed
- `lib/get-logger.js` (safe): Cleared by Jev triage; no further analysis needed
- `lib/get-next-version.js` (safe): Cleared by Jev triage; no further analysis needed
- `lib/get-release-to-add.js` (safe): Cleared by Jev triage; no further analysis needed
- `lib/hide-sensitive.js` (safe): No malicious patterns detected; the code only redacts sensitive environment variable values from log output.
- `lib/plugins/index.js` (safe): No malicious patterns detected in the plugin loading and configuration logic.
- `lib/plugins/pipeline.js` (safe): Cleared by Jev triage; no further analysis needed
- `lib/utils.js` (safe): No malicious patterns detected
- `lib/verify.js` (safe): No malicious patterns detected

AI analysis is guidance, not a guarantee. Methodology: https://security.togoder.click/methodology
