// TLS Profiles const { Modal, useToast } = window.UI; function PageTLS({ state, dispatch }) { const { fingerprints } = state; const [addOpen, setAddOpen] = React.useState(false); const [test, setTest] = React.useState(null); const toast = useToast(); const presets = fingerprints.filter(f => !f.custom); const customs = fingerprints.filter(f => f.custom); const StatusPill = ({ tested }) => ( {tested ? "verified" : "untested"} ); return (

TLS Profiles

Fingerprints applied per request via egress.tls_fingerprint. Built-ins use uTLS; custom profiles accept raw JA3, JA4, Akamai, or YAML/JSON specs.

Built-in presets

{presets.length} profiles · backed by uTLS
{presets.map(f => ( ))}
NamePresetLast testStatus
{f.name} {f.preset} {f.lastTest}

Custom profiles

raw strings & spec-driven
{customs.length === 0 ?
No custom fingerprints yet.
: ( {customs.map(f => ( ))}
NameRaw inputLast testStatus
{f.name} {f.ja3 || f.ja4 || f.akamai} {f.lastTest}
)}
setTest(null)} title={test ? `Test ${test.name}` : ""} footer={}> setAddOpen(false)} dispatch={dispatch} toast={toast}/>
); } function AddTLSModal({ open, onClose, dispatch, toast }) { const [form, setForm] = React.useState({ name: "", source: "preset", preset: "chrome", ja3: "", ja4: "", akamai: "", spec: `ja3: "771,4865-4866-4867-49196-49195-52393-49200-49199-52392-49162-49161-49172-49171,0-23-65281-10-11-16-5-13-18-51-45-43-27-21,29-23-24-25,0" ja4: "t13d2014h2_000a,002f,0035,009c,009d,1301,1302,1303,c008,c009,c00a,c012,c013,c014,c02b,c02c,c02f,c030,cca8,cca9_0000,0005,000a,000b,000d,0012,0015,0017,001b,002b,002d,0033,ff01_0403,0804,0401,0503,0805,0805,0501,0806,0601,0201" akamai: "HEADER_TABLE_SIZE=65536;ENABLE_PUSH=0;INITIAL_WINDOW_SIZE=6291456;MAX_HEADER_LIST_SIZE=262144|15663105|method,authority,scheme,path" extra_fp: tls_signature_algorithms: - ecdsa_secp256r1_sha256 - rsa_pss_rsae_sha256 - rsa_pkcs1_sha256 - ecdsa_secp384r1_sha384 - rsa_pss_rsae_sha384 - rsa_pkcs1_sha384 - rsa_pss_rsae_sha512 - rsa_pkcs1_sha512 - rsa_pkcs1_sha1 tls_cert_compression: zlib tls_grease: true`, }); const set = (k, v) => setForm(f => ({ ...f, [k]: v })); const submit = async () => { if (!form.name.trim()) { toast("Profile name is required"); return; } let config = null; let configText = ""; if (form.source === "preset") { config = { preset: form.preset }; } else if (form.source === "raw") { config = {}; if (form.ja3.trim()) config.ja3 = form.ja3.trim(); if (form.ja4.trim()) config.ja4 = form.ja4.trim(); if (form.akamai.trim()) config.akamai = form.akamai.trim(); if (!config.ja3 && !config.ja4) { toast("JA3 or JA4 raw is required"); return; } } else { configText = form.spec.trim(); if (!configText) { toast("JSON/YAML spec is required"); return; } } const ok = await dispatch({type:"addFingerprint", name: form.name.trim(), config, configText}); if (ok) onClose(); }; return ( }>
set("name", e.target.value)} placeholder="chrome-131-strict"/>
{form.source === "preset" ? (
) : ( form.source === "raw" ? ( <>
) : (
Supports raw ja3, ja4, akamai, captured JSON with tls/http2, and curl_cffi extra_fp.
) )}
); } function TLSTest({ fp, dispatch }) { const [target, setTarget] = React.useState("https://tls.browserleaks.com/json"); const [busy, setBusy] = React.useState(false); const [out, setOut] = React.useState(null); if (!fp) return null; const observed = out?.observed || {}; const observedRows = flattenObservedFields(observed); const observedJA3 = observed.tls?.ja3 || observed.ja3 || observed.ja3_text || observed.ja3n_text || observed.ja3_hash || observed.ja3n_hash || "—"; const observedJA4 = observed.tls?.ja4 || observed.tls?.ja4_r || observed.ja4 || observed.ja4_r || observed.ja4_o || "—"; const observedAkamai = observed.http2?.akamai_fingerprint || observed.akamai || observed.akamai_text || observed.akamai_hash || "—"; const observedIP = observed.ip || observed.origin || observed.remote_addr || observed.remote_ip || "—"; const observedRegion = observed.country_code || observed.country || observed.region || observed.geoip?.country_code || observed.geo?.country_code || "—"; return (
setTarget(e.target.value)}/>
{out && (
{out.status === "ok" ? "handshake ok" : "handshake failed"}
Type
{out.type || "preset"}
Preset
{out.preset || "—"}
HTTP
{out.http_status || "—"}
HTTP version
{out.http_proto || out.http_version || "—"}
Latency
{out.latency_ms ?? "—"}ms
IP
{observedIP}
Region
{observedRegion}
Observed JA3
{observedJA3}
Observed JA4
{observedJA4}
Observed Akamai
{observedAkamai}
{out.error && <>
Error
{out.error}
}
{observedRows.length > 0 && (
Observed JSON
{observedRows.map(row => (
{row.path}
{row.value}
))}
)}
)}
); } function flattenObservedFields(value, prefix = "") { if (!value || typeof value !== "object") return []; return Object.entries(value).flatMap(([key, child]) => { const path = prefix ? `${prefix}.${key}` : key; if (child && typeof child === "object" && !Array.isArray(child)) { const nested = flattenObservedFields(child, path); return nested.length ? nested : [{ path, value: "{}" }]; } return [{ path, value: formatObservedValue(child) }]; }); } function formatObservedValue(value) { if (value === null || value === undefined || value === "") return "—"; if (typeof value === "string") return value; if (typeof value === "number" || typeof value === "boolean") return String(value); try { return JSON.stringify(value, null, 2); } catch { return String(value); } } window.PageTLS = PageTLS;