[verified] Define non-file Git credential recipe

This commit is contained in:
2026-10-12 07:27:04 +00:00
parent d4db3f6a8e
commit 1e842a6e43
3 changed files with 129 additions and 2 deletions
+20 -1
View File
@@ -279,11 +279,30 @@ Runtime 실행에서 만든 server·합성 DB·임시 credential은 해당 workf
- credential 값은 role profile의 `.env`에만 저장하고 mode `0600`을 요구한다.
- worker에게 credential 값 또는 credential-bearing URL을 전달하지 않는다.
- Git 인증이 필요하면 short-lived askpass 또는 동등한 child-process mechanism을 사용하고 종료 시 제거한다.
- Git 인증은 아래 canonical non-file child-process mechanism을 우선 사용한다. 직접 실행하는 askpass 파일을 mode `0600`으로 만드는 모순을 피하며, 실행 권한을 추가하거나 credential 파일 권한을 완화하지 않는다.
- inherited credential, 다른 role의 credential 또는 interactive prompt로 fallback하지 않는다.
- 인증에 실패하면 public clone이나 unauthenticated API·content read를 initialized mirror의 증거로 대체하지 않고 중단한다.
- Worktree source, state, Git config, reflog message, commit, patch, test artifact 또는 push refspec에 credential을 기록하지 않는다.
### Non-file Git credential helper
이 패턴은 기존 Git credential protocol을 사용하는 POSIX child-process recipe이며 새 push adapter나 credential 저장소가 아니다. Lock 및 secret-bearing 파일의 기존 mode `0600` 계약은 유지한다. mode `0600` askpass 파일을 직접 실행하거나 chmod로 우회하지 않으며 이 패턴은 helper 파일을 만들지 않는다.
Main caller는 자신의 승인된 role profile `.env`에서 지정된 token 변수만 private child 안에서 읽고 role account identity와 exact HTTPS repository URL·branch/refspec·권한을 독립 검증한다. 다른 profile·ambient credential을 선택하지 않는다. 인증된 username과 token을 각각 child-only `GIT_ROLE_USERNAME`/`GIT_ROLE_TOKEN`에 넣으며 빈 값, CR/LF/NUL이 포함된 값은 주입 전에 거절한다. 이 변수는 새 영구 설정이나 `.env` key가 아니다. Parent/shared shell에 export하거나 값·request header·raw exception을 기록하지 않는다.
Child는 `GIT_TERMINAL_PROMPT=0`, `GIT_ASKPASS=/bin/false`, `SSH_ASKPASS=/bin/false`를 명시하고 승인된 최소 환경만 전달한다. 아래 첫 empty `credential.helper`는 inherited helper 목록을 지우고 다음 helper는 `get`에서만 값을 Git private protocol pipe로 공급한다. `store`/`erase`는 아무 값도 출력하거나 저장하지 않는다. Token은 argv·URL·Git config에 들어가지 않으며 모든 `-c`는 해당 호출에만 적용된다. Missing 변수는 실패하며 interactive나 다른 helper로 fallback하지 않는다.
```sh
git -c credential.helper= \
-c 'credential.helper=!f() { case "$1" in get) test -n "${GIT_ROLE_USERNAME:-}" && test -n "${GIT_ROLE_TOKEN:-}" && printf "%s\n" "username=$GIT_ROLE_USERNAME" "password=$GIT_ROLE_TOKEN";; store|erase) :;; *) return 1;; esac; }; f' \
-c credential.interactive=false -c remote.origin.mirror=false \
push --dry-run "$VERIFIED_REPOSITORY_URL" "$VERIFIED_REFSPEC"
```
`VERIFIED_REPOSITORY_URL`은 credential 없는 exact 승인 URL이고 `VERIFIED_REFSPEC`은 승인된 branch의 explicit non-force refspec이다. Canonical mirror remote/config를 변경하지 않는다. 먼저 위 exact destination dry-run으로 capability를 확인한 뒤 별도로 승인된 실제 push에서만 같은 credential option/env와 refspec을 사용한다. Dry-run은 실제 write 성공의 증명이 아니며 push 뒤 authoritative branch/PR readback과 fetch/local SHA 비교가 필요하다. 오류·timeout은 기존 reconcile 규칙을 따르고 automatic push retry하지 않는다.
Helper stdout은 Git 전용 pipe다. `git credential fill`의 credential-bearing stdout을 terminal, tool output, evidence 또는 log로 내보내지 않는다. Local synthetic protocol 검증은 이를 private child memory로만 capture하고 결과 boolean/exit code만 보고한다. Parent는 child의 전체 수명에 finite deadline을 적용하고 성공·실패·cancel 모두 wait/reap하여 credential 환경의 종료를 확인한다. 값이 담긴 파일/config를 만들지 않으므로 helper-file 삭제 단계도 없다. same-UID 환경은 cooperative trust이며 같은 UID의 악성 프로세스로부터 환경변수를 격리한다는 보장은 아니다. Network 인증·TLS·permission·lease/adoption 검증은 기존 caller 책임이며 이 recipe 자체가 증명하지 않는다.
## Fail-closed 조건
다음 중 하나라도 발생하면 cache를 사용하거나 자동 복구하지 않는다.