diff --git a/.cursor/rules/re-measure-wording.mdc b/.cursor/rules/re-measure-wording.mdc deleted file mode 100644 index 75334321f..000000000 --- a/.cursor/rules/re-measure-wording.mdc +++ /dev/null @@ -1,10 +0,0 @@ ---- -description: Use hyphenated re-measure; never remasure or remeasure -alwaysApply: true ---- - -# Wording: re-measure - -When writing about measuring again (RPS, memory, UI layout), use the hyphenated form **re-measure** (and **re-measurement**). - -Never write `remasure` or `remeasure` in code, comments, docs, wiki, the website, commit messages, or plans. diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml index 7f77d39b9..aa92927c6 100644 --- a/.github/workflows/dotnetcore.yml +++ b/.github/workflows/dotnetcore.yml @@ -78,7 +78,9 @@ jobs: run: dotnet build src/Titanium.Web.Proxy.sln --configuration Release --no-restore --warnaserror - name: Begin SonarCloud analysis + id: sonar_begin if: env.SONAR_TOKEN != '' + continue-on-error: true shell: pwsh run: > .\.sonar\scanner\dotnet-sonarscanner begin @@ -87,10 +89,10 @@ jobs: /d:sonar.token="$env:SONAR_TOKEN" /d:sonar.cs.vscoveragexml.reportsPaths="coverage/coverage.xml" /d:sonar.exclusions="**/docs/**,**/examples/**,**/benchmarks/**,**/tools/**,**/*.axaml,**/website/**,**/.github/**" - /d:sonar.coverage.exclusions="**/examples/**,**/benchmarks/**,**/docs/**,**/Http3/Http3OriginBridge.cs,**/Http3/Http3OriginClientSession.cs,**/Titanium.Plus/Dashboard/**,**/Titanium.Inspector/Views/**,**/Titanium.Inspector/Services/AppContainerLoopback.cs,**/Titanium.Inspector/App.axaml.cs,**/Titanium.Inspector/Program.cs,**/Titanium.Inspector/InspectorAppFactory.cs,**/Titanium.Inspector/Services/UpdateService.cs,**/Titanium.Cli/Program.cs,**/Titanium.Cli/AsyncConsole.cs,**/Titanium.Cli/Http3/Http3DepsCommand.cs,**/Titanium.Cli/Updates/VersionAndUpdateCommands.cs,**/Titanium.Cli/Certificates/CertificateBootstrap.cs,**/Titanium.Plus/Discovery/**,**/Titanium.Plus/Security/**,**/Titanium.Plus/State/**,**/Titanium.Plus/Resilience/**,**/Titanium.Plus/PlusLog.cs,**/Titanium.Web.Proxy/Helpers/LinuxSystemProxyBackend.cs,**/Titanium.Web.Proxy/Helpers/MacOsSystemProxyBackend.cs,**/Titanium.Web.Proxy/Helpers/IElevationPrompt.cs,**/Titanium.Web.Proxy/Helpers/IProcessRunner.cs" + /d:sonar.coverage.exclusions="**/examples/**,**/benchmarks/**,**/docs/**,**/Http3/Http3OriginBridge.cs,**/Http3/Http3OriginClientSession.cs,**/Handlers/Http11ToHttp2BridgeHandler.cs,**/Handlers/H1TerminateFastForward.cs,**/Titanium.Plus/Dashboard/**,**/Titanium.Inspector/Views/**,**/Titanium.Inspector/ViewModels/MainWindowViewModel.Trust.cs,**/Titanium.Inspector/Services/AppContainerLoopback.cs,**/Titanium.Inspector/Services/AvaloniaStatusNotifier.cs,**/Titanium.Inspector/Services/DesktopShell.cs,**/Titanium.Inspector/App.axaml.cs,**/Titanium.Inspector/Program.cs,**/Titanium.Inspector/InspectorAppFactory.cs,**/Titanium.Inspector/Services/UpdateService.cs,**/Titanium.Cli/Program.cs,**/Titanium.Cli/AsyncConsole.cs,**/Titanium.Cli/Http3/Http3DepsCommand.cs,**/Titanium.Cli/Updates/VersionAndUpdateCommands.cs,**/Titanium.Cli/Certificates/CertificateBootstrap.cs,**/Titanium.Plus/Discovery/**,**/Titanium.Plus/Security/**,**/Titanium.Plus/State/**,**/Titanium.Plus/Resilience/**,**/Titanium.Plus/PlusLog.cs,**/Titanium.Web.Proxy/Helpers/LinuxSystemProxyBackend.cs,**/Titanium.Web.Proxy/Helpers/MacOsSystemProxyBackend.cs,**/Titanium.Web.Proxy/Helpers/IElevationPrompt.cs,**/Titanium.Web.Proxy/Helpers/IProcessRunner.cs,**/Titanium.Web.Proxy/Certificates/FirefoxCertificateTrust.cs" - name: Build for SonarCloud analysis - if: env.SONAR_TOKEN != '' + if: env.SONAR_TOKEN != '' && steps.sonar_begin.outcome == 'success' run: dotnet build src/Titanium.Web.Proxy.sln --configuration Release --no-restore --no-incremental --disable-build-servers - name: Test @@ -108,7 +110,8 @@ jobs: if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } dotnet test tests/Titanium.Cli.Tests/Titanium.Cli.Tests.csproj --configuration Release --no-build --no-restore --collect:"Code Coverage" --results-directory coverage/cli if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - dotnet test tests/Titanium.E2E.Tests/Titanium.E2E.Tests.csproj --configuration Release --no-build --no-restore --filter "TestCategory=E2E|TestCategory=E2E-UI" --collect:"Code Coverage" --results-directory coverage/e2e + # CLI process E2E (TestCategory=E2E) runs on the cli-e2e OS matrix — avoid doubling Windows. + dotnet test tests/Titanium.E2E.Tests/Titanium.E2E.Tests.csproj --configuration Release --no-build --no-restore --filter "TestCategory=E2E-UI" --collect:"Code Coverage" --results-directory coverage/e2e if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } # The integration suite spins up real listening sockets and TLS handshakes, which @@ -130,12 +133,13 @@ jobs: run: .\.coverage\tools\dotnet-coverage merge "coverage\**\*.coverage" --output coverage/coverage.xml --output-format xml - name: Complete SonarCloud analysis - if: env.SONAR_TOKEN != '' + if: env.SONAR_TOKEN != '' && steps.sonar_begin.outcome == 'success' + continue-on-error: true shell: pwsh run: .\.sonar\scanner\dotnet-sonarscanner end /d:sonar.token="$env:SONAR_TOKEN" # DocFX output races other develop pushes (incl. concurrent "Update documentation"). - # Sync to origin/develop, regenerate, commit docs-only, retry push — never fail the + # Sync to origin/develop, regenerate, commit docs-only, retry push — never fail the # job on stash/rebase conflicts from EndBug/add-and-commit autostash. - name: Publish Documentation if: github.ref == 'refs/heads/develop' @@ -172,7 +176,7 @@ jobs: throw "Failed to publish documentation after retries" # Cross-OS Inspector + Plus dashboard UI gates (Headless / Visual / Playwright). - # Inspector unit suite stays on Windows `build` only except Inspector-Stress (below). + # Inspector unit suite stays on Windows `build` only except Inspector-Stress / Inspector-Trust-Decision (below). ui-portable: runs-on: ${{ matrix.os }} timeout-minutes: 35 @@ -215,7 +219,7 @@ jobs: shell: pwsh run: | $ErrorActionPreference = 'Stop' - # Intel macOS bottles are sparse; do not force-upgrade openssl (no bottle → job fail). + # Intel macOS bottles are sparse; do not force-upgrade openssl (no bottle → job fail). $env:HOMEBREW_NO_AUTO_UPDATE = '1' $env:HOMEBREW_NO_INSTALL_UPGRADE = '1' brew install openssl@3 libmsquic @@ -240,7 +244,7 @@ jobs: } if (-not (Test-QuicSupported)) { - Write-Host 'QuicListener.IsSupported still false after brew; trying Microsoft libmsquic drop…' + Write-Host 'QuicListener.IsSupported still false after brew; trying Microsoft libmsquic drop…' $arch = (& uname -m).Trim() $rid = if ($arch -eq 'arm64') { 'osx-arm64' } else { 'osx-x64' } $dest = Join-Path $env:RUNNER_TEMP 'msquic-osx' @@ -259,7 +263,7 @@ jobs: Select-Object -First 1 if ($found) { $extra = $found.Directory.FullName - # ${extra} — bare $extra: is parsed as a PowerShell drive-qualified variable. + # ${extra} — bare $extra: is parsed as a PowerShell drive-qualified variable. $dyld2 = "${extra}:${dyld}" Add-Content -Path $env:GITHUB_ENV -Value "DYLD_LIBRARY_PATH=$dyld2" Add-Content -Path $env:GITHUB_ENV -Value "DYLD_FALLBACK_LIBRARY_PATH=$dyld2" @@ -300,7 +304,7 @@ jobs: dotnet test tests/Titanium.E2E.Tests/Titanium.E2E.Tests.csproj --configuration Release --no-build --no-restore --filter "TestCategory=E2E-UI-Headless|TestCategory=E2E-UI-Visual|TestCategory=E2E-UI-Plus-Dashboard" - name: Inspector retention stress (spill + H3) run: | - dotnet test tests/Titanium.Inspector.Tests/Titanium.Inspector.Tests.csproj --configuration Release --no-restore --filter "TestCategory=Inspector-Stress" + dotnet test tests/Titanium.Inspector.Tests/Titanium.Inspector.Tests.csproj --configuration Release --no-restore --filter "TestCategory=Inspector-Stress|TestCategory=Inspector-Trust-Decision" - name: OS proxy-backend filters run: | dotnet test tests/Titanium.Web.Proxy.UnitTests/Titanium.Web.Proxy.UnitTests.csproj --configuration Release --no-build --no-restore --filter "FullyQualifiedName~UnixProxyBypassMapperTests|FullyQualifiedName~MacOsSystemProxyBackendTests|FullyQualifiedName~LinuxSystemProxyBackendTests|FullyQualifiedName~ElevationPromptCancelTests|FullyQualifiedName~SystemProxyBackendFactoryPlatformTests" @@ -327,8 +331,70 @@ jobs: **/playwright-report/** if-no-files-found: ignore - # Tiered RPS gates for beta/stable publish (parallel — wall clock ~max of the two). - # Editions: CLI/Plus tax vs Core. Peer: Core reverse vs YARP (+ MITM÷Reverse) so a + # Full CLI + CLI Plus process E2E on all three OS (command tree, services, control plane). + # Does not pre-install libmsquic so http3-deps install can exercise the real leaf when Quic is false. + cli-e2e: + runs-on: ${{ matrix.os }} + timeout-minutes: 60 + permissions: + contents: read + strategy: + fail-fast: false + matrix: + os: [windows-latest, ubuntu-latest, macos-latest] + steps: + - uses: actions/checkout@v6 + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: | + 10.0.x + - name: Linux Playwright OS deps + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y fonts-liberation libnss3 libatk-bridge2.0-0 libdrm2 libxkbcommon0 libgbm1 libasound2t64 || sudo apt-get install -y fonts-liberation libnss3 libatk-bridge2.0-0 libdrm2 libxkbcommon0 libgbm1 libasound2 + - name: Restore + run: dotnet restore src/Titanium.Web.Proxy.sln + - name: Build CLI + Plus + E2E + run: | + dotnet build src/Titanium.Cli/Titanium.Cli.csproj --configuration Release --no-restore + dotnet build src/Titanium.Plus/Titanium.Plus.csproj --configuration Release --no-restore + dotnet build tests/Titanium.E2E.Tests/Titanium.E2E.Tests.csproj --configuration Release --no-restore + - name: Install Playwright Chromium + shell: pwsh + run: | + $pw = Join-Path (Resolve-Path "tests/Titanium.E2E.Tests/bin/Release/net10.0") "playwright.ps1" + if (-not (Test-Path $pw)) { + throw "playwright.ps1 missing at $pw" + } + & $pw install chromium + - name: CLI process E2E (all leaves) + shell: pwsh + run: | + if ($IsLinux -or $IsMacOS) { + sudo -n true 2>$null + if ($LASTEXITCODE -ne 0) { + Write-Warning "sudo -n not available; machine service lifecycle may Inconclusive" + } + } + dotnet test tests/Titanium.E2E.Tests/Titanium.E2E.Tests.csproj ` + --configuration Release --no-build --no-restore ` + --filter "TestCategory=E2E" ` + --logger "trx;LogFileName=cli-e2e.trx" ` + --results-directory "artifacts/cli-e2e-${{ matrix.os }}" + - name: Upload CLI E2E artifacts + if: failure() + uses: actions/upload-artifact@v4 + with: + name: cli-e2e-${{ matrix.os }} + path: | + artifacts/cli-e2e-${{ matrix.os }}/** + tests/Titanium.E2E.Tests/TestResults/** + if-no-files-found: ignore + + # Tiered RPS gates for beta/stable publish (parallel) — wall clock ~max of the two). + # Editions: CLI/Plus tax vs Core. Peer: Core reverse vs YARP (+ MITM÷Reverse) so a # uniform Core slowdown cannot hide behind green edition ratios. rps-publish-gate: if: github.event_name == 'push' && (github.ref == 'refs/heads/beta' || github.ref == 'refs/heads/stable') @@ -388,7 +454,7 @@ jobs: sudo apt-get update sudo apt-get install -y libmsquic pwsh -NoProfile -Command 'if (-not [System.Net.Quic.QuicListener]::IsSupported) { throw "QuicListener.IsSupported is false after libmsquic install" }; Write-Host "QuicListener.IsSupported=$([System.Net.Quic.QuicListener]::IsSupported)"' - - name: compare-spot (Core÷YARP + MITM÷Reverse) + - name: compare-spot (Core÷YARP + MITM÷Reverse) shell: pwsh run: | pwsh tools/RpsLoadProbe/run-spot-matrix.ps1 @@ -447,7 +513,7 @@ jobs: # Product zips stay on release.yml (v* tags). After beta/stable merge, create/move the # version tag and dispatch release.yml (GITHUB_TOKEN tag pushes do not re-trigger workflows). # release.yml also packs/pushes Chocolatey (titanium-cli / titanium-inspector) after the - # GitHub Release exists — no separate chocolatey step here. + # GitHub Release exists — no separate chocolatey step here. cut-product-tag: if: github.event_name == 'push' && (github.ref == 'refs/heads/beta' || github.ref == 'refs/heads/stable') needs: [build, ui-portable, rps-publish-gate, rps-peer-gate] diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6149d3f5c..a4e409243 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -83,8 +83,8 @@ jobs: if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } } - Write-Host "=== E2E / E2E-UI ===" - dotnet test tests/Titanium.E2E.Tests/Titanium.E2E.Tests.csproj -c Release --no-build --no-restore --nologo --filter "TestCategory=E2E|TestCategory=E2E-UI" + Write-Host "=== E2E-UI (CLI process E2E is cli-e2e matrix) ===" + dotnet test tests/Titanium.E2E.Tests/Titanium.E2E.Tests.csproj -c Release --no-build --no-restore --nologo --filter "TestCategory=E2E-UI" if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } $maxAttempts = 2 @@ -106,8 +106,43 @@ jobs: if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + cli-e2e: + needs: resolve-version + runs-on: ${{ matrix.os }} + timeout-minutes: 60 + permissions: + contents: read + strategy: + fail-fast: false + matrix: + os: [windows-latest, ubuntu-latest, macos-latest] + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-dotnet@v5 + with: + dotnet-version: '10.0.x' + - name: Linux Playwright OS deps + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y fonts-liberation libnss3 libatk-bridge2.0-0 libdrm2 libxkbcommon0 libgbm1 libasound2t64 || sudo apt-get install -y fonts-liberation libnss3 libatk-bridge2.0-0 libdrm2 libxkbcommon0 libgbm1 libasound2 + - name: Restore and build + run: | + dotnet restore src/Titanium.Web.Proxy.sln + dotnet build src/Titanium.Cli/Titanium.Cli.csproj -c Release --no-restore + dotnet build src/Titanium.Plus/Titanium.Plus.csproj -c Release --no-restore + dotnet build tests/Titanium.E2E.Tests/Titanium.E2E.Tests.csproj -c Release --no-restore + - name: Install Playwright Chromium + shell: pwsh + run: | + $pw = Join-Path (Resolve-Path "tests/Titanium.E2E.Tests/bin/Release/net10.0") "playwright.ps1" + & $pw install chromium + - name: CLI process E2E + run: | + dotnet test tests/Titanium.E2E.Tests/Titanium.E2E.Tests.csproj -c Release --no-build --no-restore --nologo --filter "TestCategory=E2E" + build-cli: - needs: [resolve-version, test] + needs: [resolve-version, test, cli-e2e] runs-on: ${{ matrix.os }} # win-x64 uses environment "signing" for Azure OIDC (federated cred). environment: ${{ matrix.rid == 'win-x64' && 'signing' || '' }} diff --git a/.gitignore b/.gitignore index 747b0dd3e..f59d4ca55 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,8 @@ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. -# Local Cursor config (plans, caches). Shared project rules are tracked. -.cursor/* -!.cursor/rules/ -!.cursor/rules/** +# Local Cursor agent rules / config (not shared) +.cursor/ # User-specific files *.suo diff --git a/README.md b/README.md index f8586664f..832865f7d 100644 --- a/README.md +++ b/README.md @@ -19,11 +19,13 @@ Requires .NET 10 or later for the library. CLI and Inspector downloads are self- ## What you can do -- Run a reverse / edge proxy in front of any backend, or inspect and modify HTTP(S) traffic in the desktop Inspector -- Explicit, transparent, and SOCKS4/5 endpoints; decrypt HTTPS when you trust a local root certificate -- Stream bodies across HTTP/1.x, HTTP/2, and HTTP/3; upstream proxies, auth, and mutual TLS +- Decrypt and inspect HTTPS in a native desktop Inspector on Windows, macOS, and Linux — AutoResponder, Map Local/Remote, breakpoints, Composer, HAR, curl/fetch +- Keep sign-in working: SSO hosts stay on OS bypass; hosts that reject MITM auto-tunnel +- Speak modern HTTP — HTTP/2 by default, optional HTTP/3, WebSocket, gRPC, SSE, GraphQL rules in the same grid +- Run the same engine as a reverse / edge proxy from the CLI (YAML, load balancing, ACME, live reload) +- Embed in .NET via NuGet (MIT); Inspector is free for personal and education use -Protocol coverage: [protocol support matrix](https://github.com/justcoding121/titanium-web-proxy/wiki/Protocol-Support). HTTP/3 packaging: [HTTP/3 wiki](https://github.com/justcoding121/titanium-web-proxy/wiki/HTTP-3). +Full catalog: [Features](https://titaniumproxy.com/docs/features). Protocol coverage: [protocol support matrix](https://github.com/justcoding121/titanium-web-proxy/wiki/Protocol-Support). HTTP/3 packaging: [HTTP/3 wiki](https://github.com/justcoding121/titanium-web-proxy/wiki/HTTP-3). ## Performance @@ -108,8 +110,8 @@ Point your client at `127.0.0.1:8000` as its HTTP and HTTPS proxy. Trusting a ge ## Examples and documentation -- **[Website](https://titaniumproxy.com)** — product docs, [download](https://titaniumproxy.com/download), [getting started](https://titaniumproxy.com/docs/getting-started), [release notes](https://titaniumproxy.com/releases) -- **[Wiki](https://github.com/justcoding121/titanium-web-proxy/wiki)** — deeper guides (performance, streaming bodies, HTTP/3, protocol support) +- **[Website](https://titaniumproxy.com)** — product docs, [download](https://titaniumproxy.com/download), [getting started](https://titaniumproxy.com/docs/getting-started), [features](https://titaniumproxy.com/docs/features), [release notes](https://titaniumproxy.com/releases) +- **[Wiki](https://github.com/justcoding121/titanium-web-proxy/wiki)** — deeper guides (performance, streaming bodies, HTTP/3, protocol support) and a short [Features](https://github.com/justcoding121/titanium-web-proxy/wiki/Features) pointer - [Basic console proxy](examples/Titanium.Web.Proxy.Examples.Basic) - [WPF desktop example](examples/Titanium.Web.Proxy.Examples.Wpf) - [Windows service example](examples/Titanium.Web.Proxy.Examples.WindowsService) diff --git a/docs/api/Titanium.Web.Proxy.Http.Request.html b/docs/api/Titanium.Web.Proxy.Http.Request.html index c3b780634..dc1ffb9bd 100644 --- a/docs/api/Titanium.Web.Proxy.Http.Request.html +++ b/docs/api/Titanium.Web.Proxy.Http.Request.html @@ -168,7 +168,7 @@
Honored from connection-level events and from
UpstreamHttpProtocol in BeforeRequest.
- Forced Http3 skips Auto-mode warm-up gating and fails closed with no TCP fallback.
+ Forced Http3 does not require an Alt-Svc / SVCB cache entry and fails closed with no TCP fallback.
public void ApplyFastColdStartLeafSettings()
macOS/Linux SSL trust (Keychain / NSS). May show auth UI — keep on a pumping thread. +No-op on Windows (Root store presence is trust).
+public void ApplyUnixSslTrustAfterStoreInstall(bool machineTrusted = false)
+ | Type | +Name | +Description | +
|---|---|---|
| bool | +machineTrusted | ++ |
macOS/Linux Keychain/NSS untrust. May show auth UI — keep on a pumping thread.
+public void ApplyUnixSslUntrust()
+ Installs the root into Personal + Trusted Root stores only (Windows CryptUI Yes/No on Root Add). +Does not prune orphans or run Unix Keychain/NSS trust — UI callers should finish those +off the dispatcher after CryptUI returns so Avalonia does not show Not Responding.
+public bool InstallRootIntoCertificateStores(bool machineTrusted = false)
+ | Type | +Name | +Description | +
|---|---|---|
| bool | +machineTrusted | ++ |
| Type | +Description | +
|---|---|
| bool | +True when the user Root store entry was newly added. + |
+
Read-only: Root-store thumbprints matching RootCertificateName. +Uses FindBySubjectName (not a full store enumeration) so interactive Clear/reinstall +does not sit on Busy for tens of seconds on large Windows Root stores.
+public IReadOnlyList<string> ListSameCommonNameRootThumbprints(StoreLocation storeLocation, string? keepThumbprint = null)
+ | Type | +Name | +Description | +
|---|---|---|
| StoreLocation | +storeLocation | ++ |
| string | +keepThumbprint | ++ |
| Type | +Description | +
|---|---|
| IReadOnlyList<string> | ++ |
Personal (My) store same-CN cleanup only — typically no CryptUI. Safe off the UI thread.
+public void PruneOrphanedPersonalCertificates(StoreLocation storeLocation, bool keepCurrentThumbprint)
+ | Type | +Name | +Description | +
|---|---|---|
| StoreLocation | +storeLocation | ++ |
| bool | +keepCurrentThumbprint | ++ |
Removes same-CN Root/My entries (may show Windows Root Delete CryptUI). Prefer a pumping +UI thread when interactive.
+public void PruneOrphanedSameCommonNameCertificates(bool machineTrusted, bool keepCurrentThumbprint)
+ | Type | +Name | +Description | +
|---|---|---|
| bool | +machineTrusted | ++ |
| bool | +keepCurrentThumbprint | ++ |
Removes one certificate by thumbprint. Root Remove may show Windows CryptUI — UI thread.
+public bool RemoveCertificateByThumbprint(StoreName storeName, StoreLocation storeLocation, string thumbprint)
+ | Type | +Name | +Description | +
|---|---|---|
| StoreName | +storeName | ++ |
| StoreLocation | +storeLocation | ++ |
| string | +thumbprint | ++ |
| Type | +Description | +
|---|---|
| bool | ++ |
public static class FirefoxCertificateTrust
Last step tag from TryEnableWindowsEnterpriseRoots() / +TryEnableEnterpriseRootsUserPref() for Inspector ux-trace +(e.g. HkcuOk, UserJsSkippedFirefoxRunning, UserJsOk, Failed).
+public static string? LastEnterpriseRootsStep { get; }
+ | Type | +Description | +
|---|---|
| string | ++ |
Clears enterprise-roots policy/prefs (HKCU / user.js). Does not run NSS
+certutil — that can hang for minutes on a locked Firefox profile and blocked
+Clear/reinstall on "Finishing root CA removal…". Use Trust/Untrust Firefox for NSS.
public static void ClearRootTrustBestEffort(string? friendlyName)
+ | Type | +Name | +Description | +
|---|---|---|
| string | +friendlyName | ++ |
Default Via header pseudonym (RFC 9110 §7.6.3). Used by ViaHeaderPseudonym @@ -292,7 +292,7 @@
Requires MsQuic native library and a supported operating-system version (IsSupported). Setting to true with @@ -1027,7 +1027,7 @@
Marks host as actively bypassed (same as a forced learn after origin TLS failure).
+Raises DecryptFailureBypassChanged when bypass newly becomes active.
public bool ForceDecryptFailureBypass(string host)
+ | Type | +Name | +Description | +
|---|---|---|
| string | +host | ++ |
| Type | +Description | +
|---|---|
| bool | ++ |
Intercept after response event from server.
@@ -4069,7 +4118,7 @@Intercept request event to server.
@@ -4099,7 +4148,7 @@Intercept response event from server.
@@ -4129,7 +4178,7 @@Intercept connect request sent to upstream proxy.
@@ -4159,7 +4208,7 @@Event to override client certificate selection during mutual SSL authentication.
@@ -4189,7 +4238,7 @@Event occurs when client connection count changed.
@@ -4219,7 +4268,7 @@Raised when a host becomes actively bypassed (threshold reached or same-CONNECT mark). @@ -4250,7 +4299,7 @@
Event occurs when inbound HTTP/3 client connection count changed.
@@ -4280,7 +4329,7 @@Event occurs when upstream HTTP/3 server connection count changed.
@@ -4310,7 +4359,7 @@Customize TcpClient used for client connection upon create.
@@ -4340,7 +4389,7 @@Intercept request body send event to server. @@ -4372,7 +4421,7 @@
Intercept response body send event to client. @@ -4404,7 +4453,7 @@
Customize TcpClient used for server connection upon create.
@@ -4434,7 +4483,7 @@Event to override the default verification logic of remote SSL certificate received during authentication.
@@ -4464,7 +4513,7 @@Event occurs when server connection count changed.
diff --git a/docs/index.json b/docs/index.json index 037cae34e..309459583 100644 --- a/docs/index.json +++ b/docs/index.json @@ -432,7 +432,7 @@ "api/Titanium.Web.Proxy.Models.UpstreamHttpProtocol.html": { "href": "api/Titanium.Web.Proxy.Models.UpstreamHttpProtocol.html", "title": "Enum UpstreamHttpProtocol | Titanium Web Proxy", - "summary": "Enum UpstreamHttpProtocol Controls which HTTP version the proxy uses on its own connection to the origin server, independent of which HTTP version the client used to talk to the proxy. Set a connection-level default on UpstreamHttpProtocol (during BeforeTunnelConnectRequest), UpstreamHttpProtocol (during BeforeSslAuthenticate), or UpstreamHttpProtocol (during BeforeQuicAuthenticate). Per-request overrides are available via UpstreamHttpProtocol in BeforeRequest. Namespace: Titanium.Web.Proxy.Models Assembly: Titanium.Web.Proxy.dll Syntax public enum UpstreamHttpProtocol Fields Name Description Auto Couple the origin protocol to the client protocol: HTTP/2 is only ever offered to the client when the origin has also been confirmed (via a fresh probe or a cached prior result) to support HTTP/2, and the origin connection then uses whatever protocol the client ends up negotiating. When EnableHttp3 is true, a cached Alt-Svc / HTTPS/SVCB result in Titanium.Web.Proxy.Http3.Http3OriginCapabilityCache only arms background QUIC warm-up; outbound HTTP/3 is used once that origin is warm, otherwise the request stays on HTTP/2 or HTTP/1.1. This is the default. Http11 Always use HTTP/1.1 on the connection to the origin, regardless of what the client negotiates with the proxy. When AllowHttpProtocolTranslation/ AllowHttpProtocolTranslation is left at its default of false, the client is simply never offered \"h2\" via ALPN either, so it transparently negotiates HTTP/1.1 too and no translation is ever required. Setting it to true instead allows the client to negotiate HTTP/2 while the origin connection stays HTTP/1.1, which requires bridging client h2 streams onto HTTP/1.1 origin requests. Http2 Always use HTTP/2 on the connection to the origin. Without ForwardCleartext, the origin must negotiate h2 via TLS ALPN. With ForwardCleartext, the origin connection is cleartext HTTP/2 prior-knowledge (h2c) instead. A translation bridge cannot fabricate HTTP/2 at an origin that lacks it. When the client itself does not negotiate HTTP/2, reconciling that with a confirmed HTTP/2 origin connection requires AllowHttpProtocolTranslation/ AllowHttpProtocolTranslation to bridge HTTP/1.1 client requests onto the HTTP/2 origin connection. Http3 Always use HTTP/3 (QUIC) on the connection to the origin. Fails the stream with ProxyConnectException if HTTP/3 cannot be established — no fallback to HTTP/2 or HTTP/1.1. Symmetric with Http2: origin must support QUIC/h3 or the request fails. When AllowHttpProtocolTranslation is true, a non-H3 inbound client connection may still be bridged onto the H3 origin stream. Honored from connection-level events and from UpstreamHttpProtocol in BeforeRequest. Forced Http3 skips Auto-mode warm-up gating and fails closed with no TCP fallback." + "summary": "Enum UpstreamHttpProtocol Controls which HTTP version the proxy uses on its own connection to the origin server, independent of which HTTP version the client used to talk to the proxy. Set a connection-level default on UpstreamHttpProtocol (during BeforeTunnelConnectRequest), UpstreamHttpProtocol (during BeforeSslAuthenticate), or UpstreamHttpProtocol (during BeforeQuicAuthenticate). Per-request overrides are available via UpstreamHttpProtocol in BeforeRequest. Namespace: Titanium.Web.Proxy.Models Assembly: Titanium.Web.Proxy.dll Syntax public enum UpstreamHttpProtocol Fields Name Description Auto Couple the origin protocol to the client protocol: HTTP/2 is only ever offered to the client when the origin has also been confirmed (via a fresh probe or a cached prior result) to support HTTP/2, and the origin connection then uses whatever protocol the client ends up negotiating. When EnableHttp3 is true, a cached Alt-Svc / HTTPS/SVCB result in Titanium.Web.Proxy.Http3.Http3OriginCapabilityCache selects outbound HTTP/3 on the next CONNECT or new HTTP/1.1 request (background QUIC warm-up starts when the cache is filled). An already-open H2↔H2 MITM session is not upgraded mid-connection. This is the default. Http11 Always use HTTP/1.1 on the connection to the origin, regardless of what the client negotiates with the proxy. When AllowHttpProtocolTranslation/ AllowHttpProtocolTranslation is left at its default of false, the client is simply never offered \"h2\" via ALPN either, so it transparently negotiates HTTP/1.1 too and no translation is ever required. Setting it to true instead allows the client to negotiate HTTP/2 while the origin connection stays HTTP/1.1, which requires bridging client h2 streams onto HTTP/1.1 origin requests. Http2 Always use HTTP/2 on the connection to the origin. Without ForwardCleartext, the origin must negotiate h2 via TLS ALPN. With ForwardCleartext, the origin connection is cleartext HTTP/2 prior-knowledge (h2c) instead. A translation bridge cannot fabricate HTTP/2 at an origin that lacks it. When the client itself does not negotiate HTTP/2, reconciling that with a confirmed HTTP/2 origin connection requires AllowHttpProtocolTranslation/ AllowHttpProtocolTranslation to bridge HTTP/1.1 client requests onto the HTTP/2 origin connection. Http3 Always use HTTP/3 (QUIC) on the connection to the origin. Fails the stream with ProxyConnectException if HTTP/3 cannot be established — no fallback to HTTP/2 or HTTP/1.1. Symmetric with Http2: origin must support QUIC/h3 or the request fails. When AllowHttpProtocolTranslation is true, a non-H3 inbound client connection may still be bridged onto the H3 origin stream. Honored from connection-level events and from UpstreamHttpProtocol in BeforeRequest. Forced Http3 does not require an Alt-Svc / SVCB cache entry and fails closed with no TCP fallback." }, "api/Titanium.Web.Proxy.Models.WinAuthCredentials.html": { "href": "api/Titanium.Web.Proxy.Models.WinAuthCredentials.html", @@ -457,7 +457,7 @@ "api/Titanium.Web.Proxy.Network.CertificateManager.html": { "href": "api/Titanium.Web.Proxy.Network.CertificateManager.html", "title": "Class CertificateManager | Titanium Web Proxy", - "summary": "Class CertificateManager A class to manage SSL certificates used by this proxy server. Inheritance object CertificateManager Implements IDisposable Inherited Members object.Equals(object) object.Equals(object, object) object.GetHashCode() object.GetType() object.ReferenceEquals(object, object) object.ToString() Namespace: Titanium.Web.Proxy.Network Assembly: Titanium.Web.Proxy.dll Syntax public sealed class CertificateManager : IDisposable Properties | Edit this page View Source AreInteractiveRootStoreMutationsSuppressed True when Root-store Add/Remove should be skipped to avoid modal CryptUI prompts (static flag, CI env, or TITANIUM_SKIP_ROOT_STORE_UI=1). Declaration public static bool AreInteractiveRootStoreMutationsSuppressed { get; } Property Value Type Description bool | Edit this page View Source CertificateCacheTimeOutMinutes Minutes certificates should be kept in cache when not used. Declaration public int CertificateCacheTimeOutMinutes { get; set; } Property Value Type Description int | Edit this page View Source CertificateEngine Selects the certificate generation engine. Default is BouncyCastle on all platforms. On non-Windows runtimes, DefaultWindows is coerced to BouncyCastle; both BouncyCastle engines are supported. Declaration public CertificateEngine CertificateEngine { get; set; } Property Value Type Description CertificateEngine | Edit this page View Source CertificateGraceDays Number of days by which the certificate's NotBefore timestamp is backdated relative to the current UTC time. A small backdate (the default is 2 days) compensates for minor clock-skew between the proxy machine and clients; it is not necessary to backdate by a year. The total certificate lifetime is CertificateValidDays + CertificateGraceDays. Chrome 70+ and iOS 14+ cap this at 398 days for TLS leaf certificates. Declaration public int CertificateGraceDays { get; set; } Property Value Type Description int | Edit this page View Source CertificateStorage The fake certificate cache storage. The default implementation stores leaf certificates in a crts subdirectory of the per-user Titanium.Web.Proxy directory (%LocalAppData% on Windows, ApplicationData on Linux/macOS). Implement ICertificateCache and assign a concrete class here to customize. Declaration public ICertificateCache CertificateStorage { get; set; } Property Value Type Description ICertificateCache | Edit this page View Source CertificateValidDays Number of days generated HTTPS leaf certificates are valid for, measured forward from the moment of creation. The certificate's NotBefore is set to UtcNow - CertificateGraceDays, so the effective total validity window (NotAfter − NotBefore) equals CertificateValidDays + CertificateGraceDays. Chrome 70+ and iOS 14+ reject certificates whose total validity window exceeds 398 days. To stay within that limit, keep CertificateValidDays + CertificateGraceDays <= 398. The default value of 396, combined with the default grace of 2, equals exactly 398 days total. Declaration public int CertificateValidDays { get; set; } Property Value Type Description int | Edit this page View Source DisableWildCardCertificates When true, issue per-host certificates instead of *.parent.tld wildcards. Default false (wildcards enabled where applicable). Declaration public bool DisableWildCardCertificates { get; set; } Property Value Type Description bool | Edit this page View Source IntermediateCertificates Additional certificates to send to clients as part of the TLS certificate chain. Use this when RootCertificate is an intermediate CA rather than the trust anchor: set this to the ordered list of intermediate certificates between the signing certificate and the client-trusted root so that clients can build a complete verified chain. When RootCertificate is not self-signed it is automatically included in the chain even if this collection is empty; any certificates in this collection are appended after it. Declaration public X509Certificate2Collection? IntermediateCertificates { get; set; } Property Value Type Description X509Certificate2Collection | Edit this page View Source LastOsTrustResult Last OS/browser trust outcome from TrustRootCertificate(bool) / related helpers. Declaration public CertificateOsTrustResult? LastOsTrustResult { get; } Property Value Type Description CertificateOsTrustResult | Edit this page View Source LeafCertificateKeyAlgorithm Key algorithm for generated leaf certificates. Honoured by the BouncyCastle engines; the Windows engine always issues RSA. Defaults to Rsa2048. Switching to EcdsaP256 makes generating a certificate for a not-yet-seen host roughly fifty times cheaper, which is the single largest cost the proxy adds to a first visit. Only clients that accept ECDSA server certificates can be intercepted afterwards. Declaration public CertificateKeyAlgorithm LeafCertificateKeyAlgorithm { get; set; } Property Value Type Description CertificateKeyAlgorithm | Edit this page View Source LeafRsaKeyPairBufferSize How many RSA-2048 leaf private keys to keep ready in a background-refilled buffer so first visits do not pay key-generation cost on the CONNECT that needs the certificate. Defaults to 8. Set to 0 to disable buffering (keys are generated on demand). Only applies when LeafCertificateKeyAlgorithm is Rsa2048. ECDSA P-256 keys are cheap enough that they are always generated inline. The buffer is process-wide and shared by every CertificateManager instance. Declaration public static int LeafRsaKeyPairBufferSize { get; set; } Property Value Type Description int | Edit this page View Source OverwritePfxFile Overwrite Root certificate file. true : replace an existing .pfx file if password is incorrect or if RootCertificate = null. Declaration public bool OverwritePfxFile { get; set; } Property Value Type Description bool | Edit this page View Source PfxFilePath Name(path) of the Root certificate file. Set the name or path of the .pfx file. When empty, the file is named rootCert.pfx. Relative or empty values are resolved under the per-user Titanium.Web.Proxy directory (%LocalAppData% on Windows, ApplicationData on Linux/macOS). Absolute paths are honored as-is. Declaration public string PfxFilePath { get; set; } Property Value Type Description string | Edit this page View Source PfxPassword Password of the Root certificate file. Set a password for the .pfx file Declaration public string PfxPassword { get; set; } Property Value Type Description string | Edit this page View Source RootCertificate The root certificate. Declaration public X509Certificate2? RootCertificate { get; set; } Property Value Type Description X509Certificate2 | Edit this page View Source RootCertificateIssuerName Name of the root certificate issuer. (This is valid only when RootCertificate property is not set.) Declaration public string RootCertificateIssuerName { get; set; } Property Value Type Description string | Edit this page View Source RootCertificateName Subject/CN name used when generating a root certificate. (This is valid only when RootCertificate property is not set.) If no certificate is provided then a default root certificate will be created and used. Persistence uses PfxFilePath / CertificateStorage under the per-user Titanium.Web.Proxy directory (not the process executable directory). Declaration public string RootCertificateName { get; set; } Property Value Type Description string | Edit this page View Source SaveFakeCertificates When true, persist generated leaf certificates via CertificateStorage so subsequent runs can reload them instead of regenerating. Declaration public bool SaveFakeCertificates { get; set; } Property Value Type Description bool | Edit this page View Source StorageFlag Adjust behaviour when certificates are saved to filesystem. Declaration public X509KeyStorageFlags StorageFlag { get; set; } Property Value Type Description X509KeyStorageFlags | Edit this page View Source SuppressInteractiveRootStoreMutations When true, skip Root Add/Remove that trigger Windows CryptUI \"Root Certificate Store\" Yes/No dialogs (which hang headless CI and unattended dotnet test). Personal (My) mutations still run. Also treated as true when CI, GITHUB_ACTIONS, TF_BUILD, or TITANIUM_SKIP_ROOT_STORE_UI=1 is set. Opt back in for intentional interactive Install CA (e.g. local E2E-Slow Chrome) by setting this to false in a process that does not set those env vars. Declaration public static bool SuppressInteractiveRootStoreMutations { get; set; } Property Value Type Description bool Methods | Edit this page View Source ApplyFastColdStartLeafSettings() Fast first-visit MITM for modern TLS clients (browsers, current HttpClient): BouncyCastleFast, ECDSA P-256 leaves, and SaveFakeCertificates enabled. The root CA stays RSA. Library Balanced still defaults to RSA-2048 leaves for widest compatibility. Call this from Inspector, CLI, and desktop MITM hosts where clients are known to accept ECDSA server certificates — RSA leaf generation is the dominant cold-start cost when a page hits many not-yet-seen hosts (often ~1 s per host). Declaration public void ApplyFastColdStartLeafSettings() | Edit this page View Source ClearRootCertificate() Clear the root certificate and cache. Declaration public void ClearRootCertificate() | Edit this page View Source CreateRootCertificate(bool) Attempts to create a RootCertificate. Declaration public bool CreateRootCertificate(bool persistToFile = true) Parameters Type Name Description bool persistToFile if set to true try to load/save the certificate from rootCert.pfx. Returns Type Description bool true if succeeded, else false. | Edit this page View Source CreateServerCertificate(string) Creates a server certificate signed by the root certificate. Declaration public Task