

CDKのParamsAndSecretsVersions.V1_0_103で入るレイヤーは:4のままだった
この記事は、構成と文章の整理にAIを使っています。
あくまで読みやすくするための補助で、内容そのものをAIにすべて任せて書いているわけではありませんが、ところどころ相談しながら整えています。
何が起きたか
Amazon Inspectorで、Lambda関数に次の検出が出ていた。
CVE-2026-39821 - go/stdlib
関数のランタイムは nodejs24.x。Goは使っていないのに go/stdlib が出てくる。中身を見ると、検出元は関数コードではなくレイヤーだった。
Inspectorのドキュメントによると、Lambdaの標準スキャンが対象にするのは、関数コードと一緒にアップロードした依存と、レイヤーから継承した依存。ランタイムに最初から入っているAWS SDKはスキャン対象外になっている。なので、レイヤーの中にGoでビルドされたバイナリがあれば、Node.jsの関数でも go/stdlib として拾われる。
今回のレイヤーは AWS Parameters and Secrets Lambda Extension だった。この拡張機能はGo製のバイナリで、Lambdaのサンドボックス内で localhost:2773 に立ち上がり、Parameter StoreとSecrets Managerへのリクエストを中継してキャッシュする。
CVE-2026-39821の中身
Goの idna パッケージの問題。ToUnicode が、xn-- で始まるPunycodeラベルのうち、デコードするとASCII文字だけになるものをエラーにせず受け入れてしまう。たとえば ToUnicode("xn--go-") が "go" を返す。
IDNAには「Unicodeのドメイン名に対して、DNS用のASCII表現は1つだけ」という前提がある。この前提に依存してチェックしているプログラムだと、xn-- で偽装した名前でチェックをすり抜けられる可能性がある。
修正バージョンは、1.26系なら1.26.6。1.25系では1.25.13への更新でこのCVEに対応している事例がある。深刻度はソースによって違っていて、CVSS 9.6でCriticalとするところもあれば、Highとするスキャナもあった。
この拡張機能が外部から受け取ったドメイン名をIDNAで処理しているとは考えにくいので、実際に悪用される可能性は低そう。ただしこれは推測で、拡張機能のコードは確認していない。
原因はCDKのenum
CDKでは次のように書いていた。
paramsAndSecrets: lambda.ParamsAndSecretsLayerVersion.fromVersion(lambda.ParamsAndSecretsVersions.V1_0_103, { cacheSize: 500, logLevel: lambda.ParamsAndSecretsLogLevel.WARN,}),ParamsAndSecretsVersions のenumには V1_0_103 しかない。CDKのドキュメントにも「This is the latest version」と書いてある。
このenumは、拡張機能のバージョン(1.0.103)を、リージョンとアーキテクチャごとのレイヤーARNに変換するためのもの。cdk synth したテンプレートを見ると、us-east-1・arm64では次のARNになっていた。
arn:aws:lambda:us-east-1:177933569100:layer:AWS-Parameters-and-Secrets-Lambda-Extension-Arm64:4ARNに入っている 177933569100 は自分のアカウントIDではなく、AWSがus-east-1でこのレイヤーを公開しているアカウントのID。Systems Managerのドキュメントに載っている公開情報なので、そのまま載せている。
一方、Systems Managerのドキュメント(2026年8月21日更新)に載っている、us-east-1・arm64の最新ARNは :116。CDKのenumが指す :4 とはかなり差がある。
CDKにこの機能が入ったのは2023年6月ごろで、そのときの最新が1.0.103だった。以降、AWS側はレイヤーを更新し続けているが、enumには追加されていない。意図的に止めているという情報は見つからなかったので、単に更新されていないだけだと思う。
V1_0_103 が非推奨(deprecated)とされているわけではない。CDKのドキュメントの例も今でも fromVersion(V1_0_103) を使っている。ただ、このenumを使っている限り古いレイヤーに固定され、脆弱性の修正も入ってこない。
レイヤーのARNを解決する方法
ParamsAndSecretsLayerVersion には fromVersionArn もあって、ARNを直接渡せる。ARNの持ち方は3通り考えられる。
| 方法 | 解決するタイミング | 良いところ | 気になるところ |
|---|---|---|---|
| 固定ARNを書く | 書いたとき | 一番明示的で、再現性がある | 更新は完全に手作業 |
valueFromLookup |
cdk synth のとき |
値が cdk.context.json に残るので、差分で追える |
更新するにはcontextをリセットして取り直す |
valueForStringParameter |
デプロイのとき | デプロイすればその時点の最新になる | コードを変えていなくてもバージョンが変わる。差分にも出ない |
最新のARNは、SSMのパブリックパラメータとしてアーキテクチャごとに公開されている。
- x86_64:
/aws/service/aws-parameters-and-secrets-lambda-extension/x86/latest - arm64:
/aws/service/aws-parameters-and-secrets-lambda-extension/arm64/latest
Systems Managerのドキュメントには、CloudFormationで {{resolve:ssm:...}} を使ってこのパラメータを参照し、スタックの作成・更新のたびに最新を使う例が載っている。CDKでこう書けという記事は見つからなかったが、AWS自身はパブリックパラメータで最新を参照するやり方を案内している。
今回は、古いまま放置していたことが問題だったので、デプロイ時に解決する valueForStringParameter にした。
const extensionArn = ssm.StringParameter.valueForStringParameter( this, '/aws/service/aws-parameters-and-secrets-lambda-extension/arm64/latest',);
// ...
paramsAndSecrets: lambda.ParamsAndSecretsLayerVersion.fromVersionArn(extensionArn, { cacheSize: 500, logLevel: lambda.ParamsAndSecretsLogLevel.WARN,}),fromVersionArn にトークンを渡して問題なく動くかは気になっていたが、cdk diff を見る限り問題なかった。
[+] Parameter ExampleStage/ExampleStack/SsmParameterValue:--aws--service--aws-parameters-and-secrets-lambda-extension--arm64--latest:... {"Type":"AWS::SSM::Parameter::Value<String>","Default":"/aws/service/aws-parameters-and-secrets-lambda-extension/arm64/latest"}
Resources[~] AWS::Lambda::Function ExampleStage/ExampleStack/ExampleFunction ExampleFunctionXXXXXXXX └─ [~] Layers └─ @@ -1,3 +1,5 @@ [ ] [ [-] "arn:aws:lambda:us-east-1:177933569100:layer:AWS-Parameters-and-Secrets-Lambda-Extension-Arm64:4" [+] { [+] "Ref": "SsmParameterValueawsserviceawsparametersandsecretslambdaextensionarm64latest...Parameter" [+] } [ ] ]AWS::SSM::Parameter::Value<String> 型のCloudFormationパラメータが追加され、Layers がそのパラメータへの Ref に置き換わっている。diffには Ref しか出ないので、実際に何番のレイヤーが入るかはデプロイしてみるまでわからない。
flowchart LR A[CDKコード<br/>valueForStringParameter] --> B[テンプレート<br/>SSM Parameter型のParameter] B --> C[CloudFormation<br/>デプロイ時に値を解決] C --> D[SSMパブリックパラメータ<br/>.../arm64/latest] D --> E[Lambda関数のLayers<br/>その時点の最新ARN]
Lambda関数への影響
レイヤーのバージョンは不変なので、AWSが新しいバージョンを公開しても、既存の関数が勝手に切り替わることはない。切り替わるのはデプロイして関数の設定が更新されたときだけ。
- 設定が更新されると、既存の実行環境は使われなくなり、次の呼び出しから新しいレイヤーでコールドスタートする。実行中の呼び出しには影響しない
- バージョンを発行している場合は、そのときのレイヤーがそのバージョンに固定される
- 拡張機能のインターフェース(
localhost:2773とX-Aws-Parameters-Secrets-Tokenヘッダー)は変わっていないので、関数側のコードはそのままでいい
注意点として、CDK CLIにはテンプレートとパラメータが前回と同じならデプロイをスキップする仕組みがある。SSMのパスは変わらないので、他に変更がないと最新が取り込まれない可能性がある(未確認)。今回はCDK Pipelines経由のデプロイなので、CloudFormationのチェンジセットでSSMの値の変化がどう扱われるかも未確認。どちらにしても、デプロイが実際に走ったタイミングでその時点の最新になる。
結果
デプロイ後、関数のレイヤーは次のARNになった。
arn:aws:lambda:us-east-1:177933569100:layer:AWS-Parameters-and-Secrets-Lambda-Extension-Arm64:175ドキュメントの表にある :116 よりさらに新しい。ドキュメントの表はパブリックパラメータより遅れて更新されることがあるので、表を見てARNをハードコードするより、パブリックパラメータを参照したほうが確実だった。
Inspectorの再スキャン後、CVE-2026-39821を含むGo stdlib系の検出はすべて消えた。拡張機能経由でのパラメータとシークレットの取得も問題なく動いている。
fromVersion(V1_0_103) は他のスタックにもコピペで残りがちなので、リポジトリ内をまとめて確認しておくといい。