Skip to content
Open
Changes from 1 commit
Commits
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
193 changes: 193 additions & 0 deletions proposals/security_info_web_request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
# Proposal: `SecurityInfo` in `webRequest`

**Summary**

An API proposal to extend the `webRequest` API to provide TLS/QUIC certificate information and connection state for a given request.

**Document Metadata**

**Author:** [Vlad Krot](https://github.com/vkrot-cell)

**Sponsoring Browser:** Google Chrome

**Contributors:** <other contributors emails or GitHub handles>

**Created:** 2025-11-05

**Related Issues:**
* [Mozilla Bug 1322748](https://bugzilla.mozilla.org/show_bug.cgi?id=1322748)
* [Previous abandoned Chromium Proposal](https://github.com/EFForg/webrequest-tlsinfo-api/blob/master/proposal.md)

## Motivation

### Objective

This API enables extensions to inspect the server certificate of a web request after a secure connection (TLS/QUIC) has been established. This fulfills a long-standing developer need for TLS introspection, allowing for the creation of advanced security and debugging tools.

#### Use Cases

The lack of TLS introspection prevents the development of a number of security-focused browser extensions. This API would enable extensions to:
* Replicate the functionality of security scoring tools.
* Provide more granular or prominent certificate chain introspection than the browser's built-in UI.
* Attempt to detect Man-in-the-Middle (MITM) attacks.
* Assist in debugging TLS deployment and configuration issues.
* Enforce custom security policies by blocking requests with insecure or non-compliant certificates.

### Known Consumers

This API would facilitate the migration of existing Firefox extensions to Chrome and other browsers that adopt this specification.
A notable example is [IndicateTLS](https://addons.mozilla.org/en-US/firefox/addon/indicatetls/), which relies on similar functionality.

## Specification

### Schema

This proposal introduces a new optional field, `securityInfo`, to the details object of the `webRequest.onHeadersReceived` event. Its presence is controlled by new `OnHeadersReceivedOptions`.

```typescript

export enum OnHeadersReceivedOptions {
// .. old fields,

// If used, then securityInfo will be provided.
"securityInfo",
// The option is used to provide securityInfo with raw bytes of a server certificate.
"securityInfoRawDer"
}

export interface Fingerprint {
/**
* SHA-256 hash of the certificate.
*/
sha256: string;
}

export interface CertificateInfo {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Firefox includes more properties in certificateInfo. In theory, all properties could be parsed from the certificate information with a JS library, except for isBuiltInRoot. This bit is not inherently a part of the certificate itself, but of the browser.

Question: Did you consciously choose to omit the other documented fields? Was there any particular reason to do so?

extraInfoSpec is specified to accept securityInfo and securityInfoRawDer, but without other properties the securityInfo option is of limited use. Most extensions don't pass the rawDER option

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Did you consciously choose to omit the other documented fields? Was there any particular reason to do so?" - So I only included fields necessary to accomplish "google internal" goal. I did not evaluate deeply how extension devs would use it. Thank you for this research.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with you on this: extraInfoSpec is specified to accept securityInfo and securityInfoRawDer, but without other properties the securityInfo option is of limited use. Most extensions don't pass the rawDER option

However, rawDER is absolute must to include for "google-internal" needs. And yes, securityInfo then is not super useful, only fingerprint and state.

Is it a blocker to not have enough fields?

fingerprint: Fingerprint;
rawDER: Uint8Array;
}

/**
* Represents the state of the network connection.
*/
export enum ConnectionState {
/**
* The TLS handshake failed (for example, the certificate has expired).
*/
Broken = "broken",

/**
* The connection is not a TLS connection.
*/
Insecure = "insecure",

/**
* The connection is a secure TLS connection.
*/
Secure = "secure"
}

export interface SecurityInfo {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This proposal only includes certficates, but that is not enough to fulfill the stated objective of "TLS introspection". Connection-specific information that are useful includes:

  • Cipher suite (cipherSuite)
  • key exchange algorithm (keaGroupName)
  • key (well key size at least - secretKeyLength)
  • protocol version (protocolVersion)

There are more properties, see #882 (comment) for an example of the actual API result,
and #882 (comment) for a write-up and actual API usage.

The properties are currently listed in "future work". Even if you're going to implement this in two steps, it would still be nice for the proposal to be complete. Are you considering to implement additional fields, and if so, which?

/**
* The array contains a single CertificateInfo object, for the server certificate, or empty in case
* the connection state is "insecure" (http).
*/
certificates: CertificateInfo[];
/**
* State of the connection.
*/
state: ConnectionState;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As commented in #882 (comment), just state is not enough to tell the difference between trusted and untrusted. Can we add at least isUntrusted?

I see that you have specified the "broken" behavior for the scenario of "user has explicitely allowed it in the browser UI", below. This is not what Firefox has currently implemented. "broken" is a really broken state; if the certificate is good other than the expiry date, and the user accepted it, then the state is considered "secure" in Firefox.

Copy link
Author

@vkrot-cell vkrot-cell Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I answered in #882 (comment)

}

export interface OnHeadersReceivedDetails {
// ... existing fields
/**
* Information about the security of the connection. This is only
* present if "securityInfo" is passed in the extraInfoSpec parameter
* and the request used a secure connection.
*/
securityInfo?: SecurityInfo;
}

```

### Behavior

* New `onHeadersReceivedOptions` are necessary for performance reasons. They specify whether ssl data must be kept for as long as the web request is alive. Without them, unnecessary data can be kept for longer. Which is very undesirable especially in post quantum cryptography, since certificates can take a significant amount of memory space.
Note that Firefox has no such an option, because it only supports blocking web requests call while there’s still ssl data.
Comment on lines +120 to +121
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part can be dropped now. We agreed to include it in the event details, and Firefox could also implement that.

Suggested change
* New `onHeadersReceivedOptions` are necessary for performance reasons. They specify whether ssl data must be kept for as long as the web request is alive. Without them, unnecessary data can be kept for longer. Which is very undesirable especially in post quantum cryptography, since certificates can take a significant amount of memory space.
Note that Firefox has no such an option, because it only supports blocking web requests call while there’s still ssl data.


* A new `securityInfo` object can be obtained in the `onHeadersReceived` event listener.

* To receive this information, an extension **must** include `"securityInfo"` or `"securityInfoRawDer"` in the `extraInfoSpec` array when calling `addListener`. This opt-in design prevents performance overhead for the majority of extensions that don't need this data.

* The `securityInfo` object will only be populated for requests made over a secure protocol (e.g., HTTPS, WSS) where the TLS/QUIC handshake has successfully completed or also in case of certificate errors.
Browsers interrupt connections when there's a certificate error, unless user has explicitely allowed it in the browser UI, only in this case it is possible to have SecurityInfo with `state = "broken"`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have commented elsewhere; in Firefox state is still "secure"` if the user intentionally accepted an expired certificate.


* `certificates` - will contain only the leaf server certificate. This is done for future extensibility and Firefox API compatibility, because there they also provide a leaf if [certificateChain](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/getSecurityInfo#certificatechain) is not included in getSecurityInfo options.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: the majority of extensions calling getSecurityInfo also set the certificateChain option to true. Not including all certificate information may be too limiting in practice.

Copy link
Author

@vkrot-cell vkrot-cell Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I totally agree, but still I would prefer to leave this for another iteration of the API, which was developed to be compatible with this option.


* `state` - State of the connection. One of:
* `"broken"`: the TLS handshake failed (for example, the certificate has expired)
* `"insecure"`: the connection is not a TLS connection
* `"secure"`: the connection is a secure TLS connection
* Note that Firefox extension API has a `“weak”` state of connection, which does not exist in Chrome.

* The `CertificateInfo.rawDER` field contains the raw certificate bytes in DER format, which can be parsed by the extension using a third-party library. This field is only provided if `extraInfoSpec` array includes `"securityInfoRawDer"`. The reason for it is a performance optimization to not pass raw bytes when
it is not necessary, and compatibility with Firefox extensions API.

### Example

Here is an example of an extension listening for web requests to log the server's certificate fingerprint.

```javascript
chrome.webRequest.onHeadersReceived.addListener(
(details) => {
if (details.securityInfo && details.securityInfo.state == "secure") {
console.log(`Received certificate for ${details.url}. The fingerprint is ${details.securityInfo.certificates[0].fingerprint.sha256}`);
}
},
// Filter for requests.
{ urls: ["<all_urls>"] },
// Opt-in to receive the security info.
["securityInfo"]
);
```

## Security and Privacy

### Exposed Sensitive Data

This API exposes the server's leaf certificate for a given connection. This is public information that is exchanged as part of any standard TLS handshake.
So, no sensitive data is exposed.

## Alternatives

### Existing Workarounds

Currently, extensions can only approximate this functionality by capturing a request's URL and sending it to a separate backend service. This service must then perform its own TLS handshake with the destination server to retrieve the certificate. This approach has significant drawbacks:

* **Inaccuracy**: It does not guarantee that the extension sees the same certificate the browser saw. This can be caused by load balancing, geographic routing, or a Man-in-the-Middle (MITM) attack.
* **Performance**: It introduces significant latency and network overhead for both the user and the extension's backend.
* **Privacy**: It requires sending the user's browsing-related URLs to a third-party server, which is a privacy concern.

This API resolves all three issues by providing the exact, browser-verified information directly.

### Open Web API

This proposal extends Web Request API which is not available on Open Web.
However, the Open Web has [Controlled Frame API](https://wicg.github.io/controlled-frame) which includes WebRequest as well. It is planned to extend Controlled Frame API, although in a separate web proposal.

## Implementation Notes

This proposal is heavily inspired by Firefox's `webRequest.getSecurityInfo()` to promote cross-browser compatibility. However, it adapts the design to fit Chrome's non-blocking, event-driven architecture (Manifest V3):

* **Event-Based vs. Function Call**: Instead of a separate `getSecurityInfo(requestId)` function (as in Firefox), this API injects the data into the existing `onHeadersReceived` event.
* **Rationale**: In a non-blocking model, the internal network and certificate data associated with a request is often discarded immediately after the request pipeline advances. A separate asynchronous getSecurityInfo call would be racy and unreliable. By "opting in" with extraInfoSpec, the extension signals the browser to hold onto this data just long enough to deliver it with the onHeadersReceived event, ensuring data availability without blocking.


## Future Work

This proposal focuses on the most critical and high-priority data (the leaf certificate). The `SecurityInfo` object can be extended in the future to include:

* The full certificate chain (`CertificateInfo[]`).
* Specific TLS parameters (e.g., protocol version, cipher suite).
* Additional properties like `isDomainMismatch` or `isEV`.