GitHub durchsucht Repositorys nach bekannten geheimen Formaten, um die betrügerische Verwendung von Anmeldeinformationen zu verhindern, die versehentlich begangen wurden. Secret scanning erfolgt standardmäßig in öffentlichen Repositorys und öffentlichen npm-Paketen. Administratoren von Repositorys und Organisationsbesitzer können secret scanning auch für private Repositorys aktivieren. Als Dienstleister können Sie mit GitHub zusammenarbeiten, damit Ihre proprietären Formate in unser secret scanning aufgenommen werden.
Wird in einer öffentlichen Quelle eine Übereinstimmung mit deinem Geheimnisformat gefunden, wird eine Payload an einen HTTP-Endpunkt Ihrer Wahl gesendet.
Wenn eine Übereinstimmung mit Ihrem geheimen Format in einem für secret scanning konfigurierten privaten Repository gefunden wird, werden Repository-Administratoren und der Committer benachrichtigt und können das secret scanning-Ergebnis auf GitHub anzeigen und verwalten. Weitere Informationen finden Sie unter Verwalten von Warnungen zur Geheimnisüberprüfung.
In diesem Artikel wird beschrieben, wie Sie als Dienstanbieter eine Partnerschaft mit GitHub eingehen und dem secret scanning-Partnerprogramm beitreten können.
Der secret scanning Prozess
Das folgende Diagramm fasst den secret scanning Prozess für öffentliche Repositorys zusammen, wobei alle Übereinstimmungen an den Überprüfungsendpunkt eines Dienstanbieters gesendet werden. Ein ähnlicher Prozess sendet Dienstanbietertoken, die in öffentlichen Paketen in der npm-Registrierung verfügbar gemacht werden.

Dem secret scanningProgramm auf GitHub beitreten
- Wenden Sie sich an GitHub, um den Prozess zu starten.
- Identifiziere die relevanten Geheimnisse, die du überprüfen möchtest, und erstelle reguläre Ausdrücke, um diese zu erfassen. Ausführlichere Informationen und Empfehlungen finden Sie unten unter Identifizieren Ihrer geheimen Schlüssel und Erstellen regulärer Ausdrücke unten.
- Erstellen Sie für geheime Übereinstimmungen, die öffentlich gefunden wurden, einen geheimen Warnungsdienst, der Webhooks akzeptiert, die GitHub die secret scanning Nachrichtennutzlast enthalten.
- Implementieren Sie im Benachrichtigungsdienst für Geheimnisse eine Signaturüberprüfung.
- Implementieren Sie im Benachrichtigungsdienst die Sperrung von Geheimnissen sowie Benutzerbenachrichtigungen.
- Stelle für falsch positive Ergebnisse Feedback bereit (optional).
Kontaktieren Sie GitHub, um den Prozess zu starten
Senden Sie eine E-Mail an [email protected], um den Registrierungsvorgang zu starten.
Sie erhalten Details zum secret scanning Programm, und Sie müssen den Teilnahmebedingungen zustimmen GitHub, bevor Sie fortfahren.
Identifizieren von Geheimnissen und Erstellen regulärer Ausdrücke
Zum Scannen Ihrer Geheimnisse benötigt GitHub die folgenden Informationen für jedes Geheimnis, das Sie in das secret scanning-Programm aufnehmen möchten:
-
Ein eindeutig lesbarer Name für den Geheimnistyp. Dieser wird später zur Angabe des
Type-Werts in der Nachrichtennutzlast verwendet. -
Einen regulären Ausdruck für die Suche des Geheimnistyps. Wir empfehlen Ihnen, so genau wie möglich zu sein, da dies dazu beiträgt, die Zahl der falsch positiven Ergebnisse zu verringern. Einige bewährte Methoden für hochwertige, identifizierbare Geheimnisse sind:
- Ein eindeutig definiertes Präfix
- Zufällige Zeichenfolgen mit hoher Entropie
- Eine 32-Bit-Prüfsumme

