git-ctx
요약. git-ctx: 자율 개선 회차 19회, 릴리즈 9건. 최근 릴리즈 v0.77.11 (자산 2개). 건강 D
- 19회차
- 1프로젝트
- 8배포 준비 완료
- 1릴리즈 진행 중
- 2병합 완료
- 5검토 대기
- 0검증 실패
- 2변경 없음
- 1실행 오류
- $38.53비용
- 2시간 35분에이전트 시간
현황
- 저장소
- https://github.com/hkjang/git-ctx
- 마지막 회차
- 2026-09-10 19:34 KST — • 기타 guarded files, PR open PR #28
- 최근 릴리즈
- v0.77.11 — released · 자산 2개 (이전 v0.77.10: 2개) 전체 릴리즈 →
회차 이력
| 일시 | 프로젝트 | 결과 |
|---|---|---|
| 2026-09-10 19:34 | git-ctx | 검토 대기 guarded files, PR open PR #28 |
| 2026-09-10 12:22 | git-ctx | 검토 대기 review held, PR open PR #27 |
| 2026-09-09 15:32 | git-ctx | 검토 대기 guarded files, PR open PR #26 |
| 2026-09-09 15:21 | git-ctx | 실행 오류 hold: budget |
| 2026-09-09 08:17 | git-ctx | 배포 준비 완료 merged PR #25, released v0.77.10 |
| 2026-09-09 01:06 | git-ctx | 배포 준비 완료 merged PR #24, released v0.77.9 |
| 2026-09-08 20:53 | git-ctx | 배포 준비 완료 resumed: merged PR #23, released v0.77.8 |
| 2026-09-08 20:14 | git-ctx | 배포 준비 완료 merged PR #23, released v0.77.8 |
| 2026-09-08 17:43 | git-ctx | 검토 대기 review held, PR open PR #22 |
| 2026-09-07 01:11 | git-ctx | 검토 대기 guarded files, PR open PR #21 |
| 2026-09-06 14:42 | git-ctx | 릴리즈 진행 중 merged PR #20, released v0.77.7, ASSETS MISSING |
| 2026-09-06 13:50 | git-ctx | 배포 준비 완료 merged PR #19, released v0.77.6 |
| 2026-09-05 02:45 | git-ctx | 배포 준비 완료 merged PR #18, released v0.77.5 |
| 2026-09-04 06:56 | git-ctx | 배포 준비 완료 merged PR #17, released v0.77.4 |
| 2026-09-03 22:44 | git-ctx | 변경 없음 no change |
| 2026-09-03 14:30 | git-ctx | 배포 준비 완료 merged PR #16, released v0.77.3 |
| 2026-09-03 02:40 | git-ctx | 병합 완료 merged PR #15, release missing |
| 2026-09-02 20:32 | git-ctx | 병합 완료 merged PR #14, release missing |
| 2026-09-02 14:10 | git-ctx | 변경 없음 no change |
비용·사용량
| 시각 | 프로젝트 | 단계 | 시간 | 턴 | 비용 | 토큰 입력/출력 | 종료 |
|---|---|---|---|---|---|---|---|
| 19:32 | git-ctx | 개선 | 12분 | 49 | $3.94 | 3.8M / 41K | success |
| 12:22 | git-ctx | review | 3분 | 20 | $1.14 | 805K / 14K | success |
| 12:17 | git-ctx | 개선 | 6분 | 24 | $1.78 | 1.3M / 19K | success |
| 10:29 | git-ctx | 릴리즈 | 8분 | 41 | $2.15 | 2.2M / 14K | success |
| 15:31 | git-ctx | 개선 | 10분 | 43 | $3.19 | 2.9M / 33K | success |
| 07:56 | git-ctx | 릴리즈 | 10분 | 30 | $1.47 | 1.3M / 11K | success |
| 07:42 | git-ctx | review | 3분 | 16 | $0.81 | 408K / 12K | success |
| 07:37 | git-ctx | 개선 | 7분 | 28 | $1.89 | 1.4M / 21K | success |
| 00:45 | git-ctx | 릴리즈 | 7분 | 23 | $1.24 | 913K / 10K | success |
| 00:35 | git-ctx | review | 4분 | 22 | $1.44 | 997K / 17K | success |
| 00:29 | git-ctx | 개선 | 10분 | 30 | $2.10 | 1.7M / 24K | success |
| 20:53 | git-ctx | 릴리즈 | 3분 | 14 | $0.71 | 343K / 6K | success |
| 19:53 | git-ctx | 릴리즈 | 8분 | 28 | $1.55 | 1.3M / 12K | success |
| 19:42 | git-ctx | review | 4분 | 17 | $0.98 | 450K / 16K | success |
| 19:36 | git-ctx | 개선 | 6분 | 18 | $1.35 | 767K / 19K | success |
| 17:43 | git-ctx | review | 5분 | 16 | $1.11 | 593K / 18K | success |
| 17:37 | git-ctx | 개선 | 7분 | 22 | $1.56 | 847K / 23K | success |
| 01:10 | git-ctx | 개선 | 10분 | 49 | $2.57 | 2.3M / 27K | success |
| 14:20 | git-ctx | 릴리즈 | 5분 | 16 | $0.82 | 494K / 6K | success |
| 14:11 | git-ctx | review | 3분 | 9 | $0.58 | 299K / 5K | success |
| 14:06 | git-ctx | 개선 | 6분 | 27 | $1.64 | 1.3M / 15K | success |
| 13:30 | git-ctx | 릴리즈 | 4분 | 19 | $1.08 | 779K / 7K | success |
| 13:22 | git-ctx | review | 3분 | 14 | $1.10 | 679K / 12K | success |
| 13:18 | git-ctx | 개선 | 8분 | 32 | $2.32 | 1.8M / 25K | success |
아이디어 백로그 — 대기 11 / 전체 12
| 아이디어 | 가치/위험/크기 | 상태 | 메모 | 갱신 |
|---|---|---|---|---|
| Gradle 버전 카탈로그(gradle/libs.versions.toml)를 매니페스트로 인식하지 않음 | 4/3/M | 대기 | 현행 Gradle·Android 프로젝트의 기본 선언 자리. 다만 Gradle 파서를 넓히는 PR(214bf01)이 2026-09-08 사람에게 반려됐고 지금도 main에 없다(parseGradle은 여전히 좁은 형태만 읽는다). 반려 사유를 확인하기 전에는 같은 영역을 다시 넓히지 말 것. | 2026-09-10 |
| authorizationHeaderRE가 스킴과 값 사이의 줄바꿈을 공백 하나로 되써 줄을 먹음 | 3/1/S | 대기 | 직접 확인: "Authorization: Bearer\n abcdef...\nAccept: */*\n"이 3줄에서 2줄로 줄어든다. (bearer|basic|token) 뒤의 \s+가 캡처되지 않고 치환문이 '${1}${2} [REDACTED]'라서 그렇다. 이름 앞쪽 구분자는 이미 캡처돼 안전하므로, netrc 규칙과 같이 \s+를 캡처해 되쓰면 끝. TestMaskingNeverChangesTheLineCount가 지키려는 불변식의 마지막 남은 구멍. | 2026-09-10 |
| parsePOM이 <dependencyManagement> 선언까지 실제 의존성으로 집계 | 3/3/M | 대기 | BOM만 정의한 부모 POM이 허위 양성. 다만 부모 POM의 핀이 유일한 버전 근거인 멀티모듈 저장소에서는 참 양성을 잃을 위험이 있어 신중히. | 2026-09-10 |
| Sanitize의 finding이 가장 심각한 규칙이 아니라 마지막에 실행된 규칙을 보고 | 2/1/S | 대기 | 직접 확인: "AUTH_TOKEN=...\nvalue AKIA..."의 finding이 cloud_access_key 하나다. finding 변수를 규칙마다 덮어쓰기 때문. index_security_events에는 한 파일에서 실제로 걸린 종류가 하나만 남아 탐지 경로가 과소보고된다. 심각도 순위를 두거나 마지막이 아니라 첫 매치를 남기면 된다. | 2026-09-10 |
| mcp.filterLibraries / cacheKey가 호출자 슬라이스를 제자리 변경 | 2/1/S | 대기 | items[:0] 재사용과 sort.Strings(p.ACLPrincipals)가 컨텍스트에 담긴 슬라이스를 변형한다. 지금은 안전하지만 캐시된 슬라이스가 들어오면 ACL 오염 위험. | 2026-09-10 |
| 인덱스 작업 warning이 매니페스트 경고를 앞 3건만 join하고 candidates == 0일 때 덮어씀 | 2/1/S | 대기 | indexer.go 849-867. 다만 매니페스트 경고를 늘리는 방향의 PR(fbcff94)이 2026-09-07 반려된 이력이 있어 같은 접근은 피할 것. | 2026-09-10 |
| pipenv의 매니페스트 Pipfile([packages]·[dev-packages])이 인식되지 않음 | 2/1/S | 대기 | Pipfile.lock을 읽는 470ee9f는 아직 main에 없다(미머지 브랜치). 그것이 머지되면 해결된 버전은 확보되므로 남는 것은 범위 선언과 dev 구분뿐이고, '*' 같은 무의미한 버전이 대부분이라 값이 낮다. | 2026-09-10 |
| clampResponse의 truncation notice 예약치(320B) 부족 | 2/2/S | 대기 | internal/mcp/budget.go:70 responseNoticeBytes = 320 그대로. 실제 공지는 약 329B + 코드펜스라 '예산에 맞춰 잘랐다'면서 예산을 십수 바이트 넘긴다. TestATruncatedAnswerUsesTheBudgetItWasGiven도 budget+320까지 허용해 이 초과를 보지 못한다. 공지를 먼저 만들고 그 길이로 room을 계산하면 정확해진다. | 2026-09-10 |
| package.json의 overrides·resolutions·workspaces가 인벤토리에 반영되지 않음 | 2/2/M | 대기 | 모노레포 루트 package.json은 의존성을 거의 갖지 않고, overrides/resolutions가 취약 전이 의존성을 실제로 고정하는 자리인데 parsePackageJSON은 네 테이블만 읽는다. | 2026-09-10 |
| dependencyScanLimit(2000) 절단 시 어떤 버전 그룹이 잘렸는지 notes가 말하지 않음 | 2/2/S | 대기 | 2026-09-06 회차에서 통지 조건 자체는 고쳐졌으나, 여전히 '어느 그룹이 불완전한지'는 말하지 않는다. | 2026-09-10 |
| pyproject의 [project] dynamic·[tool.uv] 의존성 소스는 읽지 않음 | 2/2/M | 대기 | dynamic = ["dependencies"]는 의존성이 setup.py·requirements 파일에 있다는 표시. 요구사항 파일 인식 범위가 넓어져 그 저장소 상당수는 실제 파일 쪽으로 채워지므로 남는 것은 '이 매니페스트는 의존성을 다른 곳에 둔다'는 note뿐. | 2026-09-10 |
| YAML 블록 스칼라(password: |)의 본문이 어떤 마스킹 규칙에도 닿지 않음 | 3/2/M | 완료 | 이번 회차 구현(commit 9b9ea38). 키 다음 줄부터 들여쓰기가 깊은 줄을 블록 본문으로 보고 줄마다 [REDACTED]로 치환해 줄 수를 보존. 인디케이터가 줄 끝이어야 매치해 'token: > 5'와 Markdown 표는 제외. Revision()에 새 패턴을 넣어 기존 ref는 자동 재색인. | 2026-09-10 |
교훈 (깨졌던 변경)
- 2026-09-08 rejected-by-human — 사람이 PR 을 반려함. 같은 접근은 피할 것. (링크)
- 2026-09-07 rejected-by-human — 사람이 PR 을 반려함. 같은 접근은 피할 것. (링크)
원장 (에이전트가 남긴 기록)
2026-09-02
- 선택: MCP 도구 인자의 JSON 타입 불일치 처리 — 따옴표 친 숫자·문자열 리스트를 읽고, 읽은 방식을 응답에 기록 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공 (commit b9ffcac)
- 요약:
additionalProperties:false위반(오탈자 인자)은 이미 응답에 기록되고 있었지만, 이름은 맞고 타입만 다른 인자는 조용히 버려지고 기본값으로 응답했습니다."startLine":"400"을 보낸 read-file은 파일 전체를 예산까지 잘라 돌려주면서 무시한 줄 범위를 언급하지 않았고, export-context는 배열이 아닌 단일 문자열 libraryIds를 거부했으며, 숫자로 온 query는 ““로 검색됐습니다.internal/mcp/args.go의stringArg/intArg/stringSliceArg가 선언된 타입을 철자하는 값을 그 타입으로 읽도록 하고(float→int 변환 범위도 정의되게 제한),internal/mcp/dispatch.go에 기존unknownArgumentNote옆에argumentTypeNote를 추가해 “read as declared / ignored”를 응답에 남깁니다. 검증: 새 테스트internal/mcp/argument_type_test.go4건(read-file 줄 범위 동등성, export-context 문자열 리스트, 해석 불가 값 보고, 디코딩 단위표) +gofmt -l,go vet ./...,go build -tags sqlite_fts5 ./...,go test -tags sqlite_fts5 ./...전체 통과,-race는 mcp·search 패키지에서 통과. - 보류 아이디어: (1)
clampResponse가 “예산에 맞춰 잘랐다”고 말하면서 실제로는 예산을 최대 ~330바이트 초과할 수 있음 (가치 2 / 위험 2 / S). (2)netclient.JoinAPIPath가 base URL이 다른 리소스 경로로 끝날 때 경로를 중복 연결 (가치 2 / 위험 3 / S). (3)netclient.resetHint의 RateLimit-Reset·Retry-After 조합에 대한 테스트 보강 (가치 2 / 위험 1 / S). (4)mcp.filterLibraries가 호출자 슬라이스를items[:0]로 제자리 변경 — 지금은 안전하지만 캐시된 슬라이스가 들어오면 ACL 오염 위험 (가치 2 / 위험 1 / S).
2026-09-03
- 선택: 권고(advisory) 판정용 버전 비교의 세 가지 오판 수정 — 빌드 메타데이터·프리릴리스 숫자 필드·상한 범위 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공 (commit 3431db0)
- 요약:
find-dependency-usage가 “수정 버전 기준 영향·안전·판정 불가”를 나눌 때 쓰는internal/manifest/version.go에 오판 세 건이 있었습니다. (1)v2.0.0+incompatible의 빌드 메타데이터를 프리릴리스로 읽어, 수정 버전에 이미 올라가 있는 Go 모듈 전부가 “영향”으로 보고됐습니다. (2) 프리릴리스 꼬리를 문자열로 비교해rc.9 > rc.10이 되어, rc.10에서 고쳐진 취약점을 rc.9 저장소가 “안전”으로 응답했습니다(허위 안전). (3)requests<3같은 상한 선언을 정확한 핀으로 읽어, 3.0.0에서 고쳐진 건에 대해 도달 불가능한 버전을 근거로 “안전”이라 답했습니다. 각각 메타데이터 선제거, semver 식별자 비교(숫자는 숫자로, 숫자<영숫자, 긴 꼬리 우선), 방향을 가진boundKind(exact/floor/below/atMost) 도입으로 고쳤고 상한은 수정 버전 이하일 때만 “영향”, 그 위로는 “판정 불가”로 둡니다. 검증: 새 테스트 3건(TestBuildMetadataDoesNotOrderARelease,TestPrereleaseFieldsCompareNumerically,TestCeilingResolvesDownwards) 추가 후gofmt -l,go vet ./...,go build -tags sqlite_fts5 ./...,go test -tags sqlite_fts5 ./...전체 통과, manifest·search 패키지는-race도 통과. - 보류 아이디어: (1)
clampResponse가 “예산에 맞춰 잘랐다”면서 실제로는 truncation notice 때문에 예산을 최대 ~330바이트 초과 (가치 2 / 위험 2 / S). (2)netclient.JoinAPIPath가 base URL이 다른 리소스 경로로 끝날 때 경로를 중복 연결 (가치 2 / 위험 3 / S). (3)netclient.resetHint의 RateLimit-Reset·Retry-After 조합 테스트 보강 (가치 2 / 위험 1 / S). (4)mcp.filterLibraries가 호출자 슬라이스를items[:0]로 제자리 변경 — 캐시된 슬라이스가 들어오면 ACL 오염 위험 (가치 2 / 위험 1 / S).
2026-09-03
- 선택: Maven
<exclusions>를 의존성으로 잘못 읽는 매니페스트 파서 수정 (가치 4 / 위험 1 / 작업량 S) - 결과: 성공 (commit 969b85c)
- 요약:
internal/manifest/manifest.go의parsePOM이<dependency>블록 전체에서<groupId>/<artifactId>를 모아 마지막 값으로 덮어썼는데,<exclusions>가 같은 요소 이름을 쓰기 때문에 제외 대상이 의존성 좌표를 이겼습니다. 결과적으로log4j-core를 제외한spring-boot-starter가 인벤토리에org.apache.logging.log4j:log4j-core(버전은 starter의 2.7.18)로 들어가, 권고 상황에서find-dependency-usage가 그 저장소를 “일부러 제거한 라이브러리” 때문에 영향으로 지목하고 실제 의존성은 아예 누락시켰습니다(허위 양성 + 허위 음성 동시 발생). 필드를 읽기 전에<exclusions>...</exclusions>를 제거하도록 고쳤고, 제외 블록 뒤에 오는<optional>은 그대로 읽힙니다. 검증: 새 테스트TestPOMExclusionIsNotADependency(다중 exclusion·다중 dependency·후행 필드) 추가 후gofmt -l,go vet ./...,go build -tags sqlite_fts5 ./...,go test -tags sqlite_fts5 ./...전체 통과, manifest·search 패키지는-race도 통과. - 보류 아이디어: (1)
parsePOM이<dependencyManagement>의 버전 관리용 선언까지 실제 의존성으로 집계 — BOM만 정의한 부모 POM이 권고에 허위 양성으로 걸림 (가치 3 / 위험 3 / M). (2)parseRequirements의 연산자 목록에!=·===가 없어urllib3!=1.25.0의 버전이"!"로 기록됨 (가치 2 / 위험 1 / S). (3)clampResponse가 “예산에 맞춰 잘랐다”면서 truncation notice 예약치(320B)를 실제 공지 길이가 넘겨 예산을 수십 바이트 초과 (가치 2 / 위험 2 / S). (4)mcp.cacheKey가principalACLs로 받은p.ACLPrincipals를sort.Strings로 제자리 정렬 — 컨텍스트에 담긴 슬라이스를 변형 (가치 2 / 위험 1 / S). - 릴리즈: v0.77.3 (2026-09-03)
2026-09-04
- 선택: 이름이 따옴표로 감싸이거나 네임스페이스가 붙은 크리덴셜을 마스킹하도록 수정 (가치 5 / 위험 2 / 작업량 S)
- 결과: 성공 (commit 25bbd46)
- 요약:
internal/contentsecurity/sanitize.go의secretAssignmentRE가 맨 이름만 읽어, JSON·Terraform처럼 키를 따옴표로 쓰는 설정 파일은 이름과 콜론 사이에 닫는 따옴표가 끼면서 규칙이 아예 매치되지 않았습니다. 즉{"password": "hunter22"},"api_key" = "abcd..."는 마스킹 없이 색인되고 스니펫으로 그대로 반환됐습니다(직접 확인). 같은 모양의 공백이xmlSecretElementRE에도 있어, 이 플랫폼이 색인하는 soapUI·WSDL·WebSphere 서술자가 쓰는<con:password>는 이름이<바로 뒤에 오지 않아 전부 놓쳤습니다. 양쪽 따옴표를 선택적으로 만들고(맨 이름은 종전대로 매치), 네임스페이스 접두사를 이름과 함께 캡처해 치환 후에도 여는·닫는 태그가 짝을 유지하도록 했습니다.fields["password"] = lookup(name)처럼 대입이 아닌 인용 이름은 그대로 둡니다.Revision()이 패턴에서 파생되므로 구 규칙으로 색인된 ref는 자동으로 재색인됩니다. 검증: 크리덴셜 shape 테이블에 4건(JSON·공백 있는 인용 키·Terraform·네임스페이스 XML), 과마스킹 방지 테이블에 3건 추가 후gofmt -l,go vet ./...,go build -tags sqlite_fts5 ./...,go test -tags sqlite_fts5 ./...전체 통과(exit 0), contentsecurity·indexer·search는-race도 통과. - 보류 아이디어: (1)
parsePOM이<dependencyManagement>의 버전 관리용 선언까지 실제 의존성으로 집계 — 다만 부모 POM의 핀이 유일한 버전 근거인 멀티모듈 저장소에서 참 양성을 잃을 위험이 있어 신중히 (가치 3 / 위험 3 / M). (2)parseRequirements의 연산자 목록에!=·===가 없어urllib3!=1.25.0의 버전이"!"로 기록됨 (가치 2 / 위험 1 / S). (3)ParseLock의MaxLockPackages4000 절단이 조용해서, 큰 락파일 저장소는 해석된 버전 판정을 말없이 잃음 (가치 3 / 위험 2 / M). (4)clampResponse가 “예산에 맞춰 잘랐다”면서 truncation notice 때문에 예산을 수십 바이트 초과 (가치 2 / 위험 2 / S). - 릴리즈: v0.77.4 (2026-09-04)
2026-09-05
- 선택: 캐럿·틸드 범위의 상한을 읽어, 이미 답이 정해진 권고 판정을 “판정 불가”로 돌려주지 않도록 수정 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공 (commit 7f4f28e)
- 요약:
internal/manifest/version.go의Below가^·~·~=를 하한만 있는 열린 범위로 읽어, 범위 전체가 수정 버전 아래인데도 “매니페스트만으로 결정 불가”로 답했습니다.~2.14.0(2.15.0 미만)에 수정 버전 2.17.1,^1.2.3(2.0.0 미만)에 수정 버전 2.0.0이 모두 그랬는데, 이는 npm·Cargo·PEP 440 매니페스트가 실제로 쓰는 두 가지 표기라서 권고가 뜰 때마다 운영자가 저장소마다 손으로 확인해야 했습니다.parseDeclared를bound{version, kind, ceiling, bounded}를 돌려주도록 바꾸고rangeCeiling을 추가해, 캐럿은 맨 왼쪽 0이 아닌 자리를(^1.2.3→2.0.0,^0.2.3→0.3.0,^0.0.3→0.0.4), 틸드는 major·minor를(~1.2.3·~=1.2.3→1.3.0) 올린 값을 배타적 상한으로 씁니다. 자릿수가 모자라 생태계 해석이 갈리는~1.2·^0.2는 더 넓은 쪽을 택했습니다 — 상한은 “영향” 판정만 만들 수 있어서 너무 좁으면 없는 영향을 지어내기 때문입니다.>=·>같은 열린 하한은 종전대로 수정 버전 아래에서 아무것도 결정하지 않습니다. 검증: 새 테스트TestBoundedRangeBelowTheFixIsAffected(영향 10건·판정 불가 6건·하한 안전 4건) 추가, 기존TestRangeBelowTheFixIsUndecided의 틸드 사례는 이제 실제로 결정 가능하므로 수정 버전이 범위 안에 드는 사례(~2.14.0vs 2.14.5)로 교체.gofmt -l,go vet ./...,go build -tags sqlite_fts5 ./...,go test -tags sqlite_fts5 ./...전체 통과, manifest·search는-race도 통과. - 보류 아이디어: (1)
find-dependency-usage가 이름 substring(LIKE %name%)으로 걸린 다른 패키지의 선언까지 같은 버전 그룹에 넣고 수정 버전 기준으로 판정 —requests권고에requests-toolbelt 0.10.1을 가진 저장소가 “영향”으로 지목됨. 다만log4j질의가…:log4j-core를 찾아야 하는 요구와 충돌해 정확 일치 필터는 허위 음성 위험 (가치 4 / 위험 4 / M). (2)ParseLock의MaxLockPackages4000 절단이 조용하고, go.sum이 알파벳 순이라 뒤쪽 이름부터 통째로 사라짐 (가치 3 / 위험 2 / M). (3)parseRequirements의 연산자 목록에!=·===가 없어urllib3!=1.25.0의 버전이"!"로 기록됨 (가치 2 / 위험 1 / S). (4)clampResponse의 truncation notice 예약치 320B보다 실제 공지가 길어(약 329B+펜스) “예산에 맞춰 잘랐다”면서 예산을 십수 바이트 초과 (가치 2 / 위험 2 / S). - 릴리즈: v0.77.5 (2026-09-05)
2026-09-06
- 선택: 권고 판정을 질의한 패키지의 선언으로만 좁혀, 이름이 부분 일치한 이웃 패키지가 저장소를 “영향”으로 만들지 않도록 수정 (가치 4 / 위험 3 / 작업량 M)
- 결과: 성공 (commit 0e6f282)
- 요약:
internal/search/dependencies.go의 인벤토리 질의는name_lower LIKE %name%로 이름을 부분 일치시키는데(운영자는 좌표가 아니라 라이브러리 통칭을 들고 오므로log4j가org.apache.logging.log4j:log4j-core를 찾아야 함),classifyAgainstFix가 그렇게 걸려 온 모든 선언의 버전을 질의한 패키지의 버전인 양 읽었습니다. 그래서 requests 2.31.0 권고에requests-toolbelt 0.10.1을 쓰는 저장소가 “영향”으로 보고되고 CODEOWNERS 소유자까지 조회돼 통보 대상에 올랐습니다.namesPackage를 추가해 이름 전체가 같거나 이름의 온전한 한 조각(maven group·artifact, 모듈 경로·scoped 패키지의 경로 요소, group의 마지막 점 구분 조각)일 때만 판정하고, 접두사·하이픈 한 단어만 겹치는 이름은 제외하되 Declarations에는 남기고 notes에 제외한 패키지 이름을 적습니다. 권고 없는 질의는 종전대로 전체를 묶습니다. 겸사겸사 판정용 그룹을 limit으로 잘린result.Users가 아니라 조회한 선언 전체에서 만들도록 해, 락파일 우선 재그룹에서 limit을 넘는 저장소가 말없이 빠지던 것도 없앴습니다. 검증: 새 테스트 2건(TestAdvisoryJudgesOnlyTheQueriedPackage,TestAdvisoryStillReachesTheCoordinate) 추가 후gofmt -l,go vet ./...,go build -tags sqlite_fts5 ./...,go test -tags sqlite_fts5 ./...전체 통과, search·mcp는-race도 통과. -
보류 아이디어: (1)
ParseLock의MaxLockPackages4000 절단이 조용하고, go.sum이 알파벳 순이라 뒤쪽 이름부터 통째로 사라짐 (가치 3 / 위험 2 / M). (2)parseRequirements의 연산자 목록에!=·===가 없어urllib3!=1.25.0의 버전이"!"로 기록됨 (가치 2 / 위험 1 / S). (3)clampResponse의 truncation notice 예약치 320B보다 실제 공지가 길어 “예산에 맞춰 잘랐다”면서 예산을 십수 바이트 초과 (가치 2 / 위험 2 / S). (4)dependencyScanLimit(2000) 절단 시 버전 그룹이 불완전한데 notes는 “상위 N건만 확인”이라고만 하고 어떤 그룹이 잘렸는지 말하지 않음 (가치 2 / 위험 2 / S). (5)parsePOM이<dependencyManagement>의 버전 관리용 선언까지 실제 의존성으로 집계 (가치 3 / 위험 3 / M). - 릴리즈: v0.77.6 (2026-09-06, run 2026-09-06-131045-git-ctx-improve)
2026-09-06
- 선택: 권고 판정이 표시 limit에 잘려 일부 저장소를 아무 데도 세지 않던 것 수정, 절단 사실을 정확히 통지 (가치 4 / 위험 2 / 작업량 S)
- 결과: 성공 (commit ca7907a)
- 요약:
internal/search/dependencies.go의 인벤토리 질의는LIMIT min(limit*4, dependencyScanLimit)로 끊는데, 절단 통지는 상수dependencyScanLimit(2000)과 비교하고 있었습니다. 기본 limit 100이면 실제 SQL 한계는 400이라 통지는limit>=500이 아닌 한 절대 뜨지 않았고, 널리 쓰이는 패키지의 권고는 앞쪽 400개 선언만 보고 “영향 N개 · 안전 M개”를 냈습니다. 정렬이 이름→library_id 순이라 뒤쪽 저장소가 통째로 빠지고, 그 저장소들은 영향·안전·판정 불가 어디에도 세지 않아 결과적으로 무사 통보처럼 읽혔습니다.fixedIn이 있으면 판정은 매치 전체를 대변한다는 기존 주석대로dependencyScanLimit까지 스캔하게 하고(표시 limit은 목록만 제한), 통지는 실제 적용된 한계와 비교하도록 고쳤습니다. 권고 중 절단이 실제로 발생하면 “그 뒤 저장소는 집계에 없어 아래 수치가 불완전하다”고 명시합니다. 검증: 새 테스트TestAdvisoryJudgesEveryMatchNotOnlyTheListedOnes(저장소 12개·limit 1: 전부 판정되는지, 목록은 1건인지, 권고 없는 질의는 “상위 4건만 확인했습니다”를 내는지) 추가 — 수정 전에는 affected가 2개만 나와 실패함을 확인.gofmt -l,go vet ./...,go build -tags sqlite_fts5 ./...,go test -tags sqlite_fts5 ./...전체 통과, search·mcp는-race도 통과. -
보류 아이디어: (1)
ParseLock의MaxLockPackages4000 절단이 조용하고 go.sum이 알파벳 순이라 뒤쪽 이름부터 통째로 사라짐 (가치 3 / 위험 2 / M). (2)parseRequirements에!=·===가 없어urllib3!=1.25.0의 버전이"!"로 기록됨 (가치 2 / 위험 1 / S). (3)clampResponse의 truncation notice 예약치 320B보다 실제 공지가 길어 예산을 십수 바이트 초과 (가치 2 / 위험 2 / S). (4)parsePOM이<dependencyManagement>선언까지 실제 의존성으로 집계 (가치 3 / 위험 3 / M). (5)MaxManifestBytes(1MB)·MaxLockBytes(8MB) 초과 파일이nil로 조용히 버려져 인벤토리에서 저장소가 통째로 빠짐 (가치 3 / 위험 2 / M). - 릴리즈: v0.77.7 (2026-09-06, run 2026-09-06-140046-git-ctx-improve)
2026-09-07
- 선택: 의존성 인벤토리가 조용히 버리던 네 곳(매니페스트·락파일 크기 초과, 락파일 4000개 절단, ref당 매니페스트 60개 상한)을 색인 경고로 보고 (가치 3 / 위험 2 / 작업량 M)
- 결과: 성공 (commit fbcff94, 릴리즈 fecee25)
- 요약:
manifest.Parse/ParseLock은MaxManifestBytes(1MB)·MaxLockBytes(8MB)를 넘긴 파일에 아무 표시 없이nil을 돌려주고,MaxLockPackages(4000)를 넘긴 락파일은 앞쪽 4000개만 남기며,indexer의 2차 패스는 크기 초과 파일을 조용히 건너뛰고 ref당 60개 상한에서는break로 남은 것을 세지 않았습니다. 없어지는 것은 해석된 버전 — 권고를 즉답할 수 있는 유일한 근거이고, 인벤토리에 없는 저장소는find-dependency-usage의 답에서 그 라이브러리를 안 쓰는 저장소와 똑같이 보입니다(go.sum은 모듈 경로 순 정렬이라 4000 절단은 알파벳 뒤쪽을 통째로 지웁니다). 한계는 그대로 두고ParseNoted/ParseLockNoted가 읽지 못한 사실을 note로 돌려주게 했고,appendPackages가 그것을 이미 있던manifestWarnings경로로 올립니다.break는 남은 매니페스트를 세는continue로 바꿔 “60개를 다 읽었다”와 “60개를 읽고 40개를 버렸다”를 구분합니다.Parse·ParseLock의 서명과 한계값은 그대로입니다. 검증: 새 테스트 5건(TestOversizedManifestSaysWhatItDropped,TestOversizedLockSaysWhatItDropped,TestTruncatedLockSaysHowManyItDropped,TestAFileReadWholeCarriesNoNote,TestAppendPackagesReportsWhatItCouldNotRead) 추가 후gofmt -l,go vet ./...,go build -tags sqlite_fts5 ./...,go test -tags sqlite_fts5 ./...전체 통과, manifest·indexer·search는-race도 통과. -
보류 아이디어: (1)
parseRequirements에!=·===가 없어urllib3!=1.25.0의 버전이"!"로 기록되고foo===1.0은 빈 버전이 됨 (가치 2 / 위험 1 / S). (2)clampResponse의 truncation notice 예약치 320B보다 실제 공지가 길어 “예산에 맞춰 잘랐다”면서 예산을 십수 바이트 초과 (가치 2 / 위험 2 / S). (3)parsePOM이<dependencyManagement>선언까지 실제 의존성으로 집계 — BOM만 정의한 부모 POM이 허위 양성 (가치 3 / 위험 3 / M). (4)mcp.filterLibraries/cacheKey가 호출자 슬라이스를 제자리 변경 (가치 2 / 위험 1 / S). (5) 인덱스 작업의warning이 매니페스트 경고를 앞 3건만 join하고,candidates == 0일 때는 warning 전체를 덮어써 매니페스트 경고를 잃음 (가치 2 / 위험 1 / S). - 릴리즈: v0.77.8 (2026-09-07, run 2026-09-07-010047-git-ctx-improve)
2026-09-08
- 선택: Gradle 매니페스트 파서가 통째로 버리던 선언 형태들을 읽도록 수정 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공 (commit 214bf01)
- 요약:
internal/manifest/manifest.go의parseGradle은 열거된 7개 configuration에 인용된group:artifact:version문자열이 붙은 한 가지 형태만 매치해서, 실제 빌드 스크립트의 대부분이 의존성 인벤토리에 도달하지 못했습니다. 직접 확인한 8줄짜리 스크립트에서 1줄만 파싱됐습니다 — 옛 Groovy 빌드가 쓰는 맵 표기(implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.14.1')가 통째로 사라졌고, Android·Kotlin 플러그인이 소스셋·빌드 변형마다 생성하는 configuration(androidTestImplementation,debugImplementation,kapt,ksp,testRuntimeOnly,compileOnlyApi, 레거시testCompile)도 전부 빠졌습니다. 그렇게 빠진 저장소는find-dependency-usage의 답에서 그 라이브러리를 안 쓰는 저장소와 구별되지 않아, log4j 같은 권고가 무사 통보로 읽힙니다. configuration을 변형이 생성되는 방식 그대로 base 이름의 접미사로 매치하게 하고, 좌표를 shorthand 문자열 또는 group/name/version 맵 양쪽에서 읽습니다. 맵 분기는 group과 name을 둘 다 요구해서 후행 클로저의exclude group:…, module:…이 의존성으로 들어오지 않게 했습니다(POM 파서가 exclusion을 거르는 것과 같은 이유).project(':shared')·platform(...)·버전 카탈로그 접근자는 종전대로 좌표를 내지 않아 제외됩니다. 덤으로 보간된 버전($log4jVersion)은 변수 이름을 버전으로 기록해 저장소마다 별도 버전 그룹을 만들고 아무것도 판정하지 못했으므로, 다른 파서가 락파일에 번호를 미룰 때와 같이 빈 버전으로 남깁니다. 검증: 새 테스트 2건(TestGradleReadsEveryDeclarationShape8개 선언 전부의 이름·버전·scope,TestGradleLeavesOutWhatIsNotADeclaredLibrary플러그인·리포지터리·project·platform·카탈로그·exclusion 제외 및 보간 버전) 추가 — 수정 전에는 8건 중 1건만 파싱됨을 직접 확인.gofmt -l,go vet ./...,go build -tags sqlite_fts5 ./...,go test -tags sqlite_fts5 ./...전체 통과(exit 0), manifest·search·indexer는-race도 통과. - 보류 아이디어: (1)
parseTOMLDependencies가[dependencies.serde]·[workspace.dependencies]같은 하위 테이블 섹션을 못 읽어 Cargo 워크스페이스의 버전 핀이 통째로 빠짐 (가치 3 / 위험 2 / M). (2)parseRequirements에!=·===가 없어urllib3!=1.25.0의 버전이"!"로 기록됨 (가치 2 / 위험 1 / S). (3)clampResponse의 truncation notice 예약치 320B보다 실제 공지가 길어 예산을 십수 바이트 초과 (가치 2 / 위험 2 / S). (4) 인덱스 작업의warning이 매니페스트 경고를 앞 3건만 join하고candidates == 0일 때 warning 전체를 덮어씀 (가치 2 / 위험 1 / S). (5)parsePOM이<dependencyManagement>선언까지 실제 의존성으로 집계 (가치 3 / 위험 3 / M).
2026-09-08
- 선택: 경로 아래에 선언된 TOML 의존성(Cargo 워크스페이스·서브테이블, Poetry 그룹)을 읽도록 수정 (가치 3 / 위험 2 / 작업량 M)
- 결과: 성공 (commit c8c5f5e)
- 요약:
internal/manifest/manifest.go의parseTOMLDependencies가 섹션 이름을 정확 일치로만 봐서, 의존성을 더 긴 경로 아래에 두는 형태가 전부 버려졌습니다 — 필드가 여럿인 선언이 흔히 쓰는[dependencies.serde], 모노레포가 버전을 한 곳에 모으는[workspace.dependencies], 플랫폼별[target.'cfg(unix)'.dependencies], Poetry 1.2가 dev-dependencies를 대체한[tool.poetry.group.dev.dependencies]. 그런 워크스페이스의 멤버 크레이트는serde.workspace = true로 선언하는데, 이것이 이름serde.workspace·버전 없음인 가짜 패키지로 인벤토리에 들어가서, 그 저장소는find-dependency-usage가 보기에 serde를 안 쓰는 저장소와 똑같아지고 유일한 핀은 아무도 읽지 않는 파일에 남았습니다. 의존성 테이블보다 경로가 한 칸 긴 섹션은 그 한 패키지로, 점이 든 키는 그 키가 가리키는 패키지의 필드로 읽게 하고(serde.version만 버전을 정함), 생태계별 섹션 판정을cargoScope/pyProjectScope함수로 옮겼습니다. 검증: 새 테스트TestTOMLDependenciesStatedUnderAPath(Cargo 6건 — workspace·dotted key·서브테이블·target·dev 서브테이블, 필드가 패키지로 새지 않는지 6건 확인 / Poetry 4건 — 서브테이블·dev 그룹·test 그룹) 추가 — 수정 전 실패를 직접 확인.gofmt -l,go vet ./...,go build -tags sqlite_fts5 ./...,go test -tags sqlite_fts5 ./...전체 통과, manifest·search·indexer는-race도 통과. -
보류 아이디어: (1)
parseRequirements에!=·===가 없어urllib3!=1.25.0의 버전이"!"로 기록되고foo===1.0은 빈 버전 (가치 2 / 위험 1 / S). (2)clampResponse의 truncation notice 예약치 320B보다 실제 공지가 길어 예산을 십수 바이트 초과 (가치 2 / 위험 2 / S). (3) 인덱스 작업의warning이 매니페스트 경고를 앞 3건만 join하고candidates == 0일 때 덮어씀 (가치 2 / 위험 1 / S). (4)parsePOM이<dependencyManagement>선언까지 실제 의존성으로 집계 (가치 3 / 위험 3 / M). (5) TOML 여러 줄 인라인 테이블(tokio = { version = "1",\n features = [...] })의 이어지는 줄이features라는 패키지로 들어감 (가치 2 / 위험 1 / S). - 릴리즈: v0.77.8 (2026-09-08, run 2026-09-08-193107-git-ctx-improve)
2026-09-09
- 선택: pyproject가 선언하는 모든 의존성 배열을 읽도록 수정, 겸해 빠져 있던 PEP 440 연산자 두 개 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공 (commit c1769db, b03d267)
- 요약:
internal/manifest/manifest.go의parsePyProject는[project]의 PEP 621 배열 하나만 읽었고, 그 하나조차dependencies\s*=\s*\[(.*?)\]로 첫]까지만 잘라서"black[jupyter]>=23.0"처럼 extra를 쓰는 요구사항이 문자열 한가운데서 배열을 끊었습니다 — 직접 확인한 결과 그 뒤의 requests·urllib3가 통째로 사라졌습니다. 읽히지 않던 나머지 배열([project.optional-dependencies], PEP 735[dependency-groups],[tool.pdm.dev-dependencies])은 setuptools·hatch·PDM 프로젝트가 개발·테스트 도구를 두는 표준 자리라, 거기에 라이브러리를 핀한 저장소는find-dependency-usage가 보기에 그 라이브러리를 안 쓰는 저장소와 똑같았습니다. 인용·중첩·주석을 추적하는 스캐너로 배열을 읽어 요구사항 안의 대괄호가 요구사항의 일부로 남게 하고, 섹션 이름이 말하는 대로 scope를 매깁니다(extra=optional, test 그룹=test, 나머지=dev). 섹션 순회는tomlSections로 뽑아parseTOMLDependencies와 공유합니다. 두 번째 커밋은 요구사항 정규식에!=·===를 더해,urllib3!=1.25.0이 버전"!"로 기록되던 것(비교 불가능한 가짜 버전 그룹)을 빈 버전으로, 정확 핀인pip===23.3.1이 버전 없음으로 읽히던 것을 핀으로 고칩니다. 검증: 새 테스트 2건(TestPyProjectReadsEveryDependencyArray— 배열 4종 9개 선언의 이름·버전·scope,[build-system] requires·classifiers가 새지 않는지 /TestRequirementOperatorsThatWereMissing— 4개 형태와===핀의Comparable) 추가, 수정 전 실패를 직접 확인.gofmt -l,go vet ./...,go build -tags sqlite_fts5 ./...,go test -tags sqlite_fts5 ./...전체 통과, manifest·search·indexer는-race도 통과. -
보류 아이디어: (1)
clampResponse의 truncation notice 예약치 320B보다 실제 공지가 길어 예산을 십수 바이트 초과 (가치 2 / 위험 2 / S). (2) 인덱스 작업의warning이 매니페스트 경고를 앞 3건만 join하고candidates == 0일 때 덮어씀 (가치 2 / 위험 1 / S). (3)parsePOM이<dependencyManagement>선언까지 실제 의존성으로 집계 (가치 3 / 위험 3 / M). (4) TOML 여러 줄 인라인 테이블의 이어지는 줄이features라는 패키지로 들어감 (가치 2 / 위험 1 / S). (5)mcp.filterLibraries/cacheKey가 호출자 슬라이스를 제자리 변경 (가치 2 / 위험 1 / S). - 릴리즈: v0.77.9 (2026-09-09, run 2026-09-09-002109-git-ctx-improve)
2026-09-09
- 선택: 프로젝트가 실제로 쓰는 이름의 pip 요구사항 파일을 읽도록 수정 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공 (commit 894a868)
- 요약:
internal/manifest/manifest.go의Recognize가requirements.txt·requirements-dev.txt두 이름만 정확 일치로 봐서, 파이썬 프로젝트가 보통 갖는 형태가 통째로 인벤토리에 도달하지 못했습니다 — pip-tools 쌍(requirements.in→requirements.txt), 접두사·접미사로 갈라 쓰는 이름(requirements_test.txt,dev-requirements.txt), 그리고 대다수 Django 프로젝트가 출발점으로 삼는requirements/base.txt·requirements/prod.in레이아웃. 핀이 읽히지 않은 저장소는find-dependency-usage가 보기에 그 라이브러리를 안 쓰는 저장소와 구별되지 않아 권고가 무사 통보로 읽힙니다. 이름 규칙(requirements접두/접미,.txt·.in,requirements/디렉터리 아래)으로 인식하고, 파일 이름이dev·test를 말하면 그 scope로 기록합니다. 요구사항이 사는 자리의.txt를 읽게 되면 산문도 파서에 닿으므로, 연산자 없이 이름 뒤에 맨 단어가 오면 버전으로 읽던 것(This directory contains→ 패키지 “This” 버전 “directory”, 수정 전 실제 출력 확인)을 건너뛰게 했습니다. 검증: 새 테스트 3건(TestRequirementsFilesTheWayProjectsNameThem인식 10건·비인식 4건,TestRequirementsScopeComesFromTheFileName7건,TestProseIsNotARequirement) 추가하고 수정 전 실패를 직접 확인.gofmt -l,go vet ./...,go build -tags sqlite_fts5 ./...,go test -tags sqlite_fts5 ./...전체 통과, manifest·indexer·search는-race도 통과. docs/configuration.md의 지원 매니페스트 설명도 갱신. -
보류 아이디어: TOML 여러 줄 인라인 테이블의 이어지는 줄이
version·features라는 가짜 패키지가 되고 진짜 크레이트는 버전을 잃음 (가치 3 / 위험 1 / S). package.json의overrides·resolutions가 인벤토리에 없음 — 취약 전이 의존성을 실제로 고정하는 자리 (가치 2 / 위험 2 / M).clampResponse의 truncation notice 예약치 320B보다 실제 공지가 길어 예산을 십수 바이트 초과 (가치 2 / 위험 2 / S). 인덱스 작업warning이 매니페스트 경고를 앞 3건만 join하고candidates == 0일 때 덮어씀 (가치 2 / 위험 1 / S).parsePOM이<dependencyManagement>선언까지 실제 의존성으로 집계 (가치 3 / 위험 3 / M). - 릴리즈: v0.77.10 (2026-09-09, run 2026-09-09-073109-git-ctx-improve)
2026-09-09
- 선택: 마스킹 규칙이 시작한 줄을 넘지 않도록 수정, 겸해 놓치던 자격증명 두 형태 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공 (commit 49b72bf)
- 요약:
internal/contentsecurity/sanitize.go의secretAssignmentRE가 이름과 값 사이를\s로 이어서 줄바꿈을 넘어갔습니다. YAML에서는secret:·token:이 평범한 섹션 헤더라 이 일이 상시로 벌어집니다 — 쿠버네티스 볼륨의secret:\n secretName: db-creds가secret: [REDACTED] db-creds로 나와, 키를 가리고 값은 남기고, 자격증명이 없는 매니페스트에 보안 이벤트를 올렸습니다(수정 전 출력 직접 확인).netrcRE는 반대쪽 절반으로, 실제 .netrc는 필드를 줄마다 쓰는데 매치 전체를 고정 문자열로 되써서 세 줄을 한 줄로 합쳤습니다. 둘 다 줄바꿈을 지웠고, 청크는 마스킹된 내용에서 잘려 그 줄 번호와 함께 저장되므로(indexer.parse), 지워진 줄 뒤로는 파일 끝까지 모든 줄 번호가 실제 파일의 다른 줄을 가리켰습니다. 대입 규칙은[^\S\r\n]*로 줄 안에 머물게 하고, .netrc는 구분자를 캡처해 그대로 되쓰게 했습니다. 덤으로 못 보던 두 형태 —-----BEGIN PGP PRIVATE KEY BLOCK-----(PGP만 “PRIVATE KEY” 뒤로 이어져서, 내보낸 비밀키가 유일하게 통째로 색인되던 형식)과 사용자 이름 없는 URL(redis://:s3cr3t@cache:6379, AMQP 동일 — 콜론 앞에 한 글자를 요구해서 그 줄의 유일한 자격증명만 매치되지 않았음)을 읽습니다.Revision()이 패턴에서 파생되므로 구 규칙으로 색인된 ref는 자동 재색인됩니다. 검증: 새 테스트TestMaskingNeverChangesTheLineCount(7개 다중행 입력의 줄 수 보존), 크리덴셜 shape 테이블에 Redis·AMQP 2건, PEM 헤더 목록에 PGP 1건, 과마스킹 방지 목록에 3건(k8s secret 볼륨,token:섹션, 산문) 추가 — 수정 전 실패를 직접 확인.gofmt -l,go vet ./...,go build -tags sqlite_fts5 ./...,go test -tags sqlite_fts5 ./...전체 통과(exit 0), contentsecurity·indexer·search는-race도 통과. -
보류 아이디어: package.json의
overrides·resolutions가 인벤토리에 없음 — 취약 전이 의존성을 실제로 고정하는 자리 (가치 2 / 위험 2 / M).clampResponse의 truncation notice 예약치 320B보다 실제 공지가 길어 예산을 십수 바이트 초과 (가치 2 / 위험 2 / S).parsePOM이<dependencyManagement>선언까지 실제 의존성으로 집계 (가치 3 / 위험 3 / M).mcp.filterLibraries/cacheKey가 호출자 슬라이스를 제자리 변경 (가치 2 / 위험 1 / S). YAML 블록 스칼라(password: |)의 값은 어떤 규칙도 닿지 않음 — 줄 수를 지키며 가리려면 여러 줄을 줄 단위로 치환해야 함 (가치 3 / 위험 2 / M). - 릴리즈: v0.77.11 (2026-09-10, run 2026-09-10-102027-git-ctx-approve)
2026-09-10
- 선택: 파이썬 프로젝트가 실제로 갖는 락파일(uv.lock·pdm.lock·Pipfile.lock)을 읽도록 수정 (가치 4 / 위험 1 / 작업량 M)
- 결과: 성공 (commit 470ee9f)
- 요약:
internal/manifest/lockfile.go의RecognizeLock이 파이썬 락파일로poetry.lock하나만 알고 있어서, uv·PDM·pipenv로 해석하는 저장소는 인벤토리에pyproject.toml의 범위(>=2.31,~=4.2)만 남았습니다 — 범위는 권고 판정이 결정하지 못하는 쪽이고, 해결된 버전은 즉답할 수 있는 유일한 근거인데 그 파일이 아예 인식되지 않아 읽히지 않았습니다. uv와 PDM은 Cargo·Poetry와 같은[[package]]블록을 쓰므로 기존parseTOMLLock으로 보냈고(이름·버전이 열 0에 있어 uv 블록의requires-dist·[package.metadata]의 중첩name =은 들어오지 않습니다), JSON인Pipfile.lock은 별도 리더를 붙였습니다 —_meta는 패키지 집합이 아니고 값의 모양도 달라서 문서 전체를 한 map으로 디코드하면 그 키 하나 때문에 파일이 통째로 버려지므로default·develop만 이름으로 읽습니다. pipenv는 핀을 설치할 요구사항 형태(==2.31.0)로 저장해 다른 락파일이 쓰는 숫자와 다른 버전 그룹이 되므로 연산자를 떼고, VCS ref로 고정된 패키지는 버전이 없으므로 제외합니다. 검증: 새 테스트 2건(TestThePythonLockFilesAProjectActuallyHas— 세 형식의 인식·해결 버전·certifi/_meta/VCS 항목 비유입,TestPipfileLockStatesTheVersionAlone) 추가하고 수정 전 실패를 직접 확인.gofmt -l,go vet ./...,go build -tags sqlite_fts5 ./...,go test -tags sqlite_fts5 ./...전체 통과(exit 0), manifest·indexer·search는-race도 통과. docs/configuration.md의 지원 락파일 목록도 갱신. - 보류 아이디어: pipenv의 매니페스트
Pipfile([packages]·[dev-packages] TOML)은 여전히 인식되지 않아 범위 선언이 인벤토리에 없음 (가치 2 / 위험 1 / S).clampResponse의 truncation notice 예약치 320B보다 실제 공지가 길어 예산을 십수 바이트 초과 (가치 2 / 위험 2 / S).parsePOM이<dependencyManagement>선언까지 실제 의존성으로 집계 (가치 3 / 위험 3 / M). YAML 블록 스칼라(password: |)의 값은 어떤 마스킹 규칙도 닿지 않음 (가치 3 / 위험 2 / M).mcp.filterLibraries/cacheKey가 호출자 슬라이스를 제자리 변경 (가치 2 / 위험 1 / S).
2026-09-10
- 선택: YAML 블록 스칼라가 다음 줄부터 말하는 자격증명을 마스킹 (가치 3 / 위험 2 / 작업량 M)
- 결과: 성공 (commit 9b9ea38)
- 요약:
internal/contentsecurity/sanitize.go의 어떤 규칙도 블록 스칼라의 본문에 닿지 못했습니다 —password: |는 대입 규칙이 요구하는 네 글자 대신 콜론 뒤에 한 글자만 두고, 값은 그 다음 줄들에 있습니다. 수정 전Sanitize를 직접 돌려stringData:\n password: |\n S0me-P@ssword가 finding 없이 원문 그대로 나오는 것을 확인했습니다. 키·인증서는 privateKeyRE가 파일째 막고 긴 난수 토큰은 엔트로피 규칙이 잡으므로, 이 형식으로 남아 있던 것은 정확히 평문 비밀번호와 짧은 토큰이며 그것이 색인되고 스니펫으로 되돌아왔습니다(쿠버네티스·Helm values, Ansible vars, GitLab CI variables, compose가 한 줄에 담기지 않거나 따옴표를 피하고 싶은 값을 쓰는 방식입니다). 이제 본문을 YAML이 정하는 대로 ‘키보다 깊게 들여쓴 줄의 연속’으로 보고, 들여쓰기가 얕아지는 줄에서 블록을 벗어나며, 각 줄을 들여쓰기를 지킨 채 따로 치환합니다 — 청크는 마스킹된 내용에서 잘려 그 줄 번호와 함께 저장되므로 블록을[REDACTED]하나로 접으면 그 파일의 이후 모든 줄 번호가 어긋납니다(TestMaskingNeverChangesTheLineCount가 지키는 불변식). 인디케이터(|·>·chomping·들여쓰기 숫자)가 줄 끝이어야 매치하므로 템플릿 언어의token: > 5와 파이프로 시작하는 Markdown 표는 제외되고, 필드 이름 목록은 대입 규칙과 하나로 공유합니다.Revision()이 패턴에서 파생되므로 구 규칙으로 색인된 ref는 자동 재색인됩니다. 검증: 새 테스트TestABlockScalarStatesItsValueOnTheFollowingLines(본문 3줄 마스킹·블록 밖 보존·줄당 1건·빈 줄과 들여쓰기 보존), 크리덴셜 shape 2건(블록 스칼라, 리스트 항목 아래 folded>-), 과마스킹 방지 5건(script:·description:·Markdown 표·token: > 5·본문 없는 블록), 줄 수 보존 2건(빈 줄 포함 블록, CRLF|2) 추가 — 수정 전 실패를 직접 확인.gofmt -l,go vet ./...,go build -tags sqlite_fts5 ./...,go test -tags sqlite_fts5 ./...전체 통과(exit 0), contentsecurity·indexer·search는-race도 통과. docs/operations.md의 데이터 보호 절에 이 규칙을 적었습니다. - 보류 아이디어:
authorizationHeaderRE가 스킴과 값 사이의 줄바꿈을 공백 하나로 되써 3줄이 2줄이 됨 — 직접 확인, 줄 수 불변식의 마지막 구멍 (가치 3 / 위험 1 / S).Sanitize의 finding이 가장 심각한 규칙이 아니라 마지막에 실행된 규칙을 보고해 security event가 과소보고됨 — 직접 확인 (가치 2 / 위험 1 / S).clampResponse의 truncation notice 예약치 320B보다 실제 공지가 길어 예산을 십수 바이트 초과 (가치 2 / 위험 2 / S).parsePOM이<dependencyManagement>선언까지 실제 의존성으로 집계 (가치 3 / 위험 3 / M). Gradle 버전 카탈로그(gradle/libs.versions.toml)를 매니페스트로 인식하지 않음 — 다만 Gradle 파서를 넓히는 PR(214bf01)이 반려된 이력이 있어 사유 확인 전에는 손대지 말 것 (가치 4 / 위험 3 / M).