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.
Files changed (2) hide show
  1. package/dist/cli.js +15 -21
  2. 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.7.4';
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
- // Preserve publisher's allowed-tools, trigger config, etc. but always include our load tool
537
- const publisherAllowedTools = frontmatterFields['allowed-tools'] || '';
538
- const loadTool = `"Bash(npx skillvault@${VERSION} --load *)"`;
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(`${API_URL}/v1/skills/${skillName}/cek`, {
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
- const wrapKey = Buffer.from(hkdfSync('sha256', shared, Buffer.alloc(32, 0), Buffer.from('skillvault-cek-wrap-v1'), 32));
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', 'skillvault-structural-v1');
693
- hmac.update(id);
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', 'skillvault-visible-v1');
738
- hmac.update(id);
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillvault",
3
- "version": "0.7.4",
3
+ "version": "0.8.0",
4
4
  "description": "SkillVault — secure skill distribution for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {