Scanning for Yandex Cloud secrets in public sources
Yandex Cloud scans public sources for the following types of secrets:
- API keys
- IAM Cookies
- IAM tokens
- Static access keys
- OAuth token
- SmartCaptcha server keys
- Refresh tokens
- OIDC app secrets
Yandex Cloud is connected to the following secret scanning tools:
- GitHub Secret scanning partner program
- GitLab Secret Detection
- Yandex search index
- Helm charts in Yandex Cloud Marketplace
GitHub
Yandex Cloud is connected to the secret scanning partner program
By default, GitHub scans public repositories for Yandex Cloud secrets and sends any suspicious fragment to Yandex Cloud.
Scanning in public repositories is done automatically. A repository administrator or organization owner can enable secret scanning
GitLab
A standard list
To enable Secret Detection for your project, follow this guide
Yandex search index
By default, Yandex Cloud scans pages indexed with the Yandex search engine for secrets.
Helm charts in Yandex Cloud Marketplace
By default, Yandex Cloud scans Helm charts
How one may learn that a secret has been detected
If a valid secret is detected, the organization owner will get an email from the Yandex Cloud support email address. It will contain part of the detected secret and the URL of the resource where it is detected.
Identity and Access Management will also include the DetectLeakedCredential
event in the audit log.
What one can do if a secret is detected
If your secret got leaked to a public repository:
- Re-issue or revoke the secret by following this guide. Delete the affected resources, if required.
- Delete the secret from the repository or commit history. To do this, follow the guides for GitHub
or GitLab .
Warning
Yandex Cloud does not revoke detected secrets and does not remove them from repositories. Any action on a secret is only performed by their owner.
Scanning secrets on your own
Regular expressions for search
You can use the following regular expressions to scan your repositories on your own:
-
IAM Cookies
c1\.[A-Z0-9a-z_-]+[=]{0,2}\.[A-Z0-9a-z_-]{86}[=]{0,2}
-
IAM tokens
t1\.[A-Z0-9a-z_-]+[=]{0,2}\.[A-Z0-9a-z_-]{86}[=]{0,2}
-
API keys
AQVN[A-Za-z0-9_\-]{35,38}
-
Static access keys
YC[a-zA-Z0-9_\-]{38}
-
OAuth tokens
y[0-6]_[-_A-Za-z0-9]{55,199}
-
SmartCaptcha server keys
ysc2_[a-zA-Z0-9]{40}[0-9a-f]{8}
-
Refresh tokens
rt1\.[A-Z0-9a-z_-]+[=]{0,2}\.[A-Z0-9a-z_-]{86}[=]{0,2}
-
OIDC app secrets
yccs__[0-9a-f]{64}_[0-9a-f]{8}
Note
Use regular expressions carefully because the format of secrets may change moving forward. The changes might not appear in the documentation immediately.
Additional validation of found secrets
Finding certain types of secrets is likely to yield a large number of false-positive results. To make sure the found secrets are up-to-date, we recommend running an additional validation.
-
OAuth tokens
Gopackage main import ( "encoding/base64" "fmt" "os" "strconv" "hash/crc32" ) const ( statefulMaskLen = 40 maxShard = 16 ) func isNotValidEnvironmentType(token string) bool { n, err := strconv.Atoi(token[1:2]) if err != nil { return true } if 0 <= n && n <= 6 { return false } return true } func isStatefulToken(token string) bool { if isNotValidEnvironmentType(token) { return false } decoded, err := base64.RawURLEncoding.DecodeString(token[3:]) if err != nil { return false } crc := uint32(0) for i := 0; i < 4; i++ { crc <<= 8 crc |= uint32(decoded[i+(len(decoded)-4)]) } if crc != crc32.Checksum(decoded[:len(decoded)-4], crc32.MakeTable(crc32.IEEE)) { return false } return true } func fatalf(msg string, a ...interface{}) { _, _ = fmt.Fprintf(os.Stderr, "oauth_filter: " + msg + "\n", a...) os.Exit(1) } func main() { fmt.Println(isStatefulToken("<TOKEN>")) }
-
Static access keys
Gopackage main import ( base64 "encoding/base64" "encoding/binary" "fmt" "hash/crc32" "strings" ) const ( YcPrefix = "YC" ) func checkStaticCred(token string) bool { if !strings.HasPrefix(token, YcPrefix) { return false } decoded, err := base64.RawURLEncoding.DecodeString(token) if err != nil { return false } // CRC32-C checksum calculatedChecksum := crc32.Checksum(decoded[0:len(decoded)-4], crc32.MakeTable(crc32.Castagnoli)) checksum := binary.BigEndian.Uint32(decoded[len(decoded)-4:]) return calculatedChecksum == checksum } func main() { // ^YC[a-zA-Z0-9_\-]{38}$ - regexp fmt.Println(checkStaticCred("<TOKEN>")) fmt.Println(checkStaticCred("<TOKEN>")) }
-
SmartCaptcha server keys
GoPythonpackage main import ( "fmt" "hash/crc32" ) func isValidToken(token string) bool { calculatedChecksum := crc32.Checksum([]byte(token[:len(token)-8]), crc32.MakeTable(crc32.IEEE)) return token[len(token)-8:] == fmt.Sprintf("%08x", calculatedChecksum) } func main() { fmt.Println(isValidToken("ysc2_D0ur60kwXTL7rM52UzJ7Vi5D7a5Qu48zktqy0fE0********")) }
import re import zlib def is_valid_secret(secret): if not re.match("ysc2_[a-zA-Z0-9]{40}[0-9a-f]{8}", secret): return False if secret[-8:] != "%08x" % zlib.crc32(secret[:-8].encode()): return False return True print(is_valid_secret("ysc2_D0ur60kwXTL7rM52UzJ7Vi5D7a5Qu48zktqy0fE0********")) # True
-
OIDC app secrets
GoJavapackage main import ( "encoding/binary" "encoding/hex" "fmt" "regexp" "github.com/spaolacci/murmur3" ) func ValidateOauthClientSecret(str string) bool { re := regexp.MustCompile(`yccs__[0-9a-f]{64}_[0-9a-f]{8}`) if !re.MatchString(str) { return false } prefix := str[:71] suffix := str[71:] hash := murmur3.Sum32([]byte(prefix)) hashBytes := make([]byte, 4) binary.BigEndian.PutUint32(hashBytes, hash) expectedSuffix := hex.EncodeToString(hashBytes) return suffix == expectedSuffix } func main() { test := "<SECRET>" isValid := ValidateOauthClientSecret(test) if isValid { fmt.Println("Secret is valid.") } else { fmt.Println("Secret isn't valid") } }
package yandex.cloud.leakdetector.server.provider; import org.apache.commons.codec.binary.Hex; import org.apache.commons.codec.digest.MurmurHash3; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.regex.Pattern; public class Main { private static final Pattern SECRET_PATTERN = Pattern.compile("yccs__[0-9a-f]{64}_[0-9a-f]{8}"); public static boolean validateOauthClientSecret(String str) { if (!SECRET_PATTERN.matcher(str).matches()) { return false; } String prefix = str.substring(0, 71); String suffix = str.substring(71); String hashHex = hashStringMurmur3(prefix); return suffix.equals(hashHex); } private static String hashStringMurmur3(String str) { int intHashValue = MurmurHash3.hash32x86(str.getBytes()); return Hex.encodeHexString( ByteBuffer.allocate(4) .order(ByteOrder.BIG_ENDIAN) .putInt(intHashValue) .array() ); } public static void main(String[] args) { String test = "<SECRET>"; boolean isValid = validateOauthClientSecret(test); if (isValid) { System.out.println("Secret is valid."); } else { System.out.println("Secret isn't valid"); } } }