Sage is a TypeScript monorepo with a shared core library and platform-specific connectors.
packages/
├── core/ @gendigital/sage-core Platform-agnostic detection engine
├── claude-code/ @gendigital/sage-claude-code Claude Code hook entry points
├── openclaw/ @gendigital/sage-openclaw OpenClaw plugin connector
├── opencode/ @gendigital/sage-opencode OpenCode plugin connector
└── extension/ sage-cursor Cursor and VS Code extensions (unscoped: vsce rejects @ and / in extension names)
The core library contains all detection logic. It has no platform dependencies and is imported by all connectors.
| Module | Purpose |
|---|---|
extractors.ts |
Extracts URLs, commands, file paths from tool inputs |
heuristics.ts |
Matches artifacts against YAML threat patterns |
engine.ts |
Decision engine - combines signals into a Verdict |
threat-loader.ts |
Loads YAML threat definitions |
config.ts |
Config loading and validation (Zod schemas) |
cache.ts |
JSON file verdict cache with TTLs |
allowlist.ts |
User allowlist management |
audit-log.ts |
JSONL audit logging |
trusted-domains.ts |
Trusted domain loading and matching |
plugin-scanner.ts |
Plugin file scanning |
package-checker.ts |
npm/PyPI supply-chain checks |
installation-id.ts |
Persistent installation UUID (~/.sage/installation-id) |
version-check.ts |
Version check via POST with environment context |
session-start.ts |
Session start orchestrator (scan + version check) |
clients/url-check.ts |
URL reputation API client and endpoint resolver |
clients/file-check.ts |
File reputation API client |
clients/package-registry.ts |
npm/PyPI registry client |
Two entry points, each bundled by esbuild into a single CJS file:
pre-tool-use.ts- Reads tool call JSON from stdin, orchestrates core, outputs verdict JSON to stdout.session-start.ts- Scans installed plugins for threats.
Registered via hooks/hooks.json.
In-process plugin using api.on('before_tool_call'). Includes:
tool-handler.ts- Intercepts tool calls, runs detection pipeline, returnsrequireApprovalfor flagged actionsstartup-scan.ts- Plugin scanning at gateway/session start
VS Code API extension with platform-specific installers:
shared_extension.ts- Registers commands: enable protection, disable until restart, open config, show hook healthcursor_hook_installer.ts/vscode_hook_installer.ts- Install managed hooks into Cursor or VS Code
Hook stdin (JSON) -> extract artifacts -> check allowlist -> check cache
-> heuristics + URL check + package check -> DecisionEngine
-> cache result -> audit log -> hook stdout (JSON)
Claude Code hooks exit 0. Errors return an allow verdict.
before_tool_call event -> extract artifacts -> check allowlist -> check cache
-> heuristics + URL check + package check -> DecisionEngine
-> cache result -> audit log -> block/pass
Flagged actions return a requireApproval object that triggers native platform approval dialogs. An onResolution callback handles persistent allowlisting when the user selects "Allow always".
Managed hook intercepts tool call -> spawns sage-hook.cjs subprocess
-> extract artifacts -> check allowlist -> check cache
-> heuristics + URL check + package check -> DecisionEngine
-> cache result -> audit log -> return verdict
Extension hooks (Cursor / VS Code) always exit with code 0; the host reads the JSON response to enforce blocking.
- All patterns are data. Detection rules live in
threats/*.yaml, not in code. This makes rules easy to review, contribute, and update independently. - Fail-open. Every error path returns
allow. Sage should never break the agent. - Shared core. All platforms use the same
@gendigital/sage-corelibrary, ensuring consistent detection regardless of connector. - No runtime dependencies beyond Node.js. The core uses native
fetch,yamlfor YAML parsing, andzodfor validation. Connectors are bundled into single CJS files.