@gendigital/sage-opencode 0.10.0 → 0.12.0
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 +6 -6
- package/dist/bundled-dirs.d.ts +1 -1
- package/dist/bundled-dirs.d.ts.map +1 -1
- package/dist/bundled-dirs.js +1 -1
- package/dist/bundled-dirs.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +291 -71
- package/dist/index.js.map +1 -1
- package/dist/mcp-server.cjs +31882 -0
- package/dist/mcp-server.cjs.map +7 -0
- package/dist/plugin-discovery.d.ts +1 -0
- package/dist/plugin-discovery.d.ts.map +1 -1
- package/dist/plugin-discovery.js +27 -1
- package/dist/plugin-discovery.js.map +1 -1
- package/dist/startup-scan.d.ts +11 -3
- package/dist/startup-scan.d.ts.map +1 -1
- package/dist/startup-scan.js +60 -21
- package/dist/startup-scan.js.map +1 -1
- package/dist/tool-handler.d.ts +3 -2
- package/dist/tool-handler.d.ts.map +1 -1
- package/dist/tool-handler.js +22 -5
- package/dist/tool-handler.js.map +1 -1
- package/package.json +5 -3
- package/resources/threats/agent-layer.yaml +186 -0
- package/resources/threats/commands.yaml +24 -53
- package/resources/threats/credentials.yaml +12 -20
- package/resources/threats/dummy.yaml +44 -50
- package/resources/threats/files.yaml +34 -53
- package/resources/threats/mac-commands.yaml +20 -47
- package/resources/threats/mac-credentials.yaml +4 -12
- package/resources/threats/mac-files.yaml +5 -15
- package/resources/threats/mac-obfuscation.yaml +6 -13
- package/resources/threats/mac-persistence.yaml +11 -20
- package/resources/threats/mac-supply-chain.yaml +5 -8
- package/resources/threats/mitre.yaml +105 -167
- package/resources/threats/obfuscation.yaml +7 -15
- package/resources/threats/persistence.yaml +10 -17
- package/resources/threats/prompt-injection.yaml +92 -43
- package/resources/threats/self-defense.yaml +8 -12
- package/resources/threats/supply_chain.yaml +1 -2
- package/resources/threats/urls.yaml +9 -14
- package/resources/threats/win-commands.yaml +46 -106
- package/resources/threats/win-credentials.yaml +14 -24
- package/resources/threats/win-files.yaml +10 -20
- package/resources/threats/win-obfuscation.yaml +21 -37
- package/resources/threats/win-persistence.yaml +8 -16
- package/resources/threats/win-supply-chain.yaml +2 -3
- /package/resources/{allowlists → trusted-domains}/trusted-installer-domains.yaml +0 -0
package/README.md
CHANGED
|
@@ -38,9 +38,9 @@ Add the plugin to your OpenCode config (`~/.config/opencode/opencode.json`):
|
|
|
38
38
|
}
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
Or [build from source](https://github.com/gendigitalinc/sage/blob/main/docs/
|
|
41
|
+
Or [build from source](https://github.com/gendigitalinc/sage/blob/main/docs/user-guide.md#opencode).
|
|
42
42
|
|
|
43
|
-
> To verify it's working, ask your agent to run `echo
|
|
43
|
+
> To verify it's working, ask your agent to run `echo diagmark_cmd_a75bf229`. Sage should block this harmless canary command.
|
|
44
44
|
|
|
45
45
|
## What Sage intercepts
|
|
46
46
|
|
|
@@ -80,12 +80,12 @@ Sage works out of the box with no configuration. To customize behavior, edit `~/
|
|
|
80
80
|
}
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
-
See [Configuration](https://github.com/gendigitalinc/sage/blob/main/docs/
|
|
83
|
+
See [Configuration](https://github.com/gendigitalinc/sage/blob/main/docs/user-guide.md#configuration) for all options.
|
|
84
84
|
|
|
85
85
|
## Links
|
|
86
86
|
|
|
87
87
|
- [User Guide](https://github.com/gendigitalinc/sage/blob/main/docs/user-guide.md) — verify install, handle alerts, manage false positives
|
|
88
|
-
- [Configuration](https://github.com/gendigitalinc/sage/blob/main/docs/
|
|
89
|
-
- [Exceptions](https://github.com/gendigitalinc/sage/blob/main/docs/
|
|
90
|
-
- [Privacy](https://github.com/gendigitalinc/sage/blob/main/docs/
|
|
88
|
+
- [Configuration](https://github.com/gendigitalinc/sage/blob/main/docs/user-guide.md#configuration) — all config options
|
|
89
|
+
- [Exceptions](https://github.com/gendigitalinc/sage/blob/main/docs/user-guide.md#exceptions) — pattern-based allow/deny rules
|
|
90
|
+
- [Privacy](https://github.com/gendigitalinc/sage/blob/main/docs/user-guide.md#privacy) — what data is sent, what stays local
|
|
91
91
|
- [GitHub](https://github.com/gendigitalinc/sage)
|
package/dist/bundled-dirs.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export declare function getBundledDataDirs(): {
|
|
|
6
6
|
pluginDir: string;
|
|
7
7
|
packageRoot: string;
|
|
8
8
|
threatsDir: string;
|
|
9
|
-
|
|
9
|
+
trustedDomainsDir: string;
|
|
10
10
|
};
|
|
11
11
|
export declare function getSageVersion(): string;
|
|
12
12
|
//# sourceMappingURL=bundled-dirs.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bundled-dirs.d.ts","sourceRoot":"","sources":["../src/bundled-dirs.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,wBAAgB,kBAAkB,IAAI;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,
|
|
1
|
+
{"version":3,"file":"bundled-dirs.d.ts","sourceRoot":"","sources":["../src/bundled-dirs.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,wBAAgB,kBAAkB,IAAI;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;CAC1B,CASA;AAED,wBAAgB,cAAc,IAAI,MAAM,CASvC"}
|
package/dist/bundled-dirs.js
CHANGED
|
@@ -12,7 +12,7 @@ export function getBundledDataDirs() {
|
|
|
12
12
|
pluginDir,
|
|
13
13
|
packageRoot,
|
|
14
14
|
threatsDir: join(packageRoot, "resources", "threats"),
|
|
15
|
-
|
|
15
|
+
trustedDomainsDir: join(packageRoot, "resources", "trusted-domains"),
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
18
|
export function getSageVersion() {
|
package/dist/bundled-dirs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bundled-dirs.js","sourceRoot":"","sources":["../src/bundled-dirs.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,UAAU,kBAAkB;IAMjC,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1D,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAC1C,OAAO;QACN,SAAS;QACT,WAAW;QACX,UAAU,EAAE,IAAI,CAAC,WAAW,EAAE,WAAW,EAAE,SAAS,CAAC;QACrD,
|
|
1
|
+
{"version":3,"file":"bundled-dirs.js","sourceRoot":"","sources":["../src/bundled-dirs.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,UAAU,kBAAkB;IAMjC,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1D,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAC1C,OAAO;QACN,SAAS;QACT,WAAW;QACX,UAAU,EAAE,IAAI,CAAC,WAAW,EAAE,WAAW,EAAE,SAAS,CAAC;QACrD,iBAAiB,EAAE,IAAI,CAAC,WAAW,EAAE,WAAW,EAAE,iBAAiB,CAAC;KACpE,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc;IAC7B,IAAI,CAAC;QACJ,MAAM,EAAE,WAAW,EAAE,GAAG,kBAAkB,EAAE,CAAC;QAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;QAClD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAA4B,CAAC;QAClF,OAAQ,GAAG,CAAC,OAAkB,IAAI,OAAO,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,OAAO,CAAC;IAChB,CAAC;AACF,CAAC"}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAwBH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAmLlD,eAAO,MAAM,UAAU,EAAE,MAyOxB,CAAC;AAEF,eAAe,UAAU,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -2,12 +2,164 @@
|
|
|
2
2
|
* Sage OpenCode plugin.
|
|
3
3
|
* Intercepts tool calls and uses @gendigital/sage-core to enforce security verdicts.
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
5
|
+
import { mkdirSync, watchFile } from "node:fs";
|
|
6
|
+
import { dirname, join } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { ApprovalStore, approveAction, checkAllowlistMigration, createOperationalLogger, foreignSourceRuntime, formatAllowlistMigrationWarning, formatConfigurationWarnings, formatNoticeById, getConfigurationWarnings, loadConfig, loadSkillVerdictCache, resolveBranding, resolvePath, } from "@gendigital/sage-core";
|
|
6
9
|
import { tool } from "@opencode-ai/plugin/tool";
|
|
7
10
|
import { getBundledDataDirs } from "./bundled-dirs.js";
|
|
8
11
|
import { createSessionScanHandler } from "./startup-scan.js";
|
|
9
12
|
import { createToolHandlers } from "./tool-handler.js";
|
|
10
13
|
const APPROVAL_STORE_CLEANUP_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
|
|
14
|
+
const DEFAULT_TOAST_DURATION_MS = 5000;
|
|
15
|
+
// The startup security summary lists every risky skill, so it's shown longer to
|
|
16
|
+
// give the user time to read the list before it dismisses.
|
|
17
|
+
const STARTUP_SUMMARY_DURATION_MS = 10_000;
|
|
18
|
+
// Module-scoped so repeated SagePlugin instantiations (OpenCode may load the
|
|
19
|
+
// plugin more than once) share a single watcher and one notified-set, instead
|
|
20
|
+
// of each instance attaching its own watcher and toasting the same verdict.
|
|
21
|
+
const notifiedSkillVerdicts = new Set();
|
|
22
|
+
let skillWatcherAttached = false;
|
|
23
|
+
// One scan per session, shared across instances: the entry guards against
|
|
24
|
+
// re-running the scan when session.updated fires repeatedly or on more than one
|
|
25
|
+
// plugin instance.
|
|
26
|
+
const sessionScanPromises = new Map();
|
|
27
|
+
const toastQueue = [];
|
|
28
|
+
let toastPumpRunning = false;
|
|
29
|
+
// Bound to a live client's showToast by the first instance (see SagePlugin).
|
|
30
|
+
let displayToast;
|
|
31
|
+
async function pumpToasts() {
|
|
32
|
+
if (toastPumpRunning)
|
|
33
|
+
return;
|
|
34
|
+
toastPumpRunning = true;
|
|
35
|
+
try {
|
|
36
|
+
while (toastQueue.length > 0) {
|
|
37
|
+
const next = toastQueue[0];
|
|
38
|
+
// No client bound yet — leave the item queued; a later enqueue resumes.
|
|
39
|
+
if (!displayToast || !next)
|
|
40
|
+
break;
|
|
41
|
+
displayToast(next.message, next.variant, next.durationMs);
|
|
42
|
+
toastQueue.shift();
|
|
43
|
+
// Hold the slot for this toast's full duration before showing the next,
|
|
44
|
+
// so a following toast can't replace it early (showToast overwrites).
|
|
45
|
+
await new Promise((resolve) => setTimeout(resolve, next.durationMs));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
toastPumpRunning = false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function enqueueToast(message, variant, durationMs = DEFAULT_TOAST_DURATION_MS) {
|
|
53
|
+
toastQueue.push({ message, variant, durationMs });
|
|
54
|
+
void pumpToasts();
|
|
55
|
+
}
|
|
56
|
+
// Shared dedup key across the two surfacing paths: the scan-findings popup keys
|
|
57
|
+
// on `finding.artifact` (which is `skillId.slice(0, 16)`), and the cache watcher
|
|
58
|
+
// keys on the full skillId — so both reduce to this 16-char content-hash prefix
|
|
59
|
+
// and `notifiedSkillVerdicts` never double-announces the same skill version.
|
|
60
|
+
function shortSkillId(skillId) {
|
|
61
|
+
return skillId.slice(0, 16);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Read `~/.sage/skill_verdict_cache.json` and return every HIGH/CRITICAL verdict
|
|
65
|
+
* relevant to this runtime — one per cache entry, NOT deduped, since dedup is
|
|
66
|
+
* caller-specific (the popup keys on current content, the toast queue on
|
|
67
|
+
* skillId). HIGH maps to the "WARNING" label. Verdicts sourced only from other
|
|
68
|
+
* agent runtimes are skipped so one host doesn't echo another's finds.
|
|
69
|
+
*/
|
|
70
|
+
async function collectRiskySkills(agentRuntime) {
|
|
71
|
+
const cache = await loadSkillVerdictCache();
|
|
72
|
+
const out = [];
|
|
73
|
+
for (const [skillId, verdict] of Object.entries(cache.entries)) {
|
|
74
|
+
const risk = (verdict.verdict ?? "").toUpperCase();
|
|
75
|
+
if (risk !== "HIGH" && risk !== "CRITICAL")
|
|
76
|
+
continue;
|
|
77
|
+
if (foreignSourceRuntime(verdict.sources, agentRuntime))
|
|
78
|
+
continue;
|
|
79
|
+
const key = verdict.sources?.find((s) => s.containerKey)?.containerKey ||
|
|
80
|
+
verdict.skillName ||
|
|
81
|
+
skillId.slice(0, 16);
|
|
82
|
+
out.push({
|
|
83
|
+
skillId,
|
|
84
|
+
key,
|
|
85
|
+
severity: risk === "CRITICAL" ? "CRITICAL" : "WARNING",
|
|
86
|
+
summary: verdict.summary?.trim(),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Watch `~/.sage/skill_verdict_cache.json` and queue one toast per verdict the
|
|
93
|
+
* upload worker writes mid-session (it writes asynchronously, ~one session after
|
|
94
|
+
* discovery). The serial queue shows them one at a time so two skills finishing
|
|
95
|
+
* together are both seen.
|
|
96
|
+
*
|
|
97
|
+
* Verdicts cached by earlier sessions are *seeded silently* on attach — marked
|
|
98
|
+
* notified without a toast — because the plugin initialises before the TUI is
|
|
99
|
+
* ready to render toasts. Those are surfaced instead by the session-start
|
|
100
|
+
* summary popup (see `surfaceStartupSummary`), which runs after the scan when
|
|
101
|
+
* the TUI is up.
|
|
102
|
+
*
|
|
103
|
+
* Dedup keys on `skillId` (content hash), not the container key, so editing a
|
|
104
|
+
* skill — which yields a new hash under the same key — is treated as a new
|
|
105
|
+
* finding and re-announced rather than silently collapsed into the old one.
|
|
106
|
+
*/
|
|
107
|
+
function setupSkillFindingsWatcher(logger, agentRuntime) {
|
|
108
|
+
if (skillWatcherAttached)
|
|
109
|
+
return;
|
|
110
|
+
const sageDir = resolvePath("~/.sage");
|
|
111
|
+
const seedNotified = async () => {
|
|
112
|
+
try {
|
|
113
|
+
for (const s of await collectRiskySkills(agentRuntime)) {
|
|
114
|
+
notifiedSkillVerdicts.add(shortSkillId(s.skillId));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch (e) {
|
|
118
|
+
logger.debug("Skill verdict seed failed", { error: String(e) });
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
// Mid-session: queue one toast per newly written (not-yet-notified) verdict.
|
|
122
|
+
const announceNew = async () => {
|
|
123
|
+
try {
|
|
124
|
+
for (const s of await collectRiskySkills(agentRuntime)) {
|
|
125
|
+
const short = shortSkillId(s.skillId);
|
|
126
|
+
if (notifiedSkillVerdicts.has(short))
|
|
127
|
+
continue;
|
|
128
|
+
notifiedSkillVerdicts.add(short);
|
|
129
|
+
const detail = s.summary ? `${s.key} — ${s.summary}` : `${s.key} detected`;
|
|
130
|
+
logger.debug("Skill finding toast queued", { key: s.key, severity: s.severity });
|
|
131
|
+
enqueueToast(`${detail} (${s.severity})`, s.severity === "CRITICAL" ? "error" : "warning");
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch (e) {
|
|
135
|
+
logger.debug("Skill verdict watcher scan failed", { error: String(e) });
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
try {
|
|
139
|
+
mkdirSync(sageDir, { recursive: true });
|
|
140
|
+
void seedNotified();
|
|
141
|
+
// Poll the cache file with watchFile rather than watch() the directory.
|
|
142
|
+
// On Windows, fs.watch aborts the whole process (an uncatchable libuv
|
|
143
|
+
// assertion in fs-event.c) when the watched path is an 8.3 short name
|
|
144
|
+
// (e.g. an os.tmpdir() like C:\Users\FOO~1.BAR\...) or symlinked home.
|
|
145
|
+
// watchFile is stat-based polling — immune to that. A few seconds of
|
|
146
|
+
// latency for mid-session verdicts is fine (matches the statusline poll
|
|
147
|
+
// cadence used elsewhere). Handles a not-yet-existent file (polls until
|
|
148
|
+
// it appears).
|
|
149
|
+
const cacheFile = join(sageDir, "skill_verdict_cache.json");
|
|
150
|
+
const watcher = watchFile(cacheFile, { interval: 3000 }, (curr, prev) => {
|
|
151
|
+
if (curr.mtimeMs !== prev.mtimeMs)
|
|
152
|
+
void announceNew();
|
|
153
|
+
});
|
|
154
|
+
watcher.unref?.();
|
|
155
|
+
skillWatcherAttached = true;
|
|
156
|
+
logger.debug("Skill findings watcher attached", { cacheFile });
|
|
157
|
+
}
|
|
158
|
+
catch (e) {
|
|
159
|
+
// ~/.sage/ may be unwritable — no watcher, no notifications (fail-open).
|
|
160
|
+
logger.debug("Skill findings watcher setup failed", { error: String(e) });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
11
163
|
export const SagePlugin = async ({ client, directory }) => {
|
|
12
164
|
const config = await loadConfig();
|
|
13
165
|
const operationalLogger = createOperationalLogger(config.operational_logging, "opencode");
|
|
@@ -15,101 +167,169 @@ export const SagePlugin = async ({ client, directory }) => {
|
|
|
15
167
|
const toolLogger = operationalLogger.forComponent("tool-handler");
|
|
16
168
|
const scanLogger = operationalLogger.forComponent("startup-scan");
|
|
17
169
|
const branding = resolveBranding(config.brand_key, logger);
|
|
170
|
+
const showToast = (msg, variant, durationMs = DEFAULT_TOAST_DURATION_MS) => {
|
|
171
|
+
client.tui
|
|
172
|
+
.showToast({
|
|
173
|
+
body: { title: branding.name, message: msg, variant, duration: durationMs },
|
|
174
|
+
})
|
|
175
|
+
.catch(() => { });
|
|
176
|
+
};
|
|
177
|
+
// Bind the module-scoped skill-toast queue to this (first) instance's client.
|
|
178
|
+
// The queue passes each item's own duration, which showToast honors.
|
|
179
|
+
displayToast ??= showToast;
|
|
18
180
|
const warningMessage = formatConfigurationWarnings(await getConfigurationWarnings(undefined, logger), branding);
|
|
19
181
|
if (warningMessage) {
|
|
20
182
|
client.tui
|
|
21
183
|
.showToast({
|
|
22
|
-
body: {
|
|
184
|
+
body: {
|
|
185
|
+
title: branding.name,
|
|
186
|
+
message: warningMessage,
|
|
187
|
+
variant: "warning",
|
|
188
|
+
duration: DEFAULT_TOAST_DURATION_MS,
|
|
189
|
+
},
|
|
190
|
+
})
|
|
191
|
+
.catch(() => { });
|
|
192
|
+
}
|
|
193
|
+
const allowlistMigration = await checkAllowlistMigration();
|
|
194
|
+
if (allowlistMigration.needed) {
|
|
195
|
+
client.tui
|
|
196
|
+
.showToast({
|
|
197
|
+
body: {
|
|
198
|
+
title: branding.name,
|
|
199
|
+
message: formatAllowlistMigrationWarning(allowlistMigration.entryTypes, branding),
|
|
200
|
+
variant: "warning",
|
|
201
|
+
duration: 8000,
|
|
202
|
+
},
|
|
23
203
|
})
|
|
24
204
|
.catch(() => { });
|
|
25
205
|
}
|
|
26
|
-
const { threatsDir,
|
|
206
|
+
const { threatsDir, trustedDomainsDir } = getBundledDataDirs();
|
|
27
207
|
const approvalStore = new ApprovalStore();
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
208
|
+
// Watch for verdicts the upload worker writes mid-session (queued toasts).
|
|
209
|
+
// Verdicts cached by earlier sessions are surfaced by surfaceStartupSummary.
|
|
210
|
+
setupSkillFindingsWatcher(logger, "opencode");
|
|
211
|
+
// Surface the scan's structured findings after the scan (TUI ready). Findings
|
|
212
|
+
// are install- and content-accurate: the scan hashes current on-disk content
|
|
213
|
+
// and looks up its verdict, so a deleted skill isn't discovered and an edited
|
|
214
|
+
// skill is matched by its new hash — no cache re-derivation needed.
|
|
215
|
+
//
|
|
216
|
+
// Single startup surface: one summary toast listing every risky skill from
|
|
217
|
+
// this scan. Summarized skills are marked notified so the cache watcher does
|
|
218
|
+
// not also announce them per-skill; verdicts that land *after* the scan
|
|
219
|
+
// (worker uploads) are still surfaced individually by the watcher.
|
|
220
|
+
const surfaceStartupSummary = (scanResults) => {
|
|
221
|
+
try {
|
|
222
|
+
const skills = [];
|
|
223
|
+
const seen = new Set();
|
|
224
|
+
for (const result of scanResults) {
|
|
225
|
+
for (const finding of result.findings) {
|
|
226
|
+
if (finding.threatId !== "SKILL_CHECK")
|
|
227
|
+
continue;
|
|
228
|
+
if (finding.severity !== "critical" && finding.severity !== "warning")
|
|
229
|
+
continue;
|
|
230
|
+
if (seen.has(result.plugin.key))
|
|
231
|
+
continue;
|
|
232
|
+
seen.add(result.plugin.key);
|
|
233
|
+
skills.push({
|
|
234
|
+
key: result.plugin.key,
|
|
235
|
+
severity: finding.severity === "critical" ? "CRITICAL" : "WARNING",
|
|
236
|
+
finding,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (skills.length === 0)
|
|
241
|
+
return;
|
|
242
|
+
// Mark every summarized skill as notified so the cache watcher doesn't
|
|
243
|
+
// also announce it per-skill. `finding.artifact` is the 16-char short
|
|
244
|
+
// hash — the same key the watcher dedups on. Verdicts that land after
|
|
245
|
+
// this scan (worker uploads) are still surfaced individually.
|
|
246
|
+
for (const s of skills) {
|
|
247
|
+
if (s.finding.artifact)
|
|
248
|
+
notifiedSkillVerdicts.add(s.finding.artifact);
|
|
249
|
+
}
|
|
250
|
+
// The summary: a single toast listing every risky skill, shown longer.
|
|
251
|
+
const hasCritical = skills.some((s) => s.severity === "CRITICAL");
|
|
252
|
+
const body = skills.map((s) => `${s.key} (${s.severity})`).join("\n");
|
|
253
|
+
enqueueToast(`${branding.name} detected ${skills.length} malicious/suspicious skill(s):\n${body}`, hasCritical ? "error" : "warning", STARTUP_SUMMARY_DURATION_MS);
|
|
254
|
+
}
|
|
255
|
+
catch (e) {
|
|
256
|
+
logger.debug("Skill summary popup failed", { error: String(e) });
|
|
257
|
+
}
|
|
258
|
+
};
|
|
32
259
|
// Set up the cron job that cleans up the approval store.
|
|
33
260
|
const interval = setInterval(() => {
|
|
34
261
|
approvalStore.cleanup();
|
|
35
262
|
}, APPROVAL_STORE_CLEANUP_INTERVAL_MS);
|
|
36
263
|
interval.unref?.();
|
|
37
|
-
const toolHandlers = createToolHandlers(toolLogger, approvalStore, threatsDir,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
264
|
+
const toolHandlers = createToolHandlers(toolLogger, approvalStore, threatsDir, trustedDomainsDir, { showToast }, branding);
|
|
265
|
+
/**
|
|
266
|
+
* Run the session scan exactly once per session and cache the promise. The
|
|
267
|
+
* cached promise is the dedupe guard — a second call (repeated
|
|
268
|
+
* `session.updated`, or another plugin instance) returns the same
|
|
269
|
+
* in-flight/settled promise instead of re-scanning. The scan's structured
|
|
270
|
+
* findings drive the summary popup (see `surfaceStartupSummary`).
|
|
271
|
+
*/
|
|
272
|
+
const scanSession = (sessionID) => {
|
|
273
|
+
if (sessionID === "unknown")
|
|
274
|
+
return Promise.resolve();
|
|
275
|
+
const existing = sessionScanPromises.get(sessionID);
|
|
276
|
+
if (existing)
|
|
277
|
+
return existing;
|
|
278
|
+
const promise = (async () => {
|
|
279
|
+
logger.debug(`${branding.name}: starting session scan`, { sessionID });
|
|
280
|
+
const scanHandler = createSessionScanHandler(scanLogger, directory,
|
|
281
|
+
// After the scan (TUI ready): summary popup from the structured findings.
|
|
282
|
+
surfaceStartupSummary, branding,
|
|
283
|
+
// One-time notices (e.g. skill-upload consent) as info toasts.
|
|
284
|
+
(noticeIds) => {
|
|
285
|
+
for (const id of noticeIds) {
|
|
286
|
+
const text = formatNoticeById(id, branding);
|
|
287
|
+
if (text)
|
|
288
|
+
enqueueToast(text, "info", STARTUP_SUMMARY_DURATION_MS);
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
await scanHandler();
|
|
292
|
+
})();
|
|
293
|
+
sessionScanPromises.set(sessionID, promise);
|
|
294
|
+
return promise;
|
|
295
|
+
};
|
|
46
296
|
return {
|
|
47
|
-
"tool.execute.before": toolHandlers["tool.execute.before"],
|
|
48
297
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* to assistant messages or system prompts was less effective.
|
|
298
|
+
* Register the Sage MCP server so sage_report_false_positive /
|
|
299
|
+
* sage_list_audit_entries are available as agent tools. OpenCode runs
|
|
300
|
+
* config hooks before MCP init, so mutating cfg.mcp here is sufficient.
|
|
301
|
+
* ??= ensures a user-supplied override in opencode.json is never clobbered.
|
|
54
302
|
*/
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
if (
|
|
61
|
-
return;
|
|
62
|
-
const sessionID = message.info.sessionID;
|
|
63
|
-
const findings = pendingFindingsBySession.get(sessionID);
|
|
64
|
-
if (!findings)
|
|
303
|
+
config: async (cfg) => {
|
|
304
|
+
const dir = dirname(fileURLToPath(import.meta.url));
|
|
305
|
+
const serverPath = join(dir, "mcp-server.cjs");
|
|
306
|
+
// biome-ignore lint/suspicious/noExplicitAny: OpenCode Config.mcp types not exported
|
|
307
|
+
const mcp = cfg.mcp;
|
|
308
|
+
if (mcp?.sage)
|
|
65
309
|
return;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
"Inform the user about these security findings.",
|
|
76
|
-
`</system-reminder>`,
|
|
77
|
-
].join("\n"),
|
|
78
|
-
synthetic: true,
|
|
310
|
+
// biome-ignore lint/suspicious/noExplicitAny: assigning McpLocalConfig without importing its type
|
|
311
|
+
cfg.mcp = {
|
|
312
|
+
...mcp,
|
|
313
|
+
sage: {
|
|
314
|
+
type: "local",
|
|
315
|
+
command: [process.execPath, serverPath],
|
|
316
|
+
environment: { BUN_BE_BUN: "1", SAGE_AGENT_RUNTIME: "opencode" },
|
|
317
|
+
enabled: true,
|
|
318
|
+
},
|
|
79
319
|
};
|
|
80
|
-
message.parts.push(textPart);
|
|
81
|
-
logger.debug(`Injected sage plugin scan findings to user message`, {
|
|
82
|
-
sessionID,
|
|
83
|
-
findingsLength: findings.length,
|
|
84
|
-
});
|
|
85
|
-
pendingFindingsBySession.delete(sessionID);
|
|
86
320
|
},
|
|
87
|
-
|
|
88
|
-
//
|
|
321
|
+
"tool.execute.before": toolHandlers["tool.execute.before"],
|
|
322
|
+
// Run the session scan on the first session.updated event. It discovers
|
|
323
|
+
// skills, populates the verdict cache, and (post-scan, once the TUI is up)
|
|
324
|
+
// shows the summary popup. OpenCode >=1.3 replaced session.created with
|
|
325
|
+
// session.updated. Dedupe + the work live in scanSession.
|
|
89
326
|
event: async ({ event }) => {
|
|
90
327
|
if (event.type !== "session.updated")
|
|
91
328
|
return;
|
|
92
329
|
// biome-ignore lint/suspicious/noExplicitAny: Event types from SDK not fully typed
|
|
93
330
|
const props = event.properties;
|
|
94
331
|
const sessionID = props?.sessionID ?? props?.info?.id ?? "unknown";
|
|
95
|
-
|
|
96
|
-
return;
|
|
97
|
-
try {
|
|
98
|
-
logger.debug(`${branding.name}: starting session scan`, { sessionID });
|
|
99
|
-
const scanHandler = createSessionScanHandler(scanLogger, directory, (msg) => {
|
|
100
|
-
pendingFindingsBySession.set(sessionID, msg);
|
|
101
|
-
}, branding);
|
|
102
|
-
await scanHandler();
|
|
103
|
-
if (sessionID !== "unknown") {
|
|
104
|
-
scannedSessions.add(sessionID);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
catch (error) {
|
|
108
|
-
logger.error(`${branding.name} session scan failed (fail-open)`, {
|
|
109
|
-
sessionID,
|
|
110
|
-
error: String(error),
|
|
111
|
-
});
|
|
112
|
-
}
|
|
332
|
+
await scanSession(sessionID);
|
|
113
333
|
},
|
|
114
334
|
tool: {
|
|
115
335
|
sage_approve: tool({
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|