Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions .github/scripts/Test-RepoSanitized.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,7 @@ $placeholderDomains = @(
)
$placeholderTenantLabels = @(
'contoso', 'fabrikam', 'tenant', 'tenantname', 'yourtenant', 'your-tenant', 'mytenant',
'example', 'yourorg', 'org', 'company', 'yourcompany', 'tenantid', 'tenant-name'
)
$placeholderLocalParts = @(
'user', 'username', 'admin', 'someone', 'name', 'upn', 'firstname.lastname',
'first.last', 'john.doe', 'jane.doe', 'jdoe', 'you', 'me', 'noreply', 'no-reply'
'example', 'yourorg', 'org', 'company', 'yourcompany', 'tenantid', 'tenant-name', 'label'
)

#endregion Configuration
Expand Down Expand Up @@ -223,7 +219,8 @@ $rules = @(
},
@{
Name = 'SecretAssignment'
Pattern = '(?i)\b(?<key>client[_-]?secret|app[_-]?secret|api[_-]?key|access[_-]?token|refresh[_-]?token|sas[_-]?token|account[_-]?key|connection[_-]?string|password|passwd|pwd)\b\s*[:=]\s*["'']?(?<v>[^\s"''`,;]{12,})'
# Matches key = value, key: value and the JSON form "key": "value".
Pattern = '(?i)\b(?<key>client[_-]?secret|app[_-]?secret|api[_-]?key|access[_-]?token|refresh[_-]?token|sas[_-]?token|account[_-]?key|connection[_-]?string|password|passwd|pwd|pre[_-]?shared[_-]?key|shared[_-]?secret|proxy[_-]?password|passphrase|psk)\b["'']?\s*[:=]\s*["'']?(?<v>[^\s"''`,;]{12,})'
Scope = $null
Message = 'Secret assigned as a literal value. Use a placeholder and document the app registration / Key Vault pattern instead.'
Validate = { param($m) -not (Test-IsPlaceholderValue $m.Groups['v'].Value) }
Expand All @@ -243,26 +240,30 @@ $rules = @(
},
@{
Name = 'TenantDomain'
Pattern = '(?i)\b(?<label>[a-z0-9-]+)\.onmicrosoft\.com\b'
# The optional .mail segment is consumed so that the real label is what gets checked, and a
# match may not start right after '.' or '>' (so <TENANT>.mail.onmicrosoft.com is not flagged).
Pattern = '(?i)(?<![\p{L}\p{N}.>-])(?<label>[a-z0-9-]+)(?:\.mail)?\.onmicrosoft\.com\b'
Scope = $null
Message = 'Real tenant domain. Use contoso.onmicrosoft.com or <TENANT>.onmicrosoft.com.'
Validate = { param($m) -not ($placeholderTenantLabels -contains $m.Groups['label'].Value.ToLowerInvariant()) }
},
@{
Name = 'EmailOrUpn'
Pattern = '\b(?<local>[A-Za-z0-9._%+-]+)@(?<domain>[A-Za-z0-9.-]+\.[A-Za-z]{2,})\b'
# Also matches guest UPNs (name_domain.com#EXT#@tenant), %40-encoded and non-ASCII local parts.
Pattern = '(?<local>[\p{L}\p{N}._%+''#-]+)(?:@|%40)(?<domain>[A-Za-z0-9.-]+\.[A-Za-z]{2,})(?![\p{L}\p{N}-])'
Scope = $null
Message = 'E-mail address / UPN that is not a documented placeholder domain. Use user@contoso.com.'
Validate = {
param($m)
# JSON keys such as definition@odata.bind are not addresses.
if ($m.Groups['domain'].Value -like 'odata.*') { return $false }
if (Test-IsPlaceholderDomain $m.Groups['domain'].Value) { return $false }
if ($placeholderLocalParts -contains $m.Groups['local'].Value.ToLowerInvariant()) { return $false }
return $true
}
},
@{
Name = 'TenantObjectIdInJson'
Pattern = '"(?<key>tenantId|tenantid|groupId|targetGroupId|azureADDeviceId|azureAdDeviceId|aadDeviceId|deviceId|managedDeviceId|intuneDeviceId|entraDeviceId|userId|enrolledByUserId|roleScopeTagId|filterId|deviceAndAppManagementAssignmentFilterId)"\s*:\s*"(?<v>[^"]*)"'
Pattern = '"(?<key>tenantId|tenantid|groupId|targetGroupId|azureADDeviceId|azureAdDeviceId|azureActiveDirectoryDeviceId|aadDeviceId|deviceId|managedDeviceId|intuneDeviceId|entraDeviceId|userId|enrolledByUserId|ownerId|principalId|createdByUserId|sourceId|roleScopeTagId|filterId|assignmentFilterId|deviceAndAppManagementAssignmentFilterId|notificationTemplateId|secretReferenceValueId)"\s*:\s*"(?<v>[^"]*)"'
Scope = $null
Message = 'Tenant-specific object ID in a known key. Replace with a placeholder such as <GROUP-OBJECT-ID> and note what goes there in the doc.'
Validate = {
Expand Down
5 changes: 5 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,12 @@ Exports from the admin center and Graph contain tenant IDs, group object IDs, ti
| `<CLIENT-ID>` | App registration (client) ID |
| `<CERT-THUMBPRINT>` | Certificate thumbprint for app-only auth |
| `<UPN>` or `user@contoso.com` | A user principal name |
| `<USER-DISPLAY-NAME>` | A person's display name |
| `<DEVICE-NAME>`, `<SERIAL>` | Device identifiers |
| `<HARDWARE-HASH>`, `<MAC-ADDRESS>`, `<IMEI>` | Device identifiers that are secrets |
| `<SECRET>` | Any password, pre-shared key, token, or secret setting value |
| `<HOSTNAME>`, `<SERVER>`, `<IP-ADDRESS>` | Internal hostnames, UNC hosts, private IP addresses |
| `<BASE64-BLOB-REMOVED:n-CHARS>` | A certificate, payload, or other binary blob that was removed |

Never commit, even in history: tokens, client secrets, private keys, Autopilot hardware hashes, serial numbers, IMEIs, MAC addresses, real UPNs, internal hostnames, or employee IDs. If you realize you pushed one, rotate it first, then tell the maintainer.

Expand Down
Loading
Loading