Skip to content

fix(release): reject credential-bearing SSH remotes - #2294

Draft
Ingwannu wants to merge 2 commits into
devfrom
ingw/fix-release-ssh-credential-boundary
Draft

fix(release): reject credential-bearing SSH remotes#2294
Ingwannu wants to merge 2 commits into
devfrom
ingw/fix-release-ssh-credential-boundary

Conversation

@Ingwannu

Copy link
Copy Markdown
Owner

Summary

  • reject credential-bearing ssh:// and scp-like release remotes before they can reach logs or git push
  • reject malformed percent-encoding, query/fragment suffixes, and control characters at the SSH-target validation boundary
  • exercise the emitted GIT_SSH_COMMAND through real Git and a fake SSH process so hostile key paths remain one literal -i argument

This is a security hardening follow-up to #2290. It preserves the existing deploy-key release flow and changes only validation and regression coverage.

Validation

  • bun test tests/release-helper.test.ts: 23 passed
  • bun run typecheck: passed
  • bun run privacy:scan: passed
  • bun run prepush: 14,154 passed / 16 skipped / 0 failed
  • git diff --check: passed

Review boundary

This touches scripts/release.ts and credential-bearing release automation, so it intentionally remains Draft and requires explicit owner/security review. No release or publish command was run.

The change is TypeScript release tooling only and has no Go-native counterpart.

@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6e62a664-68cb-4ccb-b82b-f88b564596b7

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

@github-actions github-actions Bot added the bug Something isn't working label Aug 21, 2026
@Ingwannu
Ingwannu requested a review from lidge-jun August 21, 2026 13:03
@lidge-jun

Copy link
Copy Markdown
Owner

리뷰 · 우선순위 61 / 80

지금 dev HEAD c0cbe494escripts/release.ts:172-175 isSshRemote는 아직 /^ssh:\/\/[^/]+\/.+$/임. ssh://git:SECRET@host/owner/repo.git를 그대로 통과시킴. releasePushCommand (:196)가 → release push target: ${slug}를 찍고 runLoud (scripts/release.ts:107)가 실패 커맨드를 join해서 다시 찍음. #2290 리뷰에서 남긴 그 구멍임. 이 PR이 그 검사만 바꿈. 푸시 키 경로/IdentitiesOnly=yes/quoteSshArgument는 그대로. 드래프트. scripts/AGENTS.md 시큐리티 리뷰 대상 맞음. 닫을 중복 아님. types/config 스플릿 무관.

핵심이 PR 쪽 isSshRemote (scripts/release.ts:179-207)임. 컨트롤 문자/DEL이면 바로 false. ssh://new URL로 파싱함. parsed.password === ""만으론 부족함. Node가 ssh://user%3aSECRET@host/repo의 username을 디코드 안 하고 password를 빈 문자열로 둠. authority를 첫 슬래시 앞에서 자르고 @ 앞을 decodeURIComponent함. 콜론 있으면 거절. 퍼센트 콜론 우회가 그 줄에서 죽음. search/hash도 거절. scp-like는 /^[^@:\s/]+@[^:\s/]+:.+$/로 유저 쪽에 콜론 금지. 예전 /^[^@\s/]+@git:SECRET@host:path를 SSH로 봄. 그 회귀를 잠근 거임. 거부 메시지(:219)에 리모트 값을 안 넣음. 로그 바운더리가 맞음.

테스트가 그 바운더리를 봄. tests/release-helper.test.ts:507-522OCX_RELEASE_SSH_REPO=ssh://git:SECRET@example.test/owner/repository.git이랑 origin git:SECRET@example.test:owner/repository.git 두 장. exit ≠ 0, 출력에 SECRET 없음, git push 호출 없음. 맞음. 근데 origin이 ssh://git:SECRET@...인 장은 없음. 그 경로는 isSshRemote false → sshTargetFromOrigin undefined → no SSH push target이라 값은 안 찍힘. 동작은 닫혀 있음. 회귀 잠금은 아님.

GIT_SSH_COMMAND 테스트가 진짜 Git을 탐. :443-460이 키 경로에 공백·따옴표·$HOME·$(not-run)·백틱·세미콜론을 넣고 executeGitSshCommand (:278-314)가 페이크 SSH로 git ls-remote를 돌림. -i 다음이 키 경로 한 인자인지 봄. Git 워드 스플릿을 텍스트 toContain으로 속이는 걸 자른 거임. 유닉스 shebang 런처(:289)에선 맞음. Windows ssh.cmd%*(:290)는 따옴표 있는 인자를 다시 쪼갤 수 있음. 그 호스트에선 이 테스트가 거짓 초록일 수 있음. IdentitiesOnly=yes는 그대로라 에이전트 키가 먼저 나가는 회귀는 안 열림.

남은 구멍. ssh://SECRET@host/owner/repo.git은 username만 있고 콜론이 없음. parsed.password === ""라 통과함. 그 값이 :196 로그랑 runLoud 실패 출력으로 나감. HTTPS https://TOKEN@host/... 베낀 ssh 리모트가 그 모양임. scp-like는 ?/#를 안 자름. git@host:owner/repo.git?token=SECRET이 통과함. ssh://만 쿼리/프래그먼트를 거절. 비대칭임. 키 파일 exists/readable 프리플라이트는 #2290부터 그대로 없음. 이 PR 범위 밖임.

types.ts/config.ts 안 만짐. 스플릿 안 씹힘. 리베이스하지 말고 닫으라는 케이스 아님. 프리뷰 배포 아님. #2188 L1–L9/#2255/#2266/#2283이랑 다른 레인임. 방금 dev에 올라간 건 #2293 scripts/restart-codex-desktop-app.ps1이지 이 파일이 아님. #2292 윈도우 피커랑 안 겹침. 카탈로그는 그대로 Ox Alpha x-preview-f-free + deepseek-v4-flash-vision-exp. v2.29.0 태그됨. 다음 bump가 이 로그 구멍으로 시크릿을 찍을 수 있음. 시큐리티 팔로우업이라 61. 유저 도그푸딩 핫패스는 아님.

해결방안: 이 패치로 가라. 머지 전에 ssh://SECRET@host/owner/repo.git (콜론 없는 userinfo)이랑 origin ssh://git:SECRET@host/owner/repo.git 두 장을 :507 루프에 넣어라. username이 git이 아니면 거절하는 쪽이 안전함. 포지 SSH가 git 아닌 유저를 쓰면 OCX_RELEASE_SSH_REPO로만 열어라. scp-like에도 ?/# 거절. Windows ssh.cmd %* 대신 argv를 유지하는 런처로 바꿔라. 키 파일 프리플라이트는 따라오는 커밋으로. 스플릿이 scripts/release.ts를 안 건드림. 이 PR은 닫지 말고 저 장만 얹어라.

이 댓글은 grok-bot이 작성했습니다

@Ingwannu

Copy link
Copy Markdown
Owner Author

Verified the Grok/owner findings against the exact patch and updated this Draft PR at 71598fa45.

The current head now closes the remaining target-log bypasses:

  • ssh:// userinfo decodes to either empty or the fixed git principal; ssh://SECRET@... and percent-encoded git%3ASECRET are rejected.
  • Credential-bearing ssh:// origin remotes are rejected before target selection.
  • scp-like targets require git@... and reject ? / # suffixes.
  • The negative matrix asserts non-zero exit, no SECRET in output, and no git push invocation for every malicious form.
  • The GIT_SSH_COMMAND regression now replaces only the executable token with a native runtime executable. It no longer relies on a Windows .cmd %* shim that could reparse damaged quoting into a false green.
  • Legitimate credential-free ssh://git@..., host-only ssh://..., and git@host:path controls remain accepted.

Validation on the rebased exact head:

  • bun test tests/release-helper.test.ts — 24/24
  • bun run typecheck
  • bun run privacy:scan
  • git diff --check

The required full bun run prepush also passed before the no-conflict rebase: 14,155 pass / 16 skip / 0 fail, followed by a passing privacy scan. The rebase only added current dev commits outside these two changed files, and the exact-head focused/type/privacy checks were rerun afterwards.

I intentionally did not fold key-file readability preflight into this credential-log boundary patch; that remains separate operational hardening. This PR stays Draft and requires independent release/security review before merge.

@Ingwannu
Ingwannu force-pushed the ingw/fix-release-ssh-credential-boundary branch from 86ed0a4 to 71598fa Compare August 21, 2026 14:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants