CDKのParamsAndSecretsVersions.V1_0_103で入るレイヤーは:4のままだった

  • AWS
  • CDK
  • Lambda
  • Inspector

この記事は、構成と文章の整理に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:4

ARNに入っている 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) は他のスタックにもコピペで残りがちなので、リポジトリ内をまとめて確認しておくといい。