Payload/scripts/kaden/harness/harness-pretooluse-guard.ps1
|
#Requires -Version 5.1 <# .SYNOPSIS Cursor and Copilot (Windows) preToolUse / beforeShellExecution guard. .DESCRIPTION Denies bare harness scripts without pwsh -File scripts/kaden/harness-invoke.ps1, and denies production edits under src/, public/, or tests/ while RED is pending/in_progress without a CODE_FIRST_BUILD skip. Windows-only: PowerShell adapter. Copilot cloud/Linux is out of scope for this slice. Install via scripts/kaden/harness/setup-harness-hooks.ps1. #> [CmdletBinding()] param( [string]$InputJson = '', [string]$RepoRoot = '', [string]$FeatureRoot = '', # Fixture/Pester only: skip deny telemetry. Allowed solely when FeatureRoot is under TEMP. [switch]$SkipJournal ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' $modulePath = Join-Path $PSScriptRoot 'lib\HarnessContract.psm1' Import-Module $modulePath -Force Import-Module (Join-Path $PSScriptRoot 'lib\HarnessJournal.psm1') -Force if ($SkipJournal -and -not (Test-HarnessPretooluseSkipJournalAllowed -FeatureRoot $FeatureRoot)) { throw 'SkipJournal is only allowed when -FeatureRoot is an existing path under TEMP (fixture/Pester isolation). Live hooks must not pass -SkipJournal.' } $stdin = if ([string]::IsNullOrWhiteSpace($InputJson)) { [Console]::In.ReadToEnd() } else { $InputJson } if ([string]::IsNullOrWhiteSpace($stdin)) { Write-Output '{"permission":"allow"}' exit 0 } if ([string]::IsNullOrWhiteSpace($RepoRoot)) { $RepoRoot = Get-HarnessRepoRoot -StartPath $PSScriptRoot } $payload = $stdin | ConvertFrom-Json # Cursor tells the hook the model. The subagent cannot see that slug. Persist it # before the command runs so the same conversation can read the ledger. try { Save-HarnessHostModelObservation -Payload $payload -RepoRoot $RepoRoot } catch { Write-Verbose "host-model capture skipped: $($_.Exception.Message)" } $hookEventName = [string](Get-HarnessProperty -Object $payload -Name 'hook_event_name') if ($hookEventName -eq 'postToolUse' -or $hookEventName -eq 'PostToolUse') { $observedModel = [string](Get-HarnessProperty -Object $payload -Name 'model') $conversationId = [string](Get-HarnessProperty -Object $payload -Name 'conversation_id') $context = '' if (-not [string]::IsNullOrWhiteSpace($observedModel)) { $context = "Host-observed model slug: $observedModel. Write this exact value as model.slug with model_source host-reported. Ledger: .harness/host-model/$conversationId.json" } Write-Output (@{ additional_context = $context } | ConvertTo-Json -Compress) exit 0 } $parsed = ConvertFrom-HarnessToolUseEnvelope -Payload $payload $policy = Test-HarnessToolUsePolicy -RepoRoot $RepoRoot -FeatureRoot $FeatureRoot ` -ToolFamily $parsed.ToolFamily -Command $parsed.Command -Path $parsed.Path $decision = [string]$policy.Decision if ($decision -eq 'deny' -and -not $SkipJournal) { try { $open = Get-HarnessOpenOwnershipTurn -RepoRoot $RepoRoot -FeatureRoot $FeatureRoot if ($open) { $actor = New-HarnessJournalActor -Agent 'harness-pretooluse-guard.ps1' -ParentAgent $null ` -Tool 'harness-script' -Provider 'other' -Slug 'unknown' -Role 'primary' $activity = New-HarnessJournalActivity -Id (New-HarnessJournalActivityId) -Type 'sensor' -Name 'pretooluse-guard' -Role 'instant' $denyPath = if ($parsed.Path) { $parsed.Path } else { $parsed.Command } Add-HarnessJournalEvent -FeatureRoot $open.FeatureRoot -Feature $open.Feature -Slice $open.Slice ` -Kind 'SensorRunRecorded' -Activity $activity -Actor $actor -Outcome 'DENY' ` -Summary "preToolUse denied: $denyPath" ` -Payload ([pscustomobject]@{ name = 'pretooluse-guard' verdict = 'DENY' path = $denyPath reason = [string]$policy.Reason }) ` -Refs ([pscustomobject]@{ amends_event_id = $null pair_event_id = $null artifact = $null command = '.\scripts\kaden\harness\harness-pretooluse-guard.ps1' }) | Out-Null } } catch { Write-Verbose "pretooluse-guard journal append failed: $($_.Exception.Message)" } } if ($decision -ne 'deny') { $decision = 'allow' } Write-Output (ConvertTo-HarnessHookPermissionJson -ResponseFamily $parsed.ResponseFamily -Decision $decision -Reason ([string]$policy.Reason)) exit 0 |