v147 · Isolated Web Apps · Security
Compatibility Lab
Checks whether the exact Controlled Frame webRequest.onHeadersReceived SecurityInfo hook is available, then exercises the documented details.securityInfo shape with fallback states for non-IWA pages.
API probes
SecurityInfo field availability
| SecurityInfo field | Context | Chrome 147 | Description |
|---|---|---|---|
| chrome.webRequest.onHeadersReceived | IWA only | ✓ | Response-header phase where SecurityInfo is exposed |
| extraInfoSpec: securityInfo | IWA only | ✓ | Requests details.securityInfo |
| extraInfoSpec: securityInfoRawDer | IWA only | ✓ | Requests certificate raw DER bytes |
| securityInfo.state | IWA only | ✓ | "secure" | "insecure" | "broken" | "weak" |
| securityInfo.certificates[].fingerprint.sha256 | IWA only | ✓ Chrome 147 | Browser-verified certificate fingerprint |
| PerformanceResourceTiming.nextHopProtocol | Any HTTPS | ✓ | Protocol only; not a certificate API |
Live security context probe
TLS-adjacent APIs (accessible outside IWA)
Click "Run probe" to inspect available TLS-adjacent APIs…
IWA SecurityInfo pattern
/* WebRequest.SecurityInfo in ControlledFrame — IWA pattern */
/* Available in Isolated Web Apps only.
The pattern: intercept HTTPS request → get cert fingerprint →
verify the same fingerprint in a Direct Socket TLS handshake. */
/* In manifest.json (IWA): */
// "permissions": ["webRequest", "webRequestBlocking"]
/* In app code: intercept and extract SecurityInfo */
chrome.webRequest.onHeadersReceived.addListener(
(details) => {
// details.securityInfo is present only when 'securityInfo'
// or 'securityInfoRawDer' is requested in extraInfoSpec.
const info = details.securityInfo;
if (info?.state === 'secure' && info.certificates?.length) {
const cert = info.certificates[0];
const fp256 = cert.fingerprint?.sha256; // hex string
// Store for Direct Socket certificate pinning
trustedFingerprints.set(new URL(details.url).hostname, fp256);
}
},
{ urls: ['https://*/*'] },
['securityInfo'] // or ['securityInfoRawDer'] for raw DER bytes
);
/* Direct Socket connection — verify against stored fingerprint */
async function connectVerified(hostname, port) {
const stored = trustedFingerprints.get(hostname);
if (!stored) throw new Error('No fingerprint — do HTTPS preflight first');
const socket = new TCPSocket(hostname, port, { useSecureTransport: true });
const { readable } = await socket.opened;
// Compare TLS cert fingerprint from the socket with stored value
// to confirm you're connecting to the same server
return socket;
}
/* For non-IWA contexts: use PerformanceResourceTiming for protocol info */
function getTLSInfo(url) {
const entries = performance.getEntriesByName(url, 'resource');
if (!entries.length) return null;
const e = entries[0];
return {
protocol: e.nextHopProtocol,
secureConnStart: e.secureConnectionStart,
connectDuration: e.connectEnd - e.connectStart,
tlsHandshakeDuration: e.requestStart - e.secureConnectionStart,
};
}