자율 개선 일일 보고 — 2026-09-05
요약. 2026-09-05에 자율 개선 에이전트가 26개 프로젝트에서 36회차를 돌려 21건을 릴리즈하고 6건은 머지만 했으며 1건은 변경이 없었고 실패는 0건이다. 에이전트 시간 2시간 1분, 추정 비용 $50.01. 릴리즈: appstore v2.5.1, dataworks v0.9.42, git-ctx v0.77.5, igame v0.7.6, jikim v0.2.2, jupiq v1.4.4, kanpic v0.236.0, moina v0.1.21….
- 36회차
- 26프로젝트
- 16배포 준비 완료
- 5릴리즈 진행 중
- 6병합 완료
- 4검토 대기
- 2검증 실패
- 1변경 없음
- 2실행 오류
- $50.01비용
- 2시간 1분에이전트 시간
회차
| 시각 | 프로젝트 | 결과 |
|---|---|---|
| 00:10 | aiportal-front-admin | 병합 완료 merged PR #8, release skipped |
| 00:31 | aiportal-front | 병합 완료 merged PR #4, release skipped |
| 00:51 | aiportal-java | 병합 완료 merged PR #9, release skipped |
| 01:10 | aiportal-py | 병합 완료 merged PR #6, release skipped |
| 01:43 | appstore | 배포 준비 완료 merged PR #5, released v2.5.1 |
| 02:05 | dataworks | 배포 준비 완료 merged PR #7, released v0.9.42 +1 assets |
| 02:45 | git-ctx | 배포 준비 완료 merged PR #18, released v0.77.5 |
| 03:23 | igame | 릴리즈 진행 중 merged PR #7, released v0.7.6, ASSETS MISSING |
| 03:52 | jikim | 배포 준비 완료 merged PR #14, released v0.2.2 |
| 04:17 | jupiq | 배포 준비 완료 merged PR #3, released v1.4.4 |
| 04:35 | kanpic | 배포 준비 완료 merged PR #9, released v0.236.0 |
| 05:15 | moina | 릴리즈 진행 중 merged PR #7, released v0.1.21, ASSETS MISSING |
| 05:43 | muni | 배포 준비 완료 merged PR #6, released v0.27.0 |
| 06:02 | pii-masker | 배포 준비 완료 merged PR #8, released v1.0.11 +1 assets |
| 06:30 | ptium | 배포 준비 완료 merged PR #9, released v1.69.27 +7 assets |
| 06:55 | releasedock | 배포 준비 완료 merged PR #8, released v0.5.8 |
| 07:33 | relio | 릴리즈 진행 중 merged PR #9, released v1.11.16, ASSETS MISSING |
| 08:12 | relio | 릴리즈 진행 중 merged PR #10, released v1.11.17, ASSETS MISSING |
| 08:43 | umm | 배포 준비 완료 merged PR #144, released v0.71.2 |
| 09:12 | vibe-coders | 배포 준비 완료 merged PR #14, released v0.83.0 +7 assets |
| 09:39 | visitflow | 배포 준비 완료 merged PR #8, released v2.6.5 |
| 10:00 | weekly | 변경 없음 no change |
| 10:46 | AgentHub | 릴리즈 진행 중 merged PR #9, released v0.234.0, ASSETS MISSING |
| 11:08 | Clustara | 배포 준비 완료 merged PR #9, released v0.9.270 +3 assets |
| 11:20 | Invenqor | 검토 대기 guarded files, PR open PR #9 |
| 11:44 | Quantoss | 병합 완료 merged PR #11, release skipped |
| 16:24 | pii-masker | 검토 대기 CI no-ci, PR open PR #9 |
| 16:44 | AgentHub | 검토 대기 guarded files, PR open PR #10 |
| 16:47 | pii-masker | 배포 준비 완료 release-only, released v1.0.12 |
| 16:57 | AgentHub | 실행 오류 error: interrupted before PR |
| 16:57 | pii-masker | 병합 완료 resumed: merged PR #9 |
| 17:13 | Clustara | 배포 준비 완료 merged PR #10, released v0.9.271 |
| 17:27 | Invenqor | 검증 실패 verify failed: 실패한 검증: cargo test --quiet (exit 127) |
| 17:43 | Quantoss | 검토 대기 CI no-ci, PR open PR #12 |
| 18:02 | ReSSO | 검증 실패 verify failed: secrets in diff |
| 22:20 | (runner) | 실행 오류 daily cap reached: 회차 60 ≥ 60 |
비용·사용량
| 시각 | 프로젝트 | 단계 | 시간 | 턴 | 비용 | 토큰 입력/출력 | 종료 |
|---|---|---|---|---|---|---|---|
| 10:19 | AgentHub | 개선 | 9분 | 59 | $3.77 | 3.9M / 38K | success |
| 10:26 | AgentHub | 릴리즈 | 2분 | 18 | $0.75 | 559K / 5K | success |
| 11:00 | Clustara | 개선 | 10분 | 55 | $4.12 | 4.2M / 36K | success |
| 11:08 | Clustara | 릴리즈 | 3분 | 25 | $0.97 | 710K / 9K | success |
| 11:20 | Invenqor | 개선 | 10분 | 78 | $4.72 | 5.7M / 34K | success |
| 11:36 | Quantoss | 개선 | 6분 | 47 | $2.81 | 2.8M / 26K | success |
| 11:39 | Quantoss | review | 3분 | 24 | $1.05 | 639K / 13K | success |
| 11:44 | Quantoss | 릴리즈 | 1분 | 13 | $0.41 | 207K / 4K | success |
| 16:02 | pii-masker | 개선 | 0분 | 0 | $0.00 | 0 / 0 | unknown |
| 16:16 | pii-masker | 개선 | 10분 | 58 | $4.25 | 4.6M / 37K | success |
| 16:19 | pii-masker | review | 4분 | 15 | $0.81 | 431K / 10K | success |
| 16:44 | AgentHub | 개선 | 14분 | 72 | $6.23 | 7.1M / 57K | success |
| 16:46 | pii-masker | 릴리즈 | 1분 | 19 | $0.59 | 389K / 6K | success |
| 17:03 | Clustara | 개선 | 14분 | 65 | $5.57 | 6.1M / 48K | success |
| 17:09 | Clustara | review | 4분 | 19 | $1.07 | 565K / 12K | success |
| 17:13 | Clustara | 릴리즈 | 3분 | 19 | $0.95 | 609K / 8K | success |
| 17:27 | Invenqor | 개선 | 7분 | 51 | $3.13 | 3.1M / 28K | success |
| 17:36 | Quantoss | 개선 | 6분 | 54 | $2.66 | 2.4M / 25K | success |
| 17:38 | Quantoss | review | 2분 | 18 | $0.87 | 646K / 8K | success |
| 18:01 | ReSSO | 개선 | 12분 | 79 | $5.29 | 6.0M / 42K | success |
무엇을 왜 바꿨나 (원장 발췌)
aiportal-front-admin
- 선택: 기존 backend의 400 + 토큰 code를 세션 만료로 처리 (가치 5 / 위험 1 / 작업량 S)
- 결과: 성공
- 요약: 지난 두 세션이 고친 401 세션 정리 경로가 실환경에서는 아예 동작하지 않았다. 루트 앱
src/api/common/interceptors.js:192-195와docs/API_GUIDE.md(오류와 재시도 표),docs/ARCHITECTURE.md가 모두 기록하듯 기존 backend는 토큰 문제를 HTTP 401이 아니라 HTTP 400 + body code401(토큰 없음)/403(만료)/405(토큰 정보 오류) 로 알리는데, admin-v2http.ts는error.response?.status === 401만 봤다. 그래서 세션이 끝나도 캐시된 관리자 세션이 계속 유효해 보이고 router guard가/sso/ssologin을 다시 부르지 않아 사용자는 새로고침 전까지 모든 조회가 “API 요청에 실패했습니다.”로 끝나는 화면에 갇혔다. 판정을isSessionExpired()한곳에 모아 세션 정리 handler 호출과fallbackErrorMessage가 같은 기준을 쓰게 했고(HTTP 403 인가 실패와BZ01같은 400 업무 오류는 제외), admin-v2 ARCHITECTURE.md에 이 신호 계약을 문서화했다. 검증은npm run verify(typecheck + vitest 70개 통과, 기존 64개 + 신규 6개로isSessionExpired단위 판정 3개와 axios adapter를 통한 400+403handler 호출·400+BZ01미호출·fallback 문구)와npm run build(build → runtime-config 검증 → offline 검사 → integrity manifest 19개) 전체 통과, 그리고 수정을 되돌려 신규 interceptor 테스트 2개가 실제로 실패하는지 직접 확인했다. 커밋 c2b6751. - 보류 아이디어:
- 세션 만료 판정 후 refresh token으로 원요청을 1회 재시도하는 경로 도입 — 루트 앱은 이미 하지만 admin-v2는 SSO 재확인에만 의존한다 (가치 3 / 위험 3 / M)
- CatalogView·ContentAccessView가 route query를 onMounted에서만 읽어 같은 경로의 query 변경(딥링크 재진입)에 반응하지 않는 문제 — 컴포넌트 테스트 환경(jsdom, @vue/test-utils) 선행 정비 필요 (가치 3 / 위험 3 / M)
- AdvancedPolicyView가
snapshot.extensions를 v-model로 직접 변형해 저장 실패 시 화면과 서버 상태가 어긋나는 문제 (가치 2 / 위험 2 / S) monitoringParser.toPod이 kubectl 스타일restarts("3 (5d ago)")에서 NaN을 표에 그대로 노출하는 문제 (가치 2 / 위험 1 / S)shared/format.ts단위 테스트 공백 보강 +formatDateTime이 epoch millis를 날짜로 해석하지 못하는 문제 (가치 2 / 위험 1 / S)
aiportal-front
- 선택: 앱 이동(updateLogAndGo) 쿼리 유실·외부 링크 취약점 수정 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약: 모든 앱 카드 클릭이 거치는 공통 네비게이션 경로
src/utils/appUpdateLog.js에서 5건을 고쳤다 — (1)buildTo가to를 문자열로 받으면extraQuery를 그대로 버려 img 모드 앱의?mode=img가 유실되던 문제(문자열도{path, query, hash}로 병합하도록 변경), (2) 같은 이유로nextTo.path가 undefined 라/chatmain판별에 실패해writeOpenApp이 호출되지 않고 챗 화면이 앱 컨텍스트를 잃던 문제(toRoutePath도입), (3)mode === 'link'의window.open(urlLink, '_blank')이noopener없이 열려 reverse tabnabbing 이 가능하고javascript:/data:url_link 가 그대로 실행되던 문제(isSafeExternalLink/openExternalLink로 http·https·mailto 만 허용, 차단 시 일반 이동으로 폴백), (4) iframe 모드도 동일하게 검증해 위험한 값은 빈 문자열로 넘기도록(ChatFrame 은 빈 값이면 “불러올 메신저가 없습니다” 표시) 처리, (5)url_link가 문자열이 아닐 때.replace로 예외가 나던 문제. 순수 헬퍼(toRoutePath,buildTo,resolveUserIdFromEmail,applyUserIdTemplate,isSafeExternalLink,openExternalLink)로 분리하고 회귀 테스트 30건을 추가했다. 검증은npm test(총 130건 통과, 신규 30건)와npm run build:dev(빌드 성공)로 수행했다. - 보류 아이디어:
- markdown.js 의 DOMPurify 설정/onclick 파싱 경로 XSS 하드닝 검토 (transformAppAnchors 가
<를 실태그로 되돌림) (가치 4 / 위험 3 / M) useFileAttach알림 문구 하드코딩(20MB/100MB) 및msg.includes('10')로 서버 오류를 용량초과로 오판하는 문제 수정 (가치 3 / 위험 1 / S)useTableUtils.sortData가 모든 값을getNum으로 숫자 변환해 텍스트·날짜 컬럼 정렬이 동작하지 않는 문제 (현재 .vue 에서 미사용) (가치 2 / 위험 2 / S)mapAppToCard(item, idx, 'prompt')결과에to가 없어 GlobalSearch 프롬프트 카드 클릭이 무반응인 문제 (의도 확인 필요) (가치 3 / 위험 3 / S)- 빌드 산출물 단일 청크 6.2MB 문제(manualChunks 코드 스플리팅) 개선 (가치 3 / 위험 3 / M)
- markdown.js 의 DOMPurify 설정/onclick 파싱 경로 XSS 하드닝 검토 (transformAppAnchors 가
aiportal-java
- 선택: 인증 필터 예외가 컨테이너 500 으로 새어 나가는 문제 수정 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약:
JwtAuthenticationFilter는AthenaJwtException만 catch 하고 있어, IAM introspect 가 active=true 로 응답했지만 포털 DB 에 사용자 행이 없을 때 던지는BusinessException("사용자 정보가 없습니다.")과userInfoService.getUserInfoByEmail이 실패할 때의 예외가 필터 밖으로 전파됐습니다. 필터는@ControllerAdvice인GlobalExceptionHandler의 처리 대상이 아니라 이 경우 클라이언트가 약속된ResponseDto{code,message}대신 컨테이너 기본 오류 응답을 받아, IAM 계정은 있지만 포털에 미등록된 사용자가 원인을 알 수 없는 500 을 보게 됩니다. 인증 로직을authenticate()로 분리하고filterChain.doFilter를 try 밖으로 옮겨 인증 단계 예외만 응답으로 변환하도록 했습니다(컨트롤러에서 올라온 예외는 삼키지 않음).BusinessException은GlobalExceptionHandler와 동일한 형식으로, 그 외 예외는INTERNAL_SERVER_ERROR형식으로 내려줍니다. 덤으로 catch 블록 안에서 요청마다 새로 만들던 30여 개 OriginSet.of와ObjectMapper를 static 상수로 올리고 응답 기록을writeErrorResponse()로 추출했으며,Origin헤더는 항상 스킴을 포함해 절대 매칭될 수 없던"sso.kcb4u.com"항목을WebConfig와 같은"https://sso.kcb4u.com"으로 고쳤습니다. 검증은JwtAuthenticationFilterErrorResponseTest(6: 사용자 미등록 / 조회 실패 / CORS 허용·비허용 Origin / sso Origin / 다운스트림 예외 비삼킴 회귀 가드) 추가 후sh gradlew check build실행으로 했고 24개 테스트 클래스 113개 테스트 전부 통과했습니다. 커밋 ff2d4a6. - 보류 아이디어:
JwtAuthenticationFilter와WebConfig의 허용 Origin 목록 이중 관리를 설정(yaml)으로 일원화 — 현재 두 목록이 서로 다르게 드리프트 중 (가치 3 / 위험 3 / 작업량 M)AthenaServiceImpl1629·2294행,UserInfoAthenaServiceImpl215·362행의 JsonNode.get(0)무검증 접근 — 빈 items 배열에 NPE (가치 3 / 위험 2 / 작업량 S)AthenaServiceImpl1790행userInfoVo.getEmail().split("@")[0]— email null 에 NPE (가치 3 / 위험 1 / 작업량 S)FileServiceImpl.ocrUpload의 하드코딩 경로E:\KCB\doc\ocr— 호출부가 없는 사실상 죽은 코드라 설정화 또는 제거 판단 필요 (가치 2 / 위험 2 / 작업량 S)ValidUtil,ApiCallUtil단위 테스트 공백 보강 (가치 2 / 위험 1 / 작업량 S)
aiportal-py
- 선택: 워크플로 전역
original_question제거 → GraphState 로 전달 (감사 A-101) (가치 4 / 위험 2 / 작업량 M) - 결과: 성공
- 요약: KAI·KCB·법률·Confluence 4개 LangGraph 워크플로의 entry function 이 모듈 전역
original_question을 요청마다 덮어쓰고 노드들이 그 전역을 읽고 있었다. 같은 프로세스에서 요청이 겹치면 A 사용자의 질문이 B 사용자의guard_rail/query_extension/eval_weight_search/intro/check_search에서 쓰여 가드레일 분기·검색 가중치·조항 조회가 엉뚱한 질문 기준으로 수행된다.retrieve이후 노드가question을 확장 질의로 덮어쓰기 때문에 기존question필드만으로는 원본 질문을 보관할 수 없어,original_question전용 필드를 각GraphState에 추가하고 helperutil/workflow_state.py(with_original_question()으로 inputs 에 주입,get_original_question(state)로 읽되 없으면question으로 fallback)를 도입했다.global선언 4곳과 전역 읽기 8곳을 전부 교체하고,kai_workflow의 미사용 전역original_question·last_rewritten도 제거했다. 진입점이*_langgraph4개뿐임을 확인해 호출 계약은 그대로다.langgraph가 설치돼 있지 않아 워크플로 모듈은 import 할 수 없으므로, helper 는 실제 동시 실행(asyncio.gather) 테스트로(tests/unit/test_workflow_state.py), 워크플로 모듈은 AST 검사로(tests/unit/test_workflow_globals.py:global선언 금지, 모듈 전역 요청 상태 금지, 노드의 전역 읽기 금지, 진입점의with_original_question()호출,GraphState필드 선언) 검증했다. 옛law_workflow.py를 되돌려 넣어 신규 테스트 4건이 실제로 실패하는지 확인했다.python -m pytest654 passed(기존 611),python -m pyflakes .undefined name 0건. docs 3종(CURRENT_STATE_AUDIT/LANGGRAPH_WORKFLOW/TESTING) 갱신. 커밋f4fb71b. - 보류 아이디어:
- A-002 startup 무기한 대기(policy token) 에 timeout/backoff 추가 — 가치 4 / 위험 3 / M
- A-105 오류 응답 계약 통일 (except block 의 미할당
response포함) — 가치 4 / 위험 3 / L - A-206 추천 질문 캐시 무한 append → 원자적 교체·중복 제거·최대 크기 — 가치 3 / 위험 2 / S
- A-108 Confluence 그래프의
retrieve → llm_response → END명시적 edge 추가 — 가치 3 / 위험 3 / S (LangGraph 실행 확인 필요)
appstore
- 선택: 앱 입력의 단일행 필드 길이 상한과 제어문자 거부 (가치 3 / 위험 1 / 작업량 S)
- 결과: 성공
- 요약:
httpapi.ValidateAppInput이 name·slug·summary·description만 검사하고 icon·gradient·serviceUrl·language·framework·team·version은 전혀 검사하지 않아, 2MB 본문 한도까지의 문자열이 그대로 저장되어 앱을 나열하는 모든 진열대에 렌더링됐다. 각 필드에 폼의 maxLength와 같은 상한을 넣고(누락됐던 serviceUrl·language·framework·version·team의 maxLength도 폼에 추가), 단일행 필드의 탭·개행·제어문자를 거부했으며(tags·screenshots는cleanStrings가 기존처럼 조용히 버림), description의 NUL은 PostgreSQL text 컬럼이 저장할 수 없어 INSERT 실패 → 불투명한 500이 되던 것을 422 필드 오류로 바꿨다. OpenAPIAppInput스키마에도 실제 강제되는 상한을 모두 문서화했다. 검증: 필드별 테이블 테스트 2개 추가 후go test ./...,go test -race ./internal/httpapi ./openapi,go vet ./...,gofmt -l,npm run lint,npm test,npm run build, prettier check,check-env-contract.sh·check-docs.sh·check-offline-assets.sh모두 통과. 커밋 3a21896. - 보류 아이디어:
- MCP
featured_appstool이Sort: "updated"를 강제해 v2.5.0의 featured_rank 편집 순서를 무시함(REST/api/v1/apps?featured=true와 불일치). 가치 2 / 위험 1 / S - 프런트엔드
streamAiChat이 종료 시 잔여 버퍼·TextDecoder플러시를 하지 않고, chunk 경계에 걸친\r\n을 정규화하지 못해 이벤트가 유실됨. 가치 2 / 위험 1 / S - rate limiter의
Retry-After: 60고정값을 현재 분 윈도우 잔여 시간으로 계산. 가치 2 / 위험 1 / S brandingURL 가져오기(fetchBrandingSource)에 SSRF 가드가 없어 admin이 내부 주소를 탐침할 수 있고 오류 메시지로 상태 코드가 새어 나감. 가치 3 / 위험 3 / Minternal/httpapi커버리지 — DB 없이 테스트 가능한SPAHandler, middleware,DecodeJSON에 단위 테스트 추가. 가치 3 / 위험 1 / M
- MCP
- 릴리즈: v2.5.1 (2026-09-05)
dataworks
- 선택: 런타임 entitlement
scope를 부분 문자열로 검사해 조회 권한 없는 계약도 통과하던 문제 수정 (가치 4 / 위험 1 / 작업량 S) - 결과: 성공
- 요약:
internal/proxy/dataworks_runtime.go의POST /v1/data-products/{key}/query게이트가strings.Contains(strings.ToLower(ent.Scope), "query")로 권한을 판정해,no-query·query:denied·data_product:subquery처럼 조회를 허용하지 않거나 명시적으로 거부하는 scope 가 그대로 통과해 상품 데이터가 반환됐다(fail-open). 반대로 문서화된 와일드카드data_product:*는*와 정확히 같지도 “query” 를 포함하지도 않아 403scope_denied로 막혔다. scope 를 쉼표·세미콜론·파이프·공백 구분 권한 목록으로 읽고 각 권한을*·query·data_product:*·data_product:query와 정확히 비교하는entitlementAllowsQuery로 분리했으며, 빈 scope 는 필드 도입 이전 행을 위해 기존대로 무제한 유지했다(다른 판독부는 없어 admin 화면과의 불일치 없음). 검증: 권한 판정 테이블 단위 테스트와 HTTP 회귀 테스트(거부 마커 403scope_denied, 와일드카드 200)를 추가하고 옛 substring 로직으로 되돌려 둘 다 실제 실패하는 것을 확인,go build ./...·go vet ./...(0건)·go test ./...전체 통과,go run ./cmd/api-surface-auditgap 0, 수정 파일 gofmt 상태는 변경 전과 동일(기존 CRLF 파일).docs/OPERATIONS.md에 허용 권한 목록을 명시하고 v0.9.42 로 릴리즈 정렬(web 변경은 e2e/capture 픽스처의 버전 문자열뿐이라 node_modules 부재로 웹 체크는 미실행). - 보류 아이디어:
gofmt -l미정렬 64개 파일 일괄 포맷(현재 CI 게이트 제외) / Playwright e2e 를 서비스 컨테이너 기반 CI 잡으로 편입 /npm run build가 추적 파일web/dist/.gitkeep을 삭제하는 문제를 vite 설정으로 해결 / 계약·엔타이틀먼트 만료 임박 기준(30일 고정)을 쿼리 파라미터로 노출 /parseWindow의 미사용bucket인자 제거(호출부 70곳 일괄 정리) - 릴리즈: v0.9.42 (2026-09-05)
- 릴리즈: v0.9.42 (2026-09-05)
git-ctx
- 선택: 캐럿·틸드 범위의 상한을 읽어, 이미 답이 정해진 권고 판정을 “판정 불가”로 돌려주지 않도록 수정 (가치 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)
igame
- 선택: 대문자 스킴이 세션 쿠키의 Secure 플래그를 떨어뜨리는 문제 (가치 4 / 위험 1 / 작업량 S)
- 결과: 성공
- 요약: URL 스킴은 대소문자를 가리지 않아
validateSetting이public_url="HTTPS://games.example.com"을 통과시킨다(url.Parse가 검사 전에 스킴을 소문자로 만든다). 그런데 저장된 값을 읽는 쪽은 원문 문자열을 그대로 비교했고, 그 비교가 전부"https://"접두사 검사였다 — 세션 쿠키의 Secure 플래그(auth.go:173), HSTS 헤더(api.go:280), 상태 화면의 TLS 표시(status.go:81). 대문자 하나 때문에 TLS로 서비스되는 배포의 세션 쿠키가 Secure 없이 나가서 같은 호스트로 가는 평문 요청에 실려 나갔다.canonicalBaseURL을 추가해requestBaseURL이 스킴을 소문자로 정규화한 값을 돌려주게 하고 상태 화면도 같은 헬퍼를 쓰게 했다 — 이미 그렇게 저장해 둔 배포도 아무도 눈치채고 다시 저장할 필요 없이 고쳐진다. 호스트 표기는 건드리지 않았다(읽는 쪽이 이미EqualFold로 비교한다). 검증:gofmt -l,go vet,go build ./...,go test와go test -race전체 통과. 웹 테스트/린트는 이 워크트리에web/node_modules가 없어(eslint/vitest 미설치, 오프라인) 실행하지 못했고, 변경은 Go 파일 3개뿐이라 프런트엔드에 닿지 않는다. - 보류 아이디어: (1) 일일 플레이 제한이 진행 중인 세션의 경과 시간을 세지 않아(
duration_ms가 NULL) 제한을 한 세션만큼 넘겨 시작할 수 있는 문제 — SQL 한 줄 수정이지만 검증에 PostgreSQL이 필요하다. (2)internal/web의 정적 핸들러가 디렉터리도 파일로 열어서/assets/에 번들 파일 목록을 그대로 내준다 —Handler()를fs.FS를 받는 형태로 분리하면 테스트 가능. (3)cmd/igame커버리지 13.7% — 기동/종료 경로 테스트 보강. (4)listUsers만 검색어를 TrimSpace 하지 않아 다른 목록과 동작이 다른 점 정리. (5)Migrate의 체크섬 불일치 경로는 여전히 DB가 있어야만 검증 가능 — 트랜잭션 경계까지 포함한 통합 테스트 마련. - 릴리즈: v0.7.6 (2026-09-05)
jikim
- 선택: SQL LIKE 와일드카드 이스케이프로 OpenBao LIST prefix 범위 이탈 수정 (가치 4 / 위험 1 / 작업량 S)
- 결과: 성공
- 요약:
ListSecretChildren이 caller prefix를 그대로LIKE패턴에 넣어_,%가 와일드카드로 해석됐고,kvlike_xprefix LIST가kvlikeXx하위 key 이름까지 반환했습니다(감사 로그·Secret 검색어도 동일 문제).internal/store/store.go에escapeLike,secrets.go에secretChildrenPattern을 추가해 세 호출지점 모두 문자 그대로 대조하도록 고쳤습니다. 검증은logic_test.go단위 테스트 추가 + 일회용 postgres:17-alpine 컨테이너에JIKIM_TEST_POSTGRES_DSN을 걸어 통합 테스트를 실행해 수정 전 FAIL / 수정 후 PASS를 확인했고,./scripts/verify.sh전체(Go test·vet·gofmt, React test·lint·build, docs, compose)가 통과했습니다. 커밋6f66501. - 보류 아이디어:
security설정의 잘못된 타입 값(예:allow_local_login: "false")이 오류 없이 저장되고 조용히 무시되는 문제를 검증으로 막기 (가치 3 / 위험 2 / S)- 신뢰 프록시 목록 기반
X-Forwarded-For처리로 감사 로그·로그인 rate limit의 IP가 리버스 프록시 주소로 고정되는 문제 해결 (가치 4 / 위험 3 / M) - Transit
batch_input/batch_results지원 추가로 OpenBao 호환 범위 확대 (가치 3 / 위험 3 / M) baoUserpassLogin에서AllowLocalLogin확인 전에 rate limiter를succeeded로 초기화하는 순서 정리 (가치 2 / 위험 1 / S)server.go의var _ = fmt.Sprintf,auth_handlers.go의var _ = store.ErrUnauthorized같은 데드 코드 제거 (가치 1 / 위험 1 / S)
- 릴리즈: v0.2.2 (2026-09-05)
jupiq
- 선택: 보존 정책 prune 실패 재시도와 설정 읽기 실패 시 삭제 보류 (가치 4 / 위험 1 / 작업량 S)
- 결과: 성공
- 요약:
Collector.prune이 시도 시각(lastPrune)을 먼저 기록해PruneMetrics가 실패해도 24시간 동안 재시도하지 않던 문제를 고쳐, 실패한 주기는pruneRetryInterval(30분) 뒤 재시도하고 성공하면 하루 주기로 복귀하도록pruneDue/pruneFailed로 분리했다. 함께 무시되던 retention 설정 읽기 오류도 처리해,ErrNotFound가 아닌 오류로 설정을 알 수 없을 때는PruneMetrics의 기본값 30일을 적용해 운영자가 더 길게 보관하도록 설정한 샘플을 지우는 대신 삭제를 건너뛰고 재시도하게 했다. 새 순수 함수 기반 테스트 2개(TestPruneRetriesSoonAfterFailure,TestRetentionReadableOnlyToleratesMissingSettings)를 추가해 collector 커버리지가 8.5%→13.9%로 올랐고,go vet ./...,go test -race ./...,scripts/check-version.sh,scripts/check-screenshots.mjs,npm run lint,npm test(18파일 58개) 모두 통과했다. - 보류 아이디어:
internal/api/helpers.go의 사용되지 않는parseTimeQuery제거(boundedTimeRange로 대체됨) /internal/collector의collectPrometheus·collectKubernetes경로 커버리지 추가 보강 / 로그인 리미터succeeded가 ip 키를 의도적으로 유지하는 동작에 대한 테스트·문서화 /collectHubs가 goroutine을 제한 없이 띄우는 부분에 동시성 상한 도입 - 릴리즈: v1.4.4 (2026-09-05)
kanpic
- 선택: 가져오기가 스프레드시트의 수만 수로 읽는다 (가치 4 / 위험 1 / 작업량 S)
- 결과: 성공
- 요약: 값을 수로 읽는 자가 두 벌이었다 — 수식 엔진(
decimalText)과 격자(spreadsheetNumber)는 스프레드시트가 수라고 부르는 것만 읽는데, 파일을 읽어 들이는 자리(internal/importexport의parseScalar·parseXLSXValue,internal/external의csvNumber)만 Go 의ParseFloat를 그대로 써NaN·Inf·Infinity·1_000·0x1p4까지 수로 받았다. pandas·R 이 내보낸 CSV 의 빠진 값이 바로NaN이라 그 한 칸이 부동소수점 NaN 이 되었고, 그 값은json.Marshal이 거부하므로parseDelimited가 통째로 실패했다(사람이 본 것은 “json: unsupported value: NaN” 뿐).1_000은 조용히 1000 이 되어 파일에 적힌 것과 다른 값을 칸에 남겼다. 엔진의 자를formula.DecimalNumber로 내어 세 곳이 함께 쓰게 하고csvNumber중복은 지웠다(앞뒤 빈칸 다듬기는 부르는 쪽에 남겨 가져오기가 파일에 적힌 대로 두게 했다). 검증: 새 테스트 3개(TestParseScalarKeepsGoOnlyNumbersAsText,TestOneExportedNaNDoesNotStopTheWholeImport,TestParseXLSXValueKeepsGoOnlyNumbersAsText)와gofmt -l,go vet ./...,go build ./...,go test ./...(전체 통과),scripts/check-release-docs.sh,scripts/check-commit-identities.sh. 커밋 2개(fb0151d, 5eda9c8), 릴리즈 노트 v0.236.0 과 README VERSION·USER_GUIDE 갱신. 웹은 이미 같은 자를 쓰고 있어 손대지 않았고 npm 검사도 돌리지 않았다. - 보류 아이디어:
?의 자리 맞추기 빈칸을 그리지 않는다. 엑셀은# ??/??의 한 자리 분자를 빈칸으로 채워 자릿수를 맞춘다.csvNumber가 IMPORTDATA 의"1,200"·"12%"·앞뒤 통화 기호를 글자로 남긴다(이제formula.DecimalNumber). 가져오기 미리보기의 “글자로 담긴 숫자” 경고와 같은 자리에 놓을지 검토할 것.- 외부 호출 캐시 키가 함수·주소만 담아,
external.max_kb를 올려도 캐시가 남아 있는 동안은 “크기를 넘습니다” 가 그대로다. DOLLARDE·DOLLARFR이math.Pow(10, ceil(log10(fraction)))로 자리를 밀어 이진 실수 어긋남이 남아 있다(DOLLARDE(1.02,16)이 1.125 가 아니라 1.1250000000000002).- 서버의
formatValue는 글자 “123” 을toNumber로 수로 읽고 격자는 글자로 보아, 숫자처럼 생긴 글자 값에서 두 곳의 답이 갈릴 수 있다.
- 릴리즈: v0.236.0 (2026-09-05)
- 릴리즈: v0.236.0 (2026-09-05)
moina
- 선택: 공유한 주소 안의
#fragment·@handle을 Topic·멘션에서 제외 (가치 4 / 위험 2 / 작업량 S) - 결과: 성공
- 요약: v0.1.19에서 웹 앱은 Moin 본문의 http·https 주소를 하나의 링크 token으로 렌더링하기 시작했지만 서버의
extractHashtags·extractMentions는 여전히 원본 본문을 그대로 훑어,https://example.com/guide#install의 fragment가 Topic “install”이 되고https://example.com/@alice의 handle이 같은 아이디를 쓰는 이 인스턴스 회원에게 멘션 알림을 보냈습니다. 읽는 사람 화면에는 평범한 링크만 보이는데 Moin은 엉뚱한 Topic 피드에 들어가고 관계없는 회원은 자기가 멘션됐다는 알림을 받았습니다.MoinContent가 쓰는 RFC 3986 문자 집합 그대로의contentURLPattern으로 주소 구간을 공백 처리한 뒤 token을 추출하도록 고쳐 양쪽이 주소의 끝을 같은 규칙으로 판정하게 했고(문장 끝 구두점은 멘션·해시태그를 시작할 수 없어 되돌릴 필요가 없음을 주석에 명시), http·https만 주소로 보므로ftp://는 여전히 본문이며 20개 token 예산도 작성자가 실제로 쓴 태그에 쓰이게 됩니다.api/openapi.yaml의 Moin 작성 설명에 이 계약을 적었습니다. 검증은 새 단위 테스트 6케이스와 새 integration test 1개(둘 다 수정 전 코드에서 실패하는 것 확인) 포함 로컬 PostgreSQL(moina_ci)에MOINA_TEST_POSTGRES_DSN을 걸어go test -race ./...전체 통과,make fmt·make check·go vet·staticcheck 통과(frontend 무변경이라 ESLint·vitest 생략). - 보류 아이디어:
updatePost가 DB 오류를 409not_editable(“본인의 공개 Moin만 수정할 수 있습니다”)로 보고해 원인을 감춤(가치 2 / 위험 1 / S) · Makefiletest가 CI와 달리-race미사용(가치 2 / 위험 1 / S) ·detectMediaType이ftypbox만 보고 HEIC·AVIF·M4A까지video/mp4로 저장(가치 2 / 위험 2 / S) · 일간 DigestdigestDue가 DST 전환일에 존재하지 않는 지역 시각을time.Date정규화에 맡겨 발송 시각이 한 시간 밀림(가치 2 / 위험 3 / M) - 릴리즈: v0.1.21 (2026-09-05)
muni
- 선택:
.hwpx칸 음영 — 실제 있는 자리에서 읽고, 쓰기에도 추가 (가치 4 / 위험 2 / 작업량 M) - 결과: 성공
- 요약: 보류 목록의 “
.hwpx쓰기에 칸 음영 추가”를 하려다, 읽기 쪽이 애초에 동작하지 않았음을 찾았습니다 — 칸 음영은 칸에 없고 칸은borderFill을 번호로 가리키기만 하는데(런이 charPr 번호를 거치는 것과 같은 간접), 리더가<hp:tc>안에서fillBrush를 찾고 있었습니다. 공개 실제 파일 넷에서 칸 325개 중 칸 안에fillBrush를 둔 것은 0개라, 한글이 쓴 음영 표는 예외 없이 흰 표로 들어왔습니다(README 는 양방향이라고 적고 있었고, 테스트는 하나도 없었습니다). 머리를 읽을 때 borderFill 의 채우기도imp.cellFills에 담아 칸이 부르는 번호로 찾고, 쓰는 쪽은 색마다 borderFill 을 3번 뒤에 하나씩 만들어 같은 색 칸끼리 공유합니다. 무엇이 음영인지는hangul.CellShade한 곳의 판단이라 읽기와 쓰기가 어긋나지 않습니다. 검증은 변경 전에 실패하는 것을 확인한 새 테스트 둘(음영·흰색·채우기 없음·그러데이션 네 칸의 읽기 픽스처, borderFill 재사용과 왕복의 쓰기)과gofmt -l·go vet ./...·go test ./...전체 통과, 그리고 실제.hwpx넷으로 borderFill 의 요소 차례와 브러시 모양(hatchColor="#999999")을 맞췄습니다. 커밋 2d175c8. - 보류 아이디어:
- CI 에
go vet과gofmt -l추가 (가치 2 / 위험 1 / S) — 이번에도 손으로 돌려야 했음 .hwpx머리글 칸의 기본 음영 (가치 2 / 위험 3 / S) —.docx쓰기는F3F4FA를 깔아 머리글 행이 보이게 하는데.hwpx는 굵게뿐. 리더가 머리글 행의 음영을 버리므로 왕복이 어긋날 자리가 있어 미뤘음.hwp표 캡션의 위치(위/아래) 읽기 (가치 2 / 위험 2 / S) — 지금은 언제나 표 뒤에 붙임.hwp/.hwpx표 행 높이 읽기 (가치 2 / 위험 2 / S) — 너비와 같은 자리에 있는데 muni 편집기가 행 높이를 들지 않음
- CI 에
- 릴리즈: 없음(태그 푸시는 외부 절차)
- 릴리즈: v0.27.0 (2026-09-05)
pii-masker
- 선택:
PII_MASKER_ALLOW_HOSTS실제 적용(리다이렉트 포함) (가치 5 / 위험 2 / 작업량 M) - 결과: 성공
- 요약: README에 문서화되어 있고
config.Load가normalizeAllowHosts로 파싱까지 하던PII_MASKER_ALLOW_HOSTS가 코드 어디에서도 검사되지 않는 완전한 무동작 상태였습니다(UpstageConfig.AllowHosts참조처가 테스트 설정뿐). 게다가httpClient()가 리다이렉트를 기본 정책대로 무제한 추종해서, 추론 엔드포인트가 307/308을 돌려주면 Go가GetBody로 본문을 재전송하며 업로드한 개인정보 원문이 임의 호스트로 흘러가고, 302 계열에서도x-api-key인증 토큰은 Go의 교차 도메인 헤더 제거 대상이 아니라 그대로 따라갔습니다.Client.checkHostAllowed/checkURLAllowed/hostAllowed를 추가해 (1)performParseRequest에서 멀티파트 본문을 만들기 전에, (2)TestConnection에서 요청 전에, (3)CheckRedirect로 매 리다이렉트 홉마다 대상 호스트를 검사하고 최대 5홉으로 제한했습니다. 허용 목록이 비면 기본 URL의 호스트로 폴백해 무제한이 되는 일이 없고, 항목은api.upstage.ai(호스트만) /127.0.0.1:8080(포트 포함) 둘 다 매칭하며 대소문자·공백을 무시합니다. 차단은 새blockedHostError→classifyRequestError에서upstream_host_not_allowedCallError로 매핑되어/v1/mask가 502 + 해당 코드를,/v1/test-connection이ok:false를 돌려줍니다.internal/upstage테스트 5개(호스트 매칭 테이블 9케이스, 빈 목록의 기본 URL 폴백, 차단 호스트로 요청 미도달, 비허용 호스트로의 리다이렉트 차단, 허용 호스트 간 리다이렉트는 정상 전달)와 통합 테스트 1개(/v1/mask502 + 업스트림 미도달)를 추가했고,gofmt -l(무출력)·go vet ./...·go build ./...·go test -count=1 ./...·go test -race -count=1 ./...전부 통과,-race -count=5로 플래키 여부 확인,checkURLAllowed를 항상 nil 반환으로 되돌려 새 테스트 4개가 실제로 실패하는 것까지 확인했습니다. README에도 이 변수가 무엇을 강제하는지 설명을 추가했습니다. - 보류 아이디어:
/v1/history의limit상한 없음(과도한 값 요청 시 전체 목록 직렬화) /handleGetJobResult가 결과 파일 전체를 메모리에 올린 뒤 응답(http.ServeContent스트리밍 전환 여지, 삭제된 파일에 500 대신 404도 함께 해결) /cmd/pii-masker에 graceful shutdown 없음(App.Close가 있으므로 시그널 처리와 묶을 여지) /internal/config패키지 단위 테스트 전무(normalizeAllowHosts,envNonNegativeInt등) / 동기/v1/mask경로에는 동시 실행 제한이 없어 비동기 job에만 적용된 메모리 상한이 우회됨 - 릴리즈: v1.0.11 (2026-09-05)
ptium
- 선택: 시각 서식이 0.5625 같은 분수로 읽히고 날짜+시각은 시각이 잘리는 문제 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약:
internal/docs/cellformat.go의kindOfFormat이 서식 코드에서y·d(날짜)와%(백분율)만 찾고h·s(시각)는 찾지 않아서, 시각 셀이 저장된 분수 그대로 나왔다(회의 순서표 13:30 →0.5625, 게다가 숫자 열로 보여 표가 아니라 막대 차트). 날짜+시각 서식은 시각이 잘렸고(yyyy-mm-dd hh:mm→ 날짜만),[h]:mm:ss근무 합계 36시간은1.5였다. 스크래치 테스트로 세 가지를 먼저 재현한 뒤kinds를numberKind{what, seconds}구조체로 바꿔time/datetime/elapsed를 추가했고, 내장 서식표를builtInNumbers하나로 합쳐 18–21(시각)·22(날짜+시각)·45–47(경과)을 담았으며, 대괄호 안이 h·m·s 로만 된 단위를 알아보는elapsedUnit으로 경과 시간을 하루 넘겨 세게 했다.m은 월과 같은 글자라 단독으로는 시각으로 보지 않고, 음수·마지막 날 초과는 그대로 둔다.celltimes_test.go(서식 19가지 +kindOfFormat13가지 +elapsedUnit10가지 + 통합 문서 1개)를 추가하고make test(go test -race, go vet, tsc, vite build) 전부 통과. VERSION 1.69.27 스탬프(openapi·kubernetes·offline-deployment)와 릴리스 노트 작성, 커밋 2e0c8f1. - 보류 아이디어:
allNumeric이 통화 기호·괄호 음수(“₩1,200”, “(1,200)”)를 숫자로 보지 않는 문제 —deck/compile.go의parseBareNumber도 같이 손봐야 함 (가치 3 / 위험 3 / M)- 맥 엑셀의 1904 날짜 체계(
workbookPr date1904)를 무시해 날짜가 4년 이르게 읽히는 문제 (가치 2 / 위험 2 / S) writeSheet가 첫 행을 무조건 머리글로 삼아, A1 에 제목 한 칸만 있는 시트가 어긋나는 문제 (가치 2 / 위험 3 / M)- 유럽식 Excel 이 저장하는
;구분 CSV 를 한 열로 읽는 문제 — 구분자 추정 (가치 2 / 위험 3 / S)
- 릴리즈: v1.69.27 (태그·푸시는 외부 스크립트)
- 릴리즈: v1.69.27 (2026-09-05)
releasedock
- 선택: 배포되지 않은 패키지를 큐에서 다시 보낼 수 없는 문제 수정 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약: 심플 배포 화면의
stranded경고는 “남은 패키지를 다시 배포하십시오” 라고 지시하지만, 정작 화면에는 그 방법이 없었습니다. 배포 실행은QUEUED항목만 집어 가는데남은 파일 중단으로 남은건너뜀, 업로드 거부·명령 실패로 남은실패/시간 초과, 앞선 세션이 추가한확인 불가항목은 모두QUEUED가 아니고, 개별 제거 버튼도QUEUED에만 붙어 있어서 목록 비우기 후 파일을 디스크에서 다시 고르는 것 말고는 길이 없었습니다. 복제가 미뤄진 채 끝난 업로드는 이미지가 미러링되지 않은 위험 상태라 재배포가 곧 복구 절차인데 그 절차를 UI 가 막고 있었습니다. 순수 함수retryablePackage/requeue를 추가해 배포되지 않은 항목만 원래 순서 그대로QUEUED로 되돌리고(이전 시도의error·exitCode·runId는 버림, 성공 항목은 객체째 유지), 다시 시도 버튼과 실행 중이 아닐 때의 개별 제거를 붙였습니다.확인 불가도 되돌리되 그 실행이 아직 진행 중이면 대상당 1건 유니크 인덱스가 업로드를 거부하므로 이중 배포는 생기지 않습니다. 단위 테스트 3건을 추가했고npm ci,npm test -- --run(78건),npm run build(tsc -b 포함), backend/runnergo vet·go test ./...를 모두 통과했습니다. docs/simple-mode.md 두 곳을 갱신하고 VERSION 을 0.5.8 로 올렸습니다(저장소 관례). - 보류 아이디어:
- CI 와 Makefile 에
go vet(또는 golangci-lint) 단계가 없어 정적 검사가 수동입니다 (가치 3 / 위험 1 / S). - 실행 상세 화면이
appDeployError는 표시하면서replicationError는 표시하지 않아 복제 실패 사유가 단계 행에 없습니다 (가치 2 / 위험 1 / S). simpleRunLogger.append가 빈 payload 를 저장하지 않아 스크립트 출력의 빈 줄(문단 구분)이 로그에서 사라집니다 (가치 2 / 위험 1 / S).downloadSimpleRunLog이 조회한original_filename·status를 쓰지 않아 다운로드 파일명이 run id 뿐입니다 (가치 2 / 위험 1 / S).web/dist/assets/vendor청크가 617KB 로 커서 폐쇄망 초기 로딩 최적화 여지가 있습니다 (가치 2 / 위험 3 / M).
- CI 와 Makefile 에
- 릴리즈: v0.5.8 (2026-09-05)
- 릴리즈: v0.5.8 (2026-09-05)
relio
- 선택: OpenAPI 문서에 경로·질의 파라미터를 넣고 핸들러와 양방향으로 묶기 (가치 4 / 위험 1 / 작업량 M)
- 결과: 성공
- 요약:
/api/openapi.json은 경로와 요약만 담고 있어서, 클라이언트가 스스로 알아낼 수 없는 두 가지가 비어 있었습니다. 하나는 사양 위반입니다 — OpenAPI 는 경로의 모든{id}에 대응하는 path parameter 를 요구하는데 약 50개 경로 중 선언한 곳이 하나도 없어, 생성기는 id 를 넣을 인자가 없는 메서드를 만들고 린터는 문서를 거절합니다. 다른 하나는 질의 문자열 전체입니다 —/customers의cursor·sort,/opportunities의 다섯 필터, 엔드포인트별limit상한, 인증된 GET 이 모두 받는fields가 어디에도 없어 Go 소스를 읽어야만 알 수 있었습니다. 특히sort는 화이트리스트 밖의 값을 오류가 아니라 조용히 기본 정렬로 되돌리므로, 값 목록을 모르는 클라이언트는 정렬이 무시된 사실조차 알 수 없었습니다. 경로 파라미터는 템플릿에서 직접 파생시켜 나중에 추가되는 경로가 빠뜨릴 수 없게 했고, 질의 파라미터는 enum 을 DB CHECK 제약에서·정수 범위와 기본값을 그 값을 읽는httpx.IntQuery호출에서 가져와 표로 적었습니다.fields는requireAuth가 핸들러 실행 전에 적용하므로components.parameters공유 항목으로 두고 인증된 GET 마다$ref로 붙입니다. 검증은 새 테스트 4개(경로{var}↔path parameter 양방향, 문서의 질의 파라미터↔라우터가 그 경로에 연결한 핸들러가 실제로 읽는 질의 키 양방향,fields가 인증된 GET 에만·전부에 붙었는지,sortenum 이internal/crm정렬 화이트리스트와 값·개수까지 일치하는지)를 문서를 6가지로 어긋나게 만들어 실제로 실패하는지 확인한 뒤go test -race ./...,go vet ./...,gofmt -l, web typecheck·build,check-env-contract.sh,check-static-assets.sh전체 통과. 커밋 edb6ed3. - 보류 아이디어:
- 네 intelligence 필터의
Cursor필드는 어떤 질의도 쓰지 않는 죽은 코드 — 실제 커서 페이징을 붙이거나 제거 (가치 3 / 위험 1 / M) internal/audit는 테스트가 하나도 없음 —Record는Log가 nil 이면 패닉하고nullableJSON은 marshal 오류를 버려 감사 데이터를 조용히 NULL 로 만듦 (가치 3 / 위험 1 / M)list_activities,get_contracts,list_quotations등 나머지 MCP 목록 도구는 서비스가 슬라이스만 돌려주어 페이징 자체가 없음 — 상위 N건 뒤는 여전히 안 보임 (가치 3 / 위험 2 / M)internal/server/today.go:87의time.Now().Truncate(24*time.Hour)는system.timezone(기본 Asia/Seoul) 설정을 무시 (가치 3 / 위험 3 / M, 타임존 배선 필요)- CI 에
gofmt -l검사 추가 — 지금은 포맷 위반이 통과함 (가치 2 / 위험 1 / S)
- 네 intelligence 필터의
- 릴리즈: v1.11.16 (2026-09-05)
- 릴리즈: v1.11.16 (2026-09-05)
umm
- 선택: 6월의 캔버스에 걸려 있던 8월의 사진 (가치 3 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약: v0.70.0의 되감기는 생각과 연결만 스냅샷으로 바꿔치우고, 캔버스의 그림 목록은 언제나
GET /spaces/{id}/attachments(=지금의 목록)에서 와서 그 시점 이후에 붙인 사진이 과거의 캔버스에 그대로 걸려 있었습니다. 다른 무엇보다 그림에서 나쁜 결함인 이유는, 오늘의 문장은 읽으면 알아채지만 사진은 언제 붙었는지 들여다봐도 알 수 없기 때문입니다.SpaceAt이 그 시점까지의 첨부를attachments로 함께 돌려주게 하고rewindTo가 그것을 쓰게 했으며(지금으로는 오늘 목록을 다시 받음), 그때 있었지만 지금은 그릴 수 없는 그림 — 그 뒤 지워진 생각에 붙어 있던 것, 바이트는 지워지지 않은 생각에 대해서만 내어 줌 — 은 지워진 연결과 같은 방식으로removedAttachments로 세어 배너에서 말하게 했습니다(사람이 직접 뗀 그림은 행이 없어 셀 수 없음, 주석에 명시). 검증: 새 통합 시험 2개 추가 후 날짜 필터/카운트를 무력화하면 둘 다 실패함을 확인, 실제 PostgreSQL 17(도커umm-test-pg, DSNpostgres://umm:umm@127.0.0.1:15433/umm)에 대한go test ./...전체 통과 ·go vet ./...· gofmt · tsc · oxlint/Prettier · i18n 994키 · vitest 156개 ·scripts/check-version.sh통과. v0.71.2로 릴리스 커밋(3c52127). - 보류 아이디어:
- Markdown 백업에 그림이 담기지 않는 사실이 v0.71.0 릴리스 노트에만 있고 내보내기 메뉴·user-guide·features.md 어디에도 없음 — 백업으로 되돌린 뒤에야 발견 (가치 3 / 위험 1 / S)
WriteSource는 제목·리드가 모두 빈 슬라이드를 건너뛰는데SlideSources는 그 자리를 세고, 표지 판정도TrimSpace유무로 갈려 출처 매핑이 한 칸 밀림(현재는 도달 불가이나 두 곳의 규칙이 다름) (가치 3 / 위험 2 / S)- 401/403이 오프라인 큐 전체를 세우는데, 403은 한 변경에 대한 권한 거부일 수 있어 무관한 변경까지 붙잡음 (가치 3 / 위험 3 / M)
- 되감은 채로 공간을 바꾸면
rewind상태가 남아 배너와 읽기 전용이 새 공간에 그대로 이어짐 (가치 2 / 위험 2 / S) README.md상단 릴리스 소개가 v0.44.0, 배지가 v0.22.0,docs/README.md문서 허브가 v0.8.1 — 셋 다 실제(v0.71.2)와 어긋남 (가치 2 / 위험 1 / S)
- 릴리즈: v0.71.2 (2026-09-05)
- 릴리즈: v0.71.2 (2026-09-05)
vibe-coders
- 선택: Text2SQL LIMIT 규칙을 문장 자신의 결과 범위로 한정 (가치 4 / 위험 1 / 작업량 S)
- 결과: 성공
- 요약:
text2sql.ValidateSQL의 두 LIMIT 규칙이 중첩을 전혀 고려하지 않고 문장 전체를 훑어, 서브쿼리·CTE 본문의 LIMIT이 바깥 쿼리를 대신 대답하고 있었다. (1) 상한 검사는limitRe.FindString으로 텍스트 순서상 첫 매치만 봤는데 서브쿼리는 바깥 LIMIT보다 먼저 쓰이므로... (SELECT ... LIMIT 5) ... LIMIT 999999가 5로 측정돼 통과했다 → 이제 모든 LIMIT을MaxLimit과 비교한다. (2) 기본 LIMIT 주입은 어디든 LIMIT이 있으면 건너뛰어, 바깥은 무제한인데 서브쿼리에만 LIMIT이 있는 쿼리(SELECT * FROM users WHERE id IN (SELECT ... LIMIT 5))에 아무 상한도 붙지 않았다 — 기본 상한이 존재하는 이유인 바로 그 쿼리에 무제한 스캔과 ORDER BY 전체 정렬을 넘긴 셈 → 괄호 밖(depth 0) LIMIT 유무로 판정하게 바꿨다. (3) 값을fmt.Sscanf("limit %d")로 되읽었는데 포맷의 공백은 개행과 매치되지 않아 줄바꿈된LIMIT\n999999가 0으로 파싱돼 상한이 무력화됐고, 오버플로하는 리터럴도 0이 됐다 → 정규식 캡처 그룹 +strconv.Atoi로 바꾸고 파싱 불가 값은 거부한다. 회귀 테스트 6개를 새 파일로 추가해 수정 전 코드에서 실패함을 확인했고(중첩 4건·개행 1건·오버플로 1건), gofmt·go vet·go build·go test ./... -count=1·go test -race ./internal/text2sql·cmd/api-surface-audit모두 통과. Go 전용 변경이라 OpenAPI/프런트엔드 영향 없음. - 보류 아이디어:
audit.InferLanguages의addSignal이 더 높은 신뢰도 신호가 오면 기존 evidence를 통째로 버리는 문제(근거 누적 유실) / redact.go IPv4 규칙의 “사설망 제외” 주석과 실제 동작(전부 마스킹) 불일치 정리 /EstimateTokens의[]rune(text)전체 복사를utf8.RuneCountInString으로 교체 /MODEL_PRICING_KRW_PER_1M키가 소문자 정규화되지 않아lookupPrice의 정확 매칭을 항상 놓치고 prefix 루프로만 걸리는 문제 /ValidateSQL이 MySQLLIMIT 5, 100및FETCH FIRST n ROWS ONLY형식을 인식하지 못해 상한·기본값 규칙이 모두 우회되는 문제 - 참고: 2026-09-02(가격 longest-prefix 매칭)·2026-09-03(PROXY_API_KEYS 트림) 세션 수정은 여전히 master에 병합되지 않았다(
internal/audit/usage.go의lookupPrice가 아직 첫 prefix 일치를 반환). 중복 작업하지 말 것. - 릴리즈: v0.83.0 (2026-09-05)
visitflow
- 선택: 방문 목록 검색이 동행 방문자를 잘라내 인원수·대표 방문자를 틀리게 보여주는 문제 수정 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약:
queryVisits는visitor_visits·visitors를 LEFT JOIN한 뒤count(vv.id)와array_agg(...)[1]로 인원수와 대표 방문자를 집계하는데, 검색어(q)의 회사·이름 해시·전화 해시 조건을 그 참가자 행에 직접 걸어 두어 집계 전에 검색어와 무관한 동행자가 통째로 제외됐다. 그래서 5명이 오는 방문을 동행자 한 명의 전화번호로 찾으면 목록에 “방문자 1명”으로 뜨고 대표 방문자 칸에는 주 방문자 대신 검색에 걸린 사람이 나왔으며, 이 쿼리는 방문 목록·MCPvisits.search·담당자 대시보드가 공유한다. 이미 같은 문제를 EXISTS 서브쿼리로 푼 방문 이력 CSV 내보내기와 똑같이, 참가자 관련 조건만EXISTS(SELECT 1 FROM visitor_visits mvv JOIN visitors mp ...)로 옮겨 서브쿼리가 방문의 매칭 여부만 판정하고 집계는 참가자 전원을 보도록 했다. 검증은go vet ./..., docker postgres:16-alpine을 띄운VISITFLOW_TEST_DSN전체 테스트 통과,npm ci && npm run build이며, 주 방문자 1명 + 동행 1명인 방문을 만들고 동행자의 전화·이름·회사 세 가지로 각각 검색해visitorCount가 2이고primaryVisitor가 주 방문자인지 보는 통합 테스트 1개를 추가한 뒤 옛 조건으로 되돌려 실제로 실패(인원수 1)하는 것까지 확인했다. API_AND_MCP 문서에 한 문장을 덧붙였다. - 보류 아이디어: 관리자 대시보드 Watch List 타일이
starts_at을 무시해 아직 시작하지 않은 항목까지 활성으로 세는 문제(실제 매칭 쿼리는starts_at<=now()를 건다) / MCPstatistics도구만 아직v.start_at>=CURRENT_DATE-$1을 써 통계 화면의 사업장 시간대 구간과 다른 숫자를 내는 문제 /bestAcceptLanguage의q=0(수용 불가)·q>1·잘못된q값 처리 정정 / 방문 이력 CSV의 50,000행·감사 로그 10,000행 상한 초과 시 잘렸음을 사용자에게 알리는 표시 / CSV·XLSX 가져오기 파서(visitorInputsFromRows) 엣지케이스 단위 테스트 보강 - 릴리즈: v2.6.5 (2026-09-05)
AgentHub
- 선택: 넓은 차단 위에 쓴 좁은 예외(도구를 지정한 allow)가 Pod까지 전달되지 않던 문제 수정 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공 — 커밋 c930c46 (auto/2026-09-05-1010)
- 요약: 도구를 지정한 allow는 이 효과가 존재하는 이유 그 자체이고(“이 서버는 아무도 못 쓰되 read_file만 예외”), 코드 위 주석도 “좁은 예외가 넓은 차단 위에 앉을 수 있도록 명시적 allow가 있다”고 적혀 있었습니다. 그런데
CompileServer는 deny와 require_approval만 컴파일하고 좁은 allow는continue로 버렸습니다 — 게이트웨이에 전달된 것은 아래의 deny뿐이었습니다. 나머지 화면은 전부 문서와 일치했습니다: 시뮬레이터는 allow를 답하고, 감사 로그에 allow 규칙 ID가 남고, task.create·runtime.start는 API에서 판정되므로 지켜졌습니다. 오직 tool.call만 Pod 안에서 판정되는데, 에이전트가 우회할 수 없는 그 게이트웨이가 정책이 허용한 호출을 거절했고, 그 불일치는 어느 화면에도 나타나지 않았습니다.ServerRules.Allowed를 추가해 아래 restriction보다 위에 쓰인 allow의 도구 패턴을 싣고, 위쪽 restriction이 이미 잡은 패턴은 싣지 않습니다(그 규칙이 호출 시점에 먼저 결정하므로). 두 끝 다 서버가 뜨기 전에는 도구 목록을 모르므로 겹침 판정은 이름이 아니라 패턴끼리 합니다(“github/delete_*“와 “delete_temp”는 같은 호출). 필드는 기존 경로를 그대로 따라갑니다: MCPBinding → CRD 스키마(선언하지 않으면 API 서버가 잘라냄) → 오퍼레이터 → 게이트웨이. 게이트웨이에서는 플랫폼 자신의 deny/gate만 무력화하고 그 외에는 아무것도 건드리지 않습니다 — 에이전트의 허용 목록과 카탈로그의 승인 요구는 다른 사람의 진술이라, 플랫폼 예외가 소유자가 주지 않은 도구를 건네주지는 않습니다. 검증: 수정 전 새 테스트가 각 계층에서 실패하는 것 확인(policy 3개),TestTheCompiledRulesAgreeWithTheDocument가 운영자가 실제로 쓰는 6가지 배열에 대해 “게이트웨이가 받은 것 == 문서의 판정”을 스윕, go vet ./…, go test -race ./cmd/… ./internal/…, web npm ci+lint+build,scripts/release-catalog-images.sh check-versions·validate모두 통과. internal/policy·cmd/runtime-proxy가 런타임 base 이미지 소스라 BASE_VERSION 0.22.0으로 상향(5곳). - 보류 아이디어:
- 게이트웨이가 Denied를 Gated보다 먼저 보므로, 차단 위에 쓴 승인 규칙(gate above deny)은 Pod에서만 차단으로 바뀜 (3/3/M)
- scrubDecision이 record.Agent 등 남은 자유 텍스트를 검사하지 않아 에이전트 이름으로는 무엇이든 나갈 수 있음 (2/2/S)
- korean.EndsInConsonant가 괄호·따옴표로 끝나는 값에서 조사를 잘못 고름 (2/2/S)
- captureHandler.WithGroup이 그룹 이름을 버려 서로 다른 그룹의 같은 키가 충돌 (2/1/S)
- 릴리즈: v0.234.0 (2026-09-05)
Clustara
- 선택: 실클러스터 write 경로(
internal/kubeexecutor·stack applier)의 대상 조립 결함 5개 수정 (가치 5 / 위험 2 / 작업량 M) - 결과: 성공
- 요약:
internal/kube의 executor 는 Action Center 승인 뒤 실제로 클러스터를 바꾸는 곳이고 stack applier 는 GitOps Stack 의 server-side apply 경로인데, 둘 다 요청 URL 을 문자열로 조립하면서 다섯 가지가 틀렸다 — ①DeletePod는 name 이 비면/api/v1/namespaces/{ns}/pods/를 만드는데 이건 Kubernetes 가 deletecollection 으로 처리하는 URL 이라 이름 없는 delete_pod 한 건이 네임스페이스의 Pod 를 전부 지운다. 그리고 그런 요청이 실제로 만들어질 수 있었다:analyzer.PlanDevRequest가in.ResourceName != ""로만 검증하는데 핸들러는strings.TrimSpace한 값을 저장하므로 공백만 있는resource_name이 대상 없는 액션 요청으로 적재됐다(검증기는 trim 후 비교, executor 는 요청 경로를 믿지 않고 빈 namespace·name·node 를 전송 전에 거절) ②normalizeWorkloadKind가 switch 앞에서 복수형s를 무조건 떼어내sts→st,ds→d가 되면서 바로 아래case "sts"·case "ds"가 실행 불가능한 코드였다 —ResourceKind는 자유 입력이고 단축 이름은 운영자·Ops Agent 가 쓰는 표기(deploy는s로 안 끝나 우연히 동작 중이었음) ③ apps/v1 DaemonSet 에는/scale서브리소스가 없는데workloadResourcePlural이 scale 대상으로 받아들여 요청→영향도→승인을 다 통과한 뒤 실행 시점에 404 로 끝났다 ④resolveStackTargets가 모든 문서에 stack namespace 를 채우므로clusterScopedKinds에 없는 cluster-scoped kind 는/namespaces/{stack}/...로 조립돼 실패 —IngressClass·PodSecurityPolicy는pluralizeKind의 불규칙 목록에 이미 있으면서 이 목록엔 없었고, webhook configuration 2종·APIService·RuntimeClass·CSIDriver·VolumeSnapshotClass·ValidatingAdmissionPolicy(Binding)도 추가 ⑤ 같은 패키지 읽기 경로(podLogRequest·podExecURL)는url.PathEscape를 쓰는데apiResourcePath만 날것으로 이어붙여, 저장된 manifest 값의 슬래시가force=trueapply 를 다른 리소스로 돌릴 수 있었다. 검증: 신규 테스트 5개를 고치기 전 코드에 되돌려 붙여 다섯 개가 각 결함을 지목하며 실패함을 확인했고go build ./...·go vet ./...·go test ./...전부 통과. 저장소 관례대로 AppVersion·changelog·docs 버전 마커를 v0.9.270 으로 올렸다(release gate 테스트가 강제). - 보류 아이디어: ①
delete_pod실행기가act.ResourceKind를 아예 보지 않아 Deployment 를 대상으로 만든 요청이 같은 이름의 Pod 삭제로 나감 (가치 3 / 위험 1 / S) ②require_resource_limits는limits가 비어있지 않기만 하면 통과 — cpu 만 있고 memory limit 이 없는(OOM 무제한) 형태를 놓침 (가치 3 / 위험 2 / S) ③AssessImpact가 인벤토리에 없는 대상을 zero value 로 받아 “replicas 0 → N” 처럼 현재 상태를 아는 척함 (가치 3 / 위험 1 / S) ④.github에 CI 워크플로 없음 — build/vet/test 게이트 추가 (가치 3 / 위험 1 / S) ⑤kube.splitCommandLine이 빈 인자(sh -c "")를 버려 argv 가 밀림 —podExecArgs의 CommandArg 경로도 같음 (가치 2 / 위험 1 / S) - 릴리즈: v0.9.270 (2026-09-05)
- 릴리즈: v0.9.270 (2026-09-05)
Invenqor
- 선택: 중간에 실패한 행 읽기가 완전한 결과로 응답되던 문제 (가치 4 / 위험 1 / 작업량 M)
- 결과: 성공
- 요약:
*sql.Rows반복문은 “끝까지 읽었다”와 “읽다가 실패했다”를 구분하지 못한다 —Next()가 둘 다false를 돌려주고rows.Err()만이 둘을 가른다. 그런데 아홉 곳이 그것을 호출하지 않아, 연결이 끊기거나 statement가 취소되면 그때까지 도착한 행이 그대로 답이 되어 HTTP 200으로 나갔고 조각이라는 표시는 응답 어디에도 없었다. 가장 무거운 자리는/api/v1/query/execute로, 3,000대 규모 질의가 3대만 돌려줄 수 있었고 바로 다음 줄의 감사 레코드가 그 짧아진 수를 “표현식에 일치한 자산 수”로 남겼다. Agent 목록, 설정 목록·변경 이력, 자산의 수집 원천·변경 이력·관계도 같은 모양이었고,mergeAssets는 scan 오류까지 버려 실패한 scan이 빈 문자열을 “옮긴 source id” 목록에 넣었으며 그 목록은asset_changes에 영구 기록으로 들어간다. 이제 아홉 곳 모두 요청을 실패시키고(진단 pruner만 읽은 행을 유지하되 그 회차를 버림), 나머지 반복문은 이미 확인하고 있었다는 차이가 다시 생기지 않도록 Server 트리의 모든 행 반복문을 소스로 읽어 커서를 연 함수가 오류를 확인하는지 요구하는 테스트를 추가했다(CSV 컬럼의spreadsheetSafe검사와 같은 방식 — 이 저장소에 이미 AST 규약 테스트가 4개 있다). 검증: 새 테스트가 수정 전 아홉 곳을 파일·함수· 변수 이름과 함께 나열하며 실패하고 수정 후 통과함을 확인,go test ./...를 SQLite fallback과 실제 PostgreSQL(scripts/test-postgres.sh) 양쪽에서 전 패키지 통과,go vet·go build·gofmt,npm test(130개)·npm run build(webui/dist무변경 확인),redocly lint openapi.yaml,cargo build --locked통과. - 보류 아이디어:
exportAssetsCSV가 limit(기본 10,000)에서 조용히 잘림. 목록 API는total·has_more를 주는데 CSV에는 아무 표시가 없음 (가치 3 / 위험 2 / M)- Query DSL이
attributes.<키>의 존재/부재 자체를 묻는 연산자를 제공하지 않음(지금은>= ""같은 우회가 필요) (가치 3 / 위험 2 / M) /api/v1/external/query/*API key 경로의 한도·감사 로그 커버리지 (가치 3 / 위험 2 / M)- 콘솔이 마지막 scope 체크박스를 비활성화하지 않아 이제 400을 받고서야 알게 됨(web 변경 시
webui/dist재빌드 필요) (가치 2 / 위험 2 / S)
- 릴리즈: v0.2.26 (2026-09-05)
Quantoss
- 선택: 전략 override(QUANTOSS_US_STRATEGY·QUANTOSS_PAPER_STRATEGY)가 실제 실행에 반영되지 않던 버그 수정 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공
- 요약:
runAgent가 전략을QUANTOSS_STRATEGY하나로 미리 한 번 해석해(mk) 모든 시장에 재사용하고 있었다.ForMarket("US")이m.Strategy = USStrategy를 세워도 그 값은 로그·리포트·알림 라벨에만 쓰여서,QUANTOSS_MARKET=BOTH+QUANTOSS_US_STRATEGY=vwap_reclaim은 미국 세션이 국내 전략으로 돌면서 기록만vwap_reclaim으로 남았다 — 이 프로젝트의 핵심인 전진 검증 기록이 통째로 거짓이 되는 결함이다. 직전 커밋(93b0b10)이 넣은QUANTOSS_PAPER_STRATEGY도 같은 이유로 무력했다:FromEnv가Mode=="paper"일 때만Strategy를 덮어쓰는데, 모의 병행 실행은 실거래와 같은.env(QUANTOSS_MODE=live)를 쓰고runAgent가cfg.Mode="paper"로 바꾸는 것은 그보다 뒤라 조건이 성립하지 않는다. 수정:ForMarket이 override 를Strategy필드에 확정(우선순위 PaperStrategy > USStrategy > Strategy, 멱등)해 실행 전략과 라벨이 늘 같은 값을 보게 하고,runAgent는 시장별로Registry를 조회하되 API 접속 전에 모든 시장의 전략 이름을 검증한다(몇 시간 뒤 시작하는 US 세션이 오타로 그제서야 죽지 않도록).backtest/optimize의-strategy기본값도marketCfg이후의cfg.Strategy로 바꿔-market US백테스트가 override 를 무시하던 문제를 함께 고쳤고,-strategy를 명시하면 override 보다 우선하도록 했다. 검증:internal/config/market_test.go에 우선순위·멱등·런타임 모드 전환 테스트 2개를 추가하고ForMarket을 옛 동작으로 되돌리면 4개 단언이 실제로 실패함을 확인. 바이너리 스모크로QUANTOSS_US_STRATEGY=nope_typo→ “알 수 없는 전략: nope_typo (시장 US)” 즉시 종료,QUANTOSS_MODE=live+QUANTOSS_PAPER_STRATEGY=nope2+paper→ “(시장 KR)” 로 잡히는 것,-strategy combo가 오타 override 를 이기는 것까지 확인.gofmt -l(clean)·go vet ./...·go build ./...·go test -count=1 ./...전부 통과. 커밋 0e5821c. - 보류 아이디어:
- GitHub Actions CI 없음 —
go build/go vet/go test/gofmt -l워크플로 추가 (가치 4 / 위험 1 / S) internal/journal·internal/notify테스트 0건 — Summary/MaxDrawdown, Load 날짜 필터, Notifier httptest 테스트 (가치 3 / 위험 1 / S)notify.Notifier를 구조체 리터럴로 만들면http가 nil (toss.Client 의 limiter nil 패닉과 같은 계열) — 지연 초기화 (가치 3 / 위험 1 / S)agent/github.go상태 표(평균가·현재가·손절·목표)가%.0f— 미국 종목 $150.32 가 “150”, $0.90 이 “1” 로 보임,market.FormatPrice(Country, ...)사용 (가치 3 / 위험 1 / S)Config.Validate가 신규 gap_reclaim 파라미터를 검사하지 않음 —GapMin >= GapMax면 신호가 영영 안 나오는데 조용히 통과 (가치 3 / 위험 1 / S)journal.Summary가 PnL==0 인 거래를 패배로 집계 (t.PnL > 0else) — 승률·평균손실이 미세하게 왜곡 (가치 2 / 위험 1 / S)
- GitHub Actions CI 없음 —
ReSSO
- 선택: 콘솔의 세션·API Key 미들웨어가 이쪽 장애를 “로그인이 필요합니다”로 답하던 문제 수정 (가치 4 / 위험 2 / 작업량 M)
- 결과: 성공 (커밋 b25bc63)
- 요약: 관리 콘솔의 인증된 요청은 전부 같은 조회(
SessionByToken)에서 시작하는데,requireSession·requireSessionOrAPIKey가 그 에러를 “세션 없음”과 같이 401authentication_required로 답했다. 콘솔은 그 답을 보여주지 않고 실행한다 — API 계층이 알리고, auth provider가 캐시된 신원을 지우고, 사람은 “세션이 만료되었습니다”를 달고 로그인 화면에 도착한다. 그래서sso_sessions장애는 콘솔을 저하시킨 게 아니라 전원을 동시에 로그아웃시키고 거짓 이유를 붙였다: 세션은 멀쩡했고, 돌려보낸 로그인 화면도 같은 이유로 실패하고 있었다. 변경 요청은 더 나빴다 — 읽지 못한 쿠키가 쿠키를 안 보낸 것처럼 API Key 분기로 흘러가, 로그인한 사람의 모든 편집에browser_session_required(“브라우저 로그인이 필요합니다”)가 나갔다.AuthenticateAPIKey도 같은 뭉개기를 했고/metrics수집이 그 경로로 인증하므로, 서비스를 가장 봐야 할 순간에 모니터링은 “키가 폐기됐다”는 답을 받았다. 아무것도 드러나지 않았다: 여기서 401은 로그인 안 한 방문자의 평범한 답이라 요청 카운터·접근 로그에는 한산한 시간대였고, store 에러는 로그 한 줄 없이 버려졌다. 이건 인증된 모든 요청의 첫 번째 store 조회라, 전면 장애에서 가장 먼저 닿고 그 뒤의 배려(이미 둘을 구분하는 핸들러들, 그 아래writeStoreError)는 실행되지 않는다. 이제store.ErrNotFound만 “로그인 안 함”이고 나머지는 500internal_error+ 어느 조회가 멈췄는지 로그. 빈 쿠키와 빈 Bearer는 store에 닿지 않으므로 로그인 안 한 방문자의 답은 그대로고, 세션 테이블 장애에도 API Key 클라이언트는 200을 받는다. 콘솔 쪽은 그 구분을 그대로 따랐다 — 401은 거절이고 500은 아니므로, 신원 조회가 다른 이유로 실패하면 로그인 화면으로 보내지 않고 화면에 머문 채 오류와다시 시도를 보여준다(ErrorAlert의 5xx 안내가 이미 맞는 문구다). 검증: 새 연동 테스트가sso_sessions와personal_api_keys를 차례로 RENAME으로 숨기며(테스트마다 전용 스키마), 수정 전 코드에서 다섯 건 모두 실제로 실패함을 확인했다 — GET/api/v1/me·PUT/api/v1/me/profile·POST/api/v1/auth/logout·GET/api/admin/v1/realms가 모두 401이었고 API Key도 401이었다. 자격증명 없는 호출이 여전히 401인 것과 세션 테이블 장애 중에도 API Key가 200인 것도 같은 테스트가 확인한다. 콘솔 테스트 2개를 더해 500이 만료로 읽히지 않는 것과identityError가 서는 것을 고정했다.make test전체 통과(exit 0) —go test -race ./...전 패키지 ok(httpserver 84s / store 82s), 연동 테스트 SKIP 0건,go vet,golangci-lint(0 issues),govulncheck(0),npm run lint,npm run test(22파일/106테스트, 디스크 파일 수와 일치),npm run build. 빌드가 만든webui/dist/index.html변경은 되돌렸다.docs/operations.md에 401/500 구분표와 Prometheus 수집 해석을 적었다. - 보류 아이디어:
login의 Realm 조회(RealmByName·RealmByID) 실패가 401invalid_credentials로 나가 사용자에게 “비밀번호가 틀렸다”고 말하고, 로그인 카운터도 움직이지 않는다 — 같은 핸들러의 다른 여섯 조회는 모두writeLoginError(500 +result="error")를 쓰는데 이것만 그 앞에서 돌아 예외다 (가치 4 / 위험 1 / S)login의AuthorizationRequestByToken실패가 400expired_request로 나가, 사용자는 RP에서 흐름을 다시 시작하고 같은 곳에서 다시 실패한다 (가치 3 / 위험 1 / S)authorization이id_token_hint를AuthorizationRequest에 저장하지 않아, 로그인 폼을 거친 뒤에는 hint가 지목한 계정과 다른 계정으로 로그인해도 코드가 나간다 (컬럼 추가 마이그레이션 필요) (가치 3 / 위험 2 / M)- UserInfo POST에서 form-encoded
access_token파라미터 수용 (RFC 6750 §2.2). 현재는 Authorization 헤더만 읽는다 (가치 2 / 위험 1 / S) oidcLogout이 hint의sub를 쿠키 세션의 사용자와 대조하지 않아, 다른 사람의 ID Token을 hint로 줘도 지금 로그인한 사람이 로그아웃된다 (스펙상 SHOULD) (가치 2 / 위험 2 / M)
← 대시보드 · Atom 피드 · 원본 데이터 runs.jsonl