生成AI時代にハッカーとして生き残るには

タイトルは今の自分の悩みであってこの記事を読んでも特に解決はしません。

ハッカーあるいはエンジニアとは

ここでいうエンジニアはソフトウェアを開発したりデリバリーしたりするためにエンジニアリングを行う職業で、今の自分の職業でもある。
ハッカーがなんなのかはよくわかっていない。ただ自分がソフトウェアエンジニアになるきっかけになった憧れのようなものを想像している。
欲しいものがなければ自分で作るし、痒い所に手が届かないものがあれば手が届くようにする。成果を公開すれば誰かがそれを使ってくれるかもしれないし、物好きな人がそれを改造してくれるかもしれない。
好きでやっているので「技術の無駄遣い」と言われるようなこともしたりする。そしてそれを楽しそうに話す。
ビジネスとマッチすれば好きなことをしながらそこそこお金ももらえるらしい。社会貢献できる機会も増えてきた。そんな存在。

Web業界のソフトウェアエンジニアとして働き始めて9年ちょっとが経過した。
書かれているプログラムの大半は英数字と記号の羅列からなる無機質なもので、当初はソフトウェアエンジニアは淡々と無機質なものを生成する職業だと思っていた。
実際に働いてみるとそんなことはなく、機械より人間と対話する時間の方が長いし、特に特定のプロダクトに関わる時間が多ければそのコードに対して生き物とはまた違った何かを育てているような愛着を持つ。
全く理由もなくコードを書くことはまれで、関わる人々の何かしらの意思があり、それを実現するためにコードに落とし込んでいるので当然といえは当然である。
世の中で動いているコードは無機質な見た目に反して泥臭いものの集合体なんだなという実感が得られたのが最近である。

人間の介在価値とは

2023年は生成AIブーム(?)もあって街を歩いていてもAIの話題を耳にする機会が増えた。
生成AIで業務の生産性を向上させようという話が世の中では本格化しそうだが、実のところ嬉しいよりも不安の方が大きい。

会社を経営する立場からすると仕事に再現性があれば安定して成果が生産できるし、もっと言えばAIに置き換えやすくなるので嬉しいのでしょう。
雇われる側の立場としては再現性があることがわかった/再現できるようにした時点から当初の仕事の価値は減り始めているので、「生産性向上バンザイ!🙌」と喜んでいる場合ではない。
その代わりに何かできることが増えていないと給料を維持するのも難しくなってきそうだ。だからリスキリングが流行り始めたのだろう。

今更AIを頭ごなしに規制すべきだとも思わないし、積極的に取り入れて個人の能力拡張に生かしていくべきだとは思う。
仮にAIに頼らずに何かを作り上げたとしても、AIが生成するものと似通っていれば相対的に価値が下がってしまう(むしろAIの価値が上がったりする)し、自分にとってはオリジナリティがある行動だったものが実はそうでもないことがAIによって勝手に証明されてしまうところに危機感を感じている。

自分や自分たちの力で成し遂げたと思っていること、誇りに思っていること、アイデンティティだと思っていることを機械に否定されるような気がしてつらい。
今まではわざわざ他人の小さなことを逐一否定してまわるような暇な人はいなかったために各人のアイデンティティは守られていた気がするが、個人とAIの距離が縮まったことでそこがスケールしてしまった感がある。

強制的に自分が行なっていることの価値を考えさせられる世の中になって努力したり成長したりするきっかけが増えた反面、常にそれを突き付けられる世の中になってしまったように感じている。

逃げのAI活用

単に自分が想像できないことを「AIを活用すれば〜」という言葉で置き換えるのは逃げだとも感じるようになってきた。

少なくとも現時点では知らないこと、理解できないことを全てAIに任せておけば勝手に解決しておいてくれるわけではないし、逃げるとむしろうまく活用するチャンスを失ってしまう気がする。
何かをしたいという意思は必ず人間から生まれてくるものであるから、そこにどうAIを組み込んで、どんな出力が得られるように仕上げるかは人間が決めなければいけない。AIを活用しても人間の意図は最後まで途切れないはず。
あえてよくわからない状態を生み出すことを楽しむ方法もあるかもしれないが、多くはそうではないだろう。

