claw-code permissionSetup 비교 반영: 위험 자동허용 가드 + 회귀 테스트 추가
Some checks failed
Release Gate / gate (push) Has been cancelled

- 전역 권한이 Auto일 때 고위험 도구(process, spawn_agent, snippet_runner)는 자동 허용을 ask로 강등

- AgentContext 권한 계산 경로에 dangerous auto guard를 통합하여 우발적 무승인 실행 방지

- OperationModePolicyTests에 guard 동작 회귀 테스트 2건 추가

- 패리티 문서에 permissionSetup 기반 보강 항목 추가

- 로드맵/패리티 문서 테스트 수치 동기화: 374/374

- 검증: dotnet build 경고0 오류0, ParityBenchmark 12/12, ReplayStability 12/12, 전체 테스트 374/374
This commit is contained in:
2026-04-03 21:07:15 +09:00
parent b30c5f124e
commit 72f307631d
5 changed files with 74 additions and 11 deletions

View File

@@ -36,7 +36,7 @@
## 6. 최신 검증 스냅샷 (2026-04-03)
- `dotnet test --filter "Suite=ParityBenchmark"`: 12/12 통과.
- `dotnet test --filter "Suite=ReplayStability"`: 12/12 통과.
- `dotnet test`: 372/372 통과.
- `dotnet test`: 374/374 통과.
## 7. 권한 Hook 계약 (P2 마감 기준)
- lifecycle hook 키:
@@ -49,3 +49,4 @@
2. 갱신 후 `context.CheckToolPermissionAsync()`로 최종 판정.
3. hook 실패/예외는 non-blocking(권한 흐름 지속).
4. `additionalContext`는 가능한 경로에서 메시지 컨텍스트로 반영.

View File

@@ -40,7 +40,7 @@
## 6. 2026-04-03 점검 스냅샷
- 기준 시점: 2026-04-03.
- 계획 대비 현재 수준: 약 92~95%.
- 테스트 상태: `dotnet test` 372/372 통과.
- 테스트 상태: `dotnet test` 374/374 통과.
- P1 Hook 계약: 구현 완료 수준.
- P2 세션/이벤트 내구성: 구현 완료 수준(복원/재생 경계 케이스 테스트 반영).
- P3 실패 복구 표준화: 구현 완료 수준(unknown-tool/권한/정체/fork 강제 흐름 반영).
@@ -55,7 +55,7 @@
- 레거시 도구명 `process_run` 참조: 0건 (`process`로 정규화).
- 레거시 도구명 `grep_tool` 참조: 0건 (`grep`로 정규화).
- 내부 모드 차단 정책: `http_tool` 전면 차단, `open_external`의 외부 URL 차단.
- 테스트 상태: `dotnet test` 372/372 통과.
- 테스트 상태: `dotnet test` 374/374 통과.
## 8. claw-code 소스 직접 비교 결과 (2026-04-03)
- 비교 기준 소스: `claw-code/.../src/tools.ts`, `src/Tool.ts`, `src/skills/loadSkillsDir.ts`, `src/skills/bundled/*.ts`.
@@ -74,6 +74,7 @@
3. 도구 별칭 정규화: claw-code 기본 도구명군(`WebFetch`, `WebSearch`, `AskUserQuestion`, `LSP`, `ListMcpResourcesTool` 등) AX 내부 도구명으로 매핑 반영 완료.
4. 반영 완료(2026-04-03): `hooks`/`hook_filters` 계약 확장 및 runtime hook 필터링(도구/타이밍 기준) 적용.
5. 반영 완료(2026-04-03): 슬래시 스킬 실행 시 `context/agent/effort/model/disable-model-invocation/allowed-tools/hooks/hook_filters` 메타데이터를 런타임 정책 지시문으로 합성 적용.
6. 반영 완료(2026-04-03): `permissionSetup` 비교 기반으로 위험 자동허용 가드 추가(`process`, `spawn_agent`, `snippet_runner`는 전역 `Auto`에서도 승인 단계 강제).
## 10. 전체 영역 동시 비교 기준 (누락 방지)
1. 도구 계층: 도구 목록, 별칭 정규화, unknown-tool 복구, tool search/선택 정책.
@@ -106,7 +107,7 @@
### 벤치마크 배포 체크리스트 연결
1. `dotnet build` 경고 0/오류 0.
2. `dotnet test` 전체 통과 (`372/372` 기준, 증가 시 최신 값으로 동기화).
2. `dotnet test` 전체 통과 (`374/374` 기준, 증가 시 최신 값으로 동기화).
3. 위 9개 시나리오의 회귀 테스트가 모두 통과.
4. 패리티 수치/상태를 `NEXT_ROADMAP.md`와 동일 문구로 동기화.
5. 릴리즈 전 게이트 스크립트 실행: `powershell -ExecutionPolicy Bypass -File .\scripts\release-gate.ps1`
@@ -149,3 +150,4 @@
2. 갱신 후 `context.CheckToolPermissionAsync()`로 최종 판정.
3. hook 실패/예외는 non-blocking(권한 흐름 지속).
4. `additionalContext`는 가능한 경로에서 메시지 컨텍스트로 반영.

View File

