Conversation
|
Thanks for opening this, but we'd appreciate a little more information. Could you update it with more details? |
WalkthroughWarning Review details and warnings were omitted to fit the comment limit. |
Code Review Completed! 🔥The code review was successfully completed based on your current configurations.
Kody Guide: Usage and ConfigurationInteracting with Kody
Current Kody ConfigurationReview OptionsThe following review options are enabled or disabled:
|
There was a problem hiding this comment.
CodeQL found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.
| /// Broker side: accept the legacy <see cref="BrokerApiKey"/> during the migration window (plan section 8.5 | ||
| /// rule 6). Turn off, and remove the key, once the broker logs show no legacy use. | ||
| /// </summary> | ||
| public static bool BrokerLegacySharedKeyEnabled = true; |
There was a problem hiding this comment.
BrokerLegacySharedKeyEnabled enables a full-authority legacy shared credential by default, violating deny-by-default and least-privilege principles during migration. Disable it by default and require an explicit, scoped authorization path.
Kody rule violation: Implement RBAC with least privilege and deny-by-default
public static bool BrokerLegacySharedKeyEnabled = false;Prompt for LLM
File Core/Resgrid.Config/DataProtectionConfig.cs:
Line 67:
`BrokerLegacySharedKeyEnabled` enables a full-authority legacy shared credential by default, violating deny-by-default and least-privilege principles during migration. Disable it by default and require an explicit, scoped authorization path.
Suggested Code:
public static bool BrokerLegacySharedKeyEnabled = false;
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| <value>Sessions Unit, IC et Dispatch que ce département exécute toujours comme sessions partagées, quel que soit le réglage de l'installation ; les connexions qui n'indiquent pas leur application comptent aussi. Nécessite des versions d'application compatibles avec le mode partagé. Membre gestionnaire uniquement.</value> | ||
| </data> | ||
| <data xml:space="preserve" name="TableHelp.DepartmentSecurityPolicy.SharedShiftHours"> | ||
| <value>Nombre d'heures après la connexion au bout desquelles une session partagée se termine, quelle que soit l'activité, de 1 à 24. Une valeur plus courte termine plus tôt les sessions en cours ; une valeur plus longue ne les prolonge jamais. Membre gestionnaire uniquement.</value> |
There was a problem hiding this comment.
The documented shared-session absolute timeout permits up to 24 hours, exceeding the required maximum of 12 hours. Change the documentation and corresponding limits in Core/Resgrid.Config/PasskeyConfig.cs:89 and Core/Resgrid.Model/DepartmentSecurityPolicy.cs:131 to a maximum of 12 hours.
Kody rule violation: Harden session management with idle and absolute timeouts
<value>Nombre d'heures après la connexion au bout desquelles une session partagée se termine, quelle que soit l'activité, de 1 à 12. Une valeur plus courte termine plus tôt les sessions en cours ; une valeur plus longue ne les prolonge jamais. Membre gestionnaire uniquement.</value>Prompt for LLM
File Core/Resgrid.Localization/Areas/User/AdminAssist/AdminAssist.fr.resx:
Line 5521:
The documented shared-session absolute timeout permits up to 24 hours, exceeding the required maximum of 12 hours. Change the documentation and corresponding limits in `Core/Resgrid.Config/PasskeyConfig.cs:89` and `Core/Resgrid.Model/DepartmentSecurityPolicy.cs:131` to a maximum of 12 hours.
Suggested Code:
<value>Nombre d'heures après la connexion au bout desquelles une session partagée se termine, quelle que soit l'activité, de 1 à 12. Une valeur plus courte termine plus tôt les sessions en cours ; une valeur plus longue ne les prolonge jamais. Membre gestionnaire uniquement.</value>
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| @@ -29,5 +29,8 @@ void RegisterForEvents(Func<int, string, Task> personnelStatusChanged, | |||
| /// </summary> | |||
| void RegisterForChatEvents(Func<int, string, Task> chatEvent); | |||
| void RegisterForChecklistEvents(Func<int, string, Task> checklistEvent); | |||
|
|
|||
| /// <summary>Events for one session (session id, serialized <c>SessionEventMessage</c>).</summary> | |||
| void RegisterForSessionEvents(Func<string, string, Task> sessionEvent); | |||
There was a problem hiding this comment.
RegisterForSessionEvents lacks an explicit error handler and deterministic unsubscribe mechanism, so session-event failures and cleanup cannot be managed reliably. Accept an error callback and return an IDisposable cleanup handle.
Kody rule violation: Provide error handlers to subscription/listener APIs
IDisposable RegisterForSessionEvents(Func<string, string, Task> sessionEvent, Func<Exception, Task> errorHandler);Prompt for LLM
File Core/Resgrid.Model/Providers/IRabbitInboundEventProvider.cs:
Line 34:
RegisterForSessionEvents lacks an explicit error handler and deterministic unsubscribe mechanism, so session-event failures and cleanup cannot be managed reliably. Accept an error callback and return an IDisposable cleanup handle.
Suggested Code:
IDisposable RegisterForSessionEvents(Func<string, string, Task> sessionEvent, Func<Exception, Task> errorHandler);
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| { | ||
| public async Task<bool> EnrollPinAsync(int departmentId, string userId, string grantToken, string pin, CancellationToken cancellationToken = default) | ||
| { | ||
| if (pin == null || !Regex.IsMatch(pin, "^[0-9]{6,12}$")) return false; | ||
| var policy = await protection.GetPolicyByDepartmentIdAsync(departmentId, bypassCache: true); | ||
| if (policy == null || grants.ValidateGrant(grantToken, departmentId, policy.PolicyEpoch, ProtectedDataGrantScopes.Read, | ||
| out var grant) != ProtectedDataGrantValidationOutcome.Valid || grant.UserId != userId || grant.StepUpExempt || | ||
| out var grant) != ProtectedDataGrantValidationOutcome.Valid || grant.UserId != userId || | ||
| await ProtectedGrantBinding.CheckAsync(grant, userId, ProtectedGrantBinding.SessionFor(grantContext, userId), policy.StepUpWindowMinutes, |
There was a problem hiding this comment.
The awaited ProtectedGrantBinding.CheckAsync call in AdpReleaseService and the listed call sites can reject without guarded handling, propagating unhandled exceptions. Wrap the call in try/catch or apply an equivalent error-handling strategy that maps or logs the failure appropriately.
Kody rule violation: Handle async operations with proper error handling
Prompt for LLM
File Core/Resgrid.Services/AdpReleaseService.cs:
Line 30:
The awaited `ProtectedGrantBinding.CheckAsync` call in AdpReleaseService and the listed call sites can reject without guarded handling, propagating unhandled exceptions. Wrap the call in try/catch or apply an equivalent error-handling strategy that maps or logs the failure appropriately.
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
|
|
||
| var now = _time.GetUtcNow().UtcDateTime; | ||
| if (await _challenges.CountPendingForUserAsync(binding.UserId, now, cancellationToken) >= Math.Max(1, PasskeyConfig.MaxOutstandingChallengesPerUser)) | ||
| return null; |
There was a problem hiding this comment.
AuthenticationChallengeService returns null from a Task-returning method, which can cause callers to receive a null Task and fail before awaiting a result. Return an explicit Task result, or change the contract to a nullable result type with a completed Task.
Kody rule violation: Avoid Returning Null in Non-Async Task Methods
return Task.FromResult<AuthenticationChallenge>(null);Prompt for LLM
File Core/Resgrid.Services/AuthenticationChallengeService.cs:
Line 34:
AuthenticationChallengeService returns null from a Task-returning method, which can cause callers to receive a null Task and fail before awaiting a result. Return an explicit Task result, or change the contract to a nullable result type with a completed Task.
Suggested Code:
return Task.FromResult<AuthenticationChallenge>(null);
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| catch (Exception ex) | ||
| { | ||
| // Authoritative state is unavailable: the ceremony is refused, never treated as approved (plan section 3 item 4). | ||
| Logging.LogException(ex, "Authentication challenge lookup failed; the ceremony was refused."); | ||
| return AuthenticationChallengeResult.Of(AuthenticationChallengeOutcome.Unavailable); |
There was a problem hiding this comment.
AuthenticationChallengeService catches every exception and converts it to AuthenticationChallengeOutcome.Unavailable, masking non-transient database failures and preventing them from surfacing for remediation. Catch only exceptions identified by IsTransientDatabaseException and rethrow non-transient failures after logging them.
Kody rule violation: Implement proper database error checking
catch (Exception ex) when (IsTransientDatabaseException(ex))
{
Logging.LogException(ex, "Transient authentication challenge lookup failure.");
return AuthenticationChallengeResult.Of(AuthenticationChallengeOutcome.Unavailable);
}
catch (Exception ex)
{
Logging.LogException(ex, "Non-transient authentication challenge lookup failure.");
throw;
}Prompt for LLM
File Core/Resgrid.Services/AuthenticationChallengeService.cs:
Line 70 to 74:
AuthenticationChallengeService catches every exception and converts it to AuthenticationChallengeOutcome.Unavailable, masking non-transient database failures and preventing them from surfacing for remediation. Catch only exceptions identified by IsTransientDatabaseException and rethrow non-transient failures after logging them.
Suggested Code:
catch (Exception ex) when (IsTransientDatabaseException(ex))
{
Logging.LogException(ex, "Transient authentication challenge lookup failure.");
return AuthenticationChallengeResult.Of(AuthenticationChallengeOutcome.Unavailable);
}
catch (Exception ex)
{
Logging.LogException(ex, "Non-transient authentication challenge lookup failure.");
throw;
}
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| public async Task RecordFailedAttemptAsync(FactorRecoveryTransaction transaction, CancellationToken cancellationToken = default) | ||
| { | ||
| try | ||
| { | ||
| await _transactions.RecordFailedAttemptAsync(transaction.FactorRecoveryTransactionId, cancellationToken); | ||
| } | ||
| catch (Exception ex) when (!(ex is OperationCanceledException)) | ||
| { | ||
| Logging.LogException(ex, "A failed factor recovery attempt could not be counted."); | ||
| } | ||
| } |
There was a problem hiding this comment.
RecordFailedAttemptAsync swallows every non-cancellation repository failure and returns success, so FactorRecoveryController continues using the same pending transaction when the attempt counter cannot be persisted, allowing unlimited replacement-authenticator guesses during a storage outage. Propagate a refusal or failure result and stop the recovery endpoint until the guarded attempt update succeeds.
public async Task RecordFailedAttemptAsync(FactorRecoveryTransaction transaction, CancellationToken cancellationToken = default)
{
await _transactions.RecordFailedAttemptAsync(transaction.FactorRecoveryTransactionId, cancellationToken);
}Prompt for LLM
File Core/Resgrid.Services/FactorRecoveryService.cs:
Line 99 to 109:
RecordFailedAttemptAsync swallows every non-cancellation repository failure and returns success, so FactorRecoveryController continues using the same pending transaction when the attempt counter cannot be persisted, allowing unlimited replacement-authenticator guesses during a storage outage. Propagate a refusal or failure result and stop the recovery endpoint until the guarded attempt update succeeds.
Suggested Code:
public async Task RecordFailedAttemptAsync(FactorRecoveryTransaction transaction, CancellationToken cancellationToken = default)
{
await _transactions.RecordFailedAttemptAsync(transaction.FactorRecoveryTransactionId, cancellationToken);
}
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| public async Task<MfaActivityReport> ReportAsync(string userId, string mfaActivityId, string reportingSessionId, SharedSessionRequestInfo request, | ||
| CancellationToken cancellationToken = default) | ||
| { | ||
| var activity = string.IsNullOrWhiteSpace(mfaActivityId) ? null : await _rows.GetAsync(mfaActivityId, cancellationToken); |
There was a problem hiding this comment.
MfaActivityService issues a repository query when userId or mfaActivityId is missing, allowing invalid identifiers to reach the data layer. Validate both identifiers first and return MfaActivityReportOutcome.NotFound without querying when either is blank.
Kody rule violation: Order validations before database queries
if (string.IsNullOrWhiteSpace(userId) || string.IsNullOrWhiteSpace(mfaActivityId))
return new MfaActivityReport { Outcome = MfaActivityReportOutcome.NotFound };
var activity = await _rows.GetAsync(mfaActivityId, cancellationToken);Prompt for LLM
File Core/Resgrid.Services/MfaActivityService.cs:
Line 68:
MfaActivityService issues a repository query when userId or mfaActivityId is missing, allowing invalid identifiers to reach the data layer. Validate both identifiers first and return MfaActivityReportOutcome.NotFound without querying when either is blank.
Suggested Code:
if (string.IsNullOrWhiteSpace(userId) || string.IsNullOrWhiteSpace(mfaActivityId))
return new MfaActivityReport { Outcome = MfaActivityReportOutcome.NotFound };
var activity = await _rows.GetAsync(mfaActivityId, cancellationToken);
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| private static bool NamesCredential(ProtectedDataGrant grant) => | ||
| grant.Version >= 2 && grant.MfaMethod != ProtectedDataGrantMfaMethods.Totp && grant.MfaMethod != ProtectedDataGrantMfaMethods.None; |
There was a problem hiding this comment.
NamesCredential dereferences the potentially null grant before validating it, which can cause a null reference failure in the protected-grant flow. Check grant before accessing its properties and use GrantVersionWithCredential for the version comparison.
Kody rule violation: Add null checks to prevent NullReferenceException
private static bool NamesCredential(ProtectedDataGrant grant) =>
grant != null && grant.Version >= GrantVersionWithCredential && grant.MfaMethod != ProtectedDataGrantMfaMethods.Totp && grant.MfaMethod != ProtectedDataGrantMfaMethods.None;Prompt for LLM
File Core/Resgrid.Services/ProtectedGrantBinding.cs:
Line 64 to 65:
`NamesCredential` dereferences the potentially null `grant` before validating it, which can cause a null reference failure in the protected-grant flow. Check `grant` before accessing its properties and use `GrantVersionWithCredential` for the version comparison.
Suggested Code:
private static bool NamesCredential(ProtectedDataGrant grant) =>
grant != null && grant.Version >= GrantVersionWithCredential && grant.MfaMethod != ProtectedDataGrantMfaMethods.Totp && grant.MfaMethod != ProtectedDataGrantMfaMethods.None;
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| private static bool IsAllowedOrigin(string origin, string rpId) | ||
| { | ||
| if (AndroidOriginPattern.IsMatch(origin)) | ||
| return true; |
There was a problem hiding this comment.
Android origins are accepted solely because they match a 43-character shape, without checking an allowlisted signing-certificate hash, so any Android application with its own signing key can present an android:apk-key-hash: origin and bypass per-application passkey binding. Require Android origins to be explicitly configured and validate the normalized origin against the configured per-client allowlist instead of returning true for every regex match.
private static bool IsAllowedOrigin(string origin, string rpId, IReadOnlySet<string> allowedAndroidOrigins)
{
if (AndroidOriginPattern.IsMatch(origin))
return allowedAndroidOrigins.Contains(origin);
// validate HTTPS web origins as before
}Prompt for LLM
File Core/Resgrid.Services/RelyingPartyRegistry.cs:
Line 131 to 134:
Android origins are accepted solely because they match a 43-character shape, without checking an allowlisted signing-certificate hash, so any Android application with its own signing key can present an `android:apk-key-hash:` origin and bypass per-application passkey binding. Require Android origins to be explicitly configured and validate the normalized origin against the configured per-client allowlist instead of returning true for every regex match.
Suggested Code:
private static bool IsAllowedOrigin(string origin, string rpId, IReadOnlySet<string> allowedAndroidOrigins)
{
if (AndroidOriginPattern.IsMatch(origin))
return allowedAndroidOrigins.Contains(origin);
// validate HTTPS web origins as before
}
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| { | ||
| try | ||
| { | ||
| if (await DeliverAsync(notice, cancellationToken)) |
There was a problem hiding this comment.
SecurityNoticeService performs delivery and related I/O once per notice inside a loop, serializing independent operations and increasing latency. Batch the work or safely parallelize it with Task.WhenAll, or add an aggregate delivery API.
Kody rule violation: Detect N+1 style queries and suggest batching
var deliveries = due.Select(notice => DeliverAsync(notice, cancellationToken));
foreach (var delivered in await Task.WhenAll(deliveries))
{
if (delivered)
sent++;
}Prompt for LLM
File Core/Resgrid.Services/SecurityNoticeService.cs:
Line 120:
SecurityNoticeService performs delivery and related I/O once per notice inside a loop, serializing independent operations and increasing latency. Batch the work or safely parallelize it with Task.WhenAll, or add an aggregate delivery API.
Suggested Code:
var deliveries = due.Select(notice => DeliverAsync(notice, cancellationToken));
foreach (var delivered in await Task.WhenAll(deliveries))
{
if (delivered)
sent++;
}
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| { | ||
| var reader = new CborReader(coseKey, CborConformanceMode.Lax); | ||
| var entries = reader.ReadStartMap(); | ||
| for (var i = 0; entries == null || i < entries; i++) |
There was a problem hiding this comment.
The loop uses an equality-based termination expression and does not explicitly access the nullable value, reducing termination clarity. Use pattern matching with entries is null || i < entries.Value for deterministic relational termination.
Kody rule violation: Avoid equality operators in loop termination conditions
for (int i = 0; entries is null || i < entries.Value; i++)Prompt for LLM
File Providers/Resgrid.Providers.Authentication/Fido2PasskeyProvider.cs:
Line 221:
The loop uses an equality-based termination expression and does not explicitly access the nullable value, reducing termination clarity. Use pattern matching with `entries is null || i < entries.Value` for deterministic relational termination.
Suggested Code:
for (int i = 0; entries is null || i < entries.Value; i++)
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| { | ||
| var label = reader.ReadInt64(); | ||
| if (label == 3 && reader.PeekState() is CborReaderState.UnsignedInteger or CborReaderState.NegativeInteger) | ||
| return (int)reader.ReadInt64(); |
There was a problem hiding this comment.
The CBOR algorithm value is converted from Int64 to int without checked arithmetic, so an out-of-range value can silently overflow. Use checked((int)reader.ReadInt64()).
Kody rule violation: Prevent Numeric Overflow in Calculations
return checked((int)reader.ReadInt64());Prompt for LLM
File Providers/Resgrid.Providers.Authentication/Fido2PasskeyProvider.cs:
Line 230:
The CBOR algorithm value is converted from Int64 to int without checked arithmetic, so an out-of-range value can silently overflow. Use `checked((int)reader.ReadInt64())`.
Suggested Code:
return checked((int)reader.ReadInt64());
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| public Task<bool> SessionEvent(string sessionId, string payload) => SendMessage(Topics.EventingTopic, new EventingMessage | ||
| { | ||
| Id = Guid.NewGuid(), Type = (int)EventingTypes.SessionEvent, TimeStamp = DateTime.UtcNow, ItemId = sessionId, Payload = payload | ||
| }.SerializeJson()); |
There was a problem hiding this comment.
The Rabbit/message-bus publish operation in RabbitTopicProvider and the listed call sites lacks guarded failure context, making session-event delivery failures difficult to diagnose. Wrap the operation in try/catch, log the operation and session identifier with structured fields, and rethrow or map the error appropriately.
Kody rule violation: Add try-catch blocks for external calls
public async Task<bool> SessionEvent(string sessionId, string payload)
{
try
{
return await SendMessage(Topics.EventingTopic, new EventingMessage
{
Id = Guid.NewGuid(), Type = (int)EventingTypes.SessionEvent, TimeStamp = DateTime.UtcNow, ItemId = sessionId, Payload = payload
}.SerializeJson());
}
catch (Exception exception)
{
_logger.Error(exception, "Failed to publish session event for session {SessionId}", sessionId);
throw;
}
}Prompt for LLM
File Providers/Resgrid.Providers.Bus.Rabbit/RabbitTopicProvider.cs:
Line 90 to 93:
The Rabbit/message-bus publish operation in RabbitTopicProvider and the listed call sites lacks guarded failure context, making session-event delivery failures difficult to diagnose. Wrap the operation in try/catch, log the operation and session identifier with structured fields, and rethrow or map the error appropriately.
Suggested Code:
public async Task<bool> SessionEvent(string sessionId, string payload)
{
try
{
return await SendMessage(Topics.EventingTopic, new EventingMessage
{
Id = Guid.NewGuid(), Type = (int)EventingTypes.SessionEvent, TimeStamp = DateTime.UtcNow, ItemId = sessionId, Payload = payload
}.SerializeJson());
}
catch (Exception exception)
{
_logger.Error(exception, "Failed to publish session event for session {SessionId}", sessionId);
throw;
}
}
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| Alter.Table("DepartmentSsoConfigs").AddColumn("FederatedMfaMappingJson").AsString(int.MaxValue).Nullable(); | ||
|
|
||
| if (!Schema.Table("DepartmentSsoConfigs").Column("FederatedMfaMappingVersion").Exists()) | ||
| Alter.Table("DepartmentSsoConfigs").AddColumn("FederatedMfaMappingVersion").AsInt64().NotNullable().WithDefaultValue(0); |
There was a problem hiding this comment.
Adding a non-null column with a default in one migration can lock or rewrite a large table, creating deployment risk across the listed migrations. Use an expand/contract strategy: add the column nullable, backfill existing rows in batches, add the default for new writes, and enforce NOT NULL later with an online migration and documented rollback plan.
Kody rule violation: Block risky database migrations (locking ops, downtime risk)
Prompt for LLM
File Providers/Resgrid.Providers.Migrations/Migrations/M0251_AddFederatedMfaMapping.cs:
Line 19:
Adding a non-null column with a default in one migration can lock or rewrite a large table, creating deployment risk across the listed migrations. Use an expand/contract strategy: add the column nullable, backfill existing rows in batches, add the default for new writes, and enforce NOT NULL later with an online migration and documented rollback plan.
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| private const int Canceled = (int)AuthenticationChallengeState.Canceled; | ||
|
|
||
| private readonly IConnectionProvider _connections; | ||
| private readonly bool _postgres; |
There was a problem hiding this comment.
The _postgres field is initialized once and never reassigned, so its declaration should remain readonly to enforce immutability. Keep private readonly bool _postgres; or verify that the field is intentionally mutable throughout the type.
Kody rule violation: Use `readonly` or `const` for Immutable Data
Prompt for LLM
File Repositories/Resgrid.Repositories.DataRepository/AuthenticationChallengeRepository.cs:
Line 25:
The `_postgres` field is initialized once and never reassigned, so its declaration should remain readonly to enforce immutability. Keep `private readonly bool _postgres;` or verify that the field is intentionally mutable throughout the type.
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| /// <summary>The time step Identity computes for <paramref name="utcNow"/> (rounded seconds, integer division).</summary> | ||
| public static long CurrentTimeStep(DateTime utcNow) | ||
| => Convert.ToInt64(Math.Round((utcNow - UnixEpoch).TotalSeconds)) / StepSeconds; |
There was a problem hiding this comment.
CurrentTimeStep rounds Unix seconds before integer division, advancing the TOTP step by up to 0.5 seconds and allowing the server to consume a future step before the actual 30-second boundary, which causes intermittent valid-code failures. Use floor/integer division of elapsed Unix seconds, such as (long)(utcNow - UnixEpoch).TotalSeconds / StepSeconds, to match RFC 6238 and Identity's time-step calculation.
public static long CurrentTimeStep(DateTime utcNow)
=> (long)(utcNow - UnixEpoch).TotalSeconds / StepSeconds;Prompt for LLM
File Repositories/Resgrid.Repositories.DataRepository/Stores/TotpCalculator.cs:
Line 23 to 25:
`CurrentTimeStep` rounds Unix seconds before integer division, advancing the TOTP step by up to 0.5 seconds and allowing the server to consume a future step before the actual 30-second boundary, which causes intermittent valid-code failures. Use floor/integer division of elapsed Unix seconds, such as `(long)(utcNow - UnixEpoch).TotalSeconds / StepSeconds`, to match RFC 6238 and Identity's time-step calculation.
Suggested Code:
public static long CurrentTimeStep(DateTime utcNow)
=> (long)(utcNow - UnixEpoch).TotalSeconds / StepSeconds;
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| await db.ExecuteAsync($"CREATE TABLE {Q("DepartmentSsoConfigs")} ({Q("DepartmentId")} int,{Q("IsEnabled")} {boolean})"); | ||
| await db.ExecuteAsync($"CREATE TABLE {Q("UserSessions")} ({Q("UserSessionId")} {text}(128),{Q("UserId")} {text}(128),{Q("DepartmentId")} int,{Q("State")} int,{Q("CreatedOn")} {date},{Q("LastActiveOn")} {date},{Q("ExpiresOn")} {date},{Q("AuthenticationGeneration")} bigint)"); | ||
| var now = new DateTime(2026,9,24,12,0,0); | ||
| var args = new { False = false, True = true, Now = now, Old = now.AddHours(-1), Future = now.AddHours(1) }; | ||
| await db.ExecuteAsync($"INSERT INTO {Q("DepartmentMembers")} ({Q("DepartmentMemberId")},{Q("DepartmentId")},{Q("UserId")},{Q("IsDeleted")},{Q("IsDisabled")},{Q("IsHidden")},{Q("PasswordLastSetOn")}) VALUES (1,709,'security-a',@False,@False,@True,@Old),(2,709,'security-missing',@False,@False,@False,NULL),(3,709,'security-disabled',@False,@True,@False,NULL),(4,710,'security-other',@False,@False,@False,NULL)",args); | ||
| await db.ExecuteAsync($"INSERT INTO {Q("AspNetUsers")} ({Q("Id")},{Q("TwoFactorEnabled")},{Q("AuthenticationGeneration")}) VALUES ('security-a',@True,4); INSERT INTO {Q("DepartmentSsoConfigs")} VALUES (709,@True),(709,@False),(710,@True); INSERT INTO {Q("DepartmentSecurityPolicies")} VALUES (709,@True,@False,30,2,90,12)",args); | ||
| await db.ExecuteAsync($"INSERT INTO {Q("AspNetUsers")} ({Q("Id")},{Q("TwoFactorEnabled")},{Q("AuthenticationGeneration")}) VALUES ('security-a',@True,4); INSERT INTO {Q("DepartmentSsoConfigs")} VALUES (709,@True),(709,@False),(710,@True); INSERT INTO {Q("DepartmentSecurityPolicies")} VALUES (709,@True,@False,30,2,90,12,@True,@False,@False,@False,@True,@True,@True,3)",args); |
There was a problem hiding this comment.
The related database inserts execute without a transaction, so a failure can leave the test database partially seeded. Enclose the inserts in a transaction, commit only after all succeed, and roll back on failure.
Kody rule violation: Handle transaction rollbacks properly
using var transaction = db.BeginTransaction();
try
{
await db.ExecuteAsync($"INSERT INTO {Q("AspNetUsers")} (...) VALUES (...); INSERT INTO {Q("DepartmentSsoConfigs")} VALUES (...); INSERT INTO {Q("DepartmentSecurityPolicies")} VALUES (...)", args, transaction);
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}Prompt for LLM
File Tests/Resgrid.Tests/AdminAssist/AdminAssistDatabaseTests.cs:
Line 355:
The related database inserts execute without a transaction, so a failure can leave the test database partially seeded. Enclose the inserts in a transaction, commit only after all succeed, and roll back on failure.
Suggested Code:
using var transaction = db.BeginTransaction();
try
{
await db.ExecuteAsync($"INSERT INTO {Q("AspNetUsers")} (...) VALUES (...); INSERT INTO {Q("DepartmentSsoConfigs")} VALUES (...); INSERT INTO {Q("DepartmentSecurityPolicies")} VALUES (...)", args, transaction);
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| [TestFixture] | ||
| public class RazorOutputEncodingTests | ||
| { | ||
| private static readonly Regex LocalizerInject = new Regex(@"@inject\s+I(?:String|Html|View)Localizer(?:<[\w.]+>)?\s+(\w+)", RegexOptions.Compiled); |
There was a problem hiding this comment.
The listed Regex instances, including LocalizerInject in Tests/Resgrid.Tests/Localization/RazorOutputEncodingTests.cs, omit a timeout and can be exposed to regular-expression denial-of-service attacks. Define an explicit timeout for every regex that processes untrusted or externally sourced input.
Kody rule violation: Specify Timeout for Regular Expressions
Prompt for LLM
File Tests/Resgrid.Tests/Localization/RazorOutputEncodingTests.cs:
Line 45:
The listed Regex instances, including `LocalizerInject` in `Tests/Resgrid.Tests/Localization/RazorOutputEncodingTests.cs`, omit a timeout and can be exposed to regular-expression denial-of-service attacks. Define an explicit timeout for every regex that processes untrusted or externally sourced input.
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| private static string FormatItems(string value) | ||
| { | ||
| return string.Join(" ", FormatItem.Matches(value) | ||
| .Select(m => int.Parse(m.Groups[1].Value)) |
There was a problem hiding this comment.
The listed tests use int.Parse to convert string input, which can throw on invalid or unexpected formats. Use a TryParse-style API and validate the culture and format where applicable.
Kody rule violation: Use TryParse for string conversions
Prompt for LLM
File Tests/Resgrid.Tests/Localization/ResourceKeyParityTests.cs:
Line 64:
The listed tests use `int.Parse` to convert string input, which can throw on invalid or unexpected formats. Use a TryParse-style API and validate the culture and format where applicable.
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| DataConfig.DatabaseType = type; | ||
| _database = Prefix + Guid.NewGuid().ToString("N"); | ||
| await using (var master = Connect(_master)) | ||
| await master.ExecuteAsync("CREATE DATABASE " + _database); |
There was a problem hiding this comment.
The listed database tests build SQL with unsanitized _database input, allowing SQL injection through the concatenated CREATE DATABASE statement. Use parameterized queries or a validated identifier-quoting mechanism for database names.
Kody rule violation: Prevent SQL Injection in Queries
Prompt for LLM
File Tests/Resgrid.Tests/Security/BrokerReplayDatabaseTests.cs:
Line 70:
The listed database tests build SQL with unsanitized `_database` input, allowing SQL injection through the concatenated `CREATE DATABASE` statement. Use parameterized queries or a validated identifier-quoting mechanism for database names.
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| if (type == DatabaseTypes.Postgres) r.AddPostgres(); else r.AddSqlServer(); | ||
| r.WithGlobalConnectionString(_connection); | ||
| }).AddSingleton(source.Object).BuildServiceProvider(); | ||
| _runner.GetRequiredService<IMigrationRunner>().MigrateUp(); |
There was a problem hiding this comment.
The async setup method calls the migration runner's synchronous MigrateUp() API, blocking the execution thread during database work. Use the asynchronous MigrateUpAsync() API.
Kody rule violation: Use Awaitable Methods in Async Code
await _runner.GetRequiredService<IMigrationRunner>().MigrateUpAsync();Prompt for LLM
File Tests/Resgrid.Tests/Security/BrokerReplayDatabaseTests.cs:
Line 85:
The async setup method calls the migration runner's synchronous `MigrateUp()` API, blocking the execution thread during database work. Use the asynchronous `MigrateUpAsync()` API.
Suggested Code:
await _runner.GetRequiredService<IMigrationRunner>().MigrateUpAsync();
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| /// Set RESGRID_ADP_SQLSERVER_TEST_CONNECTION / RESGRID_ADP_POSTGRES_TEST_CONNECTION (server-level connections) to run. | ||
| /// </summary> | ||
| [TestFixture(DatabaseTypes.SqlServer), TestFixture(DatabaseTypes.Postgres), NonParallelizable] | ||
| public class BrokerReplayDatabaseTests(DatabaseTypes type) |
There was a problem hiding this comment.
BrokerReplayDatabaseTests uses an async-capable primary-constructor pattern for lifecycle initialization, which obscures synchronous construction and setup ordering. Keep the constructor synchronous and move asynchronous initialization into the setup method.
Kody rule violation: Avoid asynchronous operations in constructors
public class BrokerReplayDatabaseTests
{
public BrokerReplayDatabaseTests(DatabaseTypes type) { ... }Prompt for LLM
File Tests/Resgrid.Tests/Security/BrokerReplayDatabaseTests.cs:
Line 32:
BrokerReplayDatabaseTests uses an async-capable primary-constructor pattern for lifecycle initialization, which obscures synchronous construction and setup ordering. Keep the constructor synchronous and move asynchronous initialization into the setup method.
Suggested Code:
public class BrokerReplayDatabaseTests
{
public BrokerReplayDatabaseTests(DatabaseTypes type) { ... }
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| reached = true; | ||
| return Task.FromResult<ActionExecutedContext>(null); | ||
| }); | ||
| return reached && context.Result == null; |
There was a problem hiding this comment.
The listed tests block asynchronous operations with .Result or .Wait(), which can cause deadlocks and prevent efficient asynchronous execution. Convert the call paths to async and use await instead of blocking.
Kody rule violation: Avoid Blocking Calls to Async Methods
Prompt for LLM
File Tests/Resgrid.Tests/Security/DepartmentLockAuthFlowsTests.cs:
Line 63:
The listed tests block asynchronous operations with `.Result` or `.Wait()`, which can cause deadlocks and prevent efficient asynchronous execution. Convert the call paths to async and use `await` instead of blocking.
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| [TestCase("https://unit.example.org\n", "https://unit.example.org/auth/callback", TestName = "A trailing newline, trimmed like spaces")] | ||
| public void A_clean_origin_is_listed_as_the_apps_web_page(string origin, string expected) | ||
| { | ||
| LegacyAppCallbacks.RedirectUris($"unit={origin}").Should().ContainSingle(u => u.Web).Which.Uri.Should().Be(expected); |
There was a problem hiding this comment.
The LegacyAppCallbacksTests.cs expression uses an inline lambda in a call chain, although the cited JSX-prop rule is not applicable to this C# test code. If the intended concern is allocation or readability, extract the predicate into a named method; otherwise remove this finding.
Kody rule violation: Avoid using .bind() or arrow functions in JSX props
Prompt for LLM
File Tests/Resgrid.Tests/Security/LegacyAppCallbacksTests.cs:
Line 122:
The LegacyAppCallbacksTests.cs expression uses an inline lambda in a call chain, although the cited JSX-prop rule is not applicable to this C# test code. If the intended concern is allocation or readability, extract the predicate into a named method; otherwise remove this finding.
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| options.EnableDegradedMode(); | ||
| options.DisableAccessTokenEncryption(); | ||
| options.AddEphemeralEncryptionKey().AddEphemeralSigningKey(); | ||
| options.UseAspNetCore().EnableTokenEndpointPassthrough().DisableTransportSecurityRequirement(); |
There was a problem hiding this comment.
LoginMfaTransactionApiTests disables the transport security requirement for token endpoints, allowing tests to pass without verifying TLS enforcement. Remove DisableTransportSecurityRequirement and require TLS 1.2 or later while testing HTTPS behavior.
Kody rule violation: Enforce TLS 1.2+ and HSTS on all external endpoints
options.UseAspNetCore().EnableTokenEndpointPassthrough();Prompt for LLM
File Tests/Resgrid.Tests/Security/LoginMfaTransactionApiTests.cs:
Line 192:
LoginMfaTransactionApiTests disables the transport security requirement for token endpoints, allowing tests to pass without verifying TLS enforcement. Remove DisableTransportSecurityRequirement and require TLS 1.2 or later while testing HTTPS behavior.
Suggested Code:
options.UseAspNetCore().EnableTokenEndpointPassthrough();
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| /// (server-level connections) to run. | ||
| /// </summary> | ||
| [TestFixture(DatabaseTypes.SqlServer), TestFixture(DatabaseTypes.Postgres), NonParallelizable] | ||
| public class MfaApprovalRequestDatabaseTests(DatabaseTypes type) |
There was a problem hiding this comment.
MfaApprovalRequestDatabaseTests uses a primary-constructor form that can encourage asynchronous initialization in the constructor. Keep the constructor synchronous and move asynchronous setup into a separate setup method.
Kody rule violation: Avoid asynchronous operations in constructors
public class MfaApprovalRequestDatabaseTests
{
public MfaApprovalRequestDatabaseTests(DatabaseTypes type) { ... }
}Prompt for LLM
File Tests/Resgrid.Tests/Security/MfaApprovalRequestDatabaseTests.cs:
Line 35:
MfaApprovalRequestDatabaseTests uses a primary-constructor form that can encourage asynchronous initialization in the constructor. Keep the constructor synchronous and move asynchronous setup into a separate setup method.
Suggested Code:
public class MfaApprovalRequestDatabaseTests
{
public MfaApprovalRequestDatabaseTests(DatabaseTypes type) { ... }
}
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| if (type == DatabaseTypes.Postgres) r.AddPostgres(); else r.AddSqlServer(); | ||
| r.WithGlobalConnectionString(_connection); | ||
| }).AddSingleton(source.Object).BuildServiceProvider(); | ||
| _runner.GetRequiredService<IMigrationRunner>().MigrateUp(); |
There was a problem hiding this comment.
PasskeyDatabaseTests and the listed database test setup methods perform synchronous migration I/O from asynchronous initialization code. Call the migration runner’s asynchronous API with MigrateUpAsync().
Kody rule violation: Use Awaitable Methods in Async Code
await _runner.GetRequiredService<IMigrationRunner>().MigrateUpAsync();Prompt for LLM
File Tests/Resgrid.Tests/Security/PasskeyDatabaseTests.cs:
Line 88:
PasskeyDatabaseTests and the listed database test setup methods perform synchronous migration I/O from asynchronous initialization code. Call the migration runner’s asynchronous API with MigrateUpAsync().
Suggested Code:
await _runner.GetRequiredService<IMigrationRunner>().MigrateUpAsync();
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| registry.Readiness.Problems.Should().BeEmpty(); | ||
| registry.Get(UserSessionClientApplication.Web).RpId.Should().Be("app.resgrid.com"); | ||
| registry.Get(UserSessionClientApplication.Command).RpId.Should().Be("ic.resgrid.com", "'ic' is the IC app, recorded as Command"); | ||
| registry.Get(UserSessionClientApplication.Responder).Origins.Should().BeEquivalentTo("https://responder.resgrid.com", AndroidOrigin); |
There was a problem hiding this comment.
RelyingPartyRegistryTests.cs accesses Origins on the potentially absent result of registry.Get(UserSessionClientApplication.Responder), which can cause a NullReferenceException. Assert the result is non-null before accessing Origins or use a null-safe access pattern.
Kody rule violation: Add null checks before accessing properties
registry.Get(UserSessionClientApplication.Responder)?.Origins.Should().BeEquivalentTo("https://responder.resgrid.com", AndroidOrigin);Prompt for LLM
File Tests/Resgrid.Tests/Security/RelyingPartyRegistryTests.cs:
Line 54:
RelyingPartyRegistryTests.cs accesses Origins on the potentially absent result of registry.Get(UserSessionClientApplication.Responder), which can cause a NullReferenceException. Assert the result is non-null before accessing Origins or use a null-safe access pattern.
Suggested Code:
registry.Get(UserSessionClientApplication.Responder)?.Origins.Should().BeEquivalentTo("https://responder.resgrid.com", AndroidOrigin);
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| public const string TokenEndpoint = "https://idp.example.test/token"; | ||
| public const string Code = "idp-authorization-code"; | ||
|
|
||
| private readonly RSA _key = RSA.Create(2048); |
There was a problem hiding this comment.
SsoBrokerTestDoubles retains an RSA instance in _key without deterministic disposal, and the listed locations contain similar disposable-resource lifetimes. Implement IDisposable on the provider or wrap the resource in a using or await using scope.
Kody rule violation: Use using statements for disposable resources
private readonly RSA _key = RSA.Create(2048); public void Dispose() => _key.Dispose();Prompt for LLM
File Tests/Resgrid.Tests/Security/SsoBrokerTestDoubles.cs:
Line 105:
SsoBrokerTestDoubles retains an RSA instance in _key without deterministic disposal, and the listed locations contain similar disposable-resource lifetimes. Implement IDisposable on the provider or wrap the resource in a using or await using scope.
Suggested Code:
private readonly RSA _key = RSA.Create(2048); public void Dispose() => _key.Dispose();
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| [Test] | ||
| public async Task Index_EncodesCategoryNamesInTheTreeData() | ||
| { | ||
| const string hostileName = "<img src=x onerror=alert(1)>"; |
There was a problem hiding this comment.
ContactsIndexEncodingTests.cs contains an HTML payload string, and the listed Razor views contain plain img elements; the Next.js next/image rule does not apply to these C# tests or ASP.NET Razor views. Apply the project’s equivalent image component or explicit dimensions and alt text only where the relevant frontend framework supports it.
Kody rule violation: Use next/image with explicit dimensions and alt
Prompt for LLM
File Tests/Resgrid.Tests/Web/User/ContactsIndexEncodingTests.cs:
Line 41:
ContactsIndexEncodingTests.cs contains an HTML payload string, and the listed Razor views contain plain img elements; the Next.js next/image rule does not apply to these C# tests or ASP.NET Razor views. Apply the project’s equivalent image component or explicit dimensions and alt text only where the relevant frontend framework supports it.
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| server.close(); | ||
| } | ||
| })().catch(error => { | ||
| console.error(error); |
There was a problem hiding this comment.
resgrid-shared-session.test.cjs logs only the raw error object, which omits structured operation context needed to diagnose failures. Log the operation name, test file, and error object, such as logger.error('shared session test failed', { operation: 'shared-session-test', err: error }).
Kody rule violation: Include error context in structured logs
Prompt for LLM
File Tests/Resgrid.Tests/Web/resgrid-shared-session.test.cjs:
Line 266:
resgrid-shared-session.test.cjs logs only the raw error object, which omits structured operation context needed to diagnose failures. Log the operation name, test file, and error object, such as logger.error('shared session test failed', { operation: 'shared-session-test', err: error }).
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| { | ||
| credential = registry.Authenticate(clientId, key); | ||
| if (credential == null) | ||
| Framework.Logging.LogError($"Protected Data Broker refused credential '{Sanitize(clientId)}' from {RemoteHost(context)}: unknown id or wrong key."); |
There was a problem hiding this comment.
BrokerCredentialMiddleware logs the raw remote address and identifying client value, exposing personal or identifying data in application logs. Replace those values with stable hashes or tokens while retaining the authentication failure context.
Kody rule violation: Mask PII and secrets in logs
Framework.Logging.LogError("Broker credential authentication failed", new { Operation = "AuthenticateBrokerCredential", ClientIdHash = Hash(clientId), RemoteHostToken = Tokenize(RemoteHost(context)), Error = "UnknownIdOrWrongKey" });Prompt for LLM
File Web/Resgrid.Web.Broker/Middleware/BrokerCredentialMiddleware.cs:
Line 56:
BrokerCredentialMiddleware logs the raw remote address and identifying client value, exposing personal or identifying data in application logs. Replace those values with stable hashes or tokens while retaining the authentication failure context.
Suggested Code:
Framework.Logging.LogError("Broker credential authentication failed", new { Operation = "AuthenticateBrokerCredential", ClientIdHash = Hash(clientId), RemoteHostToken = Tokenize(RemoteHost(context)), Error = "UnknownIdOrWrongKey" });
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| { | ||
| credential = registry.Authenticate(clientId, key); | ||
| if (credential == null) | ||
| Framework.Logging.LogError($"Protected Data Broker refused credential '{Sanitize(clientId)}' from {RemoteHost(context)}: unknown id or wrong key."); |
There was a problem hiding this comment.
BrokerCredentialMiddleware logs personal data such as the remote address without diagnostic-purpose or lawful-basis metadata. Redact or tokenize the personal data and attach the required security purpose and legitimate-interest metadata.
Kody rule violation: Redact PII in logs and metrics by default
Framework.Logging.LogError("Broker credential authentication failed", new { Operation = "AuthenticateBrokerCredential", ClientIdHash = Hash(clientId), RemoteHostToken = Tokenize(RemoteHost(context)), Gdpr = new { Purpose = "security", LawfulBasis = "legitimate-interest" } });Prompt for LLM
File Web/Resgrid.Web.Broker/Middleware/BrokerCredentialMiddleware.cs:
Line 56:
BrokerCredentialMiddleware logs personal data such as the remote address without diagnostic-purpose or lawful-basis metadata. Redact or tokenize the personal data and attach the required security purpose and legitimate-interest metadata.
Suggested Code:
Framework.Logging.LogError("Broker credential authentication failed", new { Operation = "AuthenticateBrokerCredential", ClientIdHash = Hash(clientId), RemoteHostToken = Tokenize(RemoteHost(context)), Gdpr = new { Purpose = "security", LawfulBasis = "legitimate-interest" } });
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| public bool IsLegacy { get; init; } | ||
|
|
||
| /// <summary>The value-free audit layer for this credential and lane, e.g. <c>broker/api/attended</c> (fits 32 characters).</summary> | ||
| public string AuditLayer(BrokerLane lane) => $"broker/{Id}/{LaneName(lane)}"; |
There was a problem hiding this comment.
BrokerCredentials.AuditLayer interpolates the nullable Id directly, producing an ambiguous audit value when Id is absent. Enforce a non-null invariant in the constructor or use a defined fallback such as string.Empty.
Kody rule violation: Add null checks to prevent NullReferenceException
public string AuditLayer(BrokerLane lane) => $"broker/{Id ?? string.Empty}/{LaneName(lane)}";Prompt for LLM
File Web/Resgrid.Web.Broker/Services/BrokerCredentials.cs:
Line 38:
BrokerCredentials.AuditLayer interpolates the nullable Id directly, producing an ambiguous audit value when Id is absent. Enforce a non-null invariant in the constructor or use a defined fallback such as string.Empty.
Suggested Code:
public string AuditLayer(BrokerLane lane) => $"broker/{Id ?? string.Empty}/{LaneName(lane)}";
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| var result = new List<LinkedIdentityData>(); | ||
| foreach (var link in await _identityLinks.GetActiveLinksAsync(userId, cancellationToken) ?? Array.Empty<UserExternalIdentityLink>()) | ||
| { | ||
| var department = await _departments.GetDepartmentByIdAsync(link.DepartmentId, false); |
There was a problem hiding this comment.
AccountSecurityController performs GetDepartmentByIdAsync for each link inside a loop, creating an N+1 lookup pattern. Batch-load departments with GetDepartmentsByIdsAsync before iterating and resolve each link from the resulting dictionary.
Kody rule violation: Optimize database queries with JOINs
IReadOnlyDictionary<string, Department> departments = await _departments.GetDepartmentsByIdsAsync(links.Select(link => link.DepartmentId));
Department department = departments[link.DepartmentId];Prompt for LLM
File Web/Resgrid.Web.Services/Controllers/v4/AccountSecurityController.cs:
Line 203:
AccountSecurityController performs GetDepartmentByIdAsync for each link inside a loop, creating an N+1 lookup pattern. Batch-load departments with GetDepartmentsByIdsAsync before iterating and resolve each link from the resulting dictionary.
Suggested Code:
IReadOnlyDictionary<string, Department> departments = await _departments.GetDepartmentsByIdsAsync(links.Select(link => link.DepartmentId));
Department department = departments[link.DepartmentId];
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| /// <summary>Completes the sign-in with the current authenticator code.</summary> | ||
| [HttpPost("CompleteTotp")] | ||
| [ProducesResponseType(StatusCodes.Status200OK)] | ||
| public async Task<ActionResult<LoginCompletionResult>> CompleteTotp([FromBody] CompleteLoginCodeInput input, CancellationToken cancellationToken) |
There was a problem hiding this comment.
AuthenticationController processes CompleteTotp input before checking ModelState.IsValid, allowing invalid model data to reach authentication logic. Check ModelState.IsValid at the start of the action and return a 400 response before processing input.
Kody rule violation: Always Validate `ModelState.IsValid` in Controllers
Prompt for LLM
File Web/Resgrid.Web.Services/Controllers/v4/AuthenticationController.cs:
Line 66:
AuthenticationController processes CompleteTotp input before checking ModelState.IsValid, allowing invalid model data to reach authentication logic. Check ModelState.IsValid at the start of the action and return a 400 response before processing input.
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| _audits.SaveSystemAuditAsync(new SystemAudit | ||
| { | ||
| System = (int)SystemAuditSystems.Api, | ||
| Type = (int)type, | ||
| UserId = user?.Id, | ||
| Username = user?.UserName, | ||
| Successful = successful, | ||
| IpAddress = IpAddressHelper.GetRequestIP(Request, true), | ||
| ServerName = Environment.MachineName, | ||
| Data = data |
There was a problem hiding this comment.
FactorRecoveryController and the listed audit call sites record incomplete security-event data and do not establish an immutable audit trail. Include a UTC ISO-8601 timestamp, actor user ID and role, action, resource ID, result, trace ID, IP, and user agent, and use an append-only, signed or WORM-backed sink forwarded to the SIEM.
Kody rule violation: Emit tamper-evident audit logs with required fields
Prompt for LLM
File Web/Resgrid.Web.Services/Controllers/v4/FactorRecoveryController.cs:
Line 282 to 291:
FactorRecoveryController and the listed audit call sites record incomplete security-event data and do not establish an immutable audit trail. Include a UTC ISO-8601 timestamp, actor user ID and role, action, resource ID, result, trace ID, IP, and user agent, and use an append-only, signed or WORM-backed sink forwarded to the SIEM.
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| [Route("api/v{VersionId:apiVersion}/AccountSecurity")] | ||
| [ApiVersion("4.0")] | ||
| [ApiExplorerSettings(GroupName = "v4")] | ||
| [AllowAnonymous] |
There was a problem hiding this comment.
FactorRecoveryController permits anonymous access to recovery actions through blanket [AllowAnonymous] authorization, without binding requests to an authorized transaction, user, and recovery scope. Replace anonymous access with explicit default-deny authorization and validate each recovery request against those values.
Kody rule violation: Implement RBAC with least privilege and deny-by-default
Prompt for LLM
File Web/Resgrid.Web.Services/Controllers/v4/FactorRecoveryController.cs:
Line 30:
FactorRecoveryController permits anonymous access to recovery actions through blanket [AllowAnonymous] authorization, without binding requests to an authorized transaction, user, and recovery scope. Replace anonymous access with explicit default-deny authorization and validate each recovery request against those values.
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| return transaction.TransactionPurpose == SsoTransactionPurpose.Reauthentication | ||
| ? await ReauthenticatedAsync(transaction, cancellationToken) | ||
| : await LoginAsync(transaction, client, input.ClientId, cancellationToken); |
There was a problem hiding this comment.
Sso/Redeem routes every transaction other than Reauthentication into LoginAsync, including StepUp and AdpStepUp transactions created for an already authenticated session, which can issue login credentials instead of only recording step-up evidence. Dispatch each SsoTransactionPurpose explicitly and reject StepUp and AdpStepUp transactions from Sso/Redeem.
return transaction.TransactionPurpose switch
{
SsoTransactionPurpose.Reauthentication => await ReauthenticatedAsync(transaction, cancellationToken),
SsoTransactionPurpose.Login => await LoginAsync(transaction, client, input.ClientId, cancellationToken),
_ => Refuse(SsoBrokerOutcome.InvalidRequest)
};Prompt for LLM
File Web/Resgrid.Web.Services/Controllers/v4/SsoController.cs:
Line 149 to 151:
Sso/Redeem routes every transaction other than Reauthentication into LoginAsync, including StepUp and AdpStepUp transactions created for an already authenticated session, which can issue login credentials instead of only recording step-up evidence. Dispatch each SsoTransactionPurpose explicitly and reject StepUp and AdpStepUp transactions from Sso/Redeem.
Suggested Code:
return transaction.TransactionPurpose switch
{
SsoTransactionPurpose.Reauthentication => await ReauthenticatedAsync(transaction, cancellationToken),
SsoTransactionPurpose.Login => await LoginAsync(transaction, client, input.ClientId, cancellationToken),
_ => Refuse(SsoBrokerOutcome.InvalidRequest)
};
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| var found = await _approvals.GetForRequesterAsync(approvalRequestId, Model.Security.MfaApprovalRequesterKind.Session, session.SessionId, | ||
| cancellationToken); |
There was a problem hiding this comment.
DataProtectionController queries the approval service with an unvalidated approvalRequestId, allowing empty or malformed identifiers to reach the data layer. Reject blank or malformed approvalRequestId before calling GetForRequesterAsync.
Kody rule violation: Validate inputs on the server (zod) in Route Handlers/Actions
if (string.IsNullOrWhiteSpace(approvalRequestId)) return Json(new { success = false, error = "invalid_request" }); var found = await _approvals.GetForRequesterAsync(approvalRequestId, Model.Security.MfaApprovalRequesterKind.Session, session.SessionId,Prompt for LLM
File Web/Resgrid.Web/Areas/User/Controllers/DataProtectionController.cs:
Line 537 to 538:
DataProtectionController queries the approval service with an unvalidated approvalRequestId, allowing empty or malformed identifiers to reach the data layer. Reject blank or malformed approvalRequestId before calling GetForRequesterAsync.
Suggested Code:
if (string.IsNullOrWhiteSpace(approvalRequestId)) return Json(new { success = false, error = "invalid_request" }); var found = await _approvals.GetForRequesterAsync(approvalRequestId, Model.Security.MfaApprovalRequesterKind.Session, session.SessionId,
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| public async Task<IActionResult> CompleteRegistration([FromForm] string requestId, [FromForm] string credential, [FromForm] string displayName, | ||
| CancellationToken cancellationToken) | ||
| { | ||
| var user = await _userManager.GetUserAsync(User); |
There was a problem hiding this comment.
PasskeysController queries the identity store before validating requestId and credential, allowing malformed requests to trigger unnecessary lookups. Reject empty or malformed values before calling GetUserAsync.
Kody rule violation: Order validations before database queries
if (string.IsNullOrWhiteSpace(requestId) || string.IsNullOrWhiteSpace(credential))
return Refuse(PasskeyOutcome.InvalidRequest);
var user = await _userManager.GetUserAsync(User);Prompt for LLM
File Web/Resgrid.Web/Areas/User/Controllers/PasskeysController.cs:
Line 60:
PasskeysController queries the identity store before validating requestId and credential, allowing malformed requests to trigger unnecessary lookups. Reject empty or malformed values before calling GetUserAsync.
Suggested Code:
if (string.IsNullOrWhiteSpace(requestId) || string.IsNullOrWhiteSpace(credential))
return Refuse(PasskeyOutcome.InvalidRequest);
var user = await _userManager.GetUserAsync(User);
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| model.Unavailable = true; | ||
| return View(model); | ||
| } | ||
| model.Type = model.Families.FirstOrDefault(f => string.Equals(f, type, StringComparison.OrdinalIgnoreCase)); |
There was a problem hiding this comment.
SearchController has already verified that model.Families is non-empty, so FirstOrDefault can incorrectly imply that absence is valid. Use First to preserve the non-empty invariant and surface an unexpected missing match.
Kody rule violation: Use `First`/`Single` Instead of `FirstOrDefault`/`SingleOrDefault` for Non-Empty Collections
model.Type = model.Families.First(f => string.Equals(f, type, StringComparison.OrdinalIgnoreCase));Prompt for LLM
File Web/Resgrid.Web/Areas/User/Controllers/SearchController.cs:
Line 83:
SearchController has already verified that model.Families is non-empty, so FirstOrDefault can incorrectly imply that absence is valid. Use First to preserve the non-empty invariant and surface an unexpected missing match.
Suggested Code:
model.Type = model.Families.First(f => string.Equals(f, type, StringComparison.OrdinalIgnoreCase));
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| { | ||
| var user = await _userManager.GetUserAsync(User); | ||
| if (user == null) return NotFound(); | ||
|
|
||
| if (!await _userManager.GetTwoFactorEnabledAsync(user)) | ||
| return RedirectToAction(nameof(Enable2FA)); | ||
|
|
||
| if (!await HasFreshFirstFactorAsync(user, TwoFactorConfig.FirstFactorOperationWindowMinutes)) | ||
| return RedirectToReauthenticate(Url.Action(nameof(ReplaceAuthenticator))); |
There was a problem hiding this comment.
The ReplaceAuthenticator POST action omits the fresh-first-factor check required by the GET action, allowing an older authenticated session that satisfies HasReplacementAuthorityAsync to replace the account authenticator without fresh password or SSO proof. Call HasFreshFirstFactorAsync with TwoFactorConfig.FirstFactorOperationWindowMinutes before HasReplacementAuthorityAsync and reject or redirect when it returns false.
if (!await _userManager.GetTwoFactorEnabledAsync(user))\n return RedirectToAction(nameof(Enable2FA));\n\nif (!await HasFreshFirstFactorAsync(user, TwoFactorConfig.FirstFactorOperationWindowMinutes))\n return RedirectToReauthenticate(Url.Action(nameof(ReplaceAuthenticator)));\n\nif (!await HasReplacementAuthorityAsync(user, cancellationToken))\n return RedirectToStepUp();\n\nvar stagedKey = await GetStagedAuthenticatorKeyAsync(user);Prompt for LLM
File Web/Resgrid.Web/Areas/User/Controllers/TwoFactorController.cs:
Line 251 to 259:
The ReplaceAuthenticator POST action omits the fresh-first-factor check required by the GET action, allowing an older authenticated session that satisfies HasReplacementAuthorityAsync to replace the account authenticator without fresh password or SSO proof. Call HasFreshFirstFactorAsync with TwoFactorConfig.FirstFactorOperationWindowMinutes before HasReplacementAuthorityAsync and reject or redirect when it returns false.
Suggested Code:
if (!await _userManager.GetTwoFactorEnabledAsync(user))\n return RedirectToAction(nameof(Enable2FA));\n\nif (!await HasFreshFirstFactorAsync(user, TwoFactorConfig.FirstFactorOperationWindowMinutes))\n return RedirectToReauthenticate(Url.Action(nameof(ReplaceAuthenticator)));\n\nif (!await HasReplacementAuthorityAsync(user, cancellationToken))\n return RedirectToStepUp();\n\nvar stagedKey = await GetStagedAuthenticatorKeyAsync(user);
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| /// <summary>Words and phrases to mark in titles and excerpts.</summary> | ||
| public List<string> HighlightTerms { get; set; } = new List<string>(); | ||
|
|
||
| public int FirstIndex => (Page - 1) * PageSize + 1; |
There was a problem hiding this comment.
SearchIndexView.FirstIndex multiplies Page by PageSize using Int32 arithmetic, so large values can overflow silently. Use checked arithmetic or a wider numeric type.
Kody rule violation: Prevent Numeric Overflow in Calculations
public int FirstIndex => checked((Page - 1) * PageSize + 1);Prompt for LLM
File Web/Resgrid.Web/Areas/User/Models/Search/SearchIndexView.cs:
Line 49:
SearchIndexView.FirstIndex multiplies Page by PageSize using Int32 arithmetic, so large values can overflow silently. Use checked arithmetic or a wider numeric type.
Suggested Code:
public int FirstIndex => checked((Page - 1) * PageSize + 1);
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
|
|
||
| private static List<string> Values(string lines) | ||
| { | ||
| var values = (lines ?? string.Empty).Split('\n').Select(line => line.Trim()).Where(line => line.Length > 0).ToList(); |
There was a problem hiding this comment.
The SsoViews.cshtml LINQ chain combines splitting, trimming, filtering, and materialization in one expression, obscuring each transformation. Assign the intermediate splitLines, trimmedLines, and nonEmptyLines expressions before materializing values.
Kody rule violation: Limit Lengthy LINQ Chains
var splitLines = (lines ?? string.Empty).Split('\n');
var trimmedLines = splitLines.Select(line => line.Trim());
var nonEmptyLines = trimmedLines.Where(line => line.Length > 0);
var values = nonEmptyLines.ToList();Prompt for LLM
File Web/Resgrid.Web/Areas/User/Models/Security/SsoViews.cs:
Line 215:
The SsoViews.cshtml LINQ chain combines splitting, trimming, filtering, and materialization in one expression, obscuring each transformation. Assign the intermediate splitLines, trimmedLines, and nonEmptyLines expressions before materializing values.
Suggested Code:
var splitLines = (lines ?? string.Empty).Split('\n');
var trimmedLines = splitLines.Select(line => line.Trim());
var nonEmptyLines = trimmedLines.Where(line => line.Length > 0);
var values = nonEmptyLines.ToList();
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| @Html.Raw($"<td><a href='{@Url.Action("ViewMessage", "Messages", new { area = "User" })}?messageId={message.MessageId}' class='btn btn-primary btn-xs' title='View Message'>{@commonLocalizer["View"]}</a> <a href='{@Url.Action("DeleteMessage", "Messages", new { area = "User" })}?messageId={message.MessageId}' class='btn btn-danger btn-xs' title='Delete Message' data-confirm='{@localizer["DeleteMessageWarning"]} {message.Subject}?' rel='nofollow'>{@commonLocalizer["Delete"]}</a></td>") | ||
| @Html.Raw($"<td>{Html.Encode(message.Subject)}</td>") | ||
| @Html.Raw($"<td>{Html.Encode(message.SentOn.TimeConverterToString(Model.Department))}</td>") | ||
| @Html.Raw($"<td><a href='{@Url.Action("ViewMessage", "Messages", new { area = "User" })}?messageId={message.MessageId}' class='btn btn-primary btn-xs' title='View Message'>{Html.Encode(commonLocalizer["View"].Value)}</a> <a href='{@Url.Action("DeleteMessage", "Messages", new { area = "User" })}?messageId={message.MessageId}' class='btn btn-danger btn-xs' title='Delete Message' data-confirm='{Html.Encode(localizer["DeleteMessageWarning"].Value)} {Html.Encode(message.Subject)}?' rel='nofollow'>{Html.Encode(commonLocalizer["Delete"].Value)}</a></td>") |
There was a problem hiding this comment.
Outbox.cshtml constructs raw HTML with message.MessageId interpolated into query strings and passes it through Html.Raw, bypassing normal Razor attribute encoding. Generate URLs with Url.Action or URL-encode the identifier and let Razor encode the resulting attributes.
Kody rule violation: Always sanitize user inputs
<td><a href="@Url.Action("ViewMessage", "Messages", new { area = "User", messageId = message.MessageId })" class="btn btn-primary btn-xs" title="@localizer["View"].Value">@commonLocalizer["View"].Value</a></td>Prompt for LLM
File Web/Resgrid.Web/Areas/User/Views/Messages/Outbox.cshtml:
Line 50:
Outbox.cshtml constructs raw HTML with message.MessageId interpolated into query strings and passes it through Html.Raw, bypassing normal Razor attribute encoding. Generate URLs with Url.Action or URL-encode the identifier and let Razor encode the resulting attributes.
Suggested Code:
<td><a href="@Url.Action("ViewMessage", "Messages", new { area = "User", messageId = message.MessageId })" class="btn btn-primary btn-xs" title="@localizer["View"].Value">@commonLocalizer["View"].Value</a></td>
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| @if (Model.Searched && Model.Results.Count > 0) | ||
| { | ||
| <div class="col-sm-4"> | ||
| <div class="btn-group top-page-buttons" style="float:right;padding-right:15px;"> |
There was a problem hiding this comment.
Search/Index.cshtml embeds layout declarations in an inline style attribute, making styling harder to reuse and maintain. Move the declarations into a scoped stylesheet or component-scoped class such as a dedicated BEM modifier.
Kody rule violation: Use component-scoped styling
<div class="btn-group top-page-buttons">
Prompt for LLM
File Web/Resgrid.Web/Areas/User/Views/Search/Index.cshtml:
Line 28:
Search/Index.cshtml embeds layout declarations in an inline style attribute, making styling harder to reuse and maintain. Move the declarations into a scoped stylesheet or component-scoped class such as a dedicated BEM modifier.
Suggested Code:
<div class="btn-group top-page-buttons">
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| if (!await _recoveries.TryCompleteAsync(recovery, cancellationToken)) | ||
| return RecoveryEnded(); | ||
|
|
||
| var now = DateTime.UtcNow; | ||
| await AuthenticatorSetup.PromoteAsync(_userManager, _userStore, _mfaState, user, stagedKey, | ||
| new TotpEnrollmentContext(false, (int)UserSessionClientApplication.Web), cancellationToken); |
There was a problem hiding this comment.
The recovery transaction is marked complete before the replacement authenticator and account cleanup succeed, so an exception in PromoteAsync, passkey revocation, or pending-request cancellation consumes the recovery transaction while leaving the old factor unchanged. Complete the recovery atomically with authenticator promotion and cleanup, or retain a resumable pending state until all required mutations commit.
// Perform TryCompleteAsync together with PromoteAsync and the required cleanup in one atomic service operation,
// or complete the recovery only after those mutations have succeeded.
var completed = await _recoveries.TryCompleteAndPromoteAsync(recovery, user, stagedKey, remove, cancellationToken);
if (!completed)
return RecoveryEnded();Prompt for LLM
File Web/Resgrid.Web/Controllers/AccountController.Recovery.cs:
Line 321 to 326:
The recovery transaction is marked complete before the replacement authenticator and account cleanup succeed, so an exception in PromoteAsync, passkey revocation, or pending-request cancellation consumes the recovery transaction while leaving the old factor unchanged. Complete the recovery atomically with authenticator promotion and cleanup, or retain a resumable pending state until all required mutations commit.
Suggested Code:
// Perform TryCompleteAsync together with PromoteAsync and the required cleanup in one atomic service operation,
// or complete the recovery only after those mutations have succeeded.
var completed = await _recoveries.TryCompleteAndPromoteAsync(recovery, user, stagedKey, remove, cancellationToken);
if (!completed)
return RecoveryEnded();
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| var latest = await evidence.GetLatestSecondFactorAsync(user.Id, sessionKey, user.AuthenticationGeneration, cancellationToken); | ||
| if (excludeFederated && latest?.Method == (int)MfaEvidenceMethod.Federated) | ||
| return null; |
There was a problem hiding this comment.
GetLatestSecondFactorUtcAsync discards all evidence when the latest second-factor record is Federated and excludeFederated is true, so MayTestMappingAsync can reject a recent valid Resgrid factor within the five-minute window. Query the latest acceptable non-federated evidence or pass an exclusion filter to the repository instead of returning null after examining only the overall latest row.
if (excludeFederated && latest?.Method == (int)MfaEvidenceMethod.Federated)
latest = await evidence.GetLatestSecondFactorAsync(user.Id, sessionKey, user.AuthenticationGeneration, cancellationToken, excludeMethods: new[] { MfaEvidenceMethod.Federated });Prompt for LLM
File Web/Resgrid.Web/Helpers/StepUpEvidence.cs:
Line 34 to 36:
GetLatestSecondFactorUtcAsync discards all evidence when the latest second-factor record is Federated and excludeFederated is true, so MayTestMappingAsync can reject a recent valid Resgrid factor within the five-minute window. Query the latest acceptable non-federated evidence or pass an exclusion filter to the repository instead of returning null after examining only the overall latest row.
Suggested Code:
if (excludeFederated && latest?.Method == (int)MfaEvidenceMethod.Federated)
latest = await evidence.GetLatestSecondFactorAsync(user.Id, sessionKey, user.AuthenticationGeneration, cancellationToken, excludeMethods: new[] { MfaEvidenceMethod.Federated });
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| <div class="row"> | ||
| <div class="col-md-6 col-md-offset-3"> | ||
| <div style="text-align: center;"> | ||
| <img src="~/images/Resgrid_JustText.png" style="width: 200px;" /> |
There was a problem hiding this comment.
SsoLogOn.cshtml and the listed account and shared-session views serve a PNG without meaningful alt text, explicit dimensions, lazy loading, asynchronous decoding, or a modern-format fallback. Serve WebP with a PNG fallback and include alt, width, height, loading="lazy", and decoding="async" attributes.
Kody rule violation: Serve responsive images with modern formats and lazy-load
<picture><source srcset="~/images/Resgrid_JustText.webp" type="image/webp" /><img src="~/images/Resgrid_JustText.png" width="200" height="auto" loading="lazy" decoding="async" alt="Resgrid" /></picture>Prompt for LLM
File Web/Resgrid.Web/Views/Account/SsoLogOn.cshtml:
Line 23:
SsoLogOn.cshtml and the listed account and shared-session views serve a PNG without meaningful alt text, explicit dimensions, lazy loading, asynchronous decoding, or a modern-format fallback. Serve WebP with a PNG fallback and include alt, width, height, loading="lazy", and decoding="async" attributes.
Suggested Code:
<picture><source srcset="~/images/Resgrid_JustText.webp" type="image/webp" /><img src="~/images/Resgrid_JustText.png" width="200" height="auto" loading="lazy" decoding="async" alt="Resgrid" /></picture>
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| }).then(function (status) { | ||
| if (status && status.shared && !status.locked) | ||
| back(); | ||
| }).catch(function () { }); |
There was a problem hiding this comment.
The empty catch block in resgrid.shared.locked.js silently swallows exceptions, preventing diagnosis and allowing failures to continue without explicit handling. Log the error with relevant context and either rethrow it or handle the failure explicitly.
Kody rule violation: Avoid empty catch blocks
Prompt for LLM
File Web/Resgrid.Web/wwwroot/js/app/common/shared/resgrid.shared.locked.js:
Line 51:
The empty catch block in resgrid.shared.locked.js silently swallows exceptions, preventing diagnosis and allowing failures to continue without explicit handling. Log the error with relevant context and either rethrow it or handle the failure explicitly.
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| if (apply(status)) | ||
| broadcast({ type: 'active' }); | ||
| }); | ||
| window.setInterval(tick, 1000); |
There was a problem hiding this comment.
resgrid.shared.session.js creates an interval without retaining its ID, so the timer can survive component or page teardown. Store the interval ID and call clearInterval during teardown.
Kody rule violation: Clear timers on teardown/unmount
const intervalId = window.setInterval(tick, 1000);
// clearInterval(intervalId) during teardownPrompt for LLM
File Web/Resgrid.Web/wwwroot/js/app/common/shared/resgrid.shared.session.js:
Line 269:
resgrid.shared.session.js creates an interval without retaining its ID, so the timer can survive component or page teardown. Store the interval ID and call clearInterval during teardown.
Suggested Code:
const intervalId = window.setInterval(tick, 1000);
// clearInterval(intervalId) during teardown
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| if (xhr.status === 403) { | ||
| if (xhr.status === 403 && xhr.responseJSON && xhr.responseJSON.error === 'step_up_required' && xhr.responseJSON.redirectUrl) { | ||
| // The department requires MFA this session has not completed: verify, then come back and switch. | ||
| window.location.href = xhr.responseJSON.redirectUrl; |
There was a problem hiding this comment.
resgrid.profile.yourdepartments.js navigates directly to the server-provided xhr.responseJSON.redirectUrl, allowing an open redirect or phishing destination. Validate the URL against an explicit trusted allowlist or same-origin policy before assigning window.location.href.
Kody rule violation: Avoid unprotected HTTP request redirections
const redirectUrl = xhr.responseJSON.redirectUrl;
if (typeof redirectUrl === 'string' && redirectUrl.startsWith(window.location.origin + '/')) {
window.location.href = redirectUrl;
}Prompt for LLM
File Web/Resgrid.Web/wwwroot/js/app/internal/profile/resgrid.profile.yourdepartments.js:
Line 58:
resgrid.profile.yourdepartments.js navigates directly to the server-provided xhr.responseJSON.redirectUrl, allowing an open redirect or phishing destination. Validate the URL against an explicit trusted allowlist or same-origin policy before assigning window.location.href.
Suggested Code:
const redirectUrl = xhr.responseJSON.redirectUrl;
if (typeof redirectUrl === 'string' && redirectUrl.startsWith(window.location.origin + '/')) {
window.location.href = redirectUrl;
}
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
No description provided.