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 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 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', '',