@@ -36,7 +36,7 @@
## 7. 2026-04-03 실행 증적 동기화 (M4 포함)
- 기준 시점: 2026-04-03.
- 테스트: `dotnet test` 372/372 통과.
- 테스트: `dotnet test` 374/374 통과.
- M1 증적: Hook 계약 필드(`updatedInput`, `updatedPermissions`, `additionalContext`) 반영 경로 구현 완료.
- M2 증적: run 복원/이력 재구성(`RestoreRecentFromExecutionEvents`, `RestoreCurrentAgentRun`, plan 이력 조회) 구현 및 테스트 존재.
- M3 증적: unknown-tool 복구 루프/결정 이벤트 처리 경로 구현 및 테스트 존재.
@@ -56,7 +56,7 @@
- 기준 문서: `docs/CLAW_CODE_PARITY_PLAN.md` 13절.
- 테스트 태그: `Suite=ReplayStability`.
- 운영 기준: 릴리즈 전 `ReplayStability` 시나리오 전건 통과 시 replay 불일치 0건으로 판정.
- 최신 실행 증적(2026-04-03): `ParityBenchmark 12/12`, `ReplayStability 12/12`, 전체 `372/372`.
- 최신 실행 증적(2026-04-03): `ParityBenchmark 12/12`, `ReplayStability 12/12`, 전체 `374/374`.
- 실행 자동화: `scripts/release-gate.ps1`로 빌드/벤치마크/리플레이/전체 테스트를 일괄 점검.
## 11. 권한 Hook 계약 고정 (M1 완료 기준)
@@ -70,3 +70,4 @@
2. 반영 후 `CheckToolPermissionAsync()`로 최종 권한 판정 수행.
3. hook 예외/실패는 non-blocking으로 처리하고 권한 흐름은 지속.
4. `additionalContext`는 가능한 경로에서 실행 메시지 컨텍스트에 병합.

View File

@@ -102,4 +102,40 @@ public class OperationModePolicyTests
allowed.Should().BeTrue();
askCalled.Should().BeFalse();
}
[Fact]
public void AgentContext_GetEffectiveToolPermission_DowngradesDangerousAutoToolWhenGlobalAuto()
{
var context = new AgentContext
{
Permission = "Auto",
ToolPermissions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
["process"] = "auto",
}
};
context.GetEffectiveToolPermission("process", "git status").Should().Be("ask");
context.GetEffectiveToolPermission("file_read", @"E:\work\README.md").Should().Be("Auto");
}
[Fact]
public async Task AgentContext_CheckToolPermissionAsync_DangerousAutoToolRequiresPromptInGlobalAuto()
{
var askCalled = false;
var context = new AgentContext
{
OperationMode = OperationModePolicy.ExternalMode,
Permission = "Auto",
AskPermission = (_, _) =>
{
askCalled = true;
return Task.FromResult(false);
}
};
var allowed = await context.CheckToolPermissionAsync("process", "git status");
allowed.Should().BeFalse();
askCalled.Should().BeTrue();
}
}

View File

@@ -92,6 +92,12 @@ public class AgentContext
"process", "build_run", "git_tool", "http_tool", "open_external", "snippet_runner",
"spawn_agent", "test_loop",
};
private static readonly HashSet<string> DangerousAutoTools = new(StringComparer.OrdinalIgnoreCase)
{
"process",
"spawn_agent",
"snippet_runner",
};
private readonly object _permissionLock = new();
private readonly HashSet<string> _approvedPermissionCache = new(StringComparer.OrdinalIgnoreCase);
@@ -191,22 +197,26 @@ public class AgentContext
public string GetEffectiveToolPermission(string toolName, string? target)
{
toolName ??= "";
var normalizedToolName = toolName.Trim();
if (TryResolvePatternPermission(toolName, target, out var patternPermission))
return PermissionModeCatalog.NormalizeToolOverride(patternPermission);
return ApplyDangerousAutoGuard(normalizedToolName, PermissionModeCatalog.NormalizeToolOverride(patternPermission));
if (ToolPermissions.TryGetValue(toolName, out var toolPerm) &&
!string.IsNullOrWhiteSpace(toolPerm))
return PermissionModeCatalog.NormalizeToolOverride(toolPerm);
return ApplyDangerousAutoGuard(normalizedToolName, PermissionModeCatalog.NormalizeToolOverride(toolPerm));
if (ToolPermissions.TryGetValue("*", out var wildcardPerm) &&
!string.IsNullOrWhiteSpace(wildcardPerm))
return PermissionModeCatalog.NormalizeToolOverride(wildcardPerm);
return ApplyDangerousAutoGuard(normalizedToolName, PermissionModeCatalog.NormalizeToolOverride(wildcardPerm));
if (ToolPermissions.TryGetValue("default", out var defaultPerm) &&
!string.IsNullOrWhiteSpace(defaultPerm))
return PermissionModeCatalog.NormalizeToolOverride(defaultPerm);
return ApplyDangerousAutoGuard(normalizedToolName, PermissionModeCatalog.NormalizeToolOverride(defaultPerm));
return SensitiveTools.Contains(toolName)
var fallback = SensitiveTools.Contains(toolName)
? PermissionModeCatalog.NormalizeGlobalMode(Permission)
: PermissionModeCatalog.Auto;
return ApplyDangerousAutoGuard(normalizedToolName, fallback);
}
public async Task<bool> CheckToolPermissionAsync(string toolName, string target)
@@ -297,6 +307,19 @@ public class AgentContext
.Replace("\\?", ".") + "$";
return Regex.IsMatch(input, regex, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
}
private string ApplyDangerousAutoGuard(string toolName, string permission)
{
if (string.IsNullOrWhiteSpace(toolName))
return permission;
if (PermissionModeCatalog.IsAuto(permission)
&& PermissionModeCatalog.IsAuto(Permission)
&& DangerousAutoTools.Contains(toolName))
return "ask";
return permission;
}
}
/// <summary>에이전트 이벤트 (UI 표시용).</summary>