Allow user to configure an address to listen to

83f74a5d225ce8aabb469e4ff8f11e64c9791e75

Kristan Schlikow <kristian@schlikow.de>

Signed
2 files changed, +40 -6Ignore whitespace
default/config.yaml+2 -0
@@ -6,6 +6,8 @@ cardsCacheCapacity: 100
6# -- SERVER CONFIGURATION --6# -- SERVER CONFIGURATION --
7# Listen for incoming connections7# Listen for incoming connections
8listen: false8listen: false
9# Listen on a specific address, supports IPv4 and IPv6
10listenAddress: 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 support12# - Use option "auto" to automatically detect support
11# - Use true or false (no qoutes) to enable or disable each protocol13# - Use true or false (no qoutes) to enable or disable each protocol
server.js+38 -6
@@ -130,6 +130,7 @@ if (process.versions && process.versions.node && process.versions.node.match(/20
130const DEFAULT_PORT = 8000;130const DEFAULT_PORT = 8000;
131const DEFAULT_AUTORUN = false;131const DEFAULT_AUTORUN = false;
132const DEFAULT_LISTEN = false;132const DEFAULT_LISTEN = false;
133const DEFAULT_LISTEN_ADDRESS = '';
133const DEFAULT_CORS_PROXY = false;134const DEFAULT_CORS_PROXY = false;
134const DEFAULT_WHITELIST = true;135const DEFAULT_WHITELIST = true;
135const DEFAULT_ACCOUNTS = false;136const 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
254const autorun = (cliArguments.autorun ?? getConfigValue('autorun', DEFAULT_AUTORUN)) && !cliArguments.ssl;259const autorun = (cliArguments.autorun ?? getConfigValue('autorun', DEFAULT_AUTORUN)) && !cliArguments.ssl;
255/** @type {boolean} */260/** @type {boolean} */
256const listen = cliArguments.listen ?? getConfigValue('listen', DEFAULT_LISTEN);261const listen = cliArguments.listen ?? getConfigValue('listen', DEFAULT_LISTEN);
262/** @type {string} */
263const listenAddress = cliArguments.listenAddress ?? getConfigValue('listenAddress', DEFAULT_LISTEN_ADDRESS);
257/** @type {boolean} */264/** @type {boolean} */
258const enableCorsProxy = cliArguments.corsProxy ?? getConfigValue('enableCorsProxy', DEFAULT_CORS_PROXY);265const enableCorsProxy = cliArguments.corsProxy ?? getConfigValue('enableCorsProxy', DEFAULT_CORS_PROXY);
259const enableWhitelist = cliArguments.whitelist ?? getConfigValue('whitelistMode', DEFAULT_WHITELIST);266const enableWhitelist = cliArguments.whitelist ?? getConfigValue('whitelistMode', DEFAULT_WHITELIST);
@@ -706,15 +713,17 @@ app.use('/api/backends/scale-alt', scaleAltRouter);
706app.use('/api/speech', speechRouter);713app.use('/api/speech', speechRouter);
707app.use('/api/azure', azureRouter);714app.use('/api/azure', azureRouter);
708715
716const 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;
709const tavernUrlV6 = new URL(717const 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);
714722
723const 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;
715const tavernUrl = new URL(724const 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);
720729
@@ -780,6 +789,10 @@ const preSetupTasks = async function () {
780 */789 */
781async function getAutorunHostname(useIPv6, useIPv4) {790async 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);
784797
785 if (useIPv6 && useIPv4) {798 if (useIPv6 && useIPv4) {
@@ -842,9 +855,15 @@ const postSetupTasks = async function (v6Failed, v4Failed, useIPv6, useIPv4) {
842 console.log('\n' + getSeparator(plainGoToLog.length) + '\n');855 console.log('\n' + getSeparator(plainGoToLog.length) + '\n');
843856
844 if (listen) {857 if (listen) {
845 console.log(858 if (ipv4_regex.test(listenAddress) || ipv6_regex.test(listenAddress)) {
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',859 console.log(
847 );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 {
863 console.log(
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',
865 );
866 }
848 }867 }
849868
850 if (basicAuthMode) {869 if (basicAuthMode) {
@@ -909,6 +928,19 @@ function logSecurityAlert(message) {
909}928}
910929
911/**930/**
931 * Prints a warning message
932 * @param {string} message The warning message to print
933 * @returns {void}
934 */
935function 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 IPv6945 * @param {boolean} v6Failed If the server failed to start on IPv6
914 * @param {boolean} v4Failed If the server failed to start on IPv4946 * @param {boolean} v4Failed If the server failed to start on IPv4
@@ -1083,7 +1115,7 @@ async function verifySecuritySettings() {
1083 }1115 }
10841116
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 }
10881120
1089 const users = await getAllEnabledUsers();1121 const users = await getAllEnabledUsers();