Secret scanning partner program


GitHubは、既知のシークレットフォーマットに対してリポジトリをスキャンし、誤ってコミットされたクレデンシャルが不正利用されることを防ぎます。 Secret scanning happens by default on public repositories, and can be enabled on private repositories by repository administrators or organization owners. As a service provider, you can partner with GitHub so that your secret formats are included in our secret scanning.


When a match of your secret format is found in a private repository configured for secret scanning, then repository admins and the committer are alerted and can view and manage the secret scanning result on GitHub. 詳しい情報については、「secret scanning からのアラートを管理する」を参照してください。

This article describes how you can partner with GitHub as a service provider and join the secret scanning partner program.

The secret scanning process

How secret scanning works in a public repository

The following diagram summarizes the secret scanning process for public repositories, with any matches sent to a service provider's verify endpoint.


Joining the secret scanning program on GitHub

  1. プロセスを開始するために、GitHubに連絡してください。
  2. スキャンしたい関連シークレットを特定し、それらを捕捉するための正規表現を作成してください。
  3. For secret matches found in public repositories, create a secret alert service which accepts webhooks from GitHub that contain the secret scanning message payload.
  4. シークレットアラートサービスに、署名検証を実装してください。
  5. シークレットアラートサービスに、シークレットの破棄とユーザへの通知を実装してください。
  6. Provide feedback for false positives (optional).


To get the enrollment process started, email

You will receive details on the secret scanning program, and you will need to agree to GitHub's terms of participation before proceeding.


To scan for your secrets, GitHub needs the following pieces of information for each secret that you want included in the secret scanning program:

  • シークレットの種類に対する、ユニークで人が読める名前。 後にこれを使って、メッセージペイロード中のType値が生成されます。
  • このシークレットの種類を見つける正規表現。 できるかぎり正確にしてください。そうすることで、誤検知の数を減らすことができます。
  • GitHubからのメッセージを受信するエンドポイントのURL。 これは各シークレットの種類ごとにユニークである必要はありません。

Send this information to


提供したURLに、パブリックでインターネットからアクセスできるHTTPエンドポイントを作成してください。 When a match of your regular expression is found in a public repository, GitHub will send an HTTP POST message to your endpoint.


Host: HOST
Accept: */*
Content-Type: application/json
GITHUB-PUBLIC-KEY-IDENTIFIER: 90a421169f0a406205f1563a953312f0be898d3c7b6c06b681aa86a874555f4a
Content-Length: 0123


メッセージのボディはJSONの配列で、以下の内容を持つ1つ以上のオブジェクトを含みます。 複数の一致が見つかった場合には、GitHubは複数のシークレットの一致を含む1つのメッセージを送信することがあります。 エンドポイントは、タイムアウトすることなく大量の一致を含むリクエストを処理できなければなりません。

  • Token: シークレットの一致の値。
  • Type: 正規表現を特定するために渡されたユニークな名前。
  • URL: マッチが見つかったパブリックなコミットURL。




Note: When you send a request to the public key endpoint above, you may hit rate limits. To avoid hitting rate limits, you can use a personal access token (no scopes required) as suggested in the samples below, or use a conditional request. For more information, see "Getting started with the REST API."

次のメッセージを受信したとして、以下のコードは署名検証の方法を示しています。 The code snippets assume you've set an environment variable called GITHUB_PRODUCTION_TOKEN with a generated PAT ( to avoid hitting rate limits. The PAT does not need any scopes/permissions.

Note: The signature was generated using the raw message body. So it's important you also use the raw message body for signature validation, instead of parsing and stringifying the JSON, to avoid rearranging the message or changing spacing.


Host: HOST
Accept: */*
content-type: application/json
GITHUB-PUBLIC-KEY-IDENTIFIER: 90a421169f0a406205f1563a953312f0be898d3c7b6c06b681aa86a874555f4a
Content-Length: 0000



package main

import (

func main() {
  payload := `[{"token":"some_token","type":"some_type","url":"some_url"}]`

  kID := "90a421169f0a406205f1563a953312f0be898d3c7b6c06b681aa86a874555f4a"

  kSig := "MEUCIQDKZokqnCjrRtw0tni+2Ltvl/uiMJ1EGumEsp1BsNr32AIgQY1YXD2nlj+XNfGK4rBfkMJ1JDOQcYXxa2sY8FNkrKc="

  // Fetch the list of GitHub Public Keys
  req, err := http.NewRequest("GET", "", nil)
  if err != nil {
    fmt.Printf("Error preparing request: %s\n", err)

  if len(os.Getenv("GITHUB_PRODUCTION_TOKEN")) == 0 {
    fmt.Println("Need to define environment variable GITHUB_PRODUCTION_TOKEN")

  req.Header.Add("Authorization", "Bearer "+os.Getenv("GITHUB_PRODUCTION_TOKEN"))

  resp, err := http.DefaultClient.Do(req)
  if err != nil {
    fmt.Printf("Error requesting GitHub signing keys: %s\n", err)

  decoder := json.NewDecoder(resp.Body)
  var keys GitHubSigningKeys
  if err := decoder.Decode(&keys); err != nil {
    fmt.Printf("Error decoding GitHub signing key request: %s\n", err)

  // Find the Key used to sign our webhook
  pubKey, err := func() (string, error) {
    for _, v := range keys.PublicKeys {
      if v.KeyIdentifier == kID {
        return v.Key, nil

    return "", errors.New("specified key was not found in GitHub key list")

  if err != nil {
    fmt.Printf("Error finding GitHub signing key: %s\n", err)

  // Decode the Public Key
  block, _ := pem.Decode([]byte(pubKey))
  if block == nil {
    fmt.Println("Error parsing PEM block with GitHub public key")

  // Create our ECDSA Public Key
  key, err := x509.ParsePKIXPublicKey(block.Bytes)
  if err != nil {
    fmt.Printf("Error parsing DER encoded public key: %s\n", err)

  // Because of documentation, we know it's a *ecdsa.PublicKey
  ecdsaKey, ok := key.(*ecdsa.PublicKey)
  if !ok {
    fmt.Println("GitHub key was not ECDSA, what are they doing?!")
  rest, err := asn1.Unmarshal(asnSig, &parsedSig)
  if err != nil || len(rest) != 0 {
    fmt.Printf("Error unmarshalling asn.1 signature: %s\n", err)

  // SHA256エンコードされたペイロードをGitHubの鍵での署名に対して検証する
  digest := sha256.Sum256([]byte(payload))
  keyOk := ecdsa.Verify(ecdsaKey, digest[:], parsedSig.R, parsedSig.S)

  if keyOk {
    fmt.Println("THE PAYLOAD IS GOOD!!")
  } else {
    fmt.Println("the payload is invalid :(")

type GitHubSigningKeys struct {
  PublicKeys []struct {
    KeyIdentifier string `json:"key_identifier"`
    Key           string `json:"key"`
    IsCurrent     bool   `json:"is_current"`
  } `json:"public_keys"`

// asn1Signatureは ASN.1 シリアライズ/パース署名に対する構造体
type asn1Signature struct {
  R *big.Int
  S *big.Int


require 'openssl'
require 'net/http'
require 'uri'
require 'json'
require 'base64'

payload = <<-EOL

payload = payload

signature = "MEUCIQDKZokqnCjrRtw0tni+2Ltvl/uiMJ1EGumEsp1BsNr32AIgQY1YXD2nlj+XNfGK4rBfkMJ1JDOQcYXxa2sY8FNkrKc="

key_id = "90a421169f0a406205f1563a953312f0be898d3c7b6c06b681aa86a874555f4a"

url = URI.parse('')

raise "Need to define GITHUB_PRODUCTION_TOKEN environment variable" unless ENV['GITHUB_PRODUCTION_TOKEN']
request =
request['Authorization'] = "Bearer #{ENV['GITHUB_PRODUCTION_TOKEN']}"

http =, url.port)
http.use_ssl = (url.scheme == "https")

response = http.request(request)

parsed_response = JSON.parse(response.body)

current_key_object = parsed_response["public_keys"].find { |key| key["key_identifier"] == key_id }

current_key = current_key_object["key"]

openssl_key =

puts openssl_key.verify(, Base64.decode64(signature), payload.chomp)

Validation sample in JavaScript

const crypto = require("crypto");
const axios = require("axios");

const GITHUB_KEYS_URI = "";

 * Verify a payload and signature against a public key
 * @param {String} payload the value to verify
 * @param {String} signature the expected value
 * @param {String} keyID the id of the key used to generated the signature
 * @return {void} throws if the signature is invalid
const verify_signature = async (payload, signature, keyID) => {
  if (typeof payload !== "string" || payload.length === 0) {
    throw new Error("Invalid payload");
  if (typeof signature !== "string" || signature.length === 0) {
    throw new Error("Invalid signature");
  if (typeof keyID !== "string" || keyID.length === 0) {
    throw new Error("Invalid keyID");

  const keys = (await axios.get(GITHUB_KEYS_URI)).data;
  if (!(keys?.public_keys instanceof Array) || keys.length === 0) {
    throw new Error("No public keys found");

  const publicKey = keys.public_keys.find((k) => k.key_identifier === keyID) ?? null;
  if (publicKey === null) {
    throw new Error("No public key found matching key identifier");

  const verify = crypto.createVerify("SHA256").update(payload);
  if (!verify.verify(publicKey.key, Buffer.from(signature, "base64"), "base64")) {
    throw new Error("Signature does not match payload");


For secret scanning in public repositories, you can enhance your secret alert service to revoke the exposed secrets and notify the affected users. これをシークレットアラートサービスへどのように実装するかは実装者に任されていますが、GitHubがメッセージを送信したすべてのシークレットは、公開され、侵害されたものと考えることをおすすめします。

Provide feedback for false positives

We collect feedback on the validity of the detected individual secrets in partner responses. If you wish to take part, email us at

When we report secrets to you, we send a JSON array with each element containing the token, type identifier, and commit URL. When you send us feedback, you send us information about whether the detected token was a real or false credential. We accept feedback in the following formats.

You can send us the raw token:

    "token_raw": "The raw token",
    "token_type": "ACompany_API_token",
    "label": "true_positive"

You may also provide the token in hashed form after performing a one way cryptographic hash of the raw token using SHA-256:

    "token_hash": "The SHA-256 hashed form of the raw token",
    "token_type": "ACompany_API_token",
    "label": "false_positive"

A few important points:

  • You should only send us either the raw form of the token ("token_raw"), or the hashed form ("token_hash"), but not both.
  • For the hashed form of the raw token, you can only use SHA-256 to hash the token, not any other hashing algorithm.
  • The label indicates whether the token is a true ("true_positive") or a false positive ("false_positive"). Only these two lowercased literal strings are allowed.

Note: Our request timeout is set to be higher (that is, 30 seconds) for partners who provide data about false positives. If you require a timeout higher than 30 seconds, email us at




GitHubのすべてのドキュメントはオープンソースです。間違っていたり、はっきりしないところがありましたか?Pull Requestをお送りください。


OR, コントリビューションの方法を学んでください。