Weak SERVICE password: warn from the app, since the headless service can't ask

Ed's point: the send-only service auto-updates itself to 5.6, and a pre-existing weak
service-profile password then leaves it silently not streaming — the service is
headless (SYSTEM, no window) so it can't prompt, and nobody watches its log. Users
would never know. Closed from the two places a user WILL see:

1. App launch (MaybeWarnWeakServicePassword, in the settled RunStartupNotices
   sequence): if a service is installed and its profile password is set-but-weak, a
   readable, focus-clean dialog warns and offers to open the service settings to fix
   it. Self-resolving — stops once the service password is strengthened. The person
   running the service almost always has the app (that's how it's configured), and
   the app updates around the same time the service does, so this fires right when it
   matters. Decision extracted to the pure ServicePasswordNeedsStrengthening (tested:
   installed+weak warns; not-installed / unset / strong don't).

2. Saving the service profile (ServiceProfileDialog): Save now validates first — a
   weak password pops a warning offering to set a stronger one inline (requireStrong),
   Save-anyway, or Cancel. The save button no longer auto-closes; it validates then
   closes explicitly.

readme: a sentence in the service section explaining the service password follows the
same rule and how RemSound surfaces a weak one. Gate 71/71 + 7 relay tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Ednunp
2026-07-27 12:03:49 +01:00
co-authored by Claude Fable 5
parent 9577fc9940
commit e82268f925
4 changed files with 84 additions and 4 deletions
+1 -1
View File
@@ -1417,7 +1417,7 @@ RemSound.exe --connect 192.168.1.42
<h3>Setting it up</h3>
<p>Everything lives in the <strong>Service</strong> menu on the menu bar:</p>
<ol>
<li><strong>Configure service profile&hellip;</strong> &mdash; opens a small window with two tabs (Connectivity and Audio send) where you choose who to send to (plus a password) and what to send. On the Audio send tab, the first output choice is <strong>Use Windows default audio device, follows Windows changes</strong> &mdash; tick that to send whatever this machine is currently playing and keep following the Windows default if it later changes, rather than pinning one named card. (You can still pick specific devices, or a specific application, exactly as in the normal app.) There is no &ldquo;send my audio&rdquo; switch because the service always sends, and there is no audio-quality tab to fiddle with: the service always uses the settings that work best for live streaming (the Opus live-latency codec, small packets, locked to the audio clock), so it just sounds right. This is a separate profile from your normal ones and does not appear in the usual profile list. The <strong>Additional options</strong> button holds two extras. First, a switch for the service's own log. Second, <strong>Set the machine's volume when the service starts</strong>: tick it, pick a volume percent, and the service unmutes the machine and sets its Windows volume to that level when it starts &mdash; handy for an unattended PC that booted muted or was left turned down, so it's audible again with nobody at the keyboard. The <strong>When</strong> list chooses between <em>Only the first start after each boot</em> and <em>Every time the service starts</em>. <strong>First start after boot is the recommended, set-and-forget choice</strong>: it sets the volume once when the machine boots and never touches it again, so it won't fight you while you're using the machine. <em>Every time the service starts</em> re-applies on every service start &mdash; useful if you deliberately restart the service to reset the volume, but be aware the service also restarts by itself for routine reasons (installing a RemSound update, saving the service profile), and this mode re-applies on those too. To stop it ever machine-gunning your volume, either mode skips a re-apply if the volume was already set within the last few minutes. Changes take effect from the service's next start, no reinstall needed. (The service itself never plays sounds &mdash; it streams silently in the background &mdash; so there are no cue options here.)</li>
<li><strong>Configure service profile&hellip;</strong> &mdash; opens a small window with two tabs (Connectivity and Audio send) where you choose who to send to (plus a password) and what to send. On the Audio send tab, the first output choice is <strong>Use Windows default audio device, follows Windows changes</strong> &mdash; tick that to send whatever this machine is currently playing and keep following the Windows default if it later changes, rather than pinning one named card. (You can still pick specific devices, or a specific application, exactly as in the normal app.) There is no &ldquo;send my audio&rdquo; switch because the service always sends, and there is no audio-quality tab to fiddle with: the service always uses the settings that work best for live streaming (the Opus live-latency codec, small packets, locked to the audio clock), so it just sounds right. The service password follows the same strength rule as the app (from version 5.6): because the service runs in the background with no window, it can't ask you to strengthen a weak one &mdash; instead it simply won't stream, and RemSound warns you on its next launch (and when you save the service profile) so you know to change it here. This is a separate profile from your normal ones and does not appear in the usual profile list. The <strong>Additional options</strong> button holds two extras. First, a switch for the service's own log. Second, <strong>Set the machine's volume when the service starts</strong>: tick it, pick a volume percent, and the service unmutes the machine and sets its Windows volume to that level when it starts &mdash; handy for an unattended PC that booted muted or was left turned down, so it's audible again with nobody at the keyboard. The <strong>When</strong> list chooses between <em>Only the first start after each boot</em> and <em>Every time the service starts</em>. <strong>First start after boot is the recommended, set-and-forget choice</strong>: it sets the volume once when the machine boots and never touches it again, so it won't fight you while you're using the machine. <em>Every time the service starts</em> re-applies on every service start &mdash; useful if you deliberately restart the service to reset the volume, but be aware the service also restarts by itself for routine reasons (installing a RemSound update, saving the service profile), and this mode re-applies on those too. To stop it ever machine-gunning your volume, either mode skips a re-apply if the volume was already set within the last few minutes. Changes take effect from the service's next start, no reinstall needed. (The service itself never plays sounds &mdash; it streams silently in the background &mdash; so there are no cue options here.)</li>
<li><strong>Install service</strong> &mdash; registers it with Windows so it starts automatically at every boot. Windows asks for administrator permission (one prompt). Do this once. Straight after installing, RemSound asks whether you'd like to <strong>start it now</strong> (otherwise it waits until the next reboot). (When you first install RemSound on a PC, the app installer also offers to set the service up &mdash; and start it &mdash; for you, so you may have done this already.)</li>
<li><strong>Start service</strong> / <strong>Stop service</strong> &mdash; run or halt it now without waiting for a reboot.</li>
<li><strong>Uninstall service</strong> &mdash; removes it entirely.</li>
+39
View File
@@ -1668,8 +1668,47 @@ public sealed partial class MainForm : Form
MaybeWarnMicBlockedOnStartup();
if (IsDisposed) return;
MaybeWarnWeakPassword();
if (IsDisposed) return;
MaybeWarnWeakServicePassword();
}
/// <summary>The send-only service is headless — it CANNOT ask for a password. So when it
/// auto-updates to 5.6 with a pre-existing weak service-profile password, it silently stops
/// streaming and only a log line explains why (nobody watches a service log). This closes that
/// gap from the one place a user WILL see: the interactive app's launch. If a service is installed
/// and its profile password is too weak, warn here (readable, focus-clean, via the same settled
/// notice sequence as the other startup warnings) and offer to open the service settings to fix it
/// (Ed, 2026-07-27 — "people won't know to change the service password"). Self-resolving: once the
/// service password is strengthened, this never fires again.</summary>
private void MaybeWarnWeakServicePassword()
{
if (IsDisposed || CuePlayer.GloballyMuted) return;
try
{
var installed = ServiceControl.Query() is not (ServiceState.NotInstalled or ServiceState.Unknown);
var sp = ServiceStore.LoadProfile();
var pw = string.IsNullOrEmpty(sp?.Password) ? "" : RemSoundCrypto.Deobfuscate(sp!.Password);
if (!ServicePasswordNeedsStrengthening(installed, pw)) return;
}
catch { return; } // service state unreadable (e.g. Win7 without the feature) — nothing to warn about
var open = ForegroundDialog.Show(owner => MessageBox.Show(owner,
"Your RemSound background service is using a password that's too weak to meet the new "
+ "security rules, so the service won't stream until the password is changed. (The "
+ "service can't ask you itself, because it runs in the background with no window.)\n\n"
+ "Use at least 8 characters — three unrelated words with a number, like kettle9tiger42moon, works well.\n\n"
+ "Would you like to open the service settings now to change it?",
"RemSound — service password needs strengthening",
MessageBoxButtons.YesNo, MessageBoxIcon.Warning));
if (open == DialogResult.Yes && !IsDisposed) ConfigureServiceProfile();
}
/// <summary>Pure, testable: should the app nag about the SERVICE profile's password? Only when a
/// service is installed AND its password is set-but-weak — the regression case (a service that
/// used to stream goes silent after the 5.6 auto-update). An unset password is a never-configured
/// service, not a regression, so it's left alone here.</summary>
internal static bool ServicePasswordNeedsStrengthening(bool serviceInstalled, string? servicePlainPassword) =>
serviceInstalled && !string.IsNullOrEmpty(servicePlainPassword) && PasswordStrength.Critique(servicePlainPassword) is not null;
/// <summary>Startup warning for a profile whose password is too weak to stream under the 5.6 rule.
/// Runs from the SETTLED post-launch notice sequence — after the window is fully shown and
/// activated, one dialog at a time — NOT from the mid-connect crypto path where an earlier version
+8 -1
View File
@@ -2569,7 +2569,14 @@ internal static class SelfTest
Check(!MainForm.WeakPasswordBlocksAudio("kettle9tiger42moon", haveKey: false), "a STRONG password still deriving (key null, Critique null) must NOT show the weak warning");
Check(!MainForm.WeakPasswordBlocksAudio("Games", haveKey: true), "once a key exists the warning clears");
return "weak+common refused with advice; derivation refuses weak everywhere; cached; weak-block surfaced non-modally";
// The headless service can't ask for a password, so the app nags on launch when an INSTALLED
// service has a set-but-weak password (the silent-death-after-auto-update case, Ed 2026-07-27).
Check(MainForm.ServicePasswordNeedsStrengthening(serviceInstalled: true, "Games"), "installed service + weak password → the app must warn");
Check(!MainForm.ServicePasswordNeedsStrengthening(serviceInstalled: false, "Games"), "no service installed → nothing to warn about");
Check(!MainForm.ServicePasswordNeedsStrengthening(serviceInstalled: true, ""), "an unset service password is 'not configured', not a weak-password regression");
Check(!MainForm.ServicePasswordNeedsStrengthening(serviceInstalled: true, "kettle9tiger42moon"), "a strong service password needs no nag");
return "weak+common refused with advice; derivation refuses weak everywhere; cached; app+service weak surfaced";
}
/// <summary>The relay address-proof (2026-07-27): an AddrCheck cookie arriving at the receiver
+36 -2
View File
@@ -42,7 +42,9 @@ internal sealed class ServiceProfileDialog : Form
private const SendRate ServiceSendRate = ServiceAudioDefaults.Rate;
// --- Button row ---
private readonly Button saveButton = new() { Text = "&Save and Close", AutoSize = true, DialogResult = DialogResult.OK };
// No auto-close DialogResult: the Click handler validates the password strength first (a weak
// service password means the headless service silently won't stream), then closes explicitly.
private readonly Button saveButton = new() { Text = "&Save and Close", AutoSize = true };
private readonly Button cancelButton = new() { Text = "Cancel", AutoSize = true, DialogResult = DialogResult.Cancel };
private readonly Button additionalButton = new() { Text = "Additional &options...", AutoSize = true, AccessibleName = "Additional options" };
@@ -80,7 +82,13 @@ internal sealed class ServiceProfileDialog : Form
AcceptButton = saveButton;
CancelButton = cancelButton;
saveButton.Click += (_, _) => SaveToProfile();
saveButton.Click += (_, _) =>
{
if (!ConfirmOrFixWeakPassword()) return; // user chose to keep editing / cancel the fix
SaveToProfile();
DialogResult = DialogResult.OK;
Close();
};
additionalButton.Click += (_, _) => ShowAdditionalOptions();
}
@@ -329,6 +337,32 @@ internal sealed class ServiceProfileDialog : Form
UpdatePasswordStatus();
}
/// <summary>On Save: if the service password is too weak the headless service will silently not
/// stream, so warn and offer to strengthen it right now. Returns true to proceed with the save
/// (either the password is fine, was just strengthened, or the user chose to save anyway), false
/// to stay in the dialog. Empty is left alone — that's an un-configured profile, not a regression.</summary>
private bool ConfirmOrFixWeakPassword()
{
var current = string.IsNullOrEmpty(working.Password) ? "" : RemSoundCrypto.Deobfuscate(working.Password);
if (string.IsNullOrEmpty(current) || PasswordStrength.Critique(current) is null) return true;
var choice = ForegroundDialog.Show(owner => MessageBox.Show(owner,
"This service password is too weak to meet the new security rules, so the service won't "
+ "stream until it's strengthened.\n\n"
+ "Use at least 8 characters — three unrelated words with a number, like kettle9tiger42moon, works well.\n\n"
+ "Set a stronger password now?",
"RemSound — service password needs strengthening",
MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning));
if (choice == DialogResult.Cancel) return false; // back to the dialog, nothing saved
if (choice == DialogResult.No) return true; // save the weak one anyway (warned)
// Yes → enter a stronger one now (requireStrong disables the unchanged-exemption).
var stronger = ProfilePasswordDialog.Show(ServiceControl.ServiceProfileTitle, current, requireNonEmpty: true, requireStrong: true);
if (string.IsNullOrEmpty(stronger)) return false; // cancelled the change — stay put
working.Password = RemSoundCrypto.Obfuscate(stronger);
UpdatePasswordStatus();
return true;
}
private void UpdatePasswordStatus()
=> passwordStatus.Text = string.IsNullOrEmpty(working.Password) ? "No password set." : "Password set.";