@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
|
@@ -7,6 +7,7 @@ import type { Branding, Logger, PluginInfo } from "@gendigital/sage-core";
|
|
|
7
7
|
* Discover OpenCode plugins from all sources:
|
|
8
8
|
* 1. NPM packages from config files
|
|
9
9
|
* 2. Local plugin files (global + project)
|
|
10
|
+
* 3. Loose skills (global + project) as pseudo-plugins
|
|
10
11
|
*/
|
|
11
12
|
export declare function discoverOpenCodePlugins(logger: Logger, projectDir?: string, branding?: Branding): Promise<PluginInfo[]>;
|
|
12
13
|
//# sourceMappingURL=plugin-discovery.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin-discovery.d.ts","sourceRoot":"","sources":["../src/plugin-discovery.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,
|
|
1
|
+
{"version":3,"file":"plugin-discovery.d.ts","sourceRoot":"","sources":["../src/plugin-discovery.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAa,MAAM,uBAAuB,CAAC;AA4CrF;;;;;GAKG;AACH,wBAAsB,uBAAuB,CAC5C,MAAM,EAAE,MAAM,EACd,UAAU,CAAC,EAAE,MAAM,EACnB,QAAQ,GAAE,QAA0B,GAClC,OAAO,CAAC,UAAU,EAAE,CAAC,CA6BvB"}
|
package/dist/plugin-discovery.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { readdir, stat } from "node:fs/promises";
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
7
|
import { basename, join } from "node:path";
|
|
8
|
-
import { defaultBranding, getFileContent } from "@gendigital/sage-core";
|
|
8
|
+
import { defaultBranding, discoverLooseSkillsAcrossRoots, getFileContent, getHomeDir, } from "@gendigital/sage-core";
|
|
9
9
|
/** Resolve XDG base directories with fallbacks to homedir defaults */
|
|
10
10
|
function getConfigHome() {
|
|
11
11
|
return process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config");
|
|
@@ -14,10 +14,31 @@ function getCacheHome() {
|
|
|
14
14
|
return process.env.XDG_CACHE_HOME ?? join(homedir(), ".cache");
|
|
15
15
|
}
|
|
16
16
|
const PROJECT_CONFIG_NAME = "opencode.json";
|
|
17
|
+
/**
|
|
18
|
+
* Loose-skill roots OpenCode loads, at both scopes. Global (personal) skills
|
|
19
|
+
* live under the user profile; project skills live inside the project dir.
|
|
20
|
+
* OpenCode reads its own `opencode` family plus Claude- and agent-compatible
|
|
21
|
+
* families; each is scanned so a malicious skill in any load path is covered.
|
|
22
|
+
* The global `opencode` root is XDG-based (`~/.config/opencode/skills`), not
|
|
23
|
+
* `~/.opencode/skills`. The `scope` key segment keeps a personal and project
|
|
24
|
+
* skill of the same family from colliding in the scan cache.
|
|
25
|
+
*/
|
|
26
|
+
function resolveSkillRoots(projectDir) {
|
|
27
|
+
const roots = [
|
|
28
|
+
{ dir: join(getConfigHome(), "opencode", "skills"), tag: "opencode", scope: "personal" },
|
|
29
|
+
{ dir: join(getHomeDir(), ".claude", "skills"), tag: "claude", scope: "personal" },
|
|
30
|
+
{ dir: join(getHomeDir(), ".agents", "skills"), tag: "agents", scope: "personal" },
|
|
31
|
+
];
|
|
32
|
+
if (projectDir) {
|
|
33
|
+
roots.push({ dir: join(projectDir, ".opencode", "skills"), tag: "opencode", scope: "project" }, { dir: join(projectDir, ".claude", "skills"), tag: "claude", scope: "project" }, { dir: join(projectDir, ".agents", "skills"), tag: "agents", scope: "project" });
|
|
34
|
+
}
|
|
35
|
+
return roots;
|
|
36
|
+
}
|
|
17
37
|
/**
|
|
18
38
|
* Discover OpenCode plugins from all sources:
|
|
19
39
|
* 1. NPM packages from config files
|
|
20
40
|
* 2. Local plugin files (global + project)
|
|
41
|
+
* 3. Loose skills (global + project) as pseudo-plugins
|
|
21
42
|
*/
|
|
22
43
|
export async function discoverOpenCodePlugins(logger, projectDir, branding = defaultBranding) {
|
|
23
44
|
logger.debug(`${branding.name} plugin discovery: scanning OpenCode plugins`);
|
|
@@ -35,6 +56,11 @@ export async function discoverOpenCodePlugins(logger, projectDir, branding = def
|
|
|
35
56
|
const projectPlugins = await discoverLocalPlugins(projectPluginsDir, "project", logger);
|
|
36
57
|
plugins.push(...projectPlugins);
|
|
37
58
|
}
|
|
59
|
+
// 4. Discover loose skills as pseudo-plugins (deduped by resolved path,
|
|
60
|
+
// fails open per root) so the scan pipeline covers skills installed outside
|
|
61
|
+
// any plugin.
|
|
62
|
+
const skills = await discoverLooseSkillsAcrossRoots(resolveSkillRoots(projectDir));
|
|
63
|
+
plugins.push(...skills);
|
|
38
64
|
logger.debug(`${branding.name} plugin discovery: found ${plugins.length} plugin(s)`);
|
|
39
65
|
return plugins;
|
|
40
66
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin-discovery.js","sourceRoot":"","sources":["../src/plugin-discovery.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE3C,OAAO,
|
|
1
|
+
{"version":3,"file":"plugin-discovery.js","sourceRoot":"","sources":["../src/plugin-discovery.ts"],"names":[],"mappings":"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"}
|
package/dist/startup-scan.d.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Startup and session scan handlers for OpenCode.
|
|
3
|
-
*
|
|
3
|
+
* Runs core's session scan and hands back the structured findings so the
|
|
4
|
+
* connector can build its own TUI popup (rather than a formatted string).
|
|
4
5
|
*/
|
|
5
|
-
import { type Branding, type Logger } from "@gendigital/sage-core";
|
|
6
|
-
export declare function
|
|
6
|
+
import { type Branding, type Logger, type PluginScanResult } from "@gendigital/sage-core";
|
|
7
|
+
export declare function resolveSkillUploadWorkerPath(): string | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* Build a session-scan runner. Discovers OpenCode plugins/skills (minus Sage
|
|
10
|
+
* itself), runs the core session scan — which populates the verdict cache and
|
|
11
|
+
* spawns the skill-upload worker — and passes the structured results to
|
|
12
|
+
* `onResults`. Fails open: any error is logged and the handler resolves.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createSessionScanHandler(logger: Logger, projectDir?: string, onResults?: (results: PluginScanResult[]) => void, branding?: Branding, onNotices?: (noticeIds: string[]) => void): () => Promise<void>;
|
|
7
15
|
//# sourceMappingURL=startup-scan.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"startup-scan.d.ts","sourceRoot":"","sources":["../src/startup-scan.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"startup-scan.d.ts","sourceRoot":"","sources":["../src/startup-scan.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EACN,KAAK,QAAQ,EAEb,KAAK,MAAM,EACX,KAAK,gBAAgB,EAErB,MAAM,uBAAuB,CAAC;AAqB/B,wBAAgB,4BAA4B,IAAI,MAAM,GAAG,SAAS,CAOjE;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACvC,MAAM,EAAE,MAAM,EACd,UAAU,CAAC,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,gBAAgB,EAAE,KAAK,IAAI,EACjD,QAAQ,GAAE,QAA0B,EACpC,SAAS,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,IAAI,GACvC,MAAM,OAAO,CAAC,IAAI,CAAC,CAuCrB"}
|
package/dist/startup-scan.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Startup and session scan handlers for OpenCode.
|
|
3
|
-
*
|
|
3
|
+
* Runs core's session scan and hands back the structured findings so the
|
|
4
|
+
* connector can build its own TUI popup (rather than a formatted string).
|
|
4
5
|
*/
|
|
5
6
|
import { createRequire } from "node:module";
|
|
6
|
-
import {
|
|
7
|
+
import { defaultBranding, runSessionStart, } from "@gendigital/sage-core";
|
|
7
8
|
import { getBundledDataDirs, getSageVersion } from "./bundled-dirs.js";
|
|
8
9
|
import { discoverOpenCodePlugins } from "./plugin-discovery.js";
|
|
10
|
+
const SELF_PREFIX = "@gendigital/sage-opencode@";
|
|
9
11
|
/**
|
|
10
|
-
* OpenCode is published as an unbundled ESM plugin (tsc only). The
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* OpenCode is published as an unbundled ESM plugin (tsc only). The detached
|
|
13
|
+
* workers therefore live in the `@gendigital/sage-core` package under
|
|
14
|
+
* `dist/`. Resolve via `require.resolve` so we pick them up wherever
|
|
15
|
+
* pnpm/node placed them.
|
|
14
16
|
*/
|
|
15
17
|
function resolveModelDownloadWorkerPath() {
|
|
16
18
|
try {
|
|
@@ -21,21 +23,58 @@ function resolveModelDownloadWorkerPath() {
|
|
|
21
23
|
return undefined;
|
|
22
24
|
}
|
|
23
25
|
}
|
|
24
|
-
export function
|
|
25
|
-
|
|
26
|
+
export function resolveSkillUploadWorkerPath() {
|
|
27
|
+
try {
|
|
28
|
+
const req = createRequire(import.meta.url);
|
|
29
|
+
return req.resolve("@gendigital/sage-core/dist/skill-upload-worker.js");
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Build a session-scan runner. Discovers OpenCode plugins/skills (minus Sage
|
|
37
|
+
* itself), runs the core session scan — which populates the verdict cache and
|
|
38
|
+
* spawns the skill-upload worker — and passes the structured results to
|
|
39
|
+
* `onResults`. Fails open: any error is logged and the handler resolves.
|
|
40
|
+
*/
|
|
41
|
+
export function createSessionScanHandler(logger, projectDir, onResults, branding = defaultBranding, onNotices) {
|
|
42
|
+
const { threatsDir, trustedDomainsDir } = getBundledDataDirs();
|
|
26
43
|
const version = getSageVersion();
|
|
27
|
-
return
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
44
|
+
return async () => {
|
|
45
|
+
try {
|
|
46
|
+
const discovered = await discoverOpenCodePlugins(logger, projectDir, branding);
|
|
47
|
+
const plugins = discovered.filter((p) => !p.key.startsWith(SELF_PREFIX));
|
|
48
|
+
const { scanResults, notices } = await runSessionStart({
|
|
49
|
+
plugins,
|
|
50
|
+
threatsDir,
|
|
51
|
+
trustedDomainsDir,
|
|
52
|
+
version,
|
|
53
|
+
logger,
|
|
54
|
+
agentRuntime: "opencode",
|
|
55
|
+
// No agentRuntimeVersion: the OpenCode SDK exposes no host version synchronously.
|
|
56
|
+
modelDownloadWorkerPath: resolveModelDownloadWorkerPath(),
|
|
57
|
+
skillUploadWorkerPath: resolveSkillUploadWorkerPath(),
|
|
58
|
+
});
|
|
59
|
+
const findingsCount = scanResults.reduce((n, r) => n + r.findings.length, 0);
|
|
60
|
+
logger.debug(`${branding.name} session scan complete`, {
|
|
61
|
+
agentRuntime: "opencode",
|
|
62
|
+
pluginsCount: plugins.length,
|
|
63
|
+
resultsWithFindings: scanResults.length,
|
|
64
|
+
findingsCount,
|
|
65
|
+
});
|
|
66
|
+
// Surface the one-time notice BEFORE the finding toasts. The toast queue
|
|
67
|
+
// is FIFO, so enqueuing notices first shows the "new feature" info ahead
|
|
68
|
+
// of any malware findings.
|
|
69
|
+
if (notices && notices.length > 0)
|
|
70
|
+
onNotices?.(notices);
|
|
71
|
+
onResults?.(scanResults);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
logger.error(`${branding.name} session scan failed (fail-open)`, {
|
|
75
|
+
error: String(error),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
};
|
|
40
79
|
}
|
|
41
80
|
//# sourceMappingURL=startup-scan.js.map
|
package/dist/startup-scan.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"startup-scan.js","sourceRoot":"","sources":["../src/startup-scan.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"startup-scan.js","sourceRoot":"","sources":["../src/startup-scan.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAEN,eAAe,EAGf,eAAe,GACf,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACvE,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,MAAM,WAAW,GAAG,4BAA4B,CAAC;AAEjD;;;;;GAKG;AACH,SAAS,8BAA8B;IACtC,IAAI,CAAC;QACJ,MAAM,GAAG,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC3C,OAAO,GAAG,CAAC,OAAO,CAAC,qDAAqD,CAAC,CAAC;IAC3E,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,SAAS,CAAC;IAClB,CAAC;AACF,CAAC;AAED,MAAM,UAAU,4BAA4B;IAC3C,IAAI,CAAC;QACJ,MAAM,GAAG,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC3C,OAAO,GAAG,CAAC,OAAO,CAAC,mDAAmD,CAAC,CAAC;IACzE,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,SAAS,CAAC;IAClB,CAAC;AACF,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,wBAAwB,CACvC,MAAc,EACd,UAAmB,EACnB,SAAiD,EACjD,WAAqB,eAAe,EACpC,SAAyC;IAEzC,MAAM,EAAE,UAAU,EAAE,iBAAiB,EAAE,GAAG,kBAAkB,EAAE,CAAC;IAC/D,MAAM,OAAO,GAAG,cAAc,EAAE,CAAC;IAEjC,OAAO,KAAK,IAAI,EAAE;QACjB,IAAI,CAAC;YACJ,MAAM,UAAU,GAAG,MAAM,uBAAuB,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;YAC/E,MAAM,OAAO,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC;YAEzE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,MAAM,eAAe,CAAC;gBACtD,OAAO;gBACP,UAAU;gBACV,iBAAiB;gBACjB,OAAO;gBACP,MAAM;gBACN,YAAY,EAAE,UAAU;gBACxB,kFAAkF;gBAClF,uBAAuB,EAAE,8BAA8B,EAAE;gBACzD,qBAAqB,EAAE,4BAA4B,EAAE;aACrD,CAAC,CAAC;YAEH,MAAM,aAAa,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;YAC7E,MAAM,CAAC,KAAK,CAAC,GAAG,QAAQ,CAAC,IAAI,wBAAwB,EAAE;gBACtD,YAAY,EAAE,UAAU;gBACxB,YAAY,EAAE,OAAO,CAAC,MAAM;gBAC5B,mBAAmB,EAAE,WAAW,CAAC,MAAM;gBACvC,aAAa;aACb,CAAC,CAAC;YACH,yEAAyE;YACzE,yEAAyE;YACzE,2BAA2B;YAC3B,IAAI,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;gBAAE,SAAS,EAAE,CAAC,OAAO,CAAC,CAAC;YACxD,SAAS,EAAE,CAAC,WAAW,CAAC,CAAC;QAC1B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,CAAC,KAAK,CAAC,GAAG,QAAQ,CAAC,IAAI,kCAAkC,EAAE;gBAChE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC;aACpB,CAAC,CAAC;QACJ,CAAC;IACF,CAAC,CAAC;AACH,CAAC"}
|
package/dist/tool-handler.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { type ApprovalStore, type Branding, type Logger } from "@gendigital/sage-core";
|
|
1
|
+
import { type ApprovalStore, type Branding, type CanonicalToolType, type Logger } from "@gendigital/sage-core";
|
|
2
|
+
export declare const OPENCODE_TOOL_MAP: Record<string, CanonicalToolType>;
|
|
2
3
|
export interface ToolHandlerOptions {
|
|
3
4
|
showToast?: (msg: string, variant: "info" | "success" | "warning" | "error") => void;
|
|
4
5
|
}
|
|
5
|
-
export declare function createToolHandlers(logger: Logger, approvalStore: ApprovalStore, threatsDir: string,
|
|
6
|
+
export declare function createToolHandlers(logger: Logger, approvalStore: ApprovalStore, threatsDir: string, trustedDomainsDir: string, options?: ToolHandlerOptions, branding?: Branding): {
|
|
6
7
|
"tool.execute.before": (input: {
|
|
7
8
|
tool: string;
|
|
8
9
|
sessionID: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-handler.d.ts","sourceRoot":"","sources":["../src/tool-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,aAAa,EAClB,KAAK,QAAQ,
|
|
1
|
+
{"version":3,"file":"tool-handler.d.ts","sourceRoot":"","sources":["../src/tool-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,aAAa,EAClB,KAAK,QAAQ,EACb,KAAK,iBAAiB,EAOtB,KAAK,MAAM,EAEX,MAAM,uBAAuB,CAAC;AAE/B,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAc/D,CAAC;AA+BF,MAAM,WAAW,kBAAkB;IAClC,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,KAAK,IAAI,CAAC;CACrF;AAED,wBAAgB,kBAAkB,CACjC,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,aAAa,EAC5B,UAAU,EAAE,MAAM,EAClB,iBAAiB,EAAE,MAAM,EACzB,OAAO,CAAC,EAAE,kBAAkB,EAC5B,QAAQ,GAAE,QAA0B;mCAG5B;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,UAClD;QAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,KACvC,OAAO,CAAC,IAAI,CAAC;EAyIhB"}
|
package/dist/tool-handler.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { canonicalizeToolName, defaultBranding, formatDenyMessage, guardToolCall, summarizeArtifacts, } from "@gendigital/sage-core";
|
|
2
|
-
const OPENCODE_TOOL_MAP = {
|
|
1
|
+
import { canonicalizeToolName, captureEnabled, captureHookInput, defaultBranding, formatDenyMessage, guardToolCall, summarizeArtifacts, } from "@gendigital/sage-core";
|
|
2
|
+
export const OPENCODE_TOOL_MAP = {
|
|
3
3
|
bash: "Bash",
|
|
4
4
|
webfetch: "WebFetch",
|
|
5
5
|
write: "Write",
|
|
@@ -37,7 +37,7 @@ function normalizeToolInput(toolName, args) {
|
|
|
37
37
|
return args;
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
|
-
export function createToolHandlers(logger, approvalStore, threatsDir,
|
|
40
|
+
export function createToolHandlers(logger, approvalStore, threatsDir, trustedDomainsDir, options, branding = defaultBranding) {
|
|
41
41
|
const beforeToolUse = async (input, output) => {
|
|
42
42
|
const completeHook = (result, data = {}) => {
|
|
43
43
|
logger.debug("OpenCode tool hook completed", {
|
|
@@ -59,6 +59,17 @@ export function createToolHandlers(logger, approvalStore, threatsDir, allowlists
|
|
|
59
59
|
});
|
|
60
60
|
try {
|
|
61
61
|
const args = output.args ?? {};
|
|
62
|
+
// E2E payload-drift capture (drift loop). OpenCode is an in-process
|
|
63
|
+
// ESM plugin (not MCP, not a CJS hook), so the "raw wire" payload is the
|
|
64
|
+
// in-proc tool-call shape OpenCode hands us. Namespaced opencode-* so it
|
|
65
|
+
// can't collide with other agents sharing SAGE_E2E_CAPTURE_DIR. Inert in
|
|
66
|
+
// production (env unset); fail-open inside captureHookInput.
|
|
67
|
+
if (captureEnabled()) {
|
|
68
|
+
await captureHookInput("PreToolUse", { tool: input.tool, sessionID: input.sessionID, callID: input.callID, args }, {
|
|
69
|
+
toolName: canonicalizeToolName(OPENCODE_TOOL_MAP, input.tool),
|
|
70
|
+
toolInput: normalizeToolInput(input.tool, args),
|
|
71
|
+
}, { filePrefix: "opencode-" });
|
|
72
|
+
}
|
|
62
73
|
const artifacts = extractFromOpenCodeTool(input.tool, args);
|
|
63
74
|
if (!artifacts || artifacts.length === 0) {
|
|
64
75
|
completeHook("skipped", { skippedReason: "no_artifacts" });
|
|
@@ -74,7 +85,7 @@ export function createToolHandlers(logger, approvalStore, threatsDir, allowlists
|
|
|
74
85
|
artifacts,
|
|
75
86
|
}, {
|
|
76
87
|
threatsDir,
|
|
77
|
-
|
|
88
|
+
trustedDomainsDir,
|
|
78
89
|
logger,
|
|
79
90
|
}, approvalStore);
|
|
80
91
|
if (verdict.decision === "allow") {
|
|
@@ -107,7 +118,13 @@ export function createToolHandlers(logger, approvalStore, threatsDir, allowlists
|
|
|
107
118
|
throw new SageVerdictBlockError(formatDenyMessage(verdict, branding));
|
|
108
119
|
}
|
|
109
120
|
// ask — actionId is always set for ask verdicts
|
|
110
|
-
const
|
|
121
|
+
const maxReasons = 3;
|
|
122
|
+
const reasons = verdict.reasons.length > 0
|
|
123
|
+
? verdict.reasons.slice(0, maxReasons).join("; ") +
|
|
124
|
+
(verdict.reasons.length > maxReasons
|
|
125
|
+
? `; ... and ${verdict.reasons.length - maxReasons} more`
|
|
126
|
+
: "")
|
|
127
|
+
: verdict.category;
|
|
111
128
|
completeHook("evaluated", {
|
|
112
129
|
toolName,
|
|
113
130
|
decision: verdict.decision,
|
package/dist/tool-handler.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-handler.js","sourceRoot":"","sources":["../src/tool-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAIN,oBAAoB,EACpB,eAAe,EACf,iBAAiB,EACjB,aAAa,EAEb,kBAAkB,GAClB,MAAM,uBAAuB,CAAC;AAE/B,MAAM,iBAAiB,GAAsC;
|
|
1
|
+
{"version":3,"file":"tool-handler.js","sourceRoot":"","sources":["../src/tool-handler.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
7
|
-
"version": "0.
|
|
7
|
+
"version": "0.12.0",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"description": "Safety for Agents - ADR layer for OpenCode",
|
|
10
10
|
"homepage": "https://github.com/gendigitalinc/sage",
|
|
@@ -32,10 +32,12 @@
|
|
|
32
32
|
],
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@opencode-ai/plugin": "^1.14.48",
|
|
35
|
-
"@gendigital/sage-
|
|
35
|
+
"@gendigital/sage-mcp": "0.12.0",
|
|
36
|
+
"@gendigital/sage-core": "0.12.0"
|
|
36
37
|
},
|
|
37
38
|
"devDependencies": {
|
|
38
39
|
"@types/node": "^22.0.0",
|
|
40
|
+
"esbuild": "^0.25.0",
|
|
39
41
|
"typescript": "^5.9.0",
|
|
40
42
|
"vitest": "^4.0.0"
|
|
41
43
|
},
|
|
@@ -52,7 +54,7 @@
|
|
|
52
54
|
"malware-detection"
|
|
53
55
|
],
|
|
54
56
|
"scripts": {
|
|
55
|
-
"build": "pnpm
|
|
57
|
+
"build": "pnpm run clean && pnpm run sync:assets && tsc && node esbuild.config.mjs",
|
|
56
58
|
"clean": "node ../../scripts/clean.mjs dist resources tsconfig.tsbuildinfo",
|
|
57
59
|
"sync:assets": "node scripts/sync-assets.mjs",
|
|
58
60
|
"test": "vitest run"
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# Agent-layer threat patterns for Sage
|
|
2
|
+
#
|
|
3
|
+
# Author: ATR (Agent Threat Rules) community contributors
|
|
4
|
+
# Upstream: https://github.com/Agent-Threat-Rule/agent-threat-rules
|
|
5
|
+
# Contributed per issue #30 + vaclavbelak's comment
|
|
6
|
+
# (https://github.com/gendigitalinc/sage/issues/30#issuecomment-4266904912)
|
|
7
|
+
#
|
|
8
|
+
# License: MIT
|
|
9
|
+
# Rules in this file are adapted from the ATR project
|
|
10
|
+
# (https://github.com/Agent-Threat-Rule/agent-threat-rules),
|
|
11
|
+
# Copyright (c) 2026 ATR Contributors, and are included in Sage
|
|
12
|
+
# under the ATR MIT license per explicit agreement in issue #30.
|
|
13
|
+
# Sage-specific modifications to each rule are noted in its comment block.
|
|
14
|
+
#
|
|
15
|
+
# What this file covers (gaps vs Sage's existing rules):
|
|
16
|
+
# MCP tool / response attacks CLT-MCP-004 (1 rule -- path traversal)
|
|
17
|
+
# Skill package compromise CLT-SKL-001..008 (6 rules -- IDs 001,002,004,005,006,008; 003/007 dropped for FP risk)
|
|
18
|
+
# Context exfiltration CLT-CTX-003 (1 rule -- PEM key leak)
|
|
19
|
+
# Supply chain CLT-SUPPLY-002 (1 rule)
|
|
20
|
+
#
|
|
21
|
+
# All prompt_injection rules have been moved to prompt-injection.yaml:
|
|
22
|
+
# CLT-PI-052 (IMPORTANT-tag shadowing, ex CLT-MCP-002)
|
|
23
|
+
# CLT-PI-091..093 (HTML-comment delivery, jailbreak persona, CJK pivot)
|
|
24
|
+
# Dropped: CLT-CTX-002 (instruction tampering) -- covered by CLT-PI-001..005
|
|
25
|
+
#
|
|
26
|
+
# Design notes:
|
|
27
|
+
# - All rules use `match_on: content` so they fire on Write/Edit content,
|
|
28
|
+
# plugin/skill file scans, and any agent integration that passes a
|
|
29
|
+
# `content` artifact. They complement (do not duplicate) Sage's existing
|
|
30
|
+
# command/URL/credential-file rules.
|
|
31
|
+
# - Confidence values are calibrated to Sage's policy zones (docs/decision-pipeline.md):
|
|
32
|
+
# 0.95 = attack-only signal, deny under all sensitivities
|
|
33
|
+
# 0.85 = high-precision attack pattern, deny paranoid/balanced, ask relaxed
|
|
34
|
+
# - `case_insensitive: true` replaces ATR's `(?i)` inline flag.
|
|
35
|
+
|
|
36
|
+
# ===========================================================================
|
|
37
|
+
# MCP TOOL & RESPONSE POISONING
|
|
38
|
+
# ===========================================================================
|
|
39
|
+
|
|
40
|
+
- id: "CLT-MCP-004"
|
|
41
|
+
category: mcp_poisoning
|
|
42
|
+
severity: warning
|
|
43
|
+
confidence: 0.85
|
|
44
|
+
pattern: "\\.\\.[\\/\\\\](?:\\.\\.[\\/\\\\])+(?:etc|proc|root|sys|boot|passwd|shadow|shadow\\.bak|master\\.key)(?:[\\/\\\\]|\\s|$)"
|
|
45
|
+
match_on: content
|
|
46
|
+
case_insensitive: false
|
|
47
|
+
title: "Path traversal targeting system directory (/etc, /proc, /root, /sys, ...)"
|
|
48
|
+
expires_at: null
|
|
49
|
+
revoked: false
|
|
50
|
+
# Adapted from ATR-2026-00012: narrowed to require 2+ traversal hops terminating
|
|
51
|
+
# in a sensitive system directory (dropped dev, hosts from ATR's list); reduces
|
|
52
|
+
# FP on benign code examples that use relative paths like ../../src/.
|
|
53
|
+
# Fires in pre-hook (Write/Edit content blocking) and session-start plugin scans
|
|
54
|
+
# only; post-hook scanning is limited to category=prompt_injection rules.
|
|
55
|
+
|
|
56
|
+
# ===========================================================================
|
|
57
|
+
# SKILL PACKAGE COMPROMISE -- malicious content inside SKILL.md, plugin
|
|
58
|
+
# manifests, or skill package files.
|
|
59
|
+
# ===========================================================================
|
|
60
|
+
|
|
61
|
+
- id: "CLT-SKL-001"
|
|
62
|
+
category: skill_compromise
|
|
63
|
+
severity: critical
|
|
64
|
+
confidence: 0.85
|
|
65
|
+
pattern: "you\\s+(must|should|will|shall)\\s+(ignore|disregard|override|forget|bypass|circumvent|supersede|set\\s+aside|discard|abandon)\\s+(all\\s+)?(previous|prior|system|safety|security)\\s+(instructions?|prompts?|rules?|guidelines?|policies|restrictions?)"
|
|
66
|
+
match_on: content
|
|
67
|
+
case_insensitive: true
|
|
68
|
+
title: "SKILL.md prompt injection via mandatory override"
|
|
69
|
+
expires_at: null
|
|
70
|
+
revoked: false
|
|
71
|
+
# Adapted from ATR-2026-00120: simplified to single regex targeting the
|
|
72
|
+
# mandatory-subject ("you must/shall") framing only; omits ATR's broader
|
|
73
|
+
# context-window injection and multi-condition layers.
|
|
74
|
+
# Intentional layering with CLT-PI-001..004: those rules catch the bare
|
|
75
|
+
# imperative verb; this rule adds a distinct signal for the explicit mandatory
|
|
76
|
+
# subject, increasing confidence when both fire on the same payload.
|
|
77
|
+
|
|
78
|
+
# CLT-SKL-003 intentionally omitted — dropped for FP risk during ATR curation.
|
|
79
|
+
|
|
80
|
+
- id: "CLT-SKL-002"
|
|
81
|
+
category: skill_compromise
|
|
82
|
+
severity: critical
|
|
83
|
+
confidence: 0.85
|
|
84
|
+
pattern: "(eval\\s*\\$\\(\\s*echo\\s+[\"'][A-Za-z0-9+/=]{20,}[\"']\\s*\\|\\s*base64\\s+-d|echo\\s+[\"'][A-Za-z0-9+/=]{20,}[\"']\\s*\\|\\s*base64\\s+(-d|--decode)\\s*\\|\\s*(bash|sh|python|node|ruby|perl))"
|
|
85
|
+
match_on: content
|
|
86
|
+
case_insensitive: true
|
|
87
|
+
title: "Base64-encoded payload piped to interpreter inside skill content"
|
|
88
|
+
expires_at: null
|
|
89
|
+
revoked: false
|
|
90
|
+
# Source: ATR-2026-00121 (pattern unchanged from upstream).
|
|
91
|
+
|
|
92
|
+
- id: "CLT-SKL-004"
|
|
93
|
+
category: skill_compromise
|
|
94
|
+
severity: critical
|
|
95
|
+
confidence: 0.85
|
|
96
|
+
pattern: "<!--[\\s\\S]{0,200}?(agent\\s+should|send\\s+all|output\\s+all|exfiltrate|curl\\s|wget\\s|post\\s+.{0,30}(key|credential|token|password|secret)|api[_-]?key.{0,30}https?://)"
|
|
97
|
+
match_on: content
|
|
98
|
+
case_insensitive: true
|
|
99
|
+
title: "Hidden payload in HTML comment within skill content"
|
|
100
|
+
expires_at: null
|
|
101
|
+
revoked: false
|
|
102
|
+
# Adapted from ATR-2026-00128: changed ATR's [^>] lookahead to [\s\S] to avoid
|
|
103
|
+
# false negatives when a hyphen precedes an exfil keyword; maintains same
|
|
104
|
+
# exfil-keyword focus (distinct from CLT-PI-002's instruction-override focus).
|
|
105
|
+
|
|
106
|
+
- id: "CLT-SKL-005"
|
|
107
|
+
category: skill_compromise
|
|
108
|
+
severity: critical
|
|
109
|
+
confidence: 0.95
|
|
110
|
+
pattern: "(?:\\uDB40[\\uDC00-\\uDC7F]){3,}"
|
|
111
|
+
match_on: content
|
|
112
|
+
case_insensitive: false
|
|
113
|
+
title: "Unicode Tag character smuggling (U+E0000..U+E007F) in skill content"
|
|
114
|
+
expires_at: null
|
|
115
|
+
revoked: false
|
|
116
|
+
# Source: ATR-2026-00129 (pattern unchanged from upstream). Tag characters
|
|
117
|
+
# are invisible to humans but visible to LLMs; 3+ in sequence is an
|
|
118
|
+
# injection signature with no legitimate use.
|
|
119
|
+
|
|
120
|
+
- id: "CLT-SKL-006"
|
|
121
|
+
category: skill_compromise
|
|
122
|
+
severity: critical
|
|
123
|
+
confidence: 0.85
|
|
124
|
+
pattern: "(tar|zip|7z)\\s+[^\\n]*~/?\\.ssh|(find|ls|cat|strings)\\s+[^\\n]*(wallet\\.dat|seed\\.txt|mnemonic|keystore)[^\\n]*(\\||>|>>|curl|wget|fetch|POST)|sqlite3\\s+[^\\n]*(Cookies|Login\\s*Data|cookies\\.sqlite|places\\.sqlite)"
|
|
125
|
+
match_on: content
|
|
126
|
+
case_insensitive: true
|
|
127
|
+
title: "Compound skill-content exfiltration (wallet/browser/SSH archival)"
|
|
128
|
+
expires_at: null
|
|
129
|
+
revoked: false
|
|
130
|
+
# Adapted from ATR-2026-00149: condensed ATR's 13 detection conditions into
|
|
131
|
+
# one regex covering SSH archival, crypto wallet exfil, and browser DB dump;
|
|
132
|
+
# omits ATR's DNS-exfil, IMDS metadata, and env-file piping layers.
|
|
133
|
+
# Intentional layering with CLT-CRED-008 (command artifact): that rule catches
|
|
134
|
+
# the same SSH archival at shell execution time; this rule catches it in
|
|
135
|
+
# written skill/plugin content before it executes.
|
|
136
|
+
|
|
137
|
+
# CLT-SKL-007 intentionally omitted — dropped for FP risk during ATR curation.
|
|
138
|
+
|
|
139
|
+
- id: "CLT-SKL-008"
|
|
140
|
+
category: skill_compromise
|
|
141
|
+
severity: warning
|
|
142
|
+
confidence: 0.85
|
|
143
|
+
pattern: "(?:(?:always|must|shall|will|should)\\s+){1,3}(?:approve|accept|allow|grant|permit)\\b[^.\\n]{0,80}(?:without|regardless|even\\s+if|despite)\\s+(?:confirmation|permission|approval|consent|asking|checking|verification|user)"
|
|
144
|
+
match_on: content
|
|
145
|
+
case_insensitive: true
|
|
146
|
+
title: "Hidden override instruction in skill -- auto-approve all"
|
|
147
|
+
expires_at: null
|
|
148
|
+
revoked: false
|
|
149
|
+
# Adapted from ATR-2026-00163: added "will"/"should" modals and "despite" as
|
|
150
|
+
# an additional consent-bypass synonym; bounded modal repetition to {1,3}.
|
|
151
|
+
|
|
152
|
+
# ===========================================================================
|
|
153
|
+
# CONTEXT EXFILTRATION
|
|
154
|
+
# ===========================================================================
|
|
155
|
+
|
|
156
|
+
- id: "CLT-CTX-003"
|
|
157
|
+
category: context_exfiltration
|
|
158
|
+
severity: critical
|
|
159
|
+
confidence: 0.95
|
|
160
|
+
pattern: "-----BEGIN\\s+[A-Z ]+PRIVATE\\s+KEY-----[\\s\\S]{40,}-----END\\s+[A-Z ]+PRIVATE\\s+KEY-----"
|
|
161
|
+
match_on: content
|
|
162
|
+
title: "PEM private key block appearing in agent content"
|
|
163
|
+
expires_at: null
|
|
164
|
+
revoked: false
|
|
165
|
+
# Source: ATR-2026-00021 (pattern unchanged from upstream). Fires on Write/Edit
|
|
166
|
+
# content and session-start plugin scans (category context_exfiltration is
|
|
167
|
+
# pre-hook only; post-hook scanning is limited to prompt_injection rules).
|
|
168
|
+
# Covers RSA, EC, DSA, OPENSSH, ENCRYPTED variants.
|
|
169
|
+
|
|
170
|
+
# ===========================================================================
|
|
171
|
+
# SUPPLY CHAIN
|
|
172
|
+
# ===========================================================================
|
|
173
|
+
|
|
174
|
+
- id: "CLT-SUPPLY-002"
|
|
175
|
+
category: supply_chain
|
|
176
|
+
severity: warning
|
|
177
|
+
confidence: 0.95
|
|
178
|
+
pattern: "(?:filesytem|filsystem|file_sytem|flie_system|filessystem|filesystm|filsystm|filesysem)[-_](?:read|write|list|delete|search)"
|
|
179
|
+
match_on: content
|
|
180
|
+
case_insensitive: true
|
|
181
|
+
title: "Typosquatted filesystem tool name (targets MCP filesystem server)"
|
|
182
|
+
expires_at: null
|
|
183
|
+
revoked: false
|
|
184
|
+
# Adapted from ATR-2026-00060: extracted filesystem-server typo variants only;
|
|
185
|
+
# narrows ATR's broader MCP squatting rule to the
|
|
186
|
+
# @modelcontextprotocol/server-filesystem impersonation family.
|