0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

NVIDIA SkillSpectorとAWS Lambda FunctionでAgent Skillsの検証器を作成する

0
Last updated at Posted at 2026-07-25

はじめに

Agent Skillsは、2025/12/18に標準化が行われた「AIエージェントの手順書」とも言える機能であり、いまやどんなエージェントハーネスにも実装されている、AIエージェント開発者にとっては必須の技術要素だ。一方で、スクリプトを使って自由に色々なことができるため、野良のスキルによる脅威というリスクを同時に抱えることになる。

OWASPのAgentic Skills Top 10でも、以下のようなリスクが挙げられていて、対策が必要だと言われている。

# Risk Severity Key Mitigation Real-World Evidence
AST01 Malicious Skills Critical Merkle root signing, registry scanning(Merkleルート署名、レジストリスキャン) ClawHavoc (1,184 skills), ToxicSkills (76 payloads)
AST02 Supply Chain Compromise Critical Registry transparency, provenance tracking(レジストリの透明性、来歴追跡) ClawHub collapse, Claude Code CVE-2025-59536
AST03 Over-Privileged Skills High Least-privilege manifests, schema validation(最小権限マニフェスト、スキーマ検証) 280+ credential-leaking skills (Snyk, Feb 2026)
AST04 Insecure Metadata High Static analysis, safe parsers, sandboxed loading(静的解析、安全なパーサー、サンドボックス化された読み込み) Fake “Google” skill impersonation; YAML payload delivery in SKILL.md
AST05 Untrusted External Instructions High Source inventory, content pinning, continuous rescanning(参照元の棚卸し、コンテンツの固定、継続的な再スキャン) Air PoC bypassed all scanners; 26,000 agents at risk
AST06 Weak Isolation High Containerization, Docker sandboxing(コンテナ化、Dockerサンドボックス) OpenClaw host-mode execution, 135K exposed instances
AST07 Update Drift Medium Immutable pinning, hash verification(不変バージョン固定、ハッシュ検証) ClawJacked (CVE-2026-28363), patch-lag exploitation
AST08 Poor Scanning Medium Semantic + behavioral multi-tool pipeline(セマンティック分析と動作分析を組み合わせた複数ツールのパイプライン) Pattern-matcher bypass via natural-language injection
AST09 No Governance Medium Skill inventories, agentic identity controls(スキルのインベントリ管理、エージェントID制御) 53K exposed instances with no SOC visibility
AST10 Cross-Platform Reuse Medium Universal YAML format(汎用YAML形式) Malicious skills ported across ClawHub, skills.sh

そんな中で、NVIDIAが、脅威に対する救世主とも言える、Agent Skillsの妥当性を検証するためのOSS、SkillSpectorを発表した。

SkillSpectorとは

17カテゴリ、68パターンの脆弱性に対する検査機能を持っていて、ディレクトリやZipファイルを渡すことで結果を出力してくれる。
さらに、パターンマッチの静的解析以外に加えて、LLMによる意味評価も可能だ。
CLI/SDKの呼び出しにも対応していて、CI/CDからの呼び出しなども柔軟に対応できる。
ライセンスはApache 2.0ライセンスであり、自由度も高く利用できるのも魅力だ。

今回の記事では、SkillSpectorをAWS Lambda Functionから呼び出し、検証済みの情報を付与した結果を作成する。

全体構成

今回は、Amazon S3のイベント通知を使って、入ってきたZipファイルを検証し、結果を再度Zipファイルに固めるという構成にする。

skillspector.png

AWS Lambda Functionで実行するコードの全体像をまとめておく。
また、この後、Terraformでの構築例をまとめていく。

AWS Lambda Function実行コード

(上に空行が必要)

handler.py
import hashlib
import io
import json
import os
import tempfile
import urllib.parse
import uuid
import zipfile
from datetime import UTC, datetime
from pathlib import Path

import boto3

RECOMMENDATION_TO_DECISION = {
    "SAFE": "APPROVED",
    "CAUTION": "WARNING",
    "DO_NOT_INSTALL": "REJECTED",
}


def _normalize(value: dict) -> bytes:
    """署名対象を常に同じバイト列へ正規化する。"""
    return json.dumps(value, ensure_ascii=True, sort_keys=True, separators=(",", ":")).encode()


