From e0f416ac9af830c18359ad8509b7a58100cc0c35 Mon Sep 17 00:00:00 2001 From: Gilbert Sanchez Date: Sat, 30 May 2026 09:31:05 -0700 Subject: [PATCH 1/3] refactor(workflow): standardise publish dispatch inputs Add version, force, dry_run, create_release, publish inputs. Replaces bare workflow_dispatch with no inputs. Use github.event_name != workflow_dispatch || inputs.X to default create_release/publish to true on push events. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/publish.yaml | 56 ++++++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index d2bdeb7..dafe751 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -1,12 +1,44 @@ -name: Publish Module -on: - push: - branches: - - main - workflow_dispatch: -permissions: - contents: write -jobs: - Publish: - uses: HeyItsGilbert/.github/.github/workflows/PublishModule.yml@main - secrets: inherit +name: Publish Module +on: + push: + branches: + - main + workflow_dispatch: + inputs: + version: + description: "The version to publish. Leave empty to use the version in the module manifest." + required: false + type: string + force: + description: "If true, bypass the PSGallery version existence check. Use when re-triggering a failed publish job (pattern: force=true, create_release=false, publish=true)." + required: false + type: boolean + default: false + dry_run: + description: "If true, skip actual publishing and just validate the workflow logic." + required: false + type: boolean + default: false + create_release: + description: "If false, skip creating the GitHub release and tag." + required: false + type: boolean + default: true + publish: + description: "If false, skip publishing to PowerShell Gallery." + required: false + type: boolean + default: true +permissions: + contents: write +jobs: + publish: + name: Publish Module + uses: HeyItsGilbert/.github/.github/workflows/PublishModule.yml@main + with: + version: ${{ inputs.version || '' }} + force: ${{ inputs.force || false }} + dry_run: ${{ inputs.dry_run || false }} + create_release: ${{ github.event_name != 'workflow_dispatch' || inputs.create_release }} + publish: ${{ github.event_name != 'workflow_dispatch' || inputs.publish }} + secrets: inherit From 938a5075d5234157d0fee764094fb3ea5573d675 Mon Sep 17 00:00:00 2001 From: Gilbert Sanchez Date: Thu, 11 Jun 2026 20:48:00 -0700 Subject: [PATCH 2/3] test(fixtures): add UTF-8 BOM to gremlin fixtures Windows PowerShell 5.1 decodes BOM-less files as Windows-1252, so the en dash (U+2013, bytes E2 80 93) was misread, with byte 0x93 mapping to U+201C and failing Measure-GremlinCharacter's "Can Detect on Path" test. A UTF-8 BOM forces 5.1 to parse the fixtures as UTF-8. Co-Authored-By: Claude Opus 4.8 --- tests/fixtures/Gremlin.ps1 | 2 +- tests/fixtures/GremlinSuppressed.ps1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/fixtures/Gremlin.ps1 b/tests/fixtures/Gremlin.ps1 index 8c906f5..1c51443 100644 --- a/tests/fixtures/Gremlin.ps1 +++ b/tests/fixtures/Gremlin.ps1 @@ -1,4 +1,4 @@ -function Test-Suppressed { +function Test-Suppressed { param() Write-Host '–hello' } diff --git a/tests/fixtures/GremlinSuppressed.ps1 b/tests/fixtures/GremlinSuppressed.ps1 index 971a194..68e1a5e 100644 --- a/tests/fixtures/GremlinSuppressed.ps1 +++ b/tests/fixtures/GremlinSuppressed.ps1 @@ -1,4 +1,4 @@ -function Test-Suppressed { +function Test-Suppressed { [Diagnostics.CodeAnalysis.SuppressMessageAttribute( 'GoodEnoughRules\Measure-GremlinCharacter', '', From d018b093b89b389dc175f9b5456de22cc61002a9 Mon Sep 17 00:00:00 2001 From: Gilbert Sanchez Date: Thu, 11 Jun 2026 20:52:59 -0700 Subject: [PATCH 3/3] docs(readme): document Measure-GremlinCharacter and encoding caveat Add a README section for the gremlin-character rule and note how PowerShell edition encoding defaults (UTF-8 in 7+, ANSI in 5.1) affect detection of multi-byte gremlins in BOM-less files. Co-Authored-By: Claude Opus 4.8 --- README.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/README.md b/README.md index d027d9d..f3367fd 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,37 @@ $secureString | ConvertFrom-SecureString -Key $keyBytes **Why it matters:** Without a key, encrypted strings cannot be decrypted on different machines or by different users, limiting portability and automation scenarios. +### Measure-GremlinCharacter + +**Severity:** Varies (Error / Warning / Information depending on the character) + +Detects invisible or visually deceptive Unicode characters ("gremlins") such as +zero-width spaces, non-breaking spaces, bidirectional overrides, and curly quotes +(`‘ ’ “ ”`). These characters are nearly impossible to spot in an editor but can +introduce subtle bugs or security issues. + +```powershell +# This will trigger the rule (curly quotes instead of straight ASCII quotes): +Write-Host “Hello, World!” +``` + +**Why it matters:** Gremlin characters can silently break string comparisons, +sneak past code review, or be used to disguise malicious code. Surfacing them at +analysis time makes the invisible visible. + +> [!IMPORTANT] +> **File encoding affects detection.** The bytes a parser sees depend on how the +> file is decoded, and that default differs by PowerShell edition: +> +> - **PowerShell 7+** reads files without a byte-order mark (BOM) as **UTF-8**. +> - **Windows PowerShell 5.1** reads BOM-less files using the system **ANSI code +> page** (e.g. Windows-1252). +> +> A multi-byte UTF-8 gremlin (for example an en dash `U+2013`, stored as +> `E2 80 93`) is decoded correctly under PowerShell 7, but under 5.1 those bytes +> are interpreted as separate Windows-1252 characters — so the rule may report a +> different code point, or miss it entirely. **Save files as UTF-8 with a BOM** to +> get consistent results across both editions. ## Examples