@skillsmith/core 0.11.7 → 0.12.1
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 +210 -0
- package/README.md +7 -8
- package/dist/.tsbuildinfo +1 -1
- package/dist/src/api/client.types.d.ts +9 -0
- package/dist/src/api/client.types.d.ts.map +1 -1
- package/dist/src/api/schemas.d.ts +6 -0
- package/dist/src/api/schemas.d.ts.map +1 -1
- package/dist/src/api/schemas.js +5 -0
- package/dist/src/api/schemas.js.map +1 -1
- package/dist/src/api/security-summary.d.ts +10 -1
- package/dist/src/api/security-summary.d.ts.map +1 -1
- package/dist/src/api/security-summary.js +9 -0
- package/dist/src/api/security-summary.js.map +1 -1
- package/dist/src/api/security-summary.test.js +79 -1
- package/dist/src/api/security-summary.test.js.map +1 -1
- package/dist/src/api/types.d.ts +9 -0
- package/dist/src/api/types.d.ts.map +1 -1
- package/dist/src/config/token-credentials.d.ts +20 -0
- package/dist/src/config/token-credentials.d.ts.map +1 -1
- package/dist/src/config/token-credentials.js +59 -0
- package/dist/src/config/token-credentials.js.map +1 -1
- package/dist/src/config/token-credentials.test.d.ts +3 -0
- package/dist/src/config/token-credentials.test.d.ts.map +1 -1
- package/dist/src/config/token-credentials.test.js +68 -0
- package/dist/src/config/token-credentials.test.js.map +1 -1
- package/dist/src/exports/services.d.ts +1 -1
- package/dist/src/exports/services.d.ts.map +1 -1
- package/dist/src/exports/services.js +3 -1
- package/dist/src/exports/services.js.map +1 -1
- package/dist/src/index.d.ts +3 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/install/agent-config-merge.json-array.d.ts +8 -0
- package/dist/src/install/agent-config-merge.json-array.d.ts.map +1 -1
- package/dist/src/install/agent-config-merge.json-array.js +22 -2
- package/dist/src/install/agent-config-merge.json-array.js.map +1 -1
- package/dist/src/install/agent-pack-installer.cursor-hooks.d.ts.map +1 -1
- package/dist/src/install/agent-pack-installer.cursor-hooks.js +122 -3
- package/dist/src/install/agent-pack-installer.cursor-hooks.js.map +1 -1
- package/dist/src/install/agent-pack-installer.cursor-hooks.test.js +146 -1
- package/dist/src/install/agent-pack-installer.cursor-hooks.test.js.map +1 -1
- package/dist/src/install/agent-pack-installer.harness.d.ts +8 -0
- package/dist/src/install/agent-pack-installer.harness.d.ts.map +1 -1
- package/dist/src/install/agent-pack-installer.harness.js +8 -1
- package/dist/src/install/agent-pack-installer.harness.js.map +1 -1
- package/dist/src/scripts/__tests__/scan-imported-skills.test.js +25 -0
- package/dist/src/scripts/__tests__/scan-imported-skills.test.js.map +1 -1
- package/dist/src/scripts/skill-scanner/trust-scorer.js +1 -1
- package/dist/src/scripts/skill-scanner/trust-scorer.js.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.archive.d.ts +34 -0
- package/dist/src/security/scanner/SecurityScanner.archive.d.ts.map +1 -0
- package/dist/src/security/scanner/SecurityScanner.archive.js +218 -0
- package/dist/src/security/scanner/SecurityScanner.archive.js.map +1 -0
- package/dist/src/security/scanner/SecurityScanner.compound.d.ts +1 -0
- package/dist/src/security/scanner/SecurityScanner.compound.d.ts.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.compound.js +117 -38
- package/dist/src/security/scanner/SecurityScanner.compound.js.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.d.ts +41 -4
- package/dist/src/security/scanner/SecurityScanner.d.ts.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.decoy.d.ts +72 -0
- package/dist/src/security/scanner/SecurityScanner.decoy.d.ts.map +1 -0
- package/dist/src/security/scanner/SecurityScanner.decoy.js +265 -0
- package/dist/src/security/scanner/SecurityScanner.decoy.js.map +1 -0
- package/dist/src/security/scanner/SecurityScanner.encoding.d.ts +56 -0
- package/dist/src/security/scanner/SecurityScanner.encoding.d.ts.map +1 -0
- package/dist/src/security/scanner/SecurityScanner.encoding.js +270 -0
- package/dist/src/security/scanner/SecurityScanner.encoding.js.map +1 -0
- package/dist/src/security/scanner/SecurityScanner.exec.d.ts +23 -20
- package/dist/src/security/scanner/SecurityScanner.exec.d.ts.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.exec.js +207 -52
- package/dist/src/security/scanner/SecurityScanner.exec.js.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.fetch-correlation.d.ts +115 -0
- package/dist/src/security/scanner/SecurityScanner.fetch-correlation.d.ts.map +1 -0
- package/dist/src/security/scanner/SecurityScanner.fetch-correlation.js +181 -0
- package/dist/src/security/scanner/SecurityScanner.fetch-correlation.js.map +1 -0
- package/dist/src/security/scanner/SecurityScanner.helpers.d.ts +14 -11
- package/dist/src/security/scanner/SecurityScanner.helpers.d.ts.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.helpers.js +26 -124
- package/dist/src/security/scanner/SecurityScanner.helpers.js.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.js +114 -36
- package/dist/src/security/scanner/SecurityScanner.js.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.paste-host.d.ts +69 -0
- package/dist/src/security/scanner/SecurityScanner.paste-host.d.ts.map +1 -0
- package/dist/src/security/scanner/SecurityScanner.paste-host.js +342 -0
- package/dist/src/security/scanner/SecurityScanner.paste-host.js.map +1 -0
- package/dist/src/security/scanner/SecurityScanner.risk-score.d.ts +27 -0
- package/dist/src/security/scanner/SecurityScanner.risk-score.d.ts.map +1 -0
- package/dist/src/security/scanner/SecurityScanner.risk-score.js +178 -0
- package/dist/src/security/scanner/SecurityScanner.risk-score.js.map +1 -0
- package/dist/src/security/scanner/SecurityScanner.urls.d.ts +20 -0
- package/dist/src/security/scanner/SecurityScanner.urls.d.ts.map +1 -0
- package/dist/src/security/scanner/SecurityScanner.urls.js +26 -0
- package/dist/src/security/scanner/SecurityScanner.urls.js.map +1 -0
- package/dist/src/security/scanner/index.d.ts +2 -2
- package/dist/src/security/scanner/index.d.ts.map +1 -1
- package/dist/src/security/scanner/index.js +5 -1
- package/dist/src/security/scanner/index.js.map +1 -1
- package/dist/src/security/scanner/multiline-category-closure.test.js +6 -0
- package/dist/src/security/scanner/multiline-category-closure.test.js.map +1 -1
- package/dist/src/security/scanner/patterns.d.ts +41 -21
- package/dist/src/security/scanner/patterns.d.ts.map +1 -1
- package/dist/src/security/scanner/patterns.exec.d.ts +89 -0
- package/dist/src/security/scanner/patterns.exec.d.ts.map +1 -0
- package/dist/src/security/scanner/patterns.exec.js +115 -0
- package/dist/src/security/scanner/patterns.exec.js.map +1 -0
- package/dist/src/security/scanner/patterns.js +78 -41
- package/dist/src/security/scanner/patterns.js.map +1 -1
- package/dist/src/security/scanner/types.d.ts +15 -1
- package/dist/src/security/scanner/types.d.ts.map +1 -1
- package/dist/src/security/scanner/typosquat.d.ts +10 -0
- package/dist/src/security/scanner/typosquat.d.ts.map +1 -1
- package/dist/src/security/scanner/typosquat.js +5 -1
- package/dist/src/security/scanner/typosquat.js.map +1 -1
- package/dist/src/security/scanner/weights.d.ts +12 -3
- package/dist/src/security/scanner/weights.d.ts.map +1 -1
- package/dist/src/security/scanner/weights.js +50 -1
- package/dist/src/security/scanner/weights.js.map +1 -1
- package/dist/src/services/agent-pack/prompt-source.js +1 -1
- package/dist/src/services/agent-pack/prompt-source.js.map +1 -1
- package/dist/src/services/bundled-sibling-scan.d.ts +87 -13
- package/dist/src/services/bundled-sibling-scan.d.ts.map +1 -1
- package/dist/src/services/bundled-sibling-scan.js +177 -33
- package/dist/src/services/bundled-sibling-scan.js.map +1 -1
- package/dist/src/services/recommend-guard.d.ts.map +1 -1
- package/dist/src/services/recommend-guard.js +8 -1
- package/dist/src/services/recommend-guard.js.map +1 -1
- package/dist/src/services/recommend-guard.test.js +6 -2
- package/dist/src/services/recommend-guard.test.js.map +1 -1
- package/dist/src/sync/index.d.ts +1 -0
- package/dist/src/sync/index.d.ts.map +1 -1
- package/dist/src/sync/index.js +2 -0
- package/dist/src/sync/index.js.map +1 -1
- package/dist/src/sync/license-status-client.d.ts +56 -0
- package/dist/src/sync/license-status-client.d.ts.map +1 -0
- package/dist/src/sync/license-status-client.js +118 -0
- package/dist/src/sync/license-status-client.js.map +1 -0
- package/dist/src/sync/license-status-client.test.d.ts +14 -0
- package/dist/src/sync/license-status-client.test.d.ts.map +1 -0
- package/dist/src/sync/license-status-client.test.js +136 -0
- package/dist/src/sync/license-status-client.test.js.map +1 -0
- package/dist/src/types.d.ts +13 -0
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils/version-check.d.ts +58 -5
- package/dist/src/utils/version-check.d.ts.map +1 -1
- package/dist/src/utils/version-check.js +73 -2
- package/dist/src/utils/version-check.js.map +1 -1
- package/dist/src/utils/version-check.test.js +49 -10
- package/dist/src/utils/version-check.test.js.map +1 -1
- package/dist/tests/SecurityScanner.scoring.test.js +123 -0
- package/dist/tests/SecurityScanner.scoring.test.js.map +1 -1
- package/dist/tests/security/advisory-severity-guard.test.d.ts +20 -0
- package/dist/tests/security/advisory-severity-guard.test.d.ts.map +1 -0
- package/dist/tests/security/advisory-severity-guard.test.js +70 -0
- package/dist/tests/security/advisory-severity-guard.test.js.map +1 -0
- package/dist/tests/security/archive-evasion.test.d.ts +2 -0
- package/dist/tests/security/archive-evasion.test.d.ts.map +1 -0
- package/dist/tests/security/archive-evasion.test.js +126 -0
- package/dist/tests/security/archive-evasion.test.js.map +1 -0
- package/dist/tests/security/clawhavoc-e2e.fixtures.d.ts +71 -0
- package/dist/tests/security/clawhavoc-e2e.fixtures.d.ts.map +1 -0
- package/dist/tests/security/clawhavoc-e2e.fixtures.js +135 -0
- package/dist/tests/security/clawhavoc-e2e.fixtures.js.map +1 -0
- package/dist/tests/security/co-signal-escalation.test.d.ts +30 -0
- package/dist/tests/security/co-signal-escalation.test.d.ts.map +1 -0
- package/dist/tests/security/co-signal-escalation.test.js +494 -0
- package/dist/tests/security/co-signal-escalation.test.js.map +1 -0
- package/dist/tests/security/decoy-misdirection.test.d.ts +2 -0
- package/dist/tests/security/decoy-misdirection.test.d.ts.map +1 -0
- package/dist/tests/security/decoy-misdirection.test.js +130 -0
- package/dist/tests/security/decoy-misdirection.test.js.map +1 -0
- package/dist/tests/security/encoded-payload.test.d.ts +2 -0
- package/dist/tests/security/encoded-payload.test.d.ts.map +1 -0
- package/dist/tests/security/encoded-payload.test.js +199 -0
- package/dist/tests/security/encoded-payload.test.js.map +1 -0
- package/dist/tests/security/gatekeeper-bypass.test.d.ts +2 -0
- package/dist/tests/security/gatekeeper-bypass.test.d.ts.map +1 -0
- package/dist/tests/security/gatekeeper-bypass.test.js +167 -0
- package/dist/tests/security/gatekeeper-bypass.test.js.map +1 -0
- package/dist/tests/security/paste-host-fetch.test.d.ts +2 -0
- package/dist/tests/security/paste-host-fetch.test.d.ts.map +1 -0
- package/dist/tests/security/paste-host-fetch.test.js +199 -0
- package/dist/tests/security/paste-host-fetch.test.js.map +1 -0
- package/dist/tests/security/scanner-regression-guard.test.js +5 -0
- package/dist/tests/security/scanner-regression-guard.test.js.map +1 -1
- package/dist/tests/services/aidefence-feedback.test.js +5 -0
- package/dist/tests/services/aidefence-feedback.test.js.map +1 -1
- package/dist/tests/services/bundled-sibling-scan.test.js +171 -10
- package/dist/tests/services/bundled-sibling-scan.test.js.map +1 -1
- package/dist/tests/skill-scanner/allowlist.test.js +12 -1
- package/dist/tests/skill-scanner/allowlist.test.js.map +1 -1
- package/package.json +2 -2
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { analyzeMarkdownContext, isDocumentationContext, isWithinInlineCode, } from './SecurityScanner.helpers.js';
|
|
12
12
|
import { safeRegexTest } from './regex-utils.js';
|
|
13
|
+
import { FETCH_COMMAND_PATTERN, correlationTargetBasename, isCorrelatedWithFetchDestination, } from './SecurityScanner.fetch-correlation.js';
|
|
13
14
|
/**
|
|
14
15
|
* SMI-5424 PR2: owner-permission chmod is a COMPOUND signal, not standalone —
|
|
15
16
|
* `chmod 755 ./bin/cli` / `chmod +x build.sh` previously false-fired
|
|
@@ -34,49 +35,19 @@ import { safeRegexTest } from './regex-utils.js';
|
|
|
34
35
|
// that bare `chmod +x foo` (no u/g/o/a prefix — the most common make-executable form
|
|
35
36
|
// in install scripts and malicious droppers) still matches.
|
|
36
37
|
const OWNER_PERM_CHMOD = /\bchmod\s+(?:-[A-Za-z]+\s+)?(?:[0-7]{3,4}|[ugoa]*(?:[+\-=][rwxXstugo]+(?:,[ugoa]*[+\-=][rwxXstugo]*)*)+)/i;
|
|
37
|
-
// FIX-1: actual fetch COMMANDS only — bare prose tokens (`# downloaded`, `See https://…`,
|
|
38
|
-
// `npx tool init`) false-fired next to a chmod. curl/wget/git-clone, and npx only with a URL.
|
|
39
|
-
const CHMOD_FETCH_CONTEXT = /\b(?:curl|wget)\b|\bgit\s+clone\b|\bnpx\b[^\n]{0,80}https?:\/\//i;
|
|
40
38
|
// FIX-2: capture the chmod target path so a fetch command anywhere correlates by basename.
|
|
41
39
|
// SMI-5433: prefix widened to match the same extended forms as OWNER_PERM_CHMOD; capture
|
|
42
40
|
// group (\S+) (the target path) is unchanged.
|
|
43
41
|
const CHMOD_TARGET = /\bchmod\s+(?:-[A-Za-z]+\s+)?(?:[0-7]{3,4}|[ugoa]*(?:[+\-=][rwxXstugo]+(?:,[ugoa]*[+\-=][rwxXstugo]*)*)+)\s+(\S+)/i;
|
|
44
|
-
function escapeRegExp(s) {
|
|
45
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
46
|
-
}
|
|
47
|
-
// SMI-5431: the IMPLICIT download destination of a fetch command — the file written with
|
|
48
|
-
// NO explicit -o/-O/--output<space>/> redirect: `wget <url>` (no -O/-o) → URL last segment;
|
|
49
|
-
// `git clone <url>` → repo dir (minus `.git`); `curl --output=<file>` (equals form, missed by
|
|
50
|
-
// the explicit regex). A bare `curl <url>` GET writes to STDOUT → '' (never correlates). ReDoS-safe.
|
|
51
|
-
function implicitDownloadBasename(line) {
|
|
52
|
-
const lastSegment = (urlAfterScheme) => {
|
|
53
|
-
const noFrag = urlAfterScheme.split(/[?#]/)[0];
|
|
54
|
-
const slash = noFrag.indexOf('/'); // first slash = end of host
|
|
55
|
-
if (slash < 0)
|
|
56
|
-
return ''; // host only -> wget writes index.html
|
|
57
|
-
const path = noFrag.slice(slash + 1).replace(/\/+$/, '');
|
|
58
|
-
return path === '' ? '' : (path.split('/').pop() ?? '');
|
|
59
|
-
};
|
|
60
|
-
const wget = line.match(/\bwget\b(?![^\n]{0,200}\s-[oO]\b)[^\n]{0,200}?https?:\/\/(\S{1,400})/i);
|
|
61
|
-
if (wget)
|
|
62
|
-
return lastSegment(wget[1]);
|
|
63
|
-
const clone = line.match(/\bgit\s+clone\b[^\n]{0,200}?https?:\/\/(\S{1,400})/i);
|
|
64
|
-
if (clone)
|
|
65
|
-
return lastSegment(clone[1]).replace(/\.git$/i, '');
|
|
66
|
-
const curlEq = line.match(/\bcurl\b[^\n]{0,200}?--output=['"]?(\S{1,400})/i);
|
|
67
|
-
if (curlEq)
|
|
68
|
-
return curlEq[1].replace(/['"]/g, '').split('/').pop() ?? '';
|
|
69
|
-
return '';
|
|
70
|
-
}
|
|
71
42
|
export function scanChmodFetchCompound(content, alreadyFlaggedLines, lineContexts) {
|
|
72
43
|
const findings = [];
|
|
73
44
|
const lines = content.split('\n');
|
|
74
45
|
const contexts = lineContexts ?? analyzeMarkdownContext(content);
|
|
75
46
|
// FIX-2: lines carrying a fetch command, for distance-independent correlation.
|
|
76
47
|
// H-6 (SMI-5433): route through safeRegexTest so the 10,000-char cap applies uniformly
|
|
77
|
-
// to the per-line filter too (
|
|
48
|
+
// to the per-line filter too (FETCH_COMMAND_PATTERN is provably linear, so this is
|
|
78
49
|
// defense-in-depth consistency, not a ReDoS fix).
|
|
79
|
-
const fetchLines = lines.filter((l) => safeRegexTest(
|
|
50
|
+
const fetchLines = lines.filter((l) => safeRegexTest(FETCH_COMMAND_PATTERN, l) !== null);
|
|
80
51
|
lines.forEach((line, index) => {
|
|
81
52
|
const lineNumber = index + 1;
|
|
82
53
|
// World-writable / setuid already emitted critical for this line — skip.
|
|
@@ -88,20 +59,21 @@ export function scanChmodFetchCompound(content, alreadyFlaggedLines, lineContext
|
|
|
88
59
|
// Bounded ±1-line window for the download-then-chmod adjacency.
|
|
89
60
|
const window = [lines[index - 1] ?? '', line, lines[index + 1] ?? ''].join('\n');
|
|
90
61
|
// H-6 (SMI-5433): route through safeRegexTest for the 10,000-char cap.
|
|
91
|
-
const adjacentFetch = safeRegexTest(
|
|
92
|
-
// FIX-2 + SMI-5431: correlate the chmod target
|
|
62
|
+
const adjacentFetch = safeRegexTest(FETCH_COMMAND_PATTERN, window) !== null;
|
|
63
|
+
// FIX-2 + SMI-5431: correlate the chmod target (final segment ≥3 chars) against a fetch
|
|
93
64
|
// command's DOWNLOAD DESTINATION anywhere — explicit (-o/-O/--output<space>/>, with an
|
|
94
65
|
// optional leading path) via regex, OR implicit (wget/git-clone/curl --output=) via
|
|
95
66
|
// exact-token equality. Anchored on the destination, NOT basename-anywhere, so a URL
|
|
96
67
|
// path / query / header value (governance FP class) and a bare curl GET do not correlate.
|
|
68
|
+
// SMI-6033 Wave 3: the FULL captured path is handed to the shared utility (it used to be
|
|
69
|
+
// reduced to a bare basename here, discarding the directory the utility now compares).
|
|
97
70
|
let correlated = false;
|
|
98
71
|
// H-6 (SMI-5433): route through safeRegexTest for the 10,000-char cap.
|
|
99
72
|
const tm = safeRegexTest(CHMOD_TARGET, line);
|
|
100
73
|
if (tm) {
|
|
101
|
-
const
|
|
102
|
-
if (
|
|
103
|
-
|
|
104
|
-
correlated = fetchLines.some((l) => re.test(l) || implicitDownloadBasename(l) === base);
|
|
74
|
+
const targetPath = tm[1].replace(/['"]/g, '');
|
|
75
|
+
if (correlationTargetBasename(targetPath).length >= 3) {
|
|
76
|
+
correlated = isCorrelatedWithFetchDestination(targetPath, fetchLines);
|
|
105
77
|
}
|
|
106
78
|
}
|
|
107
79
|
if (!adjacentFetch && !correlated)
|
|
@@ -124,4 +96,111 @@ export function scanChmodFetchCompound(content, alreadyFlaggedLines, lineContext
|
|
|
124
96
|
});
|
|
125
97
|
return findings;
|
|
126
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* SMI-6033 Wave 2/3 (Gap 5, tiered per the plan's "Product decision:
|
|
101
|
+
* Gatekeeper-bypass carve-out", resolved 2026-08-14): xattr Gatekeeper-bypass
|
|
102
|
+
* detector.
|
|
103
|
+
*
|
|
104
|
+
* `xattr -c <file>` (clear ALL extended attributes) or
|
|
105
|
+
* `xattr -d com.apple.quarantine <file>` (delete just the quarantine
|
|
106
|
+
* attribute — with or without a combined `-r` recursive flag) strips
|
|
107
|
+
* macOS's "downloaded from the internet" Gatekeeper warning from an
|
|
108
|
+
* unsigned binary.
|
|
109
|
+
*
|
|
110
|
+
* NOT unconditionally standalone-critical: `critical` requires the xattr
|
|
111
|
+
* target's path to be correlated with a fetch destination elsewhere in
|
|
112
|
+
* the content (the shared `isCorrelatedWithFetchDestination` utility, same
|
|
113
|
+
* as `scanChmodFetchCompound` above and `scanArchiveEvasion`'s inline-literal
|
|
114
|
+
* form) — the "download a payload, strip its quarantine bit" attack shape.
|
|
115
|
+
* Uncorrelated usage (e.g. a general macOS-troubleshooting doc mentioning
|
|
116
|
+
* `xattr -c` with no fetched file in scope) stays `medium`, regardless of
|
|
117
|
+
* `isHighTrustAuthor`.
|
|
118
|
+
*
|
|
119
|
+
* `isHighTrustAuthor` (default `false`) is the trust-tier carve-out: when the
|
|
120
|
+
* correlated (would-be-critical) form fires AND the caller has verified the
|
|
121
|
+
* skill's author is high-trust, severity downgrades to `medium` instead of
|
|
122
|
+
* `critical` — a downgrade, not a full exemption, since even a high-trust
|
|
123
|
+
* account can be compromised (a `medium` finding still counts toward the
|
|
124
|
+
* two-distinct-medium-signal escalation rule). This parameter must be sourced
|
|
125
|
+
* from a VERIFIED author signal — the indexer's own resolved GitHub repo
|
|
126
|
+
* owner, never a self-declared `SKILL.md` frontmatter field, which is
|
|
127
|
+
* trivially spoofable. `skill_validate`'s offline call site has no such
|
|
128
|
+
* verified signal (a network call to check it would break the tool's
|
|
129
|
+
* deliberately-offline design, per Gap 7) and so must never pass this
|
|
130
|
+
* parameter — it stays defaulted `false`, keeping correlated xattr
|
|
131
|
+
* always-critical there, full stop. Checksum/signature-verification prose
|
|
132
|
+
* near the command does NOT downgrade a non-high-trust correlated form: it's
|
|
133
|
+
* attacker-authored text and proves nothing about the artifact's safety, so
|
|
134
|
+
* no such logic exists here.
|
|
135
|
+
*
|
|
136
|
+
* Two bounded, ReDoS-safe TRIGGER patterns rather than one combined
|
|
137
|
+
* alternation (mirrors CHMOD_TARGET's capture-then-inspect style):
|
|
138
|
+
* XATTR_CLEAR_ALL matches any `-c`-bearing flag cluster within 40 chars of
|
|
139
|
+
* `xattr` (`-c`, `-cr`, `-rc`, ...); XATTR_DELETE_QUARANTINE matches a
|
|
140
|
+
* `-d`-bearing flag cluster immediately followed by the literal
|
|
141
|
+
* `com.apple.quarantine` attribute name (covers both a combined `-dr`/`-rd`
|
|
142
|
+
* cluster and two separate `-r -d com.apple.quarantine` tokens, since the
|
|
143
|
+
* attribute name always immediately follows whichever token carries `-d`).
|
|
144
|
+
* Reading or writing a DIFFERENT attribute (`-l`, `-p <name>`, `-w <name>
|
|
145
|
+
* <value>`) is not a bypass and does not match either pattern. A second,
|
|
146
|
+
* TARGET-capturing sibling of each pattern (below) exists ONLY to extract the
|
|
147
|
+
* xattr'd file's basename for the correlation check — it never changes
|
|
148
|
+
* whether a line trips the detector.
|
|
149
|
+
*/
|
|
150
|
+
const XATTR_CLEAR_ALL = /\bxattr\b[^\n]{0,40}-[a-zA-Z]*c[a-zA-Z]*\b/i;
|
|
151
|
+
const XATTR_DELETE_QUARANTINE = /\bxattr\b[^\n]{0,40}-[a-zA-Z]*d[a-zA-Z]*\s+['"]?com\.apple\.quarantine\b/i;
|
|
152
|
+
const XATTR_CLEAR_ALL_TARGET = /\bxattr\b[^\n]{0,40}-[a-zA-Z]*c[a-zA-Z]*\b\s+['"]?(\S+)/i;
|
|
153
|
+
const XATTR_DELETE_QUARANTINE_TARGET = /\bxattr\b[^\n]{0,40}-[a-zA-Z]*d[a-zA-Z]*\s+['"]?com\.apple\.quarantine\b['"]?\s+['"]?(\S+)/i;
|
|
154
|
+
/**
|
|
155
|
+
* The FILE target of an xattr Gatekeeper-bypass command (real xattr CLI
|
|
156
|
+
* syntax is `xattr [-flags] [attr_name] target...`, so the target always
|
|
157
|
+
* comes right after whichever trigger variant matched) — used only for the
|
|
158
|
+
* fetch-correlation trust-tier check. Extraction failure (no trailing token)
|
|
159
|
+
* simply yields '' (never correlates); it does not affect whether the line
|
|
160
|
+
* trips the underlying detector. SMI-6033 Wave 3: returns the FULL path
|
|
161
|
+
* (quotes stripped), not a bare basename — the shared correlation utility is
|
|
162
|
+
* now directory-aware and must not be handed a path-stripped target.
|
|
163
|
+
*/
|
|
164
|
+
function extractXattrTargetPath(line) {
|
|
165
|
+
const m = safeRegexTest(XATTR_CLEAR_ALL_TARGET, line) ??
|
|
166
|
+
safeRegexTest(XATTR_DELETE_QUARANTINE_TARGET, line);
|
|
167
|
+
if (!m)
|
|
168
|
+
return '';
|
|
169
|
+
return m[1].replace(/['"]/g, '');
|
|
170
|
+
}
|
|
171
|
+
export function scanGatekeeperBypass(content, lineContexts, isHighTrustAuthor = false) {
|
|
172
|
+
const findings = [];
|
|
173
|
+
const lines = content.split('\n');
|
|
174
|
+
const contexts = lineContexts ?? analyzeMarkdownContext(content);
|
|
175
|
+
// Fetch-carrying lines, for the correlation precondition (mirrors
|
|
176
|
+
// scanChmodFetchCompound/scanArchiveEvasion's own fetchLines computation).
|
|
177
|
+
const fetchLines = lines.filter((l) => safeRegexTest(FETCH_COMMAND_PATTERN, l) !== null);
|
|
178
|
+
lines.forEach((line, index) => {
|
|
179
|
+
const match = safeRegexTest(XATTR_CLEAR_ALL, line) ?? safeRegexTest(XATTR_DELETE_QUARANTINE, line);
|
|
180
|
+
if (!match)
|
|
181
|
+
return;
|
|
182
|
+
const ctx = contexts[index];
|
|
183
|
+
const inInlineCode = ctx?.isInlineCode && isWithinInlineCode(line, match.index ?? 0);
|
|
184
|
+
const inDocContext = ctx ? isDocumentationContext(ctx) || inInlineCode : false;
|
|
185
|
+
const targetPath = extractXattrTargetPath(line);
|
|
186
|
+
const correlated = correlationTargetBasename(targetPath).length >= 3 &&
|
|
187
|
+
isCorrelatedWithFetchDestination(targetPath, fetchLines);
|
|
188
|
+
// Product decision (2026-08-14): correlated form is standalone-critical
|
|
189
|
+
// UNLESS the caller has verified the author is high-trust (indexer path
|
|
190
|
+
// only — see this function's header). Uncorrelated usage never reaches
|
|
191
|
+
// 'critical' regardless of trust tier.
|
|
192
|
+
const critical = !inDocContext && correlated && !isHighTrustAuthor;
|
|
193
|
+
findings.push({
|
|
194
|
+
type: 'gatekeeper_bypass',
|
|
195
|
+
severity: inDocContext ? 'low' : critical ? 'critical' : 'medium',
|
|
196
|
+
message: `xattr command strips the macOS Gatekeeper quarantine attribute: "${match[0].trim().slice(0, 100)}"`,
|
|
197
|
+
location: line.trim().slice(0, 100),
|
|
198
|
+
lineNumber: index + 1,
|
|
199
|
+
category: 'gatekeeper_bypass',
|
|
200
|
+
inDocumentationContext: inDocContext,
|
|
201
|
+
confidence: inDocContext ? 'low' : 'high',
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
return findings;
|
|
205
|
+
}
|
|
127
206
|
//# sourceMappingURL=SecurityScanner.compound.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SecurityScanner.compound.js","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.compound.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF;;;;;;;;GAQG;AAIH,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,8BAA8B,CAAA;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"SecurityScanner.compound.js","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.compound.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,10 +5,12 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { ScanReport, ScannerOptions } from './types.js';
|
|
7
7
|
import type { LineContext } from './SecurityScanner.helpers.js';
|
|
8
|
-
import { analyzeMarkdownContext, isDocumentationContext, isWithinInlineCode
|
|
8
|
+
import { analyzeMarkdownContext, isDocumentationContext, isWithinInlineCode } from './SecurityScanner.helpers.js';
|
|
9
|
+
import { extractUrls } from './SecurityScanner.urls.js';
|
|
10
|
+
import { calculateRiskScore } from './SecurityScanner.risk-score.js';
|
|
9
11
|
import { scanSsrfPatterns } from './SecurityScanner.ssrf.js';
|
|
10
12
|
import { toMinimalRefs, toSARIF, toGitHubAnnotations, toSummary } from './SecurityScanner.formatters.js';
|
|
11
|
-
export { LineContext, analyzeMarkdownContext, isDocumentationContext, isWithinInlineCode, calculateRiskScore, };
|
|
13
|
+
export { LineContext, analyzeMarkdownContext, isDocumentationContext, isWithinInlineCode, calculateRiskScore, extractUrls, };
|
|
12
14
|
export { scanSsrfPatterns };
|
|
13
15
|
export { toMinimalRefs, toSARIF, toGitHubAnnotations, toSummary };
|
|
14
16
|
export declare class SecurityScanner {
|
|
@@ -17,7 +19,6 @@ export declare class SecurityScanner {
|
|
|
17
19
|
private maxContentLength;
|
|
18
20
|
private riskThreshold;
|
|
19
21
|
constructor(options?: ScannerOptions);
|
|
20
|
-
private extractUrls;
|
|
21
22
|
private isAllowedDomain;
|
|
22
23
|
private scanUrls;
|
|
23
24
|
private scanJailbreakPatterns;
|
|
@@ -25,7 +26,43 @@ export declare class SecurityScanner {
|
|
|
25
26
|
private scanAIDefenceVulnerabilities;
|
|
26
27
|
/** @deprecated Use standalone calculateRiskScore function for new code */
|
|
27
28
|
calculateRiskScore: typeof calculateRiskScore;
|
|
28
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Run every content-scanning detector against `content` and return the
|
|
31
|
+
* combined findings. Factored out of `scan()` (SMI-6033 Wave 2, Gap 2) so
|
|
32
|
+
* the encoded-payload detector's recursive rescan of DECODED content can
|
|
33
|
+
* reuse the exact same detector suite instead of a parallel, narrower
|
|
34
|
+
* reimplementation.
|
|
35
|
+
*
|
|
36
|
+
* `skipEncodedPayload` is the STRUCTURAL depth-1 recursion guarantee: the
|
|
37
|
+
* recursive callback passed to `scanEncodedPayload` below always calls this
|
|
38
|
+
* method with `skipEncodedPayload: true`, so a base64 blob discovered
|
|
39
|
+
* INSIDE already-decoded content can never itself be decoded — the inner
|
|
40
|
+
* call cannot reach `scanEncodedPayload` again no matter what the decoded
|
|
41
|
+
* text contains. This disables ONLY the encoded-payload detector on the
|
|
42
|
+
* inner call, not the rest of the suite — a decoded `curl|bash` still
|
|
43
|
+
* trips `code_execution`, decoded secrets still trip `sensitive_path`, etc.
|
|
44
|
+
*/
|
|
45
|
+
private runDetectors;
|
|
46
|
+
/**
|
|
47
|
+
* SMI-6033 Wave 3 (Gap 5): `isHighTrustAuthor` (default `false`) is the
|
|
48
|
+
* Gatekeeper-bypass trust-tier carve-out — see `scanGatekeeperBypass`'s own
|
|
49
|
+
* header (`SecurityScanner.compound.ts`) for the full policy. No in-repo
|
|
50
|
+
* caller of this method currently has a verified author signal to pass
|
|
51
|
+
* here (the indexer scans via the edge twin, not this core class); the
|
|
52
|
+
* parameter exists so a future verified-author caller can opt in, and so
|
|
53
|
+
* every existing call site (skill_validate, skill_rescan,
|
|
54
|
+
* bundled-sibling-scan, skill-installation.*) defaults closed by
|
|
55
|
+
* construction, not by convention.
|
|
56
|
+
*
|
|
57
|
+
* SMI-6033 Wave 2 (Gap 8) fix (2026-08-17): `isMarkdown` defaults `true`,
|
|
58
|
+
* preserving byte-identical behavior for every existing caller. Pass
|
|
59
|
+
* `false` when `content` is a real source file, not markdown — see
|
|
60
|
+
* `analyzeMarkdownContext`'s own header (SecurityScanner.helpers.ts) for
|
|
61
|
+
* why the markdown-only indented-code-block heuristic must never apply to
|
|
62
|
+
* non-markdown content. `bundled-sibling-scan.ts`'s
|
|
63
|
+
* `collectExecutableCodeFiles` candidates are the first real caller.
|
|
64
|
+
*/
|
|
65
|
+
scan(skillId: string, content: string, isHighTrustAuthor?: boolean, isMarkdown?: boolean): ScanReport;
|
|
29
66
|
quickCheck(content: string): boolean;
|
|
30
67
|
addAllowedDomain(domain: string): void;
|
|
31
68
|
addBlockedPattern(pattern: RegExp): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SecurityScanner.d.ts","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAmB,UAAU,EAAE,cAAc,EAAqB,MAAM,YAAY,CAAA;AAUhG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AAC/D,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,kBAAkB,
|
|
1
|
+
{"version":3,"file":"SecurityScanner.d.ts","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAmB,UAAU,EAAE,cAAc,EAAqB,MAAM,YAAY,CAAA;AAUhG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AAC/D,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,kBAAkB,EAEnB,MAAM,8BAA8B,CAAA;AAErC,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AAEvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAA;AAGpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAA;AAsC5D,OAAO,EACL,aAAa,EACb,OAAO,EACP,mBAAmB,EACnB,SAAS,EACV,MAAM,iCAAiC,CAAA;AAGxC,OAAO,EACL,WAAW,EACX,sBAAsB,EACtB,sBAAsB,EACtB,kBAAkB,EAClB,kBAAkB,EAClB,WAAW,GACZ,CAAA;AACD,OAAO,EAAE,gBAAgB,EAAE,CAAA;AAC3B,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,mBAAmB,EAAE,SAAS,EAAE,CAAA;AAEjE,qBAAa,eAAe;IAC1B,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,eAAe,CAAU;IACjC,OAAO,CAAC,gBAAgB,CAAQ;IAChC,OAAO,CAAC,aAAa,CAAQ;gBAEjB,OAAO,GAAE,cAAmB;IAOxC,OAAO,CAAC,eAAe;IAYvB,OAAO,CAAC,QAAQ;IAmBhB,OAAO,CAAC,qBAAqB;IAkB7B,OAAO,CAAC,sBAAsB;IA6D9B,OAAO,CAAC,4BAA4B;IAkBpC,0EAA0E;IAC1E,kBAAkB,4BAAqB;IAEvC;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,YAAY;IAwGpB;;;;;;;;;;;;;;;;;;OAkBG;IACH,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,iBAAiB,UAAQ,EAAE,UAAU,UAAO,GAAG,UAAU;IA+ChG,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAOpC,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAItC,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAKxC,MAAM,CAAC,aAAa,uBAAgB;IACpC,MAAM,CAAC,OAAO,iBAAU;IACxB,MAAM,CAAC,mBAAmB,6BAAsB;IAChD,MAAM,CAAC,SAAS,mBAAY;CAC7B;AAED,eAAe,eAAe,CAAA"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security Scanner — decoy/misdirection URL-target heuristic
|
|
3
|
+
* @module @skillsmith/core/security/scanner/SecurityScanner.decoy
|
|
4
|
+
*
|
|
5
|
+
* SMI-6033 Wave 4 (Gap 6): reuses the typosquat detector's brand data —
|
|
6
|
+
* `BRAND_ALIASES` (typosquat.ts) and `AUTHORITY_CLAIMING_AFFIXES`
|
|
7
|
+
* (typosquat.ts, exported for exactly this reuse as part of Wave 1's Gap 6
|
|
8
|
+
* prerequisite) — to catch a fetch/exec instruction whose surrounding prose
|
|
9
|
+
* claims a specific vendor (a brand token, optionally reinforced by an
|
|
10
|
+
* authority-claiming affix like "official"/"verified") while the fetch
|
|
11
|
+
* target's actual domain does not belong to that vendor and is not in the
|
|
12
|
+
* general `DEFAULT_ALLOWED_DOMAINS` allowlist.
|
|
13
|
+
*
|
|
14
|
+
* Per the plan's §9 reconciliation table: `decoy_misdirection` is "N/A —
|
|
15
|
+
* never standalone... Approximate NL heuristic by construction; co-signal
|
|
16
|
+
* required (medium tier), unchanged." This detector therefore NEVER emits
|
|
17
|
+
* `high`/`critical` — only `medium`, or nothing (see the doc-context
|
|
18
|
+
* downgrade below, which uses `low`, not a third severity choice for the
|
|
19
|
+
* non-doc case). The co-signal mechanism that would let this medium finding
|
|
20
|
+
* contribute toward escalating a separate weak `code_execution` finding is a
|
|
21
|
+
* SEPARATE dispatch's job — `SecurityScanner.exec.ts`'s
|
|
22
|
+
* `CO_SIGNAL_MIN_SEVERITY` replacement — and is NOT implemented in this file.
|
|
23
|
+
*
|
|
24
|
+
* Design note, NOT explicit in the plan text and flagged here for review:
|
|
25
|
+
* the plan says to compare "the claimed vendor's canonical domain" against
|
|
26
|
+
* the fetch target's domain, but `BRAND_ALIASES`'s values are GitHub OWNER
|
|
27
|
+
* SLUGS (`anthropics`, `google-gemini`, ...) — they answer "whose GitHub org
|
|
28
|
+
* publishes this," not "what is this vendor's real website." A small
|
|
29
|
+
* curated `BRAND_CANONICAL_DOMAINS` map below, scoped to the exact same six
|
|
30
|
+
* brand tokens `BRAND_ALIASES` already curates, fills that gap; a brand
|
|
31
|
+
* token with no entry here is not currently possible (the two maps share the
|
|
32
|
+
* same key set by construction) but the lookup fails safe (empty domain
|
|
33
|
+
* list -> never matches -> never suppresses) if that ever drifts.
|
|
34
|
+
*
|
|
35
|
+
* Second design note: the plan text reads "a brand token OR an
|
|
36
|
+
* authority-claiming phrase implying a specific vendor." An authority-
|
|
37
|
+
* claiming affix ALONE ("the official installer") never names a specific
|
|
38
|
+
* vendor and so cannot resolve a canonical domain to compare against — this
|
|
39
|
+
* detector therefore requires a brand-token match to identify "the claimed
|
|
40
|
+
* vendor" at all (necessary for the domain comparison to mean anything), and
|
|
41
|
+
* treats a co-occurring authority-claiming affix as a confidence booster
|
|
42
|
+
* (medium -> high), not an alternate, independently-sufficient trigger. This
|
|
43
|
+
* mirrors typosquat.ts's own rule 3 (`hasBrandToken && hasAuthorityAffix`),
|
|
44
|
+
* which also requires both signals together.
|
|
45
|
+
*
|
|
46
|
+
* Two fixes from adversarial review (2026-08-16 — see
|
|
47
|
+
* docs/internal/code_review/2026-08-15-smi6033-wave4-escalation-model.md
|
|
48
|
+
* for the full account):
|
|
49
|
+
*
|
|
50
|
+
* 1. Fetch-target correlation. The original version treated ANY URL on a
|
|
51
|
+
* line that ALSO matched the generic FETCH_COMMAND_PATTERN as the fetch
|
|
52
|
+
* target — including a URL that was merely mentioned in prose alongside
|
|
53
|
+
* an unrelated fetch-verb usage on the same line (e.g.
|
|
54
|
+
* "curl --version; see mirror documentation at <url>", where curl is
|
|
55
|
+
* checking its own version, not fetching that URL). Replaced with
|
|
56
|
+
* `isActualFetchTarget`, which requires the fetch verb to be
|
|
57
|
+
* IMMEDIATELY followed (only flag-like tokens and whitespace, no
|
|
58
|
+
* command separator, no prose) by the URL — i.e. the URL must actually
|
|
59
|
+
* be the verb's argument.
|
|
60
|
+
*
|
|
61
|
+
* 2. Authority-affix proximity. `hasAuthorityAffix` used to scan the ENTIRE
|
|
62
|
+
* ±5-line window independently of where the brand token itself was
|
|
63
|
+
* found, so an unrelated authority phrase elsewhere in the window (e.g.
|
|
64
|
+
* "for official documentation on Python packaging, see PEP 517") could
|
|
65
|
+
* wrongly boost confidence to 'high' for a brand claim it has nothing to
|
|
66
|
+
* do with. Now scoped to a tight window around the brand token's OWN
|
|
67
|
+
* line (`DECOY_AUTHORITY_AFFIX_PROXIMITY_LINES`).
|
|
68
|
+
*/
|
|
69
|
+
import type { SecurityFinding } from './types.js';
|
|
70
|
+
import type { LineContext } from './SecurityScanner.helpers.js';
|
|
71
|
+
export declare function scanDecoyMisdirection(content: string, lineContexts?: LineContext[]): SecurityFinding[];
|
|
72
|
+
//# sourceMappingURL=SecurityScanner.decoy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SecurityScanner.decoy.d.ts","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.decoy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmEG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AAuK/D,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,YAAY,CAAC,EAAE,WAAW,EAAE,GAC3B,eAAe,EAAE,CAiDnB"}
|