feat(toss): 토스증권 OpenAPI LLM 인프라 — CLI + 스킬 + opnix 배선 - #1050
feat(toss): 토스증권 OpenAPI LLM 인프라 — CLI + 스킬 + opnix 배선#1050greenheadHQ wants to merge 14 commits into
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthrough이 PR은 토스증권 OpenAPI 지원을 추가한다. 자격 증명 라우팅 문서, NixOS/opnix 연동, 토큰·API 호출·원장·알림용 Bash 라이브러리, endpoint metadata 생성기, CLI 진입점 및 테스트를 포함한다. Changes토스 OpenAPI CLI와 Secret Routing
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related issues
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
3367e18 to
c12e2f2
Compare
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (1)
modules/shared/scripts/lib/toss/api.sh (1)
37-63: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated raw-text redaction regex diverges from the canonical one in
ledger.sh.
redact_rawhere has only twogsubpatterns;ledger.sh'stoss_ledger_redact_json(itsredact_raw_stringhelper) has a third pattern forauthorization="bearer ..."-style raw text. Tracing the flow:toss_ledger_record_input→toss_ledger_record→toss_ledger_build_recordre-appliestoss_ledger_redact_jsonto.response.bodybefore persisting, so the missing pattern here isn't currently an active leak. Still, having two divergent copies of the same secret-redaction logic is a latent risk if this function's output is ever consumed elsewhere without going through the ledger's second pass.Consider extracting a single shared
redact_raw_stringjq function (e.g. exposed fromledger.sh) and reusing it here instead of maintaining two copies.♻️ Minimal fix: align the pattern set
def redact_raw: gsub("(?<prefix>authorization:[[:space:]]*bearer[[:space:]]+)[^[:space:]<>\"=,]+"; "\(.prefix)<redacted>"; "i") + | gsub("(?<prefix>authorization[[:space:]]*=[[:space:]]*\"?bearer[[:space:]]+)[^\"&<>,[:space:]]+"; "\(.prefix)<redacted>"; "i") | gsub("(?<prefix>\"?(access[_-]?token|client[_-]?secret|secret|password)\"?[[:space:]]*[:=][[:space:]]*\"?)[^\"&<>,[:space:]]+"; "\(.prefix)<redacted>"; "i");🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@modules/shared/scripts/lib/toss/api.sh` around lines 37 - 63, The raw-text redaction logic in toss_ledger_response_body_json is diverging from the canonical jq redaction used by toss_ledger_redact_json/redact_raw_string in ledger.sh. Update the local redact_raw helper to match the full pattern set, including the authorization="bearer ..." variant, or better, reuse a shared redaction helper from ledger.sh so both paths stay in sync. Keep the change scoped to toss_ledger_response_body_json and the shared redaction symbols it should mirror.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.claude/skills/using-toss-api/references/vendor/api-reference.md:
- Around line 108-109: The markdown in the rankings section is missing the
required blank-line boundary before the next heading, which markdownlint flags.
Update the generated content in the reference list around the rankings table so
there is one blank line after the list items (the
SellableQuantityResponse/StockInfo links) and before the `#### Market Indicators
— 시장 지표` heading, keeping the surrounding markdown structure unchanged.
In @.claude/skills/using-toss-api/references/vendor/llms.txt:
- Line 7: The current API guidance in the reference text applies the
X-Tossinvest-Account header too broadly to all account, asset, and order APIs.
Update the wording in the reference so GET /api/v1/accounts is explicitly
excluded from the account-header requirement and described as bearer-token only,
while keeping the X-Tossinvest-Account rule for the other account-related
endpoints; use the existing reference text in llms.txt to narrow the rule
without changing the overall OAuth 2.0 Client Credentials guidance.
In @.claude/skills/using-toss-api/references/vendor/overview.md:
- Around line 59-60: Narrow the account-header requirement in the overview and
quick-start text so it only applies to the API calls that actually need it. In
the referenced vendor overview sections, keep GET /api/v1/accounts described as
bearer-only, and update the nearby account/order/conditional-order wording so it
explicitly excludes that call while still mentioning X-Tossinvest-Account for
the applicable endpoints. Use the existing section text in the overview and the
quick-start example to make the same exception consistently.
In `@modules/shared/scripts/lib/toss/api.sh`:
- Around line 151-162: The invalid-token check in toss_response_is_invalid_token
currently relies on jq -e over a stream of multiple boolean results, so it only
reflects the last match instead of “any” match. Update the jq logic inside
toss_response_is_invalid_token to aggregate nested objects with any semantics
(for example by using any over .. | objects and checking .error/.code for
invalid_token), so a single matching error/code anywhere in response_file
correctly returns success.
- Around line 388-442: `toss_api_execute` leaves `tmp_dir` and `response_file`
behind on interrupts because cleanup only happens on explicit return paths.
Update the `toss_api_execute` flow to register an `EXIT` trap right after
creating the temp directory so the temporary response data is removed even if
the subshell is interrupted, and ensure the trap is cleared or reused safely
across the success/error paths just like `toss_api_call_once` does for its
tempfiles.
In `@modules/shared/scripts/lib/toss/doctor.sh`:
- Around line 78-99: The Toss credential fallback paths are duplicated between
toss_doctor_check_credentials in the doctor.sh script and the Nix module, so
move the filesystem path defaults into libraries/constants.nix and have both
places consume those constants. Update toss_doctor_check_credentials to
reference the centralized path values instead of hardcoding the SA token and
opnix file locations, and make the Nix module reuse the same constants so the
fallback behavior stays aligned.
In `@modules/shared/scripts/lib/toss/ledger.sh`:
- Around line 113-130: `toss_ledger_append_record` and `toss_ledger_record` are
swallowing all write-path failures, so update these functions to emit a warning
to stderr whenever `toss_ledger_file`, `toss_ledger_prepare`, `with_file_lock`,
or `toss_ledger_build_record` fails instead of returning silently. Keep the
existing control flow in `toss_ledger_append_record`, `toss_ledger_record`, and
the `with_file_lock` call, but add clear diagnostic messages that identify which
step failed and include the relevant file/input context so missing ledger
entries can be detected.
---
Nitpick comments:
In `@modules/shared/scripts/lib/toss/api.sh`:
- Around line 37-63: The raw-text redaction logic in
toss_ledger_response_body_json is diverging from the canonical jq redaction used
by toss_ledger_redact_json/redact_raw_string in ledger.sh. Update the local
redact_raw helper to match the full pattern set, including the
authorization="bearer ..." variant, or better, reuse a shared redaction helper
from ledger.sh so both paths stay in sync. Keep the change scoped to
toss_ledger_response_body_json and the shared redaction symbols it should
mirror.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 2e058d38-e6d4-45af-bd72-0d0dbcdb0a60
📒 Files selected for processing (29)
.claude/skills/managing-secrets/SKILL.md.claude/skills/managing-secrets/references/1password.md.claude/skills/using-toss-api/SKILL.md.claude/skills/using-toss-api/references/vendor/api-reference.md.claude/skills/using-toss-api/references/vendor/llms.txt.claude/skills/using-toss-api/references/vendor/openapi.json.claude/skills/using-toss-api/references/vendor/overview.md.gitleaks.tomllibraries/constants.nixmodules/nixos/configuration.nixmodules/nixos/options/homeserver.nixmodules/nixos/programs/toss/default.nixmodules/shared/programs/shell/default.nixmodules/shared/scripts/lib/file-lock.shmodules/shared/scripts/lib/toss/api.shmodules/shared/scripts/lib/toss/auth.shmodules/shared/scripts/lib/toss/curl.shmodules/shared/scripts/lib/toss/doctor.shmodules/shared/scripts/lib/toss/ledger.shmodules/shared/scripts/lib/toss/metadata.shmodules/shared/scripts/lib/toss/notify.shmodules/shared/scripts/toss.shmodules/shared/scripts/toss/endpoints.jsonscripts/ai/check-skill-noise.shscripts/toss/generate-endpoint-metadata.shtests/eval-tests.nixtests/lib/test-common.shtests/shell-script-tests.shtests/suites/toss-cli.sh
greenheadHQ
left a comment
There was a problem hiding this comment.
현재 head 389cac65 기준 추가 검토입니다. 기존 13개 해결 스레드와 그 수정사항은 제외했고, 별도로 재현·검증된 P1 5건과 P2 7건을 inline으로 남깁니다. P1은 merge 전 수정이 필요하다고 판단하지만, 요청에 따라 review action은 COMMENT로만 제출합니다.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
.claude/skills/using-toss-api/SKILL.md (1)
61-63: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winSmoke test를 read-only 또는
--dry-run으로 제한하세요.Line 58에서 주문 API가 실계좌이고 sandbox가 없다고 명시하면서, Line 63은 “실제 API 호출을 수반하는 항목”을 포괄적으로 smoke test하도록 안내합니다. 이 표현은 검증 과정에서 실제 주문 mutation까지 실행하도록 오해될 수 있으므로, smoke test는 read-only endpoint 또는
--dry-run으로만 수행하고 주문 mutation은 별도 명시적 승인 없이는 실행하지 않는다고 제한해 주세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.claude/skills/using-toss-api/SKILL.md around lines 61 - 63, Revise the “toss CLI 사용법” guidance so smoke tests are limited to read-only endpoints or the `--dry-run` option; explicitly state that order-mutating API calls must not be executed without separate explicit approval, especially since the API targets a live account..claude/skills/managing-secrets/SKILL.md (1)
92-100: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winMiniPC Toss credential materialization 상태를 일치시키세요.
Line 92와 Line 95는
#1044전까지 Toss credential이 materialize되지 않는다고 설명하지만, Line 100은 MiniPC opnix가 해당 credential을 materialize한다고 현재형으로 기술합니다. 실제 활성화 게이트와 blast radius를 잘못 판단하지 않도록 Line 100에도 비활성 상태를 명시해 주세요.권장 수정
-| MiniPC SA token (opnix tokenFile, host key 복호화) | ... | MiniPC opnix가 github-pat 및 토스 credential materialize 시 사용. | +| MiniPC SA token (opnix tokenFile, host key 복호화) | ... | MiniPC opnix가 github-pat을 materialize할 때 사용. Toss credential은 `#1044` 전까지 `homeserver.toss.enable = false`로 materialize하지 않음. |🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.claude/skills/managing-secrets/SKILL.md around lines 92 - 100, Update the “SA token (minipc)” row to explicitly state that MiniPC Toss credential materialization is currently inactive because homeserver.toss.enable = false until `#1044`, while retaining that github-pat materialization remains active and accurately describing the current Automation read-only blast radius.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In @.claude/skills/managing-secrets/SKILL.md:
- Around line 92-100: Update the “SA token (minipc)” row to explicitly state
that MiniPC Toss credential materialization is currently inactive because
homeserver.toss.enable = false until `#1044`, while retaining that github-pat
materialization remains active and accurately describing the current Automation
read-only blast radius.
In @.claude/skills/using-toss-api/SKILL.md:
- Around line 61-63: Revise the “toss CLI 사용법” guidance so smoke tests are
limited to read-only endpoints or the `--dry-run` option; explicitly state that
order-mutating API calls must not be executed without separate explicit
approval, especially since the API targets a live account.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 7583f286-47ab-418b-b1ae-f9e070aa3a8b
📒 Files selected for processing (11)
.claude/skills/managing-secrets/SKILL.md.claude/skills/managing-secrets/references/1password.md.claude/skills/using-toss-api/SKILL.mdmodules/shared/scripts/lib/toss/api.shmodules/shared/scripts/lib/toss/auth.shmodules/shared/scripts/lib/toss/curl.shmodules/shared/scripts/lib/toss/doctor.shmodules/shared/scripts/lib/toss/ledger.shmodules/shared/scripts/lib/toss/notify.shtests/shell-script-tests.shtests/suites/toss-cli.sh
🚧 Files skipped from review as they are similar to previous changes (3)
- modules/shared/scripts/lib/toss/curl.sh
- .claude/skills/managing-secrets/references/1password.md
- modules/shared/scripts/lib/toss/api.sh
|
@coderabbitai 리뷰(https://github.com/greenheadHQ/nixos-config/pull/1050#pullrequestreview-4669414709)의 outside-diff 지적 2건 반영 —
|
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
git log --oneline -5
echo "---"
git show 5bc63f46 --stat
echo "--- SKILL.md using-toss-api diff ---"
git show 5bc63f46 -- .claude/skills/using-toss-api/SKILL.md
echo "--- managing-secrets SKILL.md diff ---"
git show 5bc63f46 -- .claude/skills/managing-secrets/SKILL.mdLength of output: 5699
두 건 모두 의도대로 잘 반영되었습니다. 감사합니다. |
|
Caution Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted. Error details |
greenheadHQ
left a comment
There was a problem hiding this comment.
현재 HEAD 5bc63f46 재검토 결과입니다. 이전 review의 12건은 해결된 것을 확인했습니다. 아래는 격리 재현과 vendored OpenAPI 대조 후 새로 확정한 P1 2건·P2 6건입니다.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
.claude/skills/using-toss-api/SKILL.md (1)
104-105: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win메타데이터 생성 명령에
./를 붙이세요.저장소 루트에서
scripts/toss/generate-endpoint-metadata.sh를 그대로 실행하면 현재 디렉터리가PATH에 없을 경우 실패합니다.수정 제안
-scripts/toss/generate-endpoint-metadata.sh +./scripts/toss/generate-endpoint-metadata.sh🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.claude/skills/using-toss-api/SKILL.md around lines 104 - 105, 메타데이터 생성 명령에서 스크립트 경로 앞에 ./를 추가하세요. using-toss-api 스킬의 해당 명령이 scripts/toss/generate-endpoint-metadata.sh 대신 ./scripts/toss/generate-endpoint-metadata.sh를 실행하도록 수정하세요.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.claude/skills/using-toss-api/SKILL.md:
- Line 88: Update the description of the doctor command to remove “offline”
wording and describe it as a local, side-effect-free diagnostic that requires
network access to determine the source public IP, while making clear it does not
call the Toss API or modify credentials.
---
Outside diff comments:
In @.claude/skills/using-toss-api/SKILL.md:
- Around line 104-105: 메타데이터 생성 명령에서 스크립트 경로 앞에 ./를 추가하세요. using-toss-api 스킬의 해당
명령이 scripts/toss/generate-endpoint-metadata.sh 대신
./scripts/toss/generate-endpoint-metadata.sh를 실행하도록 수정하세요.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 63d27c2e-cb60-45be-a4e6-7f240334217c
📒 Files selected for processing (9)
.claude/skills/managing-secrets/SKILL.md.claude/skills/using-toss-api/SKILL.mdmodules/shared/scripts/lib/pushover.shmodules/shared/scripts/lib/toss/api.shmodules/shared/scripts/lib/toss/ledger.shmodules/shared/scripts/lib/toss/notify.shtests/shell-script-tests.shtests/suites/pushover-helper.shtests/suites/toss-cli.sh
🚧 Files skipped from review as they are similar to previous changes (3)
- modules/shared/scripts/lib/toss/notify.sh
- modules/shared/scripts/lib/toss/ledger.sh
- modules/shared/scripts/lib/toss/api.sh
greenheadHQ
left a comment
There was a problem hiding this comment.
현재 HEAD 673eedc7 재검토 결과입니다. 직전 review의 8건은 모두 해결된 것을 확인했습니다. 아래는 전체 PR 재검토와 격리 재현 후 새로 확정한 P1 2건·P2 2건, 그리고 잔존 P3 문서 drift 1건입니다.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@modules/shared/scripts/lib/toss/api.sh`:
- Around line 66-77: toss_validate_json_body() currently normalizes JSON through
jq, which can alter large integer values; replace the final jq -cs '.[0]'
pipeline with Python-based normalization consistent with
toss_strict_json_single_value, preserving numeric precision while emitting the
normalized single JSON value.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: e47f8bf4-16cc-4265-88e0-958820b6787b
📒 Files selected for processing (4)
.claude/skills/using-toss-api/SKILL.mdmodules/shared/scripts/lib/toss/api.shtests/shell-script-tests.shtests/suites/toss-cli.sh
b9d410b to
eabbf4b
Compare
* feat(skills): finding-unknowns — 지도-영토 미지 방법론 오케스트레이션 스킬 Thariq 'Finding Your Unknowns' 방법론(미지 4분면, 구현 전·중·후 사이클)을 메모리 텍스트에서 전역 스킬로 승격. toss 세션(#1050 작업) 실측에서 확인된 3개 구멍에 강제장치를 배선: - 구현 노트 유실 → create-pr이 implementation-notes.md를 PR 본문 CIR로 흡수, 흡수 확인 전 삭제·이동 금지 불변식 - 퀴즈 게이트 미작동 → finish-pr 머지 절차에 퀴즈 게이트 단계 신설 (명시적 스킵 시 사유를 후속 코멘트에 기록) - 후반 리뷰 국면 이탈 → 리뷰 루프 발견도 미지로 정의, CIR/Deviations 갱신 grilling/prototype/run-da/create-pr/finish-pr 조합·AskUserQuestion 결합이라 Codex 비노출(intentionallyNotExposed) + skill-neutral lint 제외로 등록. Claude-Session: https://claude.ai/code/session_01PbbrsHKxkzkaKn9iJdgYDH * docs(skills): 구현 노트 흡수 후 종료 동작 명시 — PR 성공 확인 후 삭제 CodeRabbit 지적 반영: 흡수 전 삭제·이동 금지만 있고 흡수 후 동작이 없어, 노트 파일이 남으면 wt cleanup(_wt_is_dirty의 git status --porcelain이 untracked 포함)이 dirty로 중단됨을 실측 확인. finding-unknowns 불변식(SoT)과 create-pr 새 PR/update 절차 양쪽에 '성공 확인 후 삭제, 실패 시 보존' 추가. Claude-Session: https://claude.ai/code/session_01PbbrsHKxkzkaKn9iJdgYDH * fix(skills): 방법론 강제장치를 세션·리뷰·런타임·merge 경계 너머로 연장 — P1 리뷰 6건 반영 - durable marker: create-pr 흡수 계약이 PR 본문에 hidden marker를 기록, finish-pr 퀴즈 게이트의 1차 판별 신호로 사용 (별도 세션에서 게이트 증발 방지) - provenance guard: 노트 1행 owner header + untracked 확인 + 본문 반영 확인 3중 조건 충족 시에만 흡수·삭제 (전역 스킬의 동명 파일 오삭제 방지) - 흡수 계약 SSOT: 새 PR 경로 정의를 정본으로 update 경로가 참조, '새로 발견된 미지' 섹션 유실 해소 - 리뷰 국면 실배선: review-pr-feedback Step 5.5 신설 — marker PR에서 유효 피드백 반영 시 resolve 전 CIR/Deviations 증분 동기화 (stale 퀴즈 방지) - 질문 도구 런타임 매핑: 퀴즈 출제를 run-da runtime-mapping SoT에 연결, 한 문항씩 순차 출제 고정 (Codex plain-text 퇴행 방지) - merge 재고정: 퀴즈 통과 직후 headRefOid/body 재조회 + 변경 시 퀴즈 재시작, merge는 --match-head-commit으로 SHA 고정 (TOCTOU race 봉쇄) Claude-Session: https://claude.ai/code/session_01PbbrsHKxkzkaKn9iJdgYDH * fix(skills): CodeRabbit 리뷰 3건 반영 — body-file 전달·세 섹션 검증·기각 기록 동기화 - create-pr: PR 생성/업데이트 본문을 --body-file로 통일 (multiline 본문의 argv/프로세스 목록 노출 방지, review-pr-feedback 전달 규칙과 정합) - create-pr: 노트 삭제 조건 ③을 '세 섹션 각각 + marker 모두 반영'으로 명시 (일부 섹션만 반영된 상태로 통과 금지) - review-pr-feedback Step 5.5: skip 기준을 코드 무변경에서 '새 CIR 기록 전무'로 교체 — 기각(DESIGN_TRADEOFF 등)이 남긴 설계 결정·이관 미지도 동기화 대상 Claude-Session: https://claude.ai/code/session_01PbbrsHKxkzkaKn9iJdgYDH
greenheadHQ
left a comment
There was a problem hiding this comment.
현재 HEAD eabbf4b3 기준 Drift/Security 전체 재검토 결과입니다. 기존 inline thread 40개가 모두 resolved인 것을 확인하고, current HEAD에서 새로 재현되는 P1 3건·P2 4건·P3 1건만 남깁니다.
추가로 PR 본문의 재시도 불변식은 아직 “HTTP 401 또는 non-2xx body code에서 재시도”라고 되어 있지만, 현재 구현(api.sh:215-230)과 회귀 테스트는 안전하게 완결된 HTTP 401에서만 재시도하도록 바뀌었습니다. 구현은 맞으므로 PR 본문도 401-only로 동기화해 주세요.
검증: devShell 전체 shell suite 229/229, Nix eval true, ShellCheck, gitleaks 통과. 커밋된 vendor OpenAPI 30 operations와 generated metadata 30 endpoints는 byte-identical 재생성되지만, 아래처럼 live vendor 및 future-ref drift 경로가 남습니다.
|
@greenheadHQ 재검토(#1050 (review)) 감사합니다. P1 3건·P2 4건·P3 1건 모두 지적하신 재시도 불변식 문서 stale도 반영했습니다: PR 본문의 "HTTP 401 또는 non-2xx body code에서 재시도" 서술을 실제 구현( |
greenheadHQ
left a comment
There was a problem hiding this comment.
현재 HEAD fe42f7ab 기준으로 이전 8건의 반영을 다시 검증했습니다. 기존 48개 inline thread는 모두 resolved였습니다.
Security
- P1 1건: runtime env가 origin pin을 완전히 해제하며, host-only 비교도 exact base를 보장하지 않음
- P2 1건: leading
//OAuth route alias가 raw auth guard를 우회함
Spec / Drift
- P2 1건: OpenAPI
$ref처리가 일부 유효 reference 형태에서 fail-open/silent-drop - P3 1건: docs-refresh checklist가 생성되지 않는 필드를 요구함
Standards
- P3 1건: 새 opnix 절대경로가
constants.nixSoT 규칙을 우회함
검증: devShell shell suite 234/234, Nix eval true, ShellCheck(warning 이상), gitleaks 통과, committed vendor OpenAPI → metadata 재생성 byte-identical. 기존 live vendor refresh 건은 답글에서 #1054로 범위를 명시했으므로 중복 제기하지 않았습니다.
|
@greenheadHQ 재검토(#1050 (review)) 감사합니다. P1 1건·P2 2건·P3 2건 모두 특히 P1은 이전 반영이 불완전했던 것을 정확히 짚어주셨습니다 — escape hatch 제거 + host-only 비교를 base URL exact-match로 강화했습니다. Spec 쪽 chained/Path-Item |
greenheadHQ
left a comment
There was a problem hiding this comment.
현재 HEAD 1277b359 fresh 재검토 결과입니다. 직전 5건과 기존 53개 thread가 모두 resolved인 것을 확인했습니다.
Security
직전의 arbitrary-origin escape hatch, base path suffix, leading // OAuth alias는 current HEAD에서 닫혔습니다. 새 P1은 없습니다.
Spec / Drift
- P2 2건: parameter
$reffail-close가 direct/short-circuit/terminal-shape에서 아직 부분 구현, HTTP header casing drift - P3 1건: dry-run이 live exact-origin validation을 재현하지 않음
Standards
- P3 2건: official base literal 중복, repo-direct credential path가 새 constants SoT를 우회
PR 본문 Human Test Plan 2번도 현재 구현과 달리 biometric/SA 경로라고 적혀 있습니다. auth.sh는 Mac에서 SA token file을 필수로 사용하고 biometric fallback이 없으므로 SA 경로로 동기화해 주세요.
검증: devShell shell suite 237/237, Nix eval true, ShellCheck(warning 이상), gitleaks 통과, committed OpenAPI → metadata 재생성 byte-identical. 기존 live vendor refresh 건은 #1054 범위로 확인되어 중복 제기하지 않았습니다.
|
@greenheadHQ 재검토(#1050 (review)) 감사합니다. P2 2건·P3 3건 모두 지적하신 대로 직전 **Human Test Plan 2번의 |
a0ab51e to
b979eac
Compare
greenheadHQ
left a comment
There was a problem hiding this comment.
새 HEAD b979eac2 기준으로 전체 diff를 다시 확인했습니다. 직전 리뷰의 direct/later broken ref, header casing, base URL SoT, repo-direct opnix fallback pin, dry-run origin 검증 수정은 모두 재현 검증을 통과했습니다. 다만 LLM credential/safeguard 경계의 Security 2건과 vendor drift 시 metadata를 조용히 오염시키는 2건, 선언 경로 SoT drift 2건이 새로 확인되어 인라인으로 남깁니다. 전체 shell suite 251/251, eval true, ShellCheck, gitleaks, metadata byte-identical 재생성은 통과했습니다.
|
@greenheadHQ 재검토(#1050 (review)) 감사합니다. P1 2건·P2 2건·P3 2건 모두
한 가지 배포 검증 항목: toolchain pin에서 |
강한 코드 리뷰 피드백 루프 — 1라운드 결과Opus 4.8 + max 추론레벨, 4개 관점 병렬 오케스트레이션(ultracode workflow)으로 reviewer 결과
정확성·설계·회귀 관점에서 새 결함이나 decision-regression은 발견되지 않았습니다. 유지보수성 2건만 나왔고, 종합 판정은 둘 다 "실질 버그 아님, DRY 개선 제안"이었습니다. 사용자 승인 후
검증: 동작 불변 확인(M-1 결과 동일 + config 형태 추가 방어, M-2 exact/template 모두 hard floor 유지), toss 스위트 48건·eval-tests 통과. |
7d4fceb to
5a770cc
Compare
greenheadHQ
left a comment
There was a problem hiding this comment.
현재 HEAD 5a770cc 전체 재검토 결과입니다. 기존 64개 inline thread는 모두 resolved였고, standards/spec/runtime 독립 검토와 격리 fixture로 새로 확인된 P1 1건·P2 3건만 남깁니다.
LLM(Claude/Codex)이 토스증권 OpenAPI를 사용할 수 있는 인프라. 얇은 범용
`toss` CLI + Agent Skill + 1Password/opnix 시크릿 배선으로 구성한다.
- toss CLI: dispatcher(toss.sh) + lib/toss/{auth,api,metadata,ledger,doctor,notify}
- 토큰: OAuth2 CC, 휘발 경로 캐시(getconf 절대경로 fail-closed), 401 시 1회 재발급
- api: metadata 기반 X-Tossinvest-Account 자동 주입, template path 매칭, 계좌 미해결 시 fail-closed
- 주문 원장: requiresOrderSafeguards 시 redacted 구조화 기록(0700/0600, Authorization·토큰 기록 금지)
- dry-run, exit-node preflight(Mac), Pushover 알림(기본 ON)
- endpoint metadata: openapi.json → endpoints.json 생성기(rateLimitGroup 파싱, pathRegex, isKnownOrderMutation)
- with_file_lock: flock/lockf 분기 generic 헬퍼(macOS flock 부재 대응)
- 시크릿: Mac은 SA token op read, miniPC는 신규 opnix toss 모듈(user-owned 0400)
- Agent Skill using-toss-api: 인증·rate limit·함정 4종·SoT 규칙 + 공식 문서 vendor
- managing-secrets 문서: SA blast radius 경계 확장 명시(전용 vault 분리는 후속)
커밋 게이트: 외부 vendor 문서(references/vendor/)를 noise-check·gitleaks 예외로 추가.
계획 리뷰(6라운드, 30건 확정 반영). 후속: #1039(정기 잡), #1044(전용 vault+SA 분리).
스모크 테스트(실제 토큰·API)와 nrs 활성화는 후속. 새 스킬이라 SKIP_AI_SKILL_CHECK로
커밋(nrs 투영 후 verify-ai-compat 재검증 필요).
Claude-Session: https://claude.ai/code/session_01X3wfUEX71YnHbyw8mHwi5X
CodeRabbit + 셀프 리뷰에서 나온 유효 피드백 9건 반영: - invalid_token 판정: jq가 마지막 매칭 객체만 보던 것을 any()로 수정 (앞 객체가 invalid_token이어도 재시도 skip되던 버그) - tmp_dir signal cleanup: raw 응답(계좌/주문 데이터)이 SIGINT/SIGTERM 시 /tmp에 남던 것을 EXIT trap으로 정리 - 주문 원장 write 실패: lock timeout/디스크 오류 시 조용히 드롭되던 것에 stderr 경고 추가 (best-effort 유지) - SA blast radius: miniPC opnix toss credential materialization을 enable=false로 (전용 vault/SA 분리 #1044 전까지 경계 확장 최소화) - gitleaks allowlist: 모든 스킬 vendor를 열던 예외를 using-toss-api로 축소 - isKnownOrderMutation: rateLimitGroup 정확 일치 외에 order-path non-GET mutation도 fail-closed로 safeguard true - credential 파일 경로: doctor.sh/opnix 모듈 중복을 constants.nix SSOT로 통합 - vendor 문서 whitespace 정리 앞선 결정과 충돌하는 피드백(unknown endpoint 호출 정책, accountSeq 평문, vendor accounts 헤더 예외)은 계획/사용자 결정 우선으로 기각. Claude-Session: https://claude.ai/code/session_01X3wfUEX71YnHbyw8mHwi5X
P1:
- curl -g(globoff) 강제 + PATH origin-relative 검증 — {A,B} glob이 한 명령을
다중 실주문으로 확장하는 경로 차단
- 민감값(token/client secret/주문 body·응답)의 jq argv 노출 제거 — stdin/
JSON stream 전달로 전환 (curl.sh quote, urlencode, token cache, ledger 경로)
- Tailscale exit node 감지를 공식 스키마(.ExitNodeStatus, PeerStatus.ExitNode
boolean)로 수정, 상태 미확정 시 safeguarded 호출 fail-closed
- 401 동시 처리 CAS — lock 안에서 실패 token == 현재 cache일 때만 재발급,
타 프로세스가 갱신한 token은 재사용해 상호 무효화 방지
- ledger 최초 동시 기록 truncate TOCTOU 제거 — 파일 생성을 lock 안 append(>>)로
P2:
- 다중 JSON 값 응답은 raw wrapper로 기록 (jq -es 단일 문서 검사), record-input
실패 stderr 경고
- credential/API/doctor curl에 -q 선행 — 사용자 .curlrc 개입 차단 (실측 재현)
- ledger·Pushover에 전파되는 path의 query string sanitize (?<redacted>)
- Pushover 알림 실패 관측화 — stderr 경고 + 원장 notificationStatus 기록
- X-RateLimit-*/Retry-After whitelist를 stderr(toss-rate-limit:) + 원장에 노출
- doctor 문구를 실제 보장 수준으로 하향 (present / locally-unexpired)
- MiniPC toss opnix 문서에 #1044 전 비활성 게이트 명시 (managing-secrets)
테스트: toss 스위트 11건 신규 (origin-relative 거부, -q -g argv, CAS 재사용,
multi-JSON raw, append 보존, path sanitize, notify 실패/성공 기록, rate-limit
whitelist, exit-node ON 차단/unknown fail-closed)
Claude-Session: https://claude.ai/code/session_01X3wfUEX71YnHbyw8mHwi5X
…ipc) 행 게이트 명시 - using-toss-api SKILL.md: 스모크 테스트를 read-only/--dry-run으로 한정하고 주문 mutation은 사용자 별도 명시 승인 필수로 제한 (실계좌·sandbox 부재) - managing-secrets SKILL.md: SA token (minipc) 행의 토스 credential 사용 서술에 #1044 전 비활성 게이트 명시 (지난 커밋에서 92·95행만 고치고 100행 누락). blast radius는 SA 읽기 범위라 게이트와 무관함을 병기 Claude-Session: https://claude.ai/code/session_01X3wfUEX71YnHbyw8mHwi5X
P1: - 2xx 응답에는 재시도 금지 (이중 주문 차단). vendored 스펙상 인증 실패는 401이고 body code는 invalid-token/expired-token(hyphen)이며 invalid_token(underscore)은 WWW-Authenticate 헤더 표기다. 재시도는 401 또는 non-2xx 거부 응답의 body code에만 트리거되도록 좁히고, hyphen/underscore·expired 표기를 함께 매칭 - --data를 정확히 JSON 값 1개로 강제 (jq -es). 다중 문서는 live data-binary에만 두 문서가 실리고 원장/dry-run에는 첫 문서만 남는 불일치를 만들었다. body_provided를 별도 추적해 명시적 `--data null`과 body 미제공을 구분 P2: - dry-run 출력 .url에 남던 raw query secret 제거 (sanitized path 사용) + url을 jq argv가 아닌 stdin으로 전달 - 공용 pushover_send를 curl -q -g + 0600 config로 전환 — PUSHOVER_TOKEN/USER와 주문 path/account가 argv(ps)에 오르던 경로 차단, .curlrc 개입도 차단. config 값 escape로 추가 url 지시자 주입 방지. 호출자 5곳 시그니처 보존 - correlation ID(invocationId) 도입 — response record와 notify record가 공유, notify record에 accountSeq 추가 - ledger의 `// null` 제거 — jq에서 false가 null로 강등되던 문제 (request/response 양쪽) - using-toss-api SKILL.md: doctor 보장 수준을 presence/locally-unexpired로 하향, 운영 가능 host(Mac only, MiniPC는 #1044 전 비활성) 명시. PR 본문 동기화 테스트: toss 6건 신규 (2xx 무재시도, 다중 문서 거부, 명시적 null body, dry-run url sanitize, false body 보존, invocationId 공유), pushover 1건 신규(config escape) + 기존 4건을 config 검사로 갱신. toss 26건·pushover/folder-actions 10건 통과 Claude-Session: https://claude.ai/code/session_01X3wfUEX71YnHbyw8mHwi5X
…ment 차단 P1: - 재시도를 완결된 HTTP 401(curlExit==0)로만 제한. 이전엔 2xx만 제외해 4xx(≠401)·5xx· 전송 실패(000)에서도 token-like body면 재전송돼 이중 주문 위험이 남았다. vendored 스펙상 인증 실패는 401에만 정의되므로 body code substring 재시도를 제거했다. - dot-segment path 거부. /api/../oauth2/token은 metadata unknown + auth guard를 통과하지만 curl이 /oauth2/token으로 정규화해 금지된 token endpoint에 도달한다. 입력 단계에서 `.`/`..`(%2e·%2E 인코딩 변형 포함) segment를 거부 P2: - --data를 표준 JSON 파서(python3)로 검증. jq는 NaN→null, Infinity→큰수, +1/01→1로 금융 body를 조용히 바꾸고 다중 문서도 통과시켰다. python3로 "정확히 표준 JSON 값 하나"를 강제(부재 시 fail-closed, --data 주문 경로 전용 의존) - auth endpoint 거부·origin-relative·dot-segment 오류 메시지의 raw path를 toss_ledger_sanitized_path로 sanitize (?client_secret=... stderr 유출 차단) P3: - using-toss-api SKILL.md 401 복구 설명을 실제 CAS 동작으로 수정. doctor는 "오프라인" 대신 "로컬·side-effect 없음(단 공인 IP 확인엔 네트워크 필요)"으로 정정 테스트: toss 5건 신규 (non-2xx 무재시도, 전송 실패 무재시도, 비표준 JSON 숫자 거부, dot-segment 거부, auth 거부 메시지 sanitize). 이전 라운드의 non-2xx body 재시도 테스트는 동작 제거에 맞춰 무재시도 검증으로 대체. toss 31건 통과 Claude-Session: https://claude.ai/code/session_01X3wfUEX71YnHbyw8mHwi5X
CodeRabbit 지적: jq 정규화(jq -cs '.[0]')는 jq≤1.6에서 2^53 초과 정수를 9007199254740993 → 9007199254740992로 손실시킨다. --data body가 큰 숫자를 담으면 실제 전송 body가 왜곡될 수 있다. - toss_validate_json_body의 최종 정규화를 jq에서 python json.dump로 교체. 검증(strict parse)과 정규화를 하나의 python 경로로 통일해 임의정밀도 int를 보존하고, ensure_ascii=False로 UTF-8(한글 종목명 등) 유지, allow_nan=False로 방어. 정규화 출력이 전송 body·ledger·dry-run의 단일 SoT다. (이 저장소 jq는 1.8이라 현재는 무손실이나, 배포 jq 버전과 무관하게 안전하도록 통일) 테스트: toss 1건 신규 (2^53+1 정수가 --data → dry-run body에서 보존). 기존 비표준 숫자 거부·다중 문서 거부·false body 보존 테스트 회귀 없음. toss 32건 통과 Claude-Session: https://claude.ai/code/session_01X3wfUEX71YnHbyw8mHwi5X
…-encoding 차단, python pin
P1:
- credential/token 전송 origin을 공식 host(openapi.tossinvest.com)로 고정
(toss_require_trusted_base_url). TOSS_API_BASE_URL은 env override 가능해 검증 없이는
caller가 client secret·bearer token을 임의 host로 빼돌리는 confused-deputy 경로였다
(--proto =https는 scheme만 제한). 격리 테스트는 TOSS_ALLOW_INSECURE_BASE_URL=1 opt-in
- path segment의 percent-encoding 거부. /%6fauth2/token 등은 raw 문자열 auth 판정을
통과하지만 서버가 RFC 3986 unreserved를 decode해 token endpoint로 라우팅한다
- toss wrapper에 TOSS_PYTHON=${pythonWithTomlkit}/bin/python3 pin + api.sh가 그 절대경로
사용. ambient PATH의 python3가 mise shim으로 resolve되어 주문/dry-run이 hang하던
운영 host 실버그 (테스트 배포 레이아웃도 non-mise python 주입으로 동형화)
P2:
- generate-endpoint-metadata.sh: unresolved parameter $ref를 generation 실패로 처리.
broken/external ref가 account-required endpoint를 조용히 account-free로 만들던 drift
- configuration.nix 주석 정정: homeserver.toss.enable=false는 로컬 materialization만
차단하며 SA vault read ACL은 이미 확장됨(SA token 탈취 시 gate 무관하게 토스 item 접근)
- .agents/skills/using-toss-api projection symlink 추가 (Codex가 발견 가능하도록)
- docs-refresh의 llms.txt URL을 현재 공식 origin(developers.tossinvest.com)으로 수정
(openapi.tossinvest.com/llms.txt는 404)
P3:
- --data 정규화가 소수 lexeme도 보존 (parse_float=Decimal + 재귀 직렬화). 현행 토스
schema는 number 필드가 없어 영향 없으나 unknown/future endpoint drift 대비
테스트: toss 6건 신규 (unresolved ref 실패, untrusted base URL 거부 + opt-in 허용,
percent-encoded path 거부, decimal lexeme 보존). endpoints.json 재생성 diff 없음.
toss 37건·pushover/folder-actions·eval-tests 통과
Claude-Session: https://claude.ai/code/session_01X3wfUEX71YnHbyw8mHwi5X
…/ 차단, chained-ref 이전 P1 반영의 불완전함을 재검토에서 짚은 후속: P1: - base URL 검증을 exact-match로 강화하고 escape hatch 제거. host-only 비교는 https://openapi.tossinvest.com/oauth2 같은 path 접미사를 허용해 raw PATH /token이 /oauth2/token으로 합성되고, TOSS_ALLOW_INSECURE_BASE_URL은 같은 ambient caller가 pin을 해제하는 우회로였다. 이제 base URL이 공식 origin과 정확히 일치할 때만 통과 P2: - 빈 path segment(//) 거부. //oauth2/token은 origin-relative(/*)를 통과하지만 서버가 /oauth2/token으로 라우팅해 auth guard를 우회한다 - $ref 검증 확장: chained ref(AccountAlias -> AccountSeq)를 재귀 resolve(cyclic은 depth 한도로 차단)하고 Path Item $ref는 실패 처리. type==object만으론 첫 resolve가 다시 Reference Object인 chain을 걸러내지 못해 account-required가 account-free로 누락됐고, Path Item $ref는 operation iteration이 조용히 무시했다 P3: - opnix materialization root(/run/opnix)를 constants.paths.opnixRuntimeRoot SoT로 추출. toss default.nix와 shell/default.nix가 이 한 값에서 조합 (CLAUDE.md 하드코딩 경로 규칙) - SKILL.md docs-refresh 체크리스트에서 생성되지 않는 pathParamNames 필드 제거 테스트: toss 4건 신규 (base URL path 접미사 거부, // 거부, chained ref 해소, Path Item ref 실패), untrusted base URL 테스트를 exact-match 문구로 갱신. escape-hatch 테스트 제거. endpoints.json 재생성 byte-identical. toss 39건·eval-tests 통과 Claude-Session: https://claude.ai/code/session_01X3wfUEX71YnHbyw8mHwi5X
… SoT $ref 검증과 origin pin의 잔여 우회를 마저 닫는 후속: P2: - parameter $ref fail-close 완결. raw `$ref == AccountSeq` 단축 평가와 any(...)의 short-circuit을 제거하고, 전체 parameter 배열을 먼저 재귀 resolve + terminal Parameter shape(name+in) 검증한 뒤 account 여부를 계산한다. 이전엔 (a) AccountSeq component 삭제/self-cycle이 shortcut으로 통과, (b) account=true 뒤의 broken ref 미평가, (c) Schema 등 wrong-object로 끝나는 chain이 조용히 false였다 - account header 이름을 case-insensitive 비교(ascii_downcase). RFC 9110 field name은 case-insensitive라 lowercase inline header가 조용히 requiresAccount:false가 됐다 P3: - 공식 base URL literal을 TOSS_TRUSTED_API_BASE_URL 한 곳에서만 정의하고 TOSS_API_BASE_URL 기본값을 파생. 중복 시 origin 변경 때 한쪽만 갱신되면 기본 invocation이 exact-pin 검사에서 스스로 거부되던 문제 - dry-run도 non-network exact-origin 검증을 수행하도록 toss_require_trusted_base_url을 dry-run 분기 앞으로 이동. untrusted base의 dry-run이 evil URL을 출력하던 것 차단 - auth.sh opnix path fallback을 constants.paths.opnixRuntimeRoot+filename SoT와 동기화하도록 eval-tests(5b-3c)에 drift 핀 추가 (op reference fallback 핀과 동형) PR 본문 Human Test Plan 2번의 "biometric/SA 경로"를 실제 구현(Mac SA token file op read, biometric fallback 없음)에 맞게 정정. 테스트: toss 5건 신규 (dry-run untrusted 거부, shortcut broken ref 실패, case-insensitive header, account=true 뒤 broken ref 실패), eval-tests 5b-3c opnix path drift 핀. endpoints.json 재생성 byte-identical. toss 44건·eval-tests 통과 Claude-Session: https://claude.ai/code/session_01X3wfUEX71YnHbyw8mHwi5X
…r, toolchain pin
P1 (LLM credential/safeguard 경계):
- order-path mutation을 requiresOrderSafeguards runtime hard floor로. TOSS_ENDPOINTS_FILE로
분류표를 변조해 /api/v1/orders를 isKnownOrderMutation:false로 만들면 원장/알림/preflight가
꺼지던 우회를, metadata와 무관하게 order-path(/orders·/conditional-orders)+mutation method면
항상 safeguard를 켜는 floor로 차단 (generate의 order-path 규칙과 동형)
- toss wrapper가 credential-touching toolchain(curl·op·jq·coreutils 등)을 Nix store로 PATH
선두 pin + pinned bash로 raw script exec. ambient PATH 선두의 fake curl/op이 exact-origin
pin을 통과한 채 Authorization/client secret을 읽던 경로 차단 (op은 _1password-cli store화)
P2 (vendor drift silent metadata 오염):
- rate-limit marker 없는 operation을 drop하지 않고 rateLimitGroup:null emit. jq capture의
no-match empty stream이 `... as`에서 endpoint 전체를 조용히 삭제하던 문제 (`// null` 복구)
- parameter $ref의 각 hop을 #/components/parameters/ 아래로 제한. apiKey Security Scheme처럼
name+in shape가 겹치는 cross-component object로 새는 terminal 우회 차단
P3 (선언 경로 SoT drift):
- generic opnix producer(programs/opnix/default.nix의 github-pat + tmpfiles root)도
constants.paths.opnixRuntimeRoot에서 파생 — Toss만 새 root로 이동하고 tmpfiles가 옛 root에
남던 producer/consumer 분리 제거. 상수 주석을 opnix 전반 SoT로 명확화
- SA token 경로를 toss_sa_token_file 공통 helper(auth·doctor)로 통일하고 fallback을
XDG_CONFIG_HOME 반영. wrapper가 실제 producer(${config.xdg.configHome}/op/sa-token-mac)를
TOSS_OP_SA_TOKEN_FILE로 주입 — xdg.configHome 변경 시 옛 경로를 읽던 drift 제거
테스트: toss 4건 신규 (metadata override 우회 차단, marker-missing operation 유지,
cross-component ref 실패, order safeguard hard floor). assert_toss_wrapper_nix에 SA token·
toolchain pin·pinned bash exec 검증 추가. endpoints.json 재생성 byte-identical.
toss 48건·eval-tests 통과
Claude-Session: https://claude.ai/code/session_01X3wfUEX71YnHbyw8mHwi5X
…ata 후처리 helper 강한 코드 리뷰(4개 관점 병렬 + 종합 판정) 결과 반영. 정확성·설계·회귀 관점은 문제 없음, 유지보수성 2건(순수 리팩터, 동작 불변): - M-1 (CLEAN_CODE): 민감값 raw-string redaction 규칙이 api.sh redact_raw와 ledger.sh redact_raw_string 두 곳에 복제돼 이미 갈라져 있었다(api.sh엔 authorization = config 형태 gsub 누락). 규칙을 ledger.sh의 TOSS_RAW_REDACT_JQ_DEF 단일 def로 통합하고 api.sh의 toss_ledger_response_body_json이 재사용. drift 원천 차단 + 부수적으로 non-JSON 응답에서도 config 형태 authorization이 이제 redact됨(방어 강화) - M-2 (CLEAN_CODE): toss_metadata_lookup의 exact/template 후처리 블록이 metadataStatus 리터럴만 빼고 완전 중복(order-safeguard hard floor 계산 포함). status를 인자로 받는 toss_metadata_decorate helper로 추출해 requiresOrderSafeguards 판정을 단일 지점화 — 두 lookup 경로의 보상 통제 일관성이 구조적으로 보장됨 검증: 동작 불변 확인(M-1 redaction 결과 동일+config 형태 추가 방어, M-2 exact/template 모두 hard floor 유지), toss 스위트 48건·eval-tests 통과 Claude-Session: https://claude.ai/code/session_01X3wfUEX71YnHbyw8mHwi5X
5a770cc to
d799765
Compare
…losed, JSON redaction main rebase(op_get SA-first 무인 폴백 #1134) 후 재검토 4건 반영: - [P1] api.sh: 실주문 network mutation 전에 phase:"attempt" 원장을 best-effort append. 전송 후 crash/INT/TERM으로 response 원장·알림이 모두 없으면 운영자가 접수된 주문을 실패로 오인해 재전송할 수 있다. attempt는 같은 invocationId를 쓰고 response/notify가 이어받는다 (toss_record_attempt_ledger) - [P2] auth.sh: SA 조회(client_id/secret op read) 두 호출을 unset OP_CONNECT_HOST OP_CONNECT_TOKEN 서브셸로 격리. Connect env가 SA token보다 우선하는 op 정책 때문에 잔존 Connect env(원격/LLM 셸 오염)가 조회를 다른 backend로 보내거나 실패시켰다 (main op_get SA 경로 계약과 동형, 1password.md 문서와도 정합) - [P2] file-lock.sh: lock backend(flock/lockf) 부재 시 fail-open(경고 후 실행)을 fail-closed(exit 75)로. token CAS·원장 append lock이 lock 없이 도는 것을 차단. toss wrapper PATH에 util-linux(flock) pin 추가 (Darwin은 lockf 시스템 fallback) - [P2] ledger.sh: raw redaction의 authorization colon 패턴에 quote 허용을 추가해 `{"Authorization":"Bearer ..."}` 같은 quoted JSON header 반사도 가린다. SoT(TOSS_RAW_REDACT_JQ_DEF) 한 곳 수정으로 api.sh/ledger.sh 양쪽 반영 테스트: toss 4건 신규 (attempt 원장 + invocationId 공유, quoted JSON redaction, Connect env 격리, file-lock fail-closed). attempt 원장 추가로 orders.jsonl이 다중 record가 되어 기존 원장 파싱 테스트 10곳에 phase=="response" 필터 추가, append preserve 라인 수 3으로. assert_toss_wrapper_nix에 util-linux pin 검증 추가. toss 50건·eval-tests 통과 Claude-Session: https://claude.ai/code/session_01X3wfUEX71YnHbyw8mHwi5X
test_toss_auth_isolates_connect_env는 Mac op read 경로(toss_read_credentials_from_op)의 Connect env 격리를 검증하는데, auth.sh는 toss_is_darwin으로 분기해 비-darwin에서는 opnix 파일 경로를 타 op를 부르지 않는다. linux CI에서 op stub이 호출되지 않아 "expected op to be invoked"로 실패했다. 기존 preflight 테스트와 동일한 darwin 가드 ([ "$(uname -s)" = "Darwin" ] || return 0)를 추가해 Mac에서만 실행한다. Claude-Session: https://claude.ai/code/session_01X3wfUEX71YnHbyw8mHwi5X
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c3d156c9bb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| toss_curl_config_append "$config_file" "url" "$TOSS_API_BASE_URL/oauth2/token" | ||
|
|
||
| # -q는 첫 인자여야 사용자 기본 .curlrc(proxy/insecure/추가 url 등)를 차단한다. | ||
| curl -q -g -sS --proto =https --max-time 20 -K "$config_file" |
There was a problem hiding this comment.
Clear curl trust and proxy overrides before sending secrets
When an LLM or other caller can control the ambient environment, this invocation still honors HTTPS_PROXY, CURL_CA_BUNDLE/SSL_CERT_FILE, and SSLKEYLOGFILE; -q only disables .curlrc and --proto =https does not disable those variables. As confirmed by curl --manual, HTTPS_PROXY selects the HTTPS proxy and CURL_CA_BUNDLE supplies the CA bundle, so a caller can combine a local proxy with its own CA and receive the client ID/secret posted here; the bearer-token request in api.sh has the same exposure. Sanitize these variables and explicitly disable proxies or pin the trusted CA before invoking curl.
Useful? React with 👍 / 👎.
| [allowlist] | ||
| paths = [ | ||
| '''flake\.lock''', | ||
| '''\.claude/skills/using-toss-api/references/vendor/''', |
There was a problem hiding this comment.
Narrow the vendor secret-scanning allowlist
This path-level allowlist prevents gitleaks from scanning every current and future file under the vendor directory. If a docs refresh or manual troubleshooting accidentally saves a real Toss access token or client secret there, the repository's secret check will accept it silently; allowlist only the known placeholder examples or their exact fingerprints instead of excluding the entire directory.
Useful? React with 👍 / 👎.
Summary
tossCLI + Agent Skill(using-toss-api) + 1Password/opnix 시크릿 배선 + OpenAPI 기반 endpoint metadata 생성기.references/vendor/컨벤션을 도입한다.관련 후속: #1039(정기 잡 확장), #1044(전용 vault+SA 분리), #1049(CLI 구조 정리).
기존 문제 / 배경
토스증권이 2026년 OpenAPI를 개방하면서 OpenAPI 3.0 스펙과 LLM 에이전트용
llms.txt를 공식 제공하기 시작했다. 이 저장소는 이미 1Password/opnix로 시크릿을, Home Manager로 CLI를 선언 관리하지만, 토스 API를 LLM이 안전하게 호출할 표준 경로가 없었다. 특히 (1) 계좌·주문 API의 인증 헤더 규칙, (2) client당 유효 토큰 1개라는 제약, (3) 모의투자(sandbox) 부재로 주문 경로가 실탄이라는 점 때문에, 아무 래퍼나 붙이면 토큰 노출·오주문·감사 누락 위험이 컸다.CIR (Change Intent Record)
발견 경위: 사용자가 "LLM이 토스증권 API를 쓸 수 있는 인프라"를 요청. 선행으로 API 허용 IP(집 회선 공인 IP 하나로 맥북·miniPC 모두 커버, CGNAT 아님 확인)를 정리한 뒤 착수.
설계 의도:
ps노출을 막는다 (jq 등 보조 프로세스에도 민감값은 argv가 아닌 stdin으로만 전달). client당 토큰 1개 제약 때문에 401 수신 시 lock 안 CAS로 갱신한다 — 다른 프로세스가 이미 갱신한 token이 있으면 재발급 없이 재사용하고(상호 무효화 방지), 실패 token이 여전히 현재 cache일 때만 credential로 재발급한다.op read, miniPC는 신규programs/toss모듈이 opnix로 user-owned 0400 materialize하도록 준비만 되어 있다 (feat(secrets): 토스 credential 전용 vault + 전용 SA token 분리 — SA blast radius 원복 #1044 전용 vault/SA 분리 전까지homeserver.toss.enable = false로 비활성). 이 게이트는 MiniPC 로컬 opnix materialization/런타임 사용만 차단한다 — SA의 Automation vault read ACL은 이미github-pat + 토스로 확장돼 있어, SA token이 탈취되면 게이트가 false여도 1P에서 토스 item을 읽을 수 있다. 즉 게이트는 blast radius를 좁히지 않는다. 기존github-pat한정이던 SA blast radius를 "github-pat + 토스"로 의도적으로 확장하고 그 사실을 managing-secrets에 문서화했으며, blast radius 원복(전용 vault+SA)은 #1044로 예약.대안 검토: 아래 ADR 참조.
ADR (Architecture Decision Record)
toss api <METHOD> <PATH>+ OpenAPI 생성 metadata로 헤더/주문 분류toss quote AAPL등 27개 명령~/.cache구현 상세
modules/shared/scripts/toss.shmodules/shared/scripts/lib/toss/{auth,api,metadata,ledger,doctor,notify,curl}.shmodules/shared/scripts/lib/file-lock.shwith_file_lockscripts/toss/generate-endpoint-metadata.shendpoints.json(requiresAccount·rateLimitGroup·pathRegex·isKnownOrderMutation)modules/shared/scripts/toss/endpoints.jsonmodules/nixos/programs/toss/default.nixhomeserver.toss.enable게이트modules/shared/programs/shell/default.nix~/.local/bin/tosswrapper(op reference env 주입) + lib/metadata 배포libraries/constants.nixonePassword.tossOpenApi(item/field 좌표).claude/skills/using-toss-api/scripts/ai/check-skill-noise.sh,.gitleaks.tomlreferences/vendor/예외(외부 문서)tests/eval-tests.nix,tests/suites/toss-cli.sh핵심 안전 불변식 (curl config로 토큰 argv 노출 방지):
재시도 불변식: 재시도는 같은 요청(주문 mutation 포함)을 재전송하므로, side effect가 이미 반영됐을 수 있는 응답에는 트리거되면 안 된다(이중 주문 방지). 따라서 완결된 HTTP 401(curlExit==0)에서만 재시도한다 — 2xx(성공), 4xx(≠401)·5xx(서버가 주문 반영 후 응답 가능성), curlExit≠0(전송/연결 실패, 응답만 유실 가능)은 모두 재시도하지 않는다. vendored OpenAPI가 인증 실패를 401에만 정의하므로 body code substring 기반 재시도는 사용하지 않는다.
참고 레퍼런스
Human Test Plan
전제:
nrs로 배포 후 (새 스킬이라 배포 후./scripts/ai/verify-ai-compat.sh재검증 권장). 토스 Open API 키는 1Password Automation vault 「토스증권 Open API」에 이미 등록됨.운영 가능 host: 현재 Mac만 실호출 가능. MiniPC는
homeserver.toss.enable = false(#1044 전)라 opnix credential이 없어 아래 절차가 실패한다 — Mac에서만 수행할 것.toss doctor(로컬·side-effect 없는 진단 — 단 출발지 공인 IP 확인엔 네트워크 필요)present), token 캐시 로컬 만료 여부(locally-unexpired)op실제 접근 권한과 서버측 token 유효성은 검사하지 않는다 (revoked SA·타 호스트 재발급은 여기서 안 잡힘). 실검증은 아래 2번(toss token --force)이나 첫 API 호출로 한다toss token~/.config/op/sa-token-mac)로op read하여 client_id/secret을 읽어 access token 발급(biometric fallback 없음), 휘발 경로에 0600 캐시. 화면·로그에 토큰 노출 없음toss api GET /api/v1/stocks?symbols=005930(또는 스킬의 jq 레시피로 경로 확인)toss accountstoss api POST /api/v1/orders --data '{...}' --dry-run<redacted>), 전송·토큰 발급 없음, 원장에 dry-run 표시bash tests/shell-script-tests.sh,nix eval --impure --file tests/eval-tests.nixSummary by CodeRabbit