skillvault 0.7.4 → 0.8.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/dist/cli.js +15 -21
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, rmSync } from 'node:fs';
|
|
21
21
|
import { join } from 'node:path';
|
|
22
22
|
import { createDecipheriv, createHmac, createPublicKey, diffieHellman, hkdfSync, generateKeyPairSync, } from 'node:crypto';
|
|
23
|
-
const VERSION = '0.
|
|
23
|
+
const VERSION = '0.8.0';
|
|
24
24
|
const HOME = process.env.HOME || process.env.USERPROFILE || '~';
|
|
25
25
|
const API_URL = process.env.SKILLVAULT_API_URL || 'https://api.getskillvault.com';
|
|
26
26
|
const CONFIG_DIR = join(HOME, '.skillvault');
|
|
@@ -507,7 +507,7 @@ async function installSkillStubs() {
|
|
|
507
507
|
let frontmatter = '';
|
|
508
508
|
let frontmatterFields = {};
|
|
509
509
|
try {
|
|
510
|
-
const { cek } = await fetchCEK(skillName, pub.token);
|
|
510
|
+
const { cek } = await fetchCEK(skillName, pub.token, config.api_url || API_URL);
|
|
511
511
|
const vaultData = readFileSync(vaultPath);
|
|
512
512
|
const vault = decryptVault(vaultData, cek);
|
|
513
513
|
cek.fill(0);
|
|
@@ -533,17 +533,9 @@ async function installSkillStubs() {
|
|
|
533
533
|
// Build frontmatter for stub — copy all fields except body-related ones
|
|
534
534
|
let stubFrontmatter = `name: ${stubName}\n`;
|
|
535
535
|
stubFrontmatter += `description: "${stubDescription.replace(/"/g, '\\"')}"\n`;
|
|
536
|
-
//
|
|
537
|
-
const
|
|
538
|
-
|
|
539
|
-
if (publisherAllowedTools && !publisherAllowedTools.includes('skillvault')) {
|
|
540
|
-
// Merge publisher's allowed-tools with ours
|
|
541
|
-
const merged = publisherAllowedTools.replace(/\]$/, `, ${loadTool}]`);
|
|
542
|
-
stubFrontmatter += `allowed-tools: ${merged}\n`;
|
|
543
|
-
}
|
|
544
|
-
else {
|
|
545
|
-
stubFrontmatter += `allowed-tools: [${loadTool}]\n`;
|
|
546
|
-
}
|
|
536
|
+
// Only allow our specific load tool — do NOT merge publisher-specified allowed-tools
|
|
537
|
+
const loadTool = `"Bash(npx skillvault@${VERSION} --load ${skillName})"`;
|
|
538
|
+
stubFrontmatter += `allowed-tools: [${loadTool}]\n`;
|
|
547
539
|
// Copy through other frontmatter fields the publisher set (for Claude triggering)
|
|
548
540
|
for (const [key, value] of Object.entries(frontmatterFields)) {
|
|
549
541
|
if (!['name', 'description', 'allowed-tools'].includes(key)) {
|
|
@@ -633,10 +625,10 @@ function resolveSkillPublisher(skillName, config) {
|
|
|
633
625
|
}
|
|
634
626
|
return null;
|
|
635
627
|
}
|
|
636
|
-
async function fetchCEK(skillName, publisherToken) {
|
|
628
|
+
async function fetchCEK(skillName, publisherToken, apiUrl) {
|
|
637
629
|
const kp = generateKeyPairSync('x25519');
|
|
638
630
|
const pub = kp.publicKey.export({ type: 'spki', format: 'der' }).toString('base64');
|
|
639
|
-
const res = await fetch(`${
|
|
631
|
+
const res = await fetch(`${apiUrl}/v1/skills/${skillName}/cek`, {
|
|
640
632
|
method: 'POST',
|
|
641
633
|
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${publisherToken}` },
|
|
642
634
|
body: JSON.stringify({ companion_public_key: pub }),
|
|
@@ -648,7 +640,9 @@ async function fetchCEK(skillName, publisherToken) {
|
|
|
648
640
|
const serverWatermarkId = body.watermark_id || '';
|
|
649
641
|
const ephPub = createPublicKey({ key: Buffer.from(wc.ephemeralPublicKey, 'base64'), format: 'der', type: 'spki' });
|
|
650
642
|
const shared = diffieHellman({ publicKey: ephPub, privateKey: kp.privateKey });
|
|
651
|
-
|
|
643
|
+
// Use server-provided salt, or fall back to zero salt for backward compat with old servers
|
|
644
|
+
const hkdfSalt = wc.salt ? Buffer.from(wc.salt, 'base64') : Buffer.alloc(32, 0);
|
|
645
|
+
const wrapKey = Buffer.from(hkdfSync('sha256', shared, hkdfSalt, Buffer.from('skillvault-cek-wrap-v1'), 32));
|
|
652
646
|
shared.fill(0);
|
|
653
647
|
const d = createDecipheriv('aes-256-gcm', wrapKey, Buffer.from(wc.iv, 'base64'), { authTagLength: 16 });
|
|
654
648
|
d.setAuthTag(Buffer.from(wc.authTag, 'base64'));
|
|
@@ -689,8 +683,8 @@ function watermarkLayer2(content, id) {
|
|
|
689
683
|
}
|
|
690
684
|
/** Layer 3: Structural fingerprint — HMAC comment tag in code blocks */
|
|
691
685
|
function watermarkLayer3(content, id) {
|
|
692
|
-
const hmac = createHmac('sha256',
|
|
693
|
-
hmac.update(
|
|
686
|
+
const hmac = createHmac('sha256', id);
|
|
687
|
+
hmac.update('skillvault-structural-v1');
|
|
694
688
|
const tag = `// sv:${hmac.digest('hex').slice(0, 12)}`;
|
|
695
689
|
const lines = content.split('\n');
|
|
696
690
|
const result = [];
|
|
@@ -734,8 +728,8 @@ function watermarkLayer4(content, id, email, publisherName) {
|
|
|
734
728
|
const lines = content.split('\n');
|
|
735
729
|
// Determine pseudo-random insertion points based on HMAC of licensee ID
|
|
736
730
|
// This ensures the same licensee always gets the same positions (deterministic)
|
|
737
|
-
const hmac = createHmac('sha256',
|
|
738
|
-
hmac.update(
|
|
731
|
+
const hmac = createHmac('sha256', id);
|
|
732
|
+
hmac.update('watermark-positions');
|
|
739
733
|
const hash = hmac.digest();
|
|
740
734
|
// Insert at: beginning, end, and 2-4 random points in between
|
|
741
735
|
const totalLines = lines.length;
|
|
@@ -904,7 +898,7 @@ async function loadSkill(skillName) {
|
|
|
904
898
|
let cek;
|
|
905
899
|
let licenseeId;
|
|
906
900
|
try {
|
|
907
|
-
const cekResult = await fetchCEK(skillName, resolved.publisher.token);
|
|
901
|
+
const cekResult = await fetchCEK(skillName, resolved.publisher.token, config.api_url || API_URL);
|
|
908
902
|
cek = cekResult.cek;
|
|
909
903
|
// Use server-provided watermark ID (includes grant ID, customer ID, timestamp)
|
|
910
904
|
// Falls back to local composite if server didn't provide one
|