diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml
new file mode 100644
index 0000000..f69b785
--- /dev/null
+++ b/.gitea/workflows/build.yml
@@ -0,0 +1,49 @@
+name: Build MrTrust
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+ workflow_dispatch:
+
+jobs:
+ build-windows:
+ runs-on: windows
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Verify PowerShell scripts
+ shell: powershell
+ run: |
+ $scripts = @()
+ $scripts += @(Get-ChildItem . -Filter *.ps1)
+ $scripts += @(Get-ChildItem .\scripts -Filter *.ps1)
+ foreach ($script in $scripts) {
+ $tokens = $null
+ $errors = $null
+ [System.Management.Automation.Language.Parser]::ParseFile($script.FullName, [ref]$tokens, [ref]$errors) | Out-Null
+ if ($errors) { throw $errors }
+ }
+
+ - name: Build release ZIP
+ shell: powershell
+ run: |
+ $arguments = @("-ExecutionPolicy", "Bypass", "-File", ".\scripts\New-MrTrustRelease.ps1", "-Version", "0.1.0")
+ if ($env:MRTRUST_SIGNING_THUMBPRINT) {
+ $arguments += @("-SigningThumbprint", $env:MRTRUST_SIGNING_THUMBPRINT)
+ }
+ powershell @arguments
+
+ - name: Show package contents
+ shell: powershell
+ run: |
+ Get-ChildItem .\dist -Recurse | Select-Object FullName, Length
+
+ - name: Upload release artifact
+ uses: actions/upload-artifact@v3
+ with:
+ name: MrTrust-0.1.0
+ path: dist/MrTrust-0.1.0.zip
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f46df26
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+private/
+dist/
+*.pfx
+*.snk
+*.log
+bin/
+obj/
+.vs/
+.vscode/
diff --git a/AGENTS.md b/AGENTS.md
index 6dad8bc..4f062ea 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -1,60 +1,35 @@
-# Agent Instructions For This Repository
+# Agent Instructions For MrTrust
-This file is for Codex agents working on the Codex Agent Repository Kit itself. The public `README.md` is for humans and should stay focused on setup and usage.
+MrTrust manages explicit Windows certificate trust for MrSphay software.
-## Start Of Task
+## Security Boundaries
-- Check `git status --short`.
-- If the working tree is clean, run `git pull --ff-only` before editing.
-- If local changes exist, preserve them and do not overwrite user work.
-- Conserve context tokens: use `rg`, targeted file reads, and short summaries instead of loading unrelated files or long logs.
+- Do not add Defender, SmartScreen, UAC, firewall, or policy bypasses.
+- Do not add silent certificate installation.
+- Do not commit `.pfx`, private keys, passwords, tokens, or signing secrets.
+- Default to `CurrentUser` certificate stores. Use `LocalMachine` only when the user explicitly chooses all-user trust.
+- Keep all user-facing trust actions reversible.
-## Repository Purpose
+## Repository Layout
-This repository ships reusable baseline files for other repositories:
-
-- `files/` contains templates copied into target repositories.
-- `agent-quickstart.md`, `new-repository.md`, and `existing-project.md` are agent workflows.
-- `manifest.json` is the source of truth for copy targets and placeholders.
-- `profiles/` contains stack-specific guidance.
-
-## Editing Rules
-
-- Keep repository owner, repository name, project names, and local paths dynamic. This kit intentionally targets `https://git.wilkensxl.de` and SSH port `2222`, so keep that host/port consistent in user-facing setup and Gitea workflow defaults.
-- If a new placeholder is introduced, update `manifest.json`, the README placeholder list, and placeholder scans in workflow templates.
-- Keep `README.md` user-facing. Put agent operating rules in this file or the workflow docs.
-- Keep `files/AGENTS.md` generic; it is copied into target repositories and must not describe this repository specifically.
-- Do not include secrets, tokens, private data, or sensitive logs in docs, issues, commits, or release notes.
-
-## Follow-up Work
-
-- Create focused tracker issues for real follow-up work that is outside the current scope or can be done independently.
-- Do not create issues for work that can be safely completed in the current task.
-- If issue creation is unavailable, update `docs/agent-handoff.md` with the blocker and next steps.
+- `scripts/` contains the PowerShell implementation.
+- `assets/certificates/` contains public certificates only.
+- `private/` is ignored and may contain local signing material.
+- `docs/integration-prompt.md` is the prompt for adding MrTrust to other projects.
+- `docs/security-model.md` documents the intended behavior and limits.
+- `MrTrust.ps1 gui` is the user-facing GUI entry point.
## Verification
-Before committing:
+Before finishing changes, run:
```powershell
-Get-Content manifest.json | ConvertFrom-Json | Out-Null
-Get-Content manifest.schema.json | ConvertFrom-Json | Out-Null
-Get-Content files\blueprint.json | ConvertFrom-Json | Out-Null
+$scripts = Get-ChildItem .\scripts -Filter *.ps1
+foreach ($script in $scripts) {
+ $tokens = $null
+ $errors = $null
+ [System.Management.Automation.Language.Parser]::ParseFile($script.FullName, [ref]$tokens, [ref]$errors) | Out-Null
+ if ($errors) { throw $errors }
+}
git diff --check
```
-
-Also verify:
-
-- every `manifest.json` copyMap source exists,
-- every profile path exists,
-- reusable files contain no private instance defaults such as a specific username or private host,
-- `README.md` documents every placeholder listed in `manifest.json`.
-
-## Release
-
-- Bump `manifest.json` version.
-- Update `CHANGELOG.md`.
-- Commit changes.
-- Create an annotated tag such as `v1.0.2`.
-- Push `main` and tags.
-- Create or update the Gitea release when a valid API token is available.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a408b99..f79b806 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,41 +1,9 @@
# Changelog
-All notable changes to the Codex Agent Repository Kit are documented here.
+## 0.1.0
-## 1.0.5 - 2026-05-15
-
-- Restored the rainbow section divider theme in the human-facing `README.md`.
-- Added separate minimal permission guidance for `REGISTRY_TOKEN` and `GITEA_TOKEN`.
-- Clarified where package-only and API-capable tokens should be used.
-
-## 1.0.4 - 2026-05-15
-
-- Set the documented Gitea host to `git.wilkensxl.de` instead of a generic URL placeholder.
-- Documented SSH clone URLs for port `2222` and optional SSH config.
-- Restored Gitea workflow and README badge defaults for the intended Gitea instance while keeping repository owner and repository name dynamic.
-
-## 1.0.3 - 2026-05-15
-
-- Updated repository handoff notes after verifying the refreshed local `GITEA_TOKEN`.
-- Confirmed live issue creation and Gitea release API access for this repository.
-
-## 1.0.2 - 2026-05-15
-
-- Split the repository documentation into a human-facing setup `README.md` and a repository-specific agent instruction file in `AGENTS.md`.
-- Expanded the human README with full new-repository setup guidance, SSH setup, Gitea token permissions, local token configuration, repository secrets, package publishing, and release checks.
-- Documented the recommended Gitea token permission matrix shown in the token UI.
-
-## 1.0.1 - 2026-05-15
-
-- Added agent guidance to create focused tracker issues for actionable follow-up work that is outside the current scope or independently parallelizable.
-- Added safeguards against creating vague, duplicate, or sensitive public issues.
-- Updated handoff guidance to use `docs/agent-handoff.md` when no issue tracker is available or the details are too sensitive for public issues.
-
-## 1.0.0 - 2026-05-15
-
-- Added universal repository baseline templates for Codex-assisted projects.
-- Added agent quickstart, new repository, and existing project workflows.
-- Added optional Gitea workflow templates for build, security scanning, cleanup, dependency checks, release dry runs, and template compliance.
-- Added stack profiles for Node, Electron, Python, Docker, and static sites.
-- Added guidance for dynamic repository owners, safe task-start syncs, release artifact exclusions, and context token conservation.
-- Removed hard-coded private Gitea instance URLs from reusable templates.
+- Added MrTrust certificate generation, installation, removal, and signing scripts.
+- Added a simple Windows GUI for installing and removing MrTrust.
+- Added a Windows launcher EXE and Gitea Runner workflow for release ZIP builds.
+- Added integration prompt for other Windows projects.
+- Added security model documentation.
diff --git a/MrTrust.ps1 b/MrTrust.ps1
new file mode 100644
index 0000000..5bfd2af
--- /dev/null
+++ b/MrTrust.ps1
@@ -0,0 +1,52 @@
+param(
+ [Parameter(Position = 0)]
+ [ValidateSet("gui", "install", "uninstall", "new-cert", "sign", "help")]
+ [string]$Command = "help",
+ [Parameter(ValueFromRemainingArguments = $true)]
+ [string[]]$RemainingArguments
+)
+
+$ErrorActionPreference = "Stop"
+
+$root = Split-Path -Parent $MyInvocation.MyCommand.Path
+
+function Invoke-MrTrustScript {
+ param([Parameter(Mandatory)][string]$ScriptPath)
+
+ & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $ScriptPath @RemainingArguments
+ if ($LASTEXITCODE -ne 0) {
+ exit $LASTEXITCODE
+ }
+}
+
+switch ($Command) {
+ "gui" {
+ Invoke-MrTrustScript -ScriptPath (Join-Path $root "scripts\Start-MrTrustGui.ps1")
+ }
+ "install" {
+ Invoke-MrTrustScript -ScriptPath (Join-Path $root "scripts\Install-MrTrust.ps1")
+ }
+ "uninstall" {
+ Invoke-MrTrustScript -ScriptPath (Join-Path $root "scripts\Uninstall-MrTrust.ps1")
+ }
+ "new-cert" {
+ Invoke-MrTrustScript -ScriptPath (Join-Path $root "scripts\New-MrTrustCertificate.ps1")
+ }
+ "sign" {
+ Invoke-MrTrustScript -ScriptPath (Join-Path $root "scripts\Sign-MrTrustProject.ps1")
+ }
+ default {
+ Write-Host "MrTrust commands:"
+ Write-Host " .\MrTrust.ps1 gui Open the MrTrust Windows interface"
+ Write-Host " .\MrTrust.ps1 install Install MrSphay public trust certificates"
+ Write-Host " .\MrTrust.ps1 uninstall Remove MrSphay public trust certificates"
+ Write-Host " .\MrTrust.ps1 new-cert Create MrSphay root and signing certificates"
+ Write-Host " .\MrTrust.ps1 sign Sign build artifacts"
+ Write-Host ""
+ Write-Host "Examples:"
+ Write-Host " .\MrTrust.ps1 gui"
+ Write-Host " .\MrTrust.ps1 install"
+ Write-Host " .\MrTrust.ps1 install -Scope LocalMachine"
+ Write-Host " .\MrTrust.ps1 sign -Path .\dist\MyApp.exe -PfxPath .\private\MrSphay-CodeSigning.pfx"
+ }
+}
diff --git a/README.md b/README.md
index 85b7cab..547b1e2 100644
--- a/README.md
+++ b/README.md
@@ -1,368 +1,123 @@
-# Codex Agent Repository Kit
+# MrTrust
-Reusable setup kit for new or existing repositories that should be easy for Codex agents, humans, and CI workflows to maintain.
+MrTrust is a small Windows trust-onboarding kit for MrSphay software.
-This README is for humans. Agent-facing rules live in `AGENTS.md`, `agent-quickstart.md`, `new-repository.md`, and `existing-project.md`.
+It is designed for this workflow:
-

