Skip to content

fix(byok): keep the operator's environment key out of an own-credential client - #1014

Merged
Eomdahyeon merged 1 commit into
mainfrom
fix/issue-990-own-credential-env-keys
Oct 4, 2026
Merged

Eomdahyeon merged 1 commit into
mainfrom
fix/issue-990-own-credential-env-keys

Conversation

@Eomdahyeon

Copy link
Copy Markdown
Collaborator

Refs #990

문제

environment_keys=False 로 만든 클라이언트가 환경변수 키를 막지 못한다. provider_keys 에서 운영자 키를 빼도, kpubdata 클라이언트는 사용 시점에 없는 키를 환경에서 찾는다. 자기 키가 없는 사용자의 요청이 운영자 키로 실행된다.

이 PR 이 하는 것 — kpubdata 릴리스 없이 지금 할 수 있는 부분

  • _create_client(environment_keys=False) 가 Client(..., env_keys=False) 로 만든다.
  • kpubdata 0.8.0 에는 env_keys 가 없고 **extra 가 조용히 삼키므로, 시그니처를 먼저 확인한다(client_keeps_environment_keys_out()). 없으면:
    • 그런 클라이언트를 만들려는 시도는 RuntimeError — 환경 키로 내려가지 않는다.
    • serve 는 이 모드(및 이를 함의하는 멀티유저 모드)에서 기동을 거부한다 (종료 코드 1, 사유 출력). 완료 조건의 "조용한 무시 방지".
  • 기존 테스트의 구멍을 메웠다: "operator-key" not in repr(vars(client._config)) 는 폴백이 _config 에 저장되지 않으므로 항상 통과했다. 동작으로 바꿨다 — get_provider_key("datago") is None.
  • docs/deployment.md 의 설정 표, CHANGELOG [Unreleased] Security.

