suzuki-shunsuke

Stars and mentions

Last 30 days, Oct 5 – Nov 3

GitHub stars5
mentions16
02468Oct 5Oct 19Nov 3
UTC days · repositories suzuki-shunsuke owns4 repositories

Package downloads

No package download history available yet.

Top repositories

suzuki-shunsuke’s repositories ranked by new stars, as on trending.

Loading

Contributes to

Repositories outside suzuki-shunsuke’s account where suzuki-shunsuke opened or reviewed pull requests in the past 365 days.

Who noticed suzuki-shunsuke

Everyone who starred or forked suzuki-shunsuke’s repositories since the archive began in 2011.

Notable accounts

Ordered by the stars their own repositories have received.

Biggest fans

Accounts that starred the most of these repositories.

Breakout weeks

Each repository’s best week of new stars, with the release published around it.

Mentions

16posts
Nov 40 posts
  1. GitHub Actions workflow lockfiles are now GA, but they don't support reusable workflows yet. So pinact is still useful for pinning reusable workflows. Here’s a guide on how to use pinact to pin only reusable workflows. github.com/suzuki-shunsuke/pinact/blob/main/docs/…

  2. Always Set persist-credentials: false in actions/checkout --- title: "Always Set persist-credentials: false in actions/checkout" published: true description: "Set persist-credentials: false in actions/checkout. The default leaves a GitHub token on disk for later steps. The fix, and how to still git push." tags: githubactions, security, devops, cicd canonical_url: yadui.dev cover_image: yadui.dev --- There's a default in `actions/checkout` that most teams never change — and that quietly leaves a GitHub token sitting in a readable file for every subsequent step in the job. The fix is one line. The reasoning is worth understanding. The Setting - uses: actions/checkout@v4 with: persist-credentials: false # default is true By default (`persist-credentials: true`), the checkout action writes a git credential config file containing your GitHub token to `$RUNNER_TEMP`. That file persists for the lifetime of the job. What the File Looks Like The checkout action creates a file at `$RUNNER_TEMP/git-credentials-<UUID>.config`: [http "https://github.com/"] extraheader = AUTHORIZATION: basic <Base64-encoded-string> The Base64 string decodes to `x-access-token:<GITHUB_TOKEN>`. It's not encrypted. It's not write-protected. It sits in `$RUNNER_TEMP` for every step after checkout to read. How Easy It Is to Exploit Any step in the same job — including actions from third-party repos — can extract the token with a shell one-liner: GH_TOKEN=$( cat "$RUNNER_TEMP"/git-credentials-*.config \ | awk 'NR==2 {print $5}' \ | base64 --decode \ | cut -d: -f2 ) echo "token: ${GH_TOKEN:0:10}..." This doesn't require `${{ github.token }}` or `${{ secrets.GITHUB_TOKEN }}` to appear anywhere in the workflow. The token is already on disk. > [!WARNING] > If any action you use is compromised via a supply chain attack, or if your workflow is vulnerable to script injection through untrusted input, that action can silently exfiltrate your GitHub token via this file. Setting `persist-credentials: false` makes the checkout action delete the credentials file after checkout completes. The file is gone before the next step runs. The Practical Risk The most realistic threat vectors: 1. Compromised third-party action: You pin `some-org/some-action@v2`. The maintainer's account is compromised, a new patch release is pushed, and the action now exfiltrates credentials. If you reference by tag (not commit SHA), you get the new version automatically. 2. Script injection via PR titles or branch names: Workflows that echo `${{ github.event.pull_request.title }}` into a `run:` block are vulnerable. An attacker opens a PR with a crafted title that executes a shell command reading the credentials file. The `persist-credentials: false` flag eliminates the file-based exfiltration path. It doesn't prevent all attacks, but it removes the easiest one. What If I Need Git Auth in Later Steps? Setting `persist-credentials: false` means subsequent `git pull`, `git push`, or other authenticated operations will fail without credentials being re-established. The clean solution is `gh auth setup-git`, which configures Git to use the GitHub CLI credential helper: - uses: actions/checkout@v4 with: persist-credentials: false - name: Set up git credentials via GH CLI run: gh auth setup-git env: GH_HOST: github.com - name: Pull latest run: git pull origin main env: GH_TOKEN: ${{ github.token }} This configures `~/.gitconfig` with: [credential "https://github.com"] helper = helper = !/usr/bin/gh auth git-credential The credential helper calls out to the GitHub CLI at auth time, which reads `GH_TOKEN` from the environment — only when Git actually needs to authenticate, and without writing the token to disk. The token is scoped to the steps where `GH_TOKEN` is explicitly passed in `env:`. It's not sitting in a file readable by any arbitrary code in the job. Pinning Actions by Commit SHA While hardening your checkout step, pin by full commit SHA rather than a mutable tag: # Mutable — tag can be force-pushed to point at new, malicious code - uses: actions/checkout@v4 # Immutable — this exact commit is guaranteed - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 Combined with `persist-credentials: false`, this closes both the supply chain mutation vector and the credential-on-disk vector in one checkout block. Automated Detection Human review will miss this. Use static analysis in CI: zizmor — Rust-based, runs as a GitHub Action: - uses: woodruffw/zizmor-action@v1 Output when `persist-credentials` is not set: warning[artipacked]: credential persistence through GitHub Actions artifacts --> .github/workflows/build.yml:7:9 | 7 | - uses: actions/checkout@v4 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ does not set persist-credentials: false | = note: this finding has an auto-fix ghasec and ghalint also catch this and surface it with rule references. Running any of these in CI ensures the check doesn't rely on engineers remembering to apply it manually. > [!TIP] > There is an open PR in the `actions/checkout` repo to flip the default to `false`, but it has been stalled for years. Until the default changes, enforce it via linting. Checklist - [ ] `persist-credentials: false` on all `actions/checkout` steps - [ ] Actions pinned to full commit SHAs, not mutable tags - [ ] `gh auth setup-git` pattern for workflows that need post-checkout git auth - [ ] zizmor or ghasec running in CI to catch new workflows - [ ] Minimal `permissions:` scope on each job (`contents: read` unless write is required) The same lesson applies to AI tooling: a token that sits where other code can read it will eventually be read. See stop giving AI agents your production keys and the LiteLLM 1.107.0 incident response guide for what that looks like in practice. --- Originally published at yadui.dev by Abhinav Yadav.

  3. ghtknのagent、macOSでも常駐させたいな。launchdでやるのかな?/ghtkn/docs/agent-deployment.md at main · suzuki-shunsuke/ghtkn · GitHub github.com/suzuki-shunsuke/ghtkn/blob/main/docs/a…

    93 views
  4. ということはすでにpinactでも説明されていた github.com/suzuki-shunsuke/pinact/blob/main/docs/…

    11 views
  5. GitHub Actions workflow lockfiles are now GA, but they don't support reusable workflows yet. So pinact is still useful for pinning reusable workflows. Here’s a guide on how to use pinact to pin only reusable workflows. github.com/suzuki-shunsuke/pinact/blob/main/docs/…

    36 likes · 12 reposts · 1,766 views
  6. pinact は対応しない方針ってことでいいのかな。 まぁ要らないと思うしね。。。 pinact#1787

    23 views
  7. GitHub CLI's New Expiring Tokens Still Leave Your Refresh Token Exposed to Malware GitHub CLI (gh) recently added short-lived access tokens that are refreshed automatically. It is a public preview and opt-in for now, but it is planned to become the default. github.blog This is a welcome change: a leaked access token now stops working within eight hours. But the refresh token is a different story. It lives for months, and gh stores it in the OS keyring, where on macOS any process running as you can read it without a confirmation dialog, as gh's own documentation acknowledges. If malware steals the refresh token, it can keep minting new access tokens off your machine. I develop ghtkn, a CLI that creates short-lived GitHub App user access tokens for local development, and it was designed with exactly this threat in mind. This article compares ghtkn with gh's new short-lived tokens and explains what ghtkn protects against that gh doesn't. How ghtkn refreshes tokens Like gh, ghtkn issues access tokens that expire in eight hours and refreshes them automatically with a refresh token, so you only go through the device flow once per app. The difference is where the tokens live. ghtkn supports refresh tokens only on its agent backend: a local daemon that stores tokens encrypted with a key derived from your passphrase. The agent starts locked, and tokens become available only after you unlock it with your passphrase. For details, see the documentation: github.com/suzuki-shunsuke/ghtkn/blob/main/docs/r… How gh and ghtkn compare Here is where ghtkn differs from gh's short-lived tokens. - The refresh token is hard to steal even if arbitrary code runs on your machine - You can restrict which organizations and repositories a token can access - You can use different apps with different permissions for different purposes - The refresh token is never stored in plain text - You can lock the agent so that no token can be obtained - A suspected refresh token leak is detected and reported - Go CLIs integrate seamlessly through the SDK Let's look at each of them in detail. The refresh token is hard to steal even if arbitrary code runs on your machine Protecting the refresh token is essential to keeping short-lived tokens meaningful. First, note that access tokens can be obtained with either tool. gh gives you one with gh auth token, and ghtkn gives you one with ghtkn get, so there is no difference in the fact that an access token can leak if arbitrary code runs on your machine. However, an access token expires in eight hours, but a refresh token doesn't. According to GitHub's documentation, a refresh token expires only after six months without use, and each refresh issues a new refresh token. So if a refresh token leaks, an attacker can keep refreshing it and mint access tokens off your machine practically forever. That largely defeats the purpose of making access tokens short-lived. gh stores the refresh token in the OS keyring by default, but if arbitrary code runs on your machine, for example through a supply chain attack, secrets stored in the OS keyring can easily leak. On macOS, gh accesses the Keychain through /usr/bin/security, and once /usr/bin/security is trusted (most people have trusted it), it can read and write secrets without any confirmation dialog. gh's own document about the macOS keyring mentions this as well. Moreover, the name of gh's Keychain item is obvious from the source code, so malware can easily get the refresh token. When I actually tried it, a single security command gave me both the access token and the refresh token without any confirmation dialog. ghtkn's agent backend, on the other hand, encrypts and stores the refresh token on its own. The encryption key is derived from the user's passphrase, and the passphrase is never saved to disk. The agent also has no API that returns the refresh token; clients can only obtain access tokens. Furthermore, a non-root process running as the same user can't read the agent's memory. macOS restricts reading other processes' memory by default, and on Linux the agent marks its own process non-dumpable (PR_SET_DUMPABLE=0) at startup. So even if arbitrary code runs on your machine, the refresh token doesn't easily leak unless the attacker escalates to root. You can restrict which organizations and repositories a token can access gh's token can access every repository the user can access, and there is no way to restrict it. Also, gh is a privileged app, so organizations can't restrict its access with their settings. ghtkn, on the other hand, uses GitHub App user access tokens, so a token can only access the organizations and repositories the app is installed on. A private app can only be installed on the organization or user that owns it, so even if the access token of your personal GitHub App leaks, it can't access your company's organization. You can also restrict a token to specific repositories by selecting them when you install the app. You can use different apps with different permissions for different purposes gh is a single app, so you can't switch between apps, and it tends to have strong permissions. It requires at least the repo, read:org, and gist scopes, so you can't even make it read-only. With ghtkn, on the other hand, you can register multiple GitHub Apps and switch between them depending on what you are doing. For example, you can use an app with weak permissions most of the time, and a different app only when you need strong permissions. In addition, the agent backend automatically removes the access tokens and refresh tokens of apps that have gone unused for a certain period. So if you keep a separate app with strong permissions that you only use occasionally, its tokens are removed naturally while you aren't using it, which reduces the chance of a leak. The refresh token is never stored in plain text gh stores the access token and the refresh token in the OS keyring, but if writing to the OS keyring fails, it stores them in plain text in hosts.yml. It prints only a one-line warning and the login succeeds anyway, so users are unlikely to notice. On headless Linux or in containers without an OS keyring, this is in fact the behavior you are likely to get. ghtkn, on the other hand, never stores access tokens in plain text unless you explicitly select the text backend. And since refresh tokens are supported only on the agent backend, a refresh token is never stored in plain text. You can lock the agent so that no token can be obtained With gh, any process running as you can obtain a token at any time, for example with gh auth token. gh has no mechanism that lets users make tokens temporarily unobtainable. ghtkn's agent has the same exposure while it is unlocked: any process running as the same user can obtain access tokens from it. That's why ghtkn lets you lock the agent with ghtkn agent lock. While the agent is locked, no token can be obtained at all, including access tokens, and you have to enter your passphrase to unlock it before using it again. Locking doesn't need the passphrase, so you can hook it into a screen lock or logout. By keeping the agent locked while you don't need tokens for a while, you shorten the window in which an attacker can obtain them. A suspected refresh token leak is detected and reported A refresh token is replaced with a new one every time it is used (rotation). So if an attacker uses a stolen refresh token first, the legitimate user's next refresh fails. This can be a signal that the refresh token has leaked. When a refresh fails, gh deletes the credential and only prints the token for <host> has expired; please run 'gh auth login' to re-authenticate. Nothing indicates a possible leak, and since the credential is deleted, nothing is left to investigate. Users will think the token simply expired and log in again without suspecting anything. ghtkn's agent, on the other hand, returns a warning about a possible leak to the client, which shows it to the user, when a refresh token that should still be valid fails to refresh. This lets users notice the leak and start investigating and responding to it. Go CLIs integrate seamlessly through the SDK ghtkn provides a Go SDK, and various tools such as aqua, pinact, and ghir obtain access tokens seamlessly through it. You don't need to expose access tokens as environment variables, which reduces the paths through which they can leak. Will gh be able to do the same in the future? ghtkn supports refresh tokens only on the agent backend, so that refresh tokens don't easily leak even if arbitrary code runs in your development environment. gh has no such architecture with a constantly running daemon, at least for now. Also, because gh is a single privileged app, it would be hard for it to let you switch between apps to restrict permissions and access scope. I look forward to future improvements in gh, but I believe ghtkn will keep its advantages on these points for the time being. Conclusion Short-lived tokens becoming officially supported in gh is great news, but they leave the refresh token exposed, and ghtkn still has clear security advantages over them. I will keep developing ghtkn. If you got interested in ghtkn, please give it a try: suzuki-shunsuke/ghtkn The content of this article is also available in ghtkn's documentation. With ghtkn v0.4.2 or later, you can read the documentation with the ghtkn docs command. ghtkn is AI-friendly, so if you have any questions, ask a coding agent such as Claude Code. To let your agent read ghtkn's documentation, install ghtkn's agent skill: suzuki-shunsuke/ghtkn When I installed the skill and asked Claude Code, "GitHub CLI apparently supports short-lived tokens now. Does that make ghtkn unnecessary?", it explained the advantages based on the documentation. For how ghtkn is made AI-friendly, see the following article: dev.to

  8. github-action-renovate-config-validator is now much faster by bundling renovate-config-validator ⚡ It now installs in a few seconds instead of taking tens of seconds to install Renovate. github.com/suzuki-shunsuke/github-action-renovate…

  9. ghtkn が access token の自動更新をサポート ローカル開発のために GitHub App からセキュアに User Access Token を生成する CLI である ghtkn を開発しています。 suzuki-shunsuke/ghtkn ghtkn v0.3.4 から refresh token による access token の自動更新をサポートしました。 これにより、 8 時間ごとのデバイスフロー認証が不要になり、 UX が大幅に向上します。 この機能は、 macOS および Linux の agent backend でのみサポートされています。 Windows やデフォルトの keyring backend, および text backend ではサポートされていません。 この制限はセキュリティを確保するための意図的なものです。 なお、 GitHub CLI も Public Preview ながら短命トークンと自動更新をサポートしましたが、 ghtkn には様々な優位性があります。 それについては別記事で説明しています。 zenn.dev 使い方 ghtkn を v0.3.4 以降にアップデート aqua や pinact など、 ghtkn-go-sdk に依存するツールを最新バージョンにアップデート 古い SDK は、 agent backend をサポートしていない (v0.3.0 以降でサポート) か、設定ファイルで指定された backend を無視します (v0.4.0 以降で尊重)。 SDK が正しく動作しない場合は、バージョンを確認してください。 backend を agent に変更 export GHTKN_BACKEND=agent または ~/.config/ghtkn/ghtkn.yaml backend: type: agent agent を起動 ghtkn agent start agent が既に起動中の場合は、 ghtkn の更新を反映させ refresh token を有効にするために、 agent を再起動する必要があります。 ghtkn agent stop # Ctrl-C でも良い ghtkn agent start agent を unlock する際に --enable-refresh オプションを渡す ghtkn agent unlock --enable-refresh agent は起動しても、 ghtkn agent unlock コマンドでパスフレーズを渡すまで利用できません。 この状態の変化を lock/unlock という言葉で表現しています。 初回の unlock でパスフレーズを聞かれるので、自由に設定して下さい。 パスフレーズを忘れた場合、 ghtkn agent reset で再設定して下さい。 --enable-refresh は unlock するたびに毎回渡す必要があります。 付け忘れた場合、保存済みの有効な refresh token があれば、それを削除してよいか確認されます。 以上です。後は通常通りです。 app 毎に 1 度 device flow を実行した後は device flow の実行が不要になり自動で access token が更新されるようになります。 ghtkn auth を実行した際も device flow の代わりに内部で更新が行われるようになります。 何か困った場合、 ghtkn info や ghtkn agent status コマンドで、 agent が起動しているか、 lock されているか、 refresh token が有効かなどを確認できます。 agent が古いバージョンのまま動いていて再起動が必要な場合も分かります。 一定期間未使用の refresh token の自動削除 refresh token の有効期限は半年です。 暗号化されているとはいえ、有効期限の長い refresh token を保持することには多少のリスクが伴います。 そのため、 agent は一定期間使用されていない access token と refresh token を定期的 (24 時間ごと) に削除します。 削除までの期間はデフォルトで 1 週間です。 agent の安全性 ghtkn の agent は refresh token の漏洩リスクを最小限に抑えるための様々な対策を講じています。 refresh token は AES-256-GCM で暗号化されて保存されます。 暗号化に使う data key は、パスフレーズから導出した鍵で暗号化して保存されており、パスフレーズは決してディスクに保存されません。 ghtkn get コマンドなどで取得できる access token と違い、 refresh token を取得するコマンド・ API は存在しません。 ghtkn agent では OS Keyring を使わずに独自に暗号化して管理することでより高い安全性を実現しています。 例えば macOS Keychain の場合、 /usr/bin/security を使って Keychain にアクセスしますが、 /usr/bin/security を一度信頼すれば (多くの人は信頼しているはずです) 以降は確認ダイアログなしで secret を読み書きできます。 ソースコードを読めば Keychain の item 名は分かるため、マルウェアは refresh token を簡単に取得できてしまいます。 これは GNOME Keyring や Windows Credential Manager などでも同様です。 ghtkn agent では、たとえマルウェアに侵害されても、 root に昇格されない限り refresh token は容易には漏洩しません。 agent はメモリに data key を保持しますが、同じユーザーの非 root プロセスから agent のメモリを読むことはできません。 macOS では OS がデフォルトで他プロセスのメモリの読み取りを制限しており、 Linux では agent が起動時に自身のプロセスを non-dumpable (PR_SET_DUMPABLE=0) にしています。 逆に言うと、 root に容易に昇格できる環境では data key が漏洩する危険性があるので、そのような環境では agent を起動しないで下さい。 残念ながら Windows では他のプロセスのメモリを読めてしまうため agent をサポートしていません。 また、 agent が unlock されている間は、同じユーザーで動くプロセスであれば agent から access token を取得できます。 しばらく token を使わない間は ghtkn agent lock で agent を lock しておけば、再び unlock するまで一切 token を取得できなくなります。 lock にはパスフレーズが不要なので、スクリーンロックやログアウトの hook に仕込むこともできます。 さらに、有効期限内のはずの refresh token で更新に失敗した場合、 agent は漏洩の可能性を示す警告を返し、ユーザーに表示します。 refresh token は更新のたびに新しいものに置き換わるため、攻撃者が盗んだ refresh token を先に使うと、正規の更新が失敗するからです。 ホストの agent をコンテナから使う 開発用のコンテナや VM は、そもそも root で実行されていたり、パスワードなしで sudo で root に昇格できたりすることが多いです。 そのような環境で agent を起動するのは危険です。 また、コンテナの user が root でなくても、ホストの一般ユーザーが侵害されれば docker exec -u root などで root としてコンテナに入れるため、コンテナ内の agent のメモリを読まれる危険があります。 一方、ホストで agent を起動していれば、ホストで root に昇格されない限りメモリは読まれません。 そのため、コンテナで agent を起動するのではなく、ホストで起動してコンテナから利用するほうが安全です。 コンテナと agent の間でやり取りされるのは access token だけで、 refresh token や data key, パスフレーズはコンテナに入りません。 Linux ホストの場合: agent の socket をコンテナに bind mount します macOS の場合: コンテナは Linux VM の中で動くため bind mount では socket が使えません。 SSH で socket を VM に転送し、それをコンテナに bind mount します SSH による転送はコンテナ以外にも VM やリモートのサーバーなどでも使えます。 詳細は公式のドキュメントを参照して下さい。 さいごに 以上、 ghtkn の access token の自動更新機能を紹介しました。 実はこの機能がリリースされたのは 2026-07-30 と 3 ヶ月ほど前です。 この記事も 3 ヶ月以上下書きのまま放置されていましたが、ちょっと手直しして公開することにしました。 agent backend は、元々は OS Keyring が利用できない環境で ghtkn を利用するために開発されたものであり、 OS Keyring が利用可能な通常のデスクトップ環境では keyring backend を使用することが想定されていました。 しかし、前述のとおり refresh token をサポートしているのは agent backend のみであり、セキュリティ上の理由から他の backend で refresh token をサポートする予定はないため、今後はデスクトップ環境を含め agent backend が主流となると思います。

  10. GitHub CLI が短命 token をサポートしても ghtkn が必要な理由 ローカル開発のために GitHub App からセキュアに User Access Token を生成する CLI である ghtkn を開発しています。 suzuki-shunsuke/ghtkn ところで、本記事を執筆時点でまだ Public Preview ですが GitHub CLI (以下 gh) が自動 refresh される短命 token をサポートしました。 現在は opt-in ですが、今後はデフォルトになる予定だそうです。 github.blog この話を聞いて gh が公式に短命 token をサポートしたのだからサードパーティツールである ghtkn は不要になるのではないかと思われる方もいるかと思いますが、そんなことはありません。 ghtkn には gh の短命 token と比べてセキュリティ上明確な優位性があります。 自分としては ghtkn を使い続けますし、今後も開発を続けます。 本記事では gh が短命 token をサポートした後も ghtkn が必要な理由を説明します。 なお、本記事の内容は ghtkn のドキュメントにも書いてあります。 ghtkn を v0.4.2 以上に上げれば ghtkn docs コマンドでドキュメントを参照できますし、 ghtkn の agent skill をインストールすれば agent もこの優位性を理解できます。 skill をインストールして Claude Code に GitHub CLI が短命トークンをサポートしたらしいけど、そうなると ghtkn はいらなくなるのか? と質問したところ、ドキュメントの内容に従って説明してくれました。 ghtkn の access token の自動更新機能 gh との比較の前に、 ghtkn の access token の自動更新機能については以下の記事を参考にして下さい。 zenn.dev ghtkn の優位性 ghtkn には主に以下の優位性があります。 任意コードが実行されても refresh token が漏洩しにくい アクセスできる organization や repository を制限できる 用途に応じて permission の異なる app を使い分けられる refresh token を平文で保存しない agent を lock して token を取得できなくできる refresh token の漏洩の疑いを検知して警告する SDK によって Go 製の CLI とシームレスに連携できる 以下でそれぞれ詳しく説明します。 任意コードが実行されても refresh token が漏洩しにくい refresh token を守ることは、短命 token の意味を保つうえで本質的に重要です。 まず前提として、 access token はどちらでも取得できます。 gh なら gh auth token で、 ghtkn でも ghtkn get で取得できるので、任意コードが実行された場合に access token が漏洩しうる点に差はありません。 しかし access token は 8 時間で失効するのに対し、 refresh token はそうではありません。 GitHub のドキュメントによると refresh token は 6 ヶ月使われなかった場合に失効し、 refresh するたびに新しい refresh token が発行されます。 つまり refresh token が漏洩すると、攻撃者は refresh し続けることで、手元のマシンの外で access token を事実上無期限に発行し続けられます。 これでは access token を短命にした意味が大きく損なわれます。 gh は refresh token をデフォルトで OS keyring に保存しますが、 supply chain attack などで任意コードが実行された場合、 OS keyring に保存された secret は容易に漏洩しえます。 macOS の場合、 /usr/bin/security を使って Keychain にアクセスしますが、 /usr/bin/security を一度信頼すれば (多くの人は信頼しているはずです) 以降は確認ダイアログなしで secret を読み書きできます。 この点は gh 自身の macOS keyring に関するドキュメントでも言及されています。 しかも gh の Keychain の item 名はソースコードを見れば分かるため、マルウェアは refresh token を簡単に取得できます。 実際に手元で試したところ、 security コマンド一発で確認ダイアログなしに access token と refresh token の両方を取得できました。 一方、 ghtkn の agent backend では refresh token を独自に暗号化して保存しています。 暗号化に使う鍵はユーザーのパスフレーズから導出され、パスフレーズはディスクに保存されません。 また、 agent には refresh token を返す API がなく、クライアントが取得できるのは access token だけです。 さらに、同じユーザーの非 root プロセスから agent のメモリを読むこともできません。 macOS では OS がデフォルトで他プロセスのメモリの読み取りを制限しており、 Linux では agent が起動時に自身のプロセスを non-dumpable (PR_SET_DUMPABLE=0) にしています。 そのため、任意コードが実行されても root 昇格されない限り refresh token は容易には漏洩しません。 アクセスできる organization や repository を制限できる ghtkn は GitHub App の User Access Token を使うので、アクセスできる範囲は app がインストールされた organization や repository に制限されます。 private な app は所有する organization または user にしかインストールできないので、例えば個人用の GitHub App の access token が漏洩したとしても、所属企業の organization にアクセスすることはできません。 また、インストール時に repository を選択すれば、特定の repository だけに制限することもできます。 一方、 gh の token はユーザーがアクセスできる全 repository にアクセスできてしまい、制限することが出来ません。 また、 gh は特権アプリなので、 organization の設定でアクセスを制限することもできません。 用途に応じて permission の異なる app を使い分けられる ghtkn では複数の GitHub App を登録し、用途に応じて使い分けることができます。 普段は弱い権限の app を使い、強い権限が必要なときだけ別の app を使う、といった運用が可能です。 さらに agent backend では、一定期間使われていない app の access token と refresh token が backend から自動的に削除されます。 そのため、たまにしか使わない強い権限の app を分けておけば、使っていない間は token が自然と削除され、漏洩の可能性を下げることができます。 一方、 gh は単一の app なので使い分けることが出来ず、強い権限を持ちがちです。 最低でも repo, read:org, gist の scope が必要なので、 read-only にすることもできません。 refresh token を平文で保存しない ghtkn は明示的に text backend を選択しない限り、 access token を平文で保存しません。 また、 refresh token は agent backend でしかサポートしていないので、 refresh token が平文で保存されることはありません。 一方、 gh は access token と refresh token を OS keyring に保存しますが、 OS keyring への書き込みに失敗すると平文の hosts.yml に保存します。 その際は 1 行の警告が出るだけでログイン自体は成功するため、ユーザーが気づくのは困難です。 OS keyring がない headless な Linux やコンテナでは、むしろこちらの挙動になる可能性が高いでしょう。 agent を lock して token を取得できなくできる agent が unlock されている間は、同じユーザーで動くプロセスであれば agent から access token を取得できます。 そこで ghtkn では ghtkn agent lock で agent を lock できるようにしています。 ghtkn agent lock lock している間は access token も含めて一切 token を取得できなくなり、再び使うにはパスフレーズを入力して unlock する必要があります。 lock にはパスフレーズが不要なので、スクリーンロックやログアウトの hook に仕込むこともできます。 しばらく token を使わない間は lock しておくことで、攻撃者が token を取得できる時間を縮めることができます。 gh にはこのようにユーザーが token を一時的に取得できなくする仕組みはありません。 refresh token の漏洩の疑いを検知して警告する refresh token は refresh するたびに新しいものに置き換わります (rotation)。 そのため、攻撃者が盗んだ refresh token を先に使うと、正規のユーザーが次に refresh しようとしたときに失敗します。 これは refresh token が漏洩したことを示すシグナルになりえます。 gh の場合、 refresh に失敗するとクレデンシャルを削除し、 the token for <host> has expired; please run 'gh auth login' to re-authenticate と表示するだけです。 漏洩の可能性を示すものは何もなく、クレデンシャルも削除されてしまうので調査の手がかりも残りません。 ユーザーは単に期限切れになっただけだと思って、何も疑わずに再ログインするでしょう。 一方、 ghtkn の agent は、有効期限内のはずの refresh token で refresh に失敗した場合、漏洩の可能性を示す警告をクライアントに返し、ユーザーに表示します。 これによりユーザーは漏洩に気づいて調査や対応を始めることができます。 SDK による Go CLI との連携 ghtkn は Go の SDK を提供しており、 aqua や pinact, ghir など様々なツールが SDK によってシームレスに access token を取得できます。 access token を環境変数として露出する必要がないので、漏洩経路を減らすことが出来ます。 gh が今後同じことをできるようになるか ghtkn は開発環境で任意コードが実行された場合でも refresh token が容易に漏洩しないようにするために、 agent backend でのみ refresh token をサポートしています。 このように daemon を常時起動するアーキテクチャは、少なくとも現時点の gh にはありません。 また、 gh は単一の特権アプリであるという性質上、 app を使い分けて permission やアクセス範囲を制限することも難しいでしょう。 gh の今後の改善には期待していますが、これらの点で ghtkn には当面優位性があると考えています。 さいごに 以上、 gh が短命 token をサポートした後も ghtkn が必要な理由を説明しました。 短命 token が公式にサポートされることは非常に喜ばしいことですが、 ghtkn にはセキュリティ的にみて様々な優位性があります。 gh の短命 token から ghtkn に興味を持った方は是非試してみて下さい。 AI friendly なので分からないことがあれば Claude Code などの coding agent に聞いてみるとよいでしょう。 zenn.dev

Achievements

Awards earned by the repositories suzuki-shunsuke owns. Select an award to embed its badge.

No awards yet

Repositories earn awards by reaching the top three in a completed trending day or week.

A repository earns an award for finishing first, second or third in a completed trending day or week, ranked by new stars, new forks or pull request authors. Awards follow a repository through renames and transfers and count toward its current owner. Weeks run Monday to Sunday, UTC.