add http denylist checks
This commit is contained in:
parent
e8dac2048f
commit
e497630714
|
|
@ -3,8 +3,7 @@ import axios, { AxiosRequestConfig, Method, ResponseType } from 'axios'
|
||||||
import FormData from 'form-data'
|
import FormData from 'form-data'
|
||||||
import * as querystring from 'querystring'
|
import * as querystring from 'querystring'
|
||||||
import { getCredentialData, getCredentialParam } from '../../../src/utils'
|
import { getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||||
import * as ipaddr from 'ipaddr.js'
|
import { checkDenyList } from '../../../src/httpSecurity'
|
||||||
import dns from 'dns/promises'
|
|
||||||
|
|
||||||
class HTTP_Agentflow implements INode {
|
class HTTP_Agentflow implements INode {
|
||||||
label: string
|
label: string
|
||||||
|
|
@ -232,44 +231,6 @@ class HTTP_Agentflow implements INode {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
private isDeniedIP(ip: string, denyList: string[]): void {
|
|
||||||
const parsedIp = ipaddr.parse(ip)
|
|
||||||
for (const entry of denyList) {
|
|
||||||
if (entry.includes('/')) {
|
|
||||||
try {
|
|
||||||
const [range, _] = entry.split('/')
|
|
||||||
const parsedRange = ipaddr.parse(range)
|
|
||||||
if (parsedIp.kind() === parsedRange.kind()) {
|
|
||||||
if (parsedIp.match(ipaddr.parseCIDR(entry))) {
|
|
||||||
throw new Error('Access to this host is denied by policy.')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(`isDeniedIP: ${error}`)
|
|
||||||
}
|
|
||||||
} else if (ip === entry) throw new Error('Access to this host is denied by policy.')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async checkDenyList(url: string) {
|
|
||||||
const httpDenyListString: string | undefined = process.env.HTTP_DENY_LIST
|
|
||||||
if (!httpDenyListString) return url
|
|
||||||
const httpDenyList = httpDenyListString.split(',').map((ip) => ip.trim())
|
|
||||||
|
|
||||||
const urlObj = new URL(url)
|
|
||||||
|
|
||||||
const hostname = urlObj.hostname
|
|
||||||
|
|
||||||
if (ipaddr.isValid(hostname)) {
|
|
||||||
this.isDeniedIP(hostname, httpDenyList)
|
|
||||||
} else {
|
|
||||||
const addresses = await dns.lookup(hostname, { all: true })
|
|
||||||
for (const address of addresses) {
|
|
||||||
this.isDeniedIP(address.address, httpDenyList)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async run(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
async run(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||||
const method = nodeData.inputs?.method as 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
|
const method = nodeData.inputs?.method as 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
|
||||||
const url = nodeData.inputs?.url as string
|
const url = nodeData.inputs?.url as string
|
||||||
|
|
@ -332,7 +293,7 @@ class HTTP_Agentflow implements INode {
|
||||||
// Build final URL with query parameters
|
// Build final URL with query parameters
|
||||||
const finalUrl = queryString ? `${url}${url.includes('?') ? '&' : '?'}${queryString}` : url
|
const finalUrl = queryString ? `${url}${url.includes('?') ? '&' : '?'}${queryString}` : url
|
||||||
|
|
||||||
await this.checkDenyList(finalUrl)
|
await checkDenyList(finalUrl)
|
||||||
|
|
||||||
// Prepare request config
|
// Prepare request config
|
||||||
const requestConfig: AxiosRequestConfig = {
|
const requestConfig: AxiosRequestConfig = {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import fetch from 'node-fetch'
|
import fetch from 'node-fetch'
|
||||||
import { DynamicStructuredTool } from '../OpenAPIToolkit/core'
|
import { DynamicStructuredTool } from '../OpenAPIToolkit/core'
|
||||||
|
import { checkDenyList } from '../../../src/httpSecurity'
|
||||||
|
|
||||||
export const desc = `Use this when you need to execute a DELETE request to remove data from a website.`
|
export const desc = `Use this when you need to execute a DELETE request to remove data from a website.`
|
||||||
|
|
||||||
|
|
@ -165,6 +166,9 @@ export class RequestsDeleteTool extends DynamicStructuredTool {
|
||||||
finalUrl = url.toString()
|
finalUrl = url.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if URL is allowed by security policy
|
||||||
|
await checkDenyList(finalUrl)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(finalUrl, {
|
const res = await fetch(finalUrl, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import fetch from 'node-fetch'
|
import fetch from 'node-fetch'
|
||||||
import { DynamicStructuredTool } from '../OpenAPIToolkit/core'
|
import { DynamicStructuredTool } from '../OpenAPIToolkit/core'
|
||||||
|
import { checkDenyList } from '../../../src/httpSecurity'
|
||||||
|
|
||||||
export const desc = `Use this when you need to execute a GET request to get data from a website.`
|
export const desc = `Use this when you need to execute a GET request to get data from a website.`
|
||||||
|
|
||||||
|
|
@ -165,6 +166,9 @@ export class RequestsGetTool extends DynamicStructuredTool {
|
||||||
finalUrl = url.toString()
|
finalUrl = url.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if URL is allowed by security policy
|
||||||
|
await checkDenyList(finalUrl)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(finalUrl, {
|
const res = await fetch(finalUrl, {
|
||||||
headers: requestHeaders
|
headers: requestHeaders
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import fetch from 'node-fetch'
|
import fetch from 'node-fetch'
|
||||||
import { DynamicStructuredTool } from '../OpenAPIToolkit/core'
|
import { DynamicStructuredTool } from '../OpenAPIToolkit/core'
|
||||||
|
import { checkDenyList } from '../../../src/httpSecurity'
|
||||||
|
|
||||||
export const desc = `Use this when you want to execute a POST request to create or update a resource.`
|
export const desc = `Use this when you want to execute a POST request to create or update a resource.`
|
||||||
|
|
||||||
|
|
@ -126,6 +127,9 @@ export class RequestsPostTool extends DynamicStructuredTool {
|
||||||
...this.headers
|
...this.headers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if URL is allowed by security policy
|
||||||
|
await checkDenyList(inputUrl)
|
||||||
|
|
||||||
const res = await fetch(inputUrl, {
|
const res = await fetch(inputUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: requestHeaders,
|
headers: requestHeaders,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import fetch from 'node-fetch'
|
import fetch from 'node-fetch'
|
||||||
import { DynamicStructuredTool } from '../OpenAPIToolkit/core'
|
import { DynamicStructuredTool } from '../OpenAPIToolkit/core'
|
||||||
|
import { checkDenyList } from '../../../src/httpSecurity'
|
||||||
|
|
||||||
export const desc = `Use this when you want to execute a PUT request to update or replace a resource.`
|
export const desc = `Use this when you want to execute a PUT request to update or replace a resource.`
|
||||||
|
|
||||||
|
|
@ -126,6 +127,9 @@ export class RequestsPutTool extends DynamicStructuredTool {
|
||||||
...this.headers
|
...this.headers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if URL is allowed by security policy
|
||||||
|
await checkDenyList(inputUrl)
|
||||||
|
|
||||||
const res = await fetch(inputUrl, {
|
const res = await fetch(inputUrl, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: requestHeaders,
|
headers: requestHeaders,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
import * as ipaddr from 'ipaddr.js'
|
||||||
|
import dns from 'dns/promises'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if an IP address is in the deny list
|
||||||
|
* @param ip - IP address to check
|
||||||
|
* @param denyList - Array of denied IP addresses/CIDR ranges
|
||||||
|
* @throws Error if IP is in deny list
|
||||||
|
*/
|
||||||
|
export function isDeniedIP(ip: string, denyList: string[]): void {
|
||||||
|
const parsedIp = ipaddr.parse(ip)
|
||||||
|
for (const entry of denyList) {
|
||||||
|
if (entry.includes('/')) {
|
||||||
|
try {
|
||||||
|
const [range, _] = entry.split('/')
|
||||||
|
const parsedRange = ipaddr.parse(range)
|
||||||
|
if (parsedIp.kind() === parsedRange.kind()) {
|
||||||
|
if (parsedIp.match(ipaddr.parseCIDR(entry))) {
|
||||||
|
throw new Error('Access to this host is denied by policy.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`isDeniedIP: ${error}`)
|
||||||
|
}
|
||||||
|
} else if (ip === entry) {
|
||||||
|
throw new Error('Access to this host is denied by policy.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a URL is allowed based on HTTP_DENY_LIST environment variable
|
||||||
|
* @param url - URL to check
|
||||||
|
* @throws Error if URL hostname resolves to a denied IP
|
||||||
|
*/
|
||||||
|
export async function checkDenyList(url: string): Promise<void> {
|
||||||
|
const httpDenyListString: string | undefined = process.env.HTTP_DENY_LIST
|
||||||
|
if (!httpDenyListString) return
|
||||||
|
|
||||||
|
const httpDenyList = httpDenyListString.split(',').map((ip) => ip.trim())
|
||||||
|
const urlObj = new URL(url)
|
||||||
|
const hostname = urlObj.hostname
|
||||||
|
|
||||||
|
if (ipaddr.isValid(hostname)) {
|
||||||
|
isDeniedIP(hostname, httpDenyList)
|
||||||
|
} else {
|
||||||
|
const addresses = await dns.lookup(hostname, { all: true })
|
||||||
|
for (const address of addresses) {
|
||||||
|
isDeniedIP(address.address, httpDenyList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue