Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
eff47a0
chore(packaging): refresh 7.0.6 winget/Chocolatey stubs and follow-ups
justcoding121 Sep 11, 2026
d39d4e5
docs(packaging): note winget 7.0.5 PRs closed in favor of 7.0.6
justcoding121 Sep 11, 2026
0b2f20c
Mark HAProxy H3-origin impossible and close paste/chart gaps.
justcoding121 Sep 11, 2026
3a025a1
Stop the Inspector MSI from opening a second setup wizard at the end …
justcoding121 Sep 11, 2026
ef4e30d
Put each OS runner spec in its own practical RPS chart footer.
justcoding121 Sep 11, 2026
d10593e
Add real-world gRPC h2c and WebSocket wiki harnesses.
justcoding121 Sep 11, 2026
a5ab234
Polish new WS/gRPC wiki stubs and maintainer dispatch notes.
justcoding121 Sep 11, 2026
89ea6ea
Keep the heavier RPS legend clear and match tiny-chart legend size.
justcoding121 Sep 11, 2026
219846b
Paste Linux H3 remasures and real-world gRPC/WS wiki numbers.
justcoding121 Sep 11, 2026
76a88f1
docs: use hyphenated re-measure in wiki, comments, and Cursor rules.
justcoding121 Sep 11, 2026
282c80b
fix(probe): stop Mac editions false missing-arm gate fails.
justcoding121 Sep 11, 2026
6fbd62f
fix(http2): coalesce cold ALPN probes so parallel CONNECT tunnels do …
justcoding121 Sep 11, 2026
a495a9a
feat(mitm): learn origin TLS decrypt failures and auto-tunnel (Inspec…
justcoding121 Sep 11, 2026
d08c628
docs(wiki): fill remaining Performance Not measured cells.
justcoding121 Sep 11, 2026
e78413d
docs(wiki): omit all-Not-possible Performance peer columns.
justcoding121 Sep 11, 2026
6dcc891
Move Proxy localhost to Capture and drop the Excluded hosts OS-bypass…
justcoding121 Sep 11, 2026
f0d8eed
fix(mitm): learn HTTP 403/429 blocks and keep prefetch off the fallba…
justcoding121 Sep 11, 2026
b6f6124
docs(wiki): merge Performance sustain and peak into one cell.
justcoding121 Sep 11, 2026
621742a
fix(mitm): make document decrypt-bypass seamless on H1/H2.
justcoding121 Sep 11, 2026
24e4cca
chore(release): bump VersionPrefix to 7.0.7 for next beta cut.
justcoding121 Sep 11, 2026
4d800f1
fix(http2): adopt cold ALPN probe as session connection after coalesce.
justcoding121 Sep 11, 2026
b325f88
fix(http): keep origin body unread when replacing a custom response.
justcoding121 Sep 11, 2026
6e49244
Update documentation
github-actions[bot] Sep 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .cursor/rules/re-measure-wording.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
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.
2 changes: 2 additions & 0 deletions .github/workflows/rps-saturation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ on:
- compare-tls-cost
- compare-arch
- compare-grpc
- compare-ws-h1tls
- compare-ws-h2
- compare-saturation
- compare-spot
- origin-direct
Expand Down
6 changes: 4 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.

# Local Cursor agent rules / config (not shared)
.cursor/
# Local Cursor config (plans, caches). Shared project rules are tracked.
.cursor/*
!.cursor/rules/
!.cursor/rules/**

# User-specific files
*.suo
Expand Down
84 changes: 42 additions & 42 deletions docs/api/Titanium.Web.Proxy.EventArguments.SessionEventArgs.html

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,7 @@ <h3 id="methods">Methods
<a href="https://github.com/justcoding121/titanium-web-proxy/new/develop/apiSpec/new?filename=Titanium_Web_Proxy_Http_RequestResponseBase_ToString.md&amp;value=---%0Auid%3A%20Titanium.Web.Proxy.Http.RequestResponseBase.ToString%0Asummary%3A%20&#39;*You%20can%20override%20summary%20for%20the%20API%20here%20using%20*MARKDOWN*%20syntax&#39;%0A---%0A%0A*Please%20type%20below%20more%20information%20about%20this%20API%3A*%0A%0A">Edit this page</a>
</span>
<span class="small pull-right mobile-hide">
<a href="https://github.com/justcoding121/titanium-web-proxy/blob/develop/src/Titanium.Web.Proxy/Http/RequestResponseBase.cs/#L404">View Source</a>
<a href="https://github.com/justcoding121/titanium-web-proxy/blob/develop/src/Titanium.Web.Proxy/Http/RequestResponseBase.cs/#L411">View Source</a>
</span>
<a id="Titanium_Web_Proxy_Http_RequestResponseBase_ToString_" data-uid="Titanium.Web.Proxy.Http.RequestResponseBase.ToString*"></a>
<h4 id="Titanium_Web_Proxy_Http_RequestResponseBase_ToString" data-uid="Titanium.Web.Proxy.Http.RequestResponseBase.ToString">ToString()</h4>
Expand Down
374 changes: 374 additions & 0 deletions docs/api/Titanium.Web.Proxy.Models.DecryptFailureBypassEntry.html

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions docs/api/Titanium.Web.Proxy.Models.html
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ <h1 id="Titanium_Web_Proxy_Models" data-uid="Titanium.Web.Proxy.Models" class="t
<h3 id="classes">
Classes
</h3>
<h4><a class="xref" href="Titanium.Web.Proxy.Models.DecryptFailureBypassEntry.html">DecryptFailureBypassEntry</a></h4>
<section><p>A host that the proxy learned to tunnel without decrypt after repeated origin TLS
handshake failures under MITM (e.g. bot / TLS-fingerprint rejection).</p>
</section>
<h4><a class="xref" href="Titanium.Web.Proxy.Models.ExplicitProxyEndPoint.html">ExplicitProxyEndPoint</a></h4>
<section><p>A proxy endpoint that the client is aware of.
So client application know that it is communicating with a proxy server.</p>
Expand Down
487 changes: 396 additions & 91 deletions docs/api/Titanium.Web.Proxy.ProxyServer.html

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions docs/api/toc.html
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,9 @@
<a href="Titanium.Web.Proxy.Models.html" name="" title="Titanium.Web.Proxy.Models">Titanium.Web.Proxy.Models</a>

<ul class="nav level2">
<li>
<a href="Titanium.Web.Proxy.Models.DecryptFailureBypassEntry.html" name="" title="DecryptFailureBypassEntry">DecryptFailureBypassEntry</a>
</li>
<li>
<a href="Titanium.Web.Proxy.Models.ExplicitProxyEndPoint.html" name="" title="ExplicitProxyEndPoint">ExplicitProxyEndPoint</a>
</li>
Expand Down
2 changes: 1 addition & 1 deletion docs/api/toc.json

Large diffs are not rendered by default.

9 changes: 7 additions & 2 deletions docs/index.json

Large diffs are not rendered by default.

206 changes: 206 additions & 0 deletions docs/xrefmap.yml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/Titanium.Cli/Titanium.Cli.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<SignAssembly>false</SignAssembly>
<VersionPrefix>7.0.6</VersionPrefix>
<VersionPrefix>7.0.7</VersionPrefix>
<Authors>Jehonathan Thomas</Authors>
<Description>Titanium Web Proxy CLI (titanium / twp).</Description>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
Expand Down
49 changes: 10 additions & 39 deletions src/Titanium.Inspector/Services/ExclusionPreview.cs
Original file line number Diff line number Diff line change
@@ -1,53 +1,23 @@
using System.Runtime.InteropServices;
using Titanium.Web.Proxy;
using Titanium.Web.Proxy.Helpers;

namespace Titanium.Inspector.Services;

/// <summary>Formats effective OS proxy bypass lists for the exclusions UI.</summary>
/// <summary>Formats exclusion summaries and opaque-tunnel reasons for Inspector UI.</summary>
public static class ExclusionPreview
{
public static string BuildWinInetOverride(InspectorSettings settings, string? currentOverride = null)
{
var proxySettings = MitmBypass.CreateSystemProxySettings(settings);
return proxySettings.BuildProxyOverride(currentOverride);
}

public static (string Label, string Value) FormatForCurrentOs(
InspectorSettings settings,
string? currentOverride = null)
{
var winInet = BuildWinInetOverride(settings, currentOverride);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return ("WinINET bypass list", winInet);
}

if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return ("Proxy bypass domains (networksetup)", UnixProxyBypassMapper.ToCommaSeparated(winInet));
}

if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
var gsettings = UnixProxyBypassMapper.ToGsettingsArray(winInet);
var noProxy = UnixProxyBypassMapper.ToNoProxyEnv(winInet);
return ("Ignored hosts / NO_PROXY", $"gsettings: {gsettings}\nNO_PROXY={noProxy}");
}

return ("Bypass list", winInet);
}

public static string ExclusionSummary(InspectorSettings settings)
public static string ExclusionSummary(InspectorSettings settings, int learnedCount = 0)
{
var bypass = settings.SystemProxyBypassHosts?.Count(h => !string.IsNullOrWhiteSpace(h)) ?? 0;
var tunnel = settings.DecryptSkipHosts?.Count(h => !string.IsNullOrWhiteSpace(h)) ?? 0;
if (bypass == 0 && tunnel == 0)
if (bypass == 0 && tunnel == 0 && learnedCount == 0)
{
return "";
}

return $"Exclusions: {bypass} OS bypass, {tunnel} tunnel-only";
var parts = new List<string>();
if (bypass > 0 || tunnel > 0)
parts.Add($"Exclusions: {bypass} OS bypass, {tunnel} tunnel-only");
if (learnedCount > 0)
parts.Add($"Learned: {learnedCount}");
return string.Join(" · ", parts);
}

public static string DescribeOpaqueReason(OpaqueTunnelReason reason) => reason switch
Expand All @@ -57,6 +27,7 @@ public static string ExclusionSummary(InspectorSettings settings)
OpaqueTunnelReason.BuiltInPinning => "Encrypted: pinning host (tunnel only)",
OpaqueTunnelReason.UserSkipList => "Encrypted: tunnel-only exclusion list",
OpaqueTunnelReason.UserOnlyList => "Encrypted: not on decrypt-only allowlist",
OpaqueTunnelReason.LearnedFailure => "Encrypted: auto-tunneled after decrypt failure",
_ => "",
};
}
48 changes: 44 additions & 4 deletions src/Titanium.Inspector/Services/InterceptionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ public InterceptionService(ISystemProxyController? systemProxy = null)
/// </summary>
public bool DecryptHttps { get; set; }

/// <summary>
/// When true, origin TLS handshake failures under MITM train an in-memory host bypass.
/// Default on for Inspector; wired to <see cref="ProxyServer.EnableDecryptFailureBypass"/>.
/// </summary>
public bool EnableDecryptFailureBypass { get; set; } = true;

/// <summary>Extra host patterns that skip HTTPS decryption (in addition to built-in bypasses).</summary>
public List<string> DecryptSkipHosts { get; set; } = [];

Expand Down Expand Up @@ -154,6 +160,34 @@ public bool AddViaHeader

public event EventHandler<SessionSnapshot>? SessionCaptured;
public event EventHandler<SessionSnapshot>? SessionUpdated;
public event EventHandler<DecryptFailureBypassEntry>? DecryptFailureBypassLearned;

/// <summary>Applies the learning toggle to a running proxy (no-op when not started).</summary>
public void ApplyDecryptFailureBypassSetting()
{
if (_proxy is null)
return;
_proxy.EnableDecryptFailureBypass = EnableDecryptFailureBypass;
}

public IReadOnlyList<DecryptFailureBypassEntry> GetDecryptFailureBypassEntries() =>
_proxy?.GetDecryptFailureBypassEntries() ?? Array.Empty<DecryptFailureBypassEntry>();

public bool RemoveDecryptFailureBypass(string host) =>
_proxy?.RemoveDecryptFailureBypass(host) ?? false;

public void ClearDecryptFailureBypass() => _proxy?.ClearDecryptFailureBypass();

private bool IsLearnedDecryptBypass(string? host)
{
if (!EnableDecryptFailureBypass || _proxy is null || string.IsNullOrWhiteSpace(host))
return false;
// O(1) cache consult — do not Snapshot the full list on every CONNECT.
return _proxy.ShouldBypassDecryptForLearnedHost(host);
}

private void OnDecryptFailureBypassChanged(object? sender, DecryptFailureBypassEntry e) =>
DecryptFailureBypassLearned?.Invoke(this, e);

public async Task StartAsync(IPAddress address, int port, CancellationToken cancellationToken = default)
{
Expand All @@ -171,6 +205,8 @@ public async Task StartAsync(IPAddress address, int port, CancellationToken canc
ApplyLoggingOptions(_loggingSettings);
_proxy.EnableHttpInterception = true;
_proxy.EnableRequestTimingCapture = true;
_proxy.EnableDecryptFailureBypass = EnableDecryptFailureBypass;
_proxy.DecryptFailureBypassChanged += OnDecryptFailureBypassChanged;
ApplyViaHeaderOption();
// Inspector eagerly buffers bodies for the session grid; 4 MiB trips too often on
// normal browsing (images, JS bundles) and RST'd the H2 stream. 32 MiB still bounds
Expand Down Expand Up @@ -390,6 +426,7 @@ public void Stop()
_proxy.OnRequestBodyWrite -= OnRequestBodyWriteThrottle;
_proxy.OnResponseBodyWrite -= OnResponseBodyWriteThrottle;
_proxy.ServerCertificateValidationCallback -= OnServerCertValidation;
_proxy.DecryptFailureBypassChanged -= OnDecryptFailureBypassChanged;
if (_endPoint is not null)
{
_endPoint.BeforeTunnelConnectRequest -= OnBeforeTunnelConnect;
Expand Down Expand Up @@ -954,10 +991,13 @@ private Task OnBeforeTunnelConnect(object sender, TunnelConnectSessionEventArgs
host,
DecryptSkipHosts,
userOnlyHosts: null);
e.DecryptSsl = DecryptHttps && !disableDecrypt;
var opaqueReason = disableDecrypt || !DecryptHttps
? MitmBypass.ResolveOpaqueReason(host, DecryptHttps, DecryptSkipHosts, userOnlyHosts: null)
: OpaqueTunnelReason.None;
var learnedBypass = !disableDecrypt && DecryptHttps && IsLearnedDecryptBypass(host);
e.DecryptSsl = DecryptHttps && !disableDecrypt && !learnedBypass;
var opaqueReason = learnedBypass
? OpaqueTunnelReason.LearnedFailure
: disableDecrypt || !DecryptHttps
? MitmBypass.ResolveOpaqueReason(host, DecryptHttps, DecryptSkipHosts, userOnlyHosts: null)
: OpaqueTunnelReason.None;

if (!Capturing)
{
Expand Down
1 change: 1 addition & 0 deletions src/Titanium.Inspector/Services/OpaqueTunnelReason.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ public enum OpaqueTunnelReason
BuiltInPinning,
UserSkipList,
UserOnlyList,
LearnedFailure,
}
10 changes: 5 additions & 5 deletions src/Titanium.Inspector/Services/OsTrustUxCopy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,15 +94,15 @@ public static string ExcludedHostsIntro()
return "OS bypass needs Capture → System proxy. Tunnel-only rules apply to every client that hits Inspector. Factory defaults are seeded into the lists below — edit freely or reset.";
}

public static string ExcludedHostsLoopbackHint()
public static string ProxyLocalhostTip()
{
if (OperatingSystem.IsMacOS())
return "When off, localhost is omitted from the macOS proxy bypass list so loopback can use the system proxy.";
return "When System proxy is on, send localhost through Inspector. Off adds localhost to the macOS proxy bypass list.";
if (OperatingSystem.IsLinux())
return "When off, localhost is omitted from NO_PROXY so loopback can use the system proxy.";
return "When System proxy is on, send localhost through Inspector. Off adds localhost to NO_PROXY.";
if (OperatingSystem.IsWindows())
return "When off, adds the Windows <-loopback> bypass rule so loopback skips the system proxy.";
return "Controls whether localhost traffic uses the system proxy.";
return "When System proxy is on, send localhost through Inspector (WinINET <-loopback>). Off lets loopback skip the proxy.";
return "When System proxy is on, send localhost through Inspector.";
}

public static string FormatStatus(CertificateOsTrustResult? result)
Expand Down
1 change: 1 addition & 0 deletions src/Titanium.Inspector/Services/SessionSearch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,7 @@ private static bool MatchOpaqueReason(SessionSnapshot s, string reasonToken)
"skip" or "skiplist" => s.OpaqueReason == OpaqueTunnelReason.UserSkipList,
"only" or "onlylist" => s.OpaqueReason == OpaqueTunnelReason.UserOnlyList,
"decrypt-off" or "decryptoff" => s.OpaqueReason == OpaqueTunnelReason.DecryptOff,
"learned" or "auto" => s.OpaqueReason == OpaqueTunnelReason.LearnedFailure,
_ => s.OpaqueReason.ToString().Equals(reasonToken, StringComparison.OrdinalIgnoreCase),
};
}
Expand Down
9 changes: 7 additions & 2 deletions src/Titanium.Inspector/Services/SettingsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,12 @@ public sealed class InspectorSettings
/// <summary>When true, localhost uses the proxy (WinINET &lt;-loopback&gt; / Unix NO_PROXY parity).</summary>
public bool ProxyLoopback { get; set; } = true;

/// <summary>
/// When true, Inspector auto-tunnels hosts whose origin TLS fails under decrypt (learned, session-only).
/// Default on.
/// </summary>
public bool EnableDecryptFailureBypass { get; set; } = true;

/// <summary>
/// When true, <see cref="SystemProxyBypassHosts"/> and <see cref="DecryptSkipHosts"/> were seeded
/// from factory defaults (or saved by the user). When false, load applies factory seed once.
Expand Down Expand Up @@ -194,7 +200,7 @@ public bool EnsureExclusionsSeeded()
return true;
}

/// <summary>Restores factory OS-bypass and tunnel-only lists (and loopback).</summary>
/// <summary>Restores factory OS-bypass and tunnel-only lists.</summary>
public void ResetExclusionsToFactoryDefaults()
{
ApplyFactoryExclusionDefaults(Current);
Expand All @@ -207,7 +213,6 @@ public static void ApplyFactoryExclusionDefaults(InspectorSettings settings)
{
settings.SystemProxyBypassHosts = MitmExclusionDefaults.SystemProxyBypassRules.ToList();
settings.DecryptSkipHosts = MitmExclusionDefaults.TunnelOnlyPinningDomains.ToList();
settings.ProxyLoopback = true;
}

/// <summary>
Expand Down
31 changes: 21 additions & 10 deletions src/Titanium.Inspector/Services/UpdateService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -522,23 +522,34 @@ public static bool IsMsiInstall(string baseDirectory)
return true;
}

try
if (HasInspectorInstallMarker(Registry.CurrentUser)
|| HasInspectorInstallMarker(Registry.LocalMachine))
{
using var key = Registry.CurrentUser.OpenSubKey(@"Software\justcoding121\TitaniumInspector");
if (key?.GetValue("installed") is not null)
{
return true;
}
}
catch
{
// ignore registry access issues
return true;
}
}

return false;
}

private static bool HasInspectorInstallMarker(RegistryKey hive)
{
if (!OperatingSystem.IsWindows())
{
return false;
}

try
{
using var key = hive.OpenSubKey(@"Software\justcoding121\TitaniumInspector");
return key?.GetValue("installed") is not null;
}
catch
{
return false;
}
}

public static string SuggestRid()
{
var arm = RuntimeInformation.OSArchitecture is Architecture.Arm64 or Architecture.Arm;
Expand Down
2 changes: 1 addition & 1 deletion src/Titanium.Inspector/Titanium.Inspector.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<SignAssembly>false</SignAssembly>
<VersionPrefix>7.0.6</VersionPrefix>
<VersionPrefix>7.0.7</VersionPrefix>
<Authors>Jehonathan Thomas</Authors>
<Description>Titanium Inspector desktop traffic debugger (PolyForm Noncommercial).</Description>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
Expand Down
Loading
Loading