+1. MrSphay creates a private code-signing certificate once.
+2. MrSphay publishes only the public trust certificate with MrTrust.
+3. A user runs MrTrust once and explicitly approves installing that public certificate.
+4. MrSphay projects signed with the matching certificate chain are shown as trusted on that PC.
-## What This Kit Adds
+MrTrust does not bypass Microsoft Defender or SmartScreen. Windows can still scan, quarantine, or warn about suspicious files. This project only manages normal Windows certificate trust with visible user consent.
-- `AGENTS.md` and `.codex/project.md` for agent context.
-- Optional Gitea workflows for build, security scan, cleanup, dependency check, release dry run, and template compliance.
-- Release, security, handoff, changelog, and contribution templates.
-- README blueprint templates for projects that want generated README output.
-- Stack notes for Node, Electron, Python, Docker, and static-site projects.
+## What It Contains
-
+- `MrTrust.ps1 gui` opens a simple Windows interface for installing or removing trust.
+- `scripts/New-MrTrustCertificate.ps1` creates a local root certificate and a code-signing certificate for the publisher.
+- `scripts/Install-MrTrust.ps1` installs the public trust certificate for the current user or the local machine.
+- `scripts/Uninstall-MrTrust.ps1` removes the MrTrust certificate again.
+- `scripts/Sign-MrTrustProject.ps1` signs `.exe`, `.msi`, `.ps1`, and other Authenticode-compatible files.
+- `scripts/New-MrTrustRelease.ps1` builds a distributable ZIP package.
+- `docs/integration-prompt.md` is a prompt you can paste into other Windows projects.
-## Recommended New Repository Setup
+## Quick Start For MrSphay
-1. Create the repository in Gitea.
-2. Clone it locally with SSH.
-3. Copy this kit into the repository with Codex or manually from `files/`.
-4. Replace placeholders with real project values.
-5. Add repository secrets for CI publishing.
-6. Commit and push the baseline.
-7. Let the Gitea workflows report any missing setup.
-
-
-
-## SSH Setup
-
-Generate a key if you do not already have one:
+Create the certificates:
```powershell
-ssh-keygen -t ed25519 -C "you@example.com"
+.\scripts\New-MrTrustCertificate.ps1
```
-Start the SSH agent and add the key:
+This writes:
+
+- public certificates to `assets\certificates\`
+- private signing material to `private\`
+
+The `private\` directory is ignored by git. Do not publish `.pfx` files or passwords.
+
+Install the public trust certificate on your own PC:
```powershell
-Start-Service ssh-agent
-ssh-add $env:USERPROFILE\.ssh\id_ed25519
+.\MrTrust.ps1 install
```
-Show the public key:
+Open the GUI:
```powershell
-Get-Content $env:USERPROFILE\.ssh\id_ed25519.pub
+.\MrTrust.ps1 gui
```
-Add that public key in Gitea:
-
-```text
-Profile -> Settings -> SSH / GPG Keys -> Add Key
-```
-
-Clone with SSH:
-
-```bash
-git clone ssh://git@git.wilkensxl.de:2222/OWNER/REPOSITORY.git
-cd REPOSITORY
-```
-
-Optional SSH config:
-
-```text
-Host git.wilkensxl.de
- HostName git.wilkensxl.de
- User git
- Port 2222
- IdentityFile ~/.ssh/id_ed25519
-```
-
-With that config, this shorter clone URL also works:
-
-```bash
-git clone git@git.wilkensxl.de:OWNER/REPOSITORY.git
-```
-
-Verify the remote:
-
-```bash
-git remote -v
-git status --short
-```
-
-
-
-## Applying The Kit With Codex
-
-For a new repository, start Codex in the target repository and use:
-
-```text
-Use the Codex Agent Repository Kit.
-Read manifest.json, then use new-repository.md.
-Create the smallest useful baseline for this repository.
-Replace placeholders with real values from this repository.
-Keep commands truthful and do not invent scripts that cannot run.
-Do not create a release.
-```
-
-For an existing repository:
-
-```text
-Use the Codex Agent Repository Kit.
-Read manifest.json, then use existing-project.md.
-Retrofit the baseline without replacing existing project structure or README knowledge.
-Preserve current CI behavior and project style.
-Do not create a release.
-```
-
-
-
-## Manual Copy Map
-
-Use `manifest.json` as the source of truth. Common targets:
-
-| Template | Target |
-| --- | --- |
-| `files/AGENTS.md` | `AGENTS.md` |
-| `files/project.md` | `.codex/project.md` |
-| `files/build-gitea.yml` | `.gitea/workflows/build.yml` |
-| `files/security-scan-gitea.yml` | `.gitea/workflows/security-scan.yml` |
-| `files/repo-cleanup-gitea.yml` | `.gitea/workflows/repo-cleanup.yml` |
-| `files/dependency-check-gitea.yml` | `.gitea/workflows/dependency-check.yml` |
-| `files/release-dry-run-gitea.yml` | `.gitea/workflows/release-dry-run.yml` |
-| `files/template-compliance-gitea.yml` | `.gitea/workflows/template-compliance.yml` |
-| `files/SECURITY.md` | `SECURITY.md` |
-| `files/CHANGELOG.md` | `CHANGELOG.md` |
-| `files/CONTRIBUTING.md` | `CONTRIBUTING.md` |
-| `files/release-checklist.md` | `docs/release-checklist.md` |
-| `files/security-review.md` | `docs/security-review.md` |
-| `files/agent-handoff.md` | `docs/agent-handoff.md` |
-
-
-
-## Required Placeholder Values
-
-Replace or remove all placeholders before considering a repository ready:
-
-```text
-PROJECT_NAME
-PROJECT_DESCRIPTION
-REPOSITORY_OWNER
-REPOSITORY_NAME
-PACKAGE_NAME
-ARTIFACT_NAME
-ARTIFACT_OUTPUT_DIRECTORY
-AUTHOR_NAME
-PROJECT_STACK
-DOWNLOAD_URL
-CI_URL
-RELEASES_URL
-BUILD_COMMAND
-TEST_COMMAND
-LINT_COMMAND
-AUDIT_COMMAND
-README_COMMAND
-INSTALL_COMMAND
-DEV_COMMAND
-PACKAGE_MANAGER
-PROJECT_VERSION
-COMMIT_OR_VERSION
-```
-
-If a value does not apply, remove that section instead of leaving fake data. If a value is genuinely unknown, mark it as `PENDING`.
-
-
-
-## Token Overview
-
-Use separate tokens for separate jobs.
-
-| Token | Location | Purpose |
-| --- | --- | --- |
-| `REGISTRY_TOKEN` | Repository secret | CI package publishing from Gitea Actions |
-| `GITEA_TOKEN` | Local environment or repository secret | Gitea API access for issues, releases, workflow polling, and repository metadata |
-
-Repository secrets are available to workflows. They are not visible to local Codex sessions. Local Codex API actions need a local environment variable.
-
-
-
-## Gitea Token Permissions
-
-For both tokens, choose this repository access level:
-
-```text
-Repository and Organization Access: All (public, private, and limited)
-```
-
-Use separate tokens where possible. A package-only token should not be able to create issues or releases.
-
-### REGISTRY_TOKEN Permissions
-
-Use this token as a repository secret for package publishing from Gitea Actions:
-
-```text
-package: Read and Write
-repository: Read
-user: Read
-
-activitypub: No Access
-admin: No Access
-issue: No Access
-misc: No Access
-notification: No Access
-organization: No Access
-```
-
-These permissions cover generic package uploads while still allowing the workflow to read repository metadata.
-
-### GITEA_TOKEN Permissions
-
-Use this token locally on the PC for Codex API actions, or as a repository secret only when workflows need issue, release, or workflow API access:
-
-```text
-issue: Read and Write
-package: Read
-repository: Read and Write
-user: Read
-
-activitypub: No Access
-admin: No Access
-misc: No Access
-notification: No Access
-organization: No Access
-```
-
-These permissions cover creating and reading issues, creating and reading releases, reading repository metadata, and polling workflow runs where the Gitea API allows it. `package: Read` is enough for API checks; use `package: Read and Write` only if this same token must publish packages.
-
-Use a dedicated bot or automation user when possible.
-
-
-
-## Setting Local Tokens
-
-Set a local token for Codex or shell-based API work.
-
-Current PowerShell session:
+Sign another project build:
```powershell
-$env:GITEA_TOKEN = "paste-token-here"
+.\MrTrust.ps1 sign `
+ -Path "C:\Path\To\App.exe" `
+ -PfxPath ".\private\MrSphay-CodeSigning.pfx"
```
-Persist for the current Windows user:
+Remove the trust certificate:
```powershell
-setx GITEA_TOKEN "paste-token-here"
+.\MrTrust.ps1 uninstall
```
-Open a new terminal after `setx`.
-
-Test repository API access:
+Build a user-facing ZIP release:
```powershell
-$headers = @{ Authorization = "token $env:GITEA_TOKEN" }
-Invoke-RestMethod `
- -Uri "https://git.wilkensxl.de/api/v1/repos/REPOSITORY_OWNER/REPOSITORY_NAME" `
- -Headers $headers
+.\scripts\New-MrTrustRelease.ps1 -Version 0.1.0
```
-Test issue access:
+The Gitea workflow `.gitea/workflows/build.yml` builds the same ZIP on a Windows runner and uploads it as an artifact.
+If the Windows runner has the private signing certificate installed, set `MRTRUST_SIGNING_THUMBPRINT` to sign the launcher during the build.
+
+## User Installation
+
+For normal users, distribute MrTrust with the public certificate file:
+
+```text
+assets\certificates\MrSphay-LocalTrust-Root.cer
+assets\certificates\MrSphay-CodeSigning.cer
+```
+
+The user runs:
```powershell
-Invoke-RestMethod `
- -Uri "https://git.wilkensxl.de/api/v1/repos/REPOSITORY_OWNER/REPOSITORY_NAME/issues?state=open&limit=1" `
- -Headers $headers
+.\MrTrust.ps1 gui
```
-
-
-## Setting Repository Secrets
-
-In Gitea:
+By default, MrTrust installs trust only for the current Windows user:
```text
-Repository -> Settings -> Actions -> Secrets -> Add Secret
+Root certificate -> Cert:\CurrentUser\Root
+Code-signing certificate -> Cert:\CurrentUser\TrustedPublisher
```
-Add:
+For all users on the machine, run PowerShell as Administrator:
-```text
-REGISTRY_TOKEN
+```powershell
+.\MrTrust.ps1 install -Scope LocalMachine
```
-Use a token with package write access. If you want workflows to create releases or issues too, add a separate secret:
+## Using This Repo With Other Agents
-```text
-GITEA_TOKEN
-```
+Yes. Give another agent this repository URL and the target Windows project, then paste `docs/integration-prompt.md`.
-Keep package publishing and release or issue automation separate when possible. It makes permission reviews easier.
+Both sides have to be wired:
-
+- MrTrust side: users install the public trust certificates once.
+- Target project side: release artifacts are signed with the MrSphay code-signing certificate.
+- Installer side, optional: the target app can offer "Open MrTrust" or bundle the MrTrust ZIP, but it must not silently change trust.
-## Package Publishing
+If the target project is not signed, MrTrust cannot make it trusted.
-`files/build-gitea.yml` can publish generic packages when `REGISTRY_TOKEN` is available.
+## Important Limits
-The workflow:
+- This only helps for programs signed with the matching MrSphay certificate chain.
+- It does not make unsigned programs trusted.
+- It does not disable Defender, SmartScreen, UAC, or enterprise policies.
+- Public distribution without warnings is still best handled with a recognized commercial code-signing certificate.
-- builds project artifacts,
-- copies them to URL-safe filenames,
-- uploads immutable versioned packages,
-- updates a stable `latest` package path.
+## Recommended Project Integration
-The workflow uses:
-
-```text
-GITHUB_SERVER_URL
-GITHUB_REPOSITORY_OWNER
-GITHUB_REPOSITORY
-REGISTRY_TOKEN
-```
-
-When those values are unavailable, replace `REPOSITORY_OWNER`, `REPOSITORY_NAME`, and related placeholders before use. The default Gitea server is `https://git.wilkensxl.de`.
-
-
-
-## Agent Follow-up Issues
-
-Agents should create focused tracker issues for real follow-up work that is outside the current scope or can be handled independently by humans or other agents.
-
-An issue should include:
-
-- observed problem,
-- impact,
-- affected files or commands,
-- suggested next steps,
-- verification already performed.
-
-Agents must not create issues for vague reminders, duplicate work, or tasks they can safely finish immediately. Sensitive details belong in private channels or `docs/agent-handoff.md`, not public issues.
-
-
-
-## Release Checklist For A New Repo
-
-Before the first release of a target project:
-
-1. Ensure `AGENTS.md` and `.codex/project.md` match the real project.
-2. Replace all placeholders or mark genuinely unknown values as `PENDING`.
-3. Configure `REGISTRY_TOKEN` if packages are published.
-4. Configure `GITEA_TOKEN` only if workflows need issue or release API access.
-5. Verify SSH push access.
-6. Run lint, test, build, and audit commands that exist.
-7. Run `git diff --check`.
-8. Confirm release artifacts do not include Codex kit metadata unless explicitly wanted.
-9. Push and poll workflows to success or document the blocker.
-
-
-
-## Updating The Kit In A Project
-
-When this kit changes, update target repositories conservatively:
-
-```bash
-git status --short
-git pull --ff-only
-```
-
-Then ask Codex:
-
-```text
-Update this repository's Codex Agent Repository Kit files from the latest kit.
-Preserve project-specific README content, commands, release rules, and workflow customizations.
-Do not overwrite unrelated changes.
-```
+Use `docs/integration-prompt.md` in another Windows project. The prompt tells Codex or another assistant to add a visible trust check, a link or bundled copy of MrTrust, and a signing step without hiding security changes from the user.
diff --git a/assets/certificates/MrSphay-CodeSigning.cer b/assets/certificates/MrSphay-CodeSigning.cer
new file mode 100644
index 0000000..a408d84
Binary files /dev/null and b/assets/certificates/MrSphay-CodeSigning.cer differ
diff --git a/assets/certificates/MrSphay-LocalTrust-Root.cer b/assets/certificates/MrSphay-LocalTrust-Root.cer
new file mode 100644
index 0000000..fc32136
Binary files /dev/null and b/assets/certificates/MrSphay-LocalTrust-Root.cer differ
diff --git a/assets/certificates/README.md b/assets/certificates/README.md
new file mode 100644
index 0000000..99db875
--- /dev/null
+++ b/assets/certificates/README.md
@@ -0,0 +1,15 @@
+# Public Certificates
+
+Place the public MrSphay trust certificate here:
+
+```text
+MrSphay-LocalTrust-Root.cer
+```
+
+Generate it with:
+
+```powershell
+.\scripts\New-MrTrustCertificate.ps1
+```
+
+Only public `.cer` files belong in this directory. Never place `.pfx` files, private keys, or passwords here.
diff --git a/assets/certificates/thumbprints.txt b/assets/certificates/thumbprints.txt
new file mode 100644
index 0000000..495f148
--- /dev/null
+++ b/assets/certificates/thumbprints.txt
@@ -0,0 +1,7 @@
+MrSphay Local Trust Root
+Subject: CN=MrSphay Local Trust Root
+Thumbprint: 39F7458E6E2C1126E93E6A1F228196006B174DF2
+
+MrSphay Code Signing
+Subject: CN=MrSphay Code Signing
+Thumbprint: A024A89200469F099EC4A172B4F96F6428AFD41B
diff --git a/docs/integration-prompt.md b/docs/integration-prompt.md
new file mode 100644
index 0000000..906e3d5
--- /dev/null
+++ b/docs/integration-prompt.md
@@ -0,0 +1,33 @@
+# MrTrust Integration Prompt
+
+Use this prompt in another Windows project when you want Codex or another agent to add MrTrust support.
+
+```text
+Integrate MrTrust support into this Windows project.
+
+Goal:
+- Users should be able to run MrTrust once to trust software from MrSphay on their own PC.
+- The project must not disable Defender, SmartScreen, UAC, or Windows security policy.
+- All trust changes must be visible and require explicit user confirmation.
+
+Implementation requirements:
+- Add a docs section that explains MrTrust and links to or bundles the MrTrust release ZIP.
+- Add a build or release signing step that signs Windows artifacts with the MrSphay code-signing certificate.
+- Do not commit any .pfx file, private key, certificate password, or signing secret.
+- If the project has an installer, add an optional "Open MrTrust" action that runs:
+ powershell.exe -ExecutionPolicy Bypass -File .\MrTrust\MrTrust.ps1 gui
+- Prefer CurrentUser certificate installation by default.
+- Only offer LocalMachine installation when the user explicitly chooses an all-users install and the process is elevated.
+- Add an uninstall path or documentation that runs:
+ powershell.exe -ExecutionPolicy Bypass -File .\MrTrust\MrTrust.ps1 uninstall
+- Keep the UI wording clear: the user is trusting MrSphay signed software, not bypassing Windows security.
+- If this project produces an .exe, .msi, .dll, .ps1, .psm1, .psd1, or .cat release artifact, sign it with:
+ powershell.exe -ExecutionPolicy Bypass -File .\MrTrust\MrTrust.ps1 sign -Path -CertificateThumbprint A024A89200469F099EC4A172B4F96F6428AFD41B
+- Treat the certificate thumbprint as public metadata, but never commit private signing material.
+
+Verification:
+- Confirm unsigned builds still show as unsigned.
+- Confirm signed builds validate after MrTrust installation.
+- Confirm the MrTrust certificate can be removed again.
+- Confirm no private signing material is present in the repository or release artifact.
+```
diff --git a/docs/security-model.md b/docs/security-model.md
new file mode 100644
index 0000000..5b1f2be
--- /dev/null
+++ b/docs/security-model.md
@@ -0,0 +1,40 @@
+# MrTrust Security Model
+
+MrTrust is a trust bootstrapper, not a security bypass.
+
+## Allowed Behavior
+
+- Import a public MrSphay certificate into Windows certificate stores after explicit user approval.
+- Sign MrSphay build artifacts with a private code-signing certificate kept outside git.
+- Provide an uninstall script that removes the same certificate again.
+
+## Disallowed Behavior
+
+- Disabling Microsoft Defender.
+- Disabling SmartScreen.
+- Silently modifying certificate stores.
+- Installing private keys on user machines.
+- Hiding certificate installation inside unrelated app actions.
+- Shipping `.pfx` files or signing passwords in a repository or release.
+
+## Recommended Stores
+
+For normal users:
+
+```text
+Cert:\CurrentUser\Root
+Cert:\CurrentUser\TrustedPublisher
+```
+
+For managed PCs or all-user installs:
+
+```text
+Cert:\LocalMachine\Root
+Cert:\LocalMachine\TrustedPublisher
+```
+
+The LocalMachine stores require administrator approval.
+
+## Residual Windows Warnings
+
+Even after MrTrust is installed, Windows can still block suspicious software. SmartScreen reputation, Defender detections, enterprise security policy, and downloaded-file mark-of-the-web behavior are separate from Authenticode trust.
diff --git a/scripts/Build-MrTrustExe.ps1 b/scripts/Build-MrTrustExe.ps1
new file mode 100644
index 0000000..2d4190a
--- /dev/null
+++ b/scripts/Build-MrTrustExe.ps1
@@ -0,0 +1,50 @@
+[CmdletBinding()]
+param(
+ [string]$OutputPath = ".\dist\MrTrust.exe"
+)
+
+$ErrorActionPreference = "Stop"
+
+function Resolve-FullPath {
+ param([Parameter(Mandatory)][string]$Path)
+
+ $executionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)
+}
+
+$root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
+$sourcePath = Join-Path $root "src\MrTrustLauncher.cs"
+$resolvedOutputPath = Resolve-FullPath $OutputPath
+$outputDirectory = Split-Path -Parent $resolvedOutputPath
+
+if (-not (Test-Path -LiteralPath $sourcePath)) {
+ throw "Launcher source not found: $sourcePath"
+}
+
+New-Item -ItemType Directory -Force -Path $outputDirectory | Out-Null
+
+$compilerCandidates = @(
+ "$env:WINDIR\Microsoft.NET\Framework64\v4.0.30319\csc.exe",
+ "$env:WINDIR\Microsoft.NET\Framework\v4.0.30319\csc.exe"
+)
+
+$compiler = $compilerCandidates | Where-Object { Test-Path -LiteralPath $_ } | Select-Object -First 1
+if (-not $compiler) {
+ throw "csc.exe was not found. Run this build on a Windows Gitea runner with .NET Framework installed."
+}
+
+& $compiler `
+ /nologo `
+ /target:winexe `
+ /optimize+ `
+ /platform:anycpu `
+ /out:$resolvedOutputPath `
+ /reference:System.Windows.Forms.dll `
+ /reference:System.Drawing.dll `
+ $sourcePath
+
+if ($LASTEXITCODE -ne 0) {
+ throw "csc.exe failed with exit code $LASTEXITCODE."
+}
+
+Write-Host "Created EXE:"
+Write-Host " $resolvedOutputPath"
diff --git a/scripts/Install-MrTrust.ps1 b/scripts/Install-MrTrust.ps1
new file mode 100644
index 0000000..56b681b
--- /dev/null
+++ b/scripts/Install-MrTrust.ps1
@@ -0,0 +1,90 @@
+[CmdletBinding(SupportsShouldProcess)]
+param(
+ [string]$CertificatePath = ".\assets\certificates\MrSphay-LocalTrust-Root.cer",
+ [string]$PublisherCertificatePath = ".\assets\certificates\MrSphay-CodeSigning.cer",
+ [ValidateSet("CurrentUser", "LocalMachine")]
+ [string]$Scope = "CurrentUser"
+)
+
+$ErrorActionPreference = "Stop"
+
+function Resolve-FullPath {
+ param([Parameter(Mandatory)][string]$Path)
+
+ $executionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)
+}
+
+function Test-IsAdministrator {
+ $identity = [Security.Principal.WindowsIdentity]::GetCurrent()
+ $principal = [Security.Principal.WindowsPrincipal]::new($identity)
+ $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
+}
+
+$resolvedCertificatePath = Resolve-FullPath $CertificatePath
+if (-not (Test-Path -LiteralPath $resolvedCertificatePath)) {
+ throw "Certificate file not found: $resolvedCertificatePath. Run scripts\New-MrTrustCertificate.ps1 first or provide -CertificatePath."
+}
+
+if ($Scope -eq "LocalMachine" -and -not (Test-IsAdministrator)) {
+ throw "LocalMachine installation requires an elevated PowerShell session. Use -Scope CurrentUser or run as Administrator."
+}
+
+$rootCertificate = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($resolvedCertificatePath)
+if (-not $rootCertificate.Subject.StartsWith("CN=MrSphay", [System.StringComparison]::OrdinalIgnoreCase)) {
+ throw "Refusing to install an unexpected root certificate subject: $($rootCertificate.Subject)"
+}
+
+$resolvedPublisherCertificatePath = Resolve-FullPath $PublisherCertificatePath
+$publisherCertificate = $null
+if (Test-Path -LiteralPath $resolvedPublisherCertificatePath) {
+ $publisherCertificate = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($resolvedPublisherCertificatePath)
+ if (-not $publisherCertificate.Subject.StartsWith("CN=MrSphay", [System.StringComparison]::OrdinalIgnoreCase)) {
+ throw "Refusing to install an unexpected publisher certificate subject: $($publisherCertificate.Subject)"
+ }
+}
+
+$rootStore = "Cert:\$Scope\Root"
+$publisherStore = "Cert:\$Scope\TrustedPublisher"
+
+Write-Host "MrTrust will install this certificate as trusted for scope '$Scope':"
+Write-Host " Root subject: $($rootCertificate.Subject)"
+Write-Host " Root issuer: $($rootCertificate.Issuer)"
+Write-Host " Root thumbprint: $($rootCertificate.Thumbprint)"
+Write-Host " Root expires: $($rootCertificate.NotAfter.ToString('yyyy-MM-dd HH:mm:ss'))"
+if ($publisherCertificate) {
+ Write-Host " Publisher subject: $($publisherCertificate.Subject)"
+ Write-Host " Publisher thumbprint: $($publisherCertificate.Thumbprint)"
+}
+else {
+ Write-Warning "Publisher certificate not found at $resolvedPublisherCertificatePath. Only the root certificate will be installed."
+}
+Write-Host ""
+Write-Warning "Only continue if you trust MrSphay software signed with this certificate chain."
+
+$answer = Read-Host "Type INSTALL to continue"
+if ($answer -cne "INSTALL") {
+ Write-Host "Installation cancelled."
+ exit 1
+}
+
+$existingRoot = Get-ChildItem -Path $rootStore | Where-Object Thumbprint -eq $rootCertificate.Thumbprint
+
+if (-not $existingRoot) {
+ if ($PSCmdlet.ShouldProcess($rootStore, "Install MrTrust root certificate")) {
+ Import-Certificate -FilePath $resolvedCertificatePath -CertStoreLocation $rootStore | Out-Null
+ }
+}
+
+if ($publisherCertificate) {
+ $existingPublisher = Get-ChildItem -Path $publisherStore | Where-Object Thumbprint -eq $publisherCertificate.Thumbprint
+ if (-not $existingPublisher) {
+ if ($PSCmdlet.ShouldProcess($publisherStore, "Install MrTrust trusted publisher certificate")) {
+ Import-Certificate -FilePath $resolvedPublisherCertificatePath -CertStoreLocation $publisherStore | Out-Null
+ }
+ }
+}
+
+Write-Host "MrTrust certificate installed."
+Write-Host "Installed stores:"
+Write-Host " $rootStore"
+Write-Host " $publisherStore"
diff --git a/scripts/New-MrTrustCertificate.ps1 b/scripts/New-MrTrustCertificate.ps1
new file mode 100644
index 0000000..e154890
--- /dev/null
+++ b/scripts/New-MrTrustCertificate.ps1
@@ -0,0 +1,90 @@
+[CmdletBinding()]
+param(
+ [string]$PublisherName = "MrSphay",
+ [string]$RootSubject = "CN=MrSphay Local Trust Root",
+ [string]$SigningSubject = "CN=MrSphay Code Signing",
+ [string]$PublicCertificateDirectory = ".\assets\certificates",
+ [string]$PrivateDirectory = ".\private",
+ [int]$ValidYears = 5,
+ [switch]$SkipPfxExport
+)
+
+$ErrorActionPreference = "Stop"
+
+function Resolve-FullPath {
+ param([Parameter(Mandatory)][string]$Path)
+
+ $executionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)
+}
+
+if ($ValidYears -lt 1 -or $ValidYears -gt 20) {
+ throw "ValidYears must be between 1 and 20."
+}
+
+$publicDirectory = Resolve-FullPath $PublicCertificateDirectory
+$privateDirectory = Resolve-FullPath $PrivateDirectory
+
+New-Item -ItemType Directory -Force -Path $publicDirectory | Out-Null
+if (-not $SkipPfxExport) {
+ New-Item -ItemType Directory -Force -Path $privateDirectory | Out-Null
+}
+
+$rootCertPath = Join-Path $publicDirectory "$PublisherName-LocalTrust-Root.cer"
+$signingCertPath = Join-Path $publicDirectory "$PublisherName-CodeSigning.cer"
+$pfxPath = Join-Path $privateDirectory "$PublisherName-CodeSigning.pfx"
+
+Write-Host "Creating local trust root certificate: $RootSubject"
+$rootCertificate = New-SelfSignedCertificate `
+ -Type Custom `
+ -Subject $RootSubject `
+ -KeyAlgorithm RSA `
+ -KeyLength 4096 `
+ -HashAlgorithm SHA256 `
+ -KeyExportPolicy Exportable `
+ -KeyUsage CertSign, CRLSign, DigitalSignature `
+ -TextExtension @("2.5.29.19={critical}{text}ca=1&pathlength=1") `
+ -CertStoreLocation "Cert:\CurrentUser\My" `
+ -NotAfter (Get-Date).AddYears($ValidYears)
+
+Write-Host "Creating code-signing certificate: $SigningSubject"
+$signingCertificate = New-SelfSignedCertificate `
+ -Type CodeSigningCert `
+ -Subject $SigningSubject `
+ -Signer $rootCertificate `
+ -KeyAlgorithm RSA `
+ -KeyLength 4096 `
+ -HashAlgorithm SHA256 `
+ -KeyExportPolicy Exportable `
+ -CertStoreLocation "Cert:\CurrentUser\My" `
+ -NotAfter (Get-Date).AddYears($ValidYears)
+
+Export-Certificate -Cert $rootCertificate -FilePath $rootCertPath -Force | Out-Null
+Export-Certificate -Cert $signingCertificate -FilePath $signingCertPath -Force | Out-Null
+
+if (-not $SkipPfxExport) {
+ $password = Read-Host "Enter a strong password for the private code-signing PFX" -AsSecureString
+ Export-PfxCertificate -Cert $signingCertificate -FilePath $pfxPath -Password $password -Force | Out-Null
+}
+
+Write-Host ""
+Write-Host "Created public root certificate:"
+Write-Host " $rootCertPath"
+Write-Host "Root thumbprint:"
+Write-Host " $($rootCertificate.Thumbprint)"
+Write-Host ""
+Write-Host "Created public signing certificate:"
+Write-Host " $signingCertPath"
+Write-Host "Signing thumbprint:"
+Write-Host " $($signingCertificate.Thumbprint)"
+Write-Host ""
+if ($SkipPfxExport) {
+ Write-Host "Skipped private PFX export."
+ Write-Host "Use this thumbprint for signing from the Windows certificate store:"
+ Write-Host " $($signingCertificate.Thumbprint)"
+}
+else {
+ Write-Host "Created private PFX:"
+ Write-Host " $pfxPath"
+ Write-Host ""
+ Write-Warning "Do not commit, publish, or share the private .pfx file or its password."
+}
diff --git a/scripts/New-MrTrustRelease.ps1 b/scripts/New-MrTrustRelease.ps1
new file mode 100644
index 0000000..6028905
--- /dev/null
+++ b/scripts/New-MrTrustRelease.ps1
@@ -0,0 +1,70 @@
+[CmdletBinding()]
+param(
+ [string]$Version = "0.1.0",
+ [string]$OutputDirectory = ".\dist",
+ [string]$SigningThumbprint,
+ [switch]$NoTimestamp,
+ [switch]$AllowUntrustedRoot
+)
+
+$ErrorActionPreference = "Stop"
+
+function Resolve-FullPath {
+ param([Parameter(Mandatory)][string]$Path)
+
+ $executionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)
+}
+
+$root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
+$output = Resolve-FullPath $OutputDirectory
+$packageRoot = Join-Path $output "MrTrust-$Version"
+$zipPath = Join-Path $output "MrTrust-$Version.zip"
+$exePath = Join-Path $output "MrTrust.exe"
+
+if (Test-Path -LiteralPath $packageRoot) {
+ Remove-Item -LiteralPath $packageRoot -Recurse -Force
+}
+
+New-Item -ItemType Directory -Force -Path $packageRoot | Out-Null
+New-Item -ItemType Directory -Force -Path (Join-Path $packageRoot "scripts") | Out-Null
+New-Item -ItemType Directory -Force -Path (Join-Path $packageRoot "assets\certificates") | Out-Null
+New-Item -ItemType Directory -Force -Path (Join-Path $packageRoot "docs") | Out-Null
+
+& (Join-Path $root "scripts\Build-MrTrustExe.ps1") -OutputPath $exePath
+
+if ($SigningThumbprint) {
+ $signArguments = @{
+ Path = $exePath
+ CertificateThumbprint = $SigningThumbprint
+ }
+
+ if ($NoTimestamp) {
+ $signArguments.NoTimestamp = $true
+ }
+
+ if ($AllowUntrustedRoot) {
+ $signArguments.AllowUntrustedRoot = $true
+ }
+
+ & (Join-Path $root "scripts\Sign-MrTrustProject.ps1") @signArguments
+}
+
+Copy-Item -LiteralPath $exePath -Destination $packageRoot
+Copy-Item -LiteralPath (Join-Path $root "MrTrust.ps1") -Destination $packageRoot
+Copy-Item -LiteralPath (Join-Path $root "README.md") -Destination $packageRoot
+Copy-Item -LiteralPath (Join-Path $root "scripts\Install-MrTrust.ps1") -Destination (Join-Path $packageRoot "scripts")
+Copy-Item -LiteralPath (Join-Path $root "scripts\Uninstall-MrTrust.ps1") -Destination (Join-Path $packageRoot "scripts")
+Copy-Item -LiteralPath (Join-Path $root "scripts\Start-MrTrustGui.ps1") -Destination (Join-Path $packageRoot "scripts")
+Copy-Item -LiteralPath (Join-Path $root "assets\certificates\MrSphay-LocalTrust-Root.cer") -Destination (Join-Path $packageRoot "assets\certificates")
+Copy-Item -LiteralPath (Join-Path $root "assets\certificates\MrSphay-CodeSigning.cer") -Destination (Join-Path $packageRoot "assets\certificates")
+Copy-Item -LiteralPath (Join-Path $root "assets\certificates\thumbprints.txt") -Destination (Join-Path $packageRoot "assets\certificates")
+Copy-Item -LiteralPath (Join-Path $root "docs\security-model.md") -Destination (Join-Path $packageRoot "docs")
+
+if (Test-Path -LiteralPath $zipPath) {
+ Remove-Item -LiteralPath $zipPath -Force
+}
+
+Compress-Archive -Path (Join-Path $packageRoot "*") -DestinationPath $zipPath -Force
+
+Write-Host "Created release package:"
+Write-Host " $zipPath"
diff --git a/scripts/Sign-MrTrustProject.ps1 b/scripts/Sign-MrTrustProject.ps1
new file mode 100644
index 0000000..e62d7f4
--- /dev/null
+++ b/scripts/Sign-MrTrustProject.ps1
@@ -0,0 +1,104 @@
+[CmdletBinding()]
+param(
+ [Parameter(Mandatory)]
+ [string[]]$Path,
+ [string]$PfxPath,
+ [string]$CertificateThumbprint,
+ [string]$TimestampServer = "http://timestamp.digicert.com",
+ [switch]$NoTimestamp,
+ [switch]$AllowUntrustedRoot
+)
+
+$ErrorActionPreference = "Stop"
+
+function Resolve-FullPath {
+ param([Parameter(Mandatory)][string]$Path)
+
+ $executionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)
+}
+
+function Get-CodeSigningCertificateFromStore {
+ param([Parameter(Mandatory)][string]$Thumbprint)
+
+ $normalizedThumbprint = $Thumbprint -replace "\s", ""
+ $certificate = Get-ChildItem "Cert:\CurrentUser\My", "Cert:\LocalMachine\My" |
+ Where-Object { $_.Thumbprint -eq $normalizedThumbprint } |
+ Select-Object -First 1
+
+ if (-not $certificate) {
+ throw "No code-signing certificate found with thumbprint $Thumbprint."
+ }
+
+ $certificate
+}
+
+if (-not $PfxPath -and -not $CertificateThumbprint) {
+ throw "Provide either -PfxPath or -CertificateThumbprint."
+}
+
+if ($PfxPath -and $CertificateThumbprint) {
+ throw "Use either -PfxPath or -CertificateThumbprint, not both."
+}
+
+if ($PfxPath) {
+ $resolvedPfxPath = Resolve-FullPath $PfxPath
+ if (-not (Test-Path -LiteralPath $resolvedPfxPath)) {
+ throw "PFX file not found: $resolvedPfxPath"
+ }
+
+ $password = Read-Host "Enter PFX password" -AsSecureString
+ $storageFlags = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable -bor
+ [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet
+ $certificate = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($resolvedPfxPath, $password, $storageFlags)
+}
+else {
+ $certificate = Get-CodeSigningCertificateFromStore -Thumbprint $CertificateThumbprint
+}
+
+if (-not $certificate.HasPrivateKey) {
+ throw "The selected certificate does not include a private key and cannot sign files."
+}
+
+$targets = foreach ($item in $Path) {
+ $resolvedPath = Resolve-FullPath $item
+ if (Test-Path -LiteralPath $resolvedPath -PathType Container) {
+ Get-ChildItem -LiteralPath $resolvedPath -Recurse -File |
+ Where-Object { $_.Extension -in ".exe", ".msi", ".dll", ".ps1", ".psm1", ".psd1", ".cat" }
+ }
+ elseif (Test-Path -LiteralPath $resolvedPath -PathType Leaf) {
+ Get-Item -LiteralPath $resolvedPath
+ }
+ else {
+ throw "Path not found: $resolvedPath"
+ }
+}
+
+if (-not $targets) {
+ throw "No files found to sign."
+}
+
+foreach ($target in $targets) {
+ Write-Host "Signing $($target.FullName)"
+ $signatureArguments = @{
+ FilePath = $target.FullName
+ Certificate = $certificate
+ HashAlgorithm = "SHA256"
+ }
+
+ if (-not $NoTimestamp -and $TimestampServer) {
+ $signatureArguments.TimestampServer = $TimestampServer
+ }
+
+ $signature = Set-AuthenticodeSignature @signatureArguments
+
+ if ($signature.Status -eq "UnknownError" -and $AllowUntrustedRoot -and $signature.SignerCertificate) {
+ Write-Warning "Signed $($target.FullName), but the local machine does not trust the signing root yet."
+ continue
+ }
+
+ if ($signature.Status -ne "Valid") {
+ throw "Signing failed for $($target.FullName): $($signature.StatusMessage)"
+ }
+}
+
+Write-Host "Signed $($targets.Count) file(s)."
diff --git a/scripts/Start-MrTrustGui.ps1 b/scripts/Start-MrTrustGui.ps1
new file mode 100644
index 0000000..520455c
--- /dev/null
+++ b/scripts/Start-MrTrustGui.ps1
@@ -0,0 +1,324 @@
+[CmdletBinding()]
+param()
+
+$ErrorActionPreference = "Stop"
+
+Add-Type -AssemblyName System.Windows.Forms
+Add-Type -AssemblyName System.Drawing
+
+$script:RootPath = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
+$script:RootCertificatePath = Join-Path $script:RootPath "assets\certificates\MrSphay-LocalTrust-Root.cer"
+$script:PublisherCertificatePath = Join-Path $script:RootPath "assets\certificates\MrSphay-CodeSigning.cer"
+
+function Test-IsAdministrator {
+ $identity = [Security.Principal.WindowsIdentity]::GetCurrent()
+ $principal = [Security.Principal.WindowsPrincipal]::new($identity)
+ $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
+}
+
+function Get-MrTrustCertificate {
+ param([Parameter(Mandatory)][string]$Path)
+
+ if (-not (Test-Path -LiteralPath $Path)) {
+ throw "Certificate file not found: $Path"
+ }
+
+ [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($Path)
+}
+
+function Get-TrustScope {
+ if ($script:AllUsersCheckBox.Checked) {
+ "LocalMachine"
+ }
+ else {
+ "CurrentUser"
+ }
+}
+
+function Get-StorePath {
+ param(
+ [Parameter(Mandatory)][string]$Scope,
+ [Parameter(Mandatory)][string]$Store
+ )
+
+ "Cert:\$Scope\$Store"
+}
+
+function Test-CertificateInstalled {
+ param(
+ [Parameter(Mandatory)]$Certificate,
+ [Parameter(Mandatory)][string]$Scope,
+ [Parameter(Mandatory)][string]$Store
+ )
+
+ $storePath = Get-StorePath -Scope $Scope -Store $Store
+ @(Get-ChildItem -Path $storePath | Where-Object Thumbprint -eq $Certificate.Thumbprint).Count -gt 0
+}
+
+function Set-StatusText {
+ param([Parameter(Mandatory)][string]$Text)
+
+ $script:StatusLabel.Text = $Text
+}
+
+function Refresh-MrTrustStatus {
+ try {
+ $rootCertificate = Get-MrTrustCertificate -Path $script:RootCertificatePath
+ $publisherCertificate = Get-MrTrustCertificate -Path $script:PublisherCertificatePath
+ $scope = Get-TrustScope
+
+ $rootInstalled = Test-CertificateInstalled -Certificate $rootCertificate -Scope $scope -Store "Root"
+ $publisherInstalled = Test-CertificateInstalled -Certificate $publisherCertificate -Scope $scope -Store "TrustedPublisher"
+
+ $script:RootThumbprintLabel.Text = $rootCertificate.Thumbprint
+ $script:PublisherThumbprintLabel.Text = $publisherCertificate.Thumbprint
+ $script:ExpiryLabel.Text = $rootCertificate.NotAfter.ToString("yyyy-MM-dd")
+
+ if ($rootInstalled -and $publisherInstalled) {
+ Set-StatusText "Trusted for $scope"
+ $script:StatusPill.BackColor = [Drawing.Color]::FromArgb(28, 185, 111)
+ }
+ else {
+ Set-StatusText "Not installed for $scope"
+ $script:StatusPill.BackColor = [Drawing.Color]::FromArgb(242, 153, 74)
+ }
+ }
+ catch {
+ Set-StatusText $_.Exception.Message
+ $script:StatusPill.BackColor = [Drawing.Color]::FromArgb(235, 87, 87)
+ }
+}
+
+function Install-MrTrustCertificates {
+ $scope = Get-TrustScope
+ if ($scope -eq "LocalMachine" -and -not (Test-IsAdministrator)) {
+ [Windows.Forms.MessageBox]::Show(
+ "All-users trust requires running PowerShell as Administrator.",
+ "MrTrust",
+ [Windows.Forms.MessageBoxButtons]::OK,
+ [Windows.Forms.MessageBoxIcon]::Warning
+ ) | Out-Null
+ return
+ }
+
+ $rootCertificate = Get-MrTrustCertificate -Path $script:RootCertificatePath
+ $publisherCertificate = Get-MrTrustCertificate -Path $script:PublisherCertificatePath
+
+ $message = "Install MrSphay trust for $scope?`r`n`r`nRoot:`r`n$($rootCertificate.Thumbprint)`r`n`r`nPublisher:`r`n$($publisherCertificate.Thumbprint)`r`n`r`nOnly continue if you trust software signed by MrSphay."
+ $result = [Windows.Forms.MessageBox]::Show(
+ $message,
+ "Install MrTrust",
+ [Windows.Forms.MessageBoxButtons]::YesNo,
+ [Windows.Forms.MessageBoxIcon]::Warning
+ )
+
+ if ($result -ne [Windows.Forms.DialogResult]::Yes) {
+ return
+ }
+
+ Import-Certificate -FilePath $script:RootCertificatePath -CertStoreLocation (Get-StorePath -Scope $scope -Store "Root") | Out-Null
+ Import-Certificate -FilePath $script:PublisherCertificatePath -CertStoreLocation (Get-StorePath -Scope $scope -Store "TrustedPublisher") | Out-Null
+ Refresh-MrTrustStatus
+}
+
+function Remove-MrTrustCertificates {
+ $scope = Get-TrustScope
+ if ($scope -eq "LocalMachine" -and -not (Test-IsAdministrator)) {
+ [Windows.Forms.MessageBox]::Show(
+ "All-users removal requires running PowerShell as Administrator.",
+ "MrTrust",
+ [Windows.Forms.MessageBoxButtons]::OK,
+ [Windows.Forms.MessageBoxIcon]::Warning
+ ) | Out-Null
+ return
+ }
+
+ $rootCertificate = Get-MrTrustCertificate -Path $script:RootCertificatePath
+ $publisherCertificate = Get-MrTrustCertificate -Path $script:PublisherCertificatePath
+ $result = [Windows.Forms.MessageBox]::Show(
+ "Remove MrSphay trust for $scope?",
+ "Remove MrTrust",
+ [Windows.Forms.MessageBoxButtons]::YesNo,
+ [Windows.Forms.MessageBoxIcon]::Question
+ )
+
+ if ($result -ne [Windows.Forms.DialogResult]::Yes) {
+ return
+ }
+
+ $targets = @(
+ [pscustomobject]@{ Store = "Root"; Thumbprint = $rootCertificate.Thumbprint },
+ [pscustomobject]@{ Store = "TrustedPublisher"; Thumbprint = $publisherCertificate.Thumbprint }
+ )
+
+ foreach ($target in $targets) {
+ $storePath = Get-StorePath -Scope $scope -Store $target.Store
+ Get-ChildItem -Path $storePath |
+ Where-Object Thumbprint -eq $target.Thumbprint |
+ Remove-Item
+ }
+
+ Refresh-MrTrustStatus
+}
+
+[Windows.Forms.Application]::EnableVisualStyles()
+
+$form = [Windows.Forms.Form]::new()
+$form.Text = "MrTrust"
+$form.StartPosition = "CenterScreen"
+$form.ClientSize = [Drawing.Size]::new(760, 520)
+$form.MinimumSize = [Drawing.Size]::new(720, 500)
+$form.BackColor = [Drawing.Color]::FromArgb(22, 26, 29)
+$form.Font = [Drawing.Font]::new("Segoe UI", 10)
+
+$header = [Windows.Forms.Panel]::new()
+$header.Dock = "Top"
+$header.Height = 108
+$header.BackColor = [Drawing.Color]::FromArgb(27, 32, 35)
+$form.Controls.Add($header)
+
+$accent = [Windows.Forms.Panel]::new()
+$accent.Dock = "Left"
+$accent.Width = 8
+$accent.BackColor = [Drawing.Color]::FromArgb(28, 185, 111)
+$header.Controls.Add($accent)
+
+$title = [Windows.Forms.Label]::new()
+$title.Text = "MrTrust"
+$title.ForeColor = [Drawing.Color]::White
+$title.Font = [Drawing.Font]::new("Segoe UI", 24, [Drawing.FontStyle]::Bold)
+$title.AutoSize = $true
+$title.Location = [Drawing.Point]::new(30, 18)
+$header.Controls.Add($title)
+
+$subtitle = [Windows.Forms.Label]::new()
+$subtitle.Text = "Trust setup for MrSphay signed Windows apps"
+$subtitle.ForeColor = [Drawing.Color]::FromArgb(177, 190, 183)
+$subtitle.AutoSize = $true
+$subtitle.Location = [Drawing.Point]::new(34, 66)
+$header.Controls.Add($subtitle)
+
+$script:StatusPill = [Windows.Forms.Panel]::new()
+$script:StatusPill.Size = [Drawing.Size]::new(14, 14)
+$script:StatusPill.Location = [Drawing.Point]::new(610, 42)
+$script:StatusPill.BackColor = [Drawing.Color]::FromArgb(242, 153, 74)
+$header.Controls.Add($script:StatusPill)
+
+$script:StatusLabel = [Windows.Forms.Label]::new()
+$script:StatusLabel.Text = "Checking..."
+$script:StatusLabel.ForeColor = [Drawing.Color]::FromArgb(225, 231, 227)
+$script:StatusLabel.AutoSize = $true
+$script:StatusLabel.Location = [Drawing.Point]::new(632, 38)
+$header.Controls.Add($script:StatusLabel)
+
+$content = [Windows.Forms.Panel]::new()
+$content.Dock = "Fill"
+$content.Padding = [Windows.Forms.Padding]::new(30)
+$content.BackColor = [Drawing.Color]::FromArgb(22, 26, 29)
+$form.Controls.Add($content)
+
+$infoPanel = [Windows.Forms.Panel]::new()
+$infoPanel.BackColor = [Drawing.Color]::FromArgb(31, 37, 40)
+$infoPanel.Size = [Drawing.Size]::new(700, 210)
+$infoPanel.Location = [Drawing.Point]::new(30, 34)
+$content.Controls.Add($infoPanel)
+
+$scopeLabel = [Windows.Forms.Label]::new()
+$scopeLabel.Text = "Scope"
+$scopeLabel.ForeColor = [Drawing.Color]::FromArgb(177, 190, 183)
+$scopeLabel.Location = [Drawing.Point]::new(24, 24)
+$scopeLabel.AutoSize = $true
+$infoPanel.Controls.Add($scopeLabel)
+
+$script:AllUsersCheckBox = [Windows.Forms.CheckBox]::new()
+$script:AllUsersCheckBox.Text = "Install for all users (requires Administrator)"
+$script:AllUsersCheckBox.ForeColor = [Drawing.Color]::FromArgb(225, 231, 227)
+$script:AllUsersCheckBox.Location = [Drawing.Point]::new(24, 50)
+$script:AllUsersCheckBox.AutoSize = $true
+$script:AllUsersCheckBox.FlatStyle = "Flat"
+$script:AllUsersCheckBox.Add_CheckedChanged({ Refresh-MrTrustStatus })
+$infoPanel.Controls.Add($script:AllUsersCheckBox)
+
+$rootLabel = [Windows.Forms.Label]::new()
+$rootLabel.Text = "Root thumbprint"
+$rootLabel.ForeColor = [Drawing.Color]::FromArgb(177, 190, 183)
+$rootLabel.Location = [Drawing.Point]::new(24, 92)
+$rootLabel.AutoSize = $true
+$infoPanel.Controls.Add($rootLabel)
+
+$script:RootThumbprintLabel = [Windows.Forms.Label]::new()
+$script:RootThumbprintLabel.Text = "-"
+$script:RootThumbprintLabel.ForeColor = [Drawing.Color]::FromArgb(225, 231, 227)
+$script:RootThumbprintLabel.Font = [Drawing.Font]::new("Consolas", 9)
+$script:RootThumbprintLabel.Location = [Drawing.Point]::new(180, 92)
+$script:RootThumbprintLabel.AutoSize = $true
+$infoPanel.Controls.Add($script:RootThumbprintLabel)
+
+$publisherLabel = [Windows.Forms.Label]::new()
+$publisherLabel.Text = "Publisher thumbprint"
+$publisherLabel.ForeColor = [Drawing.Color]::FromArgb(177, 190, 183)
+$publisherLabel.Location = [Drawing.Point]::new(24, 128)
+$publisherLabel.AutoSize = $true
+$infoPanel.Controls.Add($publisherLabel)
+
+$script:PublisherThumbprintLabel = [Windows.Forms.Label]::new()
+$script:PublisherThumbprintLabel.Text = "-"
+$script:PublisherThumbprintLabel.ForeColor = [Drawing.Color]::FromArgb(225, 231, 227)
+$script:PublisherThumbprintLabel.Font = [Drawing.Font]::new("Consolas", 9)
+$script:PublisherThumbprintLabel.Location = [Drawing.Point]::new(180, 128)
+$script:PublisherThumbprintLabel.AutoSize = $true
+$infoPanel.Controls.Add($script:PublisherThumbprintLabel)
+
+$expiryLabelTitle = [Windows.Forms.Label]::new()
+$expiryLabelTitle.Text = "Expires"
+$expiryLabelTitle.ForeColor = [Drawing.Color]::FromArgb(177, 190, 183)
+$expiryLabelTitle.Location = [Drawing.Point]::new(24, 164)
+$expiryLabelTitle.AutoSize = $true
+$infoPanel.Controls.Add($expiryLabelTitle)
+
+$script:ExpiryLabel = [Windows.Forms.Label]::new()
+$script:ExpiryLabel.Text = "-"
+$script:ExpiryLabel.ForeColor = [Drawing.Color]::FromArgb(225, 231, 227)
+$script:ExpiryLabel.Location = [Drawing.Point]::new(180, 164)
+$script:ExpiryLabel.AutoSize = $true
+$infoPanel.Controls.Add($script:ExpiryLabel)
+
+$installButton = [Windows.Forms.Button]::new()
+$installButton.Text = "Install trust"
+$installButton.BackColor = [Drawing.Color]::FromArgb(28, 185, 111)
+$installButton.ForeColor = [Drawing.Color]::White
+$installButton.FlatStyle = "Flat"
+$installButton.Size = [Drawing.Size]::new(180, 46)
+$installButton.Location = [Drawing.Point]::new(30, 274)
+$installButton.Add_Click({ Install-MrTrustCertificates })
+$content.Controls.Add($installButton)
+
+$removeButton = [Windows.Forms.Button]::new()
+$removeButton.Text = "Remove trust"
+$removeButton.BackColor = [Drawing.Color]::FromArgb(44, 52, 56)
+$removeButton.ForeColor = [Drawing.Color]::FromArgb(225, 231, 227)
+$removeButton.FlatStyle = "Flat"
+$removeButton.Size = [Drawing.Size]::new(180, 46)
+$removeButton.Location = [Drawing.Point]::new(230, 274)
+$removeButton.Add_Click({ Remove-MrTrustCertificates })
+$content.Controls.Add($removeButton)
+
+$refreshButton = [Windows.Forms.Button]::new()
+$refreshButton.Text = "Refresh"
+$refreshButton.BackColor = [Drawing.Color]::FromArgb(44, 52, 56)
+$refreshButton.ForeColor = [Drawing.Color]::FromArgb(225, 231, 227)
+$refreshButton.FlatStyle = "Flat"
+$refreshButton.Size = [Drawing.Size]::new(140, 46)
+$refreshButton.Location = [Drawing.Point]::new(430, 274)
+$refreshButton.Add_Click({ Refresh-MrTrustStatus })
+$content.Controls.Add($refreshButton)
+
+$note = [Windows.Forms.Label]::new()
+$note.Text = "MrTrust installs public certificates only. It does not disable Defender, SmartScreen, UAC, or enterprise policies."
+$note.ForeColor = [Drawing.Color]::FromArgb(177, 190, 183)
+$note.Location = [Drawing.Point]::new(30, 352)
+$note.Size = [Drawing.Size]::new(700, 48)
+$content.Controls.Add($note)
+
+$form.Add_Shown({ Refresh-MrTrustStatus })
+[Windows.Forms.Application]::Run($form)
diff --git a/scripts/Uninstall-MrTrust.ps1 b/scripts/Uninstall-MrTrust.ps1
new file mode 100644
index 0000000..76334af
--- /dev/null
+++ b/scripts/Uninstall-MrTrust.ps1
@@ -0,0 +1,87 @@
+[CmdletBinding(SupportsShouldProcess)]
+param(
+ [string]$CertificatePath = ".\assets\certificates\MrSphay-LocalTrust-Root.cer",
+ [string]$PublisherCertificatePath = ".\assets\certificates\MrSphay-CodeSigning.cer",
+ [ValidateSet("CurrentUser", "LocalMachine")]
+ [string]$Scope = "CurrentUser",
+ [switch]$Force
+)
+
+$ErrorActionPreference = "Stop"
+
+function Resolve-FullPath {
+ param([Parameter(Mandatory)][string]$Path)
+
+ $executionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)
+}
+
+function Test-IsAdministrator {
+ $identity = [Security.Principal.WindowsIdentity]::GetCurrent()
+ $principal = [Security.Principal.WindowsPrincipal]::new($identity)
+ $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
+}
+
+if ($Scope -eq "LocalMachine" -and -not (Test-IsAdministrator)) {
+ throw "LocalMachine removal requires an elevated PowerShell session. Use -Scope CurrentUser or run as Administrator."
+}
+
+$resolvedCertificatePath = Resolve-FullPath $CertificatePath
+if (-not (Test-Path -LiteralPath $resolvedCertificatePath)) {
+ throw "Certificate file not found: $resolvedCertificatePath. Provide -CertificatePath to the public MrTrust certificate."
+}
+
+$rootCertificate = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($resolvedCertificatePath)
+if (-not $rootCertificate.Subject.StartsWith("CN=MrSphay", [System.StringComparison]::OrdinalIgnoreCase)) {
+ throw "Refusing to remove using an unexpected root certificate subject: $($rootCertificate.Subject)"
+}
+
+$resolvedPublisherCertificatePath = Resolve-FullPath $PublisherCertificatePath
+$publisherCertificate = $null
+if (Test-Path -LiteralPath $resolvedPublisherCertificatePath) {
+ $publisherCertificate = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($resolvedPublisherCertificatePath)
+ if (-not $publisherCertificate.Subject.StartsWith("CN=MrSphay", [System.StringComparison]::OrdinalIgnoreCase)) {
+ throw "Refusing to remove using an unexpected publisher certificate subject: $($publisherCertificate.Subject)"
+ }
+}
+
+$targets = @(
+ [pscustomobject]@{
+ Store = "Cert:\$Scope\Root"
+ Thumbprint = $rootCertificate.Thumbprint
+ }
+)
+
+if ($publisherCertificate) {
+ $targets += [pscustomobject]@{
+ Store = "Cert:\$Scope\TrustedPublisher"
+ Thumbprint = $publisherCertificate.Thumbprint
+ }
+}
+
+Write-Host "MrTrust will remove this certificate from scope '$Scope':"
+Write-Host " Root subject: $($rootCertificate.Subject)"
+Write-Host " Root thumbprint: $($rootCertificate.Thumbprint)"
+if ($publisherCertificate) {
+ Write-Host " Publisher subject: $($publisherCertificate.Subject)"
+ Write-Host " Publisher thumbprint: $($publisherCertificate.Thumbprint)"
+}
+Write-Host ""
+
+if (-not $Force) {
+ $answer = Read-Host "Type REMOVE to continue"
+ if ($answer -cne "REMOVE") {
+ Write-Host "Removal cancelled."
+ exit 1
+ }
+}
+
+foreach ($target in $targets) {
+ $matchingCertificates = Get-ChildItem -Path $target.Store | Where-Object Thumbprint -eq $target.Thumbprint
+ foreach ($matchingCertificate in $matchingCertificates) {
+ if ($PSCmdlet.ShouldProcess($target.Store, "Remove MrTrust certificate $($matchingCertificate.Thumbprint)")) {
+ Remove-Item -LiteralPath $matchingCertificate.PSPath
+ }
+ }
+}
+
+Write-Host "MrTrust certificate removed where present."
diff --git a/src/MrTrustLauncher.cs b/src/MrTrustLauncher.cs
new file mode 100644
index 0000000..f8ad4d4
--- /dev/null
+++ b/src/MrTrustLauncher.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Windows.Forms;
+
+namespace MrTrust
+{
+ internal static class MrTrustLauncher
+ {
+ [STAThread]
+ private static int Main()
+ {
+ string baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
+ string scriptPath = Path.Combine(baseDirectory, "MrTrust.ps1");
+
+ if (!File.Exists(scriptPath))
+ {
+ MessageBox.Show(
+ "MrTrust.ps1 was not found next to MrTrust.exe.",
+ "MrTrust",
+ MessageBoxButtons.OK,
+ MessageBoxIcon.Error);
+ return 1;
+ }
+
+ try
+ {
+ ProcessStartInfo startInfo = new ProcessStartInfo
+ {
+ FileName = "powershell.exe",
+ Arguments = "-NoProfile -ExecutionPolicy Bypass -File \"" + scriptPath + "\" gui",
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ WorkingDirectory = baseDirectory
+ };
+
+ using (Process process = Process.Start(startInfo))
+ {
+ if (process == null)
+ {
+ throw new InvalidOperationException("PowerShell could not be started.");
+ }
+ }
+
+ return 0;
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show(
+ ex.Message,
+ "MrTrust",
+ MessageBoxButtons.OK,
+ MessageBoxIcon.Error);
+ return 1;
+ }
+ }
+ }
+}