-
Ein Testkonto für Ihren Dienst. Dies ermöglicht es uns, Beispiele für die Geheimnisse zu generieren und zu analysieren, um falsch positive Ergebnisse weiter zu reduzieren.
-
Die URL des Endpunkts, der Nachrichten von GitHub empfängt. Die URL muss nicht für jeden Geheimnistyp eindeutig sein.
Senden Sie diese Informationen an [email protected].
Erstellen eines Benachrichtigungsdiensts für Geheimnisse
Erstelle einen öffentlichen, über das Internet zugänglichen HTTP-Endpunkt unter der bereitgestellten URL. Wenn öffentlich eine Übereinstimmung mit Ihrem regulären Ausdruck gefunden wird, sendet GitHub eine HTTP-POST-Nachricht an Ihren Endpunkt.
Beispiel für Anforderungstext
[
{
"token":"NMIfyYncKcRALEXAMPLE",
"type":"mycompany_api_token",
"url":"https://github.com/octocat/Hello-World/blob/12345600b9cbe38a219f39a9941c9319b600c002/foo/bar.txt",
"source":"content"
}
]
Beim Nachrichtentext handelt es sich um ein JSON-Array, das ein oder mehrere Objekte enthält. Jedes davon stellt eine einzelne Geheimnisübereinstimmung dar. Dein Endpunkt sollte Anforderungen mit vielen Übereinstimmungen ohne Timeout verarbeiten können. Die Schlüssel für jede Geheimnisübereinstimmung lauten:
- token: der Wert der Geheimnisübereinstimmung.
- type: der bereitgestellte eindeutige Name, den Sie für Ihren regulären Ausdruck angegeben haben.
- url: die öffentliche URL, unter der die Übereinstimmung gefunden wurde (kann leer gelassen werden).
- Quelle: Wo das Token auf GitHub gefunden wurde.
Die Liste der gültigen Werte für source lautet:
- Content
- Commit
- Pull_request_title
- Pull_request_description
- Pull_request_comment
- Issue_title
- Issue_description
- Issue_comment
- Discussion_title
- Discussion_body
- Discussion_comment
- Commit_comment
- Gist_content
- Gist_comment
- Wiki_Inhalt
- Wiki_commit
- Npm
- Manuelle Einreichung
- Unbekannt
Implementieren einer Signaturüberprüfung im Benachrichtigungsdienst für Geheimnisse
Die HTTP-Anfrage an Ihren Dienst enthält außerdem Header, deren Verwendung wir dringend empfehlen, um zu überprüfen, dass die von Ihnen empfangenen Nachrichten tatsächlich von GitHub stammen und nicht manipuliert wurden.
Die beiden HTTP-Header, nach denen du suchen solltest, lauten:
Github-Public-Key-Identifier: derkey_identifier, der von Ihrer API verwendet werden soll.Github-Public-Key-Signature: die Signatur der Payload.
Sie können den GitHub öffentlichen Schlüssel für Secret Scanning von https://api.github.com/meta/public_keys/secret_scanning abrufen und die Nachricht mithilfe des ECDSA-NIST-P256V1-SHA256-Algorithmus validieren. Der Endpunkt stellt mehrere key_identifier und öffentliche Schlüssel bereit. Du kannst anhand des Werts von Github-Public-Key-Identifier bestimmen, welcher öffentliche Schlüssel verwendet werden soll.
Hinweis
Wenn Sie eine Anforderung an den oben genannten Endpunkt für den öffentlichen Schlüssel senden, überschreiten Sie möglicherweise Grenzwerte für Datenübertragungsraten. Um Ratenbegrenzungen zu vermeiden, können Sie ein personal access token (classic) (keine Berechtigungsbereiche erforderlich) oder ein fine-grained personal access token (nur der automatische Lesezugriff auf öffentliche Repositories ist erforderlich) verwenden, wie in den folgenden Beispielen vorgeschlagen, oder eine bedingte Anfrage verwenden. Weitere Informationen finden Sie unter Erste Schritte mit der REST-API.
Hinweis
Die Signatur wurde mithilfe des unformatierten Nachrichtentexts generiert. Daher musst du auch für die Signaturüberprüfung den unformatierten Nachrichtentext verwenden, anstatt das JSON-Array zu analysieren und in eine Zeichenfolge umzuwandeln. So verhinderst du, die Nachricht neu ordnen oder Abstände ändern zu müssen.
HTTP POST-Beispiel zur Verifizierung des Endpunkts gesendet
POST / HTTP/2
Host: HOST
Accept: */*
Content-Length: 104
Content-Type: application/json
Github-Public-Key-Identifier: bcb53661c06b4728e59d897fb6165d5c9cda0fd9cdf9d09ead458168deb7518c
Github-Public-Key-Signature: MEQCIQDaMKqrGnE27S0kgMrEK0eYBmyG0LeZismAEz/BgZyt7AIfXt9fErtRS4XaeSt/AO1RtBY66YcAdjxji410VQV4xg==
[{"source":"commit","token":"some_token","type":"some_type","url":"https://example.com/base-repo-url/"}]
Mit den folgenden Codeausschnitten wird gezeigt, wie du eine Signaturüberprüfung durchführen kannst.
Die Codebeispiele setzen voraus, dass Sie eine Umgebungsvariable namens GITHUB_PRODUCTION_TOKEN mit einem generierten personal access token gesetzt haben, um Ratenbegrenzungen zu vermeiden. Das personal access token benötigt keine Berechtigungsbereiche/Berechtigungen.
Beispiel für eine Überprüfung in Go
package main
import (
"crypto/ecdsa"
"crypto/sha256"
"crypto/x509"
"encoding/asn1"
"encoding/base64"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"math/big"
"net/http"
"os"
)
func main() {
payload := `[{"source":"commit","token":"some_token","type":"some_type","url":"https://example.com/base-repo-url/"}]`
kID := "bcb53661c06b4728e59d897fb6165d5c9cda0fd9cdf9d09ead458168deb7518c"
kSig := "MEQCIQDaMKqrGnE27S0kgMrEK0eYBmyG0LeZismAEz/BgZyt7AIfXt9fErtRS4XaeSt/AO1RtBY66YcAdjxji410VQV4xg=="
// Fetch the list of GitHub Public Keys
req, err := http.NewRequest("GET", "https://api.github.com/meta/public_keys/secret_scanning", nil)
if err != nil {
fmt.Printf("Error preparing request: %s\n", err)
os.Exit(1)
}
if len(os.Getenv("GITHUB_PRODUCTION_TOKEN")) == 0 {
fmt.Println("Need to define environment variable GITHUB_PRODUCTION_TOKEN")
os.Exit(1)
}
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)
os.Exit(2)
}
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)
os.Exit(3)
}
// 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)
os.Exit(4)
}
// Decode the Public Key
block, _ := pem.Decode([]byte(pubKey))
if block == nil {
fmt.Println("Error parsing PEM block with GitHub public key")
os.Exit(5)
}
// 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)
os.Exit(6)
}
// 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?!")
os.Exit(7)
}
// Parse the Webhook Signature
parsedSig := asn1Signature{}
asnSig, err := base64.StdEncoding.DecodeString(kSig)
if err != nil {
fmt.Printf("unable to base64 decode signature: %s\n", err)
os.Exit(8)
}
rest, err := asn1.Unmarshal(asnSig, &parsedSig)
if err != nil || len(rest) != 0 {
fmt.Printf("Error unmarshalling asn.1 signature: %s\n", err)
os.Exit(9)
}
// Verify the SHA256 encoded payload against the signature with GitHub's Key
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 :(")
os.Exit(10)
}
}
type GitHubSigningKeys struct {
PublicKeys []struct {
KeyIdentifier string `json:"key_identifier"`
Key string `json:"key"`
IsCurrent bool `json:"is_current"`
} `json:"public_keys"`
}
// asn1Signature is a struct for ASN.1 serializing/parsing signatures.
type asn1Signature struct {
R *big.Int
S *big.Int
}
Beispiel für eine Überprüfung in Ruby
require 'openssl'
require 'net/http'
require 'uri'
require 'json'
require 'base64'
payload = <<-EOL
[{"source":"commit","token":"some_token","type":"some_type","url":"https://example.com/base-repo-url/"}]
EOL
payload = payload
signature = "MEQCIQDaMKqrGnE27S0kgMrEK0eYBmyG0LeZismAEz/BgZyt7AIfXt9fErtRS4XaeSt/AO1RtBY66YcAdjxji410VQV4xg=="
key_id = "bcb53661c06b4728e59d897fb6165d5c9cda0fd9cdf9d09ead458168deb7518c"
url = URI.parse('https://api.github.com/meta/public_keys/secret_scanning')
raise "Need to define GITHUB_PRODUCTION_TOKEN environment variable" unless ENV['GITHUB_PRODUCTION_TOKEN']
request = Net::HTTP::Get.new(url.path)
request['Authorization'] = "Bearer #{ENV['GITHUB_PRODUCTION_TOKEN']}"
http = Net::HTTP.new(url.host, 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 = OpenSSL::PKey::EC.new(current_key)
puts openssl_key.verify(OpenSSL::Digest::SHA256.new, Base64.decode64(signature), payload.chomp)
Beispiel für eine Überprüfung in JavaScript
const crypto = require("crypto");
const axios = require("axios");
const GITHUB_KEYS_URI = "https://api.github.com/meta/public_keys/secret_scanning";
/**
* 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");
}
};
Implementieren der Sperrung von Geheimnissen und von Benutzerbenachrichtigungen im Benachrichtigungsdienst
Für secret scanning öffentlich gefundene Personen können Sie Ihren geheimen Benachrichtigungsdienst verbessern, um die offengelegten Geheimnisse zu widerrufen und die betroffenen Benutzer zu benachrichtigen. Wie Sie dies in Ihrem Warndienst für Secrets umsetzen, bleibt Ihnen überlassen, wir empfehlen jedoch, alle Secrets, über die GitHub Ihnen Meldungen sendet, als öffentlich und kompromittiert zu betrachten.
Bereitstellen von Feedback für falsch positive Ergebnisse
Wir sammeln Feedback zur Gültigkeit aller identifizierten Geheimnisse in Partnerantworten. Wenn Sie teilnehmen möchten, senden Sie eine E-Mail an [email protected].
Die Nachricht, die du von uns zu einem Geheimnis erhältst, besteht aus einem JSON-Array mit dem Token, dem Typbezeichner und der Commit-URL. Informiere uns in deinem Feedback darüber, ob es sich bei dem erkannten Token um echte oder falsche Anmeldeinformationen handelte. Nutze für das Feedback eines der folgenden Formate.
Du kannst das unformatierte Token senden:
[
{
"token_raw": "The raw token",
"token_type": "ACompany_API_token",
"label": "true_positive"
}
]
Du kannst das Token auch in Form eines kryptografischen Hashs des unformatierten Tokens mit SHA-256 bereitstellen:
[
{
"token_hash": "The SHA-256 hashed form of the raw token",
"token_type": "ACompany_API_token",
"label": "false_positive"
}
]
Einige wichtige Punkte:
- Sende uns das Token entweder unformatiert („token_raw“) oder als Hash („token_hash“), nicht beides.
- Verwende zum Erstellen der Hashform des unformatierten Tokens nur SHA-256, keinen anderen Hashalgorithmus.
- Die Bezeichnung gibt an, ob es sich bei dem Token um ein echtes („true_positive“) oder ein falsch positives („false_positive“) Ergebnis handelt. Nur diese beiden Literalzeichenfolgen in Kleinbuchstaben sind zulässig.
Hinweis
Das Anforderungstimeout für Partner, die Daten zu falsch positiven Ergebnissen bereitstellen, ist höher (30 Sekunden). Wenn Sie ein Timeout von mehr als 30 Sekunden benötigen, senden Sie eine E-Mail an [email protected].