diff --git a/src/ZB.MOM.WW.ScadaBridge.Security/Auth/AuthDisableLoginOptions.cs b/src/ZB.MOM.WW.ScadaBridge.Security/Auth/AuthDisableLoginOptions.cs
index ba7d9fa8..dd5a2f63 100644
--- a/src/ZB.MOM.WW.ScadaBridge.Security/Auth/AuthDisableLoginOptions.cs
+++ b/src/ZB.MOM.WW.ScadaBridge.Security/Auth/AuthDisableLoginOptions.cs
@@ -24,4 +24,7 @@ public sealed class AuthDisableLoginOptions
/// The username the auto-login principal is minted with. Default "multi-role".
public string User { get; set; } = "multi-role";
+
+ /// Explicit second acknowledgement key: permits DisableLogin outside Development. Default false.
+ public bool AllowOutsideDevelopment { get; set; }
}
diff --git a/src/ZB.MOM.WW.ScadaBridge.Security/Auth/DisableLoginGuard.cs b/src/ZB.MOM.WW.ScadaBridge.Security/Auth/DisableLoginGuard.cs
new file mode 100644
index 00000000..0c5efc85
--- /dev/null
+++ b/src/ZB.MOM.WW.ScadaBridge.Security/Auth/DisableLoginGuard.cs
@@ -0,0 +1,25 @@
+namespace ZB.MOM.WW.ScadaBridge.Security.Auth;
+
+///
+/// Startup guard for the dev/test flag.
+/// DisableLogin authenticates EVERY request with ALL roles (including Operator+Verifier,
+/// nullifying the two-person secured-write control) on a SCADA control surface, so it is
+/// refused outside the Development environment unless the operator sets the explicit
+/// second acknowledgement key .
+/// Called by the Host composition root before AddSecurity(); fail-fast at boot.
+///
+public static class DisableLoginGuard
+{
+ public static void Validate(bool disableLogin, string environmentName, bool allowOutsideDevelopment)
+ {
+ if (!disableLogin) return;
+ if (string.Equals(environmentName, "Development", StringComparison.OrdinalIgnoreCase)) return;
+ if (allowOutsideDevelopment) return;
+ throw new InvalidOperationException(
+ "ScadaBridge:Security:Auth:DisableLogin=true disables ALL authentication on a SCADA control " +
+ $"surface and is refused outside the Development environment (current: '{environmentName}'). " +
+ "Remove the flag, run with ASPNETCORE_ENVIRONMENT=Development, or — only if you fully accept an " +
+ "unauthenticated deployment — additionally set " +
+ "ScadaBridge:Security:Auth:AllowOutsideDevelopment=true.");
+ }
+}
diff --git a/tests/ZB.MOM.WW.ScadaBridge.Security.Tests/DisableLoginGuardTests.cs b/tests/ZB.MOM.WW.ScadaBridge.Security.Tests/DisableLoginGuardTests.cs
new file mode 100644
index 00000000..5a8601f0
--- /dev/null
+++ b/tests/ZB.MOM.WW.ScadaBridge.Security.Tests/DisableLoginGuardTests.cs
@@ -0,0 +1,30 @@
+using ZB.MOM.WW.ScadaBridge.Security.Auth;
+using Xunit;
+
+namespace ZB.MOM.WW.ScadaBridge.Security.Tests;
+
+public class DisableLoginGuardTests
+{
+ [Theory]
+ [InlineData("Production")]
+ [InlineData("Staging")]
+ [InlineData("")]
+ public void Validate_DisableLoginOutsideDevelopment_WithoutAck_Throws(string env)
+ {
+ var ex = Assert.Throws(
+ () => DisableLoginGuard.Validate(disableLogin: true, environmentName: env, allowOutsideDevelopment: false));
+ Assert.Contains("DisableLogin", ex.Message);
+ Assert.Contains("Development", ex.Message);
+ }
+
+ [Theory]
+ [InlineData("Development", false)] // dev env: allowed without ack
+ [InlineData("development", false)] // case-insensitive env name
+ [InlineData("Production", true)] // explicit second ack key
+ public void Validate_Allowed_DoesNotThrow(string env, bool ack)
+ => DisableLoginGuard.Validate(true, env, ack);
+
+ [Fact]
+ public void Validate_FlagOff_NeverThrows()
+ => DisableLoginGuard.Validate(false, "Production", false);
+}