対話型の生成AIは誰でも使いやすく、組み込みやすいインターフェースを持つことから急速にいろんなサービスに組み込まれていく一方で、 人間がわざわざプログラミング言語というものを発明して自然言語の曖昧さをなくした厳密な定義を作ったのに、自然言語をコンピュータ(AI)に解釈させて非決定的な結果を頑張ってチューニングするような使い方に関してはなんとも言い難い違和感がある。
でもAIが生成したコードを人間がレビューして修正しつつ採用するスタイルは慣れてきた。人間の意図や思考を論理的に整理した結果生成されるものがコードだとすれば、思考の整理のための補助としてのAIなのだろうと考えると受け入れやすい気もする。この違いは何だろう。このあたりに生成AIをうまく活用していく切り口がある気がしている。

この先生きのこるには

自分が介在する価値を高めるために、自分がが介在しなくてもいいものをエンジニアリングで増やしていこうという気持ちが以前より強くなってきた。
AIによる人間のタスクの置き換えは自然な流れであるから乗らない手はないし、それはそれとして自分の能力がAIで拡張されるのは嬉しいので、AIの活用は増やしていきたい。

職業エンジニアとしてお金を稼いでいくこともそうだが、ハッカーへの漠然とした憧れがあるので、その楽しみがAIに置き換えられることは阻止したい。
そのためにやるべきことはなんなのか、自分らしい仕事とはなんなのか悩んでいる。
生きのこる方法について悩むのは現役世代しかいないと思うので、自分で考えていくしかない。将来の人たちにとっては憧れる対象がなくなれば憧れることもないので。

この記事はVSCodeでGitHub Copilotを使いながら書いたので何割かはAIの出力がまざっていて、自分の考えもすでにAIに引っ張られている気がする。
暗い話ばかり書いたが特に落ち込んでるわけでもなくて、好奇心さえ失わなければいろんなことが達成できるはずだし、楽しい仕事もなくならないだろうと楽観的に考えてはいる。

さいごに

個人的な答えは見つかっていませんが、AIの回答を置いておきます。

ChatGPTからの回答

Akamai x UNIQLOコラボTシャツに書かれたプログラムを解読してみる

Akamai x UNIQLOコラボのTシャツのデザインが、エンジニアからすると目を引くようなものだった。

Akamaiのリリース記事を読むと、

Akamai、ユニクロのチャリティTシャツプロジェクト「PEACE FOR ALL」に参画|アカマイ・テクノロジーズ合同会社のプレスリリース

このデザインに使われている「texture」というコードは、さまざまデジタル体験の裏側にある共通言語を表現しています。

と書かれており、どんなコードなのかさらに気になったので、解読してみることに。

写経

写真をみた感じGoっぽい感じがする。とりあえず写経してみる。

gist.github.com

ぱっと見でわかったことは

  • HTTPサーバっぽい
  • channelが多用されていそう
  • コードの右端が途切れてそう
  • Goっぽいけどセミコロンがたくさんある(1行に書くため?)

くらいだった。右端が切れていることからコピペしただけでは動かないことがわかる。 整合性が取れていなさそうに見受けられる部分もありそうなので、補うだけではダメで、若干の修正も必要そう。

解読

ということで、前後の文脈を頼りに整合性をとりながら、コンパイルが通るようにしたコードがこちら。1時間くらい掛かってしまった。

gist.github.com

何をするコードなのか

コンパイルは通るようにしたが、実行すると全てのgoroutineが停止してしまうため、panicする。 httpサーバっぽいコードだが、listenする処理などがないため、それはそうだろうという感じがする。

雰囲気から察するに /admin/status 2つのエンドポイントを持っていて、/admin

type controlMessage struct {
    Target string
    Count  int
}

このようなメッセージを受け取ったら何かの処理をするサーバのようだ。 /status は処理中かどうかがわかるようだ。

それ以上の隠された何かは特に見つけられなかったので、知っていたら誰か教えてください。

このコードからの学び

statusPollChannel := make(chan chan bool)

チャネルのチャネル(chan chan)という使い方は見たことがなかった(意識してないだけかもしれない)ので、確かにそういう使い方もあるかという学びになった。

何か特定の自分がリクエストしたものに対しての応答を待ち受けるような実装があれば使えそう。

ここまでやってから気づいたこと

Tシャツのハイライトされた文字だけ読むと、、ということなのか😇

感想

