@vellumai/assistant 0.3.18 → 0.3.19

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.
Files changed (42) hide show
  1. package/ARCHITECTURE.md +4 -0
  2. package/docs/architecture/security.md +80 -0
  3. package/package.json +1 -1
  4. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +4 -0
  5. package/src/__tests__/call-controller.test.ts +170 -0
  6. package/src/__tests__/checker.test.ts +60 -0
  7. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +511 -0
  8. package/src/__tests__/guardian-dispatch.test.ts +61 -1
  9. package/src/__tests__/guardian-grant-minting.test.ts +543 -0
  10. package/src/__tests__/ipc-snapshot.test.ts +1 -0
  11. package/src/__tests__/remote-skill-policy.test.ts +215 -0
  12. package/src/__tests__/scoped-approval-grants.test.ts +521 -0
  13. package/src/__tests__/scoped-grant-security-matrix.test.ts +443 -0
  14. package/src/__tests__/trust-store.test.ts +2 -0
  15. package/src/__tests__/voice-scoped-grant-consumer.test.ts +571 -0
  16. package/src/calls/call-controller.ts +27 -6
  17. package/src/calls/call-domain.ts +12 -0
  18. package/src/calls/guardian-dispatch.ts +8 -0
  19. package/src/calls/relay-server.ts +13 -0
  20. package/src/calls/voice-session-bridge.ts +42 -3
  21. package/src/config/bundled-skills/notifications/SKILL.md +18 -0
  22. package/src/config/schema.ts +6 -0
  23. package/src/config/skills-schema.ts +27 -0
  24. package/src/daemon/handlers/config-channels.ts +18 -0
  25. package/src/daemon/handlers/skills.ts +45 -2
  26. package/src/daemon/ipc-contract/skills.ts +1 -0
  27. package/src/daemon/session-process.ts +12 -0
  28. package/src/memory/db-init.ts +9 -1
  29. package/src/memory/embedding-local.ts +16 -7
  30. package/src/memory/guardian-action-store.ts +8 -0
  31. package/src/memory/guardian-verification.ts +1 -1
  32. package/src/memory/migrations/033-scoped-approval-grants.ts +51 -0
  33. package/src/memory/migrations/034-guardian-action-tool-metadata.ts +12 -0
  34. package/src/memory/migrations/index.ts +2 -0
  35. package/src/memory/schema.ts +30 -0
  36. package/src/memory/scoped-approval-grants.ts +509 -0
  37. package/src/permissions/checker.ts +27 -0
  38. package/src/runtime/guardian-action-grant-minter.ts +97 -0
  39. package/src/runtime/routes/guardian-approval-interception.ts +116 -0
  40. package/src/runtime/routes/inbound-message-handler.ts +94 -27
  41. package/src/security/tool-approval-digest.ts +67 -0
  42. package/src/skills/remote-skill-policy.ts +131 -0
@@ -0,0 +1,215 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+
3
+ import {
4
+ evaluateRemoteSkillInstall,
5
+ filterInstallableRemoteSkills,
6
+ type RemoteSkillPolicy,
7
+ } from '../skills/remote-skill-policy.js';
8
+
9
+ describe('remote skill policy — clawhub', () => {
10
+ const policy: RemoteSkillPolicy = {
11
+ blockSuspicious: true,
12
+ blockMalware: true,
13
+ maxSkillsShRisk: 'medium',
14
+ };
15
+
16
+ test('suspicious skills are excluded from installable list', () => {
17
+ const candidates = [
18
+ {
19
+ provider: 'clawhub' as const,
20
+ slug: 'safe-skill',
21
+ moderation: { isSuspicious: false, isMalwareBlocked: false },
22
+ },
23
+ {
24
+ provider: 'clawhub' as const,
25
+ slug: 'suspicious-skill',
26
+ moderation: { isSuspicious: true, isMalwareBlocked: false },
27
+ },
28
+ ];
29
+
30
+ const installable = filterInstallableRemoteSkills(candidates, policy);
31
+ expect(installable.map((skill) => skill.slug)).toEqual(['safe-skill']);
32
+ });
33
+
34
+ test('suspicious skills are not installable when installation is attempted', () => {
35
+ const decision = evaluateRemoteSkillInstall(
36
+ {
37
+ provider: 'clawhub',
38
+ slug: 'suspicious-skill',
39
+ moderation: { isSuspicious: true, isMalwareBlocked: false },
40
+ },
41
+ policy,
42
+ );
43
+
44
+ expect(decision).toEqual({ ok: false, reason: 'clawhub_suspicious' });
45
+ });
46
+
47
+ test('malware-blocked skills are excluded from installable list and blocked on install', () => {
48
+ const candidates = [
49
+ {
50
+ provider: 'clawhub' as const,
51
+ slug: 'malware-skill',
52
+ moderation: { isSuspicious: false, isMalwareBlocked: true },
53
+ },
54
+ ];
55
+
56
+ expect(filterInstallableRemoteSkills(candidates, policy)).toEqual([]);
57
+
58
+ const decision = evaluateRemoteSkillInstall(candidates[0], policy);
59
+ expect(decision).toEqual({ ok: false, reason: 'clawhub_malware_blocked' });
60
+ });
61
+
62
+ test('clawhub skill with undefined moderation is blocked (fail-closed)', () => {
63
+ const decision = evaluateRemoteSkillInstall(
64
+ {
65
+ provider: 'clawhub',
66
+ slug: 'no-moderation-skill',
67
+ moderation: undefined,
68
+ },
69
+ policy,
70
+ );
71
+
72
+ expect(decision).toEqual({ ok: false, reason: 'clawhub_moderation_missing' });
73
+ });
74
+
75
+ test('clawhub skill with null moderation is blocked (fail-closed)', () => {
76
+ const decision = evaluateRemoteSkillInstall(
77
+ {
78
+ provider: 'clawhub',
79
+ slug: 'null-moderation-skill',
80
+ moderation: null,
81
+ },
82
+ policy,
83
+ );
84
+
85
+ expect(decision).toEqual({ ok: false, reason: 'clawhub_moderation_missing' });
86
+ });
87
+
88
+ test('clawhub skill without moderation property is blocked (fail-closed)', () => {
89
+ const decision = evaluateRemoteSkillInstall(
90
+ {
91
+ provider: 'clawhub',
92
+ slug: 'missing-moderation-skill',
93
+ },
94
+ policy,
95
+ );
96
+
97
+ expect(decision).toEqual({ ok: false, reason: 'clawhub_moderation_missing' });
98
+ });
99
+
100
+ test('clawhub skills with missing moderation are excluded from installable list', () => {
101
+ const candidates = [
102
+ {
103
+ provider: 'clawhub' as const,
104
+ slug: 'safe-skill',
105
+ moderation: { isSuspicious: false, isMalwareBlocked: false },
106
+ },
107
+ {
108
+ provider: 'clawhub' as const,
109
+ slug: 'no-moderation-skill',
110
+ },
111
+ ];
112
+
113
+ const installable = filterInstallableRemoteSkills(candidates, policy);
114
+ expect(installable.map((skill) => skill.slug)).toEqual(['safe-skill']);
115
+ });
116
+ });
117
+
118
+ describe('remote skill policy — skills.sh', () => {
119
+ const policy: RemoteSkillPolicy = {
120
+ blockSuspicious: true,
121
+ blockMalware: true,
122
+ maxSkillsShRisk: 'medium',
123
+ };
124
+
125
+ test('high-risk skills are excluded from installable list', () => {
126
+ const candidates = [
127
+ {
128
+ provider: 'skillssh' as const,
129
+ slug: 'safe-skill',
130
+ audit: { risk: 'low' as const },
131
+ },
132
+ {
133
+ provider: 'skillssh' as const,
134
+ slug: 'suspicious-skill',
135
+ audit: { risk: 'high' as const },
136
+ },
137
+ ];
138
+
139
+ const installable = filterInstallableRemoteSkills(candidates, policy);
140
+ expect(installable.map((skill) => skill.slug)).toEqual(['safe-skill']);
141
+ });
142
+
143
+ test('high-risk skills are not installable when installation is attempted', () => {
144
+ const decision = evaluateRemoteSkillInstall(
145
+ {
146
+ provider: 'skillssh',
147
+ slug: 'suspicious-skill',
148
+ audit: { risk: 'high' },
149
+ },
150
+ policy,
151
+ );
152
+
153
+ expect(decision).toEqual({ ok: false, reason: 'skillssh_risk_exceeds_threshold' });
154
+ });
155
+
156
+ test('unknown risk is treated as suspicious and blocked by default', () => {
157
+ const decision = evaluateRemoteSkillInstall(
158
+ {
159
+ provider: 'skillssh',
160
+ slug: 'unknown-risk-skill',
161
+ audit: { risk: 'unknown' },
162
+ },
163
+ policy,
164
+ );
165
+
166
+ expect(decision).toEqual({ ok: false, reason: 'skillssh_risk_exceeds_threshold' });
167
+ });
168
+
169
+ test('risk threshold is enforced even when blockSuspicious is false', () => {
170
+ const permissivePolicy: RemoteSkillPolicy = {
171
+ blockSuspicious: false,
172
+ blockMalware: false,
173
+ maxSkillsShRisk: 'medium',
174
+ };
175
+
176
+ const decision = evaluateRemoteSkillInstall(
177
+ {
178
+ provider: 'skillssh',
179
+ slug: 'high-risk-skill',
180
+ audit: { risk: 'high' },
181
+ },
182
+ permissivePolicy,
183
+ );
184
+
185
+ expect(decision).toEqual({ ok: false, reason: 'skillssh_risk_exceeds_threshold' });
186
+ });
187
+
188
+ test('prototype property risk label is treated as unknown and blocked', () => {
189
+ const decision = evaluateRemoteSkillInstall(
190
+ {
191
+ provider: 'skillssh',
192
+ slug: 'proto-risk-skill',
193
+ // "toString" exists on Object.prototype — must not be treated as a known risk label
194
+ audit: { risk: 'toString' as never },
195
+ },
196
+ policy,
197
+ );
198
+
199
+ expect(decision).toEqual({ ok: false, reason: 'skillssh_risk_exceeds_threshold' });
200
+ });
201
+
202
+ test('unrecognized risk string is coerced to unknown and blocked', () => {
203
+ const decision = evaluateRemoteSkillInstall(
204
+ {
205
+ provider: 'skillssh',
206
+ slug: 'bogus-risk-skill',
207
+ // Cast to bypass type checking — simulates a provider returning a novel risk label
208
+ audit: { risk: 'super-duper-risky' as never },
209
+ },
210
+ policy,
211
+ );
212
+
213
+ expect(decision).toEqual({ ok: false, reason: 'skillssh_risk_exceeds_threshold' });
214
+ });
215
+ });