🎯 fix: Actions Allowed Domains Handling (#11215)

* 🔧 fix: Update domain handling in ActionsInput components for SSRF validation

- Refactored domain extraction logic in ActionsInput components to include protocol in the domain metadata for proper SSRF validation.
- Ensured that the domain is constructed as `${parsedUrl.protocol}//${parsedUrl.hostname}` to enhance security and prevent potential vulnerabilities.

This change improves the handling of user-provided domains and aligns with best practices for security in URL processing.

* 🔧 fix: Include missing `actions` field in AppService configuration
This commit is contained in:
Danny Avila 2026-01-05 14:58:26 -05:00 committed by GitHub
parent e343180740
commit 019c59f10e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 18 additions and 19 deletions

View file

@ -121,12 +121,12 @@ export default function ActionsInput({
const action_id = action?.action_id;
metadata.raw_spec = inputValue;
const parsedUrl = new URL(data[0].domain);
const domain = parsedUrl.hostname;
if (!domain) {
if (!parsedUrl.hostname) {
// alert user?
return;
}
metadata.domain = domain;
// Send protocol + hostname for proper SSRF validation (e.g., "http://192.168.1.1")
metadata.domain = `${parsedUrl.protocol}//${parsedUrl.hostname}`;
const { type, saved_auth_fields } = authFormData;
@ -208,23 +208,21 @@ export default function ActionsInput({
htmlFor="schemaInput"
className="text-token-text-primary whitespace-nowrap font-medium"
>
Schema
{localize('com_ui_schema')}
</label>
{/* TODO: Implement examples functionality
<div className="flex items-center gap-2">
{/* <button className="btn btn-neutral border-token-border-light relative h-8 min-w-[100px] rounded-lg font-medium">
<div className="flex w-full items-center justify-center text-xs">Import from URL</div>
</button> */}
<select
onChange={(e) => logger.log('actions', 'selecting example action', e.target.value)}
className="border-token-border-medium h-8 min-w-[100px] rounded-lg border bg-transparent px-2 py-0 text-sm"
>
<option value="label">{localize('com_ui_examples')}</option>
{/* TODO: make these appear and function correctly */}
<option value="0">Weather (JSON)</option>
<option value="1">Pet Store (YAML)</option>
<option value="2">Blank Template</option>
</select>
</div>
*/}
</div>
<div className="border-token-border-medium bg-token-surface-primary hover:border-token-border-hover mb-4 w-full overflow-hidden rounded-lg border ring-0">
<div className="relative">

View file

@ -131,12 +131,12 @@ export default function ActionsInput({
const action_id = action?.action_id;
metadata.raw_spec = inputValue;
const parsedUrl = new URL(data[0].domain);
const domain = parsedUrl.hostname;
if (!domain) {
if (!parsedUrl.hostname) {
// alert user?
return;
}
metadata.domain = domain;
// Send protocol + hostname for proper SSRF validation (e.g., "http://192.168.1.1")
metadata.domain = `${parsedUrl.protocol}//${parsedUrl.hostname}`;
const { type, saved_auth_fields } = authFormData;
@ -221,22 +221,20 @@ export default function ActionsInput({
>
{localize('com_ui_schema')}
</label>
{/* TODO: Implement examples functionality
<div className="flex items-center gap-2">
{/* <button className="btn btn-neutral border-token-border-light relative h-8 min-w-[100px] rounded-lg font-medium">
<div className="flex w-full items-center justify-center text-xs">Import from URL</div>
</button> */}
<select
id="example-schema"
onChange={(e) => console.log(e.target.value)}
className="border-token-border-medium h-8 min-w-[100px] rounded-lg border bg-transparent px-2 py-0 text-sm"
>
<option value="label">{localize('com_ui_examples')}</option>
{/* TODO: make these appear and function correctly */}
<option value="0">Weather (JSON)</option>
<option value="1">Pet Store (YAML)</option>
<option value="2">Blank Template</option>
</select>
</div>
*/}
</div>
<div className="border-token-border-medium bg-token-surface-primary hover:border-token-border-hover mb-4 w-full overflow-hidden rounded-lg border ring-0">
<div className="relative">

View file

@ -698,9 +698,9 @@ export function validateActionDomain(
if (clientHasProtocol) {
normalizedClientDomain = extractDomainFromUrl(clientProvidedDomain);
} else {
// IP addresses inherit protocol from spec, domains default to https
// No protocol specified by client
if (isIPAddress) {
// IPv6 addresses need brackets in URLs
// IPs inherit protocol from spec (for legitimate internal services)
const ipVersion = isIP(normalizedClientHostname);
const hostname =
ipVersion === 6 && !clientHostname.startsWith('[')
@ -708,6 +708,7 @@ export function validateActionDomain(
: clientHostname;
normalizedClientDomain = `${specUrl.protocol}//${hostname}`;
} else {
// Domain names default to HTTPS for security (forces explicit protocol)
normalizedClientDomain = `https://${clientHostname}`;
}
}

View file

@ -62,6 +62,7 @@ export const AppService = async (params?: {
const mcpServersConfig = config.mcpServers || null;
const mcpSettings = config.mcpSettings || null;
const actions = config.actions;
const registration = config.registration ?? configDefaults.registration;
const interfaceConfig = await loadDefaultInterface({ config, configDefaults });
const turnstileConfig = loadTurnstileConfig(config, configDefaults);
@ -74,6 +75,7 @@ export const AppService = async (params?: {
memory,
speech,
balance,
actions,
transactions,
mcpConfig: mcpServersConfig,
mcpSettings,
@ -103,9 +105,9 @@ export const AppService = async (params?: {
const loadedEndpoints = loadEndpoints(config, agentsDefaults);
const appConfig = {
const appConfig: AppConfig = {
...defaultConfig,
fileConfig: config?.fileConfig,
fileConfig: config?.fileConfig as AppConfig['fileConfig'],
secureImageLinks: config?.secureImageLinks,
modelSpecs: processModelSpecs(config?.endpoints, config.modelSpecs, interfaceConfig),
endpoints: loadedEndpoints,