ここまでTシャツのデザインを凝視したことはなかったので、ユニクロに立ち寄った際にこのTシャツが売っていたら買おうと思う。

「一句」を判定するライブラリikku-goをリリースしました

作ったもの

ikku-go というライブラリを作ったので公開しました。 github.com

ロゴ画像(?)はStable Diffusionで生成しました。

575を検知したい

このライブラリは何気ない会話から575が生まれているという気づきを与えてくれます。

もともとはr7kamuraさんの ikku を使っていたのですが、最近はgoを使うことが多かったため、go版も欲しくなったのが実装理由です。 実装の大部分を参考にさせていただきました。 GitHub - r7kamura/ikku: Discover haiku from text.

使い方

非常に簡単で、引数にとった文字列から5-7-5の形になっている部分を抽出します。

オプションで別途ルールを渡すことで、短歌のような5-7-5-7-7の型式にも対応可能です。 https://pkg.go.dev/github.com/kurochan/ikku-go#ReviewerOptionRule

import (
    "fmt"

    "github.com/ikawaha/kagome-dict/ipa"
    ikku "github.com/kurochan/ikku-go"
)

func main() {
    r, err := ikku.NewReviewer(ipa.Dict())
    if err != nil {
        panic(err)
    }
    song := r.Find("ああ古池や蛙飛び込む水の音。。")
    fmt.Println(song.String())
    // Output: 古池や蛙飛び込む水の音
}

形態素解析用の辞書の読み込みが重いせいか、残念ながらGo Playground上ではタイムアウトしてしまい動作せず。 https://go.dev/play/p/7BBNLRWaOml

実装

Goで実装された日本語形態素解析ライブラリ ikawaha/kagome を利用しています。

GitHub - ikawaha/kagome: Self-contained Japanese Morphological Analyzer written in pure Go

そのため別途mecabのネイティブライブラリを必要としないため利用が非常に楽です。cgoも不要です。

サンプルでは辞書にMeCab IPADICを利用していますが、他の辞書も利用可能となっています。

個人的にはコレクション操作系は https://github.com/samber/lo の書き心地が良いと思っていてよく使っているのですが、ここは好みが分かれるところかもしれません。

これ

func (s *Song) String() string {
    strs := lo.FlatMap(s.Phrases, func(ns []Node, _ int) []string {
        return lo.Map(ns, func(n Node, _ int) string { return n.String() })
    })
    return strings.Join(strs, "")
}

ikku-go/song.go at 026ed526fbeb0b2e2c76ec5df7a6cedf4923fe91 · kurochan/ikku-go · GitHub

samber/lo を使わないと

func (s *Song) String() string {
    var str string
    for _, ph := range s.Phrases {
        for _, n := range ph {
            str += n.String()
        }
    }
    return str
}

こうなります。(わかりにくいと言われそうな気がする)

こういう部分は1行で書けた方がスッキリしてていいんじゃないでしょうか。

case lo.Contains([]string{"助詞", "助動詞"}, n._type()):

ikku-go/node.go at 026ed526fbeb0b2e2c76ec5df7a6cedf4923fe91 · kurochan/ikku-go · GitHub

肝心の575判定ロジックの部分はこちらです。

ikku-go/scanner.go at 026ed526fbeb0b2e2c76ec5df7a6cedf4923fe91 · kurochan/ikku-go · GitHub

ほぼr7kamuraさんの判定ロジックを流用させて頂きました🙏

テスト

ひととおりのテストを書いたあとにfuzzingも入れてみました。

残念ながら?想定外のケースのバグは見つからず。。他のプロジェクトで意図しないバグが発見できたことがあったので、混み入ったロジックを実装した時はfuzzingも入れておきたいですね。

https://github.com/kurochan/ikku-go/blob/026ed526fbeb0b2e2c76ec5df7a6cedf4923fe91/reviewer_test.go#L236-L256

まとめ

  • go版の「一句」判定ライブラリを作りました
  • みんな気軽に575判定してみてね!

サーバサイドでJWTの即時無効化機能を持っていないサービスは脆弱なのか?

きっかけ

昨年(2021年9月ごろ)に徳丸さんのこのツイートを見て、「2022年にはJWTを用いたセッション管理に代表される、ステートレスなセッション管理は世の中に受け入れられなくなっていくのだろうか?」と思っていました。

