Allow user to configure an address to listen to
Signed| @@ -6,6 +6,8 @@ cardsCacheCapacity: 100 | |||
| 6 | # -- SERVER CONFIGURATION -- | 6 | # -- SERVER CONFIGURATION -- |
| 7 | # Listen for incoming connections | 7 | # Listen for incoming connections |
| 8 | listen: false | 8 | listen: false |
| 9 | # Listen on a specific address, supports IPv4 and IPv6 | ||
| 10 | listenAddress: 127.0.0.1 | ||
| 9 | # Enables IPv6 and/or IPv4 protocols. Need to have at least one enabled! | 11 | # Enables IPv6 and/or IPv4 protocols. Need to have at least one enabled! |
| 10 | # - Use option "auto" to automatically detect support | 12 | # - Use option "auto" to automatically detect support |
| 11 | # - Use true or false (no qoutes) to enable or disable each protocol | 13 | # - Use true or false (no qoutes) to enable or disable each protocol |
| @@ -130,6 +130,7 @@ if (process.versions && process.versions.node && process.versions.node.match(/20 | |||
| 130 | const DEFAULT_PORT = 8000; | 130 | const DEFAULT_PORT = 8000; |
| 131 | const DEFAULT_AUTORUN = false; | 131 | const DEFAULT_AUTORUN = false; |
| 132 | const DEFAULT_LISTEN = false; | 132 | const DEFAULT_LISTEN = false; |
| 133 | const DEFAULT_LISTEN_ADDRESS = ''; | ||
| 133 | const DEFAULT_CORS_PROXY = false; | 134 | const DEFAULT_CORS_PROXY = false; |
| 134 | const DEFAULT_WHITELIST = true; | 135 | const DEFAULT_WHITELIST = true; |
| 135 | const DEFAULT_ACCOUNTS = false; | 136 | const DEFAULT_ACCOUNTS = false; |
| @@ -185,6 +186,10 @@ const cliArguments = yargs(hideBin(process.argv)) | |||
| 185 | type: 'boolean', | 186 | type: 'boolean', |
| 186 | default: null, | 187 | default: null, |
| 187 | describe: `SillyTavern is listening on all network interfaces (Wi-Fi, LAN, localhost). If false, will limit it only to internal localhost (127.0.0.1).\nIf not provided falls back to yaml config 'listen'.\n[config default: ${DEFAULT_LISTEN}]`, | 188 | describe: `SillyTavern is listening on all network interfaces (Wi-Fi, LAN, localhost). If false, will limit it only to internal localhost (127.0.0.1).\nIf not provided falls back to yaml config 'listen'.\n[config default: ${DEFAULT_LISTEN}]`, |
| 189 | }).option('listenAddress', { | ||
| 190 | type: 'string', | ||
| 191 | default: null, | ||
| 192 | describe: 'Set SillyTavern to listen to a specific address. If not set, it will fallback to listen to all.\n[config default: empty ]', | ||
| 188 | }).option('corsProxy', { | 193 | }).option('corsProxy', { |
| 189 | type: 'boolean', | 194 | type: 'boolean', |
| 190 | default: null, | 195 | default: null, |
| @@ -254,6 +259,8 @@ const server_port = cliArguments.port ?? process.env.SILLY_TAVERN_PORT ?? getCon | |||
| 254 | const autorun = (cliArguments.autorun ?? getConfigValue('autorun', DEFAULT_AUTORUN)) && !cliArguments.ssl; | 259 | const autorun = (cliArguments.autorun ?? getConfigValue('autorun', DEFAULT_AUTORUN)) && !cliArguments.ssl; |
| 255 | /** @type {boolean} */ | 260 | /** @type {boolean} */ |
| 256 | const listen = cliArguments.listen ?? getConfigValue('listen', DEFAULT_LISTEN); | 261 | const listen = cliArguments.listen ?? getConfigValue('listen', DEFAULT_LISTEN); |
| 262 | /** @type {string} */ | ||
| 263 | const listenAddress = cliArguments.listenAddress ?? getConfigValue('listenAddress', DEFAULT_LISTEN_ADDRESS); | ||
| 257 | /** @type {boolean} */ | 264 | /** @type {boolean} */ |
| 258 | const enableCorsProxy = cliArguments.corsProxy ?? getConfigValue('enableCorsProxy', DEFAULT_CORS_PROXY); | 265 | const enableCorsProxy = cliArguments.corsProxy ?? getConfigValue('enableCorsProxy', DEFAULT_CORS_PROXY); |
| 259 | const enableWhitelist = cliArguments.whitelist ?? getConfigValue('whitelistMode', DEFAULT_WHITELIST); | 266 | const enableWhitelist = cliArguments.whitelist ?? getConfigValue('whitelistMode', DEFAULT_WHITELIST); |
| @@ -706,15 +713,17 @@ app.use('/api/backends/scale-alt', scaleAltRouter); | |||
| 706 | app.use('/api/speech', speechRouter); | 713 | app.use('/api/speech', speechRouter); |
| 707 | app.use('/api/azure', azureRouter); | 714 | app.use('/api/azure', azureRouter); |
| 708 | 715 | ||
| 716 | const ipv6_regex = /^(?:(?:[a-fA-F\d]{1,4}:){7}(?:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,2}|:)|(?:[a-fA-F\d]{1,4}:){4}(?:(?::[a-fA-F\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,3}|:)|(?:[a-fA-F\d]{1,4}:){3}(?:(?::[a-fA-F\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,4}|:)|(?:[a-fA-F\d]{1,4}:){2}(?:(?::[a-fA-F\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,5}|:)|(?:[a-fA-F\d]{1,4}:){1}(?:(?::[a-fA-F\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,6}|:)|(?::(?:(?::[a-fA-F\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,7}|:)))(?:%[0-9a-zA-Z]{1,})?$/m; | ||
| 709 | const tavernUrlV6 = new URL( | 717 | const tavernUrlV6 = new URL( |
| 710 | (cliArguments.ssl ? 'https://' : 'http://') + | 718 | (cliArguments.ssl ? 'https://' : 'http://') + |
| 711 | (listen ? '[::]' : '[::1]') + | 719 | (listen ? (ipv6_regex.test(listenAddress) ? listenAddress : '[::]') : '[::1]') + |
| 712 | (':' + server_port), | 720 | (':' + server_port), |
| 713 | ); | 721 | ); |
| 714 | 722 | ||
| 723 | const ipv4_regex = /^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$/m; | ||
| 715 | const tavernUrl = new URL( | 724 | const tavernUrl = new URL( |
| 716 | (cliArguments.ssl ? 'https://' : 'http://') + | 725 | (cliArguments.ssl ? 'https://' : 'http://') + |
| 717 | (listen ? '0.0.0.0' : '127.0.0.1') + | 726 | (listen ? (ipv4_regex.test(listenAddress) ? listenAddress : '0.0.0.0') : '127.0.0.1') + |
| 718 | (':' + server_port), | 727 | (':' + server_port), |
| 719 | ); | 728 | ); |
| 720 | 729 | ||
| @@ -780,6 +789,10 @@ const preSetupTasks = async function () { | |||
| 780 | */ | 789 | */ |
| 781 | async function getAutorunHostname(useIPv6, useIPv4) { | 790 | async function getAutorunHostname(useIPv6, useIPv4) { |
| 782 | if (autorunHostname === 'auto') { | 791 | if (autorunHostname === 'auto') { |
| 792 | if (listen && (ipv4_regex.test(listenAddress) || ipv6_regex.test(listenAddress))) { | ||
| 793 | return listenAddress; | ||
| 794 | } | ||
| 795 | |||
| 783 | let localhostResolve = await canResolve('localhost', useIPv6, useIPv4); | 796 | let localhostResolve = await canResolve('localhost', useIPv6, useIPv4); |
| 784 | 797 | ||
| 785 | if (useIPv6 && useIPv4) { | 798 | if (useIPv6 && useIPv4) { |
| @@ -842,10 +855,16 @@ const postSetupTasks = async function (v6Failed, v4Failed, useIPv6, useIPv4) { | |||
| 842 | console.log('\n' + getSeparator(plainGoToLog.length) + '\n'); | 855 | console.log('\n' + getSeparator(plainGoToLog.length) + '\n'); |
| 843 | 856 | ||
| 844 | if (listen) { | 857 | if (listen) { |
| 858 | if (ipv4_regex.test(listenAddress) || ipv6_regex.test(listenAddress)) { | ||
| 859 | console.log( | ||
| 860 | `SillyTavern is listening on the address ${listenAddress}. If you want to limit it only to internal localhost ([::1] or 127.0.0.1), change the setting in config.yaml to "listen: false". Check "access.log" file in the SillyTavern directory if you want to inspect incoming connections.\n`, | ||
| 861 | ); | ||
| 862 | } else { | ||
| 845 | console.log( | 863 | console.log( |
| 846 | '[::] or 0.0.0.0 means SillyTavern is listening on all network interfaces (Wi-Fi, LAN, localhost). If you want to limit it only to internal localhost ([::1] or 127.0.0.1), change the setting in config.yaml to "listen: false". Check "access.log" file in the SillyTavern directory if you want to inspect incoming connections.\n', | 864 | '[::] or 0.0.0.0 means SillyTavern is listening on all network interfaces (Wi-Fi, LAN, localhost). If you want to limit it only to internal localhost ([::1] or 127.0.0.1), change the setting in config.yaml to "listen: false". Check "access.log" file in the SillyTavern directory if you want to inspect incoming connections.\n', |
| 847 | ); | 865 | ); |
| 848 | } | 866 | } |
| 867 | } | ||
| 849 | 868 | ||
| 850 | if (basicAuthMode) { | 869 | if (basicAuthMode) { |
| 851 | if (perUserBasicAuth && !enableAccounts) { | 870 | if (perUserBasicAuth && !enableAccounts) { |
| @@ -909,6 +928,19 @@ function logSecurityAlert(message) { | |||
| 909 | } | 928 | } |
| 910 | 929 | ||
| 911 | /** | 930 | /** |
| 931 | * Prints a warning message | ||
| 932 | * @param {string} message The warning message to print | ||
| 933 | * @returns {void} | ||
| 934 | */ | ||
| 935 | function logSecurityWarning(message) { | ||
| 936 | if (basicAuthMode || enableWhitelist) return; // safe! | ||
| 937 | console.error(color.yellow(message)); | ||
| 938 | if (getConfigValue('securityOverride', false)) { | ||
| 939 | console.warn(color.red('Security has been overridden. If it\'s not a trusted network, change the settings.')); | ||
| 940 | } | ||
| 941 | } | ||
| 942 | |||
| 943 | /** | ||
| 912 | * Handles the case where the server failed to start on one or both protocols. | 944 | * Handles the case where the server failed to start on one or both protocols. |
| 913 | * @param {boolean} v6Failed If the server failed to start on IPv6 | 945 | * @param {boolean} v6Failed If the server failed to start on IPv6 |
| 914 | * @param {boolean} v4Failed If the server failed to start on IPv4 | 946 | * @param {boolean} v4Failed If the server failed to start on IPv4 |
| @@ -1083,7 +1115,7 @@ async function verifySecuritySettings() { | |||
| 1083 | } | 1115 | } |
| 1084 | 1116 | ||
| 1085 | if (!enableAccounts) { | 1117 | if (!enableAccounts) { |
| 1086 | logSecurityAlert('Your SillyTavern is currently insecurely open to the public. Enable whitelisting, basic authentication or user accounts.'); | 1118 | logSecurityAlert('Your current SillyTavern configuration is insecure (listening to non-localhost). Enable whitelisting, basic authentication or user accounts.'); |
| 1087 | } | 1119 | } |
| 1088 | 1120 | ||
| 1089 | const users = await getAllEnabledUsers(); | 1121 | const users = await getAllEnabledUsers(); |