@skillsmith/cli 0.8.9 → 0.8.10
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 +4 -0
- package/README.md +2 -2
- package/dist/.skillsmith-dist-hash +1 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/cli.js +436 -74
- package/package.json +4 -4
package/dist/cli.js
CHANGED
|
@@ -21005,6 +21005,60 @@ function safePatternMatch(value, pattern) {
|
|
|
21005
21005
|
}
|
|
21006
21006
|
}
|
|
21007
21007
|
|
|
21008
|
+
// ../core/dist/src/security/scanner/patterns.sensitive-path.js
|
|
21009
|
+
var ENV_PATH_PATTERN = /\.env(?![A-Za-z])(?!\.(?:example|sample|template|schema|dist))/i;
|
|
21010
|
+
var API_KEY_KEYWORD = /api[_-]?key/i;
|
|
21011
|
+
var AUTH_TOKEN_KEYWORD = /auth[_-]?token/i;
|
|
21012
|
+
var CREDENTIALS_FILE_PATTERN = /credentials\.(?:json|ya?ml|env|toml|txt)/i;
|
|
21013
|
+
var CREDENTIALS_ASSIGN_PATTERN = /credentials\s*[:=]/i;
|
|
21014
|
+
var SECRETS_ASSIGN_PATTERN = /\bsecrets?\s*[:=]/i;
|
|
21015
|
+
var SECRETS_PATH_PATTERN = /\bsecrets?\/[a-z0-9_.-]+/i;
|
|
21016
|
+
var PEM_PATTERN = /\.pem$/i;
|
|
21017
|
+
var KEY_FILE_PATTERN = /\.key$/i;
|
|
21018
|
+
var CRT_PATTERN = /\.crt$/i;
|
|
21019
|
+
var PASSWORD_ASSIGN_PATTERN = /password\s*[:=]/i;
|
|
21020
|
+
var SSH_DIR_PATTERN = /~\/\.ssh/i;
|
|
21021
|
+
var AWS_DIR_PATTERN = /~\/\.aws/i;
|
|
21022
|
+
var CONFIG_DIR_PATTERN = /~\/\.config/i;
|
|
21023
|
+
var ETC_SYSTEM_FILE_PATTERN = /\/etc\/(?:passwd|shadow|sudoers|hosts)\b/i;
|
|
21024
|
+
var SENSITIVE_PATH_PATTERNS = [
|
|
21025
|
+
ENV_PATH_PATTERN,
|
|
21026
|
+
CREDENTIALS_FILE_PATTERN,
|
|
21027
|
+
CREDENTIALS_ASSIGN_PATTERN,
|
|
21028
|
+
SECRETS_ASSIGN_PATTERN,
|
|
21029
|
+
SECRETS_PATH_PATTERN,
|
|
21030
|
+
PEM_PATTERN,
|
|
21031
|
+
KEY_FILE_PATTERN,
|
|
21032
|
+
CRT_PATTERN,
|
|
21033
|
+
PASSWORD_ASSIGN_PATTERN,
|
|
21034
|
+
API_KEY_KEYWORD,
|
|
21035
|
+
AUTH_TOKEN_KEYWORD,
|
|
21036
|
+
SSH_DIR_PATTERN,
|
|
21037
|
+
AWS_DIR_PATTERN,
|
|
21038
|
+
CONFIG_DIR_PATTERN,
|
|
21039
|
+
ETC_SYSTEM_FILE_PATTERN
|
|
21040
|
+
];
|
|
21041
|
+
var VALUE_GATED_KEYWORD_PATTERNS = /* @__PURE__ */ new Set([
|
|
21042
|
+
API_KEY_KEYWORD,
|
|
21043
|
+
AUTH_TOKEN_KEYWORD
|
|
21044
|
+
]);
|
|
21045
|
+
var PATH_FORM_PATTERNS = /* @__PURE__ */ new Set([
|
|
21046
|
+
CREDENTIALS_FILE_PATTERN,
|
|
21047
|
+
SECRETS_PATH_PATTERN,
|
|
21048
|
+
PEM_PATTERN,
|
|
21049
|
+
KEY_FILE_PATTERN,
|
|
21050
|
+
CRT_PATTERN,
|
|
21051
|
+
SSH_DIR_PATTERN,
|
|
21052
|
+
AWS_DIR_PATTERN,
|
|
21053
|
+
CONFIG_DIR_PATTERN,
|
|
21054
|
+
ETC_SYSTEM_FILE_PATTERN
|
|
21055
|
+
]);
|
|
21056
|
+
var VALUE_GATED_ASSIGNMENT_PATTERNS = /* @__PURE__ */ new Set([
|
|
21057
|
+
CREDENTIALS_ASSIGN_PATTERN,
|
|
21058
|
+
SECRETS_ASSIGN_PATTERN,
|
|
21059
|
+
PASSWORD_ASSIGN_PATTERN
|
|
21060
|
+
]);
|
|
21061
|
+
|
|
21008
21062
|
// ../core/dist/src/security/scanner/patterns.jailbreak.js
|
|
21009
21063
|
var JB_IGNORE_PREVIOUS = /ignore\s+(all\s+)?(previous|prior|above)\s+(instructions?|prompts?|rules?)/i;
|
|
21010
21064
|
var JB_DISREGARD_PREVIOUS = /disregard\s+(all\s+)?(previous|prior|your)\s+(instructions?|programming)/i;
|
|
@@ -21290,37 +21344,7 @@ var ANON_PASTE_HOSTS = [
|
|
|
21290
21344
|
];
|
|
21291
21345
|
var TRANSIENT_TRANSFER_HOSTS = ["transfer.sh", "file.io", "tmpfiles.org", "temp.sh"];
|
|
21292
21346
|
var URL_SHORTENER_DOMAINS = ["bit.ly", "tinyurl.com", "t.co", "is.gd"];
|
|
21293
|
-
var
|
|
21294
|
-
var API_KEY_KEYWORD = /api[_-]?key/i;
|
|
21295
|
-
var AUTH_TOKEN_KEYWORD = /auth[_-]?token/i;
|
|
21296
|
-
var SENSITIVE_PATH_PATTERNS = [
|
|
21297
|
-
ENV_PATH_PATTERN,
|
|
21298
|
-
// Contextual credentials: filename or assignment, not bare prose
|
|
21299
|
-
/credentials\.(?:json|ya?ml|env|toml|txt)/i,
|
|
21300
|
-
/credentials\s*[:=]/i,
|
|
21301
|
-
// Contextual secrets: assignment or path, not bare word
|
|
21302
|
-
/\bsecrets?\s*[:=]/i,
|
|
21303
|
-
/\bsecrets?\/[a-z0-9_.-]+/i,
|
|
21304
|
-
/\.pem$/i,
|
|
21305
|
-
/\.key$/i,
|
|
21306
|
-
/\.crt$/i,
|
|
21307
|
-
// Contextual password: assignment or URL (postgres://user:pass@host) only
|
|
21308
|
-
/password\s*[:=]/i,
|
|
21309
|
-
API_KEY_KEYWORD,
|
|
21310
|
-
AUTH_TOKEN_KEYWORD,
|
|
21311
|
-
/~\/\.ssh/i,
|
|
21312
|
-
/~\/\.aws/i,
|
|
21313
|
-
/~\/\.config/i,
|
|
21314
|
-
// SMI-4396 Wave 2: explicit system-file paths. Added so that tightening
|
|
21315
|
-
// bare /credentials/i and /password/i into assignment-context variants
|
|
21316
|
-
// doesn't drop coverage of obvious sensitive references like /etc/passwd.
|
|
21317
|
-
/\/etc\/(?:passwd|shadow|sudoers|hosts)\b/i
|
|
21318
|
-
];
|
|
21319
|
-
var VALUE_GATED_KEYWORD_PATTERNS = /* @__PURE__ */ new Set([
|
|
21320
|
-
API_KEY_KEYWORD,
|
|
21321
|
-
AUTH_TOKEN_KEYWORD
|
|
21322
|
-
]);
|
|
21323
|
-
var SCANNER_RULESET_VERSION = "2026-08-15.1";
|
|
21347
|
+
var SCANNER_RULESET_VERSION = "2026-09-07.1";
|
|
21324
21348
|
var SUSPICIOUS_PATTERNS = [
|
|
21325
21349
|
/eval\s*\(/i,
|
|
21326
21350
|
/exec\s*\(/i,
|
|
@@ -21647,7 +21671,7 @@ var CATEGORY_WEIGHTS = {
|
|
|
21647
21671
|
|
|
21648
21672
|
// ../core/dist/src/security/scanner/regex-utils.js
|
|
21649
21673
|
var MAX_LINE_LENGTH_FOR_REGEX = 1e4;
|
|
21650
|
-
var MAX_CONTENT_LENGTH_FOR_REGEX =
|
|
21674
|
+
var MAX_CONTENT_LENGTH_FOR_REGEX = 1e6;
|
|
21651
21675
|
function safeRegexTest(pattern, input7, maxLength = MAX_LINE_LENGTH_FOR_REGEX) {
|
|
21652
21676
|
const safeInput = input7.length > maxLength ? input7.slice(0, maxLength) : input7;
|
|
21653
21677
|
return safeInput.match(pattern);
|
|
@@ -21956,34 +21980,56 @@ function isWithinInlineCode(line, matchIndex) {
|
|
|
21956
21980
|
}
|
|
21957
21981
|
return false;
|
|
21958
21982
|
}
|
|
21983
|
+
var MAX_MULTILINE_LINES_PER_PATTERN = 64;
|
|
21984
|
+
var MAX_MULTILINE_ITERATIONS_PER_PATTERN = 1e4;
|
|
21959
21985
|
function scanPatternsWithMultilineSupport(content, config2, lineContexts, maxLength) {
|
|
21960
21986
|
const lines = content.split("\n");
|
|
21961
21987
|
const contexts = lineContexts ?? analyzeMarkdownContext(content);
|
|
21962
21988
|
const bestByLine = /* @__PURE__ */ new Map();
|
|
21989
|
+
let truncated = false;
|
|
21963
21990
|
const rank = (t) => EVIDENCE_RANK[t];
|
|
21991
|
+
const scannedContent = maxLength !== void 0 && content.length > maxLength ? content.slice(0, maxLength) : content;
|
|
21964
21992
|
for (const pattern of config2.patterns) {
|
|
21965
21993
|
if (resolvePatternScope(pattern) === "line")
|
|
21966
21994
|
continue;
|
|
21967
|
-
const
|
|
21968
|
-
|
|
21969
|
-
|
|
21970
|
-
|
|
21971
|
-
|
|
21972
|
-
|
|
21973
|
-
|
|
21974
|
-
|
|
21975
|
-
|
|
21976
|
-
|
|
21977
|
-
|
|
21978
|
-
|
|
21979
|
-
|
|
21980
|
-
|
|
21981
|
-
|
|
21982
|
-
|
|
21983
|
-
|
|
21984
|
-
|
|
21985
|
-
|
|
21986
|
-
|
|
21995
|
+
const flags = pattern.flags.includes("g") ? pattern.flags : pattern.flags + "g";
|
|
21996
|
+
const g = new RegExp(pattern.source, flags);
|
|
21997
|
+
const seenLines = /* @__PURE__ */ new Set();
|
|
21998
|
+
let iterations = 0;
|
|
21999
|
+
let m;
|
|
22000
|
+
while ((m = g.exec(scannedContent)) !== null) {
|
|
22001
|
+
if (m[0].length === 0)
|
|
22002
|
+
g.lastIndex++;
|
|
22003
|
+
if (++iterations > MAX_MULTILINE_ITERATIONS_PER_PATTERN) {
|
|
22004
|
+
truncated = true;
|
|
22005
|
+
break;
|
|
22006
|
+
}
|
|
22007
|
+
const matchIndex = m.index;
|
|
22008
|
+
let contentIndex = matchIndex;
|
|
22009
|
+
while (scannedContent[contentIndex] === "\n")
|
|
22010
|
+
contentIndex++;
|
|
22011
|
+
const lineNumber = scannedContent.slice(0, contentIndex).split("\n").length;
|
|
22012
|
+
if (seenLines.has(lineNumber))
|
|
22013
|
+
continue;
|
|
22014
|
+
if (seenLines.size >= MAX_MULTILINE_LINES_PER_PATTERN)
|
|
22015
|
+
break;
|
|
22016
|
+
seenLines.add(lineNumber);
|
|
22017
|
+
const ctx = contexts[lineNumber - 1];
|
|
22018
|
+
const matchLine = lines[lineNumber - 1] ?? "";
|
|
22019
|
+
const lineOffset = scannedContent.lastIndexOf("\n", contentIndex - 1) + 1;
|
|
22020
|
+
const matchCol = contentIndex - lineOffset;
|
|
22021
|
+
const inInlineCode = ctx?.isInlineCode && isWithinInlineCode(matchLine, matchCol);
|
|
22022
|
+
const inDocContext = ctx ? isDocumentationContext(ctx) || inInlineCode : false;
|
|
22023
|
+
const tier = config2.classify(pattern);
|
|
22024
|
+
const incumbent = bestByLine.get(lineNumber);
|
|
22025
|
+
if (!incumbent || rank(tier) > rank(incumbent.tier)) {
|
|
22026
|
+
bestByLine.set(lineNumber, {
|
|
22027
|
+
tier,
|
|
22028
|
+
matchText: m[0],
|
|
22029
|
+
location: m[0].trim().slice(0, 100),
|
|
22030
|
+
inDocContext
|
|
22031
|
+
});
|
|
22032
|
+
}
|
|
21987
22033
|
}
|
|
21988
22034
|
}
|
|
21989
22035
|
lines.forEach((line, index) => {
|
|
@@ -22020,11 +22066,11 @@ function scanPatternsWithMultilineSupport(content, config2, lineContexts, maxLen
|
|
|
22020
22066
|
if (!candidate)
|
|
22021
22067
|
continue;
|
|
22022
22068
|
const { severity, confidence } = resolveEvidenceSeverity(candidate.tier, candidate.inDocContext);
|
|
22023
|
-
const
|
|
22069
|
+
const truncatedText = candidate.matchText.slice(0, 50);
|
|
22024
22070
|
findings.push({
|
|
22025
22071
|
type: config2.type,
|
|
22026
22072
|
severity,
|
|
22027
|
-
message: `${config2.messagePrefix}: "${
|
|
22073
|
+
message: `${config2.messagePrefix}: "${truncatedText}${candidate.matchText.length > 50 ? "..." : ""}"`,
|
|
22028
22074
|
location: candidate.location,
|
|
22029
22075
|
lineNumber,
|
|
22030
22076
|
category: config2.type,
|
|
@@ -22033,7 +22079,7 @@ function scanPatternsWithMultilineSupport(content, config2, lineContexts, maxLen
|
|
|
22033
22079
|
evidenceType: candidate.tier
|
|
22034
22080
|
});
|
|
22035
22081
|
}
|
|
22036
|
-
return findings;
|
|
22082
|
+
return { findings, truncated };
|
|
22037
22083
|
}
|
|
22038
22084
|
|
|
22039
22085
|
// ../core/dist/src/security/scanner/confusables.js
|
|
@@ -22629,6 +22675,279 @@ function scanPiiPatterns(content, lineContexts) {
|
|
|
22629
22675
|
return findings;
|
|
22630
22676
|
}
|
|
22631
22677
|
|
|
22678
|
+
// ../core/dist/src/security/scanner/SecurityScanner.prose-lexicon.js
|
|
22679
|
+
var NEGATION_TOKENS = /* @__PURE__ */ new Set([
|
|
22680
|
+
"no",
|
|
22681
|
+
"not",
|
|
22682
|
+
"never",
|
|
22683
|
+
"without",
|
|
22684
|
+
"cannot",
|
|
22685
|
+
"can't",
|
|
22686
|
+
"doesn't",
|
|
22687
|
+
"don't",
|
|
22688
|
+
"won't",
|
|
22689
|
+
"didn't",
|
|
22690
|
+
"isn't",
|
|
22691
|
+
"aren't",
|
|
22692
|
+
"nor",
|
|
22693
|
+
"neither"
|
|
22694
|
+
]);
|
|
22695
|
+
var NOUN_DETERMINERS = /* @__PURE__ */ new Set([
|
|
22696
|
+
"the",
|
|
22697
|
+
"a",
|
|
22698
|
+
"an",
|
|
22699
|
+
"my",
|
|
22700
|
+
"your",
|
|
22701
|
+
"our",
|
|
22702
|
+
"their",
|
|
22703
|
+
"its",
|
|
22704
|
+
"his",
|
|
22705
|
+
"her"
|
|
22706
|
+
]);
|
|
22707
|
+
var DETECTION_FRAMING = /* @__PURE__ */ new Set([
|
|
22708
|
+
"detect",
|
|
22709
|
+
"detects",
|
|
22710
|
+
"detecting",
|
|
22711
|
+
"detection",
|
|
22712
|
+
"detector",
|
|
22713
|
+
"detectors",
|
|
22714
|
+
"flag",
|
|
22715
|
+
"flags",
|
|
22716
|
+
"flagging",
|
|
22717
|
+
"flagged",
|
|
22718
|
+
"identify",
|
|
22719
|
+
"identifies",
|
|
22720
|
+
"block",
|
|
22721
|
+
"blocks",
|
|
22722
|
+
"blocking",
|
|
22723
|
+
"prevent",
|
|
22724
|
+
"prevents",
|
|
22725
|
+
"warn",
|
|
22726
|
+
"warns",
|
|
22727
|
+
"describe",
|
|
22728
|
+
"describes",
|
|
22729
|
+
"explain",
|
|
22730
|
+
"explains",
|
|
22731
|
+
"scan",
|
|
22732
|
+
"scans",
|
|
22733
|
+
"scanning",
|
|
22734
|
+
"scanner",
|
|
22735
|
+
"audit",
|
|
22736
|
+
"audits",
|
|
22737
|
+
"monitor",
|
|
22738
|
+
"monitors",
|
|
22739
|
+
"catch",
|
|
22740
|
+
"catches",
|
|
22741
|
+
"guard",
|
|
22742
|
+
"guards",
|
|
22743
|
+
"protect",
|
|
22744
|
+
"protects",
|
|
22745
|
+
"rule",
|
|
22746
|
+
"rules",
|
|
22747
|
+
"linter",
|
|
22748
|
+
"analyzer"
|
|
22749
|
+
]);
|
|
22750
|
+
var RELATIVE_MARKERS = /* @__PURE__ */ new Set(["that", "which", "who", "whom", "whose"]);
|
|
22751
|
+
var PROSE_STOPWORDS = /* @__PURE__ */ new Set([
|
|
22752
|
+
"never",
|
|
22753
|
+
"not",
|
|
22754
|
+
"your",
|
|
22755
|
+
"you",
|
|
22756
|
+
"this",
|
|
22757
|
+
"that",
|
|
22758
|
+
"these",
|
|
22759
|
+
"those",
|
|
22760
|
+
"is",
|
|
22761
|
+
"are",
|
|
22762
|
+
"was",
|
|
22763
|
+
"were",
|
|
22764
|
+
"be",
|
|
22765
|
+
"been",
|
|
22766
|
+
"will",
|
|
22767
|
+
"would",
|
|
22768
|
+
"can",
|
|
22769
|
+
"could",
|
|
22770
|
+
"should",
|
|
22771
|
+
"must",
|
|
22772
|
+
"do",
|
|
22773
|
+
"does",
|
|
22774
|
+
"into",
|
|
22775
|
+
"onto",
|
|
22776
|
+
"from",
|
|
22777
|
+
"with",
|
|
22778
|
+
"without",
|
|
22779
|
+
"by",
|
|
22780
|
+
"for",
|
|
22781
|
+
"of",
|
|
22782
|
+
"to",
|
|
22783
|
+
"in",
|
|
22784
|
+
"on",
|
|
22785
|
+
"at",
|
|
22786
|
+
"as",
|
|
22787
|
+
"and",
|
|
22788
|
+
"or",
|
|
22789
|
+
"but",
|
|
22790
|
+
"if",
|
|
22791
|
+
"when",
|
|
22792
|
+
"where",
|
|
22793
|
+
"how",
|
|
22794
|
+
"why",
|
|
22795
|
+
"use",
|
|
22796
|
+
"using",
|
|
22797
|
+
"used",
|
|
22798
|
+
"see",
|
|
22799
|
+
"via",
|
|
22800
|
+
"per",
|
|
22801
|
+
"they",
|
|
22802
|
+
"them",
|
|
22803
|
+
"their",
|
|
22804
|
+
"it",
|
|
22805
|
+
"its",
|
|
22806
|
+
"we",
|
|
22807
|
+
"our",
|
|
22808
|
+
"us",
|
|
22809
|
+
"please",
|
|
22810
|
+
"ensure",
|
|
22811
|
+
"make",
|
|
22812
|
+
"sure",
|
|
22813
|
+
"the",
|
|
22814
|
+
"a",
|
|
22815
|
+
"an",
|
|
22816
|
+
"all",
|
|
22817
|
+
"any",
|
|
22818
|
+
"each",
|
|
22819
|
+
"no",
|
|
22820
|
+
"nor",
|
|
22821
|
+
"so",
|
|
22822
|
+
"than",
|
|
22823
|
+
"then",
|
|
22824
|
+
"there",
|
|
22825
|
+
"here",
|
|
22826
|
+
"what",
|
|
22827
|
+
"which",
|
|
22828
|
+
"who",
|
|
22829
|
+
"while",
|
|
22830
|
+
"during",
|
|
22831
|
+
"after",
|
|
22832
|
+
"before",
|
|
22833
|
+
"above",
|
|
22834
|
+
"below",
|
|
22835
|
+
"up",
|
|
22836
|
+
"down",
|
|
22837
|
+
"out",
|
|
22838
|
+
"off",
|
|
22839
|
+
"over",
|
|
22840
|
+
"under",
|
|
22841
|
+
"again",
|
|
22842
|
+
"further",
|
|
22843
|
+
"once",
|
|
22844
|
+
"only",
|
|
22845
|
+
"own",
|
|
22846
|
+
"same",
|
|
22847
|
+
"too",
|
|
22848
|
+
"very",
|
|
22849
|
+
"just",
|
|
22850
|
+
"now"
|
|
22851
|
+
]);
|
|
22852
|
+
|
|
22853
|
+
// ../core/dist/src/security/scanner/SecurityScanner.action-context.js
|
|
22854
|
+
var ACTION_VERBS = "cat|cp|mv|scp|rsync|curl|wget|fetch|tee|tar|zip|gzip|base64|xxd|dd|nc|netcat|chmod|ssh-add|openssl|uploads?|downloads?|sends?|posts?|exfiltrates?|steals?";
|
|
22855
|
+
var ACTION_VERB_EXACT = new RegExp(`^(?:${ACTION_VERBS})$`, "i");
|
|
22856
|
+
var SHELL_OPERATOR = /[|>]/;
|
|
22857
|
+
var NEGATION_LOOKBACK = 3;
|
|
22858
|
+
var NEGATION_LOOKAHEAD = 2;
|
|
22859
|
+
var RELATIVE_CLAUSE_LOOKBACK = 2;
|
|
22860
|
+
function inRelativeClause(norm, i) {
|
|
22861
|
+
for (let j = Math.max(0, i - RELATIVE_CLAUSE_LOOKBACK); j < i; j++) {
|
|
22862
|
+
if (RELATIVE_MARKERS.has(norm[j]))
|
|
22863
|
+
return true;
|
|
22864
|
+
}
|
|
22865
|
+
return false;
|
|
22866
|
+
}
|
|
22867
|
+
function hasActionEvidence(rawLine) {
|
|
22868
|
+
const line = rawLine.slice(0, MAX_LINE_LENGTH_FOR_REGEX);
|
|
22869
|
+
if (SHELL_OPERATOR.test(line))
|
|
22870
|
+
return true;
|
|
22871
|
+
const words = line.split(/\s+/);
|
|
22872
|
+
const norm = words.map((w) => w.toLowerCase().replace(/[^a-z']/g, ""));
|
|
22873
|
+
const describesDetection = norm.some((w) => DETECTION_FRAMING.has(w));
|
|
22874
|
+
for (let i = 0; i < words.length; i++) {
|
|
22875
|
+
if (!ACTION_VERB_EXACT.test(words[i].replace(/[^A-Za-z-]/g, "")))
|
|
22876
|
+
continue;
|
|
22877
|
+
if (i > 0 && NOUN_DETERMINERS.has(norm[i - 1]))
|
|
22878
|
+
continue;
|
|
22879
|
+
if (describesDetection && inRelativeClause(norm, i))
|
|
22880
|
+
continue;
|
|
22881
|
+
let negated = false;
|
|
22882
|
+
for (let j = Math.max(0, i - NEGATION_LOOKBACK); j <= Math.min(norm.length - 1, i + NEGATION_LOOKAHEAD); j++) {
|
|
22883
|
+
if (j !== i && NEGATION_TOKENS.has(norm[j])) {
|
|
22884
|
+
negated = true;
|
|
22885
|
+
break;
|
|
22886
|
+
}
|
|
22887
|
+
}
|
|
22888
|
+
if (!negated)
|
|
22889
|
+
return true;
|
|
22890
|
+
}
|
|
22891
|
+
return false;
|
|
22892
|
+
}
|
|
22893
|
+
var MAX_PATH_ACTION_LINE_DISTANCE = 1;
|
|
22894
|
+
function hasPathActionContext(lines, index) {
|
|
22895
|
+
const lo = Math.max(0, index - MAX_PATH_ACTION_LINE_DISTANCE);
|
|
22896
|
+
const hi = Math.min(lines.length - 1, index + MAX_PATH_ACTION_LINE_DISTANCE);
|
|
22897
|
+
for (let i = lo; i <= hi; i++) {
|
|
22898
|
+
if (hasActionEvidence(lines[i]))
|
|
22899
|
+
return true;
|
|
22900
|
+
}
|
|
22901
|
+
return false;
|
|
22902
|
+
}
|
|
22903
|
+
|
|
22904
|
+
// ../core/dist/src/security/scanner/SecurityScanner.value-gate.js
|
|
22905
|
+
var TEMPLATE_REFERENCE = /^\$|^\{\{|\$\{|^%[A-Za-z_]|^<%/;
|
|
22906
|
+
var MAX_LABEL_TOKENS = 2;
|
|
22907
|
+
var ASSIGNMENT_HEAD = /(?:credentials|\bsecrets?|password)\s*[:=]\s*/gi;
|
|
22908
|
+
function isProseValue(span) {
|
|
22909
|
+
const v = span.replace(/^['"]|['"]$/g, "").trim();
|
|
22910
|
+
if (v.length === 0)
|
|
22911
|
+
return true;
|
|
22912
|
+
if (TEMPLATE_REFERENCE.test(v))
|
|
22913
|
+
return true;
|
|
22914
|
+
if (PLACEHOLDER_SECRET_RE.test(v))
|
|
22915
|
+
return true;
|
|
22916
|
+
if (/^(.)\1+$/.test(v))
|
|
22917
|
+
return true;
|
|
22918
|
+
const tokens = v.split(/\s+/);
|
|
22919
|
+
if (tokens.some((t) => PROSE_STOPWORDS.has(t.toLowerCase().replace(/[^a-z']/g, ""))))
|
|
22920
|
+
return true;
|
|
22921
|
+
if (tokens.length > 12)
|
|
22922
|
+
return true;
|
|
22923
|
+
if (tokens.length > 1 && tokens.length <= MAX_LABEL_TOKENS && tokens.every((t) => /^[a-z]{1,19}$/.test(t)))
|
|
22924
|
+
return true;
|
|
22925
|
+
return false;
|
|
22926
|
+
}
|
|
22927
|
+
function assignmentHasRealValue(lines, index) {
|
|
22928
|
+
const line = lines[index].slice(0, MAX_LINE_LENGTH_FOR_REGEX);
|
|
22929
|
+
const heads = [...line.matchAll(ASSIGNMENT_HEAD)];
|
|
22930
|
+
if (heads.length === 0)
|
|
22931
|
+
return false;
|
|
22932
|
+
let trailingKeyIsBare = false;
|
|
22933
|
+
for (let i = 0; i < heads.length; i++) {
|
|
22934
|
+
const from = (heads[i].index ?? 0) + heads[i][0].length;
|
|
22935
|
+
const to = i + 1 < heads.length ? heads[i + 1].index ?? line.length : line.length;
|
|
22936
|
+
const value = line.slice(from, to);
|
|
22937
|
+
if (value.trim().length === 0) {
|
|
22938
|
+
trailingKeyIsBare = i === heads.length - 1;
|
|
22939
|
+
continue;
|
|
22940
|
+
}
|
|
22941
|
+
trailingKeyIsBare = false;
|
|
22942
|
+
if (!isProseValue(value))
|
|
22943
|
+
return true;
|
|
22944
|
+
}
|
|
22945
|
+
if (!trailingKeyIsBare)
|
|
22946
|
+
return false;
|
|
22947
|
+
const next = lines[index + 1];
|
|
22948
|
+
return next !== void 0 && !isProseValue(next.trim());
|
|
22949
|
+
}
|
|
22950
|
+
|
|
22632
22951
|
// ../core/dist/src/security/scanner/SecurityScanner.fetch-correlation.js
|
|
22633
22952
|
var FETCH_COMMAND_PATTERN = /\b(?:curl|wget)\b|\bgit\s+clone\b|\bnpx\b[^\n]{0,80}https?:\/\//i;
|
|
22634
22953
|
function escapeRegExp2(s) {
|
|
@@ -22813,6 +23132,10 @@ function scanSensitivePaths(content, lineContexts) {
|
|
|
22813
23132
|
severity = "medium";
|
|
22814
23133
|
} else if (pattern === ENV_PATH_PATTERN) {
|
|
22815
23134
|
severity = safeRegexCheck(ENV_EXFIL_CONTEXT, line) ? "high" : "medium";
|
|
23135
|
+
} else if (PATH_FORM_PATTERNS.has(pattern)) {
|
|
23136
|
+
severity = hasPathActionContext(lines, index) ? "high" : "medium";
|
|
23137
|
+
} else if (VALUE_GATED_ASSIGNMENT_PATTERNS.has(pattern)) {
|
|
23138
|
+
severity = assignmentHasRealValue(lines, index) ? "high" : "medium";
|
|
22816
23139
|
} else {
|
|
22817
23140
|
severity = "high";
|
|
22818
23141
|
}
|
|
@@ -22820,7 +23143,20 @@ function scanSensitivePaths(content, lineContexts) {
|
|
|
22820
23143
|
findings.push({
|
|
22821
23144
|
type: "sensitive_path",
|
|
22822
23145
|
severity,
|
|
22823
|
-
|
|
23146
|
+
// SMI-5207: the matched TEXT is prepended additively — `pattern.source` is
|
|
23147
|
+
// deliberately retained, not swapped out, because allowlist entries'
|
|
23148
|
+
// `messagePattern` values match on that regex-source substring.
|
|
23149
|
+
// Governance review L-5: `match?.[0] ?? ''` is kept (a plain `match[0]`
|
|
23150
|
+
// fails `tsc` — TS cannot correlate the separate `safeRegexCheck`/
|
|
23151
|
+
// `safeRegexTest` calls the way it narrows a local `if (match)` block, the
|
|
23152
|
+
// pattern this file's OTHER scanners use). Behaviorally the `?? ''`
|
|
23153
|
+
// fallback is unreachable: `safeRegexCheck` (.test()) already gated the
|
|
23154
|
+
// loop above on this same pattern/line pair, and for a non-`g` pattern
|
|
23155
|
+
// .test() and .match() can never disagree on the same input. If it ever
|
|
23156
|
+
// WAS reached, it would silently diverge from the edge twins (which emit
|
|
23157
|
+
// no finding at all rather than one with an empty quoted span) — a gap the
|
|
23158
|
+
// twin-parity test can't catch, since it compares files, not behavior.
|
|
23159
|
+
message: `Reference to potentially sensitive path: "${match?.[0]?.slice(0, 60) ?? ""}" (${pattern.source})`,
|
|
22824
23160
|
location: line.trim().slice(0, 100),
|
|
22825
23161
|
lineNumber: index + 1,
|
|
22826
23162
|
inDocumentationContext: inDocContext,
|
|
@@ -23485,6 +23821,7 @@ function scanDecoyMisdirection(content, lineContexts) {
|
|
|
23485
23821
|
}
|
|
23486
23822
|
|
|
23487
23823
|
// ../core/dist/src/security/scanner/SecurityScanner.js
|
|
23824
|
+
var DIRECTIVE_JAILBREAK_PATTERNS = JAILBREAK_PATTERNS.filter((p) => classifyEvidence(p) !== "mention");
|
|
23488
23825
|
var SecurityScanner = class {
|
|
23489
23826
|
allowedDomains;
|
|
23490
23827
|
blockedPatterns;
|
|
@@ -23603,13 +23940,26 @@ var SecurityScanner = class {
|
|
|
23603
23940
|
* text contains. This disables ONLY the encoded-payload detector on the
|
|
23604
23941
|
* inner call, not the rest of the suite — a decoded `curl|bash` still
|
|
23605
23942
|
* trips `code_execution`, decoded secrets still trip `sensitive_path`, etc.
|
|
23606
|
-
|
|
23607
|
-
|
|
23943
|
+
*
|
|
23944
|
+
* SMI-5879: `EncodedPayloadRescanner`'s contract is a bare
|
|
23945
|
+
* `SecurityFinding[]` return (SecurityScanner.encoding.ts, out of scope for
|
|
23946
|
+
* this change), so a recursive `runDetectors` call for decoded content has
|
|
23947
|
+
* no return channel of its own to report multiline truncation.
|
|
23948
|
+
* `truncationRef` is a shared mutable out-param instead: every call (outer
|
|
23949
|
+
* and any recursive rescan) ORs its own jailbreak/AI-defence truncation
|
|
23950
|
+
* into it, so truncation anywhere in the recursion is visible to the
|
|
23951
|
+
* original (outermost) caller. Per `ScanReport.multilineTruncated`'s own
|
|
23952
|
+
* doc: being truncated is only ever used to RAISE caution, never to lower
|
|
23953
|
+
* it, so over-reporting here (e.g. attributing an inner rescan's
|
|
23954
|
+
* truncation to the whole document) is safe by that same design.
|
|
23955
|
+
*/
|
|
23956
|
+
runDetectors(content, lineContexts, skipEncodedPayload, isHighTrustAuthor = false, isMarkdown = true, truncationRef) {
|
|
23608
23957
|
const findings = [];
|
|
23609
23958
|
const effectiveMultilineLimit = Math.min(MAX_CONTENT_LENGTH_FOR_REGEX, this.maxContentLength);
|
|
23610
23959
|
findings.push(...this.scanUrls(content));
|
|
23611
23960
|
findings.push(...scanSensitivePaths(content, lineContexts));
|
|
23612
|
-
|
|
23961
|
+
const jailbreakResult = this.scanJailbreakPatterns(content, lineContexts, effectiveMultilineLimit);
|
|
23962
|
+
findings.push(...jailbreakResult.findings);
|
|
23613
23963
|
findings.push(...this.scanSuspiciousPatterns(content, lineContexts));
|
|
23614
23964
|
findings.push(...scanSocialEngineering(content, lineContexts));
|
|
23615
23965
|
findings.push(...scanPromptLeaking(content, lineContexts));
|
|
@@ -23621,7 +23971,11 @@ var SecurityScanner = class {
|
|
|
23621
23971
|
findings.push(...scanArchiveEvasion(content, lineContexts));
|
|
23622
23972
|
findings.push(...scanPasteHostFetch(content, lineContexts));
|
|
23623
23973
|
findings.push(...scanDecoyMisdirection(content, lineContexts));
|
|
23624
|
-
|
|
23974
|
+
const aiDefenceResult = this.scanAIDefenceVulnerabilities(content, lineContexts, effectiveMultilineLimit);
|
|
23975
|
+
findings.push(...aiDefenceResult.findings);
|
|
23976
|
+
if (truncationRef) {
|
|
23977
|
+
truncationRef.truncated = truncationRef.truncated || jailbreakResult.truncated || aiDefenceResult.truncated;
|
|
23978
|
+
}
|
|
23625
23979
|
findings.push(...scanSsrfPatterns(content, lineContexts, effectiveMultilineLimit));
|
|
23626
23980
|
findings.push(...scanPiiPatterns(content, lineContexts));
|
|
23627
23981
|
findings.push(...scanCodeExecution(content, lineContexts));
|
|
@@ -23629,7 +23983,7 @@ var SecurityScanner = class {
|
|
|
23629
23983
|
escalateCodeExecution(findings);
|
|
23630
23984
|
escalateCorroboratedMentions(findings);
|
|
23631
23985
|
if (!skipEncodedPayload) {
|
|
23632
|
-
findings.push(...scanEncodedPayload(content, lineContexts, (decodedContent) => this.runDetectors(decodedContent, analyzeMarkdownContext(decodedContent, isMarkdown), true, isHighTrustAuthor, isMarkdown)));
|
|
23986
|
+
findings.push(...scanEncodedPayload(content, lineContexts, (decodedContent) => this.runDetectors(decodedContent, analyzeMarkdownContext(decodedContent, isMarkdown), true, isHighTrustAuthor, isMarkdown, truncationRef)));
|
|
23633
23987
|
}
|
|
23634
23988
|
return findings;
|
|
23635
23989
|
}
|
|
@@ -23671,7 +24025,8 @@ var SecurityScanner = class {
|
|
|
23671
24025
|
message: `Multiline regex scan truncated at ${effectiveMultilineLimit} code units (content is ${content.length} code units; configured maxContentLength is ${this.maxContentLength} code units)`
|
|
23672
24026
|
});
|
|
23673
24027
|
}
|
|
23674
|
-
|
|
24028
|
+
const truncationRef = { truncated: false };
|
|
24029
|
+
findings.push(...this.runDetectors(content, lineContexts, false, isHighTrustAuthor, isMarkdown, truncationRef));
|
|
23675
24030
|
const endTime = performance.now();
|
|
23676
24031
|
const { total: riskScore, breakdown: riskBreakdown } = calculateRiskScore(findings);
|
|
23677
24032
|
const hasCritical = findings.some((f) => f.severity === "critical");
|
|
@@ -23684,11 +24039,17 @@ var SecurityScanner = class {
|
|
|
23684
24039
|
scannedAt: /* @__PURE__ */ new Date(),
|
|
23685
24040
|
scanDurationMs: endTime - startTime,
|
|
23686
24041
|
riskScore,
|
|
23687
|
-
riskBreakdown
|
|
24042
|
+
riskBreakdown,
|
|
24043
|
+
multilineTruncated: truncationRef.truncated
|
|
23688
24044
|
};
|
|
23689
24045
|
}
|
|
24046
|
+
/**
|
|
24047
|
+
* SMI-5879 (design §5): tests only the directive-tier derived subset (a
|
|
24048
|
+
* bare mention like "jailbreak" or "DAN" alone should not fail the quick
|
|
24049
|
+
* path — see DIRECTIVE_JAILBREAK_PATTERNS above).
|
|
24050
|
+
*/
|
|
23690
24051
|
quickCheck(content) {
|
|
23691
|
-
for (const pattern of
|
|
24052
|
+
for (const pattern of DIRECTIVE_JAILBREAK_PATTERNS) {
|
|
23692
24053
|
if (safeRegexCheck(pattern, content))
|
|
23693
24054
|
return false;
|
|
23694
24055
|
}
|
|
@@ -35163,6 +35524,17 @@ function checkCursorMcpArtifact(path30) {
|
|
|
35163
35524
|
};
|
|
35164
35525
|
}
|
|
35165
35526
|
|
|
35527
|
+
// ../core/dist/src/utils/skill-name.js
|
|
35528
|
+
var VALID_SKILL_NAME_RE = /^[a-z][a-z0-9-]*$/;
|
|
35529
|
+
function validateSkillName(name) {
|
|
35530
|
+
if (!name.trim())
|
|
35531
|
+
return "Skill name is required";
|
|
35532
|
+
if (!VALID_SKILL_NAME_RE.test(name)) {
|
|
35533
|
+
return "Skill name must start with a lowercase letter and contain only lowercase letters, digits, and hyphens (e.g. my-skill)";
|
|
35534
|
+
}
|
|
35535
|
+
return true;
|
|
35536
|
+
}
|
|
35537
|
+
|
|
35166
35538
|
// ../core/dist/src/provenance/git-config.js
|
|
35167
35539
|
import * as fs8 from "fs";
|
|
35168
35540
|
import * as path10 from "path";
|
|
@@ -37758,16 +38130,6 @@ import { mkdir as mkdir9, writeFile as writeFile6, readFile as readFile12, stat
|
|
|
37758
38130
|
import { dirname as dirname22, join as join51, resolve as resolve15 } from "path";
|
|
37759
38131
|
import { createHash as createHash10 } from "crypto";
|
|
37760
38132
|
|
|
37761
|
-
// src/utils/skill-name.ts
|
|
37762
|
-
var VALID_SKILL_NAME_RE = /^[a-z][a-z0-9-]*$/;
|
|
37763
|
-
function validateSkillName(name) {
|
|
37764
|
-
if (!name.trim()) return "Skill name is required";
|
|
37765
|
-
if (!VALID_SKILL_NAME_RE.test(name)) {
|
|
37766
|
-
return "Skill name must start with a lowercase letter and contain only lowercase letters, digits, and hyphens (e.g. my-skill)";
|
|
37767
|
-
}
|
|
37768
|
-
return true;
|
|
37769
|
-
}
|
|
37770
|
-
|
|
37771
38133
|
// src/commands/author/utils.ts
|
|
37772
38134
|
import { access as access3 } from "fs/promises";
|
|
37773
38135
|
import { mkdir as mkdir7 } from "fs/promises";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skillsmith/cli",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.10",
|
|
4
4
|
"description": "A registry for sharing, scanning, and tracking agent skills across teams.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -39,14 +39,14 @@
|
|
|
39
39
|
"yaml": "2.8.3"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@skillsmith/core": "^0.12.
|
|
43
|
-
"@skillsmith/mcp-server": "^0.7.
|
|
42
|
+
"@skillsmith/core": "^0.12.3",
|
|
43
|
+
"@skillsmith/mcp-server": "^0.7.14",
|
|
44
44
|
"@types/node": "^25.9.3",
|
|
45
45
|
"esbuild": "0.28.1",
|
|
46
46
|
"vitest": "4.1.11"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
|
-
"@smith-horn/enterprise": "^0.3.
|
|
49
|
+
"@smith-horn/enterprise": "^0.3.10"
|
|
50
50
|
},
|
|
51
51
|
"peerDependenciesMeta": {
|
|
52
52
|
"@smith-horn/enterprise": {
|