def _manifest_for(skill_dir: Path) -> bytes:
    """Skill配下のファイルから決定的なManifestを作る。"""
    files = []
    for path in sorted(skill_dir.rglob("*")):
        if not path.is_file() or ".verification" in path.relative_to(skill_dir).parts:
            continue

        content = path.read_bytes()
        files.append(
            {
                "path": path.relative_to(skill_dir).as_posix(),
                "sha256": hashlib.sha256(content).hexdigest(),
                "size": len(content),
            }
        )

    manifest = {"schemaVersion": 1, "files": files}
    return _normalize(manifest)


def _sign_attestation(attestation: dict) -> bytes:
    """AttestationのSHA-256ダイジェストをAWS KMSで署名する。"""
    digest = hashlib.sha256(_normalize(attestation)).digest()
    response = boto3.client("kms").sign(
        KeyId=os.environ["KMS_KEY_ID"],
        Message=digest,
        MessageType="DIGEST",
        SigningAlgorithm="RSASSA_PSS_SHA_256",
    )
    return response["Signature"]


def _inspect_skill(skill_dir: Path, validation_id: str) -> None:
    """SkillSpectorを実行し、Skill内へ検証情報を書き込む。"""
    model = os.environ["SKILLSPECTOR_MODEL"]
    from skillspector import graph

    result = graph.invoke(
        {
            "skill_path": str(skill_dir),
            "output_format": "json",
            "use_llm": True,
        }
    )

    normalized_manifest = _manifest_for(skill_dir)
    recommendation = result["risk_recommendation"]
    decision = RECOMMENDATION_TO_DECISION[recommendation]
    severity = result["risk_severity"]
    sarif = result["sarif_report"]
    attestation = {
        "schemaVersion": 1,
        "attestationId": validation_id,
        "validationId": validation_id,
        "subject": {
            "skillName": skill_dir.name,
            "manifestSha256": hashlib.sha256(normalized_manifest).hexdigest(),
        },
        "scanner": {
            "name": "SkillSpector",
            "version": "unknown",
            "mode": "LLM",
            "model": model,
        },
        "result": {
            "status": "COMPLETED",
            "severity": severity,
            "riskScore": result["risk_score"],
            "recommendation": recommendation,
            "decision": decision,
        },
        "issuedAt": datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ"),
        "issuer": os.environ["ATTESTATION_ISSUER"],
    }

    verification_dir = skill_dir / ".verification"
    verification_dir.mkdir(exist_ok=True)
    (verification_dir / "skillspector-results.sarif").write_bytes(json.dumps(sarif).encode())
    (verification_dir / "skill-manifest.json").write_bytes(normalized_manifest)
    (verification_dir / "skill-attestation.json").write_bytes(_normalize(attestation))
    (verification_dir / "skill-attestation.sig").write_bytes(_sign_attestation(attestation))


def _build_verified_zip(extracted_dir: Path) -> bytes:
    """検証情報を追加した展開済みSkillディレクトリをZIPに戻す。"""
    output = io.BytesIO()
    with zipfile.ZipFile(output, "w", zipfile.ZIP_DEFLATED) as archive:
        for path in sorted(extracted_dir.rglob("*")):
            if path.is_file():
                archive.write(path, path.relative_to(extracted_dir).as_posix())
    return output.getvalue()


def lambda_handler(event: dict, context: object) -> dict:
    record = event["Records"][0]
    validation_id = str(uuid.uuid4())

    """ Inputバケットの情報取得""" 
    source = (
        boto3.client("s3")
        .get_object(
            Bucket=record["s3"]["bucket"]["name"], Key=urllib.parse.unquote_plus(record["s3"]["object"]["key"])
        )["Body"]
        .read()
    )

    """ Zipファイルの解凍とSkillSpectorの実行 """
    with tempfile.TemporaryDirectory() as work_dir:
        extracted_dir = Path(work_dir) / "skill"
        extracted_dir.mkdir()
        with zipfile.ZipFile(io.BytesIO(source)) as archive:
            archive.extractall(extracted_dir)

        skill_dir = next(path.parent for path in extracted_dir.rglob("SKILL.md"))
        _inspect_skill(skill_dir, validation_id)
        verified_zip = _build_verified_zip(extracted_dir)

    """ SkillSpector実行結果のOutputバケットへの格納"""
    output_bucket = os.environ["OUTPUT_BUCKET"]
    key = f"output/{validation_id}/verified-skills.zip"
    boto3.client("s3").put_object(Bucket=output_bucket, Key=key, Body=verified_zip)

    return {"result": "OK"}

