diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..00bd5c1 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,7 @@ +# Release and trust policy changes require maintainer review. +* @OneDeadMachine-Dev +/.github/workflows/ @OneDeadMachine-Dev +/.signpath/ @OneDeadMachine-Dev +/CODE_SIGNING_POLICY.md @OneDeadMachine-Dev +/PRIVACY.md @OneDeadMachine-Dev +/SECURITY.md @OneDeadMachine-Dev diff --git a/.github/ISSUE_TEMPLATE/beta-report.yml b/.github/ISSUE_TEMPLATE/beta-report.yml new file mode 100644 index 0000000..d0aaacd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/beta-report.yml @@ -0,0 +1,65 @@ +name: Beta report / Отчёт beta-тестирования +description: Report a W-Fix beta result without publishing secrets or organizational data. +title: "[Beta]: " +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + Спасибо за тестирование W-Fix. Не публикуйте пароли, реальные доменные имена, IP-адреса и необработанные логи. Прикладывайте только обезличенный support bundle. + + Thank you for testing W-Fix. Do not publish passwords, real domain names, IP addresses, or raw logs. Attach only a sanitized support bundle. + - type: input + id: version + attributes: + label: W-Fix version / Версия W-Fix + placeholder: v3.0.0-beta.1 + validations: + required: true + - type: dropdown + id: windows + attributes: + label: Windows + options: + - Windows 10 + - Windows 11 23H2 or older + - Windows 11 24H2 or newer + - Mixed Windows 10/11 + validations: + required: true + - type: dropdown + id: topology + attributes: + label: Network / Сеть + options: + - Local repair / Локальный ремонт + - Active Directory domain + - Workgroup / Одноранговая сеть + validations: + required: true + - type: textarea + id: scenario + attributes: + label: Scenario / Сценарий + description: What was broken and what repair was selected? + validations: + required: true + - type: textarea + id: result + attributes: + label: Result / Результат + description: What succeeded, failed, rolled back, or required a reboot? + validations: + required: true + - type: textarea + id: bundle + attributes: + label: Sanitized support bundle / Обезличенный support bundle + description: Drag the ZIP created by W-Fix here. This is optional. + - type: checkboxes + id: privacy + attributes: + label: Privacy confirmation / Проверка конфиденциальности + options: + - label: I verified that this report contains no passwords, document contents, real domain names, or other sensitive organizational data. + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..0b3f45e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: true +contact_links: + - name: Security vulnerability / Уязвимость + url: https://github.com/OneDeadMachine-Dev/W-FIX/security/advisories/new + about: Report security issues privately. Сообщайте об уязвимостях приватно. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..d649d40 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,197 @@ +name: Release + +on: + push: + tags: + - "v*" + +permissions: + actions: read + contents: write + +jobs: + build-sign-release: + runs-on: windows-latest + environment: release + timeout-minutes: 45 + + steps: + - name: Checkout tagged source + uses: actions/checkout@v7 + + - name: Setup .NET 8 + uses: actions/setup-dotnet@v6 + with: + dotnet-version: 8.0.x + + - name: Resolve release metadata + id: metadata + shell: pwsh + run: | + $tag = '${{ github.ref_name }}' + if ($tag -notmatch '^v(?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$') { + throw "Unsupported release tag: $tag" + } + $notes = "docs/releases/$tag.md" + if (-not (Test-Path -LiteralPath $notes)) { + throw "Bilingual release notes are required: $notes" + } + "tag=$tag" >> $env:GITHUB_OUTPUT + "version=$($Matches.version)" >> $env:GITHUB_OUTPUT + "notes=$notes" >> $env:GITHUB_OUTPUT + "prerelease=$($Matches.version.Contains('-').ToString().ToLowerInvariant())" >> $env:GITHUB_OUTPUT + + - name: Restore + run: dotnet restore W-Fix.sln + + - name: Strict Release build + shell: pwsh + run: | + dotnet build W-Fix.sln --configuration Release --no-restore -warnaserror + dotnet build tools/W-Fix.CatalogSigner/W-Fix.CatalogSigner.csproj --configuration Release -warnaserror + + - name: Test + run: dotnet test W-Fix.sln --configuration Release --no-build --logger "console;verbosity=minimal" + + - name: NuGet vulnerability report + run: dotnet list W-Fix.sln package --vulnerable --include-transitive + + - name: Publish portable win-x64 executable + run: >- + dotnet publish src/W-Fix.App/W-Fix.App.csproj + --configuration Release + --runtime win-x64 + --self-contained true + --no-restore + -p:Version=${{ steps.metadata.outputs.version }} + -p:PublishSingleFile=true + -p:IncludeAllContentForSelfExtract=true + -p:EnableCompressionInSingleFile=true + -o artifacts/publish/win-x64 + + - name: Upload unsigned artifact for origin verification + id: upload-unsigned + uses: actions/upload-artifact@v7 + with: + name: unsigned-w-fix-${{ steps.metadata.outputs.tag }} + path: artifacts/publish/win-x64/W-Fix.exe + if-no-files-found: error + retention-days: 14 + + - name: Detect SignPath configuration + id: signpath-config + shell: pwsh + env: + SIGNPATH_API_TOKEN: ${{ secrets.SIGNPATH_API_TOKEN }} + SIGNPATH_ORGANIZATION_ID: ${{ vars.SIGNPATH_ORGANIZATION_ID }} + SIGNPATH_PROJECT_SLUG: ${{ vars.SIGNPATH_PROJECT_SLUG }} + SIGNPATH_SIGNING_POLICY_SLUG: ${{ vars.SIGNPATH_SIGNING_POLICY_SLUG }} + SIGNPATH_ARTIFACT_CONFIGURATION_SLUG: ${{ vars.SIGNPATH_ARTIFACT_CONFIGURATION_SLUG }} + run: | + $values = @( + $env:SIGNPATH_API_TOKEN, + $env:SIGNPATH_ORGANIZATION_ID, + $env:SIGNPATH_PROJECT_SLUG, + $env:SIGNPATH_SIGNING_POLICY_SLUG, + $env:SIGNPATH_ARTIFACT_CONFIGURATION_SLUG + ) + $enabled = -not ($values | Where-Object { [string]::IsNullOrWhiteSpace($_) }) + "enabled=$($enabled.ToString().ToLowerInvariant())" >> $env:GITHUB_OUTPUT + + - name: Submit release-signing request to SignPath + if: steps.signpath-config.outputs.enabled == 'true' + uses: signpath/github-action-submit-signing-request@v2 + with: + api-token: ${{ secrets.SIGNPATH_API_TOKEN }} + organization-id: ${{ vars.SIGNPATH_ORGANIZATION_ID }} + project-slug: ${{ vars.SIGNPATH_PROJECT_SLUG }} + signing-policy-slug: ${{ vars.SIGNPATH_SIGNING_POLICY_SLUG }} + artifact-configuration-slug: ${{ vars.SIGNPATH_ARTIFACT_CONFIGURATION_SLUG }} + github-artifact-id: ${{ steps.upload-unsigned.outputs.artifact-id }} + wait-for-completion: true + output-artifact-directory: artifacts/signed + parameters: | + version: "${{ steps.metadata.outputs.version }}" + + - name: Prepare signed or explicitly unsigned executable + id: executable + shell: pwsh + run: | + $signingEnabled = '${{ steps.signpath-config.outputs.enabled }}' -eq 'true' + $source = 'artifacts/publish/win-x64/W-Fix.exe' + if ($signingEnabled) { + $signed = @(Get-ChildItem -LiteralPath 'artifacts/signed' -Recurse -File -Filter 'W-Fix.exe') + if ($signed.Count -ne 1) { + throw "Expected exactly one signed W-Fix.exe, found $($signed.Count)." + } + $source = $signed[0].FullName + $signature = Get-AuthenticodeSignature -LiteralPath $source + if ($signature.Status -ne 'Valid' -or $null -eq $signature.TimeStamperCertificate) { + throw "Authenticode or timestamp verification failed: $($signature.Status)" + } + } else { + Write-Warning 'SignPath is not configured. This release will be explicitly published as unsigned.' + } + New-Item -ItemType Directory -Force -Path 'artifacts/release' | Out-Null + $name = "W-Fix-${{ steps.metadata.outputs.tag }}-win-x64.exe" + Copy-Item -LiteralPath $source -Destination "artifacts/release/$name" + "name=$name" >> $env:GITHUB_OUTPUT + "signed=$($signingEnabled.ToString().ToLowerInvariant())" >> $env:GITHUB_OUTPUT + + - name: Sign and verify known-issues catalog + shell: pwsh + env: + KNOWN_ISSUES_PRIVATE_KEY_PEM: ${{ secrets.KNOWN_ISSUES_PRIVATE_KEY_PEM }} + run: | + if ([string]::IsNullOrWhiteSpace($env:KNOWN_ISSUES_PRIVATE_KEY_PEM)) { + throw 'KNOWN_ISSUES_PRIVATE_KEY_PEM is required for a release.' + } + $keyPath = Join-Path $env:RUNNER_TEMP 'known-issues-private.pem' + [IO.File]::WriteAllText($keyPath, $env:KNOWN_ISSUES_PRIVATE_KEY_PEM, [Text.UTF8Encoding]::new($false)) + Copy-Item 'src/W-Fix.Core/Catalog/known-issues.json' 'artifacts/release/known-issues.json' + dotnet run --project tools/W-Fix.CatalogSigner --configuration Release --no-build -- sign ` + artifacts/release/known-issues.json $keyPath artifacts/release/known-issues.json.sig + if ($LASTEXITCODE -ne 0) { throw 'Catalog signing failed.' } + dotnet run --project tools/W-Fix.CatalogSigner --configuration Release --no-build -- verify ` + artifacts/release/known-issues.json src/W-Fix.Core/Catalog/known-issues-public.pem artifacts/release/known-issues.json.sig + if ($LASTEXITCODE -ne 0) { throw 'Catalog signature verification failed.' } + Remove-Item -LiteralPath $keyPath -Force + + - name: Package and calculate checksums + shell: pwsh + run: | + $exe = 'artifacts/release/${{ steps.executable.outputs.name }}' + $zip = "artifacts/release/W-Fix-${{ steps.metadata.outputs.tag }}-win-x64.zip" + Compress-Archive -LiteralPath $exe -DestinationPath $zip -CompressionLevel Optimal + $files = @($exe, $zip, 'artifacts/release/known-issues.json', 'artifacts/release/known-issues.json.sig') + $lines = foreach ($file in $files) { + $hash = Get-FileHash -Algorithm SHA256 -LiteralPath $file + "$($hash.Hash.ToLowerInvariant()) $([IO.Path]::GetFileName($file))" + } + [IO.File]::WriteAllLines('artifacts/release/SHA256SUMS.txt', $lines, [Text.UTF8Encoding]::new($false)) + + - name: Upload final release artifact + uses: actions/upload-artifact@v7 + with: + name: release-${{ steps.metadata.outputs.tag }} + path: artifacts/release/* + if-no-files-found: error + retention-days: 90 + + - name: Publish GitHub Release + shell: pwsh + env: + GH_TOKEN: ${{ github.token }} + run: | + $arguments = @( + 'release', 'create', '${{ steps.metadata.outputs.tag }}', + '--verify-tag', + '--title', "W-Fix ${{ steps.metadata.outputs.version }}", + '--notes-file', '${{ steps.metadata.outputs.notes }}' + ) + if ('${{ steps.metadata.outputs.prerelease }}' -eq 'true') { + $arguments += '--prerelease' + } + $arguments += @(Get-ChildItem -LiteralPath 'artifacts/release' -File | Select-Object -ExpandProperty FullName) + & gh @arguments + if ($LASTEXITCODE -ne 0) { throw 'GitHub Release publication failed.' } diff --git a/CODE_SIGNING_POLICY.md b/CODE_SIGNING_POLICY.md new file mode 100644 index 0000000..3a82766 --- /dev/null +++ b/CODE_SIGNING_POLICY.md @@ -0,0 +1,30 @@ +# Code signing policy / Политика подписи кода + +Free code signing provided by [SignPath.io](https://signpath.io/), certificate by [SignPath Foundation](https://signpath.org/) — после одобрения проекта SignPath Foundation. + +## Roles / Роли + +- Committer and reviewer: [OneDeadMachine-Dev](https://github.com/OneDeadMachine-Dev) +- Signing approver: [OneDeadMachine-Dev](https://github.com/OneDeadMachine-Dev) + +## Release policy / Политика выпуска + +- Публичные binaries собираются только GitHub Actions из защищённой ветки `main` и опубликованного тега `v*`. +- Release workflow повторно выполняет restore, строгую Release-сборку, тесты и проверку зависимостей. +- Неподписанный artifact загружается в GitHub Actions до передачи в SignPath; SignPath проверяет происхождение сборки. +- Release signing требует ручного подтверждения. После подписи workflow проверяет Authenticode и timestamp, затем рассчитывает SHA-256. +- ZIP всегда создаётся из уже подписанного EXE. +- Detached ECDSA signature каталога известных проблем не заменяет Authenticode и управляется отдельным ключом. +- Закрытые ключи и API tokens находятся только в защищённых secret stores и не записываются в GitHub Release или логи. + +- Public binaries are built only by GitHub Actions from protected `main` and a published `v*` tag. +- The release workflow repeats restore, strict Release build, tests, and dependency checks. +- The unsigned artifact is uploaded to GitHub Actions before submission so SignPath can verify build origin. +- Release signing requires manual approval. The workflow then verifies Authenticode and timestamp before calculating SHA-256. +- ZIP archives are always created from the signed EXE. +- The known-issues catalog ECDSA signature is independent from Authenticode and uses a separate key. +- Private keys and API tokens exist only in protected secret stores and are never written to releases or logs. + +Until SignPath approval, public beta releases remain explicitly marked unsigned and include SHA-256 checksums. For internal testing, a separately documented self-signed certificate may be trusted manually on managed test computers; it is never presented as publicly trusted. + +See also [Privacy Policy](PRIVACY.md) and [Security Policy](SECURITY.md). diff --git a/PRIVACY.md b/PRIVACY.md new file mode 100644 index 0000000..7f4c773 --- /dev/null +++ b/PRIVACY.md @@ -0,0 +1,27 @@ +# Политика конфиденциальности / Privacy Policy + +## Русский + +W-Fix не содержит телеметрии, рекламы и автоматической отправки отчётов. Приложение работает локально или с компьютерами, которые явно выбрал оператор. + +- Логи хранятся локально в `%LocalAppData%\W-Fix\Logs` и могут содержать имя локального компьютера и пользователя. +- Отчёты о ремонте хранятся в `%ProgramData%\W-Fix\Runs` и могут содержать имена выбранных компьютеров, принтеров, драйверов и результаты действий. +- Обезличенный support bundle создаётся только по команде пользователя. Имена компьютеров и принтеров заменяются псевдонимами; пароли, ссылки Credential Manager, пути снимков и содержимое документов печати не экспортируются. +- Альтернативные учётные данные сохраняются только после явного согласия в Windows Credential Manager. Пароли не записываются в конфигурацию, аргументы процессов, логи и отчёты. +- При диагностике W-Fix может загрузить декларативный каталог известных проблем с официального GitHub Release проекта. Загруженный каталог проверяется цифровой подписью и не может содержать исполняемый код. +- W-Fix не передаёт сведения третьим лицам, если пользователь сам не экспортировал и не отправил отчёт или support bundle. + +Сообщить о проблеме конфиденциальности можно через [Private vulnerability reporting](https://github.com/OneDeadMachine-Dev/W-FIX/security/advisories/new). + +## English + +W-Fix contains no telemetry, advertising, or automatic report uploads. It operates locally or against computers explicitly selected by the operator. + +- Logs are stored locally in `%LocalAppData%\W-Fix\Logs` and may contain the local computer and user names. +- Repair reports are stored in `%ProgramData%\W-Fix\Runs` and may contain selected computer, printer, and driver names plus action results. +- A sanitized support bundle is created only on explicit request. Computer and printer names are replaced with aliases; passwords, Credential Manager references, snapshot paths, and print-document contents are excluded. +- Alternate credentials are saved only with explicit consent in Windows Credential Manager. Passwords are never written to configuration, process arguments, logs, or reports. +- During diagnostics W-Fix may download a declarative known-issues catalog from the project's official GitHub Release. The catalog is signature-verified and cannot contain executable code. +- W-Fix sends no information to third parties unless the user explicitly exports and shares a report or support bundle. + +Report a privacy concern through [Private vulnerability reporting](https://github.com/OneDeadMachine-Dev/W-FIX/security/advisories/new). diff --git a/README.md b/README.md index 20b6850..9dad730 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ [![Platform](https://img.shields.io/badge/platform-Windows%2010%2F11-blue?logo=windows)](https://www.microsoft.com/windows) [![.NET](https://img.shields.io/badge/.NET-8.0-purple?logo=dotnet)](https://dotnet.microsoft.com/) -[![Release](https://img.shields.io/badge/version-3.0.0--beta.1-blue)](https://github.com/OneDeadMachine/W-Fix/releases) +[![Release](https://img.shields.io/badge/version-3.0.0--beta.1-blue)](https://github.com/OneDeadMachine-Dev/W-FIX/releases) [![License](https://img.shields.io/badge/license-MIT-orange)](LICENSE) [![Author](https://img.shields.io/badge/author-OneDeadMachine-red)](https://github.com/OneDeadMachine) @@ -58,7 +58,7 @@ ## 🚀 Быстрый старт -1. Скачай `W-Fix.exe` из раздела [Releases](https://github.com/OneDeadMachine/W-Fix/releases) +1. Скачай `W-Fix.exe` из раздела [Releases](https://github.com/OneDeadMachine-Dev/W-FIX/releases) 2. Запусти **от имени администратора** (правой кнопкой → «Запуск от имени администратора») 3. Выбери принтер в левой панели 4. Выбери фиксер в правой панели → нажми **«Применить»** @@ -82,7 +82,7 @@ ### Debug-запуск ```powershell -git clone https://github.com/OneDeadMachine/W-Fix.git +git clone https://github.com/OneDeadMachine-Dev/W-FIX.git cd W-Fix dotnet run --project src/W-Fix.App ``` diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..ec3d991 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,22 @@ +# Безопасность / Security + +## Поддерживаемые версии / Supported versions + +Security fixes are provided for the latest stable release and the current public beta. Older releases should be upgraded before reporting a vulnerability. + +## Сообщение об уязвимости / Reporting a vulnerability + +Не публикуйте уязвимости, секреты, доменные имена и журналы организации в обычных GitHub Issues. Используйте [Private vulnerability reporting](https://github.com/OneDeadMachine-Dev/W-FIX/security/advisories/new). + +Do not disclose vulnerabilities, secrets, domain names, or organizational logs in public GitHub Issues. Use [Private vulnerability reporting](https://github.com/OneDeadMachine-Dev/W-FIX/security/advisories/new). + +В отчёте укажите версию W-Fix, версию Windows, ожидаемое и фактическое поведение и минимальные шаги воспроизведения. Прикладывайте только обезличенный support bundle. Мы подтвердим получение через GitHub Security Advisory и не будем просить пароль или содержимое документов печати. + +Include the W-Fix version, Windows version, expected and actual behavior, and minimal reproduction steps. Attach only a sanitized support bundle. We will acknowledge the report through GitHub Security Advisories and will never request a password or print-document contents. + +## Границы доверия / Trust boundaries + +- W-Fix требует административных прав, потому что изменяет службы, реестр, политики печати и драйверы. +- Remote Center выполняет действия только на явно выбранных целях. +- Загружаемый known-issues catalog декларативен, подписан ECDSA и ссылается только на встроенные действия. +- Private keys, GitHub tokens and SignPath tokens must never be committed to the repository. diff --git a/docs/releases/v3.0.0.md b/docs/releases/v3.0.0.md new file mode 100644 index 0000000..f707b50 --- /dev/null +++ b/docs/releases/v3.0.0.md @@ -0,0 +1,11 @@ +## Русский + +W-Fix v3.0 — стабильный выпуск Remote Repair Center для доменных Windows 10/11. Он сохраняет 12 локальных фиксеров и добавляет удалённый preflight, инвентаризацию, доказательную диагностику, планирование реального ремонта, снимки, проверку результата, rollback, пакетное выполнение и обезличенные support bundles. + +Стабильный тег разрешено создавать только после лабораторной проверки Windows 10/11 и закрытия критических дефектов текущей beta. Актуальные требования и ограничения описаны в `docs/remote-center.md`. + +## English + +W-Fix v3.0 is the stable Remote Repair Center release for domain-joined Windows 10/11 machines. It retains all 12 local fixers and adds remote preflight, inventory, evidence-based diagnostics, real repair planning, snapshots, verification, rollback, batch execution, and sanitized support bundles. + +The stable tag may be created only after Windows 10/11 lab validation and closure of all critical beta defects. Current requirements and limitations are documented in `docs/remote-center.md`.