crx3

package module
v1.7.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 24, 2026 License: Apache-2.0 Imports: 31 Imported by: 5

README

crx3

Coverage Status Documentation Go Report Card Actions

🤖 AI-ready Chrome extension tooling via Model Context Protocol (MCP)
A modern, comprehensive toolset for CRX3 Chrome extension management — pack, unpack, download, analyze, and automate with AI.

👉 MCP Configuration Guide — Setup instructions for Cursor, Claude Desktop, Opencode, Crush & more.


✨ Features

Feature Description
📦 Pack & Unpack Create and extract signed CRX3 packages with key management
🗜️ Zip & Unzip Handle extension archives with flexible output paths
⬇️ Download Fetch extensions from Chrome Web Store by ID or URL
🔑 ID Generation Generate extension IDs from public keys (SHA-256)
🔍 Analyze Inspect manifest, permissions, and extension metadata
🤖 MCP Support Native AI integration via Model Context Protocol

🤖 MCP Integration

This tool is AI-compatible through Model Context Protocol (MCP), enabling seamless interaction with:

  • Cursor IDE — Chat with your extensions, auto-generate packs
  • Claude Desktop — Natural language CRX3 operations
  • Opencode — Automated extension workflows
  • Crush — AI-assisted extension management
  • Any MCP client — HTTP/SSE or stdio transport

Automate CRX3 operations through natural language commands powered by AI.

# Start MCP server (stdio mode for local AI clients)
crx3 mcp

# Or run over HTTP/SSE for remote clients 
crx3 mcp --listen=locahost:3000
crx3 mcp --listen=localhost:3000 --sse 

📖 See MCP.md for detailed setup instructions.


🛠️ Command Reference

Tool Purpose
crx3 pack Pack directory/zip into signed .crx extension
crx3 unpack Extract .crx file contents to directory
crx3 download Download .crx extension by ID or Chrome Web Store URL
crx3 search Search Chrome Web Store by name/keywords (via DuckDuckGo)
crx3 zip Create .zip archive from directory
crx3 unzip Extract .zip archive contents
crx3 base64 Encode file to Base64 string
crx3 getid Extract Chrome Extension ID from .crx or directory
crx3 scan List/filter downloaded extensions in workspace
crx3 workspace Get absolute path to workspace root
crx3 version Show CRX3 tool version
crx3 mcp Start MCP server for AI integration

💡 All commands support --help for detailed usage: crx3 pack --help


📦 Installation

Via Homebrew (macOS/Linux)
brew tap mmadfox/tap https://github.com/mmadfox/homebrew-tap
brew install mmadfox/tap/crx3
Via install.sh (Linux/macOS)
curl -sSfL https://raw.githubusercontent.com/mmadfox/go-crx3/master/install.sh | bash -s
Via Go
go install github.com/mediabuyerbot/go-crx3/crx3@latest
Via Release Binary

Download pre-built binaries from Releases.

Verify Installation
crx3 version
# Output: crx3 version x.y.z

🚀 Quick Start Examples

Pack an extension
# Basic pack (auto-generates key if missing)
crx3 pack ./my-extension -o ./build/extension.crx3

# Pack with existing private key
crx3 pack ./my-extension -p ./keys/private.pem -o ./build/extension.crx3
Unpack and inspect
# Extract CRX3 to directory
crx3 unpack ./extension.crx3 -o ./extracted 

# View manifest
cat ./extracted/manifest.json

# Without creating a subdirectory 
crx3 unpack ./extension.crx3 -o ./extracted -s
Download from Chrome Web Store
# By extension ID
crx3 download blipmdconlkpinefehnmjammfjpmpbjk -o ./extensions/lighthouse

# Wihtout auto unpack
crx3 download blipmdconlkpinefehnmjammfjpmpbjk --unpack=false

# By full URL
crx3 download "https://chrome.google.com/webstore/detail/lighthouse/blipmdconlkpinefehnmjammfjpmpbjk"
Generate extension ID
# From existing CRX3 file
crx3 id ./extension.crx3
# Output: dgmchnekcpklnjppdmmjlgpmpohmpmgp

# From public key
crx3 id -k ./keys/public.pem
Key management
# Generate new RSA key pair
crx3 keygen ./keys/my-key.pem

# The same command creates both private.pem and extracts public key internally
Archive operations
# Zip a directory
crx3 zip ./my-extension -o ./archive.zip

# Unzip archive
crx3 unzip ./archive.zip -o ./output
Base64 encoding (for embedding)
# Encode CRX3 to base64
crx3 base64 ./extension.crx3

# Save to file
crx3 base64 ./extension.crx3 -o ./encoded.txt
Search Chrome Web Store
# Search by keyword
crx3 search "ad blocker" 

💻 Code Examples (Go API)

Pack a zip file or directory
import crx3 "github.com/mediabuyerbot/go-crx3"

// Basic pack
if err := crx3.Extension("/path/to/file.zip").Pack(nil); err != nil {
    panic(err)
}

// Pack with private key
pk, err := crx3.LoadPrivateKey("/path/to/key.pem")
if err != nil { panic(err) }
if err := crx3.Extension("/path/to/file.zip").Pack(pk); err != nil {
    panic(err)
}

// Pack to custom output path
if err := crx3.Extension("/path/to/file.zip").PackTo("/path/to/ext.crx", pk); err != nil {
    panic(err)
}
Unpack extension
import crx3 "github.com/mediabuyerbot/go-crx3"

if err := crx3.Extension("/path/to/ext.crx").Unpack(); err != nil {
   panic(err)
}
Download from Web Store
import crx3 "github.com/mediabuyerbot/go-crx3"

extensionID := "blipmdconlkpinefehnmjammfjpmpbjk"
if err := crx3.DownloadFromWebStore(extensionID, "/path/to/ext.crx"); err != nil {
    panic(err)
}
Generate/Load Keys
import crx3 "github.com/mediabuyerbot/go-crx3"

// Generate new key
pk, err := crx3.NewPrivateKey()
if err != nil { panic(err) }

// Save and load
if err := crx3.SavePrivateKey("/path/to/key.pem", pk); err != nil { panic(err) }
pk, err = crx3.LoadPrivateKey("/path/to/key.pem")
Helper Functions
import crx3 "github.com/mediabuyerbot/go-crx3"

// Type detection
crx3.Extension("/path/to/ext.zip").IsZip()   // true
crx3.Extension("/path/to/ext").IsDir()       // true  
crx3.Extension("/path/to/ext.crx").IsCRX3()  // true

// Get extension ID
id, err := crx3.Extension("/path/to/ext.crx").ID()
Base64 Encoding
import crx3 "github.com/mediabuyerbot/go-crx3"

b, err := crx3.Extension("/path/to/ext.crx").Base64()
if err != nil { panic(err) }
fmt.Println(string(b))

⚙️ Advanced Usage

MCP Server Modes
Mode Command Use Case
stdio crx3 mcp Local AI clients (Cursor, Claude Desktop)
HTTP/SSE crx3 mcp --listen=:3000 Remote clients, custom integrations
Common Flags
# Global flags
--workdir, -w    # Set working directory (default: .)
--logfile, -f    # Path to log file (default: stderr)

# MCP-specific flags  
--listen, -l     # Run HTTP server at address (e.g., :3000)
--tools.disabled, -d  # Disable specific tools (comma-separated)
--tools.show, -s      # List available tools and exit
Example: Secure MCP Setup
# Run MCP with restricted tools and sandboxed directory
crx3 mcp \
  --workdir=/safe/extensions \
  --tools.disabled=crx3_download,crx3_unpack \
  --logfile=/var/log/crx3-mcp.log

🧪 Development

# Generate protobuf code
make proto

# Run tests with coverage
make test/cover

# View coverage report
go tool cover -html=coverage.out

📚 Resources


📄 License

go-crx3 is released under the Apache 2.0 License.
See LICENSE for details.


Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrUnknownFileExtension  = errors.New("crx3: unknown file extension")
	ErrUnsupportedFileFormat = errors.New("crx3: unsupported file format")
	ErrExtensionNotSpecified = errors.New("crx3: extension id not specified")
	ErrPathNotFound          = errors.New("crx3: filepath not found")
	ErrPrivateKeyNotFound    = errors.New("crx3: private key not found")
	ErrInvalidReader         = errors.New("crx3: invalid reader")
)

Functions

func Base64 added in v1.3.0

func Base64(filename string) (b []byte, err error)

Base64 encodes an extension file to a base64 string. It returns a bytes and an error encountered while encodes, if any.

func CopyFile added in v1.4.0

func CopyFile(src, dst string) (int64, error)

CopyFile copies the contents of the source file 'src' to the destination file 'dst'. It returns the number of bytes copied and any encountered error. If the source file does not exist, is not a regular file, or other errors occur during the copy, it returns an error with a descriptive message.

func DownloadFromWebStore

func DownloadFromWebStore(extensionID string, filename string) error

DownloadFromWebStore downloads a Chrome extension from the web store. ExtensionID can be an identifier or an url.

func ExtractExtensionID added in v1.7.0

func ExtractExtensionID(tgt string) string

func ExtractExtensionNameFromURL added in v1.7.0

func ExtractExtensionNameFromURL(rawURL string) (string, bool)

func ID added in v1.2.0

func ID(filename string) (id string, err error)

ID returns the extension ID extracted from a CRX (Chrome Extension) file specified by 'filename'. It checks if the file is in the CRX format, reads its header and signed data, and then converts the CRX ID into a string representation

func IDFromPubKey added in v1.5.1

func IDFromPubKey(pubKey []byte) (string, error)

IDFromPubKey generates the Chrome Extension ID from a public key. It handles PEM formatting, base64 decoding, and SHA-256 hashing to produce the ID. Returns the ID or an error if the key processing fails.

func IsValidChromeWebStoreURL added in v1.7.0

func IsValidChromeWebStoreURL(rawURL string) bool

func IsValidExtensionID added in v1.7.0

func IsValidExtensionID(s string) bool

func LoadPrivateKey

func LoadPrivateKey(filename string) (*rsa.PrivateKey, error)

LoadPrivateKey loads the RSA private key from the specified 'filename' into memory. It returns the loaded private key or an error if the key cannot be loaded.

func NewPrivateKey

func NewPrivateKey() (*rsa.PrivateKey, error)

NewPrivateKey returns a new RSA private key with a bit size of 4096.

func Pack

func Pack(src string, dst string, pk *rsa.PrivateKey) (err error)

Pack packs a zip file or unzipped directory into a crx extension. It takes the source 'src' (zip file or directory), target 'dst' CRX file path, and a private key 'pk' (optional). If 'pk' is nil, it generates a new private key. It creates a CRX extension from the source and writes it to the destination.

func PackZipToCRX added in v1.5.0

func PackZipToCRX(zip io.ReadSeeker, w io.Writer, pk *rsa.PrivateKey) error

PackZipToCRX reads a ZIP archive from the provided Reader, signs it using the provided RSA private key, and writes the signed CRX file to the provided Writer. This function is essential for producing production-ready CRX files that require digital signatures to be installed in browsers. The function will return an error if any issues occur during the zip reading, signing, or CRX writing processes.

func PrivateKeyToPEM added in v1.5.0

func PrivateKeyToPEM(key *rsa.PrivateKey) []byte

PublicKeyToPEM converts the provided RSA public key to a PEM-encoded byte slice.

func ReadZipFile added in v1.5.0

func ReadZipFile(filename string) (*bytes.Reader, error)

ReadZipFile opens and reads the contents of a zip file specified by 'filename'. It can handle both direct paths to zip files or directories. If 'filename' is a directory, the function zips its contents into a buffer and returns a reader for that buffer. If 'filename' is a zip file, it reads the file into a buffer and returns a reader for it. The function returns a *bytes.Reader to allow random access reads, which is particularly useful for large files. It returns an error if the file cannot be opened, read, or if the path does not correspond to a zip file or directory.

func SavePrivateKey

func SavePrivateKey(filename string, key *rsa.PrivateKey) error

SavePrivateKey saves the provided 'key' private key to the specified 'filename'. If 'key' is nil, it generates a new private key and saves it to the file.

func Scan added in v1.7.0

func Scan(rootPath string, opts ...ScanOption) iter.Seq2[*ExtensionInfo, error]

Scan walks the directory tree starting at rootPath and collects information about Chrome extensions in CRX, ZIP, or unpacked directory formats.

It identifies extensions by:

  • Filenames matching the pattern: <name>_<32-character-id>.crx|.zip
  • Directories containing a "manifest.json" file or a "extension-id" file
  • Standalone .crx and .zip files that contain a "manifest.json"

For each found extension, an ExtensionInfo struct is created with details including name, path, type ("crx", "zip", or "dir"), size, and modification time.

Directories without recognized files are skipped. Files larger than 300MB are ignored.

func SetDefaultKeySize added in v1.5.0

func SetDefaultKeySize(size int)

SetDefaultKeySize sets the global default key size for RSA key generation. It accepts key sizes of 2048, 3072, or 4096 bits. If a size outside these specified options is passed, the function panics to prevent the use of an unsupported key size, which could lead to security vulnerabilities. This strict enforcement helps ensure that only strong, widely accepted key sizes are used throughout the application.

Usage:

SetDefaultKeySize(2048)  // sets the default key size to 2048 bits
SetDefaultKeySize(4096)  // sets the default key size to 4096 bits

Valid key sizes are:

  • 2048 bits
  • 3072 bits
  • 4096 bits

Panics:

This function will panic if any key size other than the above mentioned
valid sizes is attempted to be set. This is a deliberate design choice
to catch incorrect key size settings during the development phase.

func SetWebStoreURL added in v1.3.0

func SetWebStoreURL(u string)

SetWebStoreURL sets the web store url to download extensions.

func Unpack

func Unpack(filename string) error

Unpack unpacks a CRX (Chrome Extension) file specified by 'filename' to its original contents. It checks if the file is in the CRX format, reads its header and signed data, and then extracts and decompresses the original contents. The unpacked contents are placed in a directory with the same name as the original file (without the '.crx' extension).

func UnpackTo added in v1.4.0

func UnpackTo(filename string, dirname string, opts ...UnpackOption) error

UnpackTo unpacks a CRX (Chrome Extension) file specified by 'filename' to the directory 'dirname'. If 'dirname' does not exist, it creates the directory before unpacking.

func Unzip

func Unzip(r io.ReaderAt, size int64, unpacked string) error

Unzip extracts all files and directories from the provided ZIP archive. It takes an io.ReaderAt 'r', the size 'size' of the ZIP archive, and the target directory 'unpacked' for extraction. It iterates through the archive, creating directories and writing files as necessary.

func UnzipTo added in v1.4.0

func UnzipTo(basepath string, filename string) error

UnzipTo extracts the contents of a ZIP archive specified by 'filename' to the 'basepath' directory. It opens the ZIP file, creates the necessary directory structure, and extracts all files.

func WritePrivateKey added in v1.5.0

func WritePrivateKey(w io.Writer, key *rsa.PrivateKey) error

WritePrivateKey writes the RSA private key to the provided io.Writer in the PEM format. The function expects a non-nil *rsa.PrivateKey. If the key is nil, it returns an ErrPrivateKeyNotFound error. This function handles the marshalling of the private key into PKCS#8 format and then encodes it into PEM format before writing.

Parameters:

w    : An io.Writer to which the PEM encoded private key will be written.
key  : A non-nil *rsa.PrivateKey that will be marshalled and written.

Returns:

An error if the private key is nil, if there is a marshalling error, or if writing
to the io.Writer fails. The error includes a descriptive message to aid in debugging.

Usage example:

file, err := os.Create("private_key.pem")
if err != nil {
    log.Fatal(err)
}
defer file.Close()

privKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
    log.Fatal(err)
}

if err := WritePrivateKey(file, privKey); err != nil {
    log.Printf("Failed to write private key: %v", err)
}

Note:

This function does not close the io.Writer; the caller is responsible for managing
the writer's lifecycle, including opening and closing it.

func Zip

func Zip(dst io.Writer, dirname string) error

Zip creates a *.zip archive and adds all files from the specified directory to it.

func ZipTo added in v1.4.0

func ZipTo(filename string, dirname string) error

ZipTo creates a ZIP archive with the specified filename and adds all files from the given directory to it.

Types

type Extension

type Extension string

Extension represents an extension for google chrome.

func (Extension) Base64 added in v1.3.0

func (e Extension) Base64() ([]byte, error)

Base64 encodes an extension file to a base64 string.

func (Extension) ID added in v1.2.0

func (e Extension) ID() (string, error)

ID calculates the Chrome Extension ID for the Extension instance. It supports directories, ZIP archives, and CRX3 files. If the extension is unpacked, contained in a ZIP archive, or is a CRX3 file with a specified key in its manifest, the ID is generated from this key. The function returns an error if the extension is empty, the file cannot be read, the key is not found, or the file format is unsupported.

func (Extension) IsCRX3 added in v1.1.0

func (e Extension) IsCRX3() bool

IsCRX3 reports whether extension describes a crx file.

func (Extension) IsDir added in v1.1.0

func (e Extension) IsDir() bool

IsDir reports whether extension describes a directory.

func (Extension) IsEmpty added in v1.4.0

func (e Extension) IsEmpty() bool

IsEmpty checks if the extension is empty.

func (Extension) IsZip added in v1.1.0

func (e Extension) IsZip() bool

IsZip reports whether extension describes a zip-archive.

func (Extension) Pack

func (e Extension) Pack(pk *rsa.PrivateKey) error

Pack packs zip file or an unpacked directory into a CRX3 file.

func (Extension) PackTo

func (e Extension) PackTo(dst string, pk *rsa.PrivateKey) error

PackTo packs zip file or an unpacked directory into a CRX3 file.

func (Extension) String

func (e Extension) String() string

String returns a string representation.

func (Extension) Unpack

func (e Extension) Unpack() error

Unpack unpacks the CRX3 extension into a directory.

func (Extension) Unzip

func (e Extension) Unzip() error

Unzip extracts all files from the archive.

func (Extension) WriteTo added in v1.5.0

func (e Extension) WriteTo(w io.Writer, pk *rsa.PrivateKey) error

WriteTo packs the contents of the Extension into a CRX file and writes it to the provided io.Writer. This method requires a non-nil *rsa.PrivateKey to sign the CRX package. The Extension must not be empty, and its associated zip file must be readable and correctly formatted.

Parameters:

w  - The io.Writer where the CRX file will be written.
pk - The RSA private key used for signing the CRX file.

Returns:

An error if the Extension is empty, if the private key is nil, if there are issues reading the
zip file associated with the Extension, or if there is a failure during the packing process.
Errors are wrapped with context to provide more details about the failure.

Usage example:

ext := Extension("path/to/your/extension/folder") // OR zip file
pk, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
    log.Fatalf("Failed to generate private key: %v", err)
}

var buf bytes.Buffer
if err := ext.WriteTo(&buf, pk); err != nil {
    log.Printf("Failed to write CRX: %v", err)
} else {
    // Use buf to save CRX to a file or further processing
}

func (Extension) Zip

func (e Extension) Zip() error

Zip creates a *.zip archive and adds all the files to it.

type ExtensionInfo added in v1.7.0

type ExtensionInfo struct {
	Name     string `json:"name"`
	Path     string `json:"path"`
	Type     string `json:"type"`
	Size     int64  `json:"size"`
	Modified string `json:"modified"`
}

ExtensionInfo represents metadata about a Chrome extension found during scanning. It includes the extension's name, file path, type (crx, zip, or dir), size in bytes, and modification time formatted as a string.

func (*ExtensionInfo) String added in v1.7.0

func (e *ExtensionInfo) String() string

type ScanOption added in v1.7.0

type ScanOption func(*scanFilter)

ScanOption is a function that configures the internal scan filter. It is used to pass optional arguments to the Scan function.

func WithMaxDepth added in v1.7.0

func WithMaxDepth(depth int) ScanOption

WithMaxDepth returns a ScanOption that limits the directory traversal depth. For example, WithMaxDepth(2) will scan only root, its subdirs, and their subdirs.

func WithMaxResults added in v1.7.0

func WithMaxResults(n int) ScanOption

WithMaxResults returns a ScanOption that stops scanning after finding n extensions.

func WithNameFilter added in v1.7.0

func WithNameFilter(query string) ScanOption

WithNameFilter returns a ScanOption that filters results by partial case-insensitive match of the file or directory name.

For example, WithNameFilter("adblock") will match:

  • "adblock_plus_pammpkd...crx"
  • "my_adblock_tool.zip"
  • "Adblock-Extra" (as a directory)

Multiple filters can be combined; they are joined with logical OR.

type SearchResult added in v1.7.0

type SearchResult struct {
	Name        string `json:"name"`
	URL         string `json:"url"`
	ExtensionID string `json:"extensionId"`
}

func SearchExtensionByName added in v1.7.0

func SearchExtensionByName(ctx context.Context, name string) ([]SearchResult, error)

type UnpackOption added in v1.7.0

type UnpackOption func(*unpackOptions)

UnpackOption is a function that configures unpacking behavior.

func UnpackDisableSubdir added in v1.7.0

func UnpackDisableSubdir() UnpackOption

UnpackDisableSubdir returns an option to disable creation of a subdirectory when unpacking. The contents will be extracted directly into the target directory.

Directories

Path Synopsis
examples
extension_id command
pack_to_file command
pack_to_memory command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL