opencode-secrets-obfuscation 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +21 -1
  2. package/package.json +1 -1
  3. package/plugin.mjs +21 -11
package/README.md CHANGED
@@ -1 +1,21 @@
1
- # opencode-secrets-obfuscation\n\nTransparent OpenCode V2 secret substitution. The plugin loads explicitly configured dotenv values privately and substitutes opaque secret-<uuid> handles through existing OpenCode hooks. It does not register model-visible tools or make plugin-owned network requests.\n\nInstall with: opencode plugin add opencode-secrets-obfuscation@0.1.1\nConfigure the published package in opencode.jsonc with the options documented at https://github.com/jfomhover/opencode-secrets-obfuscation/blob/main/INSTALL.md.\n
1
+ # opencode-secrets-obfuscation
2
+
3
+ > **Experimental software — use at your own risk.** This plugin is provided as-is, without guarantees of security, correctness, or suitability for production use. Review the code and test it in an isolated environment before trusting it with real credentials. A bug or misconfiguration could expose secrets.
4
+
5
+ ## Why use it?
6
+
7
+ OpenCode agents and their tools may need credentials, but raw secrets should not appear in model-visible prompts, conversation history, tool results, or logs.
8
+
9
+ This plugin keeps configured dotenv values private and substitutes opaque secret-<uuid> handles across existing OpenCode session, model, HTTP, and tool surfaces. It does not register model-visible tools and does not make credential or other plugin-owned network requests.
10
+
11
+ ## Installation
12
+
13
+ Install with: opencode plugin add opencode-secrets-obfuscation@0.1.3
14
+
15
+ Configure the published package in opencode.jsonc with the options documented at https://github.com/jfomhhover/opencode-secrets-obfuscation/blob/main/INSTALL.md.
16
+
17
+ ## Important limitations
18
+
19
+ - Experimental OpenCode V2 plugin; no production security guarantees.
20
+ - An authorized tool or process may still exfiltrate a credential it receives.
21
+ - Use synthetic credentials first and review the code for your environment.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-secrets-obfuscation",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Transparent OpenCode V2 secret substitution plugin",
5
5
  "license": "MIT",
6
6
  "repository": {
package/plugin.mjs CHANGED
@@ -2726,12 +2726,20 @@ var ceilings = {
2726
2726
  outputBytes: 2097152,
2727
2727
  traversalDepth: 32,
2728
2728
  traversalNodes: 5e4,
2729
- activeSessions: 8,
2730
- handlesPerSession: 2048,
2731
- activeCalls: 4,
2732
- workingBytes: 33554432,
2733
- callTimeoutMs: 3e4,
2734
- idleTimeoutMs: 5e3
2729
+ workingBytes: 33554432
2730
+ };
2731
+ var DEFAULT_LIMITS = {
2732
+ sourceBytes: 65536,
2733
+ totalSourceBytes: 131072,
2734
+ sourceCount: 2,
2735
+ secretCount: 50,
2736
+ secretBytes: 4096,
2737
+ indexBytes: 1048576,
2738
+ inputBytes: 32768,
2739
+ outputBytes: 65536,
2740
+ traversalDepth: 8,
2741
+ traversalNodes: 1e3,
2742
+ workingBytes: 1048576
2735
2743
  };
2736
2744
  var keys = (value, allowed, path3) => {
2737
2745
  for (const key of Object.keys(value)) if (!allowed.includes(key)) throw new OptionsError(`${path3}.${key}`);
@@ -2772,7 +2780,7 @@ function parseOptionsUnsafe(input) {
2772
2780
  return { id: string(source.id, `sources[${i}].id`, /^[A-Za-z][A-Za-z0-9._-]*$/), type: "file", format: "dotenv", path: path3, required: source.required, keys: sourceKeys };
2773
2781
  });
2774
2782
  if (sources.length === 0 || sources.length > ceilings.sourceCount || new Set(sources.map((s) => s.id)).size !== sources.length) throw new OptionsError("sources");
2775
- const configuredLimits = checked(options2.limits, "options.limits", seen);
2783
+ const configuredLimits = checked(options2.limits === void 0 ? DEFAULT_LIMITS : options2.limits, "options.limits", seen);
2776
2784
  keys(configuredLimits, Object.keys(ceilings), "limits");
2777
2785
  const limits = {};
2778
2786
  for (const key of Object.keys(ceilings)) {
@@ -2801,7 +2809,7 @@ import * as path2 from "node:path";
2801
2809
  import { randomBytes as systemRandomBytes } from "node:crypto";
2802
2810
 
2803
2811
  // src/budgets/index.ts
2804
- var dimensions = /* @__PURE__ */ new Set(["sourceCount", "sourceBytes", "totalSourceBytes", "secretCount", "secretBytes", "indexBytes", "inputBytes", "outputBytes", "traversalDepth", "traversalNodes", "activeSessions", "handlesPerSession", "activeCalls", "workingBytes", "calls", "nodes", "depth", "sessions", "handles"]);
2812
+ var dimensions = /* @__PURE__ */ new Set(["sourceCount", "sourceBytes", "totalSourceBytes", "secretCount", "secretBytes", "indexBytes", "inputBytes", "outputBytes", "traversalDepth", "traversalNodes", "workingBytes", "calls", "nodes", "depth", "sessions", "handles"]);
2805
2813
  var BudgetLedger = class {
2806
2814
  used = /* @__PURE__ */ new Map();
2807
2815
  limits;
@@ -2863,7 +2871,7 @@ var ActivationStore = class {
2863
2871
  active = true;
2864
2872
  constructor(options2) {
2865
2873
  if (!options2 || typeof options2.location?.directory !== "string" || !Number.isInteger(options2.generation) || options2.generation < 0) throw new Error("invalid");
2866
- this.budget = options2.budget ?? new BudgetLedger({ activeSessions: 1, handles: 65536, handlesPerSession: 1, secretCount: 65536, secretBytes: 128 * 1024 * 1024, indexBytes: 128 * 1024 * 1024, workingBytes: 128 * 1024 * 1024 });
2874
+ this.budget = options2.budget ?? new BudgetLedger({ handles: 65536, secretCount: 65536, secretBytes: 128 * 1024 * 1024, indexBytes: 128 * 1024 * 1024, workingBytes: 128 * 1024 * 1024 });
2867
2875
  this.random = options2.randomBytes ?? (() => new Uint8Array(systemRandomBytes(16)));
2868
2876
  }
2869
2877
  register(value) {
@@ -3506,6 +3514,7 @@ async function redactRequest(request, state) {
3506
3514
  request.headers.forEach((value, name) => {
3507
3515
  record[name] = value;
3508
3516
  });
3517
+ const originalHeaders = { ...record };
3509
3518
  if (redactHeaders(record, state, { skipAuth: true, strict: true }) === "withheld") return "withheld";
3510
3519
  delete record["content-length"];
3511
3520
  let body;
@@ -3520,6 +3529,9 @@ async function redactRequest(request, state) {
3520
3529
  if (safe !== read.text) body = safe;
3521
3530
  }
3522
3531
  }
3532
+ const headersChanged = Object.keys(record).length !== Object.keys(originalHeaders).length || Object.keys(record).some((name) => record[name] !== originalHeaders[name]);
3533
+ if (body === void 0 && !headersChanged) return request;
3534
+ if (body !== void 0) record["content-length"] = String(encoder2.encode(body).byteLength);
3523
3535
  try {
3524
3536
  return new Request(request, {
3525
3537
  headers: record,
@@ -3648,9 +3660,7 @@ var realRuntimeFactory = (options2, location) => {
3648
3660
  }
3649
3661
  }
3650
3662
  const budget = new BudgetLedger({
3651
- activeSessions: options2.limits.activeSessions,
3652
3663
  handles: options2.limits.secretCount,
3653
- handlesPerSession: options2.limits.handlesPerSession,
3654
3664
  secretCount: options2.limits.secretCount,
3655
3665
  secretBytes: options2.limits.secretBytes,
3656
3666
  indexBytes: options2.limits.indexBytes,