From c7a347af978346dff6e0afcca031c5ab44305881 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Fri, 25 Sep 2026 09:47:22 +0700 Subject: [PATCH 1/2] perf(source-clean): compile exhaustive fingerprint matcher without weakening checks --- scripts/verify-source-clean.ps1 | 112 +++++++++++++++++++------------- 1 file changed, 66 insertions(+), 46 deletions(-) diff --git a/scripts/verify-source-clean.ps1 b/scripts/verify-source-clean.ps1 index f03c91ad5..ace318e6b 100644 --- a/scripts/verify-source-clean.ps1 +++ b/scripts/verify-source-clean.ps1 @@ -46,9 +46,6 @@ $ForbiddenIdentifierHashes = [System.Collections.Generic.HashSet[string]]::new([ "0e443fe512c39ce723fc1be519b8e2a13a4ba75916989123078b59308480b2f8" ) | ForEach-Object { [void]$ForbiddenIdentifierHashes.Add($_) } -$CandidateLengths = [System.Collections.Generic.HashSet[int]]::new() -@(7, 8, 12, 22) | ForEach-Object { [void]$CandidateLengths.Add($_) } - $ForbiddenTextPatterns = @( "C:\Users\", "C:\Program Files\dotnet\sdk", @@ -72,57 +69,80 @@ function Normalize-RelativePath { return $Path.Replace('\', '/').TrimStart('/') } -$Sha256 = [System.Security.Cryptography.SHA256]::Create() -$IdentifierCandidateCache = [System.Collections.Generic.Dictionary[string,bool]]::new([System.StringComparer]::Ordinal) - -function Test-ForbiddenIdentifierCandidate { - param([Parameter(Mandatory=$true)][string]$Value) - - if (-not $CandidateLengths.Contains($Value.Length)) { return $false } - if ($IdentifierCandidateCache.ContainsKey($Value)) { - return $IdentifierCandidateCache[$Value] - } +# Keep the one-way fingerprint policy; execute the exhaustive substring scan in +# compiled .NET code rather than millions of PowerShell pipeline/function calls. +# The matcher preserves the original four-token, exact-length and embedded-token rules. +$MatcherSource = @' +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; + +namespace ArsasSourceClean +{ + public sealed class IdentifierMatcher : IDisposable + { + private static readonly Regex Words = new Regex("[a-z0-9]+", RegexOptions.Compiled | RegexOptions.CultureInvariant); + private readonly HashSet hashes; + private readonly HashSet lengths; + private readonly Dictionary cache = new Dictionary(StringComparer.Ordinal); + private readonly SHA256 sha = SHA256.Create(); + + public IdentifierMatcher(string[] forbiddenHashes, int[] candidateLengths) + { + hashes = new HashSet(forbiddenHashes, StringComparer.OrdinalIgnoreCase); + lengths = new HashSet(candidateLengths); + } - $bytes = [System.Text.Encoding]::UTF8.GetBytes($Value) - $hash = -join ($Sha256.ComputeHash($bytes) | ForEach-Object { $_.ToString("x2") }) - $isForbidden = $ForbiddenIdentifierHashes.Contains($hash) - $IdentifierCandidateCache[$Value] = $isForbidden - return $isForbidden -} + private bool IsForbidden(string candidate) + { + if (!lengths.Contains(candidate.Length)) return false; + bool found; + if (cache.TryGetValue(candidate, out found)) return found; + string digest = BitConverter.ToString(sha.ComputeHash(Encoding.UTF8.GetBytes(candidate))).Replace("-", ""); + found = hashes.Contains(digest); + cache[candidate] = found; + return found; + } -function Test-ContainsForbiddenIdentifier { - param([AllowEmptyString()][string]$Text) + public bool ContainsForbiddenIdentifier(string text) + { + if (String.IsNullOrWhiteSpace(text)) return false; + MatchCollection words = Words.Matches(text.ToLowerInvariant()); + for (int index = 0; index < words.Count; index++) + { + string word = words[index].Value; + foreach (int length in lengths) + { + for (int offset = 0; offset <= word.Length - length; offset++) + { + if (IsForbidden(word.Substring(offset, length))) return true; + } + } - if ([string]::IsNullOrWhiteSpace($Text)) { return $false } - $words = @([regex]::Matches($Text.ToLowerInvariant(), '[a-z0-9]+') | ForEach-Object { $_.Value }) - - for ($index = 0; $index -lt $words.Count; $index++) { - $word = $words[$index] - - # Detect identifiers embedded in source/path tokens such as TypeNameSuffix. - # This closes the common case where a prohibited product name is attached - # to a class, fixture, job, or filename rather than separated by punctuation. - foreach ($length in $CandidateLengths) { - if ($word.Length -lt $length) { continue } - for ($offset = 0; $offset -le ($word.Length - $length); $offset++) { - $fragment = $word.Substring($offset, $length) - if (Test-ForbiddenIdentifierCandidate $fragment) { - return $true + string candidate = ""; + for (int count = 1; count <= 4 && index + count - 1 < words.Count; count++) + { + candidate += words[index + count - 1].Value; + if (candidate.Length > 22) break; + if (IsForbidden(candidate)) return true; } } + return false; } - $candidate = "" - for ($count = 1; $count -le 4 -and ($index + $count - 1) -lt $words.Count; $count++) { - $candidate += $words[$index + $count - 1] - if ($candidate.Length -gt 22) { break } - if (Test-ForbiddenIdentifierCandidate $candidate) { - return $true - } - } + public void Dispose() { sha.Dispose(); } } +} +'@ +Add-Type -TypeDefinition $MatcherSource -Language CSharp -ErrorAction Stop +$IdentifierMatcher = [ArsasSourceClean.IdentifierMatcher]::new( + [string[]]@($ForbiddenIdentifierHashes), [int[]]@(7, 8, 12, 22)) - return $false +function Test-ContainsForbiddenIdentifier { + param([AllowEmptyString()][string]$Text) + return $IdentifierMatcher.ContainsForbiddenIdentifier($Text) } function Get-TrackedRelativePaths { @@ -185,5 +205,5 @@ if (-not $ScanOnly) { & (Join-Path $PSScriptRoot "verify-auto-update.ps1") } -$Sha256.Dispose() +$IdentifierMatcher.Dispose() Write-Host "All Git-tracked ARSAS content passed source and external-identifier checks." -ForegroundColor Green From 37d13fea71bd03c04e5c01d25cb61301d9d181f7 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Fri, 25 Sep 2026 09:47:42 +0700 Subject: [PATCH 2/2] test(source-clean): preserve multi-token and case-insensitive rejection --- scripts/test-source-clean-guard.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/test-source-clean-guard.ps1 b/scripts/test-source-clean-guard.ps1 index ed4e98f5c..9b7448b31 100644 --- a/scripts/test-source-clean-guard.ps1 +++ b/scripts/test-source-clean-guard.ps1 @@ -17,7 +17,9 @@ $cases = @( @{ Path = "evidence/fixture.json"; Text = "{`"reference`": `"$identifier`"}"; Expected = "text" }, @{ Path = ".github/workflows/smart-discovery-post-merge-production.yml"; Text = "name: $identifier"; Expected = "text" }, @{ Path = "tests/ARSAS.Tests/SyntheticFixture.cs"; Text = "// $identifier"; Expected = "text" }, - @{ Path = "docs/${identifier}-fixture.md"; Text = "# independently generated fixture"; Expected = "path" } + @{ Path = "docs/${identifier}-fixture.md"; Text = "# independently generated fixture"; Expected = "path" }, + @{ Path = "docs/split-name.md"; Text = $identifier.Substring(0, 3) + " " + $identifier.Substring(3); Expected = "text" }, + @{ Path = "docs/mixed-case.md"; Text = $identifier.ToUpperInvariant(); Expected = "text" } ) function Invoke-Case {