結論

冒頭だけ読んで勘違いが広まるとよくないので、議論の結果最新版では方針が変わったということだけ先に書いておきます。詳細が気になる方は最後まで読んでみてください🙇‍♂️

現状(2022年3月)の日本語版の記述

OWASP Top10 2022の「A01:2021 – アクセス制御の不備」の日本語版では、2022年3月3日時点では以下のように書かれています。

JWTトークンはログアウト後にはサーバー上で無効とされるべきである。

github.com

この記述からは、ログアウト時にクライアントサイドでセッション(JWT)を捨てるだけでは不十分で、クライアント側がJWTを再利用すればサーバへのアクセスが成功してしまうのが問題であるということが読み取れます。 サーバ側で無効なJWTを管理しなければならないということは、JWTを利用したセッション管理に代表されるようなステートレスなセッション管理は認証の実装方法として不十分なのでしょうか?

議論

こちらのIssueで議論がされていました。(徳丸さんのツイートよりも後に行われた議論のようです)

github.com

議論の詳細はここでは書きませんが、ここの議論からわかることをまとめます。

まず、JWT仕様を見る限り、OAuthをベースとしたOpenID Connectで使用することが想定された設計であることは明らかです。

datatracker.ietf.org

OAuthには2つのトークンがあり、短命なアクセストークンと長命なリフレッシュトークンから構成されています。 それぞれのトークンがどんな形式であるべきかは指定されていませんが、アクセストークンにはJWTが使われている実装が多いようです。

アクセストークンの生存期間を短命にすることで、アクセストークンが不正に利用されるリスクを低減しています(リスクを低減しているのであって、完全に防いでいるわけではない)。この短命なトークンの実装としてJWTを使うことで、ステートレスな認証を実装することができ、データベースの負荷低減に寄与します。

その代わりにリフレッシュトークンはサーバサイドでの管理を必須とし、ユーザがログアウトするなどの操作を行うとリフレッシュトークンが即時無効化され、アクセストークンの更新はできなくなります。

この前提があるため、少なくとも長命なアクセストークンを生成することは許容されなさそうです。どれくらいの時間が短命なのかどうかはサービスごとのリスク許容度によって判断する形になりそうですね。

(追記ここから 2022/04/18) 徳丸さんから反応をいただいたのですが、

たしかに誤解が広がるとよくないので、こちらでも強調しておきます。 JWTは本来的にはセッション管理のためのものではなく認証情報の受け渡しのためのものですが、その便利な特性からセッション管理にも転用されることもあるという背景があります。

こちらも参考になったので紹介しておきます 🙇‍♂️

(追記ここまで)

「数分であろうと即時無効化されているべきであるセッションが有効なものとして使えるのはリスクは低減されているが問題であることに変わりはないのでは?」という意見ももちろんありますが、絶対に許容されないという温度感ではないようです。

その代わりに、この特性が一般にはまだ広く理解が広まっていないのでは?という懸念への対応として、以下のリンクを紹介することでメリットとデメリットを正しく理解してもらおう、ということで着地したようです。 www.oauth.com

変更

この議論の結果、2021年9月24日にはA01の記述を修正するプルリクエストが出おり、即日取り込まれていたのでした。

github.com

Stateful session identifiers should be invalidated on the server after logout. Stateless JWT tokens should rather be short-lived so that the window of opportunity for an attacker is minimized. For longer lived JWTs it's highy recommended to follow the OAUTH standards to revoke access.

ということで、

「ステートフルなセッション識別子は、ログアウト後にはサーバー上で無効とされるべきである。ステートレスなJWTトークンは、攻撃者の機会を最小にするために、むしろ短命であるべきである。寿命の長いJWTの場合は、アクセスを取り消すためにOAuth標準に従うことが強く推奨される。」

という内容に記述が変わりました。

日本語版が追いついていない😱

日本語版の記述はどうなっているかというと、この議論の結果が反映されておらず、英語版と日本語版で乖離が出ていることがわかりました。 日本語版だけを読んで勘違いが広がってしまうのもよくないので、英語版の最新に追従するプルリクエストを出してみました。問題がなければ取り込んでもらえるかな?と思っています。

github.com

JWTの取り扱いに関する記述が意味的な差分としては一番大きそうですが、他の差分に関しては軽微な修正だと思われます。