Add MrTrust GUI and Gitea release build
Some checks failed
Build MrTrust / build-windows (push) Has been cancelled

This commit is contained in:
MrSphay
2026-05-15 23:47:10 +02:00
parent 7d4e9759e6
commit b58b6358f4
20 changed files with 1179 additions and 403 deletions

View File

@@ -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

9
.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
private/
dist/
*.pfx
*.snk
*.log
bin/
obj/
.vs/
.vscode/

View File

@@ -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`. - Do not add Defender, SmartScreen, UAC, firewall, or policy bypasses.
- If the working tree is clean, run `git pull --ff-only` before editing. - Do not add silent certificate installation.
- If local changes exist, preserve them and do not overwrite user work. - Do not commit `.pfx`, private keys, passwords, tokens, or signing secrets.
- Conserve context tokens: use `rg`, targeted file reads, and short summaries instead of loading unrelated files or long logs. - 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: - `scripts/` contains the PowerShell implementation.
- `assets/certificates/` contains public certificates only.
- `files/` contains templates copied into target repositories. - `private/` is ignored and may contain local signing material.
- `agent-quickstart.md`, `new-repository.md`, and `existing-project.md` are agent workflows. - `docs/integration-prompt.md` is the prompt for adding MrTrust to other projects.
- `manifest.json` is the source of truth for copy targets and placeholders. - `docs/security-model.md` documents the intended behavior and limits.
- `profiles/` contains stack-specific guidance. - `MrTrust.ps1 gui` is the user-facing GUI entry point.
## 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.
## Verification ## Verification
Before committing: Before finishing changes, run:
```powershell ```powershell
Get-Content manifest.json | ConvertFrom-Json | Out-Null $scripts = Get-ChildItem .\scripts -Filter *.ps1
Get-Content manifest.schema.json | ConvertFrom-Json | Out-Null foreach ($script in $scripts) {
Get-Content files\blueprint.json | ConvertFrom-Json | Out-Null $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 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.

View File

@@ -1,41 +1,9 @@
# Changelog # Changelog
All notable changes to the Codex Agent Repository Kit are documented here. ## 0.1.0
## 1.0.5 - 2026-05-15 - Added MrTrust certificate generation, installation, removal, and signing scripts.
- Added a simple Windows GUI for installing and removing MrTrust.
- Restored the rainbow section divider theme in the human-facing `README.md`. - Added a Windows launcher EXE and Gitea Runner workflow for release ZIP builds.
- Added separate minimal permission guidance for `REGISTRY_TOKEN` and `GITEA_TOKEN`. - Added integration prompt for other Windows projects.
- Clarified where package-only and API-capable tokens should be used. - Added security model documentation.
## 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.

52
MrTrust.ps1 Normal file
View File

@@ -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"
}
}

389
README.md
View File

@@ -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:
<p align="center"><img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" alt="-----------------------------------------------------" width="100%"></p> 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. ## What It Contains
- 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.
<p align="center"><img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" alt="-----------------------------------------------------" width="100%"></p> - `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. Create the certificates:
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.
<p align="center"><img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" alt="-----------------------------------------------------" width="100%"></p>
## SSH Setup
Generate a key if you do not already have one:
```powershell ```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 ```powershell
Start-Service ssh-agent .\MrTrust.ps1 install
ssh-add $env:USERPROFILE\.ssh\id_ed25519
``` ```
Show the public key: Open the GUI:
```powershell ```powershell
Get-Content $env:USERPROFILE\.ssh\id_ed25519.pub .\MrTrust.ps1 gui
``` ```
Add that public key in Gitea: Sign another project build:
```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
```
<p align="center"><img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" alt="-----------------------------------------------------" width="100%"></p>
## 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.
```
<p align="center"><img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" alt="-----------------------------------------------------" width="100%"></p>
## 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` |
<p align="center"><img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" alt="-----------------------------------------------------" width="100%"></p>
## 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`.
<p align="center"><img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" alt="-----------------------------------------------------" width="100%"></p>
## 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.
<p align="center"><img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" alt="-----------------------------------------------------" width="100%"></p>
## 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.
<p align="center"><img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" alt="-----------------------------------------------------" width="100%"></p>
## Setting Local Tokens
Set a local token for Codex or shell-based API work.
Current PowerShell session:
```powershell ```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 ```powershell
setx GITEA_TOKEN "paste-token-here" .\MrTrust.ps1 uninstall
``` ```
Open a new terminal after `setx`. Build a user-facing ZIP release:
Test repository API access:
```powershell ```powershell
$headers = @{ Authorization = "token $env:GITEA_TOKEN" } .\scripts\New-MrTrustRelease.ps1 -Version 0.1.0
Invoke-RestMethod `
-Uri "https://git.wilkensxl.de/api/v1/repos/REPOSITORY_OWNER/REPOSITORY_NAME" `
-Headers $headers
``` ```
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 ```powershell
Invoke-RestMethod ` .\MrTrust.ps1 gui
-Uri "https://git.wilkensxl.de/api/v1/repos/REPOSITORY_OWNER/REPOSITORY_NAME/issues?state=open&limit=1" `
-Headers $headers
``` ```
<p align="center"><img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" alt="-----------------------------------------------------" width="100%"></p> By default, MrTrust installs trust only for the current Windows user:
## Setting Repository Secrets
In Gitea:
```text ```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 ```powershell
REGISTRY_TOKEN .\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 Yes. Give another agent this repository URL and the target Windows project, then paste `docs/integration-prompt.md`.
GITEA_TOKEN
```
Keep package publishing and release or issue automation separate when possible. It makes permission reviews easier. Both sides have to be wired:
<p align="center"><img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" alt="-----------------------------------------------------" width="100%"></p> - 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, ## Recommended Project Integration
- copies them to URL-safe filenames,
- uploads immutable versioned packages,
- updates a stable `latest` package path.
The workflow uses: 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.
```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`.
<p align="center"><img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" alt="-----------------------------------------------------" width="100%"></p>
## 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.
<p align="center"><img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" alt="-----------------------------------------------------" width="100%"></p>
## 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.
<p align="center"><img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" alt="-----------------------------------------------------" width="100%"></p>
## 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.
```

Binary file not shown.

Binary file not shown.

View File

@@ -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.

View File

@@ -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

View File

@@ -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 <artifact-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.
```

40
docs/security-model.md Normal file
View File

@@ -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.

View File

@@ -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"

View File

@@ -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"

View File

@@ -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."
}

View File

@@ -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"

View File

@@ -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)."

View File

@@ -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)

View File

@@ -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."

58
src/MrTrustLauncher.cs Normal file
View File

@@ -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;
}
}
}
}