권한 체계를 사내 모드 기준으로 정리하고 실행 단위 승인 범위를 바로잡음
사내 모드에서 process/build_run/open_external 경로의 외부 접근 차단 범위를 강화했습니다. http_tool과 외부 URI 차단에 더해 curl, Invoke-WebRequest 같은 네트워크성 명령과 build_run custom 실행을 내부 정책으로 막아 실제 동작이 정책 선언과 더 가깝게 맞춰지도록 했습니다. ChatWindow의 '이번 실행 동안 허용' 승인 규칙을 run-scope로 변경했습니다. 탭 실행 시작과 종료 시 승인 캐시를 초기화하고 같은 실행 안에서만 동일 범위 접근을 재질문 없이 재사용하도록 정리해 창 수명 동안 규칙이 남던 문제를 줄였습니다. 권한 건너뛰기 관련 UI/상태 문구를 실제 동작과 맞췄고, OperationModePolicyTests·OperationModeReadinessTests·AgentLoopE2ETests·LlmOperationModeTests를 통해 권한 정책과 사내 모드 차단 회귀를 검증했습니다. dotnet build 경고 0 / 오류 0, 권한 관련 테스트 49건 통과를 확인했습니다.
This commit is contained in:
@@ -99,7 +99,8 @@ public partial class ChatWindow : Window
|
||||
private int _lastRenderedMessageCount;
|
||||
private int _lastRenderedEventCount;
|
||||
private bool _lastRenderedShowHistory;
|
||||
private readonly HashSet<string> _sessionPermissionRules = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<string, HashSet<string>> _sessionPermissionRules = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly object _sessionPermissionRulesLock = new();
|
||||
private readonly Dictionary<string, bool> _sessionMcpEnabledOverrides = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<string, string> _sessionMcpAuthTokens = new(StringComparer.OrdinalIgnoreCase);
|
||||
// 경과 시간 표시
|
||||
@@ -617,14 +618,22 @@ public partial class ChatWindow : Window
|
||||
}), DispatcherPriority.Input);
|
||||
}
|
||||
|
||||
private bool IsPermissionAutoApprovedForSession(string toolName, string target)
|
||||
private bool IsPermissionAutoApprovedForSession(string tab, string toolName, string target)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(toolName) || string.IsNullOrWhiteSpace(target))
|
||||
return false;
|
||||
|
||||
var normalizedTarget = target.Trim();
|
||||
var pathLikeTool = IsPathLikePermissionTool(toolName);
|
||||
foreach (var rule in _sessionPermissionRules)
|
||||
List<string> rulesSnapshot;
|
||||
lock (_sessionPermissionRulesLock)
|
||||
{
|
||||
if (!_sessionPermissionRules.TryGetValue(tab, out var rules) || rules.Count == 0)
|
||||
return false;
|
||||
rulesSnapshot = rules.ToList();
|
||||
}
|
||||
|
||||
foreach (var rule in rulesSnapshot)
|
||||
{
|
||||
var pivot = rule.IndexOf('|');
|
||||
if (pivot <= 0 || pivot >= rule.Length - 1)
|
||||
@@ -675,7 +684,7 @@ public partial class ChatWindow : Window
|
||||
}
|
||||
}
|
||||
|
||||
private void RememberPermissionRuleForSession(string toolName, string target)
|
||||
private void RememberPermissionRuleForSession(string tab, string toolName, string target)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(toolName) || string.IsNullOrWhiteSpace(target))
|
||||
return;
|
||||
@@ -701,7 +710,24 @@ public partial class ChatWindow : Window
|
||||
}
|
||||
}
|
||||
|
||||
_sessionPermissionRules.Add($"{toolName}|{scopedTarget}");
|
||||
lock (_sessionPermissionRulesLock)
|
||||
{
|
||||
if (!_sessionPermissionRules.TryGetValue(tab, out var rules))
|
||||
{
|
||||
rules = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
_sessionPermissionRules[tab] = rules;
|
||||
}
|
||||
|
||||
rules.Add($"{toolName}|{scopedTarget}");
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetPermissionRulesForRun(string tab)
|
||||
{
|
||||
lock (_sessionPermissionRulesLock)
|
||||
{
|
||||
_sessionPermissionRules.Remove(tab);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsPathLikePermissionTool(string toolName)
|
||||
@@ -5845,7 +5871,7 @@ public partial class ChatWindow : Window
|
||||
|
||||
var resolvedTarget = NormalizePermissionTarget(toolName, filePath, wsFolder);
|
||||
|
||||
if (IsPermissionAutoApprovedForSession(toolName, resolvedTarget))
|
||||
if (IsPermissionAutoApprovedForSession(tab, toolName, resolvedTarget))
|
||||
return true;
|
||||
|
||||
PermissionRequestWindow.PermissionPromptResult decision = PermissionRequestWindow.PermissionPromptResult.Reject;
|
||||
@@ -5870,7 +5896,7 @@ public partial class ChatWindow : Window
|
||||
});
|
||||
|
||||
if (decision == PermissionRequestWindow.PermissionPromptResult.AllowForSession)
|
||||
RememberPermissionRuleForSession(toolName, resolvedTarget);
|
||||
RememberPermissionRuleForSession(tab, toolName, resolvedTarget);
|
||||
|
||||
return decision != PermissionRequestWindow.PermissionPromptResult.Reject;
|
||||
},
|
||||
@@ -5893,6 +5919,7 @@ public partial class ChatWindow : Window
|
||||
|
||||
_tabCumulativeInputTokens[runTab] = 0;
|
||||
_tabCumulativeOutputTokens[runTab] = 0;
|
||||
ResetPermissionRulesForRun(runTab);
|
||||
|
||||
var loop = GetAgentLoop(runTab);
|
||||
// 클로저로 runTab 캡처 — 동시에 여러 탭이 실행될 때도 이벤트가 올바른 탭에 귀속됨
|
||||
@@ -5952,6 +5979,7 @@ public partial class ChatWindow : Window
|
||||
}
|
||||
finally
|
||||
{
|
||||
ResetPermissionRulesForRun(runTab);
|
||||
loop.RuntimeWorkFolderOverride = null;
|
||||
loop.EventOccurred -= agentEventHandler;
|
||||
loop.UserDecisionCallback = null;
|
||||
|
||||
Reference in New Issue
Block a user