AWSのリソース作成

Amazon S3 Bucket

Amazon S3 Bucketを以下のように作っていく。

例によって、aws_s3_bucket_public_access_blockaws_s3_bucket_ownership_controlsはセキュリティのおなじないとして設定していく。
今回は最小限の構成として書いておくので、aws_s3_bucket_server_side_encryption_configurationaws_s3_bucket_lifecycle_configurationは必要に応じて適宜設定をしておこう。

Input用のバケット
resource "aws_s3_bucket" "input" {
  bucket        = local.s3_input_bucket_name
}

resource "aws_s3_bucket_public_access_block" "input" {
  bucket = aws_s3_bucket.input.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

resource "aws_s3_bucket_ownership_controls" "input" {
  bucket = aws_s3_bucket.input.id

  rule {
    object_ownership = "BucketOwnerEnforced"
  }
}

resource "aws_s3_bucket_versioning" "input" {
  bucket = aws_s3_bucket.input.id

  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_s3_bucket_notification" "input" {
  bucket = aws_s3_bucket.input.id

  lambda_function {
    lambda_function_arn = aws_lambda_function.skillspector.arn
    filter_suffix       = ".zip"

    events = [
      "s3:ObjectCreated:*",
    ]
  }

  depends_on = [
    aws_lambda_permission.s3_invoke_skillspector,
  ]
}
Output用のバケット
resource "aws_s3_bucket" "output" {
  bucket        = local.s3_output_bucket_name
  force_destroy = true
}

resource "aws_s3_bucket_public_access_block" "output" {
  bucket = aws_s3_bucket.output.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

resource "aws_s3_bucket_ownership_controls" "output" {
  bucket = aws_s3_bucket.output.id

  rule {
    object_ownership = "BucketOwnerEnforced"
  }
}

AWS Lambda Functionの準備

続いて、AWS Lambda関数を用意していく。

今回、LLM評価のモデルはClaude Sonnet 4.6を使う。
また、SkillSpectorはパッケージが大きいため、通常のZip渡しではサイズ超過で設定がエラーになる。
S3バケット経由でアプリケーションのZipを渡すことにする。
inputバケットはAmazon S3イベント通知の設定をしているので、outputバケットに置くことにする。

resource "aws_lambda_function" "skillspector" {
  function_name = local.lambda_function_name
  role          = aws_iam_role.skillspector.arn
  handler       = "handler.lambda_handler"
  runtime       = "python3.12"

  s3_bucket        = aws_s3_bucket.output.id
  s3_key           = aws_s3_object.skillspector_lambda_artifact.key
  source_code_hash = filebase64sha256(local.skillspector_lambda_zip_path)

  memory_size = 512
  timeout     = 900

  ephemeral_storage {
    size = 1024
  }

  environment {
    variables = {
      OUTPUT_BUCKET      = aws_s3_bucket.output.id
      KMS_KEY_ID         = aws_kms_key.attestation.key_id
      SKILLSPECTOR_MODEL = "jp.anthropic.claude-sonnet-4-6"
      ATTESTATION_ISSUER = "SkillSpector"
    }
  }

  depends_on = [
    aws_cloudwatch_log_group.skillspector,
  ]
}

resource "aws_cloudwatch_log_group" "skillspector" {
  name = "/aws/lambda/${local.lambda_function_name}"
}

resource "aws_lambda_permission" "s3_invoke_skillspector" {
  statement_id   = "AllowS3InvokeSeal"
  action         = "lambda:InvokeFunction"
  function_name  = aws_lambda_function.skillspector.function_name
  principal      = "s3.amazonaws.com"
  source_arn     = aws_s3_bucket.input.arn
  source_account = data.aws_caller_identity.current.account_id
}

resource "aws_s3_object" "skillspector_lambda_artifact" {
  bucket = aws_s3_bucket.output.id
  key    = "_lambda_artifacts/skill_spector.zip"
  source = local.skillspector_lambda_zip_path
  etag   = filemd5(local.skillspector_lambda_zip_path)
}

data "aws_caller_identity" "current" {}

IAMは以下のように設定する。
LLM評価のモデルにClaude Sonnet 4.6を使うため、モデルと推論プロファイル両方のARNを設定する。

resource "aws_iam_role" "skillspector" {
  name               = local.iam_role_lambda_skillspector_name
  assume_role_policy = data.aws_iam_policy_document.lambda_assume.json
}

data "aws_iam_policy_document" "lambda_assume" {
  statement {
    effect = "Allow"

    principals {
      type = "Service"
      identifiers = [
        "lambda.amazonaws.com",
      ]
    }

    actions = [
      "sts:AssumeRole",
    ]
  }
}

resource "aws_iam_role_policy" "skillspector" {
  name   = local.iam_policy_lambda_skillspector_name
  role   = aws_iam_role.skillspector.id
  policy = data.aws_iam_policy_document.lambda_custom.json
}

data "aws_iam_policy_document" "lambda_custom" {
  statement {
    effect = "Allow"

    actions = [
      "s3:GetObject",
      "s3:GetObjectVersion",
    ]

    resources = [
      "${aws_s3_bucket.input.arn}/*",
    ]
  }

  statement {
    effect = "Allow"

    actions = [
      "s3:PutObject",
    ]

    resources = [
      "${aws_s3_bucket.output.arn}/*",
    ]
  }

  statement {
    effect = "Allow"

    actions = [
      "bedrock:InvokeModel",
    ]

    resources = [
      "arn:aws:bedrock:ap-northeast-3::foundation-model/anthropic.claude-sonnet-4-6",
      "arn:aws:bedrock:ap-northeast-1::foundation-model/anthropic.claude-sonnet-4-6",
      "arn:aws:bedrock:ap-northeast-1:${data.aws_caller_identity.current.id}:inference-profile/jp.anthropic.claude-sonnet-4-6",
    ]
  }

  statement {
    effect = "Allow"

    actions = [
      "logs:CreateLogGroup",
      "logs:CreateLogStream",
      "logs:PutLogEvents",
    ]

    resources = [
      aws_cloudwatch_log_group.skillspector.arn,
      "${aws_cloudwatch_log_group.skillspector.arn}:log-stream:*",
    ]
  }
}

AWS Lambda Functionのコードのポイント

さて、AWSのリソースができたので、ここからAWS Lambda Functionのコードのポイントを説明する。

Inputバケットからの情報取得

今回、Inputバケットの入力書式は問わない(Suffixが.zipであれば通知される)ので、そのまま取得しよう。

    source = (
        boto3.client("s3")
        .get_object(
            Bucket=record["s3"]["bucket"]["name"], Key=urllib.parse.unquote_plus(record["s3"]["object"]["key"])
        )["Body"]
        .read()
    )

なお、Amazon S3イベント通知のイベント構造は以下を参照。

Zip解凍で一時ファイルを作成

Pythonの標準モジュールであるtempfileを使って、一時的な領域を作りそこにZipファイルを展開する。

    """ Zipファイルの解凍とSkillSpectorの実行 """
    with tempfile.TemporaryDirectory() as work_dir:
        extracted_dir = Path(work_dir) / "skill"
        extracted_dir.mkdir()
        with zipfile.ZipFile(io.BytesIO(source)) as archive:
            archive.extractall(extracted_dir)

        skill_dir = next(path.parent for path in extracted_dir.rglob("SKILL.md"))
        _inspect_skill(skill_dir, validation_id)

SkillSpectorの呼び出し

Skillspectorは簡単に呼び出しが可能だ。
"use_llm": Trueにする場合、SKILLSPECTOR_MODELのモデルを実行しにいくようになっている。
これは、先述したAWS Lambda Functionのリソース生成時にClaude Sonnet 4.6を渡しているため、ここで実行が決定する。

    model = os.environ["SKILLSPECTOR_MODEL"]
    from skillspector import graph

    result = graph.invoke(
        {
            "skill_path": str(skill_dir),
            "output_format": "json",
            "use_llm": True,
        }
    )

SkillSpectorのレポート自体は result["sarif_report"] に格納される。
これを、出力ディレクトリに出そう。

    sarif = result["sarif_report"]
    verification_dir = skill_dir / ".verification"
    verification_dir.mkdir(exist_ok=True)
    (verification_dir / "skillspector-results.sarif").write_bytes(json.dumps(sarif).encode())

また、以下の情報が取得可能だ。

  • 重大度: result["risk_severity"]
  • 推奨: result["risk_recommendation"]
  • リスクスコア: result["risk_score"]

これらは、レポートとは別に、skill-attestation.jsonとしてレポートを作成しておこう。
※コードは長いので割愛。

出力Zipファイルの作成

以下の関数を作成し、tempfile.TemporaryDirectoryのWithブロックから呼び出そう。

def _build_verified_zip(extracted_dir: Path) -> bytes:
    """検証情報を追加した展開済みSkillディレクトリをZIPに戻す。"""
    output = io.BytesIO()
    with zipfile.ZipFile(output, "w", zipfile.ZIP_DEFLATED) as archive:
        for path in sorted(extracted_dir.rglob("*")):
            if path.is_file():
                archive.write(path, path.relative_to(extracted_dir).as_posix())
    return output.getvalue()

これで、最低限の実装は完了だ。

いざ、動かす!

さて、これでInputのAmazon S3バケットに、Skillsの入ったZipファイルを格納すれば、Outputバケットに、verified-skills.zipというファイルが作成されるはずだ。LLMの実行もあるので、Agent Skillsの大きさにもよるが、数分はかかる。

今回、サンプルとして、Anthropic公式Agent Skillsのxlsxを入力してみた。

結果としては、

  • Severity: CRITICAL
  • Recommendation: DO_NOT_INSTALL
  • RiskScore: 100

となり、非常にハイリスクなものになった。
リスク内容がsarif形式で出力されるので確認すると、やはりスクリプトや外部コマンド実行が、OWASPの脅威に該当するとして高リスク判定されているようだ。

{
  "version": "2.1.0",
  "$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.4.json",
  "runs": [
    {
      "tool": {
        "driver": {
          "name": "skillspector",
          "version": "2.3.13",
          "rules": [
            {
              "id": "AST4",
              "shortDescription": {
                "text": "The `run_soffice` function passes user-supplied `args` directly into a subprocess call without any sanitization or allowlisting. An attacker who controls the arguments could inject arbitrary shell-level options or file paths (e.g., `--infiltrate`, macros, or paths outside the expected working directory). While the command is passed as a list (not a shell string), the lack of validation still permits misuse via crafted argument lists."
              }
            },
            {
              "id": "E2",
              "shortDescription": {
                "text": "Env Variable Harvesting"
              }
            },
            (中略)
          ]
        }
      },
      "results": [
        {
          "ruleId": "AST4",
          "message": {
            "text": "The `run_soffice` function passes user-supplied `args` directly into a subprocess call without any sanitization or allowlisting. An attacker who controls the arguments could inject arbitrary shell-level options or file paths (e.g., `--infiltrate`, macros, or paths outside the expected working directory). While the command is passed as a list (not a shell string), the lack of validation still permits misuse via crafted argument lists."
          },
          "level": "warning",
          "locations": [
            {
              "physicalLocation": {
                "artifactLocation": {
                  "uri": "scripts/office/soffice.py"
                },
                "region": {
                  "startLine": 37,
                  "endLine": 37
                }
              }
            }
          ],
          "properties": {
            "severity": "MEDIUM",
            "category": "Dangerous Code Execution",
            "pattern": "subprocess module call",
            "confidence": 0.72,
            "finding": "    return subprocess.run([\"soffice\"] + args, env=env, **kwargs)",
            "explanation": "The `run_soffice` function passes user-supplied `args` directly into a subprocess call without any sanitization or allowlisting. An attacker who controls the arguments could inject arbitrary shell-level options or file paths (e.g., `--infiltrate`, macros, or paths outside the expected working directory). While the command is passed as a list (not a shell string), the lack of validation still permits misuse via crafted argument lists.",
            "remediation": "Validate and allowlist all arguments passed to `run_soffice`. At minimum, enforce that accepted flags match a known safe set (e.g., `--headless`, `--convert-to`) and that file paths are within expected directories. Consider using `subprocess.run` with `check=True` and explicit timeouts.",
            "code_snippet": "\ndef run_soffice(args: list[str], **kwargs) -> subprocess.CompletedProcess:\n    env = get_soffice_env()\n    return subprocess.run([\"soffice\"] + args, env=env, **kwargs)\n\n\n",
            "tags": [
              "Dangerous Code Execution"
            ]
          }
        },
        (中略)
      ]
    }
  ]
}

利用者は、このレポートの内容を確認し、問題がないかを評価して導入を判断するうえで、さらなるリスク対策として独立・隔離したサンドボックス環境で動作させるなどの対策が必要になる。
AIエージェントに限らずだが、セキュリティは多層的な対策を行っていくのが原則なので、利便性とのトレードオフをみながら最適解を探すことになる。

次の一手をどう考えるか

個人で利用する場合は自己責任でリスク評価をすれば良いが、筆者はマルチテナントのエージェントハーネスプラットフォームの運用をしている。
Strands Agentsの機能を使いAgent Skillsも実装しているが、自己責任とはいえ事故られて自分が運営しているプラットフォームから情報流出すると困ったことになってしまうため、ある程度Skillsにも縛りを入れる必要があるだろう。

今回作ったZipファイルには、skill-attestation.jsonとして評価結果のサマリを格納している。

この内容でリスクが高い場合は、Skillsのロード時にエラーを出して、Skillsを使わせないという変更を行った。(当然、.verificationのないSkill実行は未検証としてエラーにするようにもした)
また、それだと利便性が下がるため、validationIdによるホワイトリスト運用を行うことにした。

なお、ベースラインのYAMLを作成することで、検知した脆弱性を抑止することもできるが、これを許容してしまうと、結局利用者が好き放題に通すことができてしまいガバナンスが効かないので、採用を見送った。

多少の運用負荷が高まるが、先述した、利便性とのトレードオフがある中で解を探した一つの例として考えていただきたい。

でもskill-attestation.jsonを改ざんしたら動かせてしまうのでは?

当然、マルチテナントで運用する以上、そういう抜け道を探す人が出てくることは想定しなければいけない。

そこで、今回の仕組みでは、ファイル構成(skill-manifest.json)、結果のサマリ(skill-attestation.json)に対して署名を行い、プラットフォーム側で署名検証を行うことで、上記のような改ざんを検知してブロックするような実装も行っている。

署名は、KMSの非対称キーを用いた公開鍵によるダイジェストの検証を行う。

AWSリソースの変更

以下のAWS KMSリソースを作成する。

resource "aws_kms_key" "attestation" {
  description              = "SkillSeal Attestation signing key (${local.prefix})"
  key_usage                = "SIGN_VERIFY"
  customer_master_key_spec = "RSA_3072"
}

resource "aws_kms_alias" "attestation" {
  name          = local.kms_alias_name
  target_key_id = aws_kms_key.attestation.key_id
}

data "aws_kms_public_key" "attestation" {
  key_id = aws_kms_key.attestation.key_id
}

AWS Lambda FunctionにアタッチしているIAMロールに以下の権限を追加する。

  statement {
    effect = "Allow"

    actions = [
      "kms:Sign",
    ]

    resources = [
      aws_kms_key.attestation.arn,
    ]
  }

コードで以下の関数を定義し、

def _sign_attestation(attestation: dict) -> bytes:
    """AttestationのSHA-256ダイジェストをAWS KMSで署名する。"""
    digest = hashlib.sha256(_normalize(attestation)).digest()
    response = boto3.client("kms").sign(
        KeyId=os.environ["KMS_KEY_ID"],
        Message=digest,
        MessageType="DIGEST",
        SigningAlgorithm="RSASSA_PSS_SHA_256",
    )
    return response["Signature"]

_inspect_skillの最後に呼び出しを追加する。

def _inspect_skill(skill_dir: Path, validation_id: str) -> None:
    # (中略)
    (verification_dir / "skill-attestation.sig").write_bytes(_sign_attestation(attestation))

これで、より強固なAgent Skillsを実行できる基盤が作れるようになった!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?