diff options
| author | Hazelnoot <acomputerdog@gmail.com> | 2025-07-08 11:27:31 -0400 |
|---|---|---|
| committer | dakkar <dakkar@thenautilus.net> | 2025-07-27 19:39:20 +0100 |
| commit | 3c59a7ae01f7ea7094178cf961c3f4a668672f08 (patch) | |
| tree | f0310bd53666b20223339c3fd269defa655611a3 /packages/backend/src/core | |
| parent | refactor actor validation to reduce code duplication (diff) | |
| download | sharkey-3c59a7ae01f7ea7094178cf961c3f4a668672f08.tar.gz sharkey-3c59a7ae01f7ea7094178cf961c3f4a668672f08.tar.bz2 sharkey-3c59a7ae01f7ea7094178cf961c3f4a668672f08.zip | |
allow HTTP connections to private IPs
Diffstat (limited to 'packages/backend/src/core')
| -rw-r--r-- | packages/backend/src/core/HttpRequestService.ts | 17 | ||||
| -rw-r--r-- | packages/backend/src/core/UtilityService.ts | 8 |
2 files changed, 18 insertions, 7 deletions
diff --git a/packages/backend/src/core/HttpRequestService.ts b/packages/backend/src/core/HttpRequestService.ts index 046b0dc244..bbc127fa86 100644 --- a/packages/backend/src/core/HttpRequestService.ts +++ b/packages/backend/src/core/HttpRequestService.ts @@ -20,7 +20,6 @@ import type { IObject, IObjectWithId } from '@/core/activitypub/type.js'; import { UtilityService } from '@/core/UtilityService.js'; import { ApUtilityService } from '@/core/activitypub/ApUtilityService.js'; import type { Response } from 'node-fetch'; -import type { URL } from 'node:url'; import type { Socket } from 'node:net'; export type HttpRequestSendOptions = { @@ -28,6 +27,15 @@ export type HttpRequestSendOptions = { validators?: ((res: Response) => void)[]; }; +export function isPrivateUrl(url: URL): boolean { + if (!ipaddr.isValid(url.hostname)) { + return false; + } + + const ip = ipaddr.parse(url.hostname); + return ip.range() !== 'unicast'; +} + export function isPrivateIp(allowedPrivateNetworks: PrivateNetwork[] | undefined, ip: string, port?: number): boolean { const parsedIp = ipaddr.parse(ip); @@ -303,6 +311,7 @@ export class HttpRequestService { timeout?: number, size?: number, isLocalAddressAllowed?: boolean, + allowHttp?: boolean, } = {}, extra: HttpRequestSendOptions = { throwErrorWhenResponseNotOk: true, @@ -311,7 +320,9 @@ export class HttpRequestService { ): Promise<Response> { const timeout = args.timeout ?? 5000; - this.utilityService.assertUrl(url); + const parsedUrl = new URL(url); + const allowHttp = args.allowHttp || isPrivateUrl(parsedUrl); + this.utilityService.assertUrl(parsedUrl, allowHttp); const controller = new AbortController(); setTimeout(() => { @@ -320,7 +331,7 @@ export class HttpRequestService { const isLocalAddressAllowed = args.isLocalAddressAllowed ?? false; - const res = await fetch(url, { + const res = await fetch(parsedUrl, { method: args.method ?? 'GET', headers: { 'User-Agent': this.config.userAgent, diff --git a/packages/backend/src/core/UtilityService.ts b/packages/backend/src/core/UtilityService.ts index 27d3cb7776..a90774cf59 100644 --- a/packages/backend/src/core/UtilityService.ts +++ b/packages/backend/src/core/UtilityService.ts @@ -226,7 +226,7 @@ export class UtilityService { * @throws {IdentifiableError} If URL contains credentials */ @bindThis - public assertUrl(url: string | URL): URL | never { + public assertUrl(url: string | URL, allowHttp?: boolean): URL | never { // If string, parse and validate if (typeof(url) === 'string') { try { @@ -237,7 +237,7 @@ export class UtilityService { } // Must be HTTPS - if (!this.checkHttps(url)) { + if (!this.checkHttps(url, allowHttp)) { throw new IdentifiableError('0bedd29b-e3bf-4604-af51-d3352e2518af', `invalid url ${url}: unsupported protocol ${url.protocol}`); } @@ -255,12 +255,12 @@ export class UtilityService { * Based on check-https.ts. */ @bindThis - public checkHttps(url: string | URL): boolean { + public checkHttps(url: string | URL, allowHttp = false): boolean { const isNonProd = this.envService.env.NODE_ENV !== 'production'; try { const proto = new URL(url).protocol; - return proto === 'https:' || (proto === 'http:' && isNonProd); + return proto === 'https:' || (proto === 'http:' && (isNonProd || allowHttp)); } catch { // Invalid URLs don't "count" as HTTPS return false; |