@skillsmith/core 0.11.7 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +200 -0
- package/README.md +2 -2
- 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/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 +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -2
- 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 +5 -0
- package/dist/tests/skill-scanner/allowlist.test.js.map +1 -1
- package/package.json +1 -1
|
@@ -7,10 +7,16 @@
|
|
|
7
7
|
* Unicode-concealment attacks the prod edge gate currently scores at ~1 point:
|
|
8
8
|
*
|
|
9
9
|
* • code_execution — a skill instructing a remote fetch piped into an
|
|
10
|
-
* interpreter (curl|bash and friends)
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* interpreter (curl|bash and friends), or — SMI-6033 Wave 4, Gap 1 — the
|
|
11
|
+
* same instruction written as free-text prose with no shell syntax at all
|
|
12
|
+
* ("download the installer from thisurl.com and run it",
|
|
13
|
+
* `IMPERATIVE_FETCH_EXEC_PROSE` in patterns.exec.ts). Emits ONE medium
|
|
14
|
+
* finding (score 12, sub-threshold) on its own, whichever pattern set
|
|
15
|
+
* fired; escalated to critical (score 40, quarantines) only when it
|
|
16
|
+
* co-occurs with a NON-documentation exfiltration / privilege /
|
|
17
|
+
* credential-path / obfuscation signal, OR — SMI-6033 Wave 4, Gap 6 — with
|
|
18
|
+
* TWO DISTINCT high-confidence advisory-tier signals
|
|
19
|
+
* (`CO_SIGNAL_MIN_SEVERITY` below). The non-doc gate keeps legitimate
|
|
14
20
|
* security-research skills (which document these techniques inside fenced
|
|
15
21
|
* examples) below the threshold.
|
|
16
22
|
*
|
|
@@ -30,7 +36,10 @@
|
|
|
30
36
|
* representation) is an attack even inside a fenced block — there is no
|
|
31
37
|
* legitimate reason to ship invisible/homoglyph-spliced directives.
|
|
32
38
|
*/
|
|
33
|
-
|
|
39
|
+
// SMI-6033 Wave 4 (Gap 1): both code_execution pattern sets now live in the
|
|
40
|
+
// patterns.exec.ts sibling (patterns.ts re-exports them unchanged); imported
|
|
41
|
+
// from their source of truth here.
|
|
42
|
+
import { CODE_EXECUTION_PATTERNS, IMPERATIVE_FETCH_EXEC_PROSE } from './patterns.exec.js';
|
|
34
43
|
import { safeRegexTest, safeRegexCheck } from './regex-utils.js';
|
|
35
44
|
import { analyzeMarkdownContext, isDocumentationContext } from './SecurityScanner.helpers.js';
|
|
36
45
|
// SMI-595: confusable/homoglyph primitives extracted to a standalone module
|
|
@@ -81,32 +90,53 @@ const OBFUSCATION_DIRECTIVE_PATTERN = /(?:ignore|disregard|forget)\s+(?:all\s+|t
|
|
|
81
90
|
// Detectors
|
|
82
91
|
// ============================================================================
|
|
83
92
|
/**
|
|
84
|
-
* code_execution: remote fetch piped into an interpreter
|
|
85
|
-
*
|
|
93
|
+
* code_execution: remote fetch piped into an interpreter (literal shell
|
|
94
|
+
* syntax), OR — SMI-6033 Wave 4, Gap 1 — the same instruction expressed as a
|
|
95
|
+
* natural-language install-and-run imperative with no shell syntax at all.
|
|
96
|
+
*
|
|
97
|
+
* Single-emission — at most one MEDIUM finding per skill (the first match, in
|
|
98
|
+
* line order), whichever pattern set produced it. escalateCodeExecution()
|
|
86
99
|
* promotes it to CRITICAL on co-occurrence.
|
|
100
|
+
*
|
|
101
|
+
* Ordering is deliberately line-major (each line is tested against the literal
|
|
102
|
+
* syntax set first, then the prose set, before moving to the next line), so a
|
|
103
|
+
* document containing only literal-syntax matches produces byte-identical
|
|
104
|
+
* output to the pre-Gap-1 detector — same line, same message, same severity.
|
|
105
|
+
* The message names WHICH set fired so a reviewer can tell a prose-triggered
|
|
106
|
+
* finding from a syntax-triggered one at a glance.
|
|
87
107
|
*/
|
|
88
108
|
export function scanCodeExecution(content, lineContexts) {
|
|
89
109
|
const lines = content.split('\n');
|
|
90
110
|
const contexts = lineContexts ?? analyzeMarkdownContext(content);
|
|
111
|
+
const emit = (i, line, matched, prefix) => {
|
|
112
|
+
const ctx = contexts[i];
|
|
113
|
+
const inDocContext = ctx ? isDocumentationContext(ctx) : false;
|
|
114
|
+
return [
|
|
115
|
+
{
|
|
116
|
+
type: 'code_execution',
|
|
117
|
+
severity: 'medium',
|
|
118
|
+
message: `${prefix}: "${matched.slice(0, 60)}${matched.length > 60 ? '...' : ''}"`,
|
|
119
|
+
location: line.trim().slice(0, 100),
|
|
120
|
+
lineNumber: i + 1,
|
|
121
|
+
category: 'code_execution',
|
|
122
|
+
inDocumentationContext: inDocContext,
|
|
123
|
+
confidence: 'high',
|
|
124
|
+
},
|
|
125
|
+
];
|
|
126
|
+
};
|
|
91
127
|
for (let i = 0; i < lines.length; i++) {
|
|
92
128
|
const line = lines[i];
|
|
93
129
|
for (const pattern of CODE_EXECUTION_PATTERNS) {
|
|
130
|
+
const match = safeRegexTest(pattern, line);
|
|
131
|
+
if (match)
|
|
132
|
+
return emit(i, line, match[0], 'Remote fetch piped to an interpreter');
|
|
133
|
+
}
|
|
134
|
+
// SMI-6033 Wave 4 (Gap 1): same finding type, same medium/advisory tier —
|
|
135
|
+
// only the evidence shape differs (free-text imperative, no shell syntax).
|
|
136
|
+
for (const pattern of IMPERATIVE_FETCH_EXEC_PROSE) {
|
|
94
137
|
const match = safeRegexTest(pattern, line);
|
|
95
138
|
if (match) {
|
|
96
|
-
|
|
97
|
-
const inDocContext = ctx ? isDocumentationContext(ctx) : false;
|
|
98
|
-
return [
|
|
99
|
-
{
|
|
100
|
-
type: 'code_execution',
|
|
101
|
-
severity: 'medium',
|
|
102
|
-
message: `Remote fetch piped to an interpreter: "${match[0].slice(0, 60)}${match[0].length > 60 ? '...' : ''}"`,
|
|
103
|
-
location: line.trim().slice(0, 100),
|
|
104
|
-
lineNumber: i + 1,
|
|
105
|
-
category: 'code_execution',
|
|
106
|
-
inDocumentationContext: inDocContext,
|
|
107
|
-
confidence: 'high',
|
|
108
|
-
},
|
|
109
|
-
];
|
|
139
|
+
return emit(i, line, match[0], 'Natural-language instruction to fetch a remote file and execute it');
|
|
110
140
|
}
|
|
111
141
|
}
|
|
112
142
|
}
|
|
@@ -159,35 +189,69 @@ export function scanObfuscatedDirective(content) {
|
|
|
159
189
|
return [];
|
|
160
190
|
}
|
|
161
191
|
/**
|
|
162
|
-
*
|
|
163
|
-
*
|
|
192
|
+
* SMI-6033 Wave 4 (Gap 6): per-type MINIMUM co-signal severity, replacing the
|
|
193
|
+
* flat `CODE_EXECUTION_CO_OCCURRENCE` set. Equality-parity-pinned against its
|
|
194
|
+
* edge twin (security-scanner-edge.exec.ts) — identical keys AND identical
|
|
195
|
+
* values, not a superset; see the co-signal parity test in
|
|
196
|
+
* scripts/tests/indexer/security-scanner-edge.co-signal-escalation.test.ts.
|
|
197
|
+
*
|
|
198
|
+
* A `'high'` minimum means "one such co-signal escalates on its own" (path a
|
|
199
|
+
* below — today's behavior, byte-identical for the original four types). A
|
|
200
|
+
* `'medium'` minimum means "advisory tier: never sufficient alone, but two
|
|
201
|
+
* DISTINCT such types together escalate" (path b). A type absent from this
|
|
202
|
+
* map is not a co-signal at all.
|
|
164
203
|
*/
|
|
165
|
-
const
|
|
166
|
-
'
|
|
167
|
-
'
|
|
168
|
-
'
|
|
169
|
-
'
|
|
170
|
-
|
|
204
|
+
const CO_SIGNAL_MIN_SEVERITY = {
|
|
205
|
+
// Existing four — behavior byte-identical to today, min 'high'.
|
|
206
|
+
data_exfiltration: 'high',
|
|
207
|
+
privilege_escalation: 'high',
|
|
208
|
+
sensitive_path: 'high',
|
|
209
|
+
obfuscated_directive: 'high',
|
|
210
|
+
// New ClawHavoc advisory-tier categories — eligible at 'medium'.
|
|
211
|
+
decoy_misdirection: 'medium',
|
|
212
|
+
archive_evasion: 'medium',
|
|
213
|
+
paste_host_fetch: 'medium',
|
|
214
|
+
gatekeeper_bypass: 'medium', // its correlated/critical form already quarantines on its own
|
|
215
|
+
};
|
|
216
|
+
/** Ordinal ranking so "at or above its type's minimum" is a single comparison. */
|
|
217
|
+
const SEVERITY_RANK = { low: 0, medium: 1, high: 2, critical: 3 };
|
|
171
218
|
/**
|
|
172
219
|
* Maximum line distance between the `code_execution` finding and the co-signal
|
|
173
220
|
* that escalates it (SMI-5880). Before this bound, a co-signal ANYWHERE in the
|
|
174
221
|
* document — any distance, any unrelated section — escalated the single
|
|
175
222
|
* `code_execution` finding to critical.
|
|
176
223
|
*
|
|
177
|
-
* Why a bounded window is NOT a bypass vector: every
|
|
178
|
-
*
|
|
179
|
-
* `passed = !hasCritical && !hasHigh &&
|
|
180
|
-
* with no locality filter and no
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
224
|
+
* Why a bounded window is NOT a bypass vector FOR PATH (a): every `'high'`-
|
|
225
|
+
* minimum co-signal is itself `high` or `critical`, and `scan()`
|
|
226
|
+
* (SecurityScanner.ts) computes `passed = !hasCritical && !hasHigh &&
|
|
227
|
+
* !exceedsThreshold` across ALL findings with no locality filter and no
|
|
228
|
+
* documentation filter. A qualifying path-(a) co-signal anywhere therefore
|
|
229
|
+
* forces `passed === false` on its own, BEFORE this function runs. Splitting
|
|
230
|
+
* the two signals to duck this window cannot turn a failing scan into a
|
|
231
|
+
* passing one; it changes only this finding's own label (medium vs critical)
|
|
232
|
+
* and the risk-score magnitude of a scan that already blocks.
|
|
185
233
|
* Hostile-update detection is likewise untouched: `detectSupplyChain()`
|
|
186
234
|
* (SecurityScanner.hostile-update.ts) pairs a NEW non-doc `code_execution` of ANY
|
|
187
235
|
* severity with a NEW non-doc high/critical co-signal at ANY distance, so a
|
|
188
236
|
* far-apart rug-pull still returns the `hostile` verdict and its supply-chain
|
|
189
237
|
* reason string.
|
|
190
238
|
*
|
|
239
|
+
* SMI-6033 Wave 4 (Gap 6) — that argument does NOT carry over to path (b),
|
|
240
|
+
* and the difference is deliberate, not an oversight. Path (b)'s co-signals
|
|
241
|
+
* are `medium` advisory findings that do NOT independently trip `passed`, so
|
|
242
|
+
* an attacker who spreads the weak `code_execution` finding and its two
|
|
243
|
+
* advisory co-signals more than 40 lines apart genuinely does keep a scan
|
|
244
|
+
* under the bar (12 + 12 + 12 = 36 < the 40 threshold) that would otherwise
|
|
245
|
+
* have escalated to critical. This is the accepted cost of bounding locality
|
|
246
|
+
* at all: the alternative — an unbounded window for fuzzy heuristic signals —
|
|
247
|
+
* re-creates the exact SMI-5880 defect (an unrelated advisory finding in a
|
|
248
|
+
* different section escalating an unrelated `code_execution` finding) with a
|
|
249
|
+
* far higher false-positive rate than the high-tier co-signals ever had,
|
|
250
|
+
* because these signals are approximate by construction. The residual is
|
|
251
|
+
* bounded: the attack still requires the two advisory signals to be real
|
|
252
|
+
* detections in the same document, and `detectSupplyChain()`'s
|
|
253
|
+
* distance-independent rug-pull check above is unaffected.
|
|
254
|
+
*
|
|
191
255
|
* Deliberately the same 40 as the sibling corroboration mechanism's locality
|
|
192
256
|
* constant (SecurityScanner.evidence.ts) so the scanner has ONE locality model
|
|
193
257
|
* — but kept as its own constant, not imported: the two mechanisms are
|
|
@@ -205,11 +269,13 @@ const MAX_CODE_EXECUTION_CO_SIGNAL_LINE_DISTANCE = 40;
|
|
|
205
269
|
* that pair rather than suppressing the co-signal.
|
|
206
270
|
*
|
|
207
271
|
* Unreachable through `scan()` today: `scanCodeExecution` above and every emitter
|
|
208
|
-
* of the
|
|
272
|
+
* of the eight `CO_SIGNAL_MIN_SEVERITY` types set `lineNumber` unconditionally
|
|
209
273
|
* (the sensitive-path/data-exfiltration/privilege-escalation scanners in the
|
|
210
|
-
* scanners module, the chmod-compound
|
|
211
|
-
* above
|
|
212
|
-
*
|
|
274
|
+
* scanners module, the chmod-compound and Gatekeeper-bypass detectors,
|
|
275
|
+
* `scanObfuscatedDirective` above, and — SMI-6033 — the archive-evasion,
|
|
276
|
+
* paste-host and decoy-misdirection detectors). Kept as an explicit,
|
|
277
|
+
* directly-tested branch so a future detector that forgets `lineNumber`
|
|
278
|
+
* degrades safe instead of silently ceasing to escalate.
|
|
213
279
|
*/
|
|
214
280
|
function isWithinCoSignalWindow(codeExecLine, coSignalLine) {
|
|
215
281
|
if (typeof codeExecLine !== 'number' || typeof coSignalLine !== 'number')
|
|
@@ -217,31 +283,120 @@ function isWithinCoSignalWindow(codeExecLine, coSignalLine) {
|
|
|
217
283
|
return Math.abs(codeExecLine - coSignalLine) <= MAX_CODE_EXECUTION_CO_SIGNAL_LINE_DISTANCE;
|
|
218
284
|
}
|
|
219
285
|
/**
|
|
220
|
-
* Escalate the code_execution finding
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
286
|
+
* Escalate the code_execution finding (literal `curl|bash` syntax OR a Gap-1
|
|
287
|
+
* `IMPERATIVE_FETCH_EXEC_PROSE` match — same finding type either way) to
|
|
288
|
+
* CRITICAL, on either of two paths. Both require: the `code_execution`
|
|
289
|
+
* finding ITSELF to be non-documentation (see the adversarial-review fix
|
|
290
|
+
* note below), the co-signal to be NON-documentation, and the co-signal to be
|
|
291
|
+
* WITHIN `MAX_CODE_EXECUTION_CO_SIGNAL_LINE_DISTANCE` lines (SMI-5880 —
|
|
292
|
+
* previously any distance qualified, so an unrelated finding in a different
|
|
293
|
+
* section of a long document escalated an unrelated code_execution finding).
|
|
294
|
+
* Mutates the finding in place.
|
|
295
|
+
*
|
|
296
|
+
* Path (a) — ONE co-signal whose type's `CO_SIGNAL_MIN_SEVERITY` minimum is
|
|
297
|
+
* `'high'`, at `high` or `critical`. Byte-identical to the pre-SMI-6033
|
|
298
|
+
* behavior for the original four types (`data_exfiltration`,
|
|
299
|
+
* `privilege_escalation`, `sensitive_path`, `obfuscated_directive`) EXCEPT
|
|
300
|
+
* for the `codeExec`-own-doc-context fix below (see that note — the
|
|
301
|
+
* pre-existing behavior there was a real gap, not an intentional design):
|
|
302
|
+
* same co-signal membership test, same severity test, same locality gate,
|
|
303
|
+
* same message. Deliberately NO confidence requirement on the CO-SIGNAL here
|
|
304
|
+
* — adding one would change existing behavior, which the Gap 6 design
|
|
305
|
+
* explicitly forbids.
|
|
306
|
+
*
|
|
307
|
+
* Path (b) — SMI-6033 Wave 4 (Gap 6), only evaluated when path (a) did not
|
|
308
|
+
* fire: at least TWO DISTINCT `'medium'`-minimum types (two findings of the
|
|
309
|
+
* SAME type do not count), each at or above its own minimum. A single fuzzy
|
|
310
|
+
* medium signal (one decoy mismatch alone, one transfer.sh link alone) can
|
|
311
|
+
* therefore never flip a legitimate vendor `curl|bash` to critical.
|
|
312
|
+
*
|
|
313
|
+
* Confidence requirement (revised after adversarial review, 2026-08-16 —
|
|
314
|
+
* see `docs/internal/code_review/2026-08-15-smi6033-wave4-escalation-model.md`
|
|
315
|
+
* for the full account): the plan's literal text requires `confidence:
|
|
316
|
+
* 'high'` on both path-(b) co-signals. An earlier revision of this file
|
|
317
|
+
* relaxed that to `confidence !== 'low'` GLOBALLY, reasoning that
|
|
318
|
+
* `paste_host_fetch`'s ANON/TRANSIENT medium form is ALWAYS
|
|
319
|
+
* `confidence: 'medium'` by construction (`SecurityScanner.paste-host.ts`)
|
|
320
|
+
* and would otherwise be permanently unable to satisfy path (b) despite
|
|
321
|
+
* being a registered-eligible type. That reasoning was correct for
|
|
322
|
+
* `paste_host_fetch` specifically, but the GLOBAL relaxation was wrong: it
|
|
323
|
+
* also admitted `archive_evasion`'s prose-only co-occurrence sub-signal
|
|
324
|
+
* (explicitly documented in that file's own header as "the fuzzy, FP-prone
|
|
325
|
+
* case" and capped at `confidence: 'medium'` for exactly that reason) and
|
|
326
|
+
* `decoy_misdirection`'s no-authority-affix form (also `confidence:
|
|
327
|
+
* 'medium'`) — an adversarial review constructed a working false-positive
|
|
328
|
+
* example combining exactly those two fuzzy medium-confidence signals (a
|
|
329
|
+
* mundane "distributed from our company site" skill blurb plus an unrelated
|
|
330
|
+
* "supplied as a zip archive... ask your administrator for the password"
|
|
331
|
+
* sentence) with a real vendor `curl|bash`, and it escalated to a hard
|
|
332
|
+
* quarantine. Fixed by narrowing the confidence carve-out to ONLY
|
|
333
|
+
* `paste_host_fetch` (`CO_SIGNAL_MEDIUM_CONFIDENCE_EXCEPTION` below) — every
|
|
334
|
+
* other medium-minimum type reverts to requiring `confidence: 'high'`,
|
|
335
|
+
* matching the plan's literal text. `gatekeeper_bypass` loses nothing by
|
|
336
|
+
* this (its own detector always emits `confidence: 'high'` regardless of
|
|
337
|
+
* severity); `archive_evasion` and `decoy_misdirection` now only participate
|
|
338
|
+
* via their own higher-confidence forms (CLI-syntax inline-secret-adjacent
|
|
339
|
+
* password mentions; an authority-affix-boosted brand claim), which is
|
|
340
|
+
* exactly the "not noise" signal path (b) was meant to require. See the
|
|
341
|
+
* co-signal escalation tests for the pinned behavior of this decision.
|
|
342
|
+
*
|
|
343
|
+
* `codeExec`-own-doc-context fix (same adversarial review): neither path
|
|
344
|
+
* previously checked `codeExec.inDocumentationContext` itself — only the
|
|
345
|
+
* CO-SIGNAL's doc-context was checked. A `code_execution` finding inside a
|
|
346
|
+
* fenced security-research example could therefore still be escalated by
|
|
347
|
+
* two genuine (non-doc) co-signals elsewhere in the document, contradicting
|
|
348
|
+
* this function's own historical doc comments claiming fenced examples stay
|
|
349
|
+
* at MEDIUM. This gap predates Wave 4 (path (a) had it too) but Wave 4's new
|
|
350
|
+
* path (b) co-signal types meaningfully widen how often it's reachable, so
|
|
351
|
+
* it's fixed here for both paths rather than left in place for path (a)
|
|
352
|
+
* alone.
|
|
228
353
|
*
|
|
229
354
|
* Runs BEFORE the sibling corroboration mechanism in `scan()` — see that
|
|
230
355
|
* function's doc comment (SecurityScanner.evidence.ts) for why the ordering is
|
|
231
356
|
* safe in both directions.
|
|
232
357
|
*/
|
|
358
|
+
const CO_SIGNAL_MEDIUM_CONFIDENCE_EXCEPTION = new Set([
|
|
359
|
+
'paste_host_fetch',
|
|
360
|
+
]);
|
|
233
361
|
export function escalateCodeExecution(findings) {
|
|
234
362
|
const codeExec = findings.find((f) => f.type === 'code_execution');
|
|
235
363
|
if (!codeExec)
|
|
236
364
|
return;
|
|
237
|
-
|
|
238
|
-
|
|
365
|
+
if (codeExec.inDocumentationContext === true)
|
|
366
|
+
return;
|
|
367
|
+
const eligible = findings.filter((f) => f !== codeExec &&
|
|
368
|
+
CO_SIGNAL_MIN_SEVERITY[f.type] !== undefined &&
|
|
239
369
|
f.inDocumentationContext !== true &&
|
|
240
|
-
(f.severity === 'high' || f.severity === 'critical') &&
|
|
241
370
|
isWithinCoSignalWindow(codeExec.lineNumber, f.lineNumber));
|
|
371
|
+
// Path (a): one high-minimum co-signal at high/critical.
|
|
372
|
+
const hasDangerousCoSignal = eligible.some((f) => CO_SIGNAL_MIN_SEVERITY[f.type] === 'high' &&
|
|
373
|
+
(f.severity === 'high' || f.severity === 'critical'));
|
|
242
374
|
if (hasDangerousCoSignal) {
|
|
243
375
|
codeExec.severity = 'critical';
|
|
244
376
|
codeExec.message = `Remote fetch piped to an interpreter, co-occurring with exfiltration/privilege/credential signals — likely supply-chain execution. ${codeExec.message}`;
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
// Path (b): two DISTINCT medium-minimum types, each confidence 'high' —
|
|
380
|
+
// except paste_host_fetch, whose medium form is always confidence:'medium'
|
|
381
|
+
// by construction (see this function's doc comment for the full rationale
|
|
382
|
+
// and the adversarial-review finding that narrowed this from a blanket
|
|
383
|
+
// `!== 'low'` relaxation).
|
|
384
|
+
const advisoryTypes = new Set(eligible
|
|
385
|
+
.filter((f) => {
|
|
386
|
+
const min = CO_SIGNAL_MIN_SEVERITY[f.type];
|
|
387
|
+
const confidenceOk = CO_SIGNAL_MEDIUM_CONFIDENCE_EXCEPTION.has(f.type)
|
|
388
|
+
? f.confidence !== 'low'
|
|
389
|
+
: f.confidence === 'high';
|
|
390
|
+
return min === 'medium' && SEVERITY_RANK[f.severity] >= SEVERITY_RANK[min] && confidenceOk;
|
|
391
|
+
})
|
|
392
|
+
.map((f) => f.type));
|
|
393
|
+
if (advisoryTypes.size >= 2) {
|
|
394
|
+
codeExec.severity = 'critical';
|
|
395
|
+
codeExec.message = `Remote fetch/execute instruction corroborated by two independent advisory-tier signals (${[
|
|
396
|
+
...advisoryTypes,
|
|
397
|
+
]
|
|
398
|
+
.sort()
|
|
399
|
+
.join(', ')}) — likely supply-chain execution. ${codeExec.message}`;
|
|
245
400
|
}
|
|
246
401
|
}
|
|
247
402
|
//# sourceMappingURL=SecurityScanner.exec.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SecurityScanner.exec.js","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.exec.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"SecurityScanner.exec.js","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.exec.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security Scanner — shared fetch-correlation utilities
|
|
3
|
+
* @module @skillsmith/core/security/scanner/SecurityScanner.fetch-correlation
|
|
4
|
+
*
|
|
5
|
+
* SMI-6033 Wave 2: extracted out of SecurityScanner.compound.ts so every
|
|
6
|
+
* provenance-conditioned detector (chmod, xattr/gatekeeper_bypass,
|
|
7
|
+
* archive_evasion, paste_host_fetch) correlates a target file against a fetch
|
|
8
|
+
* command's download destination using the SAME fetch-verb regex and the SAME
|
|
9
|
+
* distance-independent matching logic, instead of drifting copies. Pure
|
|
10
|
+
* functions of their arguments — no scanner instance state. Byte-identical
|
|
11
|
+
* structure is mirrored in the edge twin
|
|
12
|
+
* (security-scanner-edge.fetch-correlation.ts) — see that file's header for
|
|
13
|
+
* the parity-test pointer.
|
|
14
|
+
*
|
|
15
|
+
* SMI-6033 Wave 3 (adversarial-review fix): correlation is now
|
|
16
|
+
* DIRECTORY-PATH-AWARE. It previously matched on the final path segment
|
|
17
|
+
* (basename) alone, with zero directory awareness — so two entirely unrelated
|
|
18
|
+
* files that merely shared a filename registered as "correlated": a
|
|
19
|
+
* legitimate `curl -o /tmp/install.sh <url>` fetch alongside a separate,
|
|
20
|
+
* pre-existing `./vendor/other-tool/install.sh` that gets `chmod +x`'d
|
|
21
|
+
* elsewhere in the same skill. Because this utility IS the shared provenance
|
|
22
|
+
* gate behind the xattr / archive / paste-host "correlated ->
|
|
23
|
+
* standalone-critical" escalations, that false positive is a
|
|
24
|
+
* quarantine-level one, not a cosmetic one.
|
|
25
|
+
*
|
|
26
|
+
* The rule, in one sentence: when BOTH sides carry directory information the
|
|
27
|
+
* normalized directories must be EQUAL; when EITHER side is a bare filename
|
|
28
|
+
* (no directory information at all — `wget https://x/install.sh` writes into
|
|
29
|
+
* the CWD, `chmod +x install.sh` names no directory) fall back to
|
|
30
|
+
* final-segment matching, because there is then no path information to
|
|
31
|
+
* distinguish the two by. Normalization (`normalizeCorrelationPath`) strips
|
|
32
|
+
* quotes, collapses empty segments, drops `.` and resolves `..`, so
|
|
33
|
+
* `./vendor/tool/x` and `vendor/tool/x` are the same directory while `/tmp`
|
|
34
|
+
* and `vendor/tool` are not. An absolute path with no intermediate directory
|
|
35
|
+
* (`/install.sh`) normalizes to the real directory `/` — NOT to "no
|
|
36
|
+
* directory information" — so it does not silently fall back.
|
|
37
|
+
*/
|
|
38
|
+
/**
|
|
39
|
+
* Matches an actual fetch COMMAND (curl/wget/git-clone, or npx immediately
|
|
40
|
+
* followed by a URL) — not bare prose tokens (`# downloaded`, `See
|
|
41
|
+
* https://…`, `npx tool init`) which false-fire when used as a correlation
|
|
42
|
+
* gate on their own. Generic name (not chmod-specific, formerly
|
|
43
|
+
* `CHMOD_FETCH_CONTEXT`): every provenance-conditioned detector that needs to
|
|
44
|
+
* know whether a line issues a fetch reuses this single pattern.
|
|
45
|
+
*/
|
|
46
|
+
export declare const FETCH_COMMAND_PATTERN: RegExp;
|
|
47
|
+
/**
|
|
48
|
+
* Escape a string for safe interpolation into a `new RegExp(...)` pattern.
|
|
49
|
+
*/
|
|
50
|
+
export declare function escapeRegExp(s: string): string;
|
|
51
|
+
/**
|
|
52
|
+
* A path split into its normalized directory component and its final segment.
|
|
53
|
+
* `dir` is the empty string ONLY when the raw path carried no directory
|
|
54
|
+
* information at all (a bare filename, or a `./`-prefixed one, which names
|
|
55
|
+
* the same CWD-relative location) — that empty string is what triggers the
|
|
56
|
+
* basename-only fallback in `isCorrelatedWithFetchDestination`.
|
|
57
|
+
*/
|
|
58
|
+
export interface NormalizedCorrelationPath {
|
|
59
|
+
dir: string;
|
|
60
|
+
base: string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Normalize a raw path captured out of a shell command into
|
|
64
|
+
* `{ dir, base }`: strip quotes, drop empty (`//`) and `.` segments, resolve
|
|
65
|
+
* `..` lexically, and preserve absolute-vs-relative as a real distinction
|
|
66
|
+
* (`/tmp/x` and `tmp/x` are different locations, so they must not correlate).
|
|
67
|
+
* Lexical only — the scanner never touches the filesystem, so there is no
|
|
68
|
+
* symlink/realpath resolution to do and none is implied.
|
|
69
|
+
*/
|
|
70
|
+
export declare function normalizeCorrelationPath(raw: string): NormalizedCorrelationPath;
|
|
71
|
+
/**
|
|
72
|
+
* The normalized final segment of `rawPath`. Callers use this for their own
|
|
73
|
+
* minimum-length gate (every current caller requires >= 3 chars) before
|
|
74
|
+
* handing the FULL path to `isCorrelatedWithFetchDestination` — the length
|
|
75
|
+
* policy stays a caller decision, exactly as it was before this file became
|
|
76
|
+
* path-aware.
|
|
77
|
+
*/
|
|
78
|
+
export declare function correlationTargetBasename(rawPath: string): string;
|
|
79
|
+
/**
|
|
80
|
+
* SMI-5431: the IMPLICIT download destination of a fetch command — the file
|
|
81
|
+
* written with NO explicit -o/-O/--output<space>/> redirect: `wget <url>` (no
|
|
82
|
+
* -O/-o) → URL last segment; `git clone <url>` → repo dir (minus `.git`);
|
|
83
|
+
* `curl --output=<file>` (equals form, missed by the explicit regex). A bare
|
|
84
|
+
* `curl <url>` GET writes to STDOUT → '' (never correlates). ReDoS-safe.
|
|
85
|
+
*
|
|
86
|
+
* SMI-6033 Wave 3: returns the destination PATH, not a bare basename — the
|
|
87
|
+
* `curl --output=/tmp/p` form carries a real directory the correlation check
|
|
88
|
+
* now compares against. The wget / git-clone forms genuinely have no
|
|
89
|
+
* directory component (both write into the CWD), so they still yield a bare
|
|
90
|
+
* final segment; that is a property of those commands, not discarded
|
|
91
|
+
* information.
|
|
92
|
+
*/
|
|
93
|
+
export declare function implicitDownloadDestination(line: string): string;
|
|
94
|
+
/**
|
|
95
|
+
* Distance-independent correlation: is `targetPath` (the raw path captured
|
|
96
|
+
* from the caller's own command — quotes and directory prefix INCLUDED; this
|
|
97
|
+
* function normalizes) the DOWNLOAD DESTINATION of any fetch command line,
|
|
98
|
+
* anywhere in the document (not just physically adjacent)? Covers explicit
|
|
99
|
+
* destinations (-o/-O/--output<space>/>, with an optional leading path) via
|
|
100
|
+
* regex, and implicit destinations (wget/git-clone/curl --output=) via
|
|
101
|
+
* `implicitDownloadDestination`. Anchored on the destination, NOT
|
|
102
|
+
* basename-anywhere, so a URL path/query/header value (a known FP class) and
|
|
103
|
+
* a bare `curl <url>` GET (writes to stdout) never correlate.
|
|
104
|
+
*
|
|
105
|
+
* Final segments must always match; directories must additionally match
|
|
106
|
+
* whenever BOTH sides carry one (see this file's header for the full rule and
|
|
107
|
+
* the FP class it closes).
|
|
108
|
+
*
|
|
109
|
+
* Callers gate the minimum basename length themselves before calling (every
|
|
110
|
+
* current caller requires >= 3 chars, via `correlationTargetBasename`) — this
|
|
111
|
+
* function imposes no minimum, so a future caller's own length policy stays a
|
|
112
|
+
* caller decision.
|
|
113
|
+
*/
|
|
114
|
+
export declare function isCorrelatedWithFetchDestination(targetPath: string, fetchLines: readonly string[]): boolean;
|
|
115
|
+
//# sourceMappingURL=SecurityScanner.fetch-correlation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SecurityScanner.fetch-correlation.d.ts","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.fetch-correlation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,QACkC,CAAA;AAEpE;;GAEG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED;;;;;;GAMG;AACH,MAAM,WAAW,yBAAyB;IACxC,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,yBAAyB,CAgB/E;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEjE;AAeD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAehE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gCAAgC,CAC9C,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,SAAS,MAAM,EAAE,GAC5B,OAAO,CAoBT"}
|