이 PR 이 하지 않는 것 — 그래서 Refs

  • 핀 상향. env_keys 를 담은 kpubdata 릴리스가 없다(chore(release): ship env_keys=False — Builder's own-credential gate cannot work on 0.8.0 kpubdata#780, 사람의 일). 핀은 >=0.8.0,<0.9 그대로다.
  • 그 결과 지금 설치되는 kpubdata(0.8.0)로는 own-credential/멀티유저 모드를 serve 할 수 없다. 이전에는 뜨기는 했지만 약속을 지키지 못했다. 이것이 의도한 실패이지만 배포에는 영향이 있다 — 현재 운영은 API 키 단일 사용자 모드라 해당하지 않는다(이슈 본문).
  • 완료 조건 3(운영자 키가 요청·로그·매니페스트에 없다는 누출 테스트)은 env_keys 가 있는 kpubdata 에서만 의미가 있어 넣지 않았다.

검증

  • uv run pytest (kpubdata 0.8.0): 4377 passed, 11 skipped, 16 deselected.
  • 새 테스트: env_keys 없는 Client → RuntimeError 이고 클라이언트가 만들어지지 않음 / 있는 Client → env_keys=False 가 전달됨 / serve 가 기동 거부.
  • test_the_real_client_cannot_reach_the_operators_key 는 설치된 kpubdata 에 env_keys 가 있을 때만 돈다 — 로컬(0.8.0)에서는 skip 됐다. kpubdata main ↔ Builder CI 잡에서 실행되는지는 이 PR 의 CI 에서 확인해야 한다.
  • ruff, mypy 통과

review:R3 — 작성자가 아닌 사람의 승인이 필요하다.

🤖 Generated with Claude Code

@Eomdahyeon
Eomdahyeon force-pushed the fix/issue-990-own-credential-env-keys branch from 725a537 to 6e6721c Compare October 4, 2026 13:35

@yeongseon yeongseon left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

리뷰 (코멘트, 승인 아님 — R3).

diff 를 읽었고 걸리는 점이 없습니다. 시그니처를 먼저 확인해 조용한 무시를 막은 것, 저장 여부가 아니라 동작(get_provider_key("datago") is None)으로 테스트를 바꾼 것이 핵심입니다.

  • 머지되면 kpubdata 0.8.0 에서는 own-credential·멀티유저 모드의 serve 가 기동을 거부합니다. 의도된 실패이고, 멀티유저는 이 PR 과 #1008, 그리고 env_keys 를 담은 kpubdata 릴리스가 모두 있은 뒤에 켭니다.
  • 그 kpubdata 릴리스(kpubdata#780)는 10월 정기 릴리스에 싣기로 했습니다 — 별도로 앞당기지 않습니다. 핀 상향은 그 뒤에.

@yeongseon yeongseon left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

승인합니다. 앞선 리뷰 코멘트 그대로입니다.

…al client

The client is built with env_keys=False, and a kpubdata without that option
is refused instead of silently falling back to the environment.

Refs #990

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Eomdahyeon
Eomdahyeon force-pushed the fix/issue-990-own-credential-env-keys branch from 6e6721c to 9577285 Compare October 4, 2026 14:42
@Eomdahyeon
Eomdahyeon merged commit 8568e94 into main Oct 4, 2026
21 checks passed
yeongseon pushed a commit that referenced this pull request Oct 6, 2026
Closes #1002
Refs #990, kpubdata-lab/kpubdata#780

kpubdata 0.9.0 이 PyPI 에 올라왔다(`https://pypi.org/pypi/kpubdata/json` →
`0.9.0`). kpubdata#812 가 정한 대로 핀을 옮긴다: `kpubdata>=0.9.0,<0.10`.

> **머지 전에 읽어 주세요 — 배포의 동작이 바뀝니다.** 아래 "0.9.0 카탈로그가 말하는 약관" 절.

## 변경

- `pyproject.toml`, `uv.lock`: `kpubdata>=0.8.0,<0.9` → `>=0.9.0,<0.10`.
잠금 파일에서 바뀐 것은 kpubdata 항목뿐이다(`uv lock --no-sources --upgrade-package
kpubdata`, `--check` 통과).
- **#1002:** kpubdata 의 private 모듈에서 가져오던 import 7개를 public 이름으로 바꾸고
allowlist 항목을 지웠다.
- `find_spec` (agent/monitor.py, agent/pipeline.py, cli.py),
`discover_specs` (cli.py), `KPubDataConfig` (service/providers.py,
verify/runner.py) — 이슈가 적은 6개.
- `SENSITIVE_PARAM_KEYS` (logging_redaction.py) — 0.9.0 이 public 으로
내보낸다(kpubdata#782). 이슈에는 "남는 항목"으로 적혀 있었지만 이제 필요 없다.
- 0.9.0 을 설치하면 게이트가 **스스로 이것을 요구한다**: 핀만 올린 상태에서
`test_the_repository_passes_against_the_installed_kpubdata` 가 실패했고, 메시지가
위 6개를 하나씩 짚었다.
- 남은 allowlist 는 `verify/runner.py` 의 7개다: `SpecExecutor`,
`check_payload_error`, `extract_items`, `extract_total_count`,
`ExampleSpec`, `SpecDefinition`, `HttpTransport`.
- `tests/unit/test_verify_cli.py`: `find_spec` 을 패치하는 대상을
`kpubdata.core.spec` → `kpubdata` 로. import 경로가 바뀌어 예전 대상은 더 이상 닿지
않는다(테스트 하나가 실패했다).
- `pyproject.toml` 주석, `CONTRIBUTING.md` 의 핀 설명, `cli.py` 의 안내 문구를 0.9
기준으로.
- CHANGELOG.

## #990 에 대해

#1014 가 "`env_keys` 없는 kpubdata 로는 이 모드로 기동하지 않는다"를 넣었고, 0.8.0 에서는 그 거부가
항상 걸렸다. 0.9 라인에서는 `Client` 가 `env_keys` 를 받으므로 다중 사용자 모드가 기동한다. 0.8.0 이라
건너뛰던 테스트(`test_require_own_credential.py` 의 `env_keys` 실동작 테스트)가 이제 **돌고
통과한다** — skip 수가 9 → 8 로 줄었다.

이슈의 세 조건이 이 핀으로 모두 충족되는지는 판단을 맡긴다. 그래서 `Closes` 가 아니라 `Refs` 다.
`client_keeps_environment_keys_out()` 확인은 남겨 두었다 — 누군가 더 낮은 버전을 설치한 환경을
위한 것이다.

## 0.9.0 카탈로그가 말하는 약관 — 배포에서 달라지는 것

설치된 0.9.0 카탈로그를 읽으면(`Client(...).datasets.list()`): 약관 선언 없음 134,
`unknown` 18, **`forbidden` 1**, `allowed` 3.

- **`datago.air_quality` 가 `forbidden` 이다.** 0.8.0 에서는 `redistribution`
선언이 없어 `unknown` 이었다. Builder 는 `forbidden` 에서 게시(공개·비공개 모두)와
`/query`·`/preview`·stage sample·warehouse 읽기·export·artifact 다운로드를 **모두
막는다**(#688). 이 핀이 들어가면 air_quality 로 만든 빌드는 Builder 밖으로 한 행도 나가지 않는다.
#759("air_quality 는 KOGL 3유형인데 변형해 게시된다")가 다루던 문제의 답이 이것이라면 의도된 결과다 — 다만
**데모·문서·e2e 가 air_quality 를 예제로 많이 쓴다**는 점은 확인이 필요하다. 단위 테스트는 약관 조회를
conftest 에서 고정하므로 이 변화를 보지 못한다.
- **baseline 의 16개는 `unknown` 이다** — kpubdata 가 직접 그렇게
보고하고(kpubdata#814), Builder 의 #1036 도 같은 판정을 한다. 16개 전부를 실제 카탈로그로 확인했다:
`source_verdict(...)` → 16개 모두 `unknown`. #1036 리뷰가 남긴 "핀을 올리는 PR 에서 봐야
한다"는 확인이 이것이다.
- `allowed` 로 남는 것은 `apt_trade`, `apt_rent`, `village_fcst` 셋이다.

## 검증

- `pytest tests/unit` (kpubdata 0.9.0 설치) → `4409 passed, 8 skipped` (8분
12초).
- `scripts/check_kpubdata_imports.py` → `checked 246 files: no unlisted
kpubdata private imports`.
- `ruff check`, `ruff format --check`, `mypy src` 통과.
- **하지 않은 것:** 실제 Builder 를 띄워 air_quality 빌드가 어떻게 답하는지 보지 않았다. e2e·데모
스크립트를 0.9.0 으로 돌리지 않았다. kpubdata 0.9.0 의 다른 breaking 변경(401 →
`AuthError`, 건수 0, NODATA, 자격 증명 요청의 리다이렉트 미추적)이 Builder 의 실제 provider
호출 경로에 주는 영향은 단위 테스트가 통과한다는 것까지만 확인했다.
- CI 의 `kpubdata 0.8.0` 잡은 이름이 워크플로에 박혀 있다 — 핀이 바뀌면 그 잡이 무엇을 설치하는지 확인이
필요하고, 워크플로 파일은 제가 고칠 수 없다.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Eomdahyeon <213566566+Eomdahyeon@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
yeongseon pushed a commit that referenced this pull request Oct 6, 2026
…dential mode (#1051)

Closes #990

#1050 리뷰가 남긴 것: "#990 은 누출 테스트(완료 조건 3)까지 보고 닫을지 정하면 됩니다". 그 테스트다. 코드
변경은 없다.

## 이슈의 세 조건과 근거

1. **키 없는 빌드가 거부된다** —
`test_require_own_credential.py::test_build_is_refused_before_any_client_exists`
(#1014). 이 PR 의 두 번째 테스트도 실제 client 로 같은 것을 본다.
2. **`env_keys` 가 없는 kpubdata 로는 이 모드로 기동하지 않는다** —
`test_serve_refuses_to_start_when_it_cannot_keep_the_promise`,
`test_a_kpubdata_without_env_keys_is_refused_not_silently_used` (#1014).
3. **누출 테스트: 운영자 키 문자열이 요청·로그·매니페스트 어디에도 없다** — 이 PR. 지금까지의 테스트는 client
가 **무엇으로 만들어지는지**(`env_keys=False`, 키 조회가 `None`)를 봤고, 실제로 **무엇이 나가는지**는
보지 않았다. 0.8.0 에서는 볼 수 없었다.

## 테스트 (`tests/unit/test_operator_key_never_leaves.py`)

진짜 kpubdata `Client`(`cli._create_client`)와 진짜 `HttpTransport` 로
`BuilderService.build` 를 돌리고, 소켓(`httpx.Client.send`)만 바꿨다. 운영자 키는
kpubdata 가 읽는 이름 셋(`KPUBDATA_DATAGO_API_KEY`, `DATAGO_API_KEY`,
`KPUBDATA_API_KEY`) 모두에 canary 값으로 넣었다.

- **자기 키가 있는 요청자의 빌드:** 요청이 실제로 나갔고, 나간 요청마다 요청자의 키가 있고 운영자 키는 없다. 응답
본문, DEBUG 로그(본문과 레코드의 `extra`), run 디렉터리의 모든 파일(매니페스트·스냅샷·이벤트·산출물)에 운영자
키가 없다. 요청자의 키도 로그와 디스크에 없다.
- **키가 없는 요청자:** 403 `provider_credential_required` 이고 **요청이 하나도 나가지
않는다**(나가면 테스트가 그 자리에서 실패한다).
- **측정이 실제로 보는지:** 스위치를 끄면 같은 키 없는 빌드가 운영자 키로 나가고, 위 캡처에 그 키가 잡힌다. 이것이
없으면 "없다"는 단언들이 아무것도 안 나가도 통과한다.

데이터셋은 `datago.apt_trade` 를 썼다 — 0.9.0 에서 약관이 `allowed` 라 키 말고는 빌드를 막는 것이
없다(`air_quality` 는 이제 `forbidden`).

## 검증

- `pytest tests/unit/test_operator_key_never_leaves.py
tests/unit/test_require_own_credential.py` → `14 passed`.
- `ruff check`, `ruff format` 통과. `# type: ignore` 없음.
- kpubdata 가 `env_keys` 를 받지 않으면 파일 전체가 skip 된다(핀이 0.9 라인이라 CI 에서는 돈다).
- 전체 스위트는 CI 에 맡긴다. 실제 provider 호출은 아니다 — 응답은 표준 envelope 한 건을 흉내 낸 것이다.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Eomdahyeon <213566566+Eomdahyeon@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants