mirror of
https://github.com/tlsnotary/tlsn-extension.git
synced 2026-01-08 22:58:04 -05:00
feat: add plugin permission control system (#211)
* Add plugin permission control system - Add RequestPermission interface to plugin-sdk types - Extend PluginConfig with requests[] and urls[] permission arrays - Create permissionValidator.ts with validation functions: - validateProvePermission() for prove() calls - validateOpenWindowPermission() for openWindow() calls - deriveProxyUrl() for automatic proxy URL derivation - matchesPathnamePattern() using URLPattern API - Update SessionManager to validate permissions before execution - Update ConfirmationManager to pass permissions to popup - Update ConfirmPopup UI to display network and URL permissions - Add URLPattern type declaration to global.d.ts - Update demo plugins (twitter.js, swissbank.js) with permissions - Add comprehensive tests for permission validation - Add Chrome Web Store listing documentation * Fix lint --------- Co-authored-by: Hendrik Eeckhaut <hendrik@eeckhaut.org>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tlsn-monorepo",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.0-alpha.13",
|
||||
"private": true,
|
||||
"description": "TLSN Extension monorepo with plugin SDK",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
const config = {
|
||||
name: 'Swiss Bank Prover',
|
||||
description: 'This plugin will prove your Swiss Bank account balance.',
|
||||
requests: [
|
||||
{
|
||||
method: 'GET',
|
||||
host: 'swissbank.tlsnotary.org',
|
||||
pathname: '/balances',
|
||||
verifierUrl: 'http://localhost:7047',
|
||||
},
|
||||
],
|
||||
urls: [
|
||||
'https://swissbank.tlsnotary.org/*',
|
||||
],
|
||||
};
|
||||
|
||||
const host = 'swissbank.tlsnotary.org';
|
||||
|
||||
@@ -8,6 +8,17 @@
|
||||
const config = {
|
||||
name: 'X Profile Prover',
|
||||
description: 'This plugin will prove your X.com profile.',
|
||||
requests: [
|
||||
{
|
||||
method: 'GET',
|
||||
host: 'api.x.com',
|
||||
pathname: '/1.1/account/settings.json',
|
||||
verifierUrl: 'http://localhost:7047',
|
||||
},
|
||||
],
|
||||
urls: [
|
||||
'https://x.com/*',
|
||||
],
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
|
||||
@@ -21,6 +21,9 @@
|
||||
"browser": true,
|
||||
"node": true
|
||||
},
|
||||
"globals": {
|
||||
"URLPattern": "readonly"
|
||||
},
|
||||
"settings": {
|
||||
"import/resolver": "typescript"
|
||||
},
|
||||
|
||||
179
packages/extension/STORE_LISTING.md
Normal file
179
packages/extension/STORE_LISTING.md
Normal file
@@ -0,0 +1,179 @@
|
||||
# Chrome Web Store Listing
|
||||
|
||||
## Extension Name
|
||||
TLSNotary
|
||||
|
||||
## Description
|
||||
|
||||
TLSNotary Extension enables you to create cryptographic proofs of any data you access on the web. Prove ownership of your online accounts, verify your credentials, or demonstrate that you received specific information from a website—all without exposing your private data.
|
||||
|
||||
### What is TLSNotary?
|
||||
|
||||
TLSNotary is an open-source protocol that allows you to prove the authenticity of any data fetched from websites. When you visit an HTTPS website, your browser establishes a secure TLS (Transport Layer Security) connection. TLSNotary leverages this existing security infrastructure to generate verifiable proofs that specific data was genuinely returned by a particular website, without requiring any cooperation from the website itself.
|
||||
|
||||
### Why Install This Extension?
|
||||
|
||||
**Prove What You See Online**
|
||||
Have you ever needed to prove that a website displayed certain information? Whether it's proving your account balance, ownership of a social media profile, or the contents of a private message, TLSNotary creates tamper-proof cryptographic evidence that stands up to scrutiny.
|
||||
|
||||
**Privacy-Preserving Proofs**
|
||||
Unlike screenshots or screen recordings that can be easily faked and expose all your data, TLSNotary proofs are:
|
||||
- Cryptographically verifiable and cannot be forged
|
||||
- Selectively disclosed—reveal only the specific data points you choose while keeping everything else private
|
||||
- Generated without exposing your login credentials or session tokens to third parties
|
||||
|
||||
**No Website Cooperation Required**
|
||||
TLSNotary works with any HTTPS website without requiring the website to implement any special support. The proof generation happens entirely in your browser, using the standard TLS connection you already have.
|
||||
|
||||
### Key Features
|
||||
|
||||
**Cryptographic Data Proofs**
|
||||
Generate unforgeable proofs that specific data was returned by a website. Each proof contains cryptographic evidence tied to the website's TLS certificate, making it impossible to fabricate or alter.
|
||||
|
||||
**Selective Disclosure**
|
||||
Choose exactly what information to reveal in your proofs. Prove your account balance without revealing your transaction history. Verify your identity without exposing your full profile. Show specific fields while keeping everything else hidden.
|
||||
|
||||
**Plugin System**
|
||||
Build and run custom plugins for specific proof workflows. The extension includes a Developer Console with a code editor for creating and testing plugins. Use React-like hooks for reactive UI updates and easy integration with the proof generation pipeline.
|
||||
|
||||
**Multi-Window Management**
|
||||
Open and manage multiple browser windows for tracking different proof sessions. Each window maintains its own request history, allowing you to work on multiple proofs simultaneously.
|
||||
|
||||
**Request Interception**
|
||||
Automatically capture HTTP/HTTPS requests from managed windows. View intercepted requests in real-time through an intuitive overlay interface. Select the specific requests you want to include in your proofs.
|
||||
|
||||
**Sandboxed Execution**
|
||||
Plugins run in an isolated QuickJS WebAssembly environment for security. Network and filesystem access are disabled by default, ensuring plugins cannot access data beyond what you explicitly provide.
|
||||
|
||||
### Use Cases
|
||||
|
||||
**Identity Verification**
|
||||
Prove you own a specific social media account, email address, or online profile without sharing your password or giving third-party access to your account.
|
||||
|
||||
**Financial Attestations**
|
||||
Demonstrate your account balance, transaction history, or financial standing to lenders, landlords, or other parties who require proof—without exposing your complete financial information.
|
||||
|
||||
**Content Authentication**
|
||||
Create verifiable evidence of online content that cannot be forged. Useful for legal documentation, journalism, research, or any situation where proving the authenticity of web content matters.
|
||||
|
||||
**Credential Verification**
|
||||
Prove your credentials, certifications, or qualifications as displayed by official issuing organizations, without relying on easily-faked screenshots.
|
||||
|
||||
**Privacy-Preserving KYC**
|
||||
Complete Know Your Customer (KYC) requirements while revealing only the minimum necessary information. Prove you meet eligibility criteria without exposing your full identity.
|
||||
|
||||
### How It Works
|
||||
|
||||
1. **Install the Extension**: Add TLSNotary to Chrome from the Web Store.
|
||||
|
||||
2. **Access the Developer Console**: Right-click on any webpage and select "Developer Console" to open the plugin editor.
|
||||
|
||||
3. **Run a Plugin**: Use the built-in example plugins or write your own. Plugins define what data to capture and which parts to include in proofs.
|
||||
|
||||
4. **Generate Proofs**: The extension captures your HTTPS traffic, creates a cryptographic commitment with a verifier server, and generates a proof of the data you selected.
|
||||
|
||||
5. **Share Selectively**: Export proofs containing only the data you want to reveal. Verifiers can confirm the proof's authenticity without seeing your hidden information.
|
||||
|
||||
### Technical Details
|
||||
|
||||
- **Manifest V3**: Built on Chrome's latest extension platform for improved security and performance
|
||||
- **WebAssembly Powered**: Uses compiled Rust code via WebAssembly for efficient cryptographic operations
|
||||
- **Plugin SDK**: Comprehensive SDK for developing custom proof workflows with TypeScript support
|
||||
- **Open Source**: Full source code available for review and community contributions
|
||||
|
||||
### Requirements
|
||||
|
||||
- Chrome browser version 109 or later (for offscreen document support)
|
||||
- A verifier server for proof generation (public servers available or run your own)
|
||||
- Active internet connection for HTTPS request interception
|
||||
|
||||
### Privacy and Security
|
||||
|
||||
TLSNotary is designed with privacy as a core principle:
|
||||
|
||||
- **No Data Collection**: The extension does not collect, store, or transmit your browsing data to any third party
|
||||
- **Local Processing**: All proof generation happens locally in your browser
|
||||
- **Open Source**: The entire codebase is publicly auditable
|
||||
- **Selective Disclosure**: You control exactly what data appears in proofs
|
||||
- **Sandboxed Plugins**: Plugin code runs in an isolated environment with no access to your system
|
||||
|
||||
### Getting Started
|
||||
|
||||
After installation:
|
||||
|
||||
1. Right-click anywhere on a webpage
|
||||
2. Select "Developer Console" from the context menu
|
||||
3. Review the example plugin code in the editor
|
||||
4. Click "Run Code" to execute the plugin
|
||||
5. Follow the on-screen instructions to generate your first proof
|
||||
|
||||
For detailed documentation, tutorials, and plugin development guides, visit the TLSNotary documentation site.
|
||||
|
||||
### About TLSNotary
|
||||
|
||||
TLSNotary is an open-source project dedicated to enabling data portability and verifiable provenance for web data. The protocol has been in development since 2013 and has undergone multiple security audits. Join our community to learn more about trustless data verification and contribute to the future of verifiable web data.
|
||||
|
||||
### Support and Feedback
|
||||
|
||||
- Documentation: https://docs.tlsnotary.org/
|
||||
- GitHub: https://github.com/tlsnotary/tlsn-extension
|
||||
- Issues: https://github.com/tlsnotary/tlsn-extension/issues
|
||||
|
||||
Licensed under MIT and Apache 2.0 licenses.
|
||||
|
||||
---
|
||||
|
||||
## Screenshot Captions
|
||||
|
||||
### Screenshot 1: Plugin UI
|
||||
**Caption:** "Prove any web data without compromising privacy"
|
||||
|
||||
### Screenshot 2: Permission Popup
|
||||
**Caption:** "Control exactly what data you reveal in each proof"
|
||||
|
||||
### Screenshot 3: Developer Console
|
||||
**Caption:** "Build custom plugins with the built-in code editor"
|
||||
|
||||
---
|
||||
|
||||
## Permission Justifications
|
||||
|
||||
The following permissions are required for the extension's core functionality of generating cryptographic proofs of web data:
|
||||
|
||||
### offscreen
|
||||
|
||||
**Justification:** Required to create offscreen documents for executing WebAssembly-based cryptographic operations. The TLSNotary proof generation uses Rust compiled to WebAssembly, which requires DOM APIs unavailable in Manifest V3 service workers. The offscreen document hosts the plugin execution environment (QuickJS sandbox) and the cryptographic prover that generates TLS proofs. Without this permission, the extension cannot perform its core function of generating cryptographic proofs.
|
||||
|
||||
### webRequest
|
||||
|
||||
**Justification:** Required to intercept HTTP/HTTPS requests from browser windows managed by the extension. When users initiate a proof generation workflow, the extension opens a managed browser window and captures the HTTP request/response data that will be included in the cryptographic proof. This interception is essential for capturing the exact data the user wants to prove, including request headers and URLs. The extension only intercepts requests in windows it explicitly manages for proof generation—not general browsing activity.
|
||||
|
||||
### storage
|
||||
|
||||
**Justification:** Required to persist user preferences and plugin configurations across browser sessions. The extension stores user settings such as preferred verifier server URLs and plugin code. This ensures users do not need to reconfigure the extension each time they restart their browser.
|
||||
|
||||
### activeTab
|
||||
|
||||
**Justification:** Required to access information about the currently active tab when the user initiates a proof generation workflow. The extension needs to read the current page URL and title to display context in the Developer Console and to determine which requests belong to the active proof session.
|
||||
|
||||
### tabs
|
||||
|
||||
**Justification:** Required to create, query, and manage browser tabs for proof generation workflows. When a plugin opens a managed window for capturing web data, the extension must create new tabs, send messages to content scripts in those tabs, and track which tabs belong to which proof session. This is essential for the multi-window proof management feature.
|
||||
|
||||
### windows
|
||||
|
||||
**Justification:** Required to create and manage browser windows for proof generation sessions. The extension opens dedicated browser windows when users run proof plugins, allowing isolation of the proof capture session from regular browsing. The extension tracks these windows to route intercepted requests to the correct proof session and to clean up resources when windows are closed.
|
||||
|
||||
### contextMenus
|
||||
|
||||
**Justification:** Required to add the "Developer Console" menu item to the browser's right-click context menu. This provides the primary access point for users to open the plugin development and execution interface. Without this permission, users would have no convenient way to access the Developer Console for writing and running proof plugins.
|
||||
|
||||
### Host Permissions (<all_urls>)
|
||||
|
||||
**Justification:** Required because TLSNotary is designed to generate cryptographic proofs of data from any HTTPS website. Users need to prove data from various websites including social media platforms, financial services, government portals, and any other web service. The extension cannot predict which websites users will need to generate proofs for, so it requires broad host access to intercept requests and inject content scripts for the proof overlay UI. The extension only actively intercepts requests in windows explicitly managed for proof generation—it does not monitor or collect data from general browsing activity.
|
||||
|
||||
---
|
||||
|
||||
## Single Purpose Description
|
||||
|
||||
TLSNotary Extension has a single purpose: to generate cryptographic proofs of web data. All requested permissions directly support this purpose by enabling request interception for proof capture, window management for proof sessions, and background processing for cryptographic operations.
|
||||
BIN
packages/extension/src/assets/img/store-icon.png
Normal file
BIN
packages/extension/src/assets/img/store-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
@@ -1,12 +1,6 @@
|
||||
import browser from 'webextension-polyfill';
|
||||
import { logger } from '@tlsn/common';
|
||||
|
||||
export interface PluginConfig {
|
||||
name: string;
|
||||
description: string;
|
||||
version?: string;
|
||||
author?: string;
|
||||
}
|
||||
import { PluginConfig } from '@tlsn/plugin-sdk/src/types';
|
||||
|
||||
interface PendingConfirmation {
|
||||
requestId: string;
|
||||
@@ -30,7 +24,7 @@ export class ConfirmationManager {
|
||||
|
||||
// Popup window dimensions
|
||||
private readonly POPUP_WIDTH = 600;
|
||||
private readonly POPUP_HEIGHT = 400;
|
||||
private readonly POPUP_HEIGHT = 550;
|
||||
|
||||
constructor() {
|
||||
// Listen for window removal to handle popup close
|
||||
@@ -192,6 +186,18 @@ export class ConfirmationManager {
|
||||
if (config.author) {
|
||||
params.set('author', encodeURIComponent(config.author));
|
||||
}
|
||||
|
||||
// Pass permission arrays as JSON
|
||||
if (config.requests && config.requests.length > 0) {
|
||||
params.set(
|
||||
'requests',
|
||||
encodeURIComponent(JSON.stringify(config.requests)),
|
||||
);
|
||||
}
|
||||
|
||||
if (config.urls && config.urls.length > 0) {
|
||||
params.set('urls', encodeURIComponent(JSON.stringify(config.urls)));
|
||||
}
|
||||
}
|
||||
|
||||
return `${baseUrl}?${params.toString()}`;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Confirmation Popup Styles
|
||||
// Size: 600x400 pixels
|
||||
// Size: 600x550 pixels
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
body {
|
||||
width: 600px;
|
||||
height: 400px;
|
||||
height: 550px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
||||
@@ -154,6 +154,114 @@ body {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
// Permissions Section
|
||||
&__permissions {
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
&__permissions-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
&__permission-group {
|
||||
margin-bottom: 12px;
|
||||
|
||||
label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: #8b8b9a;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
&__permission-icon {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&__permission-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 8px;
|
||||
max-height: 120px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
&__permission-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
font-size: 13px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__method {
|
||||
background: rgba(102, 126, 234, 0.2);
|
||||
color: #8fa4f0;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
min-width: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__host {
|
||||
color: #e8e8e8;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&__pathname {
|
||||
color: #8b8b9a;
|
||||
font-family: 'SF Mono', Monaco, 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__url {
|
||||
color: #8fa4f0;
|
||||
font-family: 'SF Mono', Monaco, 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
&__no-permissions {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
padding: 12px;
|
||||
background: rgba(139, 139, 154, 0.1);
|
||||
border: 1px solid rgba(139, 139, 154, 0.3);
|
||||
border-radius: 8px;
|
||||
margin-top: 12px;
|
||||
|
||||
p {
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
color: #8b8b9a;
|
||||
}
|
||||
}
|
||||
|
||||
&__error-message {
|
||||
font-size: 14px;
|
||||
color: #ff6b6b;
|
||||
|
||||
@@ -7,11 +7,21 @@ import './index.scss';
|
||||
// Initialize logger at DEBUG level for popup (no IndexedDB access)
|
||||
logger.init(LogLevel.DEBUG);
|
||||
|
||||
interface RequestPermission {
|
||||
method: string;
|
||||
host: string;
|
||||
pathname: string;
|
||||
verifierUrl: string;
|
||||
proxyUrl?: string;
|
||||
}
|
||||
|
||||
interface PluginInfo {
|
||||
name: string;
|
||||
description: string;
|
||||
version?: string;
|
||||
author?: string;
|
||||
requests?: RequestPermission[];
|
||||
urls?: string[];
|
||||
}
|
||||
|
||||
const ConfirmPopup: React.FC = () => {
|
||||
@@ -26,6 +36,8 @@ const ConfirmPopup: React.FC = () => {
|
||||
const description = params.get('description');
|
||||
const version = params.get('version');
|
||||
const author = params.get('author');
|
||||
const requestsParam = params.get('requests');
|
||||
const urlsParam = params.get('urls');
|
||||
const reqId = params.get('requestId');
|
||||
|
||||
if (!reqId) {
|
||||
@@ -36,6 +48,26 @@ const ConfirmPopup: React.FC = () => {
|
||||
setRequestId(reqId);
|
||||
|
||||
if (name) {
|
||||
// Parse permission arrays from JSON
|
||||
let requests: RequestPermission[] | undefined;
|
||||
let urls: string[] | undefined;
|
||||
|
||||
try {
|
||||
if (requestsParam) {
|
||||
requests = JSON.parse(decodeURIComponent(requestsParam));
|
||||
}
|
||||
} catch (e) {
|
||||
logger.warn('Failed to parse requests param:', e);
|
||||
}
|
||||
|
||||
try {
|
||||
if (urlsParam) {
|
||||
urls = JSON.parse(decodeURIComponent(urlsParam));
|
||||
}
|
||||
} catch (e) {
|
||||
logger.warn('Failed to parse urls param:', e);
|
||||
}
|
||||
|
||||
setPluginInfo({
|
||||
name: decodeURIComponent(name),
|
||||
description: description
|
||||
@@ -43,6 +75,8 @@ const ConfirmPopup: React.FC = () => {
|
||||
: 'No description provided',
|
||||
version: version ? decodeURIComponent(version) : undefined,
|
||||
author: author ? decodeURIComponent(author) : undefined,
|
||||
requests,
|
||||
urls,
|
||||
});
|
||||
} else {
|
||||
// No plugin info available - show unknown plugin warning
|
||||
@@ -174,6 +208,62 @@ const ConfirmPopup: React.FC = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Permissions Section */}
|
||||
{(pluginInfo.requests || pluginInfo.urls) && (
|
||||
<div className="confirm-popup__permissions">
|
||||
<h2 className="confirm-popup__permissions-title">Permissions</h2>
|
||||
|
||||
{pluginInfo.requests && pluginInfo.requests.length > 0 && (
|
||||
<div className="confirm-popup__permission-group">
|
||||
<label>
|
||||
<span className="confirm-popup__permission-icon">🌐</span>
|
||||
Network Requests
|
||||
</label>
|
||||
<ul className="confirm-popup__permission-list">
|
||||
{pluginInfo.requests.map((req, index) => (
|
||||
<li key={index} className="confirm-popup__permission-item">
|
||||
<span className="confirm-popup__method">
|
||||
{req.method}
|
||||
</span>
|
||||
<span className="confirm-popup__host">{req.host}</span>
|
||||
<span className="confirm-popup__pathname">
|
||||
{req.pathname}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{pluginInfo.urls && pluginInfo.urls.length > 0 && (
|
||||
<div className="confirm-popup__permission-group">
|
||||
<label>
|
||||
<span className="confirm-popup__permission-icon">🔗</span>
|
||||
Allowed URLs
|
||||
</label>
|
||||
<ul className="confirm-popup__permission-list">
|
||||
{pluginInfo.urls.map((url, index) => (
|
||||
<li key={index} className="confirm-popup__permission-item">
|
||||
<span className="confirm-popup__url">{url}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* No permissions warning */}
|
||||
{!pluginInfo.requests && !pluginInfo.urls && !isUnknown && (
|
||||
<div className="confirm-popup__no-permissions">
|
||||
<span className="confirm-popup__warning-icon">!</span>
|
||||
<p>
|
||||
This plugin has no permissions defined. It will not be able to
|
||||
make network requests or open browser windows.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isUnknown && (
|
||||
<div className="confirm-popup__warning">
|
||||
<span className="confirm-popup__warning-icon">!</span>
|
||||
|
||||
47
packages/extension/src/global.d.ts
vendored
47
packages/extension/src/global.d.ts
vendored
@@ -2,3 +2,50 @@ declare module '*.png' {
|
||||
const value: any;
|
||||
export = value;
|
||||
}
|
||||
|
||||
// URLPattern Web API (available in Chrome 95+)
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/URLPattern
|
||||
interface URLPatternInit {
|
||||
protocol?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
hostname?: string;
|
||||
port?: string;
|
||||
pathname?: string;
|
||||
search?: string;
|
||||
hash?: string;
|
||||
baseURL?: string;
|
||||
}
|
||||
|
||||
interface URLPatternComponentResult {
|
||||
input: string;
|
||||
groups: Record<string, string | undefined>;
|
||||
}
|
||||
|
||||
interface URLPatternResult {
|
||||
inputs: [string | URLPatternInit];
|
||||
protocol: URLPatternComponentResult;
|
||||
username: URLPatternComponentResult;
|
||||
password: URLPatternComponentResult;
|
||||
hostname: URLPatternComponentResult;
|
||||
port: URLPatternComponentResult;
|
||||
pathname: URLPatternComponentResult;
|
||||
search: URLPatternComponentResult;
|
||||
hash: URLPatternComponentResult;
|
||||
}
|
||||
|
||||
declare class URLPattern {
|
||||
constructor(input: string | URLPatternInit, baseURL?: string);
|
||||
|
||||
test(input: string | URLPatternInit): boolean;
|
||||
exec(input: string | URLPatternInit): URLPatternResult | null;
|
||||
|
||||
readonly protocol: string;
|
||||
readonly username: string;
|
||||
readonly password: string;
|
||||
readonly hostname: string;
|
||||
readonly port: string;
|
||||
readonly pathname: string;
|
||||
readonly search: string;
|
||||
readonly hash: string;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "TLSN Extension",
|
||||
"description": "A Chrome extension for TLSN",
|
||||
"version": "0.1.0.13",
|
||||
"name": "TLSNotary",
|
||||
"description": "A Chrome extension for TLSNotary",
|
||||
"options_page": "options.html",
|
||||
"background": {
|
||||
"service_worker": "background.bundle.js"
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import Host, { Parser } from '@tlsn/plugin-sdk/src';
|
||||
import { ProveManager } from './ProveManager';
|
||||
import { Method } from 'tlsn-js';
|
||||
import { DomJson, Handler } from '@tlsn/plugin-sdk/src/types';
|
||||
import { DomJson, Handler, PluginConfig } from '@tlsn/plugin-sdk/src/types';
|
||||
import { processHandlers } from './rangeExtractor';
|
||||
import { logger } from '@tlsn/common';
|
||||
import {
|
||||
validateProvePermission,
|
||||
validateOpenWindowPermission,
|
||||
} from './permissionValidator';
|
||||
|
||||
export class SessionManager {
|
||||
private host: Host;
|
||||
private proveManager: ProveManager;
|
||||
private initPromise: Promise<void>;
|
||||
private currentConfig: PluginConfig | null = null;
|
||||
|
||||
constructor() {
|
||||
this.host = new Host({
|
||||
@@ -36,6 +41,13 @@ export class SessionManager {
|
||||
throw new Error('Invalid URL');
|
||||
}
|
||||
|
||||
// Validate permissions before proceeding
|
||||
validateProvePermission(
|
||||
requestOptions,
|
||||
proverOptions,
|
||||
this.currentConfig,
|
||||
);
|
||||
|
||||
// Build sessionData with defaults + user-provided data
|
||||
const sessionData: Record<string, string> = {
|
||||
...proverOptions.sessionData,
|
||||
@@ -131,6 +143,9 @@ export class SessionManager {
|
||||
url: string,
|
||||
options?: { width?: number; height?: number; showOverlay?: boolean },
|
||||
) => {
|
||||
// Validate permissions before proceeding
|
||||
validateOpenWindowPermission(url, this.currentConfig);
|
||||
|
||||
const chromeRuntime = (
|
||||
global as unknown as { chrome?: { runtime?: any } }
|
||||
).chrome?.runtime;
|
||||
@@ -164,6 +179,14 @@ export class SessionManager {
|
||||
if (!chromeRuntime?.onMessage) {
|
||||
throw new Error('Chrome runtime not available');
|
||||
}
|
||||
|
||||
// Extract and store plugin config before execution for permission validation
|
||||
this.currentConfig = await this.extractConfig(code);
|
||||
logger.debug(
|
||||
'[SessionManager] Extracted plugin config:',
|
||||
this.currentConfig,
|
||||
);
|
||||
|
||||
return this.host.executePlugin(code, {
|
||||
eventEmitter: {
|
||||
addListener: (listener: (message: any) => void) => {
|
||||
|
||||
141
packages/extension/src/offscreen/permissionValidator.ts
Normal file
141
packages/extension/src/offscreen/permissionValidator.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import { PluginConfig, RequestPermission } from '@tlsn/plugin-sdk/src/types';
|
||||
|
||||
/**
|
||||
* Derives the default proxy URL from a verifier URL.
|
||||
* https://verifier.example.com -> wss://verifier.example.com/proxy?token={host}
|
||||
* http://localhost:7047 -> ws://localhost:7047/proxy?token={host}
|
||||
*/
|
||||
export function deriveProxyUrl(
|
||||
verifierUrl: string,
|
||||
targetHost: string,
|
||||
): string {
|
||||
const url = new URL(verifierUrl);
|
||||
const protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
return `${protocol}//${url.host}/proxy?token=${targetHost}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches a URL pathname against a URLPattern pathname pattern.
|
||||
* Uses the URLPattern API for pattern matching.
|
||||
*/
|
||||
export function matchesPathnamePattern(
|
||||
pathname: string,
|
||||
pattern: string,
|
||||
): boolean {
|
||||
try {
|
||||
// URLPattern is available in modern browsers
|
||||
const urlPattern = new URLPattern({ pathname: pattern });
|
||||
return urlPattern.test({ pathname });
|
||||
} catch {
|
||||
// Fallback: simple wildcard matching
|
||||
// Convert * to regex .* and ** to multi-segment match
|
||||
const regexPattern = pattern
|
||||
.replace(/\*\*/g, '<<<MULTI>>>')
|
||||
.replace(/\*/g, '[^/]*')
|
||||
.replace(/<<<MULTI>>>/g, '.*');
|
||||
const regex = new RegExp(`^${regexPattern}$`);
|
||||
return regex.test(pathname);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that a prove() call is allowed by the plugin's permissions.
|
||||
* Throws an error if the permission is not granted.
|
||||
*/
|
||||
export function validateProvePermission(
|
||||
requestOptions: { url: string; method: string },
|
||||
proverOptions: { verifierUrl: string; proxyUrl: string },
|
||||
config: PluginConfig | null,
|
||||
): void {
|
||||
// If no config or no requests permissions defined, deny by default
|
||||
if (!config?.requests || config.requests.length === 0) {
|
||||
throw new Error(
|
||||
`Permission denied: Plugin has no request permissions defined. ` +
|
||||
`Cannot make ${requestOptions.method} request to ${requestOptions.url}`,
|
||||
);
|
||||
}
|
||||
|
||||
const url = new URL(requestOptions.url);
|
||||
const requestMethod = requestOptions.method.toUpperCase();
|
||||
|
||||
const matchingPermission = config.requests.find((perm: RequestPermission) => {
|
||||
// Check method (case-insensitive)
|
||||
const methodMatch = perm.method.toUpperCase() === requestMethod;
|
||||
if (!methodMatch) return false;
|
||||
|
||||
// Check host
|
||||
const hostMatch = perm.host === url.hostname;
|
||||
if (!hostMatch) return false;
|
||||
|
||||
// Check pathname pattern
|
||||
const pathnameMatch = matchesPathnamePattern(url.pathname, perm.pathname);
|
||||
if (!pathnameMatch) return false;
|
||||
|
||||
// Check verifier URL
|
||||
const verifierMatch = perm.verifierUrl === proverOptions.verifierUrl;
|
||||
if (!verifierMatch) return false;
|
||||
|
||||
// Check proxy URL (use derived default if not specified in permission)
|
||||
const expectedProxyUrl =
|
||||
perm.proxyUrl ?? deriveProxyUrl(perm.verifierUrl, url.hostname);
|
||||
const proxyMatch = expectedProxyUrl === proverOptions.proxyUrl;
|
||||
if (!proxyMatch) return false;
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!matchingPermission) {
|
||||
const permissionsSummary = config.requests
|
||||
.map(
|
||||
(p: RequestPermission) =>
|
||||
` - ${p.method} ${p.host}${p.pathname} (verifier: ${p.verifierUrl})`,
|
||||
)
|
||||
.join('\n');
|
||||
|
||||
throw new Error(
|
||||
`Permission denied: Plugin does not have permission to make ${requestMethod} request to ${url.hostname}${url.pathname} ` +
|
||||
`with verifier ${proverOptions.verifierUrl} and proxy ${proverOptions.proxyUrl}.\n` +
|
||||
`Declared request permissions:\n${permissionsSummary}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that an openWindow() call is allowed by the plugin's permissions.
|
||||
* Throws an error if the permission is not granted.
|
||||
*/
|
||||
export function validateOpenWindowPermission(
|
||||
url: string,
|
||||
config: PluginConfig | null,
|
||||
): void {
|
||||
// If no config or no urls permissions defined, deny by default
|
||||
if (!config?.urls || config.urls.length === 0) {
|
||||
throw new Error(
|
||||
`Permission denied: Plugin has no URL permissions defined. ` +
|
||||
`Cannot open URL ${url}`,
|
||||
);
|
||||
}
|
||||
|
||||
const hasPermission = config.urls.some((allowedPattern: string) => {
|
||||
try {
|
||||
// Try URLPattern first
|
||||
const pattern = new URLPattern(allowedPattern);
|
||||
return pattern.test(url);
|
||||
} catch {
|
||||
// Fallback: treat as simple glob pattern
|
||||
// Convert * to regex
|
||||
const regexPattern = allowedPattern
|
||||
.replace(/[.+^${}()|[\]\\]/g, '\\$&') // Escape special regex chars except *
|
||||
.replace(/\*/g, '.*');
|
||||
const regex = new RegExp(`^${regexPattern}$`);
|
||||
return regex.test(url);
|
||||
}
|
||||
});
|
||||
|
||||
if (!hasPermission) {
|
||||
throw new Error(
|
||||
`Permission denied: Plugin does not have permission to open URL ${url}.\n` +
|
||||
`Declared URL permissions:\n${config.urls.map((u: string) => ` - ${u}`).join('\n')}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
295
packages/extension/tests/offscreen/permissionValidator.test.ts
Normal file
295
packages/extension/tests/offscreen/permissionValidator.test.ts
Normal file
@@ -0,0 +1,295 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
deriveProxyUrl,
|
||||
matchesPathnamePattern,
|
||||
validateProvePermission,
|
||||
validateOpenWindowPermission,
|
||||
} from '../../src/offscreen/permissionValidator';
|
||||
import { PluginConfig } from '@tlsn/plugin-sdk/src/types';
|
||||
|
||||
describe('deriveProxyUrl', () => {
|
||||
it('should derive wss proxy URL from https verifier', () => {
|
||||
const result = deriveProxyUrl('https://verifier.example.com', 'api.x.com');
|
||||
expect(result).toBe('wss://verifier.example.com/proxy?token=api.x.com');
|
||||
});
|
||||
|
||||
it('should derive ws proxy URL from http verifier', () => {
|
||||
const result = deriveProxyUrl('http://localhost:7047', 'api.x.com');
|
||||
expect(result).toBe('ws://localhost:7047/proxy?token=api.x.com');
|
||||
});
|
||||
|
||||
it('should preserve port in proxy URL', () => {
|
||||
const result = deriveProxyUrl(
|
||||
'https://verifier.example.com:8080',
|
||||
'api.x.com',
|
||||
);
|
||||
expect(result).toBe(
|
||||
'wss://verifier.example.com:8080/proxy?token=api.x.com',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('matchesPathnamePattern', () => {
|
||||
it('should match exact pathname', () => {
|
||||
expect(matchesPathnamePattern('/api/v1/users', '/api/v1/users')).toBe(true);
|
||||
});
|
||||
|
||||
it('should not match different pathname', () => {
|
||||
expect(matchesPathnamePattern('/api/v1/users', '/api/v1/posts')).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it('should match wildcard at end', () => {
|
||||
expect(matchesPathnamePattern('/api/v1/users/123', '/api/v1/users/*')).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it('should match wildcard in middle', () => {
|
||||
expect(
|
||||
matchesPathnamePattern(
|
||||
'/api/v1/users/123/profile',
|
||||
'/api/v1/users/*/profile',
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should not match wildcard across segments', () => {
|
||||
// Single * should only match one segment
|
||||
expect(
|
||||
matchesPathnamePattern('/api/v1/users/123/456', '/api/v1/users/*'),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should match double wildcard across segments', () => {
|
||||
expect(
|
||||
matchesPathnamePattern(
|
||||
'/api/v1/users/123/456/profile',
|
||||
'/api/v1/users/**',
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateProvePermission', () => {
|
||||
const baseConfig: PluginConfig = {
|
||||
name: 'Test Plugin',
|
||||
description: 'Test',
|
||||
requests: [
|
||||
{
|
||||
method: 'GET',
|
||||
host: 'api.x.com',
|
||||
pathname: '/1.1/account/settings.json',
|
||||
verifierUrl: 'https://verifier.tlsnotary.org',
|
||||
},
|
||||
{
|
||||
method: 'POST',
|
||||
host: 'api.example.com',
|
||||
pathname: '/api/v1/*',
|
||||
verifierUrl: 'http://localhost:7047',
|
||||
proxyUrl: 'ws://localhost:7047/proxy?token=api.example.com',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
it('should allow matching request with exact pathname', () => {
|
||||
expect(() =>
|
||||
validateProvePermission(
|
||||
{ url: 'https://api.x.com/1.1/account/settings.json', method: 'GET' },
|
||||
{
|
||||
verifierUrl: 'https://verifier.tlsnotary.org',
|
||||
proxyUrl: 'wss://verifier.tlsnotary.org/proxy?token=api.x.com',
|
||||
},
|
||||
baseConfig,
|
||||
),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('should allow matching request with wildcard pathname', () => {
|
||||
expect(() =>
|
||||
validateProvePermission(
|
||||
{ url: 'https://api.example.com/api/v1/users', method: 'POST' },
|
||||
{
|
||||
verifierUrl: 'http://localhost:7047',
|
||||
proxyUrl: 'ws://localhost:7047/proxy?token=api.example.com',
|
||||
},
|
||||
baseConfig,
|
||||
),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('should deny request with wrong method', () => {
|
||||
expect(() =>
|
||||
validateProvePermission(
|
||||
{ url: 'https://api.x.com/1.1/account/settings.json', method: 'POST' },
|
||||
{
|
||||
verifierUrl: 'https://verifier.tlsnotary.org',
|
||||
proxyUrl: 'wss://verifier.tlsnotary.org/proxy?token=api.x.com',
|
||||
},
|
||||
baseConfig,
|
||||
),
|
||||
).toThrow('Permission denied');
|
||||
});
|
||||
|
||||
it('should deny request with wrong host', () => {
|
||||
expect(() =>
|
||||
validateProvePermission(
|
||||
{
|
||||
url: 'https://api.twitter.com/1.1/account/settings.json',
|
||||
method: 'GET',
|
||||
},
|
||||
{
|
||||
verifierUrl: 'https://verifier.tlsnotary.org',
|
||||
proxyUrl: 'wss://verifier.tlsnotary.org/proxy?token=api.twitter.com',
|
||||
},
|
||||
baseConfig,
|
||||
),
|
||||
).toThrow('Permission denied');
|
||||
});
|
||||
|
||||
it('should deny request with wrong pathname', () => {
|
||||
expect(() =>
|
||||
validateProvePermission(
|
||||
{ url: 'https://api.x.com/1.1/users/show.json', method: 'GET' },
|
||||
{
|
||||
verifierUrl: 'https://verifier.tlsnotary.org',
|
||||
proxyUrl: 'wss://verifier.tlsnotary.org/proxy?token=api.x.com',
|
||||
},
|
||||
baseConfig,
|
||||
),
|
||||
).toThrow('Permission denied');
|
||||
});
|
||||
|
||||
it('should deny request with wrong verifier URL', () => {
|
||||
expect(() =>
|
||||
validateProvePermission(
|
||||
{ url: 'https://api.x.com/1.1/account/settings.json', method: 'GET' },
|
||||
{
|
||||
verifierUrl: 'http://localhost:7047',
|
||||
proxyUrl: 'ws://localhost:7047/proxy?token=api.x.com',
|
||||
},
|
||||
baseConfig,
|
||||
),
|
||||
).toThrow('Permission denied');
|
||||
});
|
||||
|
||||
it('should deny request with wrong proxy URL', () => {
|
||||
expect(() =>
|
||||
validateProvePermission(
|
||||
{ url: 'https://api.x.com/1.1/account/settings.json', method: 'GET' },
|
||||
{
|
||||
verifierUrl: 'https://verifier.tlsnotary.org',
|
||||
proxyUrl: 'wss://malicious.com/proxy?token=api.x.com',
|
||||
},
|
||||
baseConfig,
|
||||
),
|
||||
).toThrow('Permission denied');
|
||||
});
|
||||
|
||||
it('should deny request when no permissions defined', () => {
|
||||
const noPermConfig: PluginConfig = {
|
||||
name: 'No Perm Plugin',
|
||||
description: 'Test',
|
||||
};
|
||||
|
||||
expect(() =>
|
||||
validateProvePermission(
|
||||
{ url: 'https://api.x.com/test', method: 'GET' },
|
||||
{
|
||||
verifierUrl: 'https://verifier.tlsnotary.org',
|
||||
proxyUrl: 'wss://verifier.tlsnotary.org/proxy?token=api.x.com',
|
||||
},
|
||||
noPermConfig,
|
||||
),
|
||||
).toThrow('Plugin has no request permissions defined');
|
||||
});
|
||||
|
||||
it('should deny request when config is null', () => {
|
||||
expect(() =>
|
||||
validateProvePermission(
|
||||
{ url: 'https://api.x.com/test', method: 'GET' },
|
||||
{
|
||||
verifierUrl: 'https://verifier.tlsnotary.org',
|
||||
proxyUrl: 'wss://verifier.tlsnotary.org/proxy?token=api.x.com',
|
||||
},
|
||||
null,
|
||||
),
|
||||
).toThrow('Plugin has no request permissions defined');
|
||||
});
|
||||
|
||||
it('should be case-insensitive for HTTP method', () => {
|
||||
expect(() =>
|
||||
validateProvePermission(
|
||||
{ url: 'https://api.x.com/1.1/account/settings.json', method: 'get' },
|
||||
{
|
||||
verifierUrl: 'https://verifier.tlsnotary.org',
|
||||
proxyUrl: 'wss://verifier.tlsnotary.org/proxy?token=api.x.com',
|
||||
},
|
||||
baseConfig,
|
||||
),
|
||||
).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateOpenWindowPermission', () => {
|
||||
const baseConfig: PluginConfig = {
|
||||
name: 'Test Plugin',
|
||||
description: 'Test',
|
||||
urls: [
|
||||
'https://x.com/*',
|
||||
'https://twitter.com/*',
|
||||
'https://example.com/specific/page',
|
||||
],
|
||||
};
|
||||
|
||||
it('should allow matching URL with wildcard', () => {
|
||||
expect(() =>
|
||||
validateOpenWindowPermission('https://x.com/user/profile', baseConfig),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('should allow exact URL match', () => {
|
||||
expect(() =>
|
||||
validateOpenWindowPermission(
|
||||
'https://example.com/specific/page',
|
||||
baseConfig,
|
||||
),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('should deny URL not in permissions', () => {
|
||||
expect(() =>
|
||||
validateOpenWindowPermission(
|
||||
'https://malicious.com/phishing',
|
||||
baseConfig,
|
||||
),
|
||||
).toThrow('Permission denied');
|
||||
});
|
||||
|
||||
it('should deny URL when no permissions defined', () => {
|
||||
const noPermConfig: PluginConfig = {
|
||||
name: 'No Perm Plugin',
|
||||
description: 'Test',
|
||||
};
|
||||
|
||||
expect(() =>
|
||||
validateOpenWindowPermission('https://x.com/test', noPermConfig),
|
||||
).toThrow('Plugin has no URL permissions defined');
|
||||
});
|
||||
|
||||
it('should deny URL when config is null', () => {
|
||||
expect(() =>
|
||||
validateOpenWindowPermission('https://x.com/test', null),
|
||||
).toThrow('Plugin has no URL permissions defined');
|
||||
});
|
||||
|
||||
it('should match wildcard at end of URL', () => {
|
||||
expect(() =>
|
||||
validateOpenWindowPermission('https://x.com/', baseConfig),
|
||||
).not.toThrow();
|
||||
expect(() =>
|
||||
validateOpenWindowPermission('https://x.com/any/path/here', baseConfig),
|
||||
).not.toThrow();
|
||||
});
|
||||
});
|
||||
@@ -151,4 +151,7 @@ describe('extractConfig', () => {
|
||||
expect(result?.name).toBe('Backtick Plugin');
|
||||
expect(result?.description).toBe('Uses template literals');
|
||||
});
|
||||
|
||||
// Note: The regex-based extractConfig cannot handle array fields like requests and urls.
|
||||
// For full config extraction including permissions, use Host.getPluginConfig() which uses QuickJS sandbox.
|
||||
});
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
WindowMessage,
|
||||
Handler,
|
||||
PluginConfig,
|
||||
RequestPermission,
|
||||
} from './types';
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
|
||||
@@ -738,8 +739,11 @@ async function waitForWindow(callback: () => Promise<any>, retry = 0): Promise<a
|
||||
|
||||
/**
|
||||
* Extract plugin configuration from plugin code without executing it.
|
||||
* Uses regex-based parsing to extract the config object from the source code
|
||||
* without running any JavaScript.
|
||||
* Uses regex-based parsing to extract the config object from the source code.
|
||||
*
|
||||
* Note: This regex-based approach cannot extract complex fields like arrays
|
||||
* (requests, urls). For full config extraction including permissions, use
|
||||
* Host.getPluginConfig() which uses the QuickJS sandbox.
|
||||
*
|
||||
* @param code - The plugin source code
|
||||
* @returns The plugin config object, or null if extraction fails
|
||||
@@ -792,7 +796,7 @@ export async function extractConfig(code: string): Promise<PluginConfig | null>
|
||||
}
|
||||
|
||||
// Export types
|
||||
export type { PluginConfig };
|
||||
export type { PluginConfig, RequestPermission };
|
||||
|
||||
// Re-export LogLevel for consumers
|
||||
export { LogLevel } from '@tlsn/common';
|
||||
|
||||
@@ -185,6 +185,33 @@ export type AllHandler = {
|
||||
|
||||
export type Handler = StartLineHandler | HeadersHandler | BodyHandler | AllHandler;
|
||||
|
||||
/**
|
||||
* Permission for making HTTP requests via prove()
|
||||
*/
|
||||
export interface RequestPermission {
|
||||
/** HTTP method (GET, POST, etc.) */
|
||||
method: string;
|
||||
|
||||
/** Host name (e.g., "api.x.com") */
|
||||
host: string;
|
||||
|
||||
/**
|
||||
* URL pathname pattern (URLPattern syntax, e.g., "/1.1/users/*")
|
||||
* Supports wildcards: * matches any single segment, ** matches multiple segments
|
||||
*/
|
||||
pathname: string;
|
||||
|
||||
/** Verifier URL to use for this request */
|
||||
verifierUrl: string;
|
||||
|
||||
/**
|
||||
* Proxy URL for WebSocket connection.
|
||||
* Defaults to ws/wss of verifierUrl's /proxy endpoint if not specified.
|
||||
* e.g., verifierUrl "https://verifier.example.com" -> "wss://verifier.example.com/proxy?token={host}"
|
||||
*/
|
||||
proxyUrl?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin configuration object that all plugins must export
|
||||
*/
|
||||
@@ -197,4 +224,17 @@ export interface PluginConfig {
|
||||
version?: string;
|
||||
/** Optional author name */
|
||||
author?: string;
|
||||
|
||||
/**
|
||||
* Allowed HTTP requests the plugin can make via prove().
|
||||
* Empty array or undefined means no prove() calls allowed.
|
||||
*/
|
||||
requests?: RequestPermission[];
|
||||
|
||||
/**
|
||||
* Allowed URLs the plugin can open via openWindow().
|
||||
* Supports URLPattern syntax (e.g., "https://x.com/*").
|
||||
* Empty array or undefined means no openWindow() calls allowed.
|
||||
*/
|
||||
urls?: string[];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user