ruvector 0.2.30 → 0.2.32

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.
@@ -0,0 +1,95 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * MCP tool-access policy (ADR-256 — default-deny posture).
5
+ *
6
+ * Borrows metaharness's "default-deny allowlist" security concept using a small,
7
+ * pure, testable module. Operators restrict the exposed/callable MCP tool surface
8
+ * via environment variables:
9
+ *
10
+ * RUVECTOR_MCP_ALLOW="hooks_route,hooks_recall" → only these are exposed/callable
11
+ * RUVECTOR_MCP_DENY="hooks_security_scan" → these are blocked (wins over allow)
12
+ * RUVECTOR_MCP_PROFILE=readonly → a curated safe subset
13
+ *
14
+ * Precedence: DENY > ALLOW/PROFILE > (default allow-all).
15
+ * With no policy configured, all registered tools are allowed (backward compatible);
16
+ * `policy.configured` is false so the server can nudge toward an allowlist.
17
+ *
18
+ * This module is dependency-free and side-effect-free so it can be unit-tested
19
+ * without spawning an MCP stdio server.
20
+ */
21
+
22
+ /** Curated read-only / low-risk tool subsets. */
23
+ const MCP_PROFILES = {
24
+ readonly: [
25
+ 'ruvector',
26
+ 'hooks_stats',
27
+ 'hooks_recall',
28
+ 'hooks_route',
29
+ 'hooks_route_enhanced',
30
+ 'hooks_suggest_context',
31
+ 'hooks_capabilities',
32
+ 'hooks_export',
33
+ 'hooks_doctor',
34
+ 'hooks_attention_info',
35
+ 'hooks_gnn_info',
36
+ ],
37
+ };
38
+
39
+ /** Split a comma/space separated env list into a clean array. */
40
+ function parseList(value) {
41
+ if (!value || typeof value !== 'string') return [];
42
+ return value
43
+ .split(/[,\s]+/)
44
+ .map((s) => s.trim())
45
+ .filter(Boolean);
46
+ }
47
+
48
+ /**
49
+ * Build a tool-access policy from an env-like object (defaults to process.env).
50
+ * Returns { allowSet: Set|null, deny: Set, profile: string|null, configured: bool }.
51
+ */
52
+ function buildToolPolicy(env) {
53
+ const e = env || process.env;
54
+ const allow = parseList(e.RUVECTOR_MCP_ALLOW);
55
+ const deny = new Set(parseList(e.RUVECTOR_MCP_DENY));
56
+ const profileName = e.RUVECTOR_MCP_PROFILE && e.RUVECTOR_MCP_PROFILE.trim();
57
+
58
+ let allowSet = null;
59
+ if (profileName && MCP_PROFILES[profileName]) {
60
+ allowSet = new Set(MCP_PROFILES[profileName]);
61
+ }
62
+ if (allow.length) {
63
+ allowSet = new Set([...(allowSet || []), ...allow]);
64
+ }
65
+
66
+ return {
67
+ allowSet,
68
+ deny,
69
+ profile: profileName && MCP_PROFILES[profileName] ? profileName : null,
70
+ // An unknown profile name with no allow/deny is treated as "not configured".
71
+ configured: Boolean(allowSet || deny.size),
72
+ };
73
+ }
74
+
75
+ /** Is a tool name permitted under the given policy? DENY always wins. */
76
+ function isToolAllowed(name, policy) {
77
+ if (!policy) return true;
78
+ if (policy.deny.has(name)) return false;
79
+ if (policy.allowSet) return policy.allowSet.has(name);
80
+ return true;
81
+ }
82
+
83
+ /** Filter a TOOLS array (objects with a `.name`) down to the allowed subset. */
84
+ function filterAllowedTools(tools, policy) {
85
+ if (!Array.isArray(tools)) return [];
86
+ return tools.filter((t) => t && isToolAllowed(t.name, policy));
87
+ }
88
+
89
+ module.exports = {
90
+ MCP_PROFILES,
91
+ parseList,
92
+ buildToolPolicy,
93
+ isToolAllowed,
94
+ filterAllowedTools,
95
+ };