Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
### 토큰 파일 권한 테스트의 실제 파일 모드 검증

- 임시 파일이 현재 프로세스의 소속 그룹을 사용하도록 준비하고, 각 특수
권한 비트가 실제로 설정됐는지 먼저 확인한다. 공유 임시 디렉터리에서
setgid가 조용히 제거돼 안전한 파일을 거부하라고 요구하던 테스트를 고쳤다.
운영 토큰 검증은 변경하지 않으며 테스트 자식 프로세스에는 필요한 환경만
전달한다. 재현과 검증 범위는
[doctoring 기록](docs/doctoring/token-file-permission-fixture.md)에 남겼다.

### Strix gate names the sandbox bootstrap failure and retries it once

- `scripts/ci/strix_quick_gate.sh` gives the Caido sandbox bootstrap race (`loginAsGuest failed after 10 attempts` on `127.0.0.1:<port>`, upstream usestrix/strix#1036/#1037/#1056) its own bounded same-model retry budget, `STRIX_SANDBOX_BOOTSTRAP_RETRIES` (default 1), drawn on top of `STRIX_TRANSIENT_RETRY_PER_MODEL`. That budget is 0 in production because the gateway owns model failover, so the documented sandbox retry never ran: `argos` Strix run 34013128112 (2026-09-06) shows one attempt, `Docker image ready`, the proxy never reachable, Strix exiting after 240 s -- while the sidecar reported four ready and four deferred routes that were never called. The budget is charged in the same branch that grants the attempt, so a log matching the sandbox class together with a gateway class cannot extend the loop without charging it (caught by adversarial review of the first draft). The primary-scan verdict for that class now reads `STRIX_PROVIDER_UNAVAILABLE: STRIX_SANDBOX_UNAVAILABLE: the last Strix attempt ended in the sandbox bootstrap (...) after N sandbox-specific same-model retries (budget B); this verdict names Strix's sandbox, not the LLM gateway.` instead of `orchestrator/free exhausted`, stating only what the gate observed; the leading token is unchanged so the workflow's finding-free classification and its tests are untouched, and the second token lets the review census split sandbox outages from gateway ones (two of six recent Strix artifacts were this class). Refs #1948.
Expand Down
52 changes: 52 additions & 0 deletions docs/doctoring/token-file-permission-fixture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# 토큰 파일 권한 테스트의 실제 모드 검증

## 상태와 원인

2026-09-06 제안. 운영 토큰 loader의 보안 조건을 바꾸지 않는 테스트 수정이다.
기준 커밋은 `43024633eba9d96b0456970391360da5a171fbda`이며,
대상은 `tests/test_contextual_orchestrator_review_sidecar_contract.py`의
`test_token_loader_accepts_only_private_owned_single_line_files`다.

macOS 공유 임시 디렉터리에서 파일은 GID 0을 상속했지만 프로세스 GID는
20이었고 그룹 0에 소속되지 않았다. Python의 `chmod(0o2600)`은 오류 없이
돌아왔으나 실제 파일 모드는 `0600`이었다. 따라서 loader가 안전한 파일을
받아들였는데도 테스트가 거부를 기대했다. Linux도 권한 없는 호출자의 파일
그룹이 소속 그룹과 다르면 setgid를 오류 없이 제거할 수 있다고 명시한다.
이 Linux 설명은 표준 근거이며 이번 macOS 실행을 Linux 실행 증거로 보지 않는다.
([Linux man-pages project, 2026](https://man7.org/linux/man-pages/man2/chmod.2.html))

## 수정과 보존한 경계

테스트가 만든 파일만 `os.chown(file, -1, os.getgid())`로 준비한 뒤 모드를
설정한다. `-1`은 UID를 그대로 유지한다.
([Python Software Foundation, n.d.](https://docs.python.org/3/library/os.html#os.chown))
각 `1600`, `2600`, `4600` 모드를 `stat()`으로 확인한 다음 실제 shell loader의
거부를 검사한다. 모드를 흉내 내거나 검사를 skip하지 않는다. `0600` 허용,
`0644`·특수 비트·symlink·여러 줄 거부와 Actions 마스킹 검사는 유지한다.
자식 프로세스에는 PATH와 명시적 테스트 변수만 전달해 운영 환경을 상속하지 않는다.

## 재현과 검증

실제 모드 assertion을 추가한 `952963caabfb17aa000dbac21b66f894b851f851`은
loader 실행 전에 `0600 != 2600`으로 실패했다. GID 준비를 추가한
`fbe8a1f086544d12b8eedcca76eda086247088a8`은 동일한 격리 환경에서 해당 파일의
29개 테스트를 모두 통과했다. Python 3.14.6 / pytest 9.1.1의 기존 프로젝트
가상환경을 사용했으며 새 lock 설치 검증은 아니다.

프로젝트 가상환경에서 실행할 최소 검증 명령은 다음과 같다.

```sh
env -i PATH="$PATH" CI=true GITHUB_ACTIONS=true python -m pytest -q -W error \
tests/test_contextual_orchestrator_review_sidecar_contract.py
```

전체 `tests/` 결과와 최종 정확 HEAD는 PR 증거에 별도로 기록한다. 기준 main의
다른 HTTP 응답 정리 실패 11건은 [#1879](https://github.com/ContextualWisdomLab/.github/pull/1879)가
담당하며 이 테스트 수정에 운영 코드를 복사하지 않는다. 로컬 통과는 hosted
Checks, 독립 승인, 보호 병합 또는 조직 전체 Actions 적체 해소의 증거가 아니다.

## 참고 문헌

Linux man-pages project. (2026, February 8). *chmod(2)—Linux manual page (6.18).* https://man7.org/linux/man-pages/man2/chmod.2.html

Python Software Foundation. (n.d.). *os—Miscellaneous operating system interfaces: os.chown.* Python 3.14 documentation. Retrieved September 6, 2026, from https://docs.python.org/3/library/os.html#os.chown
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ def test_token_loader_accepts_only_private_owned_single_line_files(tmp_path: Pat
"""Exercise the loader's real file boundary, including mode and symlinks."""
token_file = tmp_path / "bearer.token"
token_file.write_text("synthetic-test-bearer", encoding="utf-8")
# A shared temporary root can assign a group that cannot retain setgid.
os.chown(token_file, -1, os.getgid())
token_file.chmod(0o600)
command = (
'set -euo pipefail; source "$TOKEN_LOADER"; '
Expand All @@ -187,7 +189,7 @@ def run(candidate: Path) -> subprocess.CompletedProcess[str]:
return subprocess.run(
["bash", "-c", command],
env={
**os.environ,
"PATH": os.environ["PATH"],
"GITHUB_ACTIONS": "false",
"TOKEN_LOADER": str(TOKEN_LOADER),
"CONTEXTUAL_ORCHESTRATOR_TOKEN_FILE": str(candidate),
Expand All @@ -205,7 +207,7 @@ def run(candidate: Path) -> subprocess.CompletedProcess[str]:
actions = subprocess.run(
["bash", "-c", command],
env={
**os.environ,
"PATH": os.environ["PATH"],
"GITHUB_ACTIONS": "true",
"TOKEN_LOADER": str(TOKEN_LOADER),
"CONTEXTUAL_ORCHESTRATOR_TOKEN_FILE": str(token_file),
Expand All @@ -224,6 +226,7 @@ def run(candidate: Path) -> subprocess.CompletedProcess[str]:

for special_mode in (0o1600, 0o2600, 0o4600):
token_file.chmod(special_mode)
assert (token_file.stat().st_mode & 0o7777) == special_mode
special_bits = run(token_file)
assert special_bits.returncode != 0
assert "must have mode 600" in special_bits.stderr
Expand Down
Loading