90 lines
3.4 KiB
C#
90 lines
3.4 KiB
C#
using System.Collections.Concurrent;
|
|
using System.Text.RegularExpressions;
|
|
|
|
namespace Nexus.Api.Services;
|
|
|
|
public static class AgentActivityText
|
|
{
|
|
private static readonly (Regex Pattern, string Replacement)[] InlineRedactions =
|
|
[
|
|
(new Regex(@"(?i)(authorization\s*:\s*bearer)\s+\S+", RegexOptions.CultureInvariant), "$1 [redacted]"),
|
|
(new Regex(@"(?i)(x-nexus-api-key\s*:\s*)\S+", RegexOptions.CultureInvariant), "$1[redacted]"),
|
|
(new Regex(@"(?i)(api[_-]?key\s*[:=]\s*)\S+", RegexOptions.CultureInvariant), "$1[redacted]"),
|
|
(new Regex(@"(?i)(token\s*[:=]\s*)\S+", RegexOptions.CultureInvariant), "$1[redacted]"),
|
|
(new Regex(@"(?i)(password\s*[:=]\s*)\S+", RegexOptions.CultureInvariant), "$1[redacted]"),
|
|
(new Regex(@"(?i)(secret\s*[:=]\s*)\S+", RegexOptions.CultureInvariant), "$1[redacted]"),
|
|
(new Regex(@"(?i)(jwt\s*[:=]\s*)\S+", RegexOptions.CultureInvariant), "$1[redacted]"),
|
|
(new Regex(@"(?i)(private[_-]?key\s*[:=]\s*)\S+", RegexOptions.CultureInvariant), "$1[redacted]")
|
|
];
|
|
|
|
private static readonly Regex[] ResidualSensitivePatterns =
|
|
[
|
|
new(@"(?i)bearer\s+(?!\[redacted\])\S+", RegexOptions.CultureInvariant),
|
|
new(@"(?i)x-nexus-api-key\s*:\s*(?!\[redacted\])\S+", RegexOptions.CultureInvariant),
|
|
new(@"(?i)private[_-]?key\s*[:=]\s*(?!\[redacted\])\S+", RegexOptions.CultureInvariant)
|
|
];
|
|
|
|
private static readonly string[] KnownActorIds =
|
|
[
|
|
.. AgentIdentityCatalog.DefaultConfiguredAgentIds,
|
|
"bao",
|
|
"nexus-system"
|
|
];
|
|
|
|
public static string RedactForDisplay(string? content)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(content))
|
|
return content ?? string.Empty;
|
|
|
|
var lines = content.Split('\n');
|
|
for (var i = 0; i < lines.Length; i++)
|
|
{
|
|
var sanitized = lines[i];
|
|
foreach (var (pattern, replacement) in InlineRedactions)
|
|
{
|
|
sanitized = pattern.Replace(sanitized, replacement);
|
|
}
|
|
|
|
if (ResidualSensitivePatterns.Any(pattern => pattern.IsMatch(sanitized)))
|
|
sanitized = "[redacted sensitive line]";
|
|
|
|
lines[i] = sanitized;
|
|
}
|
|
|
|
return string.Join('\n', lines).Trim();
|
|
}
|
|
|
|
public static bool MatchesAgent(string? content, string agentId)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(agentId))
|
|
return false;
|
|
|
|
var normalized = agentId.Trim().ToLowerInvariant();
|
|
return ExtractAgentIds(content).Contains(normalized, StringComparer.OrdinalIgnoreCase);
|
|
}
|
|
|
|
public static string[] ExtractAgentIds(string? content)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(content))
|
|
return [];
|
|
|
|
var matches = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
foreach (var actorId in KnownActorIds)
|
|
{
|
|
if (BuildActorRegex(actorId).IsMatch(content))
|
|
matches.Add(actorId);
|
|
}
|
|
|
|
return matches
|
|
.Select(actorId => actorId.ToLowerInvariant())
|
|
.OrderBy(actorId => actorId, StringComparer.Ordinal)
|
|
.ToArray();
|
|
}
|
|
|
|
private static Regex BuildActorRegex(string actorId)
|
|
=> ActorPatternCache.GetOrAdd(actorId, static key =>
|
|
new Regex($@"(?<![a-z0-9]){Regex.Escape(key)}(?![a-z0-9])", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant));
|
|
|
|
private static readonly ConcurrentDictionary<string, Regex> ActorPatternCache = new(StringComparer.OrdinalIgnoreCase);
|
|
}
|