diff --git a/src/NATS.Server/Auth/Account.cs b/src/NATS.Server/Auth/Account.cs
index e2a7cbc..afe5b03 100644
--- a/src/NATS.Server/Auth/Account.cs
+++ b/src/NATS.Server/Auth/Account.cs
@@ -777,9 +777,89 @@ public sealed class Account : IDisposable
/// Returns a snapshot of all reply subjects currently in the reverse response map.
public IReadOnlyList GetReverseResponseMapKeys() => [.. _reverseResponseMap.Keys];
+ ///
+ /// Checks whether any local subscription in this account's SubList would shadow
+ /// (intercept) messages on the given service import subject, preventing the import
+ /// from receiving them.
+ /// Go reference: accounts.go serviceImportShadowed (~line 2015).
+ ///
+ public bool ServiceImportShadowed(string importSubject)
+ {
+ var matchResult = SubList.Match(importSubject);
+ return matchResult.PlainSubs.Length > 0 || matchResult.QueueSubs.Length > 0;
+ }
+
+ ///
+ /// Returns all service import subjects registered on this account that are currently
+ /// shadowed by a local subscription in the SubList.
+ /// Go reference: accounts.go serviceImportShadowed (~line 2015).
+ ///
+ public IReadOnlyList GetShadowedServiceImports()
+ {
+ var shadowed = new List();
+ foreach (var subject in Imports.Services.Keys)
+ {
+ if (ServiceImportShadowed(subject))
+ shadowed.Add(subject);
+ }
+ return shadowed;
+ }
+
+ ///
+ /// Returns when at least one registered service import subject
+ /// is shadowed by a local subscription.
+ /// Go reference: accounts.go serviceImportShadowed (~line 2015).
+ ///
+ public bool HasShadowedImports
+ {
+ get
+ {
+ foreach (var subject in Imports.Services.Keys)
+ {
+ if (ServiceImportShadowed(subject))
+ return true;
+ }
+ return false;
+ }
+ }
+
+ ///
+ /// Returns a detailed for the given import subject,
+ /// including the list of local subscription subjects that shadow it.
+ /// Go reference: accounts.go serviceImportShadowed (~line 2015).
+ ///
+ public ShadowCheckResult CheckServiceImportShadowing(string importSubject)
+ {
+ var matchResult = SubList.Match(importSubject);
+ var shadowingSubs = new List();
+
+ foreach (var sub in matchResult.PlainSubs)
+ shadowingSubs.Add(sub.Subject);
+
+ foreach (var queueGroup in matchResult.QueueSubs)
+ foreach (var sub in queueGroup)
+ shadowingSubs.Add(sub.Subject);
+
+ bool isShadowed = shadowingSubs.Count > 0;
+ return new ShadowCheckResult(isShadowed, importSubject, shadowingSubs);
+ }
+
public void Dispose() => SubList.Dispose();
}
+///
+/// Result of describing whether a service import
+/// subject is intercepted by a local subscription and which subscriptions are responsible.
+/// Go reference: accounts.go serviceImportShadowed (~line 2015).
+///
+/// Whether any local subscription shadows the import subject.
+/// The service import subject that was checked.
+/// The subjects of local subscriptions that match the import subject.
+public sealed record ShadowCheckResult(
+ bool IsShadowed,
+ string ImportSubject,
+ IReadOnlyList ShadowingSubscriptions);
+
///
/// Carries the result of a call.
///
diff --git a/tests/NATS.Server.Tests/Auth/ImportShadowingTests.cs b/tests/NATS.Server.Tests/Auth/ImportShadowingTests.cs
new file mode 100644
index 0000000..b64d992
--- /dev/null
+++ b/tests/NATS.Server.Tests/Auth/ImportShadowingTests.cs
@@ -0,0 +1,170 @@
+// Tests for service import shadowing detection.
+// Go reference: accounts.go serviceImportShadowed (~line 2015).
+
+using NATS.Server.Auth;
+using NATS.Server.Imports;
+using NATS.Server.Subscriptions;
+
+namespace NATS.Server.Tests.Auth;
+
+public class ImportShadowingTests
+{
+ private static Account CreateAccount(string name) => new(name);
+
+ private static Subscription MakeSub(string subject) =>
+ new() { Subject = subject, Sid = subject };
+
+ ///
+ /// Adds a service import entry directly to the account's import map (bypassing
+ /// export/cycle checks) so that shadowing tests can exercise the import map iteration.
+ ///
+ private static void RegisterServiceImport(Account account, string fromSubject)
+ {
+ var dest = CreateAccount("Dest");
+ var si = new ServiceImport
+ {
+ DestinationAccount = dest,
+ From = fromSubject,
+ To = fromSubject,
+ };
+ account.Imports.AddServiceImport(si);
+ }
+
+ // Go reference: accounts.go serviceImportShadowed (~line 2015).
+ [Fact]
+ public void ServiceImportShadowed_NoLocalSubs_ReturnsFalse()
+ {
+ var account = CreateAccount("A");
+
+ var result = account.ServiceImportShadowed("orders.create");
+
+ result.ShouldBeFalse();
+ }
+
+ // Go reference: accounts.go serviceImportShadowed (~line 2015).
+ [Fact]
+ public void ServiceImportShadowed_ExactMatch_ReturnsTrue()
+ {
+ var account = CreateAccount("A");
+ account.SubList.Insert(MakeSub("orders.create"));
+
+ var result = account.ServiceImportShadowed("orders.create");
+
+ result.ShouldBeTrue();
+ }
+
+ // Go reference: accounts.go serviceImportShadowed (~line 2015).
+ [Fact]
+ public void ServiceImportShadowed_WildcardMatch_ReturnsTrue()
+ {
+ // Local subscription "orders.*" shadows import on "orders.create"
+ var account = CreateAccount("A");
+ account.SubList.Insert(MakeSub("orders.*"));
+
+ var result = account.ServiceImportShadowed("orders.create");
+
+ result.ShouldBeTrue();
+ }
+
+ // Go reference: accounts.go serviceImportShadowed (~line 2015).
+ [Fact]
+ public void ServiceImportShadowed_GtWildcard_ReturnsTrue()
+ {
+ // Local subscription "orders.>" shadows import on "orders.create.new"
+ var account = CreateAccount("A");
+ account.SubList.Insert(MakeSub("orders.>"));
+
+ var result = account.ServiceImportShadowed("orders.create.new");
+
+ result.ShouldBeTrue();
+ }
+
+ // Go reference: accounts.go serviceImportShadowed (~line 2015).
+ [Fact]
+ public void ServiceImportShadowed_NoMatch_ReturnsFalse()
+ {
+ // Local subscription "users.*" does NOT shadow import on "orders.create"
+ var account = CreateAccount("A");
+ account.SubList.Insert(MakeSub("users.*"));
+
+ var result = account.ServiceImportShadowed("orders.create");
+
+ result.ShouldBeFalse();
+ }
+
+ // Go reference: accounts.go serviceImportShadowed (~line 2015).
+ [Fact]
+ public void GetShadowedServiceImports_ReturnsOnlyShadowed()
+ {
+ var account = CreateAccount("A");
+
+ // Register two service imports
+ RegisterServiceImport(account, "orders.create");
+ RegisterServiceImport(account, "users.profile");
+
+ // Only add a local sub that shadows "orders.create"
+ account.SubList.Insert(MakeSub("orders.create"));
+
+ var shadowed = account.GetShadowedServiceImports();
+
+ shadowed.Count.ShouldBe(1);
+ shadowed.ShouldContain("orders.create");
+ shadowed.ShouldNotContain("users.profile");
+ }
+
+ // Go reference: accounts.go serviceImportShadowed (~line 2015).
+ [Fact]
+ public void HasShadowedImports_True_WhenShadowed()
+ {
+ var account = CreateAccount("A");
+
+ RegisterServiceImport(account, "orders.create");
+ account.SubList.Insert(MakeSub("orders.create"));
+
+ account.HasShadowedImports.ShouldBeTrue();
+ }
+
+ // Go reference: accounts.go serviceImportShadowed (~line 2015).
+ [Fact]
+ public void HasShadowedImports_False_WhenNone()
+ {
+ var account = CreateAccount("A");
+
+ RegisterServiceImport(account, "orders.create");
+ // No local subs — nothing shadows the import
+
+ account.HasShadowedImports.ShouldBeFalse();
+ }
+
+ // Go reference: accounts.go serviceImportShadowed (~line 2015).
+ [Fact]
+ public void CheckServiceImportShadowing_ReturnsShadowingSubscriptions()
+ {
+ var account = CreateAccount("A");
+ account.SubList.Insert(MakeSub("orders.*"));
+ account.SubList.Insert(MakeSub("orders.>"));
+
+ var result = account.CheckServiceImportShadowing("orders.create");
+
+ result.IsShadowed.ShouldBeTrue();
+ result.ImportSubject.ShouldBe("orders.create");
+ result.ShadowingSubscriptions.Count.ShouldBeGreaterThan(0);
+ // Both wildcard subs match "orders.create"
+ result.ShadowingSubscriptions.ShouldContain("orders.*");
+ result.ShadowingSubscriptions.ShouldContain("orders.>");
+ }
+
+ // Go reference: accounts.go serviceImportShadowed (~line 2015).
+ [Fact]
+ public void CheckServiceImportShadowing_NotShadowed()
+ {
+ var account = CreateAccount("A");
+ account.SubList.Insert(MakeSub("users.*"));
+
+ var result = account.CheckServiceImportShadowing("orders.create");
+
+ result.IsShadowed.ShouldBeFalse();
+ result.ImportSubject.ShouldBe("orders.create");
+ result.ShadowingSubscriptions.Count.ShouldBe(0);
+ }
+}