@skillsmith/cli 0.8.7 → 0.8.8
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/CHANGELOG.md +40 -0
- package/README.md +3 -3
- package/dist/.skillsmith-dist-hash +1 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/cli.js +1384 -376
- package/package.json +5 -5
package/dist/cli.js
CHANGED
|
@@ -1767,7 +1767,7 @@ async function removeLinks(skillId) {
|
|
|
1767
1767
|
}
|
|
1768
1768
|
|
|
1769
1769
|
// ../core/dist/src/install/agent-pack-installer.js
|
|
1770
|
-
import { existsSync as
|
|
1770
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
1771
1771
|
import { homedir as homedir6 } from "node:os";
|
|
1772
1772
|
import { join as join16 } from "node:path";
|
|
1773
1773
|
|
|
@@ -2551,7 +2551,7 @@ var PAYWALL_TRIGGERS = [
|
|
|
2551
2551
|
{
|
|
2552
2552
|
id: "T2",
|
|
2553
2553
|
title: "T2 - quota forecast (to Individual)",
|
|
2554
|
-
body: `When usage is on track to exhaust the free
|
|
2554
|
+
body: `When usage is on track to exhaust the free 100-call monthly quota, you may note the forecast ("at this pace you reach the cap in about K days") and mention Individual's 1,000 calls. Use this sparingly; a quota nag reads as a tax.`
|
|
2555
2555
|
},
|
|
2556
2556
|
{
|
|
2557
2557
|
id: "T4",
|
|
@@ -3382,9 +3382,12 @@ function mergeJsonArrayEntry(opts) {
|
|
|
3382
3382
|
let missingDefaults = false;
|
|
3383
3383
|
if (ensureTopLevelDefaults) {
|
|
3384
3384
|
for (const [key, value] of Object.entries(ensureTopLevelDefaults)) {
|
|
3385
|
-
|
|
3385
|
+
const existingValue = doc[key];
|
|
3386
|
+
if (existingValue === void 0) {
|
|
3386
3387
|
doc[key] = value;
|
|
3387
3388
|
missingDefaults = true;
|
|
3389
|
+
} else if (!deepEqualJson(existingValue, value)) {
|
|
3390
|
+
return { status: "conflict", path: path27, backupPath: null };
|
|
3388
3391
|
}
|
|
3389
3392
|
}
|
|
3390
3393
|
}
|
|
@@ -3695,6 +3698,7 @@ function installMcpConfig(harness, ctx, report) {
|
|
|
3695
3698
|
}
|
|
3696
3699
|
|
|
3697
3700
|
// ../core/dist/src/install/agent-pack-installer.cursor-hooks.js
|
|
3701
|
+
import { existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync9 } from "node:fs";
|
|
3698
3702
|
import { join as join15 } from "node:path";
|
|
3699
3703
|
function mergeSucceeded2(status) {
|
|
3700
3704
|
return status === "created" || status === "updated" || status === "unchanged";
|
|
@@ -3753,16 +3757,81 @@ function installCursorHooks(startArtifact, endArtifact, ctx, report) {
|
|
|
3753
3757
|
ensureTopLevelDefaults: CURSOR_HOOKS_JSON_DEFAULTS
|
|
3754
3758
|
});
|
|
3755
3759
|
report.hookConfig.push(startWire, endWire);
|
|
3756
|
-
|
|
3760
|
+
const wireConflict = startWire.status === "conflict" || endWire.status === "conflict";
|
|
3761
|
+
const legacyCleanup = wireConflict ? null : stripStaleLegacyHookKeys(configPath2, startPath, endPath, ctx);
|
|
3762
|
+
if (legacyCleanup)
|
|
3763
|
+
report.hookConfig.push(legacyCleanup);
|
|
3764
|
+
if (mergeSucceeded2(startWire.status) || mergeSucceeded2(endWire.status) || legacyCleanup !== null && mergeSucceeded2(legacyCleanup.status)) {
|
|
3757
3765
|
ctx.entries.push({
|
|
3758
3766
|
path: configPath2,
|
|
3759
3767
|
kind: "hook-config",
|
|
3760
3768
|
harness: "cursor",
|
|
3761
|
-
backupPath: startWire.backupPath ?? endWire.backupPath,
|
|
3769
|
+
backupPath: startWire.backupPath ?? endWire.backupPath ?? legacyCleanup?.backupPath ?? null,
|
|
3762
3770
|
executable: false
|
|
3763
3771
|
});
|
|
3764
3772
|
}
|
|
3765
3773
|
}
|
|
3774
|
+
function stripStaleLegacyHookKeys(configPath2, startPath, endPath, ctx) {
|
|
3775
|
+
const unchanged = () => ({ status: "unchanged", path: configPath2, backupPath: null });
|
|
3776
|
+
if (!existsSync9(configPath2))
|
|
3777
|
+
return unchanged();
|
|
3778
|
+
let doc;
|
|
3779
|
+
try {
|
|
3780
|
+
const parsed = JSON.parse(readFileSync8(configPath2, "utf-8"));
|
|
3781
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
3782
|
+
return {
|
|
3783
|
+
status: "error",
|
|
3784
|
+
path: configPath2,
|
|
3785
|
+
backupPath: null,
|
|
3786
|
+
errorMessage: "not a JSON object"
|
|
3787
|
+
};
|
|
3788
|
+
}
|
|
3789
|
+
doc = parsed;
|
|
3790
|
+
} catch (e) {
|
|
3791
|
+
return {
|
|
3792
|
+
status: "error",
|
|
3793
|
+
path: configPath2,
|
|
3794
|
+
backupPath: null,
|
|
3795
|
+
errorMessage: e.message
|
|
3796
|
+
};
|
|
3797
|
+
}
|
|
3798
|
+
const hooks = doc.hooks;
|
|
3799
|
+
if (!hooks || typeof hooks !== "object" || Array.isArray(hooks))
|
|
3800
|
+
return unchanged();
|
|
3801
|
+
const hooksObj = hooks;
|
|
3802
|
+
const claudeTarget = AGENT_HOOK_TARGETS["claude-code"];
|
|
3803
|
+
const legacyStartKey = claudeTarget.sessionStartKeyPath.at(-1);
|
|
3804
|
+
const legacyEndKey = claudeTarget.sessionEndKeyPath.at(-1);
|
|
3805
|
+
let changed = false;
|
|
3806
|
+
for (const [legacyKey, ownPath] of [
|
|
3807
|
+
[legacyStartKey, startPath],
|
|
3808
|
+
[legacyEndKey, endPath]
|
|
3809
|
+
]) {
|
|
3810
|
+
if (legacyKey === void 0)
|
|
3811
|
+
continue;
|
|
3812
|
+
const legacyValue = hooksObj[legacyKey];
|
|
3813
|
+
if (legacyValue === void 0)
|
|
3814
|
+
continue;
|
|
3815
|
+
if (!Array.isArray(legacyValue))
|
|
3816
|
+
continue;
|
|
3817
|
+
const remaining = legacyValue.filter((item) => hookEntryCommand(item) !== ownPath);
|
|
3818
|
+
const needsCleanup = legacyValue.length === 0 || remaining.length !== legacyValue.length;
|
|
3819
|
+
if (!needsCleanup)
|
|
3820
|
+
continue;
|
|
3821
|
+
changed = true;
|
|
3822
|
+
if (remaining.length === 0) {
|
|
3823
|
+
delete hooksObj[legacyKey];
|
|
3824
|
+
} else {
|
|
3825
|
+
hooksObj[legacyKey] = remaining;
|
|
3826
|
+
}
|
|
3827
|
+
}
|
|
3828
|
+
if (!changed)
|
|
3829
|
+
return unchanged();
|
|
3830
|
+
const backupPath = shouldBackup(configPath2, ctx.backedUpPaths) ? writeBackup5(configPath2, ctx.backupDir) : null;
|
|
3831
|
+
markBackedUp(configPath2, ctx.backedUpPaths);
|
|
3832
|
+
writeFileSync9(configPath2, JSON.stringify(doc, null, 2) + "\n", { mode: 384 });
|
|
3833
|
+
return { status: "updated", path: configPath2, backupPath };
|
|
3834
|
+
}
|
|
3766
3835
|
function cursorHookEntry(scriptPath) {
|
|
3767
3836
|
return { command: scriptPath };
|
|
3768
3837
|
}
|
|
@@ -3787,10 +3856,10 @@ var HARNESS_SUPPORT_TIER = {
|
|
|
3787
3856
|
// ../core/dist/src/install/agent-pack-installer.js
|
|
3788
3857
|
var OPTIONAL_SKILL_PACK_HARNESSES = ["windsurf", "opencode", "hermes"];
|
|
3789
3858
|
function isPresent(nativePath, homeDir) {
|
|
3790
|
-
return
|
|
3859
|
+
return existsSync10(relocateUnderHome(nativePath, homeDir));
|
|
3791
3860
|
}
|
|
3792
3861
|
function isCodexPresent(homeDir) {
|
|
3793
|
-
return
|
|
3862
|
+
return existsSync10(relocateUnderHome(join16(homedir6(), ".codex"), homeDir));
|
|
3794
3863
|
}
|
|
3795
3864
|
function writeSkillPackFor(clientNativePath, content, ctx, harness) {
|
|
3796
3865
|
const path27 = join16(relocateUnderHome(clientNativePath, ctx.homeDir), AGENT_PACK_SKILL_NAME, "SKILL.md");
|
|
@@ -3824,7 +3893,7 @@ function newReport(harness) {
|
|
|
3824
3893
|
function carryForwardPriorBackups(entries) {
|
|
3825
3894
|
const priorBackupByPath = /* @__PURE__ */ new Map();
|
|
3826
3895
|
for (const prior of loadAgentManifest().entries) {
|
|
3827
|
-
if (prior.backupPath &&
|
|
3896
|
+
if (prior.backupPath && existsSync10(prior.backupPath)) {
|
|
3828
3897
|
priorBackupByPath.set(prior.path, prior.backupPath);
|
|
3829
3898
|
}
|
|
3830
3899
|
}
|
|
@@ -3921,7 +3990,7 @@ function installAgentPack(opts = {}) {
|
|
|
3921
3990
|
|
|
3922
3991
|
// ../core/dist/src/install/agent-pack-uninstaller.js
|
|
3923
3992
|
import { dirname as dirname9 } from "node:path";
|
|
3924
|
-
import { existsSync as
|
|
3993
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9, rmdirSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync10 } from "node:fs";
|
|
3925
3994
|
|
|
3926
3995
|
// ../core/dist/src/install/agent-manifest-path-guard.js
|
|
3927
3996
|
import { homedir as homedir7 } from "node:os";
|
|
@@ -3989,14 +4058,14 @@ function uninstallAgentPack(_opts = {}) {
|
|
|
3989
4058
|
rejected.push(entry.path);
|
|
3990
4059
|
continue;
|
|
3991
4060
|
}
|
|
3992
|
-
if (!
|
|
4061
|
+
if (!existsSync11(entry.path)) {
|
|
3993
4062
|
alreadyGone.push(entry.path);
|
|
3994
4063
|
continue;
|
|
3995
4064
|
}
|
|
3996
4065
|
touchedDirs.add(dirname9(entry.path));
|
|
3997
|
-
if (entry.backupPath &&
|
|
3998
|
-
const content =
|
|
3999
|
-
|
|
4066
|
+
if (entry.backupPath && existsSync11(entry.backupPath)) {
|
|
4067
|
+
const content = readFileSync9(entry.backupPath, "utf-8");
|
|
4068
|
+
writeFileSync10(entry.path, content, "utf-8");
|
|
4000
4069
|
restored.push(entry.path);
|
|
4001
4070
|
} else {
|
|
4002
4071
|
unlinkSync2(entry.path);
|
|
@@ -20476,6 +20545,36 @@ function assertEvidenceCoverage() {
|
|
|
20476
20545
|
}
|
|
20477
20546
|
assertEvidenceCoverage();
|
|
20478
20547
|
|
|
20548
|
+
// ../core/dist/src/security/scanner/patterns.exec.js
|
|
20549
|
+
var CODE_EXECUTION_PATTERNS = [
|
|
20550
|
+
// curl|wget <target> | [sudo] <interpreter> (fetch piped to a shell or scripting interpreter)
|
|
20551
|
+
/(?:curl|wget)\b[^\n|]{0,150}?(?:https?:\/\/|\d{1,3}(?:\.\d{1,3}){3}|[\w-]{2,63}\.[a-z]{2,24})[^\n|]{0,150}?\|\s*(?:sudo\s+(?:-[A-Za-z]+\s+)?)?(?:(?:ba|z|da)?sh|python[23]?|node|ruby|perl|php|fish|bun|deno)\b/i,
|
|
20552
|
+
// process substitution: bash/sh/zsh/source/. <(curl|wget <target> ...)
|
|
20553
|
+
/(?:^|[\s;&])(?:source|\.|ba?sh|zsh|exec)\s+<\(\s*(?:curl|wget)\b[^\n)]{0,150}?(?:https?:\/\/|\d{1,3}(?:\.\d{1,3}){3}|[\w-]{2,63}\.[a-z]{2,24})/i,
|
|
20554
|
+
// command substitution into eval or `sh -c`: eval "$(curl <target>...)", bash -c "`wget <target>...`"
|
|
20555
|
+
/(?:\beval\b|(?:ba|z)?sh\s+-c)\s+["']?[$`]\(?\s*(?:curl|wget)\b[^\n)]{0,150}?(?:https?:\/\/|\d{1,3}(?:\.\d{1,3}){3}|[\w-]{2,63}\.[a-z]{2,24})/i,
|
|
20556
|
+
// PowerShell download-and-execute: iex(irm ...), Invoke-Expression(... DownloadString/Invoke-WebRequest)
|
|
20557
|
+
/\b(?:iex|invoke-expression)\b[^\n]{0,100}?(?:\birm\b|\biwr\b|invoke-webrequest|invoke-restmethod|downloadstring|net\.webclient)/i,
|
|
20558
|
+
// PowerShell encoded command (base64 payload handed to the interpreter)
|
|
20559
|
+
/\bpowershell\b[^\n]{0,60}?\s-e(?:nc|ncodedcommand)?\b\s*[A-Za-z0-9+/=]{16,}/i,
|
|
20560
|
+
// decode-then-exec: ... base64 -d ... | <interpreter> (SMI-5359 retro NIT: da sink, matches the curl pattern)
|
|
20561
|
+
/\bbase64\s+(?:-d|--decode|-D)\b[^\n|]{0,60}?\|\s*(?:(?:ba|z|da)?sh|python[23]?|node|ruby|perl|php|fish|bun|deno)\b/i,
|
|
20562
|
+
// SMI-5424 FN-1: chained / redirect download-then-execute (curl URL -o /tmp/x && bash /tmp/x)
|
|
20563
|
+
/(?:curl|wget)\b[^\n]{0,150}?(?:https?:\/\/|\d{1,3}(?:\.\d{1,3}){3}|[\w-]{2,63}\.[a-z]{2,24})[^\n]{0,150}?(?:&&|;)\s*(?:sudo\s+(?:-[A-Za-z]+\s+)?)?(?:(?:ba|z|da)?sh|python[23]?|node|ruby|perl|php|fish|bun|deno)\b/i,
|
|
20564
|
+
// SMI-5424 FN-2: npx executing a REMOTE source (URL or github:), never a local package (npx tsc is clean)
|
|
20565
|
+
/\bnpx\s+(?:--yes\s+|-y\s+)?(?:https?:\/\/\S+|github:\S+)/i,
|
|
20566
|
+
// SMI-5424 FN-4: node/python/deno/bun inline-eval (-e/-c) with a dangerous payload
|
|
20567
|
+
/\b(?:node|python[23]?|deno|bun)\s+(?:-e|-c|--eval|--exec)\s+['"][^'"]{0,200}?(?:require\(|child_process|fetch\(|\bexec\b|eval\(|base64|urllib|os\.system|subprocess)/i
|
|
20568
|
+
];
|
|
20569
|
+
var IMPERATIVE_FETCH_EXEC_PROSE = [
|
|
20570
|
+
// P1: <fetch verb> … <exec noun> … <remote target> … <execution imperative>
|
|
20571
|
+
// "Download the installer from thisurl.com and run it"
|
|
20572
|
+
/\b(?:download|fetch|grab|get)\b[\s\S]{0,40}?\b(?:file|binary|executable|script|installer|setup)\b[\s\S]{0,60}?(?:https?:\/\/[^\s"'<>)\]]{1,200}|\b(?:from|at|on|via)\s+(?:the\s+)?[\w-]{2,63}(?:\.[\w-]{2,63}){0,3}\.(?!(?:sh|bash|zsh|exe|py|js|mjs|cjs|ts|md|txt|zip|tar|gz|tgz|json|ya?ml|toml|bin|dmg|pkg|msi|deb|rpm|jar|php|rb|pl|ps1|bat|cmd|app)\b)[a-z]{2,24}\b)[\s\S]{0,120}?\b(?:run|execute|open|install)\s+(?:it|this|that|them|(?:the|this|that|your)\s+(?:file|binary|executable|script|installer|setup))\b/i,
|
|
20573
|
+
// P2: <execution imperative + exec noun> … <fetch verb> … <remote target>
|
|
20574
|
+
// "Run the installer you downloaded from thisurl.com"
|
|
20575
|
+
/\b(?:run|execute|open|install)\s+(?:the|this|that|your)\s+(?:file|binary|executable|script|installer|setup)\b[\s\S]{0,80}?\b(?:download|fetch|grab|get)(?:ed|s|ing)?\b[\s\S]{0,60}?(?:https?:\/\/[^\s"'<>)\]]{1,200}|\b(?:from|at|on|via)\s+(?:the\s+)?[\w-]{2,63}(?:\.[\w-]{2,63}){0,3}\.(?!(?:sh|bash|zsh|exe|py|js|mjs|cjs|ts|md|txt|zip|tar|gz|tgz|json|ya?ml|toml|bin|dmg|pkg|msi|deb|rpm|jar|php|rb|pl|ps1|bat|cmd|app)\b)[a-z]{2,24}\b)/i
|
|
20576
|
+
];
|
|
20577
|
+
|
|
20479
20578
|
// ../core/dist/src/security/scanner/patterns.js
|
|
20480
20579
|
var DEFAULT_ALLOWED_DOMAINS = [
|
|
20481
20580
|
"github.com",
|
|
@@ -20491,6 +20590,24 @@ var DEFAULT_ALLOWED_DOMAINS = [
|
|
|
20491
20590
|
"nodejs.org",
|
|
20492
20591
|
"typescriptlang.org"
|
|
20493
20592
|
];
|
|
20593
|
+
var ANON_PASTE_HOSTS = [
|
|
20594
|
+
"glot.io",
|
|
20595
|
+
"pastebin.com",
|
|
20596
|
+
"paste.ee",
|
|
20597
|
+
"hastebin.com",
|
|
20598
|
+
"ix.io",
|
|
20599
|
+
"0x0.st",
|
|
20600
|
+
"dpaste.org",
|
|
20601
|
+
"dpaste.com",
|
|
20602
|
+
"ghostbin.com",
|
|
20603
|
+
"paste.rs",
|
|
20604
|
+
"controlc.com",
|
|
20605
|
+
"rentry.co",
|
|
20606
|
+
"paste.gg",
|
|
20607
|
+
"justpaste.it"
|
|
20608
|
+
];
|
|
20609
|
+
var TRANSIENT_TRANSFER_HOSTS = ["transfer.sh", "file.io", "tmpfiles.org", "temp.sh"];
|
|
20610
|
+
var URL_SHORTENER_DOMAINS = ["bit.ly", "tinyurl.com", "t.co", "is.gd"];
|
|
20494
20611
|
var ENV_PATH_PATTERN = /\.env(?![A-Za-z])(?!\.(?:example|sample|template|schema|dist))/i;
|
|
20495
20612
|
var API_KEY_KEYWORD = /api[_-]?key/i;
|
|
20496
20613
|
var AUTH_TOKEN_KEYWORD = /auth[_-]?token/i;
|
|
@@ -20521,7 +20638,7 @@ var VALUE_GATED_KEYWORD_PATTERNS = /* @__PURE__ */ new Set([
|
|
|
20521
20638
|
API_KEY_KEYWORD,
|
|
20522
20639
|
AUTH_TOKEN_KEYWORD
|
|
20523
20640
|
]);
|
|
20524
|
-
var SCANNER_RULESET_VERSION = "2026-
|
|
20641
|
+
var SCANNER_RULESET_VERSION = "2026-08-15.1";
|
|
20525
20642
|
var SUSPICIOUS_PATTERNS = [
|
|
20526
20643
|
/eval\s*\(/i,
|
|
20527
20644
|
/exec\s*\(/i,
|
|
@@ -20537,26 +20654,6 @@ var SUSPICIOUS_PATTERNS = [
|
|
|
20537
20654
|
// Curl pipe to shell
|
|
20538
20655
|
/wget\s+.*\|\s*(bash|sh)/i
|
|
20539
20656
|
];
|
|
20540
|
-
var CODE_EXECUTION_PATTERNS = [
|
|
20541
|
-
// curl|wget <target> | [sudo] <interpreter> (fetch piped to a shell or scripting interpreter)
|
|
20542
|
-
/(?:curl|wget)\b[^\n|]{0,150}?(?:https?:\/\/|\d{1,3}(?:\.\d{1,3}){3}|[\w-]{2,63}\.[a-z]{2,24})[^\n|]{0,150}?\|\s*(?:sudo\s+(?:-[A-Za-z]+\s+)?)?(?:(?:ba|z|da)?sh|python[23]?|node|ruby|perl|php|fish|bun|deno)\b/i,
|
|
20543
|
-
// process substitution: bash/sh/zsh/source/. <(curl|wget <target> ...)
|
|
20544
|
-
/(?:^|[\s;&])(?:source|\.|ba?sh|zsh|exec)\s+<\(\s*(?:curl|wget)\b[^\n)]{0,150}?(?:https?:\/\/|\d{1,3}(?:\.\d{1,3}){3}|[\w-]{2,63}\.[a-z]{2,24})/i,
|
|
20545
|
-
// command substitution into eval or `sh -c`: eval "$(curl <target>...)", bash -c "`wget <target>...`"
|
|
20546
|
-
/(?:\beval\b|(?:ba|z)?sh\s+-c)\s+["']?[$`]\(?\s*(?:curl|wget)\b[^\n)]{0,150}?(?:https?:\/\/|\d{1,3}(?:\.\d{1,3}){3}|[\w-]{2,63}\.[a-z]{2,24})/i,
|
|
20547
|
-
// PowerShell download-and-execute: iex(irm ...), Invoke-Expression(... DownloadString/Invoke-WebRequest)
|
|
20548
|
-
/\b(?:iex|invoke-expression)\b[^\n]{0,100}?(?:\birm\b|\biwr\b|invoke-webrequest|invoke-restmethod|downloadstring|net\.webclient)/i,
|
|
20549
|
-
// PowerShell encoded command (base64 payload handed to the interpreter)
|
|
20550
|
-
/\bpowershell\b[^\n]{0,60}?\s-e(?:nc|ncodedcommand)?\b\s*[A-Za-z0-9+/=]{16,}/i,
|
|
20551
|
-
// decode-then-exec: ... base64 -d ... | <interpreter> (SMI-5359 retro NIT: da sink, matches the curl pattern)
|
|
20552
|
-
/\bbase64\s+(?:-d|--decode|-D)\b[^\n|]{0,60}?\|\s*(?:(?:ba|z|da)?sh|python[23]?|node|ruby|perl|php|fish|bun|deno)\b/i,
|
|
20553
|
-
// SMI-5424 FN-1: chained / redirect download-then-execute (curl URL -o /tmp/x && bash /tmp/x)
|
|
20554
|
-
/(?:curl|wget)\b[^\n]{0,150}?(?:https?:\/\/|\d{1,3}(?:\.\d{1,3}){3}|[\w-]{2,63}\.[a-z]{2,24})[^\n]{0,150}?(?:&&|;)\s*(?:sudo\s+(?:-[A-Za-z]+\s+)?)?(?:(?:ba|z|da)?sh|python[23]?|node|ruby|perl|php|fish|bun|deno)\b/i,
|
|
20555
|
-
// SMI-5424 FN-2: npx executing a REMOTE source (URL or github:), never a local package (npx tsc is clean)
|
|
20556
|
-
/\bnpx\s+(?:--yes\s+|-y\s+)?(?:https?:\/\/\S+|github:\S+)/i,
|
|
20557
|
-
// SMI-5424 FN-4: node/python/deno/bun inline-eval (-e/-c) with a dangerous payload
|
|
20558
|
-
/\b(?:node|python[23]?|deno|bun)\s+(?:-e|-c|--eval|--exec)\s+['"][^'"]{0,200}?(?:require\(|child_process|fetch\(|\bexec\b|eval\(|base64|urllib|os\.system|subprocess)/i
|
|
20559
|
-
];
|
|
20560
20657
|
var SOCIAL_ENGINEERING_PATTERNS = [
|
|
20561
20658
|
/pretend\s+(to\s+be|you\s+are|that\s+you)/i,
|
|
20562
20659
|
/roleplay\s+as/i,
|
|
@@ -20823,7 +20920,47 @@ var CATEGORY_WEIGHTS = {
|
|
|
20823
20920
|
// ~1) to the total; a saturated breakdown (capped at 100) contributes 4 —
|
|
20824
20921
|
// comfortably under the riskThreshold: 40 quarantine cutoff on its own. See the
|
|
20825
20922
|
// stacked-risk test in SecurityScanner.scoring.test.ts.
|
|
20826
|
-
typosquat: 1.2
|
|
20923
|
+
typosquat: 1.2,
|
|
20924
|
+
// SMI-6033 Wave 2 (Gap 5/Gap 3): top-tier weight matching code_execution/
|
|
20925
|
+
// obfuscated_directive — the SAME single weight is used for both the
|
|
20926
|
+
// critical (standalone-quarantining) and medium (advisory, co-signal-
|
|
20927
|
+
// eligible) forms of each type; severity alone (SEVERITY_WEIGHTS 50 vs 15)
|
|
20928
|
+
// does the two-tier split, exactly as scanChmodFetchCompound's own
|
|
20929
|
+
// privilege_escalation compound signal already does with ONE weight
|
|
20930
|
+
// (1.9/0.11) across its high/low severities. Paired with the 0.40
|
|
20931
|
+
// coefficient in calculateRiskScore: a single CRITICAL finding reaches
|
|
20932
|
+
// exactly the 40 quarantine threshold on its own (50 * 2.0 * 1.0 = 100 ->
|
|
20933
|
+
// capped 100 -> * 0.40 = 40); a single MEDIUM finding contributes 12
|
|
20934
|
+
// (15 * 2.0 * 1.0 = 30 -> * 0.40 = 12) — well under threshold alone.
|
|
20935
|
+
gatekeeper_bypass: 2,
|
|
20936
|
+
archive_evasion: 2,
|
|
20937
|
+
// SMI-6033 Wave 2 (Gap 4): same top-tier weight as gatekeeper_bypass/
|
|
20938
|
+
// archive_evasion, for the same reason — a paste-host URL that is the
|
|
20939
|
+
// TARGET of a fetch command reaches exactly the 40 quarantine threshold
|
|
20940
|
+
// standalone (50 * 2.0 * 1.0 = 100 -> capped 100 -> * 0.40 = 40), while a
|
|
20941
|
+
// paste-host URL that is merely linked never reaches this detector at all
|
|
20942
|
+
// (see SecurityScanner.paste-host.ts) and stays covered by the existing
|
|
20943
|
+
// url:medium finding instead.
|
|
20944
|
+
paste_host_fetch: 2,
|
|
20945
|
+
// SMI-6033 Wave 2 (Gap 2): the sensitive_path/typosquat tier (1.2/0.04 —
|
|
20946
|
+
// NOT the 2.0/0.40 tier every other Wave 2 detector above uses). This
|
|
20947
|
+
// detector's wrapper finding is deliberately advisory-only: the escalation
|
|
20948
|
+
// Gap 2 achieves comes for free from the decoded content's OWN findings
|
|
20949
|
+
// (e.g. a decoded `curl|bash` natively trips code_execution at ITS OWN
|
|
20950
|
+
// top-tier weight), not from encoded_payload itself. A single medium,
|
|
20951
|
+
// high-confidence finding scores 15 * 1.2 * 1.0 = 18 -> * 0.04 = 0.72
|
|
20952
|
+
// (rounds to ~1); a saturated breakdown (capped at 100) contributes 4 —
|
|
20953
|
+
// comfortably under the riskThreshold: 40 quarantine cutoff on its own.
|
|
20954
|
+
encoded_payload: 1.2,
|
|
20955
|
+
// SMI-6033 Wave 4 (Gap 6): same advisory tier as typosquat/encoded_payload/
|
|
20956
|
+
// sensitive_path (1.2/0.04) — NOT the 2.0/0.40 top tier gatekeeper_bypass/
|
|
20957
|
+
// archive_evasion/paste_host_fetch use, since this finding type must never
|
|
20958
|
+
// be standalone-critical (plan §9: "N/A — never standalone... Approximate
|
|
20959
|
+
// NL heuristic by construction; co-signal required"). A single medium,
|
|
20960
|
+
// high-confidence finding scores 15 * 1.2 * 1.0 = 18 -> * 0.04 = 0.72
|
|
20961
|
+
// (rounds to ~1); a saturated breakdown (capped at 100) contributes 4 —
|
|
20962
|
+
// comfortably under the riskThreshold: 40 quarantine cutoff on its own.
|
|
20963
|
+
decoy_misdirection: 1.2
|
|
20827
20964
|
};
|
|
20828
20965
|
|
|
20829
20966
|
// ../core/dist/src/security/scanner/regex-utils.js
|
|
@@ -21076,7 +21213,7 @@ function assertScopeCoverage() {
|
|
|
21076
21213
|
assertScopeCoverage();
|
|
21077
21214
|
|
|
21078
21215
|
// ../core/dist/src/security/scanner/SecurityScanner.helpers.js
|
|
21079
|
-
function analyzeMarkdownContext(content) {
|
|
21216
|
+
function analyzeMarkdownContext(content, isMarkdown = true) {
|
|
21080
21217
|
const lines = content.split("\n");
|
|
21081
21218
|
const contexts = [];
|
|
21082
21219
|
let inFencedCodeBlock = false;
|
|
@@ -21104,7 +21241,7 @@ function analyzeMarkdownContext(content) {
|
|
|
21104
21241
|
inFencedCodeBlock = !inFencedCodeBlock;
|
|
21105
21242
|
}
|
|
21106
21243
|
const inTable = !lineInFrontmatter && trimmedLine.startsWith("|");
|
|
21107
|
-
const isIndentedCode = !lineInFrontmatter && /^( {4,}|\t)/.test(line) && !inFencedCodeBlock && !trimmedLine.startsWith("-") && !trimmedLine.startsWith("*");
|
|
21244
|
+
const isIndentedCode = isMarkdown && !lineInFrontmatter && /^( {4,}|\t)/.test(line) && !inFencedCodeBlock && !trimmedLine.startsWith("-") && !trimmedLine.startsWith("*");
|
|
21108
21245
|
const isInlineCode = !lineInFrontmatter && /`[^`]+`/.test(line) && !inFencedCodeBlock;
|
|
21109
21246
|
contexts.push({
|
|
21110
21247
|
lineNumber: i + 1,
|
|
@@ -21216,95 +21353,6 @@ function scanPatternsWithMultilineSupport(content, config2, lineContexts, maxLen
|
|
|
21216
21353
|
}
|
|
21217
21354
|
return findings;
|
|
21218
21355
|
}
|
|
21219
|
-
function calculateRiskScore(findings) {
|
|
21220
|
-
const breakdown = {
|
|
21221
|
-
jailbreak: 0,
|
|
21222
|
-
socialEngineering: 0,
|
|
21223
|
-
promptLeaking: 0,
|
|
21224
|
-
dataExfiltration: 0,
|
|
21225
|
-
privilegeEscalation: 0,
|
|
21226
|
-
suspiciousCode: 0,
|
|
21227
|
-
sensitivePaths: 0,
|
|
21228
|
-
externalUrls: 0,
|
|
21229
|
-
aiDefence: 0,
|
|
21230
|
-
ssrf: 0,
|
|
21231
|
-
pii: 0,
|
|
21232
|
-
codeExecution: 0,
|
|
21233
|
-
obfuscatedDirective: 0,
|
|
21234
|
-
typosquat: 0
|
|
21235
|
-
};
|
|
21236
|
-
const confidenceWeights = {
|
|
21237
|
-
high: 1,
|
|
21238
|
-
medium: 0.7,
|
|
21239
|
-
low: 0.3
|
|
21240
|
-
};
|
|
21241
|
-
for (const finding of findings) {
|
|
21242
|
-
const severityWeight = SEVERITY_WEIGHTS[finding.severity];
|
|
21243
|
-
const categoryWeight = CATEGORY_WEIGHTS[finding.type] ?? 1;
|
|
21244
|
-
const confidenceWeight = confidenceWeights[finding.confidence ?? "high"];
|
|
21245
|
-
const score = severityWeight * categoryWeight * confidenceWeight;
|
|
21246
|
-
switch (finding.type) {
|
|
21247
|
-
case "jailbreak":
|
|
21248
|
-
breakdown.jailbreak += score;
|
|
21249
|
-
break;
|
|
21250
|
-
case "social_engineering":
|
|
21251
|
-
breakdown.socialEngineering += score;
|
|
21252
|
-
break;
|
|
21253
|
-
case "prompt_leaking":
|
|
21254
|
-
breakdown.promptLeaking += score;
|
|
21255
|
-
break;
|
|
21256
|
-
case "data_exfiltration":
|
|
21257
|
-
breakdown.dataExfiltration += score;
|
|
21258
|
-
break;
|
|
21259
|
-
case "privilege_escalation":
|
|
21260
|
-
breakdown.privilegeEscalation += score;
|
|
21261
|
-
break;
|
|
21262
|
-
case "suspicious_pattern":
|
|
21263
|
-
breakdown.suspiciousCode += score;
|
|
21264
|
-
break;
|
|
21265
|
-
case "sensitive_path":
|
|
21266
|
-
breakdown.sensitivePaths += score;
|
|
21267
|
-
break;
|
|
21268
|
-
case "url":
|
|
21269
|
-
breakdown.externalUrls += score;
|
|
21270
|
-
break;
|
|
21271
|
-
case "ai_defence":
|
|
21272
|
-
breakdown.aiDefence += score;
|
|
21273
|
-
break;
|
|
21274
|
-
case "ssrf":
|
|
21275
|
-
breakdown.ssrf += score;
|
|
21276
|
-
break;
|
|
21277
|
-
case "pii":
|
|
21278
|
-
breakdown.pii += score;
|
|
21279
|
-
break;
|
|
21280
|
-
case "code_execution":
|
|
21281
|
-
breakdown.codeExecution += score;
|
|
21282
|
-
break;
|
|
21283
|
-
case "obfuscated_directive":
|
|
21284
|
-
breakdown.obfuscatedDirective += score;
|
|
21285
|
-
break;
|
|
21286
|
-
case "typosquat":
|
|
21287
|
-
breakdown.typosquat += score;
|
|
21288
|
-
break;
|
|
21289
|
-
}
|
|
21290
|
-
}
|
|
21291
|
-
breakdown.jailbreak = Math.min(100, breakdown.jailbreak);
|
|
21292
|
-
breakdown.socialEngineering = Math.min(100, breakdown.socialEngineering);
|
|
21293
|
-
breakdown.promptLeaking = Math.min(100, breakdown.promptLeaking);
|
|
21294
|
-
breakdown.dataExfiltration = Math.min(100, breakdown.dataExfiltration);
|
|
21295
|
-
breakdown.privilegeEscalation = Math.min(100, breakdown.privilegeEscalation);
|
|
21296
|
-
breakdown.suspiciousCode = Math.min(100, breakdown.suspiciousCode);
|
|
21297
|
-
breakdown.sensitivePaths = Math.min(100, breakdown.sensitivePaths);
|
|
21298
|
-
breakdown.externalUrls = Math.min(100, breakdown.externalUrls);
|
|
21299
|
-
breakdown.aiDefence = Math.min(100, breakdown.aiDefence);
|
|
21300
|
-
breakdown.ssrf = Math.min(100, breakdown.ssrf);
|
|
21301
|
-
breakdown.pii = Math.min(100, breakdown.pii);
|
|
21302
|
-
breakdown.codeExecution = Math.min(100, breakdown.codeExecution);
|
|
21303
|
-
breakdown.obfuscatedDirective = Math.min(100, breakdown.obfuscatedDirective);
|
|
21304
|
-
breakdown.typosquat = Math.min(100, breakdown.typosquat);
|
|
21305
|
-
const total = Math.min(100, Math.round(breakdown.jailbreak * 0.2 + breakdown.socialEngineering * 0.11 + breakdown.promptLeaking * 0.11 + breakdown.dataExfiltration * 0.08 + breakdown.privilegeEscalation * 0.11 + breakdown.suspiciousCode * 0.07 + breakdown.sensitivePaths * 0.04 + breakdown.externalUrls * 0.04 + breakdown.aiDefence * 0.12 + breakdown.ssrf * 0.04 + breakdown.pii * 0.08 + breakdown.codeExecution * 0.4 + breakdown.obfuscatedDirective * 0.4 + breakdown.typosquat * 0.04));
|
|
21306
|
-
return { total, breakdown };
|
|
21307
|
-
}
|
|
21308
21356
|
|
|
21309
21357
|
// ../core/dist/src/security/scanner/confusables.js
|
|
21310
21358
|
var CONFUSABLES = {
|
|
@@ -21383,25 +21431,33 @@ var OBFUSCATION_DIRECTIVE_PATTERN = /(?:ignore|disregard|forget)\s+(?:all\s+|the
|
|
|
21383
21431
|
function scanCodeExecution(content, lineContexts) {
|
|
21384
21432
|
const lines = content.split("\n");
|
|
21385
21433
|
const contexts = lineContexts ?? analyzeMarkdownContext(content);
|
|
21434
|
+
const emit = (i, line, matched, prefix) => {
|
|
21435
|
+
const ctx = contexts[i];
|
|
21436
|
+
const inDocContext = ctx ? isDocumentationContext(ctx) : false;
|
|
21437
|
+
return [
|
|
21438
|
+
{
|
|
21439
|
+
type: "code_execution",
|
|
21440
|
+
severity: "medium",
|
|
21441
|
+
message: `${prefix}: "${matched.slice(0, 60)}${matched.length > 60 ? "..." : ""}"`,
|
|
21442
|
+
location: line.trim().slice(0, 100),
|
|
21443
|
+
lineNumber: i + 1,
|
|
21444
|
+
category: "code_execution",
|
|
21445
|
+
inDocumentationContext: inDocContext,
|
|
21446
|
+
confidence: "high"
|
|
21447
|
+
}
|
|
21448
|
+
];
|
|
21449
|
+
};
|
|
21386
21450
|
for (let i = 0; i < lines.length; i++) {
|
|
21387
21451
|
const line = lines[i];
|
|
21388
21452
|
for (const pattern of CODE_EXECUTION_PATTERNS) {
|
|
21453
|
+
const match = safeRegexTest(pattern, line);
|
|
21454
|
+
if (match)
|
|
21455
|
+
return emit(i, line, match[0], "Remote fetch piped to an interpreter");
|
|
21456
|
+
}
|
|
21457
|
+
for (const pattern of IMPERATIVE_FETCH_EXEC_PROSE) {
|
|
21389
21458
|
const match = safeRegexTest(pattern, line);
|
|
21390
21459
|
if (match) {
|
|
21391
|
-
|
|
21392
|
-
const inDocContext = ctx ? isDocumentationContext(ctx) : false;
|
|
21393
|
-
return [
|
|
21394
|
-
{
|
|
21395
|
-
type: "code_execution",
|
|
21396
|
-
severity: "medium",
|
|
21397
|
-
message: `Remote fetch piped to an interpreter: "${match[0].slice(0, 60)}${match[0].length > 60 ? "..." : ""}"`,
|
|
21398
|
-
location: line.trim().slice(0, 100),
|
|
21399
|
-
lineNumber: i + 1,
|
|
21400
|
-
category: "code_execution",
|
|
21401
|
-
inDocumentationContext: inDocContext,
|
|
21402
|
-
confidence: "high"
|
|
21403
|
-
}
|
|
21404
|
-
];
|
|
21460
|
+
return emit(i, line, match[0], "Natural-language instruction to fetch a remote file and execute it");
|
|
21405
21461
|
}
|
|
21406
21462
|
}
|
|
21407
21463
|
}
|
|
@@ -21446,29 +21502,71 @@ function scanObfuscatedDirective(content) {
|
|
|
21446
21502
|
}
|
|
21447
21503
|
return [];
|
|
21448
21504
|
}
|
|
21449
|
-
var
|
|
21450
|
-
|
|
21451
|
-
"
|
|
21452
|
-
"
|
|
21453
|
-
"
|
|
21454
|
-
|
|
21505
|
+
var CO_SIGNAL_MIN_SEVERITY = {
|
|
21506
|
+
// Existing four — behavior byte-identical to today, min 'high'.
|
|
21507
|
+
data_exfiltration: "high",
|
|
21508
|
+
privilege_escalation: "high",
|
|
21509
|
+
sensitive_path: "high",
|
|
21510
|
+
obfuscated_directive: "high",
|
|
21511
|
+
// New ClawHavoc advisory-tier categories — eligible at 'medium'.
|
|
21512
|
+
decoy_misdirection: "medium",
|
|
21513
|
+
archive_evasion: "medium",
|
|
21514
|
+
paste_host_fetch: "medium",
|
|
21515
|
+
gatekeeper_bypass: "medium"
|
|
21516
|
+
// its correlated/critical form already quarantines on its own
|
|
21517
|
+
};
|
|
21518
|
+
var SEVERITY_RANK = { low: 0, medium: 1, high: 2, critical: 3 };
|
|
21455
21519
|
var MAX_CODE_EXECUTION_CO_SIGNAL_LINE_DISTANCE = 40;
|
|
21456
21520
|
function isWithinCoSignalWindow(codeExecLine, coSignalLine) {
|
|
21457
21521
|
if (typeof codeExecLine !== "number" || typeof coSignalLine !== "number")
|
|
21458
21522
|
return true;
|
|
21459
21523
|
return Math.abs(codeExecLine - coSignalLine) <= MAX_CODE_EXECUTION_CO_SIGNAL_LINE_DISTANCE;
|
|
21460
21524
|
}
|
|
21525
|
+
var CO_SIGNAL_MEDIUM_CONFIDENCE_EXCEPTION = /* @__PURE__ */ new Set([
|
|
21526
|
+
"paste_host_fetch"
|
|
21527
|
+
]);
|
|
21461
21528
|
function escalateCodeExecution(findings) {
|
|
21462
21529
|
const codeExec = findings.find((f) => f.type === "code_execution");
|
|
21463
21530
|
if (!codeExec)
|
|
21464
21531
|
return;
|
|
21465
|
-
|
|
21532
|
+
if (codeExec.inDocumentationContext === true)
|
|
21533
|
+
return;
|
|
21534
|
+
const eligible = findings.filter((f) => f !== codeExec && CO_SIGNAL_MIN_SEVERITY[f.type] !== void 0 && f.inDocumentationContext !== true && isWithinCoSignalWindow(codeExec.lineNumber, f.lineNumber));
|
|
21535
|
+
const hasDangerousCoSignal = eligible.some((f) => CO_SIGNAL_MIN_SEVERITY[f.type] === "high" && (f.severity === "high" || f.severity === "critical"));
|
|
21466
21536
|
if (hasDangerousCoSignal) {
|
|
21467
21537
|
codeExec.severity = "critical";
|
|
21468
21538
|
codeExec.message = `Remote fetch piped to an interpreter, co-occurring with exfiltration/privilege/credential signals \u2014 likely supply-chain execution. ${codeExec.message}`;
|
|
21539
|
+
return;
|
|
21540
|
+
}
|
|
21541
|
+
const advisoryTypes = new Set(eligible.filter((f) => {
|
|
21542
|
+
const min = CO_SIGNAL_MIN_SEVERITY[f.type];
|
|
21543
|
+
const confidenceOk = CO_SIGNAL_MEDIUM_CONFIDENCE_EXCEPTION.has(f.type) ? f.confidence !== "low" : f.confidence === "high";
|
|
21544
|
+
return min === "medium" && SEVERITY_RANK[f.severity] >= SEVERITY_RANK[min] && confidenceOk;
|
|
21545
|
+
}).map((f) => f.type));
|
|
21546
|
+
if (advisoryTypes.size >= 2) {
|
|
21547
|
+
codeExec.severity = "critical";
|
|
21548
|
+
codeExec.message = `Remote fetch/execute instruction corroborated by two independent advisory-tier signals (${[
|
|
21549
|
+
...advisoryTypes
|
|
21550
|
+
].sort().join(", ")}) \u2014 likely supply-chain execution. ${codeExec.message}`;
|
|
21469
21551
|
}
|
|
21470
21552
|
}
|
|
21471
21553
|
|
|
21554
|
+
// ../core/dist/src/security/scanner/typosquat.js
|
|
21555
|
+
var BRAND_ALIASES = {
|
|
21556
|
+
anthropic: "anthropics",
|
|
21557
|
+
claude: "anthropics",
|
|
21558
|
+
gemini: "google-gemini",
|
|
21559
|
+
copilot: "microsoft",
|
|
21560
|
+
vercel: "vercel-labs",
|
|
21561
|
+
salesforce: "SalesforceCommerceCloud"
|
|
21562
|
+
};
|
|
21563
|
+
var AUTHORITY_CLAIMING_AFFIXES = /* @__PURE__ */ new Set([
|
|
21564
|
+
"official",
|
|
21565
|
+
"verified",
|
|
21566
|
+
"authentic",
|
|
21567
|
+
"genuine"
|
|
21568
|
+
]);
|
|
21569
|
+
|
|
21472
21570
|
// ../core/dist/src/security/scanner/SecurityScanner.formatters.js
|
|
21473
21571
|
function toMinimalRefs(report) {
|
|
21474
21572
|
return report.findings.map((finding) => {
|
|
@@ -21570,6 +21668,137 @@ function toSummary(report) {
|
|
|
21570
21668
|
};
|
|
21571
21669
|
}
|
|
21572
21670
|
|
|
21671
|
+
// ../core/dist/src/security/scanner/SecurityScanner.urls.js
|
|
21672
|
+
function extractUrls(content) {
|
|
21673
|
+
const urlPattern = /https?:\/\/[^\s<>"')\]]+/gi;
|
|
21674
|
+
const lines = content.split("\n");
|
|
21675
|
+
const results = [];
|
|
21676
|
+
lines.forEach((line, index) => {
|
|
21677
|
+
let match;
|
|
21678
|
+
while ((match = urlPattern.exec(line)) !== null) {
|
|
21679
|
+
results.push({ url: match[0], line: index + 1 });
|
|
21680
|
+
}
|
|
21681
|
+
});
|
|
21682
|
+
return results;
|
|
21683
|
+
}
|
|
21684
|
+
|
|
21685
|
+
// ../core/dist/src/security/scanner/SecurityScanner.risk-score.js
|
|
21686
|
+
function calculateRiskScore(findings) {
|
|
21687
|
+
const breakdown = {
|
|
21688
|
+
jailbreak: 0,
|
|
21689
|
+
socialEngineering: 0,
|
|
21690
|
+
promptLeaking: 0,
|
|
21691
|
+
dataExfiltration: 0,
|
|
21692
|
+
privilegeEscalation: 0,
|
|
21693
|
+
suspiciousCode: 0,
|
|
21694
|
+
sensitivePaths: 0,
|
|
21695
|
+
externalUrls: 0,
|
|
21696
|
+
aiDefence: 0,
|
|
21697
|
+
ssrf: 0,
|
|
21698
|
+
pii: 0,
|
|
21699
|
+
codeExecution: 0,
|
|
21700
|
+
obfuscatedDirective: 0,
|
|
21701
|
+
typosquat: 0,
|
|
21702
|
+
gatekeeperBypass: 0,
|
|
21703
|
+
archiveEvasion: 0,
|
|
21704
|
+
pasteHostFetch: 0,
|
|
21705
|
+
encodedPayload: 0,
|
|
21706
|
+
decoyMisdirection: 0
|
|
21707
|
+
};
|
|
21708
|
+
const confidenceWeights = {
|
|
21709
|
+
high: 1,
|
|
21710
|
+
medium: 0.7,
|
|
21711
|
+
low: 0.3
|
|
21712
|
+
};
|
|
21713
|
+
for (const finding of findings) {
|
|
21714
|
+
const severityWeight = SEVERITY_WEIGHTS[finding.severity];
|
|
21715
|
+
const categoryWeight = CATEGORY_WEIGHTS[finding.type] ?? 1;
|
|
21716
|
+
const confidenceWeight = confidenceWeights[finding.confidence ?? "high"];
|
|
21717
|
+
const score = severityWeight * categoryWeight * confidenceWeight;
|
|
21718
|
+
switch (finding.type) {
|
|
21719
|
+
case "jailbreak":
|
|
21720
|
+
breakdown.jailbreak += score;
|
|
21721
|
+
break;
|
|
21722
|
+
case "social_engineering":
|
|
21723
|
+
breakdown.socialEngineering += score;
|
|
21724
|
+
break;
|
|
21725
|
+
case "prompt_leaking":
|
|
21726
|
+
breakdown.promptLeaking += score;
|
|
21727
|
+
break;
|
|
21728
|
+
case "data_exfiltration":
|
|
21729
|
+
breakdown.dataExfiltration += score;
|
|
21730
|
+
break;
|
|
21731
|
+
case "privilege_escalation":
|
|
21732
|
+
breakdown.privilegeEscalation += score;
|
|
21733
|
+
break;
|
|
21734
|
+
case "suspicious_pattern":
|
|
21735
|
+
breakdown.suspiciousCode += score;
|
|
21736
|
+
break;
|
|
21737
|
+
case "sensitive_path":
|
|
21738
|
+
breakdown.sensitivePaths += score;
|
|
21739
|
+
break;
|
|
21740
|
+
case "url":
|
|
21741
|
+
breakdown.externalUrls += score;
|
|
21742
|
+
break;
|
|
21743
|
+
case "ai_defence":
|
|
21744
|
+
breakdown.aiDefence += score;
|
|
21745
|
+
break;
|
|
21746
|
+
case "ssrf":
|
|
21747
|
+
breakdown.ssrf += score;
|
|
21748
|
+
break;
|
|
21749
|
+
case "pii":
|
|
21750
|
+
breakdown.pii += score;
|
|
21751
|
+
break;
|
|
21752
|
+
case "code_execution":
|
|
21753
|
+
breakdown.codeExecution += score;
|
|
21754
|
+
break;
|
|
21755
|
+
case "obfuscated_directive":
|
|
21756
|
+
breakdown.obfuscatedDirective += score;
|
|
21757
|
+
break;
|
|
21758
|
+
case "typosquat":
|
|
21759
|
+
breakdown.typosquat += score;
|
|
21760
|
+
break;
|
|
21761
|
+
case "gatekeeper_bypass":
|
|
21762
|
+
breakdown.gatekeeperBypass += score;
|
|
21763
|
+
break;
|
|
21764
|
+
case "archive_evasion":
|
|
21765
|
+
breakdown.archiveEvasion += score;
|
|
21766
|
+
break;
|
|
21767
|
+
case "paste_host_fetch":
|
|
21768
|
+
breakdown.pasteHostFetch += score;
|
|
21769
|
+
break;
|
|
21770
|
+
case "encoded_payload":
|
|
21771
|
+
breakdown.encodedPayload += score;
|
|
21772
|
+
break;
|
|
21773
|
+
case "decoy_misdirection":
|
|
21774
|
+
breakdown.decoyMisdirection += score;
|
|
21775
|
+
break;
|
|
21776
|
+
}
|
|
21777
|
+
}
|
|
21778
|
+
breakdown.jailbreak = Math.min(100, breakdown.jailbreak);
|
|
21779
|
+
breakdown.socialEngineering = Math.min(100, breakdown.socialEngineering);
|
|
21780
|
+
breakdown.promptLeaking = Math.min(100, breakdown.promptLeaking);
|
|
21781
|
+
breakdown.dataExfiltration = Math.min(100, breakdown.dataExfiltration);
|
|
21782
|
+
breakdown.privilegeEscalation = Math.min(100, breakdown.privilegeEscalation);
|
|
21783
|
+
breakdown.suspiciousCode = Math.min(100, breakdown.suspiciousCode);
|
|
21784
|
+
breakdown.sensitivePaths = Math.min(100, breakdown.sensitivePaths);
|
|
21785
|
+
breakdown.externalUrls = Math.min(100, breakdown.externalUrls);
|
|
21786
|
+
breakdown.aiDefence = Math.min(100, breakdown.aiDefence);
|
|
21787
|
+
breakdown.ssrf = Math.min(100, breakdown.ssrf);
|
|
21788
|
+
breakdown.pii = Math.min(100, breakdown.pii);
|
|
21789
|
+
breakdown.codeExecution = Math.min(100, breakdown.codeExecution);
|
|
21790
|
+
breakdown.obfuscatedDirective = Math.min(100, breakdown.obfuscatedDirective);
|
|
21791
|
+
breakdown.typosquat = Math.min(100, breakdown.typosquat);
|
|
21792
|
+
breakdown.gatekeeperBypass = Math.min(100, breakdown.gatekeeperBypass);
|
|
21793
|
+
breakdown.archiveEvasion = Math.min(100, breakdown.archiveEvasion);
|
|
21794
|
+
breakdown.pasteHostFetch = Math.min(100, breakdown.pasteHostFetch);
|
|
21795
|
+
breakdown.encodedPayload = Math.min(100, breakdown.encodedPayload);
|
|
21796
|
+
breakdown.decoyMisdirection = Math.min(100, breakdown.decoyMisdirection);
|
|
21797
|
+
const total = Math.min(100, Math.round(breakdown.jailbreak * 0.2 + breakdown.socialEngineering * 0.11 + breakdown.promptLeaking * 0.11 + breakdown.dataExfiltration * 0.08 + breakdown.privilegeEscalation * 0.11 + breakdown.suspiciousCode * 0.07 + breakdown.sensitivePaths * 0.04 + breakdown.externalUrls * 0.04 + breakdown.aiDefence * 0.12 + breakdown.ssrf * 0.04 + breakdown.pii * 0.08 + breakdown.codeExecution * 0.4 + breakdown.obfuscatedDirective * 0.4 + breakdown.typosquat * 0.04 + breakdown.gatekeeperBypass * 0.4 + breakdown.archiveEvasion * 0.4 + breakdown.pasteHostFetch * 0.4 + breakdown.encodedPayload * 0.04 + // SMI-6033 Wave 4 (Gap 6): additive 0.04, same advisory tier as encodedPayload/typosquat.
|
|
21798
|
+
breakdown.decoyMisdirection * 0.04));
|
|
21799
|
+
return { total, breakdown };
|
|
21800
|
+
}
|
|
21801
|
+
|
|
21573
21802
|
// ../core/dist/src/security/scanner/SecurityScanner.ssrf.js
|
|
21574
21803
|
function scanSsrfPatterns(content, lineContexts, maxLength) {
|
|
21575
21804
|
const findings = [];
|
|
@@ -21718,14 +21947,40 @@ function scanPiiPatterns(content, lineContexts) {
|
|
|
21718
21947
|
return findings;
|
|
21719
21948
|
}
|
|
21720
21949
|
|
|
21721
|
-
// ../core/dist/src/security/scanner/SecurityScanner.
|
|
21722
|
-
var
|
|
21723
|
-
var CHMOD_FETCH_CONTEXT = /\b(?:curl|wget)\b|\bgit\s+clone\b|\bnpx\b[^\n]{0,80}https?:\/\//i;
|
|
21724
|
-
var CHMOD_TARGET = /\bchmod\s+(?:-[A-Za-z]+\s+)?(?:[0-7]{3,4}|[ugoa]*(?:[+\-=][rwxXstugo]+(?:,[ugoa]*[+\-=][rwxXstugo]*)*)+)\s+(\S+)/i;
|
|
21950
|
+
// ../core/dist/src/security/scanner/SecurityScanner.fetch-correlation.js
|
|
21951
|
+
var FETCH_COMMAND_PATTERN = /\b(?:curl|wget)\b|\bgit\s+clone\b|\bnpx\b[^\n]{0,80}https?:\/\//i;
|
|
21725
21952
|
function escapeRegExp2(s) {
|
|
21726
21953
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
21727
21954
|
}
|
|
21728
|
-
function
|
|
21955
|
+
function normalizeCorrelationPath(raw) {
|
|
21956
|
+
const cleaned = raw.replace(/['"]/g, "");
|
|
21957
|
+
const isAbsolute5 = cleaned.startsWith("/");
|
|
21958
|
+
const segments = [];
|
|
21959
|
+
for (const segment of cleaned.split("/")) {
|
|
21960
|
+
if (segment === "" || segment === ".")
|
|
21961
|
+
continue;
|
|
21962
|
+
if (segment === "..") {
|
|
21963
|
+
const last = segments[segments.length - 1];
|
|
21964
|
+
if (last !== void 0 && last !== "..")
|
|
21965
|
+
segments.pop();
|
|
21966
|
+
else if (!isAbsolute5)
|
|
21967
|
+
segments.push("..");
|
|
21968
|
+
continue;
|
|
21969
|
+
}
|
|
21970
|
+
segments.push(segment);
|
|
21971
|
+
}
|
|
21972
|
+
const base = segments.pop() ?? "";
|
|
21973
|
+
return { dir: isAbsolute5 ? "/" + segments.join("/") : segments.join("/"), base };
|
|
21974
|
+
}
|
|
21975
|
+
function correlationTargetBasename(rawPath) {
|
|
21976
|
+
return normalizeCorrelationPath(rawPath).base;
|
|
21977
|
+
}
|
|
21978
|
+
function directoriesCorrelate(a, b) {
|
|
21979
|
+
if (a === "" || b === "")
|
|
21980
|
+
return true;
|
|
21981
|
+
return a === b;
|
|
21982
|
+
}
|
|
21983
|
+
function implicitDownloadDestination(line) {
|
|
21729
21984
|
const lastSegment = (urlAfterScheme) => {
|
|
21730
21985
|
const noFrag = urlAfterScheme.split(/[?#]/)[0];
|
|
21731
21986
|
const slash = noFrag.indexOf("/");
|
|
@@ -21742,14 +21997,39 @@ function implicitDownloadBasename(line) {
|
|
|
21742
21997
|
return lastSegment(clone2[1]).replace(/\.git$/i, "");
|
|
21743
21998
|
const curlEq = line.match(/\bcurl\b[^\n]{0,200}?--output=['"]?(\S{1,400})/i);
|
|
21744
21999
|
if (curlEq)
|
|
21745
|
-
return curlEq[1].replace(/['"]/g, "")
|
|
22000
|
+
return curlEq[1].replace(/['"]/g, "");
|
|
21746
22001
|
return "";
|
|
21747
22002
|
}
|
|
22003
|
+
function isCorrelatedWithFetchDestination(targetPath, fetchLines) {
|
|
22004
|
+
const target = normalizeCorrelationPath(targetPath);
|
|
22005
|
+
if (target.base === "")
|
|
22006
|
+
return false;
|
|
22007
|
+
const re = new RegExp(`(?:-o|-O|--output|>>?)\\s*['"]?((?:[^\\s'"]*/)?${escapeRegExp2(target.base)})(?:[\\s'"?]|$)`, "g");
|
|
22008
|
+
for (const line of fetchLines) {
|
|
22009
|
+
for (const match of line.matchAll(re)) {
|
|
22010
|
+
if (directoriesCorrelate(target.dir, normalizeCorrelationPath(match[1]).dir))
|
|
22011
|
+
return true;
|
|
22012
|
+
}
|
|
22013
|
+
const implicit = implicitDownloadDestination(line);
|
|
22014
|
+
if (implicit === "")
|
|
22015
|
+
continue;
|
|
22016
|
+
const destination = normalizeCorrelationPath(implicit);
|
|
22017
|
+
if (destination.base !== target.base)
|
|
22018
|
+
continue;
|
|
22019
|
+
if (directoriesCorrelate(target.dir, destination.dir))
|
|
22020
|
+
return true;
|
|
22021
|
+
}
|
|
22022
|
+
return false;
|
|
22023
|
+
}
|
|
22024
|
+
|
|
22025
|
+
// ../core/dist/src/security/scanner/SecurityScanner.compound.js
|
|
22026
|
+
var OWNER_PERM_CHMOD = /\bchmod\s+(?:-[A-Za-z]+\s+)?(?:[0-7]{3,4}|[ugoa]*(?:[+\-=][rwxXstugo]+(?:,[ugoa]*[+\-=][rwxXstugo]*)*)+)/i;
|
|
22027
|
+
var CHMOD_TARGET = /\bchmod\s+(?:-[A-Za-z]+\s+)?(?:[0-7]{3,4}|[ugoa]*(?:[+\-=][rwxXstugo]+(?:,[ugoa]*[+\-=][rwxXstugo]*)*)+)\s+(\S+)/i;
|
|
21748
22028
|
function scanChmodFetchCompound(content, alreadyFlaggedLines, lineContexts) {
|
|
21749
22029
|
const findings = [];
|
|
21750
22030
|
const lines = content.split("\n");
|
|
21751
22031
|
const contexts = lineContexts ?? analyzeMarkdownContext(content);
|
|
21752
|
-
const fetchLines = lines.filter((l) => safeRegexTest(
|
|
22032
|
+
const fetchLines = lines.filter((l) => safeRegexTest(FETCH_COMMAND_PATTERN, l) !== null);
|
|
21753
22033
|
lines.forEach((line, index) => {
|
|
21754
22034
|
const lineNumber = index + 1;
|
|
21755
22035
|
if (alreadyFlaggedLines.has(lineNumber))
|
|
@@ -21758,14 +22038,13 @@ function scanChmodFetchCompound(content, alreadyFlaggedLines, lineContexts) {
|
|
|
21758
22038
|
if (!match)
|
|
21759
22039
|
return;
|
|
21760
22040
|
const window2 = [lines[index - 1] ?? "", line, lines[index + 1] ?? ""].join("\n");
|
|
21761
|
-
const adjacentFetch = safeRegexTest(
|
|
22041
|
+
const adjacentFetch = safeRegexTest(FETCH_COMMAND_PATTERN, window2) !== null;
|
|
21762
22042
|
let correlated = false;
|
|
21763
22043
|
const tm = safeRegexTest(CHMOD_TARGET, line);
|
|
21764
22044
|
if (tm) {
|
|
21765
|
-
const
|
|
21766
|
-
if (
|
|
21767
|
-
|
|
21768
|
-
correlated = fetchLines.some((l) => re.test(l) || implicitDownloadBasename(l) === base);
|
|
22045
|
+
const targetPath = tm[1].replace(/['"]/g, "");
|
|
22046
|
+
if (correlationTargetBasename(targetPath).length >= 3) {
|
|
22047
|
+
correlated = isCorrelatedWithFetchDestination(targetPath, fetchLines);
|
|
21769
22048
|
}
|
|
21770
22049
|
}
|
|
21771
22050
|
if (!adjacentFetch && !correlated)
|
|
@@ -21788,6 +22067,44 @@ function scanChmodFetchCompound(content, alreadyFlaggedLines, lineContexts) {
|
|
|
21788
22067
|
});
|
|
21789
22068
|
return findings;
|
|
21790
22069
|
}
|
|
22070
|
+
var XATTR_CLEAR_ALL = /\bxattr\b[^\n]{0,40}-[a-zA-Z]*c[a-zA-Z]*\b/i;
|
|
22071
|
+
var XATTR_DELETE_QUARANTINE = /\bxattr\b[^\n]{0,40}-[a-zA-Z]*d[a-zA-Z]*\s+['"]?com\.apple\.quarantine\b/i;
|
|
22072
|
+
var XATTR_CLEAR_ALL_TARGET = /\bxattr\b[^\n]{0,40}-[a-zA-Z]*c[a-zA-Z]*\b\s+['"]?(\S+)/i;
|
|
22073
|
+
var XATTR_DELETE_QUARANTINE_TARGET = /\bxattr\b[^\n]{0,40}-[a-zA-Z]*d[a-zA-Z]*\s+['"]?com\.apple\.quarantine\b['"]?\s+['"]?(\S+)/i;
|
|
22074
|
+
function extractXattrTargetPath(line) {
|
|
22075
|
+
const m = safeRegexTest(XATTR_CLEAR_ALL_TARGET, line) ?? safeRegexTest(XATTR_DELETE_QUARANTINE_TARGET, line);
|
|
22076
|
+
if (!m)
|
|
22077
|
+
return "";
|
|
22078
|
+
return m[1].replace(/['"]/g, "");
|
|
22079
|
+
}
|
|
22080
|
+
function scanGatekeeperBypass(content, lineContexts, isHighTrustAuthor = false) {
|
|
22081
|
+
const findings = [];
|
|
22082
|
+
const lines = content.split("\n");
|
|
22083
|
+
const contexts = lineContexts ?? analyzeMarkdownContext(content);
|
|
22084
|
+
const fetchLines = lines.filter((l) => safeRegexTest(FETCH_COMMAND_PATTERN, l) !== null);
|
|
22085
|
+
lines.forEach((line, index) => {
|
|
22086
|
+
const match = safeRegexTest(XATTR_CLEAR_ALL, line) ?? safeRegexTest(XATTR_DELETE_QUARANTINE, line);
|
|
22087
|
+
if (!match)
|
|
22088
|
+
return;
|
|
22089
|
+
const ctx = contexts[index];
|
|
22090
|
+
const inInlineCode = ctx?.isInlineCode && isWithinInlineCode(line, match.index ?? 0);
|
|
22091
|
+
const inDocContext = ctx ? isDocumentationContext(ctx) || inInlineCode : false;
|
|
22092
|
+
const targetPath = extractXattrTargetPath(line);
|
|
22093
|
+
const correlated = correlationTargetBasename(targetPath).length >= 3 && isCorrelatedWithFetchDestination(targetPath, fetchLines);
|
|
22094
|
+
const critical = !inDocContext && correlated && !isHighTrustAuthor;
|
|
22095
|
+
findings.push({
|
|
22096
|
+
type: "gatekeeper_bypass",
|
|
22097
|
+
severity: inDocContext ? "low" : critical ? "critical" : "medium",
|
|
22098
|
+
message: `xattr command strips the macOS Gatekeeper quarantine attribute: "${match[0].trim().slice(0, 100)}"`,
|
|
22099
|
+
location: line.trim().slice(0, 100),
|
|
22100
|
+
lineNumber: index + 1,
|
|
22101
|
+
category: "gatekeeper_bypass",
|
|
22102
|
+
inDocumentationContext: inDocContext,
|
|
22103
|
+
confidence: inDocContext ? "low" : "high"
|
|
22104
|
+
});
|
|
22105
|
+
});
|
|
22106
|
+
return findings;
|
|
22107
|
+
}
|
|
21791
22108
|
|
|
21792
22109
|
// ../core/dist/src/security/scanner/SecurityScanner.scanners.js
|
|
21793
22110
|
var ENV_EXFIL_CONTEXT = /\b(?:cat|cp|mv|scp|rsync|source|curl|wget|fetch|less|more|head|tail|tee|upload|tar|zip|gzip|base64|xxd|dd|nc|netcat)\b|[|>]/i;
|
|
@@ -21950,6 +22267,541 @@ function scanPrivilegeEscalation(content, lineContexts) {
|
|
|
21950
22267
|
return findings;
|
|
21951
22268
|
}
|
|
21952
22269
|
|
|
22270
|
+
// ../core/dist/src/security/scanner/SecurityScanner.archive.js
|
|
22271
|
+
var UNZIP_INVOCATION = /\bunzip\b[^\n]{0,200}/i;
|
|
22272
|
+
var UNRAR_INVOCATION = /\bunrar\s+x\b[^\n]{0,200}/i;
|
|
22273
|
+
var SEVENZIP_INVOCATION = /\b7z\s+x\b[^\n]{0,200}/i;
|
|
22274
|
+
var ZIP_INVOCATION = /\bzip\b[^\n]{0,200}/i;
|
|
22275
|
+
var UNZIP_PASSWORD_ARG = /-P\s+(\S{1,200})/;
|
|
22276
|
+
var UNRAR_SEVENZIP_PASSWORD_ARG = /-p(\S{1,200})/i;
|
|
22277
|
+
var ZIP_PASSWORD_ARG = /-P\s+(\S{1,200})/;
|
|
22278
|
+
var ZIP_ENCRYPT_FLAG = /(?:^|\s)-e(?:\s|$)/;
|
|
22279
|
+
var ARCHIVE_FILENAME = /(\S+\.(?:zip|rar|7z|tar\.gz|tgz))\b/i;
|
|
22280
|
+
function findArchiveCliPassword(line) {
|
|
22281
|
+
const unzip = safeRegexTest(UNZIP_INVOCATION, line);
|
|
22282
|
+
if (unzip) {
|
|
22283
|
+
const pw = safeRegexTest(UNZIP_PASSWORD_ARG, unzip[0]);
|
|
22284
|
+
if (pw)
|
|
22285
|
+
return { tool: "unzip", password: pw[1], index: unzip.index ?? 0 };
|
|
22286
|
+
}
|
|
22287
|
+
const unrar = safeRegexTest(UNRAR_INVOCATION, line);
|
|
22288
|
+
if (unrar) {
|
|
22289
|
+
const pw = safeRegexTest(UNRAR_SEVENZIP_PASSWORD_ARG, unrar[0]);
|
|
22290
|
+
if (pw)
|
|
22291
|
+
return { tool: "unrar", password: pw[1], index: unrar.index ?? 0 };
|
|
22292
|
+
}
|
|
22293
|
+
const sevenZip = safeRegexTest(SEVENZIP_INVOCATION, line);
|
|
22294
|
+
if (sevenZip) {
|
|
22295
|
+
const pw = safeRegexTest(UNRAR_SEVENZIP_PASSWORD_ARG, sevenZip[0]);
|
|
22296
|
+
if (pw)
|
|
22297
|
+
return { tool: "7z", password: pw[1], index: sevenZip.index ?? 0 };
|
|
22298
|
+
}
|
|
22299
|
+
const zip = safeRegexTest(ZIP_INVOCATION, line);
|
|
22300
|
+
if (zip) {
|
|
22301
|
+
const pw = safeRegexTest(ZIP_PASSWORD_ARG, zip[0]);
|
|
22302
|
+
if (pw && safeRegexCheck(ZIP_ENCRYPT_FLAG, zip[0])) {
|
|
22303
|
+
return { tool: "zip", password: pw[1], index: zip.index ?? 0 };
|
|
22304
|
+
}
|
|
22305
|
+
}
|
|
22306
|
+
return null;
|
|
22307
|
+
}
|
|
22308
|
+
function extractArchiveTargetPath(line) {
|
|
22309
|
+
const m = safeRegexTest(ARCHIVE_FILENAME, line);
|
|
22310
|
+
if (!m)
|
|
22311
|
+
return "";
|
|
22312
|
+
return m[1].replace(/['"]/g, "");
|
|
22313
|
+
}
|
|
22314
|
+
var SHELL_VAR_REF = /^\$\{?[A-Za-z_][A-Za-z0-9_]*\}?$/;
|
|
22315
|
+
function isInlineLiteralPassword(password2) {
|
|
22316
|
+
if (!password2)
|
|
22317
|
+
return false;
|
|
22318
|
+
const unquoted = password2.replace(/^['"]|['"]$/g, "");
|
|
22319
|
+
if (safeRegexCheck(SHELL_VAR_REF, unquoted))
|
|
22320
|
+
return false;
|
|
22321
|
+
return !looksLikePlaceholderSecret(unquoted);
|
|
22322
|
+
}
|
|
22323
|
+
var ARCHIVE_NOUN = /\b(?:zip|rar|7z|tar\.gz|tgz|archive)\b/i;
|
|
22324
|
+
var PASSWORD_NOUN = /\bpassword\b|\bpasscode\b|\bpassphrase\b/i;
|
|
22325
|
+
function findArchivePasswordProseLines(lines) {
|
|
22326
|
+
const flagged = /* @__PURE__ */ new Set();
|
|
22327
|
+
lines.forEach((line, index) => {
|
|
22328
|
+
if (!safeRegexCheck(ARCHIVE_NOUN, line))
|
|
22329
|
+
return;
|
|
22330
|
+
const start = Math.max(0, index - 2);
|
|
22331
|
+
const end = Math.min(lines.length - 1, index + 2);
|
|
22332
|
+
for (let i = start; i <= end; i++) {
|
|
22333
|
+
if (safeRegexCheck(PASSWORD_NOUN, lines[i])) {
|
|
22334
|
+
flagged.add(index + 1);
|
|
22335
|
+
break;
|
|
22336
|
+
}
|
|
22337
|
+
}
|
|
22338
|
+
});
|
|
22339
|
+
return Array.from(flagged);
|
|
22340
|
+
}
|
|
22341
|
+
function scanArchiveEvasion(content, lineContexts) {
|
|
22342
|
+
const findings = [];
|
|
22343
|
+
const lines = content.split("\n");
|
|
22344
|
+
const contexts = lineContexts ?? analyzeMarkdownContext(content);
|
|
22345
|
+
const fetchLines = lines.filter((l) => safeRegexTest(FETCH_COMMAND_PATTERN, l) !== null);
|
|
22346
|
+
const emittedLines = /* @__PURE__ */ new Set();
|
|
22347
|
+
lines.forEach((line, index) => {
|
|
22348
|
+
const lineNumber = index + 1;
|
|
22349
|
+
const cli = findArchiveCliPassword(line);
|
|
22350
|
+
if (!cli)
|
|
22351
|
+
return;
|
|
22352
|
+
const ctx = contexts[index];
|
|
22353
|
+
const inInlineCode = ctx?.isInlineCode && isWithinInlineCode(line, cli.index);
|
|
22354
|
+
const inDocContext = ctx ? isDocumentationContext(ctx) || inInlineCode : false;
|
|
22355
|
+
const inlineLiteral = isInlineLiteralPassword(cli.password);
|
|
22356
|
+
const targetPath = extractArchiveTargetPath(line);
|
|
22357
|
+
const correlated = correlationTargetBasename(targetPath).length >= 3 && isCorrelatedWithFetchDestination(targetPath, fetchLines);
|
|
22358
|
+
const critical = !inDocContext && inlineLiteral && correlated;
|
|
22359
|
+
findings.push({
|
|
22360
|
+
type: "archive_evasion",
|
|
22361
|
+
severity: inDocContext ? "low" : critical ? "critical" : "medium",
|
|
22362
|
+
message: `Password-protected archive CLI usage (${cli.tool}): "${line.trim().slice(0, 100)}"`,
|
|
22363
|
+
location: line.trim().slice(0, 100),
|
|
22364
|
+
lineNumber,
|
|
22365
|
+
category: "archive_evasion",
|
|
22366
|
+
inDocumentationContext: inDocContext,
|
|
22367
|
+
confidence: inDocContext ? "low" : "high"
|
|
22368
|
+
});
|
|
22369
|
+
emittedLines.add(lineNumber);
|
|
22370
|
+
});
|
|
22371
|
+
for (const lineNumber of findArchivePasswordProseLines(lines)) {
|
|
22372
|
+
if (emittedLines.has(lineNumber))
|
|
22373
|
+
continue;
|
|
22374
|
+
const ctx = contexts[lineNumber - 1];
|
|
22375
|
+
const inDocContext = ctx ? isDocumentationContext(ctx) : false;
|
|
22376
|
+
findings.push({
|
|
22377
|
+
type: "archive_evasion",
|
|
22378
|
+
severity: inDocContext ? "low" : "medium",
|
|
22379
|
+
message: `Archive and password mentioned in close proximity: "${lines[lineNumber - 1].trim().slice(0, 100)}"`,
|
|
22380
|
+
location: lines[lineNumber - 1].trim().slice(0, 100),
|
|
22381
|
+
lineNumber,
|
|
22382
|
+
category: "archive_evasion",
|
|
22383
|
+
inDocumentationContext: inDocContext,
|
|
22384
|
+
confidence: inDocContext ? "low" : "medium"
|
|
22385
|
+
});
|
|
22386
|
+
}
|
|
22387
|
+
return findings;
|
|
22388
|
+
}
|
|
22389
|
+
|
|
22390
|
+
// ../core/dist/src/security/scanner/SecurityScanner.paste-host.js
|
|
22391
|
+
var ANON_PASTE_HOST_SET = new Set([...ANON_PASTE_HOSTS, ...URL_SHORTENER_DOMAINS].map((d) => d.toLowerCase()));
|
|
22392
|
+
var TRANSIENT_TRANSFER_HOST_SET = new Set(TRANSIENT_TRANSFER_HOSTS.map((d) => d.toLowerCase()));
|
|
22393
|
+
function classifyPasteHostTier(url2) {
|
|
22394
|
+
let hostname7;
|
|
22395
|
+
try {
|
|
22396
|
+
hostname7 = new URL(url2).hostname.toLowerCase();
|
|
22397
|
+
} catch {
|
|
22398
|
+
return null;
|
|
22399
|
+
}
|
|
22400
|
+
const matchesSet = (set2) => {
|
|
22401
|
+
for (const domain2 of set2) {
|
|
22402
|
+
if (hostname7 === domain2 || hostname7.endsWith("." + domain2))
|
|
22403
|
+
return true;
|
|
22404
|
+
}
|
|
22405
|
+
return false;
|
|
22406
|
+
};
|
|
22407
|
+
if (matchesSet(ANON_PASTE_HOST_SET))
|
|
22408
|
+
return "anon";
|
|
22409
|
+
if (matchesSet(TRANSIENT_TRANSFER_HOST_SET))
|
|
22410
|
+
return "transient";
|
|
22411
|
+
return null;
|
|
22412
|
+
}
|
|
22413
|
+
var FETCH_VERB_PATTERN = /\b(?:curl|wget)\b/i;
|
|
22414
|
+
var PIPE_TO_INTERPRETER_TAIL = /^\|\s*(?:sudo\s+(?:-[A-Za-z]+\s+)?)?(?:(?:ba|z|da)?sh|python[23]?|node|ruby|perl|php|fish|bun|deno)\b/i;
|
|
22415
|
+
function isDirectPipeToInterpreter(url2, lineContent) {
|
|
22416
|
+
const pipeIndex = lineContent.indexOf("|");
|
|
22417
|
+
if (pipeIndex < 0)
|
|
22418
|
+
return false;
|
|
22419
|
+
const beforePipe = lineContent.slice(0, pipeIndex);
|
|
22420
|
+
if (!beforePipe.includes(url2))
|
|
22421
|
+
return false;
|
|
22422
|
+
if (!safeRegexCheck(FETCH_VERB_PATTERN, beforePipe))
|
|
22423
|
+
return false;
|
|
22424
|
+
return safeRegexCheck(PIPE_TO_INTERPRETER_TAIL, lineContent.slice(pipeIndex));
|
|
22425
|
+
}
|
|
22426
|
+
var NPX_DIRECT_EXEC_PATTERN = /\bnpx\b[^\n]{0,80}https?:\/\//i;
|
|
22427
|
+
var FETCH_VERB_TOKENS = /* @__PURE__ */ new Set(["curl", "wget", "npx"]);
|
|
22428
|
+
var FLAG_TOKEN = /^-{1,2}[A-Za-z][\w-]*$/;
|
|
22429
|
+
var VALUE_TAKING_FLAGS = /* @__PURE__ */ new Set([
|
|
22430
|
+
"-o",
|
|
22431
|
+
"-x",
|
|
22432
|
+
"-h",
|
|
22433
|
+
"-d",
|
|
22434
|
+
"-a",
|
|
22435
|
+
"-e",
|
|
22436
|
+
"-u",
|
|
22437
|
+
"-b",
|
|
22438
|
+
"--output",
|
|
22439
|
+
"--request",
|
|
22440
|
+
"--header",
|
|
22441
|
+
"--data",
|
|
22442
|
+
"--data-raw",
|
|
22443
|
+
"--data-binary",
|
|
22444
|
+
"--data-urlencode",
|
|
22445
|
+
"--user-agent",
|
|
22446
|
+
"--referer",
|
|
22447
|
+
"--proxy",
|
|
22448
|
+
"--cookie"
|
|
22449
|
+
]);
|
|
22450
|
+
function bareToken(token) {
|
|
22451
|
+
return token.replace(/^[^A-Za-z0-9/.-]+/, "").replace(/[^A-Za-z0-9/._-]+$/, "").toLowerCase();
|
|
22452
|
+
}
|
|
22453
|
+
function isFetchVerbArgument(prefix) {
|
|
22454
|
+
const segment = prefix.split(/[;|&]/).pop() ?? "";
|
|
22455
|
+
const tokens = segment.trim().split(/\s+/).map(bareToken).filter((t) => t.length > 0);
|
|
22456
|
+
let afterVerb = -1;
|
|
22457
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
22458
|
+
if (tokens[i] === "git" && tokens[i + 1] === "clone")
|
|
22459
|
+
afterVerb = i + 2;
|
|
22460
|
+
else if (FETCH_VERB_TOKENS.has(tokens[i]))
|
|
22461
|
+
afterVerb = i + 1;
|
|
22462
|
+
}
|
|
22463
|
+
if (afterVerb < 0)
|
|
22464
|
+
return false;
|
|
22465
|
+
for (let i = afterVerb; i < tokens.length; i++) {
|
|
22466
|
+
const token = tokens[i];
|
|
22467
|
+
if (!safeRegexCheck(FLAG_TOKEN, token))
|
|
22468
|
+
return false;
|
|
22469
|
+
if (VALUE_TAKING_FLAGS.has(token) && i + 1 < tokens.length)
|
|
22470
|
+
i++;
|
|
22471
|
+
}
|
|
22472
|
+
return true;
|
|
22473
|
+
}
|
|
22474
|
+
function isActualFetchTarget(lineContent, url2) {
|
|
22475
|
+
for (let from = 0; ; ) {
|
|
22476
|
+
const urlIndex = lineContent.indexOf(url2, from);
|
|
22477
|
+
if (urlIndex < 0)
|
|
22478
|
+
return false;
|
|
22479
|
+
if (isFetchVerbArgument(lineContent.slice(0, urlIndex)))
|
|
22480
|
+
return true;
|
|
22481
|
+
from = urlIndex + 1;
|
|
22482
|
+
}
|
|
22483
|
+
}
|
|
22484
|
+
var CHMOD_TARGET2 = /\bchmod\s+(?:-[A-Za-z]+\s+)?(?:[0-7]{3,4}|[ugoa]*(?:[+\-=][rwxXstugo]+(?:,[ugoa]*[+\-=][rwxXstugo]*)*)+)\s+(\S+)/i;
|
|
22485
|
+
var DIRECT_EXEC_TARGET = /\b(?:sudo\s+)?(?:(?:ba|z|da)?sh|python[23]?|node|ruby|perl|php|fish|bun|deno)\s+(\.{0,2}\/?[\w-]+(?:[./][\w-]+)*)/i;
|
|
22486
|
+
var DOT_SLASH_EXEC_TARGET = /(?:^|[\s;&|])\.\/([\w-]+(?:[./][\w-]+)*)/;
|
|
22487
|
+
var SOURCE_TARGET = /(?:^|[;&|]\s*)source\s+(\.{0,2}\/?[\w-]+(?:[./][\w-]+)+)/i;
|
|
22488
|
+
var EXEC_TARGET_PATTERNS = [
|
|
22489
|
+
CHMOD_TARGET2,
|
|
22490
|
+
DIRECT_EXEC_TARGET,
|
|
22491
|
+
DOT_SLASH_EXEC_TARGET,
|
|
22492
|
+
SOURCE_TARGET
|
|
22493
|
+
];
|
|
22494
|
+
function collectExecTargetPaths(lines) {
|
|
22495
|
+
const paths = [];
|
|
22496
|
+
for (const line of lines) {
|
|
22497
|
+
for (const pattern of EXEC_TARGET_PATTERNS) {
|
|
22498
|
+
const m = safeRegexTest(pattern, line);
|
|
22499
|
+
if (!m?.[1])
|
|
22500
|
+
continue;
|
|
22501
|
+
const targetPath = m[1].replace(/['"]/g, "");
|
|
22502
|
+
if (correlationTargetBasename(targetPath).length >= 3)
|
|
22503
|
+
paths.push(targetPath);
|
|
22504
|
+
}
|
|
22505
|
+
}
|
|
22506
|
+
return paths;
|
|
22507
|
+
}
|
|
22508
|
+
function isExecutedElsewhereCorrelated(fetchLine, execTargetPaths) {
|
|
22509
|
+
return execTargetPaths.some((path27) => isCorrelatedWithFetchDestination(path27, [fetchLine]));
|
|
22510
|
+
}
|
|
22511
|
+
function scanPasteHostFetch(content, lineContexts) {
|
|
22512
|
+
const findings = [];
|
|
22513
|
+
const lines = content.split("\n");
|
|
22514
|
+
const contexts = lineContexts ?? analyzeMarkdownContext(content);
|
|
22515
|
+
const urls = extractUrls(content);
|
|
22516
|
+
const execTargetPaths = collectExecTargetPaths(lines);
|
|
22517
|
+
for (const { url: url2, line } of urls) {
|
|
22518
|
+
const tier = classifyPasteHostTier(url2);
|
|
22519
|
+
if (!tier)
|
|
22520
|
+
continue;
|
|
22521
|
+
const lineIndex = line - 1;
|
|
22522
|
+
const lineContent = lines[lineIndex] ?? "";
|
|
22523
|
+
if (!isActualFetchTarget(lineContent, url2))
|
|
22524
|
+
continue;
|
|
22525
|
+
const ctx = contexts[lineIndex];
|
|
22526
|
+
const inDocContext = ctx ? isDocumentationContext(ctx) : false;
|
|
22527
|
+
if (tier === "transient") {
|
|
22528
|
+
findings.push({
|
|
22529
|
+
type: "paste_host_fetch",
|
|
22530
|
+
severity: inDocContext ? "low" : "medium",
|
|
22531
|
+
message: `Ephemeral file-transfer host URL fetched (never standalone-critical by design): ${url2}`,
|
|
22532
|
+
location: lineContent.trim().slice(0, 100),
|
|
22533
|
+
lineNumber: line,
|
|
22534
|
+
category: "paste_host_fetch",
|
|
22535
|
+
inDocumentationContext: inDocContext,
|
|
22536
|
+
confidence: inDocContext ? "low" : "medium"
|
|
22537
|
+
});
|
|
22538
|
+
continue;
|
|
22539
|
+
}
|
|
22540
|
+
const executed = isDirectPipeToInterpreter(url2, lineContent) || safeRegexCheck(NPX_DIRECT_EXEC_PATTERN, lineContent) || isExecutedElsewhereCorrelated(lineContent, execTargetPaths);
|
|
22541
|
+
if (!executed)
|
|
22542
|
+
continue;
|
|
22543
|
+
findings.push({
|
|
22544
|
+
type: "paste_host_fetch",
|
|
22545
|
+
// Standalone-critical (execution evidence required, see above) — doc
|
|
22546
|
+
// context is the only downgrade, matching every other detector's
|
|
22547
|
+
// noise-reduction convention in this Wave.
|
|
22548
|
+
severity: inDocContext ? "low" : "critical",
|
|
22549
|
+
message: `Paste/snippet-host URL is the target of an execution instruction: ${url2}`,
|
|
22550
|
+
location: lineContent.trim().slice(0, 100),
|
|
22551
|
+
lineNumber: line,
|
|
22552
|
+
category: "paste_host_fetch",
|
|
22553
|
+
inDocumentationContext: inDocContext,
|
|
22554
|
+
confidence: inDocContext ? "low" : "high"
|
|
22555
|
+
});
|
|
22556
|
+
}
|
|
22557
|
+
return findings;
|
|
22558
|
+
}
|
|
22559
|
+
|
|
22560
|
+
// ../core/dist/src/security/scanner/SecurityScanner.encoding.js
|
|
22561
|
+
var MAX_ENCODED_CANDIDATE_BYTES = 2e5;
|
|
22562
|
+
var MAX_OVERSIZED_ADVISORIES = 8;
|
|
22563
|
+
var MAX_BASE64_CANDIDATES = 8;
|
|
22564
|
+
var MAX_DECODED_TOTAL_BYTES = 256e3;
|
|
22565
|
+
var BASE64_CANDIDATE = /[A-Za-z0-9+/]{120,}={0,2}/g;
|
|
22566
|
+
var DATA_URI_LOOKBACK = 60;
|
|
22567
|
+
var DATA_URI_PREFIX = /data:(?:image|font|audio)\//i;
|
|
22568
|
+
var CONTROL_CHAR = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/;
|
|
22569
|
+
var MIN_PRINTABLE_RATIO = 0.9;
|
|
22570
|
+
function tryDecodeBase64ToPlausibleText(candidate) {
|
|
22571
|
+
let binary;
|
|
22572
|
+
try {
|
|
22573
|
+
binary = atob(candidate);
|
|
22574
|
+
} catch {
|
|
22575
|
+
return null;
|
|
22576
|
+
}
|
|
22577
|
+
if (binary.length === 0)
|
|
22578
|
+
return null;
|
|
22579
|
+
if (btoa(binary).replace(/=+$/, "") !== candidate.replace(/=+$/, ""))
|
|
22580
|
+
return null;
|
|
22581
|
+
const bytes = new Uint8Array(binary.length);
|
|
22582
|
+
for (let i = 0; i < binary.length; i++)
|
|
22583
|
+
bytes[i] = binary.charCodeAt(i);
|
|
22584
|
+
let text;
|
|
22585
|
+
try {
|
|
22586
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
22587
|
+
} catch {
|
|
22588
|
+
return null;
|
|
22589
|
+
}
|
|
22590
|
+
if (text.length === 0)
|
|
22591
|
+
return null;
|
|
22592
|
+
let controlCount = 0;
|
|
22593
|
+
for (const ch of text) {
|
|
22594
|
+
if (CONTROL_CHAR.test(ch))
|
|
22595
|
+
controlCount++;
|
|
22596
|
+
}
|
|
22597
|
+
const printableRatio = (text.length - controlCount) / text.length;
|
|
22598
|
+
if (printableRatio < MIN_PRINTABLE_RATIO)
|
|
22599
|
+
return null;
|
|
22600
|
+
return text;
|
|
22601
|
+
}
|
|
22602
|
+
function scanEncodedPayload(content, lineContexts, rescan) {
|
|
22603
|
+
const findings = [];
|
|
22604
|
+
const lines = content.split("\n");
|
|
22605
|
+
const contexts = lineContexts ?? analyzeMarkdownContext(content);
|
|
22606
|
+
let candidatesProcessed = 0;
|
|
22607
|
+
let decodedTotalBytes = 0;
|
|
22608
|
+
let aggregateBudgetExhausted = false;
|
|
22609
|
+
let oversizedAdvisories = 0;
|
|
22610
|
+
lines.forEach((line, index) => {
|
|
22611
|
+
const lineNumber = index + 1;
|
|
22612
|
+
for (const match of line.matchAll(BASE64_CANDIDATE)) {
|
|
22613
|
+
const candidate = match[0];
|
|
22614
|
+
const start = match.index ?? 0;
|
|
22615
|
+
const before = line.slice(Math.max(0, start - DATA_URI_LOOKBACK), start);
|
|
22616
|
+
if (DATA_URI_PREFIX.test(before))
|
|
22617
|
+
continue;
|
|
22618
|
+
if (candidate.length > MAX_ENCODED_CANDIDATE_BYTES) {
|
|
22619
|
+
if (oversizedAdvisories >= MAX_OVERSIZED_ADVISORIES)
|
|
22620
|
+
continue;
|
|
22621
|
+
oversizedAdvisories++;
|
|
22622
|
+
const oversizeCtx = contexts[index];
|
|
22623
|
+
const oversizeInInlineCode = oversizeCtx?.isInlineCode && isWithinInlineCode(line, start);
|
|
22624
|
+
const oversizeInDocContext = oversizeCtx ? isDocumentationContext(oversizeCtx) || oversizeInInlineCode : false;
|
|
22625
|
+
findings.push({
|
|
22626
|
+
type: "encoded_payload",
|
|
22627
|
+
severity: "low",
|
|
22628
|
+
message: `Base64-encoded payload exceeds the ${MAX_ENCODED_CANDIDATE_BYTES}-byte per-candidate cap \u2014 oversized candidate, not decoded/rescanned (${candidate.length} chars)`,
|
|
22629
|
+
location: line.trim().slice(0, 100),
|
|
22630
|
+
lineNumber,
|
|
22631
|
+
category: "encoded_payload",
|
|
22632
|
+
inDocumentationContext: oversizeInDocContext,
|
|
22633
|
+
confidence: "low"
|
|
22634
|
+
});
|
|
22635
|
+
continue;
|
|
22636
|
+
}
|
|
22637
|
+
if (candidatesProcessed >= MAX_BASE64_CANDIDATES || aggregateBudgetExhausted)
|
|
22638
|
+
continue;
|
|
22639
|
+
candidatesProcessed++;
|
|
22640
|
+
const decoded = tryDecodeBase64ToPlausibleText(candidate);
|
|
22641
|
+
if (decoded === null)
|
|
22642
|
+
continue;
|
|
22643
|
+
const decodedByteLength = new TextEncoder().encode(decoded).length;
|
|
22644
|
+
if (decodedTotalBytes + decodedByteLength > MAX_DECODED_TOTAL_BYTES) {
|
|
22645
|
+
aggregateBudgetExhausted = true;
|
|
22646
|
+
continue;
|
|
22647
|
+
}
|
|
22648
|
+
decodedTotalBytes += decodedByteLength;
|
|
22649
|
+
const ctx = contexts[index];
|
|
22650
|
+
const inInlineCode = ctx?.isInlineCode && isWithinInlineCode(line, start);
|
|
22651
|
+
const inDocContext = ctx ? isDocumentationContext(ctx) || inInlineCode : false;
|
|
22652
|
+
findings.push({
|
|
22653
|
+
type: "encoded_payload",
|
|
22654
|
+
// Advisory-tier only (weights.ts: 1.2 / 0.04, the sensitive_path/
|
|
22655
|
+
// typosquat tier) — see module header for why this is deliberately
|
|
22656
|
+
// NOT the 2.0/0.40 tier the other three Wave 2 detectors use.
|
|
22657
|
+
severity: inDocContext ? "low" : "medium",
|
|
22658
|
+
message: `Base64-encoded payload decoded and rescanned (${candidate.length} chars)`,
|
|
22659
|
+
location: line.trim().slice(0, 100),
|
|
22660
|
+
lineNumber,
|
|
22661
|
+
category: "encoded_payload",
|
|
22662
|
+
inDocumentationContext: inDocContext,
|
|
22663
|
+
confidence: inDocContext ? "low" : "high"
|
|
22664
|
+
});
|
|
22665
|
+
for (const inner of rescan(decoded)) {
|
|
22666
|
+
findings.push({ ...inner, decodedFrom: lineNumber });
|
|
22667
|
+
}
|
|
22668
|
+
}
|
|
22669
|
+
});
|
|
22670
|
+
return findings;
|
|
22671
|
+
}
|
|
22672
|
+
|
|
22673
|
+
// ../core/dist/src/security/scanner/SecurityScanner.decoy.js
|
|
22674
|
+
var DECOY_WINDOW_LINES = 5;
|
|
22675
|
+
var DECOY_AUTHORITY_AFFIX_PROXIMITY_LINES = 0;
|
|
22676
|
+
var FETCH_VERBS = /* @__PURE__ */ new Set(["curl", "wget", "npx"]);
|
|
22677
|
+
var FLAG_TOKEN2 = /^-{1,2}[A-Za-z][\w-]*$/;
|
|
22678
|
+
var VALUE_TAKING_FLAGS2 = /* @__PURE__ */ new Set([
|
|
22679
|
+
"-o",
|
|
22680
|
+
"-x",
|
|
22681
|
+
"-h",
|
|
22682
|
+
"-d",
|
|
22683
|
+
"-a",
|
|
22684
|
+
"-e",
|
|
22685
|
+
"-u",
|
|
22686
|
+
"-b",
|
|
22687
|
+
"--output",
|
|
22688
|
+
"--request",
|
|
22689
|
+
"--header",
|
|
22690
|
+
"--data",
|
|
22691
|
+
"--data-raw",
|
|
22692
|
+
"--data-binary",
|
|
22693
|
+
"--data-urlencode",
|
|
22694
|
+
"--user-agent",
|
|
22695
|
+
"--referer",
|
|
22696
|
+
"--proxy",
|
|
22697
|
+
"--cookie"
|
|
22698
|
+
]);
|
|
22699
|
+
function isActualFetchTarget2(lineContent, url2) {
|
|
22700
|
+
const urlIndex = lineContent.indexOf(url2);
|
|
22701
|
+
if (urlIndex < 0)
|
|
22702
|
+
return false;
|
|
22703
|
+
const prefix = lineContent.slice(0, urlIndex);
|
|
22704
|
+
if (/[;|&]/.test(prefix))
|
|
22705
|
+
return false;
|
|
22706
|
+
const tokens = prefix.trim().split(/\s+/).filter((t) => t.length > 0);
|
|
22707
|
+
if (tokens.length === 0)
|
|
22708
|
+
return false;
|
|
22709
|
+
let i;
|
|
22710
|
+
if (tokens[0]?.toLowerCase() === "git" && tokens[1]?.toLowerCase() === "clone") {
|
|
22711
|
+
i = 2;
|
|
22712
|
+
} else if (FETCH_VERBS.has(tokens[0]?.toLowerCase() ?? "")) {
|
|
22713
|
+
i = 1;
|
|
22714
|
+
} else {
|
|
22715
|
+
return false;
|
|
22716
|
+
}
|
|
22717
|
+
for (; i < tokens.length; i++) {
|
|
22718
|
+
const token = tokens[i];
|
|
22719
|
+
if (!FLAG_TOKEN2.test(token))
|
|
22720
|
+
return false;
|
|
22721
|
+
if (VALUE_TAKING_FLAGS2.has(token.toLowerCase()) && i + 1 < tokens.length) {
|
|
22722
|
+
i++;
|
|
22723
|
+
}
|
|
22724
|
+
}
|
|
22725
|
+
return true;
|
|
22726
|
+
}
|
|
22727
|
+
var BRAND_CANONICAL_DOMAINS = {
|
|
22728
|
+
anthropic: ["anthropic.com", "claude.ai"],
|
|
22729
|
+
claude: ["anthropic.com", "claude.ai"],
|
|
22730
|
+
gemini: ["google.com", "ai.google.dev", "deepmind.google"],
|
|
22731
|
+
copilot: ["github.com", "microsoft.com"],
|
|
22732
|
+
vercel: ["vercel.com"],
|
|
22733
|
+
salesforce: ["salesforce.com"]
|
|
22734
|
+
};
|
|
22735
|
+
function tokenize(raw) {
|
|
22736
|
+
return raw.toLowerCase().split(/[^a-z0-9]+/i).filter((t) => t.length > 0);
|
|
22737
|
+
}
|
|
22738
|
+
function matchesDomainSet(hostname7, domains) {
|
|
22739
|
+
return domains.some((d) => hostname7 === d || hostname7.endsWith("." + d));
|
|
22740
|
+
}
|
|
22741
|
+
function findVendorClaimInWindow(lines, lineIndex) {
|
|
22742
|
+
const start = Math.max(0, lineIndex - DECOY_WINDOW_LINES);
|
|
22743
|
+
const end = Math.min(lines.length - 1, lineIndex + DECOY_WINDOW_LINES);
|
|
22744
|
+
let brandToken = null;
|
|
22745
|
+
let brandLineIndex = -1;
|
|
22746
|
+
for (let i = start; i <= end && !brandToken; i++) {
|
|
22747
|
+
for (const token of tokenize(lines[i] ?? "")) {
|
|
22748
|
+
if (Object.prototype.hasOwnProperty.call(BRAND_ALIASES, token)) {
|
|
22749
|
+
brandToken = token;
|
|
22750
|
+
brandLineIndex = i;
|
|
22751
|
+
break;
|
|
22752
|
+
}
|
|
22753
|
+
}
|
|
22754
|
+
}
|
|
22755
|
+
if (!brandToken)
|
|
22756
|
+
return null;
|
|
22757
|
+
const affixStart = Math.max(0, brandLineIndex - DECOY_AUTHORITY_AFFIX_PROXIMITY_LINES);
|
|
22758
|
+
const affixEnd = Math.min(lines.length - 1, brandLineIndex + DECOY_AUTHORITY_AFFIX_PROXIMITY_LINES);
|
|
22759
|
+
const hasAuthorityAffix = tokenize(lines.slice(affixStart, affixEnd + 1).join(" ")).some((t) => AUTHORITY_CLAIMING_AFFIXES.has(t));
|
|
22760
|
+
return { brandToken, hasAuthorityAffix };
|
|
22761
|
+
}
|
|
22762
|
+
function scanDecoyMisdirection(content, lineContexts) {
|
|
22763
|
+
const findings = [];
|
|
22764
|
+
const lines = content.split("\n");
|
|
22765
|
+
const contexts = lineContexts ?? analyzeMarkdownContext(content);
|
|
22766
|
+
const urls = extractUrls(content);
|
|
22767
|
+
for (const { url: url2, line } of urls) {
|
|
22768
|
+
const lineIndex = line - 1;
|
|
22769
|
+
const lineContent = lines[lineIndex] ?? "";
|
|
22770
|
+
if (!isActualFetchTarget2(lineContent, url2))
|
|
22771
|
+
continue;
|
|
22772
|
+
let hostname7;
|
|
22773
|
+
try {
|
|
22774
|
+
hostname7 = new URL(url2).hostname.toLowerCase();
|
|
22775
|
+
} catch {
|
|
22776
|
+
continue;
|
|
22777
|
+
}
|
|
22778
|
+
const claim = findVendorClaimInWindow(lines, lineIndex);
|
|
22779
|
+
if (!claim)
|
|
22780
|
+
continue;
|
|
22781
|
+
const canonicalDomains = BRAND_CANONICAL_DOMAINS[claim.brandToken] ?? [];
|
|
22782
|
+
if (matchesDomainSet(hostname7, canonicalDomains))
|
|
22783
|
+
continue;
|
|
22784
|
+
if (matchesDomainSet(hostname7, DEFAULT_ALLOWED_DOMAINS))
|
|
22785
|
+
continue;
|
|
22786
|
+
const ctx = contexts[lineIndex];
|
|
22787
|
+
const inDocContext = ctx ? isDocumentationContext(ctx) : false;
|
|
22788
|
+
findings.push({
|
|
22789
|
+
type: "decoy_misdirection",
|
|
22790
|
+
// Never high/critical — an approximate NL heuristic by construction
|
|
22791
|
+
// (plan §9 reconciliation table); the escalation-into-code_execution
|
|
22792
|
+
// co-signal mechanism lives in a separate dispatch.
|
|
22793
|
+
severity: inDocContext ? "low" : "medium",
|
|
22794
|
+
message: `Fetch target domain ("${hostname7}") does not match the "${claim.brandToken}" vendor claimed nearby in the skill's own prose: ${url2}`,
|
|
22795
|
+
location: lineContent.trim().slice(0, 100),
|
|
22796
|
+
lineNumber: line,
|
|
22797
|
+
category: "decoy_misdirection",
|
|
22798
|
+
inDocumentationContext: inDocContext,
|
|
22799
|
+
confidence: inDocContext ? "low" : claim.hasAuthorityAffix ? "high" : "medium"
|
|
22800
|
+
});
|
|
22801
|
+
}
|
|
22802
|
+
return findings;
|
|
22803
|
+
}
|
|
22804
|
+
|
|
21953
22805
|
// ../core/dist/src/security/scanner/SecurityScanner.js
|
|
21954
22806
|
var SecurityScanner = class {
|
|
21955
22807
|
allowedDomains;
|
|
@@ -21962,18 +22814,6 @@ var SecurityScanner = class {
|
|
|
21962
22814
|
this.maxContentLength = options.maxContentLength ?? 1e6;
|
|
21963
22815
|
this.riskThreshold = options.riskThreshold ?? 40;
|
|
21964
22816
|
}
|
|
21965
|
-
extractUrls(content) {
|
|
21966
|
-
const urlPattern = /https?:\/\/[^\s<>"')\]]+/gi;
|
|
21967
|
-
const lines = content.split("\n");
|
|
21968
|
-
const results = [];
|
|
21969
|
-
lines.forEach((line, index) => {
|
|
21970
|
-
let match;
|
|
21971
|
-
while ((match = urlPattern.exec(line)) !== null) {
|
|
21972
|
-
results.push({ url: match[0], line: index + 1 });
|
|
21973
|
-
}
|
|
21974
|
-
});
|
|
21975
|
-
return results;
|
|
21976
|
-
}
|
|
21977
22817
|
isAllowedDomain(url2) {
|
|
21978
22818
|
try {
|
|
21979
22819
|
const parsed = new URL(url2);
|
|
@@ -21985,7 +22825,7 @@ var SecurityScanner = class {
|
|
|
21985
22825
|
}
|
|
21986
22826
|
scanUrls(content) {
|
|
21987
22827
|
const findings = [];
|
|
21988
|
-
const urls =
|
|
22828
|
+
const urls = extractUrls(content);
|
|
21989
22829
|
for (const { url: url2, line } of urls) {
|
|
21990
22830
|
if (!this.isAllowedDomain(url2)) {
|
|
21991
22831
|
findings.push({
|
|
@@ -22066,25 +22906,25 @@ var SecurityScanner = class {
|
|
|
22066
22906
|
}
|
|
22067
22907
|
/** @deprecated Use standalone calculateRiskScore function for new code */
|
|
22068
22908
|
calculateRiskScore = calculateRiskScore;
|
|
22069
|
-
|
|
22070
|
-
|
|
22909
|
+
/**
|
|
22910
|
+
* Run every content-scanning detector against `content` and return the
|
|
22911
|
+
* combined findings. Factored out of `scan()` (SMI-6033 Wave 2, Gap 2) so
|
|
22912
|
+
* the encoded-payload detector's recursive rescan of DECODED content can
|
|
22913
|
+
* reuse the exact same detector suite instead of a parallel, narrower
|
|
22914
|
+
* reimplementation.
|
|
22915
|
+
*
|
|
22916
|
+
* `skipEncodedPayload` is the STRUCTURAL depth-1 recursion guarantee: the
|
|
22917
|
+
* recursive callback passed to `scanEncodedPayload` below always calls this
|
|
22918
|
+
* method with `skipEncodedPayload: true`, so a base64 blob discovered
|
|
22919
|
+
* INSIDE already-decoded content can never itself be decoded — the inner
|
|
22920
|
+
* call cannot reach `scanEncodedPayload` again no matter what the decoded
|
|
22921
|
+
* text contains. This disables ONLY the encoded-payload detector on the
|
|
22922
|
+
* inner call, not the rest of the suite — a decoded `curl|bash` still
|
|
22923
|
+
* trips `code_execution`, decoded secrets still trip `sensitive_path`, etc.
|
|
22924
|
+
*/
|
|
22925
|
+
runDetectors(content, lineContexts, skipEncodedPayload, isHighTrustAuthor = false, isMarkdown = true) {
|
|
22071
22926
|
const findings = [];
|
|
22072
|
-
const lineContexts = analyzeMarkdownContext(content);
|
|
22073
|
-
if (content.length > this.maxContentLength) {
|
|
22074
|
-
findings.push({
|
|
22075
|
-
type: "suspicious_pattern",
|
|
22076
|
-
severity: "low",
|
|
22077
|
-
message: `Content exceeds maximum length (${this.maxContentLength} code units)`
|
|
22078
|
-
});
|
|
22079
|
-
}
|
|
22080
22927
|
const effectiveMultilineLimit = Math.min(MAX_CONTENT_LENGTH_FOR_REGEX, this.maxContentLength);
|
|
22081
|
-
if (content.length > effectiveMultilineLimit) {
|
|
22082
|
-
findings.push({
|
|
22083
|
-
type: "suspicious_pattern",
|
|
22084
|
-
severity: "low",
|
|
22085
|
-
message: `Multiline regex scan truncated at ${effectiveMultilineLimit} code units (content is ${content.length} code units; configured maxContentLength is ${this.maxContentLength} code units)`
|
|
22086
|
-
});
|
|
22087
|
-
}
|
|
22088
22928
|
findings.push(...this.scanUrls(content));
|
|
22089
22929
|
findings.push(...scanSensitivePaths(content, lineContexts));
|
|
22090
22930
|
findings.push(...this.scanJailbreakPatterns(content, lineContexts, effectiveMultilineLimit));
|
|
@@ -22095,6 +22935,10 @@ var SecurityScanner = class {
|
|
|
22095
22935
|
findings.push(...scanPrivilegeEscalation(content, lineContexts));
|
|
22096
22936
|
const privEscLines = new Set(findings.filter((f) => f.type === "privilege_escalation" && f.lineNumber).map((f) => f.lineNumber));
|
|
22097
22937
|
findings.push(...scanChmodFetchCompound(content, privEscLines, lineContexts));
|
|
22938
|
+
findings.push(...scanGatekeeperBypass(content, lineContexts, isHighTrustAuthor));
|
|
22939
|
+
findings.push(...scanArchiveEvasion(content, lineContexts));
|
|
22940
|
+
findings.push(...scanPasteHostFetch(content, lineContexts));
|
|
22941
|
+
findings.push(...scanDecoyMisdirection(content, lineContexts));
|
|
22098
22942
|
findings.push(...this.scanAIDefenceVulnerabilities(content, lineContexts, effectiveMultilineLimit));
|
|
22099
22943
|
findings.push(...scanSsrfPatterns(content, lineContexts, effectiveMultilineLimit));
|
|
22100
22944
|
findings.push(...scanPiiPatterns(content, lineContexts));
|
|
@@ -22102,6 +22946,50 @@ var SecurityScanner = class {
|
|
|
22102
22946
|
findings.push(...scanObfuscatedDirective(content));
|
|
22103
22947
|
escalateCodeExecution(findings);
|
|
22104
22948
|
escalateCorroboratedMentions(findings);
|
|
22949
|
+
if (!skipEncodedPayload) {
|
|
22950
|
+
findings.push(...scanEncodedPayload(content, lineContexts, (decodedContent) => this.runDetectors(decodedContent, analyzeMarkdownContext(decodedContent, isMarkdown), true, isHighTrustAuthor, isMarkdown)));
|
|
22951
|
+
}
|
|
22952
|
+
return findings;
|
|
22953
|
+
}
|
|
22954
|
+
/**
|
|
22955
|
+
* SMI-6033 Wave 3 (Gap 5): `isHighTrustAuthor` (default `false`) is the
|
|
22956
|
+
* Gatekeeper-bypass trust-tier carve-out — see `scanGatekeeperBypass`'s own
|
|
22957
|
+
* header (`SecurityScanner.compound.ts`) for the full policy. No in-repo
|
|
22958
|
+
* caller of this method currently has a verified author signal to pass
|
|
22959
|
+
* here (the indexer scans via the edge twin, not this core class); the
|
|
22960
|
+
* parameter exists so a future verified-author caller can opt in, and so
|
|
22961
|
+
* every existing call site (skill_validate, skill_rescan,
|
|
22962
|
+
* bundled-sibling-scan, skill-installation.*) defaults closed by
|
|
22963
|
+
* construction, not by convention.
|
|
22964
|
+
*
|
|
22965
|
+
* SMI-6033 Wave 2 (Gap 8) fix (2026-08-17): `isMarkdown` defaults `true`,
|
|
22966
|
+
* preserving byte-identical behavior for every existing caller. Pass
|
|
22967
|
+
* `false` when `content` is a real source file, not markdown — see
|
|
22968
|
+
* `analyzeMarkdownContext`'s own header (SecurityScanner.helpers.ts) for
|
|
22969
|
+
* why the markdown-only indented-code-block heuristic must never apply to
|
|
22970
|
+
* non-markdown content. `bundled-sibling-scan.ts`'s
|
|
22971
|
+
* `collectExecutableCodeFiles` candidates are the first real caller.
|
|
22972
|
+
*/
|
|
22973
|
+
scan(skillId, content, isHighTrustAuthor = false, isMarkdown = true) {
|
|
22974
|
+
const startTime = performance.now();
|
|
22975
|
+
const findings = [];
|
|
22976
|
+
const lineContexts = analyzeMarkdownContext(content, isMarkdown);
|
|
22977
|
+
if (content.length > this.maxContentLength) {
|
|
22978
|
+
findings.push({
|
|
22979
|
+
type: "suspicious_pattern",
|
|
22980
|
+
severity: "low",
|
|
22981
|
+
message: `Content exceeds maximum length (${this.maxContentLength} code units)`
|
|
22982
|
+
});
|
|
22983
|
+
}
|
|
22984
|
+
const effectiveMultilineLimit = Math.min(MAX_CONTENT_LENGTH_FOR_REGEX, this.maxContentLength);
|
|
22985
|
+
if (content.length > effectiveMultilineLimit) {
|
|
22986
|
+
findings.push({
|
|
22987
|
+
type: "suspicious_pattern",
|
|
22988
|
+
severity: "low",
|
|
22989
|
+
message: `Multiline regex scan truncated at ${effectiveMultilineLimit} code units (content is ${content.length} code units; configured maxContentLength is ${this.maxContentLength} code units)`
|
|
22990
|
+
});
|
|
22991
|
+
}
|
|
22992
|
+
findings.push(...this.runDetectors(content, lineContexts, false, isHighTrustAuthor, isMarkdown));
|
|
22105
22993
|
const endTime = performance.now();
|
|
22106
22994
|
const { total: riskScore, breakdown: riskBreakdown } = calculateRiskScore(findings);
|
|
22107
22995
|
const hasCritical = findings.some((f) => f.severity === "critical");
|
|
@@ -24429,10 +25317,10 @@ function isBetterSqlite3Available() {
|
|
|
24429
25317
|
|
|
24430
25318
|
// ../core/dist/src/db/drivers/sqljsDriver.js
|
|
24431
25319
|
import { createRequire as createRequire2 } from "node:module";
|
|
24432
|
-
import { existsSync as
|
|
25320
|
+
import { existsSync as existsSync14, readFileSync as readFileSync11, writeFileSync as writeFileSync11, renameSync as renameSync3, unlinkSync as unlinkSync3 } from "node:fs";
|
|
24433
25321
|
|
|
24434
25322
|
// ../core/dist/src/db/drivers/corruption.js
|
|
24435
|
-
import { existsSync as
|
|
25323
|
+
import { existsSync as existsSync13, renameSync as renameSync2 } from "node:fs";
|
|
24436
25324
|
var CORRUPTION_MARKERS = [
|
|
24437
25325
|
"sqlite_corrupt",
|
|
24438
25326
|
"malformed",
|
|
@@ -24448,7 +25336,7 @@ function backupCorruptDbFile(path27) {
|
|
|
24448
25336
|
if (path27 === ":memory:") {
|
|
24449
25337
|
throw new Error("[Skillsmith] backupCorruptDbFile: cannot back up an in-memory database");
|
|
24450
25338
|
}
|
|
24451
|
-
if (!
|
|
25339
|
+
if (!existsSync13(path27)) {
|
|
24452
25340
|
throw new Error(`[Skillsmith] backupCorruptDbFile: file does not exist: ${path27}`);
|
|
24453
25341
|
}
|
|
24454
25342
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
@@ -24666,11 +25554,11 @@ var SqlJsDatabaseAdapter = class {
|
|
|
24666
25554
|
const data = this.db.export();
|
|
24667
25555
|
const tmpPath = `${this.filePath}.tmp`;
|
|
24668
25556
|
try {
|
|
24669
|
-
|
|
25557
|
+
writeFileSync11(tmpPath, Buffer.from(data));
|
|
24670
25558
|
renameSync3(tmpPath, this.filePath);
|
|
24671
25559
|
} catch (error46) {
|
|
24672
25560
|
try {
|
|
24673
|
-
if (
|
|
25561
|
+
if (existsSync14(tmpPath))
|
|
24674
25562
|
unlinkSync3(tmpPath);
|
|
24675
25563
|
} catch {
|
|
24676
25564
|
}
|
|
@@ -24707,8 +25595,8 @@ var SqlJsDatabaseAdapter = class {
|
|
|
24707
25595
|
async function createSqlJsDatabase(path27 = ":memory:", options) {
|
|
24708
25596
|
const SQL = await loadSqlJs();
|
|
24709
25597
|
let data;
|
|
24710
|
-
if (path27 !== ":memory:" &&
|
|
24711
|
-
data =
|
|
25598
|
+
if (path27 !== ":memory:" && existsSync14(path27)) {
|
|
25599
|
+
data = readFileSync11(path27);
|
|
24712
25600
|
} else if (path27 !== ":memory:" && options?.fileMustExist) {
|
|
24713
25601
|
throw new Error(`SQLITE_CANTOPEN: unable to open database file: ${path27}`);
|
|
24714
25602
|
}
|
|
@@ -24728,7 +25616,7 @@ async function createSqlJsDatabase(path27 = ":memory:", options) {
|
|
|
24728
25616
|
} catch {
|
|
24729
25617
|
}
|
|
24730
25618
|
}
|
|
24731
|
-
if (!isCorruptionError(error46) || path27 === ":memory:" || !
|
|
25619
|
+
if (!isCorruptionError(error46) || path27 === ":memory:" || !existsSync14(path27)) {
|
|
24732
25620
|
throw error46;
|
|
24733
25621
|
}
|
|
24734
25622
|
const backupPath = backupCorruptDbFile(path27);
|
|
@@ -24892,7 +25780,7 @@ function findSimilarBruteForceFromMap(embeddings, queryEmbedding, topK) {
|
|
|
24892
25780
|
}
|
|
24893
25781
|
|
|
24894
25782
|
// ../core/dist/src/embeddings/hnsw-search.js
|
|
24895
|
-
import { existsSync as
|
|
25783
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync8, readFileSync as readFileSync12, renameSync as renameSync4, unlinkSync as unlinkSync4, writeFileSync as writeFileSync12 } from "fs";
|
|
24896
25784
|
import { dirname as dirname13, join as join22 } from "path";
|
|
24897
25785
|
var cachedCtor = null;
|
|
24898
25786
|
async function loadHnswCtor() {
|
|
@@ -24928,10 +25816,10 @@ function cachePaths(modelName) {
|
|
|
24928
25816
|
};
|
|
24929
25817
|
}
|
|
24930
25818
|
function readMeta(metaPath) {
|
|
24931
|
-
if (!
|
|
25819
|
+
if (!existsSync15(metaPath))
|
|
24932
25820
|
return null;
|
|
24933
25821
|
try {
|
|
24934
|
-
const parsed = JSON.parse(
|
|
25822
|
+
const parsed = JSON.parse(readFileSync12(metaPath, "utf-8"));
|
|
24935
25823
|
if (parsed.version !== 1)
|
|
24936
25824
|
return null;
|
|
24937
25825
|
return parsed;
|
|
@@ -24940,10 +25828,10 @@ function readMeta(metaPath) {
|
|
|
24940
25828
|
}
|
|
24941
25829
|
}
|
|
24942
25830
|
function readLabels(labelsPath) {
|
|
24943
|
-
if (!
|
|
25831
|
+
if (!existsSync15(labelsPath))
|
|
24944
25832
|
return null;
|
|
24945
25833
|
try {
|
|
24946
|
-
const parsed = JSON.parse(
|
|
25834
|
+
const parsed = JSON.parse(readFileSync12(labelsPath, "utf-8"));
|
|
24947
25835
|
if (!Array.isArray(parsed))
|
|
24948
25836
|
return null;
|
|
24949
25837
|
return parsed;
|
|
@@ -24953,7 +25841,7 @@ function readLabels(labelsPath) {
|
|
|
24953
25841
|
}
|
|
24954
25842
|
function writeAtomic(tmp, final, contents) {
|
|
24955
25843
|
mkdirSync8(dirname13(tmp), { recursive: true });
|
|
24956
|
-
|
|
25844
|
+
writeFileSync12(tmp, contents, typeof contents === "string" ? { encoding: "utf-8" } : void 0);
|
|
24957
25845
|
renameSync4(tmp, final);
|
|
24958
25846
|
}
|
|
24959
25847
|
async function loadOrBuildHnsw(args) {
|
|
@@ -24972,7 +25860,7 @@ async function loadOrBuildHnsw(args) {
|
|
|
24972
25860
|
const efConstruction = args.efConstruction ?? 400;
|
|
24973
25861
|
const efSearch = args.efSearch ?? 200;
|
|
24974
25862
|
const capacity = Math.max(args.maxElements ?? Math.max(count * 2, 1024), 1024);
|
|
24975
|
-
const reusable = meta3 !== null && labels !== null && meta3.modelName === args.modelName && meta3.dim === args.dim && meta3.count === count &&
|
|
25863
|
+
const reusable = meta3 !== null && labels !== null && meta3.modelName === args.modelName && meta3.dim === args.dim && meta3.count === count && existsSync15(paths.bin);
|
|
24976
25864
|
let index;
|
|
24977
25865
|
let labelToId;
|
|
24978
25866
|
let idToLabel;
|
|
@@ -24987,11 +25875,11 @@ async function loadOrBuildHnsw(args) {
|
|
|
24987
25875
|
nextLabel = labels.reduce((max, [label]) => Math.max(max, label), -1) + 1;
|
|
24988
25876
|
} catch (err) {
|
|
24989
25877
|
try {
|
|
24990
|
-
if (
|
|
25878
|
+
if (existsSync15(paths.bin))
|
|
24991
25879
|
unlinkSync4(paths.bin);
|
|
24992
|
-
if (
|
|
25880
|
+
if (existsSync15(paths.meta))
|
|
24993
25881
|
unlinkSync4(paths.meta);
|
|
24994
|
-
if (
|
|
25882
|
+
if (existsSync15(paths.labels))
|
|
24995
25883
|
unlinkSync4(paths.labels);
|
|
24996
25884
|
} catch {
|
|
24997
25885
|
}
|
|
@@ -25051,7 +25939,7 @@ function createHandle(args) {
|
|
|
25051
25939
|
clearTimeout(timer);
|
|
25052
25940
|
timer = null;
|
|
25053
25941
|
}
|
|
25054
|
-
if (!dirty &&
|
|
25942
|
+
if (!dirty && existsSync15(args.paths.bin) && existsSync15(args.paths.meta)) {
|
|
25055
25943
|
return;
|
|
25056
25944
|
}
|
|
25057
25945
|
args.index.writeIndexSync(args.paths.binTmp);
|
|
@@ -26497,7 +27385,7 @@ function redactSensitiveObject(obj, seen = /* @__PURE__ */ new WeakSet()) {
|
|
|
26497
27385
|
}
|
|
26498
27386
|
|
|
26499
27387
|
// ../core/dist/src/logging/rotation.js
|
|
26500
|
-
import { createWriteStream, existsSync as
|
|
27388
|
+
import { createWriteStream, existsSync as existsSync16, statSync as statSync2 } from "node:fs";
|
|
26501
27389
|
import { mkdir as mkdir2, readdir as readdir2, stat, unlink as unlink2 } from "node:fs/promises";
|
|
26502
27390
|
import { homedir as homedir10 } from "node:os";
|
|
26503
27391
|
import { join as join23 } from "node:path";
|
|
@@ -26517,7 +27405,7 @@ function dailyFilePath(surface, date5) {
|
|
|
26517
27405
|
function nextRolledFilePath(surface, date5) {
|
|
26518
27406
|
const base = dailyFilePath(surface, date5);
|
|
26519
27407
|
let n = 1;
|
|
26520
|
-
while (
|
|
27408
|
+
while (existsSync16(`${base}.${n}`))
|
|
26521
27409
|
n++;
|
|
26522
27410
|
return `${base}.${n}`;
|
|
26523
27411
|
}
|
|
@@ -26618,7 +27506,7 @@ function writeLogLine(surface, line) {
|
|
|
26618
27506
|
async function pruneExpiredLogs() {
|
|
26619
27507
|
const dir = getLogDir();
|
|
26620
27508
|
try {
|
|
26621
|
-
if (!
|
|
27509
|
+
if (!existsSync16(dir))
|
|
26622
27510
|
return;
|
|
26623
27511
|
const entries = await readdir2(dir);
|
|
26624
27512
|
const cutoff = Date.now() - RETENTION_DAYS * 24 * 60 * 60 * 1e3;
|
|
@@ -27015,7 +27903,7 @@ async function buildInventoryPayload(opts) {
|
|
|
27015
27903
|
// ../core/dist/src/config/token-credentials.js
|
|
27016
27904
|
import { homedir as homedir11 } from "os";
|
|
27017
27905
|
import { join as join25 } from "path";
|
|
27018
|
-
import { existsSync as
|
|
27906
|
+
import { existsSync as existsSync17, readFileSync as readFileSync13, writeFileSync as writeFileSync13, chmodSync as chmodSync6 } from "fs";
|
|
27019
27907
|
|
|
27020
27908
|
// ../core/dist/src/api/utils.js
|
|
27021
27909
|
function calculateBackoff(attempt, baseDelay = 1e3) {
|
|
@@ -27048,10 +27936,10 @@ function getConfigPath2() {
|
|
|
27048
27936
|
}
|
|
27049
27937
|
function readConfigFile() {
|
|
27050
27938
|
const p = getConfigPath2();
|
|
27051
|
-
if (!
|
|
27939
|
+
if (!existsSync17(p))
|
|
27052
27940
|
return {};
|
|
27053
27941
|
try {
|
|
27054
|
-
return JSON.parse(
|
|
27942
|
+
return JSON.parse(readFileSync13(p, "utf-8"));
|
|
27055
27943
|
} catch {
|
|
27056
27944
|
return {};
|
|
27057
27945
|
}
|
|
@@ -27059,7 +27947,7 @@ function readConfigFile() {
|
|
|
27059
27947
|
function writeConfigFile(data) {
|
|
27060
27948
|
ensureConfigDir();
|
|
27061
27949
|
const p = getConfigPath2();
|
|
27062
|
-
|
|
27950
|
+
writeFileSync13(p, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
27063
27951
|
try {
|
|
27064
27952
|
chmodSync6(p, 384);
|
|
27065
27953
|
} catch {
|
|
@@ -27118,6 +28006,46 @@ async function loadCredentials() {
|
|
|
27118
28006
|
version: 2
|
|
27119
28007
|
};
|
|
27120
28008
|
}
|
|
28009
|
+
async function clearCredentials() {
|
|
28010
|
+
const sources = [];
|
|
28011
|
+
let keyringError;
|
|
28012
|
+
const keytar = await getKeytar2();
|
|
28013
|
+
if (keytar) {
|
|
28014
|
+
try {
|
|
28015
|
+
const deleted = await keytar.deletePassword(KEYTAR_SERVICE2, KEYTAR_ACCOUNT_REFRESH);
|
|
28016
|
+
if (deleted) {
|
|
28017
|
+
sources.push("keyring");
|
|
28018
|
+
}
|
|
28019
|
+
} catch (err) {
|
|
28020
|
+
keyringError = err instanceof Error ? err.message : String(err);
|
|
28021
|
+
}
|
|
28022
|
+
}
|
|
28023
|
+
const configPath2 = getConfigPath2();
|
|
28024
|
+
ensureConfigDir();
|
|
28025
|
+
const release = acquireConfigLock(configPath2);
|
|
28026
|
+
try {
|
|
28027
|
+
const existing = readConfigFile();
|
|
28028
|
+
delete existing.accessToken;
|
|
28029
|
+
delete existing.refreshToken;
|
|
28030
|
+
delete existing.expiresAt;
|
|
28031
|
+
delete existing.version;
|
|
28032
|
+
atomicWriteFile(configPath2, JSON.stringify(existing, null, 2), 384);
|
|
28033
|
+
} finally {
|
|
28034
|
+
release();
|
|
28035
|
+
}
|
|
28036
|
+
sources.push("config file");
|
|
28037
|
+
if (keyringError) {
|
|
28038
|
+
return {
|
|
28039
|
+
success: false,
|
|
28040
|
+
source: sources.join(" and "),
|
|
28041
|
+
error: keyringError
|
|
28042
|
+
};
|
|
28043
|
+
}
|
|
28044
|
+
return {
|
|
28045
|
+
success: true,
|
|
28046
|
+
source: sources.join(" and ")
|
|
28047
|
+
};
|
|
28048
|
+
}
|
|
27121
28049
|
async function refreshAccessToken(refreshToken) {
|
|
27122
28050
|
try {
|
|
27123
28051
|
const resp = await fetch(`${SUPABASE_AUTH_URL}/token?grant_type=refresh_token`, {
|
|
@@ -27726,7 +28654,7 @@ function addInferredMcp(inferred, declaredMcpServers, result) {
|
|
|
27726
28654
|
|
|
27727
28655
|
// ../core/dist/src/services/skill-installation.helpers.js
|
|
27728
28656
|
import * as fs5 from "fs/promises";
|
|
27729
|
-
import { existsSync as
|
|
28657
|
+
import { existsSync as existsSync18, readFileSync as readFileSync14 } from "fs";
|
|
27730
28658
|
import * as path4 from "path";
|
|
27731
28659
|
import { createHash as createHash7 } from "crypto";
|
|
27732
28660
|
|
|
@@ -28159,10 +29087,10 @@ async function ensureDirNoFollow(dirPath) {
|
|
|
28159
29087
|
}
|
|
28160
29088
|
}
|
|
28161
29089
|
async function mkdirNoFollow(baseDir, dir) {
|
|
28162
|
-
const
|
|
28163
|
-
if (
|
|
29090
|
+
const relative10 = path3.relative(baseDir, dir);
|
|
29091
|
+
if (relative10 === "" || relative10.startsWith(".."))
|
|
28164
29092
|
return;
|
|
28165
|
-
const segments =
|
|
29093
|
+
const segments = relative10.split(path3.sep);
|
|
28166
29094
|
let current = baseDir;
|
|
28167
29095
|
for (const segment of segments) {
|
|
28168
29096
|
current = path3.join(current, segment);
|
|
@@ -28298,10 +29226,10 @@ function generateTips(skillName, optimizationInfo, client = CANONICAL_CLIENT, sk
|
|
|
28298
29226
|
}
|
|
28299
29227
|
function getRegisteredMcpServers(projectRoot = process.cwd()) {
|
|
28300
29228
|
const mcpJsonPath = path4.join(projectRoot, ".mcp.json");
|
|
28301
|
-
if (!
|
|
29229
|
+
if (!existsSync18(mcpJsonPath))
|
|
28302
29230
|
return void 0;
|
|
28303
29231
|
try {
|
|
28304
|
-
const raw =
|
|
29232
|
+
const raw = readFileSync14(mcpJsonPath, "utf-8");
|
|
28305
29233
|
const parsed = JSON.parse(raw);
|
|
28306
29234
|
if (!parsed || typeof parsed !== "object")
|
|
28307
29235
|
return void 0;
|
|
@@ -29282,7 +30210,7 @@ function buildEmptyStackGuidance() {
|
|
|
29282
30210
|
return "No technology stack could be derived for recommendations \u2014 this usually means a non-Node project, a stack with no production dependencies, or an unsupported language, not a backend or registry problem. Provide project context (a short description of the project or its tooling) or an explicit list of installed/currently-used skills, then try again.";
|
|
29283
30211
|
}
|
|
29284
30212
|
function getRecommendAutoDetectedFooterText() {
|
|
29285
|
-
return "auto-detected from your installed skills
|
|
30213
|
+
return "auto-detected from your installed skills";
|
|
29286
30214
|
}
|
|
29287
30215
|
|
|
29288
30216
|
// ../core/dist/src/services/context-words.js
|
|
@@ -31543,6 +32471,11 @@ var ApiSearchResultSchema = external_exports.object({
|
|
|
31543
32471
|
last_scanned_at: external_exports.string().nullable().optional(),
|
|
31544
32472
|
security_findings: external_exports.array(external_exports.unknown()).nullable().optional(),
|
|
31545
32473
|
quarantined: external_exports.boolean().optional(),
|
|
32474
|
+
// SMI-6033 Wave 2 (Gap 8): partial-scan coverage columns. Declared here so
|
|
32475
|
+
// Zod does not strip them before get-skill.ts / search.ts read them (same
|
|
32476
|
+
// reason as compatibility/license below).
|
|
32477
|
+
scan_coverage_incomplete: external_exports.boolean().optional(),
|
|
32478
|
+
scan_coverage_note: external_exports.string().nullable().optional(),
|
|
31546
32479
|
// SMI-5327: SPDX license surfaced by skills-get / skills-search. Declared here
|
|
31547
32480
|
// so Zod does not strip the field before get-skill.ts / search.ts read it
|
|
31548
32481
|
// (same reason as compatibility above). skills-search may omit it → optional.
|
|
@@ -32098,7 +33031,12 @@ function deriveSecuritySummaryFromApiSkill(apiSkill) {
|
|
|
32098
33031
|
passed: apiSkill.quarantined === true ? false : apiSkill.security_score == null ? null : true,
|
|
32099
33032
|
riskScore: apiSkill.security_score ?? null,
|
|
32100
33033
|
findingsCount: Array.isArray(apiSkill.security_findings) ? apiSkill.security_findings.length : 0,
|
|
32101
|
-
scannedAt: apiSkill.last_scanned_at
|
|
33034
|
+
scannedAt: apiSkill.last_scanned_at,
|
|
33035
|
+
// SMI-6033 Wave 2 (Gap 8): default to complete coverage when the column
|
|
33036
|
+
// is absent (older cached rows / pre-Wave-2 API responses) — never
|
|
33037
|
+
// fabricate an incomplete-scan caveat for data that predates this field.
|
|
33038
|
+
scanCoverageIncomplete: apiSkill.scan_coverage_incomplete ?? false,
|
|
33039
|
+
scanCoverageNote: apiSkill.scan_coverage_note ?? null
|
|
32102
33040
|
};
|
|
32103
33041
|
}
|
|
32104
33042
|
|
|
@@ -33159,11 +34097,11 @@ function defaultParseFrontmatter(content) {
|
|
|
33159
34097
|
const line = rawLine.trim();
|
|
33160
34098
|
if (!line || line.startsWith("#"))
|
|
33161
34099
|
continue;
|
|
33162
|
-
const
|
|
33163
|
-
if (
|
|
34100
|
+
const sep7 = line.indexOf(":");
|
|
34101
|
+
if (sep7 === -1)
|
|
33164
34102
|
continue;
|
|
33165
|
-
const key = line.slice(0,
|
|
33166
|
-
const value = line.slice(
|
|
34103
|
+
const key = line.slice(0, sep7).trim();
|
|
34104
|
+
const value = line.slice(sep7 + 1).trim();
|
|
33167
34105
|
assignKey(result, key, value);
|
|
33168
34106
|
}
|
|
33169
34107
|
return result;
|
|
@@ -33584,7 +34522,7 @@ var SourceRecoveryService = class {
|
|
|
33584
34522
|
};
|
|
33585
34523
|
|
|
33586
34524
|
// ../core/dist/src/provenance/backfill.js
|
|
33587
|
-
import { existsSync as
|
|
34525
|
+
import { existsSync as existsSync20 } from "fs";
|
|
33588
34526
|
import * as fs11 from "fs/promises";
|
|
33589
34527
|
import * as os4 from "os";
|
|
33590
34528
|
import * as path13 from "path";
|
|
@@ -33683,7 +34621,7 @@ function mergeEntry(existing, planned) {
|
|
|
33683
34621
|
};
|
|
33684
34622
|
}
|
|
33685
34623
|
async function maybeWriteFrontmatter(dir, sourceUrl) {
|
|
33686
|
-
if (
|
|
34624
|
+
if (existsSync20(path13.join(dir, ".git", "config")))
|
|
33687
34625
|
return false;
|
|
33688
34626
|
const skillMdPath = path13.join(dir, "SKILL.md");
|
|
33689
34627
|
let content;
|
|
@@ -33906,7 +34844,7 @@ async function probeEmbeddingCapability(opts = {}) {
|
|
|
33906
34844
|
}
|
|
33907
34845
|
|
|
33908
34846
|
// src/version.ts
|
|
33909
|
-
import { readFileSync as
|
|
34847
|
+
import { readFileSync as readFileSync18 } from "node:fs";
|
|
33910
34848
|
import { join as join39 } from "node:path";
|
|
33911
34849
|
|
|
33912
34850
|
// src/utils/package-root.ts
|
|
@@ -33920,7 +34858,7 @@ function packageRoot() {
|
|
|
33920
34858
|
function readVersion() {
|
|
33921
34859
|
try {
|
|
33922
34860
|
const pkgPath = join39(packageRoot(), "package.json");
|
|
33923
|
-
const pkg = JSON.parse(
|
|
34861
|
+
const pkg = JSON.parse(readFileSync18(pkgPath, "utf-8"));
|
|
33924
34862
|
return pkg.version ?? "0.0.0";
|
|
33925
34863
|
} catch {
|
|
33926
34864
|
return "0.0.0";
|
|
@@ -33938,7 +34876,7 @@ function getCliLogger() {
|
|
|
33938
34876
|
}
|
|
33939
34877
|
|
|
33940
34878
|
// src/utils/open-database.ts
|
|
33941
|
-
import { existsSync as
|
|
34879
|
+
import { existsSync as existsSync21 } from "node:fs";
|
|
33942
34880
|
var logger8 = getCliLogger();
|
|
33943
34881
|
async function openCliDatabase(path27, options) {
|
|
33944
34882
|
if (options?.readonly) {
|
|
@@ -33950,7 +34888,7 @@ async function openCliDatabase(path27, options) {
|
|
|
33950
34888
|
initializeSchema(db);
|
|
33951
34889
|
return db;
|
|
33952
34890
|
} catch (err) {
|
|
33953
|
-
if (!isCorruptionError(err) || path27 === ":memory:" || !
|
|
34891
|
+
if (!isCorruptionError(err) || path27 === ":memory:" || !existsSync21(path27)) {
|
|
33954
34892
|
throw err;
|
|
33955
34893
|
}
|
|
33956
34894
|
if (db) {
|
|
@@ -34772,10 +35710,19 @@ import { dirname as dirname20 } from "path";
|
|
|
34772
35710
|
// src/utils/skills-directory.ts
|
|
34773
35711
|
import { readdir as readdir6, readFile as readFile6, realpath as realpath3, stat as stat6 } from "fs/promises";
|
|
34774
35712
|
import { createHash as createHash8 } from "crypto";
|
|
34775
|
-
import { join as
|
|
35713
|
+
import { join as join41 } from "path";
|
|
35714
|
+
|
|
35715
|
+
// src/utils/local-skills-dir.ts
|
|
35716
|
+
import { join as join40, relative as relative7, sep as sep5 } from "path";
|
|
35717
|
+
var LOCAL_SKILLS_DIR_SEGMENTS = [".claude", "skills"];
|
|
34776
35718
|
function getLocalSkillsDir() {
|
|
34777
|
-
return join40(process.cwd(),
|
|
35719
|
+
return join40(process.cwd(), ...LOCAL_SKILLS_DIR_SEGMENTS);
|
|
34778
35720
|
}
|
|
35721
|
+
function getLocalSkillsDirDisplay() {
|
|
35722
|
+
return `./${relative7(process.cwd(), getLocalSkillsDir()).split(sep5).join("/")}`;
|
|
35723
|
+
}
|
|
35724
|
+
|
|
35725
|
+
// src/utils/skills-directory.ts
|
|
34779
35726
|
async function resolvesToDirectory2(entryPath, isDirectory, isSymbolicLink) {
|
|
34780
35727
|
if (isDirectory) return true;
|
|
34781
35728
|
if (!isSymbolicLink) return false;
|
|
@@ -34802,14 +35749,14 @@ async function getSkillsFromDirectory(skillsDir, dbPath, installedVia = CANONICA
|
|
|
34802
35749
|
const entries = await readdir6(skillsDir, { withFileTypes: true });
|
|
34803
35750
|
for (const entry of entries) {
|
|
34804
35751
|
if (entry.name.startsWith(".")) continue;
|
|
34805
|
-
const skillPath =
|
|
35752
|
+
const skillPath = join41(skillsDir, entry.name);
|
|
34806
35753
|
const isSkillDir = await resolvesToDirectory2(
|
|
34807
35754
|
skillPath,
|
|
34808
35755
|
entry.isDirectory(),
|
|
34809
35756
|
entry.isSymbolicLink?.() ?? false
|
|
34810
35757
|
);
|
|
34811
35758
|
if (isSkillDir) {
|
|
34812
|
-
const skillMdPath =
|
|
35759
|
+
const skillMdPath = join41(skillPath, "SKILL.md");
|
|
34813
35760
|
try {
|
|
34814
35761
|
const skillMdStat = await stat6(skillMdPath);
|
|
34815
35762
|
const content = await readFile6(skillMdPath, "utf-8");
|
|
@@ -34878,7 +35825,7 @@ async function safeRealpath2(p) {
|
|
|
34878
35825
|
}
|
|
34879
35826
|
async function readSkillMd(skillPath) {
|
|
34880
35827
|
try {
|
|
34881
|
-
const content = await readFile6(
|
|
35828
|
+
const content = await readFile6(join41(skillPath, "SKILL.md"), "utf-8");
|
|
34882
35829
|
const contentHash = createHash8("sha256").update(content, "utf8").digest("hex");
|
|
34883
35830
|
const parser2 = new SkillParser();
|
|
34884
35831
|
const parsed = parser2.parse(content);
|
|
@@ -34984,15 +35931,15 @@ async function getInstalledSkillsForClient(client, dbPath) {
|
|
|
34984
35931
|
import { confirm } from "@inquirer/prompts";
|
|
34985
35932
|
import ora3 from "ora";
|
|
34986
35933
|
import { readFile as readFile8 } from "fs/promises";
|
|
34987
|
-
import { basename as basename6, join as
|
|
35934
|
+
import { basename as basename6, join as join43 } from "path";
|
|
34988
35935
|
|
|
34989
35936
|
// src/utils/manifest.ts
|
|
34990
35937
|
import { createHash as createHash9, randomUUID as randomUUID7 } from "crypto";
|
|
34991
35938
|
import { readFile as readFile7, writeFile as writeFile4, mkdir as mkdir5, rename as rename3, unlink as unlink5 } from "fs/promises";
|
|
34992
|
-
import { join as
|
|
35939
|
+
import { join as join42, dirname as dirname19 } from "path";
|
|
34993
35940
|
import { homedir as homedir18 } from "os";
|
|
34994
|
-
var SKILLSMITH_DIR =
|
|
34995
|
-
var MANIFEST_PATH =
|
|
35941
|
+
var SKILLSMITH_DIR = join42(homedir18(), ".skillsmith");
|
|
35942
|
+
var MANIFEST_PATH = join42(SKILLSMITH_DIR, "manifest.json");
|
|
34996
35943
|
async function loadManifest2() {
|
|
34997
35944
|
try {
|
|
34998
35945
|
const content = await readFile7(MANIFEST_PATH, "utf-8");
|
|
@@ -35100,7 +36047,7 @@ var AUTO_APPLY_RECOVERY_CONFIDENCES = /* @__PURE__ */ new Set([
|
|
|
35100
36047
|
async function recoverConfidentSourceId(skillName, installed, db) {
|
|
35101
36048
|
let skillMd;
|
|
35102
36049
|
try {
|
|
35103
|
-
skillMd = await readFile8(
|
|
36050
|
+
skillMd = await readFile8(join43(installed.path, "SKILL.md"), "utf-8");
|
|
35104
36051
|
} catch {
|
|
35105
36052
|
skillMd = null;
|
|
35106
36053
|
}
|
|
@@ -35120,6 +36067,16 @@ async function recoverConfidentSourceId(skillName, installed, db) {
|
|
|
35120
36067
|
}
|
|
35121
36068
|
return result.recoveredSource?.url ?? result.registryId ?? null;
|
|
35122
36069
|
}
|
|
36070
|
+
async function readClaimedAuthor(installedPath) {
|
|
36071
|
+
try {
|
|
36072
|
+
const skillMd = await readFile8(join43(installedPath, "SKILL.md"), "utf-8");
|
|
36073
|
+
const parsed = new SkillParser().parse(skillMd);
|
|
36074
|
+
const author = parsed?.["author"];
|
|
36075
|
+
return typeof author === "string" && author.trim().length > 0 ? author.trim() : null;
|
|
36076
|
+
} catch {
|
|
36077
|
+
return null;
|
|
36078
|
+
}
|
|
36079
|
+
}
|
|
35123
36080
|
async function getSkillDiff(skillName, dbPath, client = CANONICAL_CLIENT) {
|
|
35124
36081
|
const installed = (await getInstalledSkillsForClient(client, dbPath)).find(
|
|
35125
36082
|
(s) => s.name.toLowerCase() === skillName.toLowerCase()
|
|
@@ -35131,9 +36088,13 @@ async function getSkillDiff(skillName, dbPath, client = CANONICAL_CLIENT) {
|
|
|
35131
36088
|
const skillRepo = new SkillRepository(db);
|
|
35132
36089
|
try {
|
|
35133
36090
|
const allSkills = skillRepo.findAll(1e3, 0);
|
|
35134
|
-
const
|
|
36091
|
+
const claimedAuthor = await readClaimedAuthor(installed.path);
|
|
36092
|
+
const nameMatches = allSkills.items.filter(
|
|
35135
36093
|
(s) => s.name.toLowerCase() === skillName.toLowerCase()
|
|
35136
36094
|
);
|
|
36095
|
+
const skill = claimedAuthor ? nameMatches.find(
|
|
36096
|
+
(s) => s.author && s.author.toLowerCase() === claimedAuthor.toLowerCase()
|
|
36097
|
+
) : void 0;
|
|
35137
36098
|
if (skill) {
|
|
35138
36099
|
const changes = [];
|
|
35139
36100
|
const skillWithVersion = skill;
|
|
@@ -35347,7 +36308,7 @@ function displaySkillsTable(skills, client = CANONICAL_CLIENT) {
|
|
|
35347
36308
|
console.log(
|
|
35348
36309
|
source_default.dim(
|
|
35349
36310
|
`
|
|
35350
|
-
${skills.length} skill(s) found (global: ${getInstallPath(client)}, local:
|
|
36311
|
+
${skills.length} skill(s) found (global: ${getInstallPath(client)}, local: ${getLocalSkillsDirDisplay()})
|
|
35351
36312
|
`
|
|
35352
36313
|
)
|
|
35353
36314
|
);
|
|
@@ -35550,7 +36511,7 @@ var InitSkillError = class _InitSkillError extends Error {
|
|
|
35550
36511
|
import { input as input2, confirm as confirm3, select as select2 } from "@inquirer/prompts";
|
|
35551
36512
|
import ora5 from "ora";
|
|
35552
36513
|
import { mkdir as mkdir9, writeFile as writeFile6, readFile as readFile9, stat as stat7, readdir as readdir7 } from "fs/promises";
|
|
35553
|
-
import { dirname as dirname21, join as
|
|
36514
|
+
import { dirname as dirname21, join as join45, resolve as resolve12 } from "path";
|
|
35554
36515
|
import { createHash as createHash10 } from "crypto";
|
|
35555
36516
|
|
|
35556
36517
|
// src/utils/skill-name.ts
|
|
@@ -35655,7 +36616,7 @@ function validateSubagentDefinition(content) {
|
|
|
35655
36616
|
|
|
35656
36617
|
// src/commands/author/init.helpers.ts
|
|
35657
36618
|
import { mkdir as mkdir8, writeFile as writeFile5, rm as rm4 } from "fs/promises";
|
|
35658
|
-
import { join as
|
|
36619
|
+
import { join as join44 } from "path";
|
|
35659
36620
|
|
|
35660
36621
|
// src/templates/skill.md.template.ts
|
|
35661
36622
|
var SKILL_MD_TEMPLATE = `---
|
|
@@ -36044,11 +37005,22 @@ var CLIENT_SNIPPETS = {
|
|
|
36044
37005
|
// SMI-5894 Wave 1 Step 7: SKILLSMITH_CLIENT tells the server to install
|
|
36045
37006
|
// to ~/.cursor/skills instead of the default ~/.claude/skills — without
|
|
36046
37007
|
// it, Cursor users silently get Claude Code's install path.
|
|
37008
|
+
//
|
|
37009
|
+
// SMI-5893 Wave 11 (GH#2368 C-01): `command` is a resolved-path
|
|
37010
|
+
// placeholder, not `npx` — Cursor's bundled Node cannot resolve `npx`
|
|
37011
|
+
// packages (a real ENOENT on a missing Resources/app/resources/lib
|
|
37012
|
+
// directory), confirmed by an external tester hitting this on two
|
|
37013
|
+
// separate live UAT passes even after `npx` carried strong caveat text.
|
|
37014
|
+
// A guessed default path (e.g. a Homebrew-style tab) can *also* be
|
|
37015
|
+
// wrong for an nvm/asdf/custom-prefix install, which fails silently
|
|
37016
|
+
// differently instead of predictably — so this deliberately never
|
|
37017
|
+
// guesses a path at all; the primary copied snippet can never be wrong,
|
|
37018
|
+
// only require one resolve-and-paste step. `npx` stays available as an
|
|
37019
|
+
// explicit, clearly-labeled fallback in `notes` below, not removed.
|
|
36047
37020
|
body: `{
|
|
36048
37021
|
"mcpServers": {
|
|
36049
37022
|
"{{name}}": {
|
|
36050
|
-
"command": "
|
|
36051
|
-
"args": ["-y", "{{name}}"],
|
|
37023
|
+
"command": "<paste output of: which {{name}} (macOS/Linux) or where {{name}} (Windows)>",
|
|
36052
37024
|
"env": {
|
|
36053
37025
|
"SKILLSMITH_API_KEY": "sk_live_...",
|
|
36054
37026
|
"SKILLSMITH_CLIENT": "cursor"
|
|
@@ -36056,7 +37028,14 @@ var CLIENT_SNIPPETS = {
|
|
|
36056
37028
|
}
|
|
36057
37029
|
}
|
|
36058
37030
|
}`,
|
|
36059
|
-
|
|
37031
|
+
// Assumes the package's `bin` name matches its npm package name — true for
|
|
37032
|
+
// every server scaffolded by mcp-server.template.ts (PACKAGE_JSON_TEMPLATE's
|
|
37033
|
+
// `bin` field is literally `{{name}}`), the only production caller of this
|
|
37034
|
+
// matrix today. The real @skillsmith/mcp-server package is the one exception
|
|
37035
|
+
// (bin is the shorter `skillsmith-mcp`, not the scoped package name) — its
|
|
37036
|
+
// docs are hand-maintained separately (root README, packages/mcp-server/README.md,
|
|
37037
|
+
// website's mcp-client-snippets.ts) rather than rendered through this file.
|
|
37038
|
+
notes: 'Cursor 2.4+ required, Node >=22.22 (Cursor\'s own bundled Node meets this). Setup: run `npm install -g {{name}}`, then run `which {{name}}` (macOS/Linux) or `where {{name}}` (Windows) and paste that path into `command` above \u2014 Cursor\'s bundled Node cannot resolve packages via `npx` (a real ENOENT on a missing Resources/app/resources/lib directory), so pointing directly at the installed binary is the only form confirmed to work inside Cursor. Prefer to try `npx` first anyway? Replace `command` with `"npx"` and add `"args": ["-y", "{{name}}"]` \u2014 simpler, but may hit the same ENOENT, plus EBADENGINE or ENOTEMPTY on repeated installs. After saving: enable the server in Cursor\'s Settings -> MCP panel and start a new chat \u2014 a correctly-configured entry still shows disconnected until toggled on there \u2014 then reload the window.'
|
|
36060
37039
|
},
|
|
36061
37040
|
copilot: {
|
|
36062
37041
|
label: "GitHub Copilot (VS Code)",
|
|
@@ -36205,9 +37184,10 @@ function renderAllSnippetsAsMarkdown(packageName) {
|
|
|
36205
37184
|
const sections = Object.keys(CLIENT_SNIPPETS).map((id) => {
|
|
36206
37185
|
const snippet = CLIENT_SNIPPETS[id];
|
|
36207
37186
|
const body = renderSnippet(id, packageName);
|
|
36208
|
-
const
|
|
37187
|
+
const renderedNotes = snippet.notes?.replace(/\{\{name\}\}/g, packageName);
|
|
37188
|
+
const notes = renderedNotes ? `
|
|
36209
37189
|
|
|
36210
|
-
${
|
|
37190
|
+
${renderedNotes}` : "";
|
|
36211
37191
|
return [
|
|
36212
37192
|
`<details>`,
|
|
36213
37193
|
`<summary><strong>${snippet.label}</strong> \u2014 \`${snippet.configPath}\`</summary>`,
|
|
@@ -36577,15 +37557,15 @@ function renderMcpServerTemplates(data) {
|
|
|
36577
37557
|
async function scaffoldSkillDirectory(input7) {
|
|
36578
37558
|
const { skillDir, skillName, description, author, category, createdFresh } = input7;
|
|
36579
37559
|
try {
|
|
36580
|
-
await mkdir8(
|
|
36581
|
-
await mkdir8(
|
|
37560
|
+
await mkdir8(join44(skillDir, "scripts"), { recursive: true });
|
|
37561
|
+
await mkdir8(join44(skillDir, "resources"), { recursive: true });
|
|
36582
37562
|
const skillMdContent = SKILL_MD_TEMPLATE.replace(/\{\{name\}\}/g, skillName).replace(/\{\{description\}\}/g, description).replace(/\{\{author\}\}/g, author).replace(/\{\{category\}\}/g, category).replace(/\{\{date\}\}/g, (/* @__PURE__ */ new Date()).toISOString().split("T")[0] || "").replace(/\{\{behavioralClassification\}\}/g, "");
|
|
36583
|
-
await writeFile5(
|
|
37563
|
+
await writeFile5(join44(skillDir, "SKILL.md"), skillMdContent, "utf-8");
|
|
36584
37564
|
const readmeContent = README_MD_TEMPLATE.replace(/\{\{name\}\}/g, skillName).replace(
|
|
36585
37565
|
/\{\{description\}\}/g,
|
|
36586
37566
|
description
|
|
36587
37567
|
);
|
|
36588
|
-
await writeFile5(
|
|
37568
|
+
await writeFile5(join44(skillDir, "README.md"), readmeContent, "utf-8");
|
|
36589
37569
|
const placeholderScript = `#!/usr/bin/env node
|
|
36590
37570
|
/**
|
|
36591
37571
|
* ${skillName} - Example Script
|
|
@@ -36595,7 +37575,7 @@ async function scaffoldSkillDirectory(input7) {
|
|
|
36595
37575
|
|
|
36596
37576
|
console.log('${skillName} script executed');
|
|
36597
37577
|
`;
|
|
36598
|
-
await writeFile5(
|
|
37578
|
+
await writeFile5(join44(skillDir, "scripts", "example.js"), placeholderScript, "utf-8");
|
|
36599
37579
|
const gitignore = `# Dependencies
|
|
36600
37580
|
node_modules/
|
|
36601
37581
|
|
|
@@ -36610,7 +37590,7 @@ dist/
|
|
|
36610
37590
|
.DS_Store
|
|
36611
37591
|
Thumbs.db
|
|
36612
37592
|
`;
|
|
36613
|
-
await writeFile5(
|
|
37593
|
+
await writeFile5(join44(skillDir, ".gitignore"), gitignore, "utf-8");
|
|
36614
37594
|
return { ok: true };
|
|
36615
37595
|
} catch (error46) {
|
|
36616
37596
|
await rollbackPartialScaffold(skillDir, createdFresh);
|
|
@@ -36726,11 +37706,11 @@ async function validateSkill(skillPath) {
|
|
|
36726
37706
|
try {
|
|
36727
37707
|
const stats = await stat7(filePath);
|
|
36728
37708
|
if (stats.isDirectory()) {
|
|
36729
|
-
filePath =
|
|
37709
|
+
filePath = join45(filePath, "SKILL.md");
|
|
36730
37710
|
}
|
|
36731
37711
|
} catch {
|
|
36732
37712
|
if (!filePath.endsWith(".md")) {
|
|
36733
|
-
filePath =
|
|
37713
|
+
filePath = join45(filePath, "SKILL.md");
|
|
36734
37714
|
}
|
|
36735
37715
|
}
|
|
36736
37716
|
const content = await readFile9(filePath, "utf-8");
|
|
@@ -36777,7 +37757,7 @@ async function publishSkill(skillPath, options = {}) {
|
|
|
36777
37757
|
spinner.fail(`Directory not found: ${dirPath}`);
|
|
36778
37758
|
return false;
|
|
36779
37759
|
}
|
|
36780
|
-
const skillMdPath =
|
|
37760
|
+
const skillMdPath = join45(dirPath, "SKILL.md");
|
|
36781
37761
|
spinner.text = "Validating skill...";
|
|
36782
37762
|
const content = await readFile9(skillMdPath, "utf-8");
|
|
36783
37763
|
const parser2 = new SkillParser({ requireName: true });
|
|
@@ -36823,7 +37803,7 @@ async function publishSkill(skillPath, options = {}) {
|
|
|
36823
37803
|
}).filter((p) => p !== null);
|
|
36824
37804
|
let totalWarnings = 0;
|
|
36825
37805
|
for (const mdFile of mdFiles) {
|
|
36826
|
-
const filePath =
|
|
37806
|
+
const filePath = join45(dirPath, mdFile);
|
|
36827
37807
|
const fileContent = await readFile9(filePath, "utf-8");
|
|
36828
37808
|
const result = SkillParser.checkReferences(fileContent, customPatterns);
|
|
36829
37809
|
if (result.matches.length > 0) {
|
|
@@ -36852,7 +37832,7 @@ async function publishSkill(skillPath, options = {}) {
|
|
|
36852
37832
|
spinner.start();
|
|
36853
37833
|
}
|
|
36854
37834
|
}
|
|
36855
|
-
const manifestPath =
|
|
37835
|
+
const manifestPath = join45(dirPath, ".skillsmith-publish.json");
|
|
36856
37836
|
await writeFile6(manifestPath, JSON.stringify(publishInfo, null, 2), "utf-8");
|
|
36857
37837
|
spinner.succeed("Skill prepared for publishing");
|
|
36858
37838
|
console.log(source_default.bold("\nPublish Information:"));
|
|
@@ -36959,7 +37939,7 @@ function createPublishCommand() {
|
|
|
36959
37939
|
import { Command as Command5 } from "commander";
|
|
36960
37940
|
import ora6 from "ora";
|
|
36961
37941
|
import { readFile as readFile10, writeFile as writeFile7, stat as stat8 } from "fs/promises";
|
|
36962
|
-
import { basename as basename7, dirname as dirname22, join as
|
|
37942
|
+
import { basename as basename7, dirname as dirname22, join as join46, resolve as resolve13 } from "path";
|
|
36963
37943
|
|
|
36964
37944
|
// src/utils/tool-analyzer.ts
|
|
36965
37945
|
var TOOL_PATTERNS3 = {
|
|
@@ -37088,13 +38068,13 @@ async function generateSubagent2(skillPath, options) {
|
|
|
37088
38068
|
try {
|
|
37089
38069
|
const stats = await stat8(dirPath);
|
|
37090
38070
|
if (stats.isDirectory()) {
|
|
37091
|
-
skillMdPath =
|
|
38071
|
+
skillMdPath = join46(dirPath, "SKILL.md");
|
|
37092
38072
|
} else {
|
|
37093
38073
|
skillMdPath = dirPath;
|
|
37094
38074
|
dirPath = dirname22(dirPath);
|
|
37095
38075
|
}
|
|
37096
38076
|
} catch {
|
|
37097
|
-
skillMdPath = dirPath.endsWith(".md") ? dirPath :
|
|
38077
|
+
skillMdPath = dirPath.endsWith(".md") ? dirPath : join46(dirPath, "SKILL.md");
|
|
37098
38078
|
}
|
|
37099
38079
|
spinner.text = "Reading SKILL.md...";
|
|
37100
38080
|
const content = await readFile10(skillMdPath, "utf-8");
|
|
@@ -37145,7 +38125,7 @@ async function generateSubagent2(skillPath, options) {
|
|
|
37145
38125
|
"{name}",
|
|
37146
38126
|
basename7(metadata.name)
|
|
37147
38127
|
);
|
|
37148
|
-
const subagentPath =
|
|
38128
|
+
const subagentPath = join46(agentsDir, subagentFilename);
|
|
37149
38129
|
if (await fileExists(subagentPath)) {
|
|
37150
38130
|
if (!options.force) {
|
|
37151
38131
|
spinner.warn(`Subagent already exists: ${subagentPath}`);
|
|
@@ -37212,7 +38192,7 @@ function createSubagentCommand() {
|
|
|
37212
38192
|
import { Command as Command6 } from "commander";
|
|
37213
38193
|
import ora7 from "ora";
|
|
37214
38194
|
import { readFile as readFile11, readdir as readdir8 } from "fs/promises";
|
|
37215
|
-
import { join as
|
|
38195
|
+
import { join as join47, resolve as resolve14 } from "path";
|
|
37216
38196
|
var logger14 = getCliLogger();
|
|
37217
38197
|
async function transformSkill2(skillPath, options) {
|
|
37218
38198
|
const spinner = ora7("Transforming skill...").start();
|
|
@@ -37227,9 +38207,9 @@ async function transformSkill2(skillPath, options) {
|
|
|
37227
38207
|
const subdirs = await readdir8(dirPath, { withFileTypes: true });
|
|
37228
38208
|
for (const entry of subdirs) {
|
|
37229
38209
|
if (entry.isDirectory()) {
|
|
37230
|
-
const skillMdPath2 =
|
|
38210
|
+
const skillMdPath2 = join47(dirPath, entry.name, "SKILL.md");
|
|
37231
38211
|
if (await fileExists(skillMdPath2)) {
|
|
37232
|
-
skillDirs.push(
|
|
38212
|
+
skillDirs.push(join47(dirPath, entry.name));
|
|
37233
38213
|
}
|
|
37234
38214
|
}
|
|
37235
38215
|
}
|
|
@@ -37251,7 +38231,7 @@ Processing: ${skillDir}`));
|
|
|
37251
38231
|
}
|
|
37252
38232
|
return;
|
|
37253
38233
|
}
|
|
37254
|
-
const skillMdPath =
|
|
38234
|
+
const skillMdPath = join47(dirPath, "SKILL.md");
|
|
37255
38235
|
if (!await fileExists(skillMdPath)) {
|
|
37256
38236
|
spinner.fail(`No SKILL.md found at: ${skillMdPath}`);
|
|
37257
38237
|
throw new Error(`No SKILL.md found at: ${skillMdPath}`);
|
|
@@ -37323,7 +38303,7 @@ import { Command as Command7 } from "commander";
|
|
|
37323
38303
|
import { input as input3, confirm as confirm4 } from "@inquirer/prompts";
|
|
37324
38304
|
import ora8 from "ora";
|
|
37325
38305
|
import { mkdir as mkdir10, writeFile as writeFile8, stat as stat9 } from "fs/promises";
|
|
37326
|
-
import { dirname as dirname23, join as
|
|
38306
|
+
import { dirname as dirname23, join as join48, resolve as resolve15 } from "path";
|
|
37327
38307
|
var logger15 = getCliLogger();
|
|
37328
38308
|
async function initMcpServer(name, options) {
|
|
37329
38309
|
const serverName = name || await input3({
|
|
@@ -37436,10 +38416,10 @@ async function initMcpServer(name, options) {
|
|
|
37436
38416
|
author
|
|
37437
38417
|
});
|
|
37438
38418
|
await mkdir10(targetDir, { recursive: true });
|
|
37439
|
-
await mkdir10(
|
|
37440
|
-
await mkdir10(
|
|
38419
|
+
await mkdir10(join48(targetDir, "src"), { recursive: true });
|
|
38420
|
+
await mkdir10(join48(targetDir, "src", "tools"), { recursive: true });
|
|
37441
38421
|
for (const [filePath, content] of files) {
|
|
37442
|
-
const fullPath =
|
|
38422
|
+
const fullPath = join48(targetDir, filePath);
|
|
37443
38423
|
const dir = dirname23(fullPath);
|
|
37444
38424
|
await mkdir10(dir, { recursive: true });
|
|
37445
38425
|
await writeFile8(fullPath, content, "utf-8");
|
|
@@ -37459,7 +38439,7 @@ async function initMcpServer(name, options) {
|
|
|
37459
38439
|
"mcpServers": {
|
|
37460
38440
|
"${serverName}": {
|
|
37461
38441
|
"command": "npx",
|
|
37462
|
-
"args": ["tsx", "${
|
|
38442
|
+
"args": ["tsx", "${join48(targetDir, "src", "index.ts")}"]
|
|
37463
38443
|
}
|
|
37464
38444
|
}
|
|
37465
38445
|
}`)
|
|
@@ -37640,8 +38620,8 @@ import { Command as Command9 } from "commander";
|
|
|
37640
38620
|
import ora9 from "ora";
|
|
37641
38621
|
|
|
37642
38622
|
// src/commands/recommend.helpers.ts
|
|
37643
|
-
import { existsSync as
|
|
37644
|
-
import { join as
|
|
38623
|
+
import { existsSync as existsSync22, readdirSync as readdirSync2, readFileSync as readFileSync19, statSync as statSync4 } from "node:fs";
|
|
38624
|
+
import { join as join49 } from "node:path";
|
|
37645
38625
|
|
|
37646
38626
|
// src/commands/recommend.types.ts
|
|
37647
38627
|
var VALID_TRUST_TIERS = [
|
|
@@ -37971,14 +38951,14 @@ function buildStackFromAnalysis(context) {
|
|
|
37971
38951
|
}
|
|
37972
38952
|
function getInstalledSkills2() {
|
|
37973
38953
|
const skillsDir = getCanonicalInstallPath();
|
|
37974
|
-
if (!
|
|
38954
|
+
if (!existsSync22(skillsDir)) {
|
|
37975
38955
|
return [];
|
|
37976
38956
|
}
|
|
37977
38957
|
const installedSkills = [];
|
|
37978
38958
|
try {
|
|
37979
38959
|
const entries = readdirSync2(skillsDir);
|
|
37980
38960
|
for (const entry of entries) {
|
|
37981
|
-
const skillPath =
|
|
38961
|
+
const skillPath = join49(skillsDir, entry);
|
|
37982
38962
|
const stat13 = statSync4(skillPath);
|
|
37983
38963
|
if (!stat13.isDirectory()) continue;
|
|
37984
38964
|
const skill = {
|
|
@@ -37987,10 +38967,10 @@ function getInstalledSkills2() {
|
|
|
37987
38967
|
tags: [],
|
|
37988
38968
|
category: null
|
|
37989
38969
|
};
|
|
37990
|
-
const skillMdPath =
|
|
37991
|
-
if (
|
|
38970
|
+
const skillMdPath = join49(skillPath, "SKILL.md");
|
|
38971
|
+
if (existsSync22(skillMdPath)) {
|
|
37992
38972
|
try {
|
|
37993
|
-
const content =
|
|
38973
|
+
const content = readFileSync19(skillMdPath, "utf-8");
|
|
37994
38974
|
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
37995
38975
|
const frontmatter = frontmatterMatch?.[1];
|
|
37996
38976
|
if (frontmatter) {
|
|
@@ -38645,7 +39625,7 @@ function createSyncCommand() {
|
|
|
38645
39625
|
// src/commands/merge.ts
|
|
38646
39626
|
import { Command as Command11 } from "commander";
|
|
38647
39627
|
import { resolve as resolve16 } from "path";
|
|
38648
|
-
import { existsSync as
|
|
39628
|
+
import { existsSync as existsSync23 } from "fs";
|
|
38649
39629
|
var logger19 = getCliLogger();
|
|
38650
39630
|
function formatMergeResult(result) {
|
|
38651
39631
|
const lines = [
|
|
@@ -38679,11 +39659,11 @@ async function mergeActionImpl(sourcePath, targetPath, options) {
|
|
|
38679
39659
|
}
|
|
38680
39660
|
const resolvedSource = resolve16(sourcePath);
|
|
38681
39661
|
const resolvedTarget = targetPath ? resolve16(targetPath) : getDefaultDbPath();
|
|
38682
|
-
if (!
|
|
39662
|
+
if (!existsSync23(resolvedSource)) {
|
|
38683
39663
|
logger19.error(`Source database not found: ${resolvedSource}`);
|
|
38684
39664
|
process.exit(1);
|
|
38685
39665
|
}
|
|
38686
|
-
if (!
|
|
39666
|
+
if (!existsSync23(resolvedTarget)) {
|
|
38687
39667
|
logger19.error(`Target database not found: ${resolvedTarget}`);
|
|
38688
39668
|
logger19.error("Create a new database first with: skillsmith init");
|
|
38689
39669
|
process.exit(1);
|
|
@@ -38948,13 +39928,13 @@ function createRegistryCommand() {
|
|
|
38948
39928
|
import { Command as Command13 } from "commander";
|
|
38949
39929
|
import ora12 from "ora";
|
|
38950
39930
|
import { mkdir as mkdir11, copyFile as copyFile2, stat as stat10, readdir as readdir9 } from "fs/promises";
|
|
38951
|
-
import { join as
|
|
39931
|
+
import { join as join50, dirname as dirname24 } from "path";
|
|
38952
39932
|
var logger21 = getCliLogger();
|
|
38953
39933
|
function getAssetsPath() {
|
|
38954
|
-
return
|
|
39934
|
+
return join50(packageRoot(), "assets", "skillsmith-skill");
|
|
38955
39935
|
}
|
|
38956
39936
|
function getTargetPath(client) {
|
|
38957
|
-
return
|
|
39937
|
+
return join50(getInstallPath(client), "skillsmith");
|
|
38958
39938
|
}
|
|
38959
39939
|
async function directoryExists(path27) {
|
|
38960
39940
|
try {
|
|
@@ -38971,8 +39951,8 @@ async function copyDirectory(src, dest) {
|
|
|
38971
39951
|
if (entry.isSymbolicLink()) {
|
|
38972
39952
|
continue;
|
|
38973
39953
|
}
|
|
38974
|
-
const srcPath =
|
|
38975
|
-
const destPath =
|
|
39954
|
+
const srcPath = join50(src, entry.name);
|
|
39955
|
+
const destPath = join50(dest, entry.name);
|
|
38976
39956
|
if (entry.isDirectory()) {
|
|
38977
39957
|
await mkdir11(destPath, { recursive: true });
|
|
38978
39958
|
filesCopied += await copyDirectory(srcPath, destPath);
|
|
@@ -39350,28 +40330,34 @@ import { Command as Command15 } from "commander";
|
|
|
39350
40330
|
import { confirm as confirm5 } from "@inquirer/prompts";
|
|
39351
40331
|
async function logoutActionImpl() {
|
|
39352
40332
|
const status = await getAuthStatus();
|
|
39353
|
-
|
|
40333
|
+
const jwtSession = await loadCredentials();
|
|
40334
|
+
const hasJwtSession = jwtSession !== null;
|
|
40335
|
+
if (!status.authenticated && !hasJwtSession) {
|
|
39354
40336
|
console.log("Not authenticated. Nothing to log out.");
|
|
39355
40337
|
process.exit(0);
|
|
39356
40338
|
}
|
|
39357
40339
|
const confirmed = await confirm5({
|
|
39358
|
-
message: "Log out and remove stored
|
|
40340
|
+
message: "Log out and remove stored credentials?",
|
|
39359
40341
|
default: false
|
|
39360
40342
|
});
|
|
39361
40343
|
if (!confirmed) {
|
|
39362
40344
|
console.log("Cancelled.");
|
|
39363
40345
|
process.exit(0);
|
|
39364
40346
|
}
|
|
39365
|
-
const
|
|
39366
|
-
|
|
39367
|
-
|
|
40347
|
+
const apiKeyResult = await clearApiKey();
|
|
40348
|
+
const jwtResult = await clearCredentials();
|
|
40349
|
+
const results = [apiKeyResult, jwtResult];
|
|
40350
|
+
if (results.every((r) => r.success)) {
|
|
40351
|
+
const sources = [...new Set(results.flatMap((r) => r.source.split(" and ")))].join(" and ");
|
|
40352
|
+
console.log(source_default.green(`Logged out. Credentials removed from ${sources}.`));
|
|
39368
40353
|
} else {
|
|
39369
|
-
console.log(
|
|
39370
|
-
|
|
39371
|
-
|
|
39372
|
-
|
|
39373
|
-
|
|
39374
|
-
|
|
40354
|
+
console.log(source_default.yellow("Logged out (config file cleared), but with keyring warnings:"));
|
|
40355
|
+
for (const result of results) {
|
|
40356
|
+
if (!result.success) {
|
|
40357
|
+
console.log(source_default.yellow(` Could not remove from keyring: ${result.error}`));
|
|
40358
|
+
}
|
|
40359
|
+
}
|
|
40360
|
+
console.log(source_default.dim("Some credentials may still be stored in your OS keyring."));
|
|
39375
40361
|
}
|
|
39376
40362
|
process.exit(0);
|
|
39377
40363
|
}
|
|
@@ -39381,7 +40367,7 @@ var logoutAction = withTelemetry(logoutActionImpl, {
|
|
|
39381
40367
|
extractFramework: () => "cli"
|
|
39382
40368
|
});
|
|
39383
40369
|
function createLogoutCommand() {
|
|
39384
|
-
return new Command15("logout").description("Remove stored Skillsmith
|
|
40370
|
+
return new Command15("logout").description("Remove stored Skillsmith credentials").action(logoutAction);
|
|
39385
40371
|
}
|
|
39386
40372
|
|
|
39387
40373
|
// src/commands/whoami.ts
|
|
@@ -39393,6 +40379,17 @@ var SOURCE_LABELS = {
|
|
|
39393
40379
|
none: "none"
|
|
39394
40380
|
};
|
|
39395
40381
|
async function whoamiActionImpl() {
|
|
40382
|
+
const jwtSession = await loadCredentials();
|
|
40383
|
+
if (jwtSession) {
|
|
40384
|
+
console.log(source_default.bold("Skillsmith CLI"));
|
|
40385
|
+
console.log(source_default.dim(" Session: ") + source_default.cyan("device-code login"));
|
|
40386
|
+
const expired = Date.now() >= jwtSession.expiresAt;
|
|
40387
|
+
const expiresLabel = new Date(jwtSession.expiresAt).toLocaleString();
|
|
40388
|
+
console.log(
|
|
40389
|
+
source_default.dim(" Access token: ") + (expired ? source_default.yellow(`expired ${expiresLabel} (refreshes automatically on next use)`) : source_default.green(`valid until ${expiresLabel}`))
|
|
40390
|
+
);
|
|
40391
|
+
process.exit(0);
|
|
40392
|
+
}
|
|
39396
40393
|
const status = await getAuthStatus();
|
|
39397
40394
|
if (!status.authenticated || !status.keyPrefix) {
|
|
39398
40395
|
console.log(`Not authenticated. Run ${source_default.cyan("`skillsmith login`")} to authenticate.`);
|
|
@@ -39424,7 +40421,7 @@ function createWhoamiCommand() {
|
|
|
39424
40421
|
// src/commands/diff.ts
|
|
39425
40422
|
import { Command as Command17 } from "commander";
|
|
39426
40423
|
import { readFile as readFile12 } from "fs/promises";
|
|
39427
|
-
import { join as
|
|
40424
|
+
import { join as join51 } from "path";
|
|
39428
40425
|
|
|
39429
40426
|
// src/utils/license-types.ts
|
|
39430
40427
|
var TIER_FEATURES = {
|
|
@@ -39554,7 +40551,7 @@ var TIER_PRICING = {
|
|
|
39554
40551
|
community: "$0/month",
|
|
39555
40552
|
individual: "$9.99/month",
|
|
39556
40553
|
team: "$25/user/month",
|
|
39557
|
-
enterprise: "
|
|
40554
|
+
enterprise: "Custom pricing \u2014 Contact Sales"
|
|
39558
40555
|
};
|
|
39559
40556
|
async function requireTier(minimumTier) {
|
|
39560
40557
|
if (process.env["SKILLSMITH_SKIP_LICENSE_CHECK"] === "true") {
|
|
@@ -39632,7 +40629,7 @@ function diffSections(oldContent, newContent) {
|
|
|
39632
40629
|
return { added, removed, modified };
|
|
39633
40630
|
}
|
|
39634
40631
|
async function readInstalledSkillContent(skillName) {
|
|
39635
|
-
const skillPath =
|
|
40632
|
+
const skillPath = join51(getCanonicalInstallPath(), skillName, "SKILL.md");
|
|
39636
40633
|
try {
|
|
39637
40634
|
return await readFile12(skillPath, "utf-8");
|
|
39638
40635
|
} catch {
|
|
@@ -39844,7 +40841,7 @@ import { Command as Command22 } from "commander";
|
|
|
39844
40841
|
import * as crypto12 from "node:crypto";
|
|
39845
40842
|
import * as fs34 from "node:fs";
|
|
39846
40843
|
import { homedir as homedir28 } from "node:os";
|
|
39847
|
-
import { join as
|
|
40844
|
+
import { join as join63 } from "node:path";
|
|
39848
40845
|
import { Command as Command19 } from "commander";
|
|
39849
40846
|
import { input as input4, select as select3 } from "@inquirer/prompts";
|
|
39850
40847
|
|
|
@@ -44423,6 +45420,9 @@ function makeClaudeMdEntry(claudeMdPath, phrase, mtime) {
|
|
|
44423
45420
|
identifier: hashClaudeMdLine(claudeMdPath, phrase),
|
|
44424
45421
|
triggerSurface: [phrase],
|
|
44425
45422
|
mtime,
|
|
45423
|
+
// CLAUDE.md rules are Claude Code-only (SMI-6077) — no other supported
|
|
45424
|
+
// client reads this file today.
|
|
45425
|
+
client: CANONICAL_CLIENT,
|
|
44426
45426
|
meta: { description: phrase }
|
|
44427
45427
|
};
|
|
44428
45428
|
}
|
|
@@ -45067,8 +46067,14 @@ import * as os10 from "node:os";
|
|
|
45067
46067
|
import * as fs20 from "node:fs";
|
|
45068
46068
|
import * as os7 from "node:os";
|
|
45069
46069
|
import * as path18 from "node:path";
|
|
45070
|
-
var
|
|
45071
|
-
var
|
|
46070
|
+
var REAL_HOME_DIR = os7.homedir();
|
|
46071
|
+
var DEFAULT_HOME_CLAUDE_DIR = path18.join(REAL_HOME_DIR, ".claude");
|
|
46072
|
+
var DEFAULT_MANIFEST_PATH3 = path18.join(REAL_HOME_DIR, ".skillsmith", "manifest.json");
|
|
46073
|
+
function resolveClientSkillsDir(client, homeDir) {
|
|
46074
|
+
const nativePath = CLIENT_NATIVE_PATHS[client];
|
|
46075
|
+
const relativeToRealHome = path18.relative(REAL_HOME_DIR, nativePath);
|
|
46076
|
+
return path18.join(homeDir, relativeToRealHome);
|
|
46077
|
+
}
|
|
45072
46078
|
async function scanLocalInventory(opts = {}) {
|
|
45073
46079
|
const startedAt = process.hrtime.bigint();
|
|
45074
46080
|
const homeDir = opts.homeDir ?? os7.homedir();
|
|
@@ -45077,9 +46083,10 @@ async function scanLocalInventory(opts = {}) {
|
|
|
45077
46083
|
const warnings = [];
|
|
45078
46084
|
const entries = [];
|
|
45079
46085
|
const manifest = loadManifest3(manifestPath);
|
|
45080
|
-
|
|
45081
|
-
|
|
45082
|
-
|
|
46086
|
+
for (const client of CLIENT_IDS) {
|
|
46087
|
+
const skillsDir = resolveClientSkillsDir(client, homeDir);
|
|
46088
|
+
entries.push(...scanSkills(skillsDir, manifest, warnings, client));
|
|
46089
|
+
}
|
|
45083
46090
|
entries.push(...scanCommands(path18.join(claudeDir, "commands"), warnings));
|
|
45084
46091
|
entries.push(...scanAgents(path18.join(claudeDir, "agents"), warnings));
|
|
45085
46092
|
const userClaudeMd = path18.join(claudeDir, "CLAUDE.md");
|
|
@@ -45099,10 +46106,9 @@ async function scanLocalInventory(opts = {}) {
|
|
|
45099
46106
|
});
|
|
45100
46107
|
const elapsedNs = process.hrtime.bigint() - startedAt;
|
|
45101
46108
|
const durationMs = Number(elapsedNs) / 1e6;
|
|
45102
|
-
void homeDir;
|
|
45103
46109
|
return { entries, warnings, durationMs };
|
|
45104
46110
|
}
|
|
45105
|
-
function scanSkills(skillsDir, manifest, warnings) {
|
|
46111
|
+
function scanSkills(skillsDir, manifest, warnings, client) {
|
|
45106
46112
|
if (!fs20.existsSync(skillsDir))
|
|
45107
46113
|
return [];
|
|
45108
46114
|
const out = [];
|
|
@@ -45144,6 +46150,7 @@ function scanSkills(skillsDir, manifest, warnings) {
|
|
|
45144
46150
|
identifier,
|
|
45145
46151
|
triggerSurface: phrases,
|
|
45146
46152
|
mtime,
|
|
46153
|
+
client,
|
|
45147
46154
|
meta: {
|
|
45148
46155
|
description,
|
|
45149
46156
|
author: author.author,
|
|
@@ -45193,6 +46200,7 @@ function scanMdDir(dir, kind, warnings) {
|
|
|
45193
46200
|
identifier,
|
|
45194
46201
|
triggerSurface: phrases,
|
|
45195
46202
|
mtime: readMtime(filePath),
|
|
46203
|
+
client: CANONICAL_CLIENT,
|
|
45196
46204
|
meta: {
|
|
45197
46205
|
description: triggerLine || void 0
|
|
45198
46206
|
}
|
|
@@ -46811,11 +47819,11 @@ import * as os14 from "node:os";
|
|
|
46811
47819
|
|
|
46812
47820
|
// ../core/dist/src/audit/exclusions.js
|
|
46813
47821
|
import { promises as fs30 } from "node:fs";
|
|
46814
|
-
import { join as
|
|
47822
|
+
import { join as join61 } from "node:path";
|
|
46815
47823
|
var EXCLUSIONS_FILE = "audit-exclusions.json";
|
|
46816
47824
|
var EMPTY_CONFIG = { version: 1, exclusions: [] };
|
|
46817
47825
|
function getExclusionsPath(opts) {
|
|
46818
|
-
return
|
|
47826
|
+
return join61(opts?.configDir ?? getConfigDir(), EXCLUSIONS_FILE);
|
|
46819
47827
|
}
|
|
46820
47828
|
async function loadExclusions(opts = {}) {
|
|
46821
47829
|
const path27 = opts.configPath ?? getExclusionsPath();
|
|
@@ -47213,7 +48221,7 @@ function formatTierBadge(tier) {
|
|
|
47213
48221
|
function displayLicenseStatus(status) {
|
|
47214
48222
|
const tierBadge = formatTierBadge(status.tier);
|
|
47215
48223
|
if (status.tier === "community") {
|
|
47216
|
-
console.error(`License: ${tierBadge} ${source_default.dim("(free tier -
|
|
48224
|
+
console.error(`License: ${tierBadge} ${source_default.dim("(free tier - 100 API calls/month)")}`);
|
|
47217
48225
|
} else if (status.tier === "individual") {
|
|
47218
48226
|
const expiresInfo = status.expiresAt ? source_default.green(`(expires: ${status.expiresAt.toISOString().split("T")[0]})`) : "";
|
|
47219
48227
|
console.error(`License: ${tierBadge} ${expiresInfo}`);
|
|
@@ -47277,10 +48285,10 @@ async function requireConfirmationPhrase(expected, prompt) {
|
|
|
47277
48285
|
}
|
|
47278
48286
|
}
|
|
47279
48287
|
function ledgerPath() {
|
|
47280
|
-
return
|
|
48288
|
+
return join63(homedir28(), ".skillsmith", "namespace-overrides.json");
|
|
47281
48289
|
}
|
|
47282
48290
|
function backupsDir() {
|
|
47283
|
-
return
|
|
48291
|
+
return join63(homedir28(), ".skillsmith", "backups");
|
|
47284
48292
|
}
|
|
47285
48293
|
function backupLedgerForReset() {
|
|
47286
48294
|
const src = ledgerPath();
|
|
@@ -47289,7 +48297,7 @@ function backupLedgerForReset() {
|
|
|
47289
48297
|
fs34.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
47290
48298
|
const ts2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
47291
48299
|
const suffix = crypto12.randomBytes(4).toString("hex");
|
|
47292
|
-
const backupFile =
|
|
48300
|
+
const backupFile = join63(dir, `ledger-${ts2}-${suffix}.json`);
|
|
47293
48301
|
fs34.copyFileSync(src, backupFile);
|
|
47294
48302
|
return backupFile;
|
|
47295
48303
|
}
|
|
@@ -47548,9 +48556,9 @@ function methodCell(skill) {
|
|
|
47548
48556
|
function printHumanReport(report, applying) {
|
|
47549
48557
|
console.log(source_default.bold.blue("\n=== Skillsmith \u2014 Source Recovery ===\n"));
|
|
47550
48558
|
const hdr = pad("skill", COL_SKILL) + "| " + pad("source", COL_SOURCE) + "| " + pad("confidence", COL_CONF) + "| method";
|
|
47551
|
-
const
|
|
48559
|
+
const sep7 = "-".repeat(hdr.length);
|
|
47552
48560
|
console.log(hdr);
|
|
47553
|
-
console.log(
|
|
48561
|
+
console.log(sep7);
|
|
47554
48562
|
for (const skill of report.skills) {
|
|
47555
48563
|
const row = pad(skill.skillName, COL_SKILL) + "| " + pad(sourceCell(skill), COL_SOURCE) + "| " + pad(confCell(skill), COL_CONF) + "| " + methodCell(skill);
|
|
47556
48564
|
console.log(row);
|
|
@@ -47703,10 +48711,10 @@ function createAuditSourcesSubcommand() {
|
|
|
47703
48711
|
import { Command as Command21 } from "commander";
|
|
47704
48712
|
|
|
47705
48713
|
// src/commands/audit-security.candidates.ts
|
|
47706
|
-
var
|
|
48714
|
+
var SEVERITY_RANK2 = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
47707
48715
|
function compareCandidates(a, b) {
|
|
47708
|
-
const sa =
|
|
47709
|
-
const sb =
|
|
48716
|
+
const sa = SEVERITY_RANK2[a.finding.severity] ?? 4;
|
|
48717
|
+
const sb = SEVERITY_RANK2[b.finding.severity] ?? 4;
|
|
47710
48718
|
if (sa !== sb) return sa - sb;
|
|
47711
48719
|
if (a.identifier !== b.identifier) return a.identifier < b.identifier ? -1 : 1;
|
|
47712
48720
|
const fa = a.finding.filePath ?? "";
|
|
@@ -48326,7 +49334,7 @@ import { Command as Command23 } from "commander";
|
|
|
48326
49334
|
import { input as input6, confirm as confirm6, select as select4 } from "@inquirer/prompts";
|
|
48327
49335
|
import ora13 from "ora";
|
|
48328
49336
|
import { mkdir as mkdir17, writeFile as writeFile15, stat as stat12 } from "fs/promises";
|
|
48329
|
-
import { join as
|
|
49337
|
+
import { join as join64 } from "path";
|
|
48330
49338
|
var logger29 = getCliLogger();
|
|
48331
49339
|
var VALID_TYPES = ["basic", "intermediate", "advanced"];
|
|
48332
49340
|
var VALID_BEHAVIORS = ["autonomous", "guided", "interactive", "configurable"];
|
|
@@ -48462,7 +49470,7 @@ async function createSkill(name, options = {}) {
|
|
|
48462
49470
|
default: false
|
|
48463
49471
|
});
|
|
48464
49472
|
const outputDir = options.output ?? getCanonicalInstallPath();
|
|
48465
|
-
const skillDir =
|
|
49473
|
+
const skillDir = join64(outputDir, skillName);
|
|
48466
49474
|
let exists = false;
|
|
48467
49475
|
try {
|
|
48468
49476
|
await stat12(skillDir);
|
|
@@ -48536,16 +49544,16 @@ Thumbs.db
|
|
|
48536
49544
|
const spinner = ora13("Scaffolding skill...").start();
|
|
48537
49545
|
try {
|
|
48538
49546
|
await mkdir17(skillDir, { recursive: true });
|
|
48539
|
-
await mkdir17(
|
|
49547
|
+
await mkdir17(join64(skillDir, "resources"), { recursive: true });
|
|
48540
49548
|
if (includeScripts) {
|
|
48541
|
-
await mkdir17(
|
|
49549
|
+
await mkdir17(join64(skillDir, "scripts"), { recursive: true });
|
|
48542
49550
|
}
|
|
48543
|
-
await writeFile15(
|
|
48544
|
-
await writeFile15(
|
|
48545
|
-
await writeFile15(
|
|
48546
|
-
await writeFile15(
|
|
49551
|
+
await writeFile15(join64(skillDir, "SKILL.md"), skillMdContent, "utf-8");
|
|
49552
|
+
await writeFile15(join64(skillDir, "README.md"), readmeContent, "utf-8");
|
|
49553
|
+
await writeFile15(join64(skillDir, "CHANGELOG.md"), changelogContent, "utf-8");
|
|
49554
|
+
await writeFile15(join64(skillDir, ".gitignore"), gitignoreContent, "utf-8");
|
|
48547
49555
|
if (includeScripts) {
|
|
48548
|
-
await writeFile15(
|
|
49556
|
+
await writeFile15(join64(skillDir, "scripts", "example.js"), scriptContent, "utf-8");
|
|
48549
49557
|
}
|
|
48550
49558
|
spinner.succeed(`Skill scaffolded at ${skillDir}`);
|
|
48551
49559
|
} catch (error46) {
|
|
@@ -48924,7 +49932,7 @@ import { promises as fs36 } from "node:fs";
|
|
|
48924
49932
|
// src/commands/import-local.helpers.ts
|
|
48925
49933
|
import { createHash as createHash19 } from "node:crypto";
|
|
48926
49934
|
import { promises as fs35 } from "node:fs";
|
|
48927
|
-
import { join as
|
|
49935
|
+
import { join as join65, resolve as resolve17, dirname as dirname29, basename as basename10, sep as sep6, relative as relative9 } from "node:path";
|
|
48928
49936
|
import matter from "gray-matter";
|
|
48929
49937
|
var SKILL_FILENAME = "SKILL.md";
|
|
48930
49938
|
var MAX_DEPTH = 8;
|
|
@@ -48945,7 +49953,7 @@ async function walkSkillFiles(rootDir) {
|
|
|
48945
49953
|
return;
|
|
48946
49954
|
}
|
|
48947
49955
|
for (const entry of entries) {
|
|
48948
|
-
const entryPath =
|
|
49956
|
+
const entryPath = join65(dir, entry.name);
|
|
48949
49957
|
if (entry.isSymbolicLink()) {
|
|
48950
49958
|
let realPath;
|
|
48951
49959
|
try {
|
|
@@ -48953,8 +49961,8 @@ async function walkSkillFiles(rootDir) {
|
|
|
48953
49961
|
} catch {
|
|
48954
49962
|
continue;
|
|
48955
49963
|
}
|
|
48956
|
-
const rel =
|
|
48957
|
-
if (rel.startsWith("..") || rel === ".." || rel.startsWith(`..${
|
|
49964
|
+
const rel = relative9(canonicalRoot, realPath);
|
|
49965
|
+
if (rel.startsWith("..") || rel === ".." || rel.startsWith(`..${sep6}`)) {
|
|
48958
49966
|
skipped.push({ path: entryPath, reason: "symlink-escapes-root" });
|
|
48959
49967
|
continue;
|
|
48960
49968
|
}
|
|
@@ -49254,7 +50262,7 @@ function printHumanSummary(result) {
|
|
|
49254
50262
|
import * as crypto13 from "node:crypto";
|
|
49255
50263
|
import * as fs37 from "node:fs";
|
|
49256
50264
|
import { homedir as homedir29 } from "node:os";
|
|
49257
|
-
import { join as
|
|
50265
|
+
import { join as join66, dirname as dirname30 } from "node:path";
|
|
49258
50266
|
import { Command as Command27 } from "commander";
|
|
49259
50267
|
var logger33 = getCliLogger();
|
|
49260
50268
|
var CONFIG_DIR3 = ".skillsmith";
|
|
@@ -49278,7 +50286,7 @@ function isSupportedKey(key) {
|
|
|
49278
50286
|
return SUPPORTED_KEYS.includes(key);
|
|
49279
50287
|
}
|
|
49280
50288
|
function configPath() {
|
|
49281
|
-
return
|
|
50289
|
+
return join66(homedir29(), CONFIG_DIR3, CONFIG_FILE3);
|
|
49282
50290
|
}
|
|
49283
50291
|
function readConfigFile2() {
|
|
49284
50292
|
const path27 = configPath();
|
|
@@ -49395,15 +50403,15 @@ function createConfigCommand2() {
|
|
|
49395
50403
|
import { Command as Command28 } from "commander";
|
|
49396
50404
|
|
|
49397
50405
|
// src/commands/telemetry.action.ts
|
|
49398
|
-
import { existsSync as
|
|
50406
|
+
import { existsSync as existsSync29, copyFileSync as copyFileSync2, chmodSync as chmodSync9, mkdirSync as mkdirSync14 } from "node:fs";
|
|
49399
50407
|
import { homedir as homedir31 } from "node:os";
|
|
49400
|
-
import { join as
|
|
50408
|
+
import { join as join68, dirname as dirname32 } from "node:path";
|
|
49401
50409
|
import { readdirSync as readdirSync4, unlinkSync as unlinkSync5, statSync as statSync6 } from "node:fs";
|
|
49402
50410
|
|
|
49403
50411
|
// src/commands/telemetry.helpers.ts
|
|
49404
50412
|
import * as crypto14 from "node:crypto";
|
|
49405
50413
|
import * as fs38 from "node:fs";
|
|
49406
|
-
import { join as
|
|
50414
|
+
import { join as join67, dirname as dirname31 } from "node:path";
|
|
49407
50415
|
import { homedir as homedir30 } from "node:os";
|
|
49408
50416
|
var TelemetryHookError = class extends Error {
|
|
49409
50417
|
constructor(code, message) {
|
|
@@ -49415,9 +50423,9 @@ var TelemetryHookError = class extends Error {
|
|
|
49415
50423
|
};
|
|
49416
50424
|
function resolveSettingsPath(scope) {
|
|
49417
50425
|
if (scope === "user") {
|
|
49418
|
-
return
|
|
50426
|
+
return join67(homedir30(), ".claude", "settings.json");
|
|
49419
50427
|
}
|
|
49420
|
-
return
|
|
50428
|
+
return join67(process.cwd(), ".claude", "settings.json");
|
|
49421
50429
|
}
|
|
49422
50430
|
function loadClaudeSettings(scope) {
|
|
49423
50431
|
const path27 = resolveSettingsPath(scope);
|
|
@@ -49509,10 +50517,10 @@ var PRIVACY_URL = "https://skillsmith.app/privacy#telemetry";
|
|
|
49509
50517
|
var DEFAULT_ENDPOINT = "https://vrcnzpmndtroqxxoqkzy.supabase.co/functions/v1/events";
|
|
49510
50518
|
var ORPHAN_TTL_MS = 60 * 60 * 1e3;
|
|
49511
50519
|
function hookScriptPath() {
|
|
49512
|
-
return
|
|
50520
|
+
return join68(homedir31(), ".skillsmith", "hooks", "skill-telemetry.sh");
|
|
49513
50521
|
}
|
|
49514
50522
|
function runDir() {
|
|
49515
|
-
return
|
|
50523
|
+
return join68(homedir31(), ".skillsmith", "run");
|
|
49516
50524
|
}
|
|
49517
50525
|
function idTail(id) {
|
|
49518
50526
|
if (!id) return "(none)";
|
|
@@ -49521,11 +50529,11 @@ function idTail(id) {
|
|
|
49521
50529
|
function gcOrphanRunFiles() {
|
|
49522
50530
|
try {
|
|
49523
50531
|
const dir = runDir();
|
|
49524
|
-
if (!
|
|
50532
|
+
if (!existsSync29(dir)) return;
|
|
49525
50533
|
const now = Date.now();
|
|
49526
50534
|
for (const f of readdirSync4(dir)) {
|
|
49527
50535
|
if (!f.startsWith("skill-")) continue;
|
|
49528
|
-
const fp =
|
|
50536
|
+
const fp = join68(dir, f);
|
|
49529
50537
|
try {
|
|
49530
50538
|
const st = statSync6(fp);
|
|
49531
50539
|
if (now - st.mtimeMs > ORPHAN_TTL_MS) unlinkSync5(fp);
|
|
@@ -49625,8 +50633,8 @@ async function runStatus() {
|
|
|
49625
50633
|
}
|
|
49626
50634
|
}
|
|
49627
50635
|
async function runInstallHook(options) {
|
|
49628
|
-
const templateSrc =
|
|
49629
|
-
if (!
|
|
50636
|
+
const templateSrc = join68(packageRoot(), "templates", "skill-telemetry.sh");
|
|
50637
|
+
if (!existsSync29(templateSrc)) {
|
|
49630
50638
|
throw new Error(
|
|
49631
50639
|
"skill-telemetry.sh template not found. Ensure the CLI package is fully built: npm run build"
|
|
49632
50640
|
);
|
|
@@ -49669,7 +50677,7 @@ async function runUninstallHook(options) {
|
|
|
49669
50677
|
writeClaudeSettings(scope, updated);
|
|
49670
50678
|
try {
|
|
49671
50679
|
const scriptPath = hookScriptPath();
|
|
49672
|
-
if (
|
|
50680
|
+
if (existsSync29(scriptPath)) unlinkSync5(scriptPath);
|
|
49673
50681
|
} catch {
|
|
49674
50682
|
}
|
|
49675
50683
|
const scopeLabel = scope === "user" ? "~/.claude/settings.json" : "./.claude/settings.json";
|
|
@@ -49880,7 +50888,7 @@ async function runStatus2(opts) {
|
|
|
49880
50888
|
const localCount = countByHarness.get("local") ?? 0;
|
|
49881
50889
|
if (localCount > 0) {
|
|
49882
50890
|
console.log(
|
|
49883
|
-
` Local skills: ${source_default.dim(`${localCount} skill${localCount === 1 ? "" : "s"} in
|
|
50891
|
+
` Local skills: ${source_default.dim(`${localCount} skill${localCount === 1 ? "" : "s"} in ${getLocalSkillsDirDisplay()} (repo-local \u2014 not synced to your account)`)}`
|
|
49884
50892
|
);
|
|
49885
50893
|
if (opts?.verbose) {
|
|
49886
50894
|
for (const s of allSkills.filter((e) => e.harness === "local")) {
|
|
@@ -50088,14 +51096,14 @@ function createAgentCommand() {
|
|
|
50088
51096
|
}
|
|
50089
51097
|
|
|
50090
51098
|
// src/commands/diagnose.ts
|
|
50091
|
-
import { mkdirSync as mkdirSync15, readFileSync as
|
|
51099
|
+
import { mkdirSync as mkdirSync15, readFileSync as readFileSync27, writeFileSync as writeFileSync17 } from "node:fs";
|
|
50092
51100
|
import { basename as basename11, dirname as dirname33, resolve as resolve19 } from "node:path";
|
|
50093
51101
|
import { Command as Command31 } from "commander";
|
|
50094
51102
|
|
|
50095
51103
|
// src/commands/log-records.helpers.ts
|
|
50096
|
-
import { existsSync as
|
|
51104
|
+
import { existsSync as existsSync30, readFileSync as readFileSync26, readdirSync as readdirSync5, statSync as statSync7 } from "node:fs";
|
|
50097
51105
|
import { homedir as homedir32 } from "node:os";
|
|
50098
|
-
import { join as
|
|
51106
|
+
import { join as join69 } from "node:path";
|
|
50099
51107
|
var LOG_FILE_PATTERN = /^skillsmith-[a-z]+-\d{4}-\d{2}-\d{2}\.jsonl(\.\d+)?$/;
|
|
50100
51108
|
var LOG_LEVEL_ORDER = {
|
|
50101
51109
|
debug: 0,
|
|
@@ -50108,12 +51116,12 @@ function isLogLevel(value) {
|
|
|
50108
51116
|
return VALID_LEVELS.has(value);
|
|
50109
51117
|
}
|
|
50110
51118
|
function resolveLogDir() {
|
|
50111
|
-
return process.env["SKILLSMITH_LOG_DIR"] ||
|
|
51119
|
+
return process.env["SKILLSMITH_LOG_DIR"] || join69(homedir32(), ".skillsmith", "logs");
|
|
50112
51120
|
}
|
|
50113
51121
|
function listLogFiles(dir) {
|
|
50114
|
-
if (!
|
|
51122
|
+
if (!existsSync30(dir)) return [];
|
|
50115
51123
|
try {
|
|
50116
|
-
return readdirSync5(dir).filter((name) => LOG_FILE_PATTERN.test(name)).sort().map((name) =>
|
|
51124
|
+
return readdirSync5(dir).filter((name) => LOG_FILE_PATTERN.test(name)).sort().map((name) => join69(dir, name));
|
|
50117
51125
|
} catch {
|
|
50118
51126
|
return [];
|
|
50119
51127
|
}
|
|
@@ -50121,7 +51129,7 @@ function listLogFiles(dir) {
|
|
|
50121
51129
|
function readLogRecords(filePath) {
|
|
50122
51130
|
let content;
|
|
50123
51131
|
try {
|
|
50124
|
-
content =
|
|
51132
|
+
content = readFileSync26(filePath, "utf8");
|
|
50125
51133
|
} catch {
|
|
50126
51134
|
return [];
|
|
50127
51135
|
}
|
|
@@ -50227,7 +51235,7 @@ function buildBundleContent(summary, files) {
|
|
|
50227
51235
|
parts.push("");
|
|
50228
51236
|
parts.push(`===== ${basename11(file2)} (${fileSizeBytes(file2)} bytes) =====`);
|
|
50229
51237
|
try {
|
|
50230
|
-
parts.push(
|
|
51238
|
+
parts.push(readFileSync27(file2, "utf8"));
|
|
50231
51239
|
} catch (error46) {
|
|
50232
51240
|
parts.push(`[failed to read: ${sanitizeError(error46)}]`);
|
|
50233
51241
|
}
|
|
@@ -50240,7 +51248,7 @@ function writeBundle(bundleOption, summary, files) {
|
|
|
50240
51248
|
const targetPath = resolve19(rawPath);
|
|
50241
51249
|
const content = buildBundleContent(summary, files);
|
|
50242
51250
|
mkdirSync15(dirname33(targetPath), { recursive: true });
|
|
50243
|
-
|
|
51251
|
+
writeFileSync17(targetPath, content, "utf8");
|
|
50244
51252
|
return targetPath;
|
|
50245
51253
|
}
|
|
50246
51254
|
async function runDiagnose(options) {
|
|
@@ -50298,8 +51306,8 @@ function createDiagnoseCommand() {
|
|
|
50298
51306
|
}
|
|
50299
51307
|
|
|
50300
51308
|
// src/commands/logs.ts
|
|
50301
|
-
import { existsSync as
|
|
50302
|
-
import { join as
|
|
51309
|
+
import { existsSync as existsSync31, readFileSync as readFileSync28, statSync as statSync8 } from "node:fs";
|
|
51310
|
+
import { join as join70 } from "node:path";
|
|
50303
51311
|
import { Command as Command32 } from "commander";
|
|
50304
51312
|
var logger38 = getCliLogger();
|
|
50305
51313
|
var TAIL_SURFACES = ["cli", "mcp", "vscode", "doc-retrieval"];
|
|
@@ -50308,7 +51316,7 @@ function todayDateString2() {
|
|
|
50308
51316
|
}
|
|
50309
51317
|
function todaysFilePaths(dir) {
|
|
50310
51318
|
const date5 = todayDateString2();
|
|
50311
|
-
return TAIL_SURFACES.map((surface) =>
|
|
51319
|
+
return TAIL_SURFACES.map((surface) => join70(dir, `skillsmith-${surface}-${date5}.jsonl`));
|
|
50312
51320
|
}
|
|
50313
51321
|
function resolveLevel(raw) {
|
|
50314
51322
|
if (raw === void 0) return void 0;
|
|
@@ -50341,7 +51349,7 @@ async function startTail(dir, level, opts = {}) {
|
|
|
50341
51349
|
const offsets = /* @__PURE__ */ new Map();
|
|
50342
51350
|
let printedAny = false;
|
|
50343
51351
|
for (const path27 of paths) {
|
|
50344
|
-
if (!
|
|
51352
|
+
if (!existsSync31(path27)) {
|
|
50345
51353
|
offsets.set(path27, 0);
|
|
50346
51354
|
continue;
|
|
50347
51355
|
}
|
|
@@ -50380,7 +51388,7 @@ async function startTail(dir, level, opts = {}) {
|
|
|
50380
51388
|
}
|
|
50381
51389
|
let content;
|
|
50382
51390
|
try {
|
|
50383
|
-
content =
|
|
51391
|
+
content = readFileSync28(path27).subarray(previousOffset).toString("utf8");
|
|
50384
51392
|
} catch {
|
|
50385
51393
|
return;
|
|
50386
51394
|
}
|
|
@@ -50457,12 +51465,12 @@ function applyRootQuietOption(rootQuiet) {
|
|
|
50457
51465
|
}
|
|
50458
51466
|
|
|
50459
51467
|
// src/utils/node-version.ts
|
|
50460
|
-
import { readFileSync as
|
|
50461
|
-
import { join as
|
|
51468
|
+
import { readFileSync as readFileSync29 } from "fs";
|
|
51469
|
+
import { join as join71 } from "path";
|
|
50462
51470
|
function loadMinNodeVersion() {
|
|
50463
51471
|
try {
|
|
50464
|
-
const packageJsonPath2 =
|
|
50465
|
-
const packageJson2 = JSON.parse(
|
|
51472
|
+
const packageJsonPath2 = join71(packageRoot(), "package.json");
|
|
51473
|
+
const packageJson2 = JSON.parse(readFileSync29(packageJsonPath2, "utf-8"));
|
|
50466
51474
|
const engineConstraint = packageJson2.engines?.node ?? ">=22.22.0";
|
|
50467
51475
|
return engineConstraint.replace(/[>=<^~\s]/g, "");
|
|
50468
51476
|
} catch {
|
|
@@ -50533,21 +51541,21 @@ function checkNodeVersion() {
|
|
|
50533
51541
|
}
|
|
50534
51542
|
|
|
50535
51543
|
// src/index.ts
|
|
50536
|
-
import { readFileSync as
|
|
50537
|
-
import { join as
|
|
51544
|
+
import { readFileSync as readFileSync30 } from "fs";
|
|
51545
|
+
import { join as join72 } from "path";
|
|
50538
51546
|
var logger39 = getCliLogger();
|
|
50539
51547
|
var versionError = checkNodeVersion();
|
|
50540
51548
|
if (versionError) {
|
|
50541
51549
|
logger39.error(versionError);
|
|
50542
51550
|
process.exit(1);
|
|
50543
51551
|
}
|
|
50544
|
-
var packageJsonPath =
|
|
50545
|
-
var packageJson = JSON.parse(
|
|
51552
|
+
var packageJsonPath = join72(packageRoot(), "package.json");
|
|
51553
|
+
var packageJson = JSON.parse(readFileSync30(packageJsonPath, "utf-8"));
|
|
50546
51554
|
var CLI_VERSION = packageJson.version;
|
|
50547
51555
|
var program = new Command33();
|
|
50548
51556
|
var commandName = process.argv[1]?.endsWith("sklx") ? "sklx" : "skillsmith";
|
|
50549
51557
|
program.name(commandName).description(
|
|
50550
|
-
"
|
|
51558
|
+
"A registry for sharing, scanning, and tracking agent skills across teams. (alias: sklx)"
|
|
50551
51559
|
).version(CLI_VERSION).option(
|
|
50552
51560
|
"--quiet",
|
|
50553
51561
|
"Suppress advisory/progress output across all commands (sets SKILLSMITH_QUIET)"
|