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.
- package/README.md +21 -1
- package/package.json +1 -1
- package/plugin.mjs +21 -11
package/README.md
CHANGED
|
@@ -1 +1,21 @@
|
|
|
1
|
-
# opencode-secrets-obfuscation
|
|
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
package/plugin.mjs
CHANGED
|
@@ -2726,12 +2726,20 @@ var ceilings = {
|
|
|
2726
2726
|
outputBytes: 2097152,
|
|
2727
2727
|
traversalDepth: 32,
|
|
2728
2728
|
traversalNodes: 5e4,
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
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", "
|
|
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({
|
|
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,
|