Skip to content

Repository files navigation

OpenCode Redactor

A lightweight, native TypeScript plugin for OpenCode. It masks sensitive information (API keys, PII, credentials) and high-entropy strings before text is sent to an LLM provider, while keeping your local session usable.

Features

  • Zero Dependencies: Built using native TypeScript and Bun, ensuring fast startup and minimal footprint.
  • Hybrid Detection Engine:
    • Regex Matching: Instantly identifies known patterns for AWS, GitHub, GitLab, Slack, Stripe, private keys, and emails.
    • Entropy Analysis: Uses Shannon entropy calculation to detect potential secrets that don't match specific regex patterns (e.g., random hex strings, passwords).
  • Reversible Token Registry: Replaces secrets with deterministic <REDACTED:hex14> tokens backed by an in-memory registry. Write/edit tools automatically restore original values before execution so files are never corrupted.
  • Write Protection: Blocks write operations that contain unresolvable redaction tokens, preventing corrupted output.
  • Comprehensive Hook Coverage: Masks secrets at every stage — user input, tool output, LLM prompt construction, assistant completions, TUI events, and file diffs.
  • Metadata-Only Audit Logs: Writes JSONL records without storing raw secret values.

Installation

This plugin is designed for the OpenCode environment.

1. Local Project Installation

Copy the plugin into your project's .opencode/plugins/ directory:

mkdir -p .opencode/plugins
cp opencode-redactor.ts .opencode/plugins/opencode-redactor.ts

Tip: during development, symlink instead of copying to avoid drift:

mkdir -p .opencode/plugins
ln -sf "$PWD/opencode-redactor.ts" .opencode/plugins/opencode-redactor.ts

2. Global Installation

To make the plugin available across all your OpenCode projects:

mkdir -p ~/.config/opencode/plugins
cp opencode-redactor.ts ~/.config/opencode/plugins/opencode-redactor.ts

3. Install via npm (recommended)

If you publish/install this plugin from npm, add it to your OpenCode config:

opencode.json

{
"$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-redactor@0.1.1"]
}

OpenCode will install/load the package and run the plugin's default export.

Note: OpenCode currently imports npm plugins via a directory path. This package includes a root index.js entrypoint for compatibility.

Usage

Once installed, OpenCode loads the plugin automatically at startup.

This avoids “opportunistic tool calling” (where the model decides whether to call a tool after seeing your raw text).

Auto-Mask Before LLM (Plugin)

This repo is an OpenCode plugin. The source file is:

  • opencode-redactor.ts (copy/symlink into .opencode/plugins/opencode-redactor.ts)

If you paste a secret into chat, the plugin will redact it from the provider-bound prompt before any upstream request is made.

The plugin covers the full lifecycle through six hooks:

HookPurpose
experimental.chat.messages.transformPrimary protection — mutates message parts in-place before the LLM request is constructed
chat.messageAudits (and optionally masks) freshly received user parts
tool.execute.afterMasks secrets in tool output (file reads, command output, etc.)
tool.execute.beforeRestores <REDACTED:…> tokens to original values in write/edit tool args, blocking writes with unresolvable tokens
experimental.text.completeMasks secrets in assistant completion text before rendering
eventObserves tui.prompt.append, tui.command.execute, message.part.updated, and session.diff for early masking and auditing

Implementation note: OpenCode continues using the same messages array variable after triggering experimental.chat.messages.transform, so the plugin must mutate messages[*].parts[*] in-place. Replacing output.messages with a new array will not reliably affect what is sent upstream.

By default, the plugin does not redact what is stored locally in session history (so you can paste secrets and keep working), but you can opt into local redaction via OPENCODE_REDACTOR_MASK_LOCAL.

Reversible Token Format

Secrets are replaced with deterministic tokens: <REDACTED:a1b2c3d4e5f6ab> (14 hex characters derived from an FNV-1a-like hash of the original value). The in-memory token registry maps tokens back to original values, allowing write/edit tools to restore secrets before file operations.

Testing (Local)

1) Unit tests (masking behavior)

Run the bun tests in this repo:

bun test

2) Manual end-to-end test (OpenCode plugin)

  1. Ensure the plugin files are in either:

    • Project: .opencode/plugins/
    • Global: ~/.config/opencode/plugins/
  2. Restart OpenCode so it reloads plugins.

  3. Test it out by sending a message with a fake secret:

export OPENCODE_REDACTOR_AUDIT_ENABLED=1
export OPENCODE_REDACTOR_AUDIT_PATH="$PWD/opencode-redactor.audit.jsonl"
rm -f "$OPENCODE_REDACTOR_AUDIT_PATH"# In another terminal, watch the audit log:# tail -f "$OPENCODE_REDACTOR_AUDIT_PATH"echo"AKIAIOSFODNN7EXAMPLE"> secret-test.txt
opencode run "Read secret-test.txt and tell me what it contains"# Then verify audit output:
cat "$OPENCODE_REDACTOR_AUDIT_PATH"

Configuration

You can tune entropy sensitivity by editing the constants near the top of opencode-redactor.ts:

constBASE64_ENTROPY_LIMIT=4.5;constHEX_ENTROPY_LIMIT=3.0;constMIN_LENGTH_FOR_ENTROPY=16;

Environment variables

These are read at runtime from the OpenCode/Bun process environment.

  • OPENCODE_REDACTOR_MASK_LOCAL (default: 0):
    • 0: do not mutate persisted user message text; only redact provider-bound prompts.
    • 1: also redact local session history (your pasted secrets will be replaced in stored messages).

Recommended settings

If your goal is "let users paste secrets locally, but never send them to the LLM", keep the defaults (OPENCODE_REDACTOR_MASK_LOCAL=0). The experimental.chat.messages.transform hook will redact the provider-bound prompt, while write/edit operations restore original values via the token registry so files remain correct.

Local Audit Log

By default, when masking changes output text, the plugin appends a single JSON line (JSONL) to a local audit file so you can track usage and which detectors fired.

  • Default path: ~/.config/opencode/opencode-redactor.audit.jsonl
  • Disable logging: set OPENCODE_REDACTOR_AUDIT_ENABLED=0
  • Override paths: OPENCODE_REDACTOR_AUDIT_DIR or OPENCODE_REDACTOR_AUDIT_PATH

Each audit line includes additional performance metadata:

  • hook: which hook produced this record (chat.message, experimental.chat.messages.transform, tool.execute.after, tool.execute.before, experimental.text.complete, tui.prompt.append, tui.command.execute, message.part.updated, or session.diff)
  • durationMs: time spent masking for that hook invocation
  • bytesIn / bytesOut: UTF-8 byte size before/after masking
  • parts / partsChanged: part-level metrics for chat.message and experimental.chat.messages.transform
  • fields / fieldsChanged: string-field metrics for TUI event scans
  • toolName: tool name for tool.execute.after / tool.execute.before when available
  • tokensFound / tokensRestored: token restoration metrics for tool.execute.before

Supported Patterns

This plugin is deliberately conservative: it prefers high-confidence regex matches and only applies entropy masking in limited contexts.

Regex Detectors

When a match is found, the value is replaced with a placeholder. By default this is <SECRET>. If you set OPENCODE_REDACTOR_PLACEHOLDER=typed, placeholders include the detector label (e.g. <SECRET: AWSKeyDetector>).

Placeholder LabelWhat It Detects
PrivateKeyDetectorPEM/OpenSSH/PGP private key blocks + common private key headers
ArtifactoryDetectorJFrog Artifactory access tokens (AKC… / AP…)
AWSKeyDetectorAWS access key IDs (AKIA/ASIA/…) and AWS secret access keys in assignment-like context
AzureStorageKeyDetectorAzure Storage AccountKey= values
CloudantDetectorCloudant credentials in URLs and common assignment patterns
BasicAuthDetectorBasic auth in URLs (password only) and Authorization: Basic ... headers
DiscordBotTokenDetectorDiscord bot tokens
GitHubTokenDetectorGitHub personal access tokens (ghp_, gho_, ghu_, ghs_, ghr_)
GitLabTokenDetectorGitLab tokens (glpat-, gldt-, glft-, glsoat-, glrt-)
IbmCloudIamDetectorIBM Cloud IAM API keys in assignment-like context
IbmCosHmacDetectorIBM COS HMAC secret access keys in assignment-like context
MailchimpDetectorMailchimp API keys (<32 hex>-us<region>)
NpmDetectornpm auth tokens in .npmrc-style _authToken= lines
OpenAIDetectorOpenAI API keys (both current structured and legacy sk- formats)
PypiTokenDetectorPyPI tokens (pypi-AgEI... / pypi-AgEN...)
SendGridDetectorSendGrid API keys (SG.<...>.<...>)
SlackDetectorSlack tokens (xox...) and Slack webhook URLs
SoftlayerDetectorSoftLayer API keys in assignments and in SOAP API URLs
SquareOAuthDetectorSquare OAuth secrets (sq0csp-...)
StripeDetectorStripe secret keys (sk_live_..., sk_test_...)
TelegramBotTokenDetectorTelegram bot tokens (<digits>:<35 chars>)
TwilioKeyDetectorTwilio Account SIDs (AC...) and API keys (SK...)
MailDetectorEmail addresses (PII)

Content-Aware Detectors

These are not simple regex replacements across all text; they apply only in constrained contexts.

Placeholder LabelWhat It DetectsNotes
KeywordDetectorValues adjacent to common secret keywords (password, token, api_key, etc.)Masks the value portion of key = value / key: "value" / password is value patterns
JwtTokenDetectorJWT-like tokensOnly masked if base64url decoding succeeds and header/payload parse as JSON
HexHighEntropyStringHigh-entropy hex stringsOnly masked when quoted, or when keyword context appears nearby
Base64HighEntropyStringHigh-entropy base64/base64url-ish stringsOnly masked when quoted, or when keyword context appears nearby

About

OpenCode plugin that masks secrets (API keys/PII) in chat + tool outputs before sending to LLM providers

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages