@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
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security Scanner — paste/snippet-host reputation + fetch-context escalation
|
|
3
|
+
* @module @skillsmith/core/security/scanner/SecurityScanner.paste-host
|
|
4
|
+
*
|
|
5
|
+
* SMI-6033 Wave 3 (Gap 4 fix): the originally-shipped version of this
|
|
6
|
+
* detector (commit b74c18436) implemented a SIMPLER, and in one specific way
|
|
7
|
+
* INCORRECT, design relative to the approved plan
|
|
8
|
+
* (docs/internal/implementation/smi-6033-clawhavoc-scanner-gaps.md, Gap 4):
|
|
9
|
+
* it used a single flat PASTE_HOST_DOMAINS list (incorrectly including
|
|
10
|
+
* transfer.sh/file.io alongside glot.io/pastebin.com) and treated ANY
|
|
11
|
+
* same-line fetch of a paste-host URL as standalone-critical, with no
|
|
12
|
+
* requirement that the fetched content actually be EXECUTED. This file fixes
|
|
13
|
+
* both:
|
|
14
|
+
*
|
|
15
|
+
* 1. Two separate reputation tiers (patterns.ts): `ANON_PASTE_HOSTS` (+
|
|
16
|
+
* `URL_SHORTENER_DOMAINS`, execution-gated the same way) vs.
|
|
17
|
+
* `TRANSIENT_TRANSFER_HOSTS` (never standalone-critical — a deliberate,
|
|
18
|
+
* documented exception for legitimate debugging/incident-response
|
|
19
|
+
* fetches of ephemeral reproducers).
|
|
20
|
+
* 2. Execution evidence is now REQUIRED for `ANON_PASTE_HOSTS`/shorteners
|
|
21
|
+
* to reach critical — either (a) the URL is piped directly to an
|
|
22
|
+
* interpreter on the same line (`curl <url> | bash`, no intermediate
|
|
23
|
+
* file), (b) `npx <url>` directly executes the fetched content (no
|
|
24
|
+
* pipe, no intermediate file either), or (c) the fetch destination is
|
|
25
|
+
* subsequently executed/chmod'd/sourced ELSEWHERE in the content,
|
|
26
|
+
* correlated via the shared `isCorrelatedWithFetchDestination` utility
|
|
27
|
+
* (SecurityScanner.fetch-correlation.ts). Fetched-but-not-executed, or
|
|
28
|
+
* merely linked, now correctly stays at the existing scanUrls()
|
|
29
|
+
* `url`:medium finding — no new finding here.
|
|
30
|
+
*
|
|
31
|
+
* (a)/(b) are a NEW, local execution-detection shape distinct from (c):
|
|
32
|
+
* `isCorrelatedWithFetchDestination` only detects "was this basename later
|
|
33
|
+
* written as a fetch command's DOWNLOAD DESTINATION, then referenced
|
|
34
|
+
* elsewhere" — it has no notion of a same-line direct pipe/direct-exec (no
|
|
35
|
+
* intermediate file at all). The direct-pipe/npx-direct-exec patterns below
|
|
36
|
+
* are local to this file (not promoted to the shared fetch-correlation
|
|
37
|
+
* module, which Gap 5's concurrent xattr fix does not touch) and are
|
|
38
|
+
* deliberately scoped to "the URL being piped/executed is specifically a
|
|
39
|
+
* paste-host or shortener domain" — `CODE_EXECUTION_PATTERNS`' own curl|bash
|
|
40
|
+
* pattern (patterns.ts) already fires independently for ANY curl|bash
|
|
41
|
+
* regardless of domain reputation; this file adds the DOMAIN-REPUTATION-aware
|
|
42
|
+
* escalation on top.
|
|
43
|
+
*
|
|
44
|
+
* `TRANSIENT_TRANSFER_HOSTS` never reaches critical: it emits
|
|
45
|
+
* `paste_host_fetch` at medium whenever it is an actual fetch target (the URL
|
|
46
|
+
* is the ARGUMENT of a same-line fetch verb — see `isActualFetchTarget`
|
|
47
|
+
* below), regardless of execution evidence — `curl file.io/x | bash` alone
|
|
48
|
+
* staying sub-threshold is intentional, not a gap (see patterns.ts's
|
|
49
|
+
* `TRANSIENT_TRANSFER_HOSTS` comment for the rationale).
|
|
50
|
+
*
|
|
51
|
+
* SMI-6033 Wave 3 (adversarial-review fix): the "is this URL fetched?" gate
|
|
52
|
+
* used to be a bare `FETCH_COMMAND_PATTERN` test against the whole LINE, so a
|
|
53
|
+
* fetch verb ANYWHERE on the line counted — `curl --version; see mirror docs
|
|
54
|
+
* at https://pastebin.com/abc` wrongly registered the pastebin URL as
|
|
55
|
+
* fetched. Replaced with `isActualFetchTarget`, which binds the URL to the
|
|
56
|
+
* verb by tokenizing the prefix that precedes it. Same bug class as Wave 4's
|
|
57
|
+
* `decoy_misdirection` fix; see `isActualFetchTarget`'s own doc comment for
|
|
58
|
+
* the two deliberate adaptations to this detector's inputs. This affects ONLY
|
|
59
|
+
* the same-line fetch-target gate — the direct-pipe and npx-direct-exec
|
|
60
|
+
* execution-evidence paths below are a different code path and are unchanged.
|
|
61
|
+
*
|
|
62
|
+
* Known, documented residual (static scanner, no network I/O): redirect-
|
|
63
|
+
* chain/final-host resolution is out of scope by design — custom domains,
|
|
64
|
+
* redirects, and compromised allowlisted hosts are a known residual.
|
|
65
|
+
*/
|
|
66
|
+
import { analyzeMarkdownContext, isDocumentationContext } from './SecurityScanner.helpers.js';
|
|
67
|
+
import { extractUrls } from './SecurityScanner.urls.js';
|
|
68
|
+
import { ANON_PASTE_HOSTS, TRANSIENT_TRANSFER_HOSTS, URL_SHORTENER_DOMAINS } from './patterns.js';
|
|
69
|
+
import { safeRegexCheck, safeRegexTest } from './regex-utils.js';
|
|
70
|
+
import { correlationTargetBasename, isCorrelatedWithFetchDestination, } from './SecurityScanner.fetch-correlation.js';
|
|
71
|
+
// URL_SHORTENER_DOMAINS folds into the SAME critical-eligibility set as
|
|
72
|
+
// ANON_PASTE_HOSTS (both require execution evidence to reach critical) —
|
|
73
|
+
// they are exported separately from patterns.ts purely for documentation
|
|
74
|
+
// clarity (a shortener is not a "content host" the way pastebin is).
|
|
75
|
+
const ANON_PASTE_HOST_SET = new Set([...ANON_PASTE_HOSTS, ...URL_SHORTENER_DOMAINS].map((d) => d.toLowerCase()));
|
|
76
|
+
const TRANSIENT_TRANSFER_HOST_SET = new Set(TRANSIENT_TRANSFER_HOSTS.map((d) => d.toLowerCase()));
|
|
77
|
+
/** Which reputation tier (if any) does `url`'s hostname belong to? */
|
|
78
|
+
function classifyPasteHostTier(url) {
|
|
79
|
+
let hostname;
|
|
80
|
+
try {
|
|
81
|
+
hostname = new URL(url).hostname.toLowerCase();
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
const matchesSet = (set) => {
|
|
87
|
+
for (const domain of set) {
|
|
88
|
+
if (hostname === domain || hostname.endsWith('.' + domain))
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
return false;
|
|
92
|
+
};
|
|
93
|
+
if (matchesSet(ANON_PASTE_HOST_SET))
|
|
94
|
+
return 'anon';
|
|
95
|
+
if (matchesSet(TRANSIENT_TRANSFER_HOST_SET))
|
|
96
|
+
return 'transient';
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
// Direct pipe to an interpreter, no intermediate file: `curl <url> | bash`.
|
|
100
|
+
// Anchored at the FIRST pipe (mirrors CODE_EXECUTION_PATTERNS' own curl|bash
|
|
101
|
+
// shape in patterns.ts) and scoped to "the piped URL is specifically the
|
|
102
|
+
// paste-host/shortener URL under test" by requiring the URL text to appear
|
|
103
|
+
// BEFORE that pipe, alongside a fetch verb (curl/wget).
|
|
104
|
+
const FETCH_VERB_PATTERN = /\b(?:curl|wget)\b/i;
|
|
105
|
+
const PIPE_TO_INTERPRETER_TAIL = /^\|\s*(?:sudo\s+(?:-[A-Za-z]+\s+)?)?(?:(?:ba|z|da)?sh|python[23]?|node|ruby|perl|php|fish|bun|deno)\b/i;
|
|
106
|
+
function isDirectPipeToInterpreter(url, lineContent) {
|
|
107
|
+
const pipeIndex = lineContent.indexOf('|');
|
|
108
|
+
if (pipeIndex < 0)
|
|
109
|
+
return false;
|
|
110
|
+
const beforePipe = lineContent.slice(0, pipeIndex);
|
|
111
|
+
if (!beforePipe.includes(url))
|
|
112
|
+
return false;
|
|
113
|
+
if (!safeRegexCheck(FETCH_VERB_PATTERN, beforePipe))
|
|
114
|
+
return false;
|
|
115
|
+
return safeRegexCheck(PIPE_TO_INTERPRETER_TAIL, lineContent.slice(pipeIndex));
|
|
116
|
+
}
|
|
117
|
+
// npx directly executes whatever it fetches from a URL — no pipe, no
|
|
118
|
+
// intermediate file — so this is its own direct-execution shape (npx never
|
|
119
|
+
// just downloads, so a line that fetches a URL through npx has, by that
|
|
120
|
+
// fact alone, executed it).
|
|
121
|
+
const NPX_DIRECT_EXEC_PATTERN = /\bnpx\b[^\n]{0,80}https?:\/\//i;
|
|
122
|
+
// ============================================================================
|
|
123
|
+
// Same-line fetch-TARGET binding (SMI-6033 Wave 3 adversarial-review fix)
|
|
124
|
+
// ============================================================================
|
|
125
|
+
/**
|
|
126
|
+
* This detector used to decide "is this paste-host URL fetched?" with a bare
|
|
127
|
+
* `FETCH_COMMAND_PATTERN` test against the whole LINE — i.e. "does a fetch
|
|
128
|
+
* verb appear anywhere on this line", not "is THIS URL the argument to that
|
|
129
|
+
* verb". `curl --version; see mirror docs at https://pastebin.com/abc`
|
|
130
|
+
* therefore registered the pastebin URL as fetched purely because the token
|
|
131
|
+
* `curl` appeared earlier in an unrelated command.
|
|
132
|
+
*
|
|
133
|
+
* Same bug class as the one already fixed in Wave 4's `decoy_misdirection`
|
|
134
|
+
* detector (`SecurityScanner.decoy.ts`'s `isActualFetchTarget`), and the fix
|
|
135
|
+
* is the same idea — tokenize the line prefix that precedes the URL and
|
|
136
|
+
* require the URL to actually be a fetch verb's argument — with two
|
|
137
|
+
* deliberate adaptations for this detector's inputs:
|
|
138
|
+
*
|
|
139
|
+
* 1. A command separator (`;`, `|`, `&`) does not disqualify the whole
|
|
140
|
+
* line, it just bounds the search: only the segment the URL itself sits
|
|
141
|
+
* in can bind it. `cd /tmp && curl <url> | bash` is a real attack shape
|
|
142
|
+
* that decoy.ts's stricter "reject on any separator" rule would drop,
|
|
143
|
+
* and this detector's whole point is catching piped/chained execution.
|
|
144
|
+
* 2. The verb need not be the FIRST token of that segment. Paste-host
|
|
145
|
+
* fetches routinely arrive wrapped in markdown/shell decoration
|
|
146
|
+
* (`- Run \`curl <url> | bash\``, `$ curl <url>`, `bash <(curl <url>)`),
|
|
147
|
+
* so the check anchors on the LAST fetch verb in the segment and then
|
|
148
|
+
* requires every token between that verb and the URL to be a flag (or
|
|
149
|
+
* the value argument of a value-taking flag). Prose between the verb
|
|
150
|
+
* and the URL — "see mirror docs at" — still fails, which is exactly
|
|
151
|
+
* the FP being closed.
|
|
152
|
+
*/
|
|
153
|
+
const FETCH_VERB_TOKENS = new Set(['curl', 'wget', 'npx']);
|
|
154
|
+
/** A bare flag token, e.g. `-o`, `-fsSL`, `--output`, `--data=x`. */
|
|
155
|
+
const FLAG_TOKEN = /^-{1,2}[A-Za-z][\w-]*$/;
|
|
156
|
+
/**
|
|
157
|
+
* Common curl/wget/npx flags that take a SEPARATE value token (e.g.
|
|
158
|
+
* `-o setup.sh`, `-X POST`) — that value token must be consumed as part of
|
|
159
|
+
* the flag, not mistaken for prose.
|
|
160
|
+
*/
|
|
161
|
+
const VALUE_TAKING_FLAGS = new Set([
|
|
162
|
+
'-o',
|
|
163
|
+
'-x',
|
|
164
|
+
'-h',
|
|
165
|
+
'-d',
|
|
166
|
+
'-a',
|
|
167
|
+
'-e',
|
|
168
|
+
'-u',
|
|
169
|
+
'-b',
|
|
170
|
+
'--output',
|
|
171
|
+
'--request',
|
|
172
|
+
'--header',
|
|
173
|
+
'--data',
|
|
174
|
+
'--data-raw',
|
|
175
|
+
'--data-binary',
|
|
176
|
+
'--data-urlencode',
|
|
177
|
+
'--user-agent',
|
|
178
|
+
'--referer',
|
|
179
|
+
'--proxy',
|
|
180
|
+
'--cookie',
|
|
181
|
+
]);
|
|
182
|
+
/**
|
|
183
|
+
* Strip markdown/shell decoration off a token (leading backticks, quotes,
|
|
184
|
+
* `$`/`>` prompt markers, `<(`; trailing quotes, backticks, brackets) while
|
|
185
|
+
* preserving the leading `-`/`--` that makes a flag a flag. Lowercased, so
|
|
186
|
+
* the verb and flag lookups above stay case-insensitive.
|
|
187
|
+
*/
|
|
188
|
+
function bareToken(token) {
|
|
189
|
+
return token
|
|
190
|
+
.replace(/^[^A-Za-z0-9/.-]+/, '')
|
|
191
|
+
.replace(/[^A-Za-z0-9/._-]+$/, '')
|
|
192
|
+
.toLowerCase();
|
|
193
|
+
}
|
|
194
|
+
/** Is the URL that follows `prefix` on this line the argument of a fetch verb? */
|
|
195
|
+
function isFetchVerbArgument(prefix) {
|
|
196
|
+
// Only the segment after the LAST command separator can bind the URL —
|
|
197
|
+
// anything before it belongs to a different command.
|
|
198
|
+
const segment = prefix.split(/[;|&]/).pop() ?? '';
|
|
199
|
+
const tokens = segment
|
|
200
|
+
.trim()
|
|
201
|
+
.split(/\s+/)
|
|
202
|
+
.map(bareToken)
|
|
203
|
+
.filter((t) => t.length > 0);
|
|
204
|
+
let afterVerb = -1;
|
|
205
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
206
|
+
if (tokens[i] === 'git' && tokens[i + 1] === 'clone')
|
|
207
|
+
afterVerb = i + 2;
|
|
208
|
+
else if (FETCH_VERB_TOKENS.has(tokens[i]))
|
|
209
|
+
afterVerb = i + 1;
|
|
210
|
+
}
|
|
211
|
+
if (afterVerb < 0)
|
|
212
|
+
return false;
|
|
213
|
+
for (let i = afterVerb; i < tokens.length; i++) {
|
|
214
|
+
const token = tokens[i];
|
|
215
|
+
if (!safeRegexCheck(FLAG_TOKEN, token))
|
|
216
|
+
return false;
|
|
217
|
+
if (VALUE_TAKING_FLAGS.has(token) && i + 1 < tokens.length)
|
|
218
|
+
i++; // consume the flag's value
|
|
219
|
+
}
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* True when `url` is actually the argument to a fetch verb somewhere on
|
|
224
|
+
* `lineContent` — checked at EVERY occurrence of the URL on the line, so a
|
|
225
|
+
* prose mention followed by a real fetch of the same URL still binds.
|
|
226
|
+
*/
|
|
227
|
+
function isActualFetchTarget(lineContent, url) {
|
|
228
|
+
for (let from = 0;;) {
|
|
229
|
+
const urlIndex = lineContent.indexOf(url, from);
|
|
230
|
+
if (urlIndex < 0)
|
|
231
|
+
return false;
|
|
232
|
+
if (isFetchVerbArgument(lineContent.slice(0, urlIndex)))
|
|
233
|
+
return true;
|
|
234
|
+
from = urlIndex + 1;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// Execution/chmod/source instructions whose target path is correlated
|
|
238
|
+
// against a paste-host/shortener fetch's download destination via the
|
|
239
|
+
// shared `isCorrelatedWithFetchDestination`. Deliberately local to this file
|
|
240
|
+
// (not imported from SecurityScanner.compound.ts, which Gap 5's concurrent
|
|
241
|
+
// xattr fix is editing) — same normalization (strip quotes, ≥3-char gate on
|
|
242
|
+
// the final path segment) as compound.ts's own chmod correlation.
|
|
243
|
+
const CHMOD_TARGET = /\bchmod\s+(?:-[A-Za-z]+\s+)?(?:[0-7]{3,4}|[ugoa]*(?:[+\-=][rwxXstugo]+(?:,[ugoa]*[+\-=][rwxXstugo]*)*)+)\s+(\S+)/i;
|
|
244
|
+
const DIRECT_EXEC_TARGET = /\b(?:sudo\s+)?(?:(?:ba|z|da)?sh|python[23]?|node|ruby|perl|php|fish|bun|deno)\s+(\.{0,2}\/?[\w-]+(?:[./][\w-]+)*)/i;
|
|
245
|
+
const DOT_SLASH_EXEC_TARGET = /(?:^|[\s;&|])\.\/([\w-]+(?:[./][\w-]+)*)/;
|
|
246
|
+
// "sourced" is deliberately scoped to the portable `source X` keyword, not
|
|
247
|
+
// the bare POSIX `. X` alias — the latter is too easily confused with prose
|
|
248
|
+
// ("... . More info at ...") to be worth the extra correlation-signal noise.
|
|
249
|
+
const SOURCE_TARGET = /(?:^|[;&|]\s*)source\s+(\.{0,2}\/?[\w-]+(?:[./][\w-]+)+)/i;
|
|
250
|
+
const EXEC_TARGET_PATTERNS = [
|
|
251
|
+
CHMOD_TARGET,
|
|
252
|
+
DIRECT_EXEC_TARGET,
|
|
253
|
+
DOT_SLASH_EXEC_TARGET,
|
|
254
|
+
SOURCE_TARGET,
|
|
255
|
+
];
|
|
256
|
+
/**
|
|
257
|
+
* Every exec/chmod/source target PATH (final segment ≥3 chars, quotes
|
|
258
|
+
* stripped) across the document. SMI-6033 Wave 3: full paths, not basenames —
|
|
259
|
+
* the shared correlation utility is directory-aware and must not be handed a
|
|
260
|
+
* path-stripped target.
|
|
261
|
+
*/
|
|
262
|
+
function collectExecTargetPaths(lines) {
|
|
263
|
+
const paths = [];
|
|
264
|
+
for (const line of lines) {
|
|
265
|
+
for (const pattern of EXEC_TARGET_PATTERNS) {
|
|
266
|
+
const m = safeRegexTest(pattern, line);
|
|
267
|
+
if (!m?.[1])
|
|
268
|
+
continue;
|
|
269
|
+
const targetPath = m[1].replace(/['"]/g, '');
|
|
270
|
+
if (correlationTargetBasename(targetPath).length >= 3)
|
|
271
|
+
paths.push(targetPath);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return paths;
|
|
275
|
+
}
|
|
276
|
+
/** Was `fetchLine`'s download destination later executed/chmod'd/sourced elsewhere? */
|
|
277
|
+
function isExecutedElsewhereCorrelated(fetchLine, execTargetPaths) {
|
|
278
|
+
return execTargetPaths.some((path) => isCorrelatedWithFetchDestination(path, [fetchLine]));
|
|
279
|
+
}
|
|
280
|
+
export function scanPasteHostFetch(content, lineContexts) {
|
|
281
|
+
const findings = [];
|
|
282
|
+
const lines = content.split('\n');
|
|
283
|
+
const contexts = lineContexts ?? analyzeMarkdownContext(content);
|
|
284
|
+
const urls = extractUrls(content);
|
|
285
|
+
const execTargetPaths = collectExecTargetPaths(lines);
|
|
286
|
+
for (const { url, line } of urls) {
|
|
287
|
+
const tier = classifyPasteHostTier(url);
|
|
288
|
+
if (!tier)
|
|
289
|
+
continue;
|
|
290
|
+
const lineIndex = line - 1;
|
|
291
|
+
const lineContent = lines[lineIndex] ?? '';
|
|
292
|
+
// The URL is a fetch TARGET only when it is ACTUALLY the argument of a
|
|
293
|
+
// fetch verb on the SAME line (SMI-6033 Wave 3 — see isActualFetchTarget
|
|
294
|
+
// above for the FP this replaced). Deliberately not widened to a bounded
|
|
295
|
+
// window (unlike the chmod/archive compound signals) since a URL
|
|
296
|
+
// literally is the thing being fetched when it is that verb's argument;
|
|
297
|
+
// widening would risk correlating an unrelated fetch command to an
|
|
298
|
+
// unrelated nearby paste-host mention.
|
|
299
|
+
if (!isActualFetchTarget(lineContent, url))
|
|
300
|
+
continue; // merely linked -> scanUrls()'s url:medium finding already covers this
|
|
301
|
+
const ctx = contexts[lineIndex];
|
|
302
|
+
const inDocContext = ctx ? isDocumentationContext(ctx) : false;
|
|
303
|
+
if (tier === 'transient') {
|
|
304
|
+
// Always medium/co-signal-eligible, NEVER standalone-critical —
|
|
305
|
+
// regardless of execution evidence. See patterns.ts's
|
|
306
|
+
// TRANSIENT_TRANSFER_HOSTS comment for the rationale.
|
|
307
|
+
findings.push({
|
|
308
|
+
type: 'paste_host_fetch',
|
|
309
|
+
severity: inDocContext ? 'low' : 'medium',
|
|
310
|
+
message: `Ephemeral file-transfer host URL fetched (never standalone-critical by design): ${url}`,
|
|
311
|
+
location: lineContent.trim().slice(0, 100),
|
|
312
|
+
lineNumber: line,
|
|
313
|
+
category: 'paste_host_fetch',
|
|
314
|
+
inDocumentationContext: inDocContext,
|
|
315
|
+
confidence: inDocContext ? 'low' : 'medium',
|
|
316
|
+
});
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
// tier === 'anon' (ANON_PASTE_HOSTS or a URL shortener): critical
|
|
320
|
+
// requires actual EXECUTION evidence, not just a fetch.
|
|
321
|
+
const executed = isDirectPipeToInterpreter(url, lineContent) ||
|
|
322
|
+
safeRegexCheck(NPX_DIRECT_EXEC_PATTERN, lineContent) ||
|
|
323
|
+
isExecutedElsewhereCorrelated(lineContent, execTargetPaths);
|
|
324
|
+
if (!executed)
|
|
325
|
+
continue; // fetched but not executed -> scanUrls()'s url:medium finding already covers this
|
|
326
|
+
findings.push({
|
|
327
|
+
type: 'paste_host_fetch',
|
|
328
|
+
// Standalone-critical (execution evidence required, see above) — doc
|
|
329
|
+
// context is the only downgrade, matching every other detector's
|
|
330
|
+
// noise-reduction convention in this Wave.
|
|
331
|
+
severity: inDocContext ? 'low' : 'critical',
|
|
332
|
+
message: `Paste/snippet-host URL is the target of an execution instruction: ${url}`,
|
|
333
|
+
location: lineContent.trim().slice(0, 100),
|
|
334
|
+
lineNumber: line,
|
|
335
|
+
category: 'paste_host_fetch',
|
|
336
|
+
inDocumentationContext: inDocContext,
|
|
337
|
+
confidence: inDocContext ? 'low' : 'high',
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
return findings;
|
|
341
|
+
}
|
|
342
|
+
//# sourceMappingURL=SecurityScanner.paste-host.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SecurityScanner.paste-host.js","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.paste-host.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security Scanner — risk score calculation
|
|
3
|
+
* @module @skillsmith/core/security/scanner/SecurityScanner.risk-score
|
|
4
|
+
*
|
|
5
|
+
* SMI-6033 Wave 4: extracted out of `SecurityScanner.helpers.ts` (which had
|
|
6
|
+
* grown to 504/500 lines, over this repo's file-length gate, once Wave 4's
|
|
7
|
+
* `decoyMisdirection` breakdown field/switch-arm/cap/sum entries landed).
|
|
8
|
+
* This filename intentionally matches the one already referenced by the
|
|
9
|
+
* separate, still-open SMI-5879 Wave 2 PR (#2192,
|
|
10
|
+
* `feat/smi-5879-wave2-pattern-port` — see that branch's
|
|
11
|
+
* `scripts/indexer/smi5879-corroboration.types.ts`, which already names
|
|
12
|
+
* `packages/core/src/security/scanner/SecurityScanner.risk-score.ts` as its
|
|
13
|
+
* own planned extraction target for this same function): reusing the name
|
|
14
|
+
* that PR already committed to avoids a second, differently-named
|
|
15
|
+
* competing extraction landing later and forcing a rename/merge fight.
|
|
16
|
+
*/
|
|
17
|
+
import type { SecurityFinding, RiskScoreBreakdown } from './types.js';
|
|
18
|
+
/**
|
|
19
|
+
* SMI-685: Calculate risk score from findings
|
|
20
|
+
* SMI-1513: Accounts for confidence levels (low confidence = reduced weight)
|
|
21
|
+
* Aggregates multiple findings into a risk score from 0-100
|
|
22
|
+
*/
|
|
23
|
+
export declare function calculateRiskScore(findings: SecurityFinding[]): {
|
|
24
|
+
total: number;
|
|
25
|
+
breakdown: RiskScoreBreakdown;
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=SecurityScanner.risk-score.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SecurityScanner.risk-score.d.ts","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.risk-score.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAqB,MAAM,YAAY,CAAA;AAGxF;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,eAAe,EAAE,GAAG;IAC/D,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,kBAAkB,CAAA;CAC9B,CAqKA"}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security Scanner — risk score calculation
|
|
3
|
+
* @module @skillsmith/core/security/scanner/SecurityScanner.risk-score
|
|
4
|
+
*
|
|
5
|
+
* SMI-6033 Wave 4: extracted out of `SecurityScanner.helpers.ts` (which had
|
|
6
|
+
* grown to 504/500 lines, over this repo's file-length gate, once Wave 4's
|
|
7
|
+
* `decoyMisdirection` breakdown field/switch-arm/cap/sum entries landed).
|
|
8
|
+
* This filename intentionally matches the one already referenced by the
|
|
9
|
+
* separate, still-open SMI-5879 Wave 2 PR (#2192,
|
|
10
|
+
* `feat/smi-5879-wave2-pattern-port` — see that branch's
|
|
11
|
+
* `scripts/indexer/smi5879-corroboration.types.ts`, which already names
|
|
12
|
+
* `packages/core/src/security/scanner/SecurityScanner.risk-score.ts` as its
|
|
13
|
+
* own planned extraction target for this same function): reusing the name
|
|
14
|
+
* that PR already committed to avoids a second, differently-named
|
|
15
|
+
* competing extraction landing later and forcing a rename/merge fight.
|
|
16
|
+
*/
|
|
17
|
+
import { SEVERITY_WEIGHTS, CATEGORY_WEIGHTS } from './weights.js';
|
|
18
|
+
/**
|
|
19
|
+
* SMI-685: Calculate risk score from findings
|
|
20
|
+
* SMI-1513: Accounts for confidence levels (low confidence = reduced weight)
|
|
21
|
+
* Aggregates multiple findings into a risk score from 0-100
|
|
22
|
+
*/
|
|
23
|
+
export function calculateRiskScore(findings) {
|
|
24
|
+
const breakdown = {
|
|
25
|
+
jailbreak: 0,
|
|
26
|
+
socialEngineering: 0,
|
|
27
|
+
promptLeaking: 0,
|
|
28
|
+
dataExfiltration: 0,
|
|
29
|
+
privilegeEscalation: 0,
|
|
30
|
+
suspiciousCode: 0,
|
|
31
|
+
sensitivePaths: 0,
|
|
32
|
+
externalUrls: 0,
|
|
33
|
+
aiDefence: 0,
|
|
34
|
+
ssrf: 0,
|
|
35
|
+
pii: 0,
|
|
36
|
+
codeExecution: 0,
|
|
37
|
+
obfuscatedDirective: 0,
|
|
38
|
+
typosquat: 0,
|
|
39
|
+
gatekeeperBypass: 0,
|
|
40
|
+
archiveEvasion: 0,
|
|
41
|
+
pasteHostFetch: 0,
|
|
42
|
+
encodedPayload: 0,
|
|
43
|
+
decoyMisdirection: 0,
|
|
44
|
+
};
|
|
45
|
+
const confidenceWeights = {
|
|
46
|
+
high: 1.0,
|
|
47
|
+
medium: 0.7,
|
|
48
|
+
low: 0.3,
|
|
49
|
+
};
|
|
50
|
+
for (const finding of findings) {
|
|
51
|
+
const severityWeight = SEVERITY_WEIGHTS[finding.severity];
|
|
52
|
+
const categoryWeight = CATEGORY_WEIGHTS[finding.type] ?? 1.0;
|
|
53
|
+
const confidenceWeight = confidenceWeights[finding.confidence ?? 'high'];
|
|
54
|
+
const score = severityWeight * categoryWeight * confidenceWeight;
|
|
55
|
+
switch (finding.type) {
|
|
56
|
+
case 'jailbreak':
|
|
57
|
+
breakdown.jailbreak += score;
|
|
58
|
+
break;
|
|
59
|
+
case 'social_engineering':
|
|
60
|
+
breakdown.socialEngineering += score;
|
|
61
|
+
break;
|
|
62
|
+
case 'prompt_leaking':
|
|
63
|
+
breakdown.promptLeaking += score;
|
|
64
|
+
break;
|
|
65
|
+
case 'data_exfiltration':
|
|
66
|
+
breakdown.dataExfiltration += score;
|
|
67
|
+
break;
|
|
68
|
+
case 'privilege_escalation':
|
|
69
|
+
breakdown.privilegeEscalation += score;
|
|
70
|
+
break;
|
|
71
|
+
case 'suspicious_pattern':
|
|
72
|
+
breakdown.suspiciousCode += score;
|
|
73
|
+
break;
|
|
74
|
+
case 'sensitive_path':
|
|
75
|
+
breakdown.sensitivePaths += score;
|
|
76
|
+
break;
|
|
77
|
+
case 'url':
|
|
78
|
+
breakdown.externalUrls += score;
|
|
79
|
+
break;
|
|
80
|
+
case 'ai_defence':
|
|
81
|
+
breakdown.aiDefence += score;
|
|
82
|
+
break;
|
|
83
|
+
case 'ssrf':
|
|
84
|
+
breakdown.ssrf += score;
|
|
85
|
+
break;
|
|
86
|
+
case 'pii':
|
|
87
|
+
breakdown.pii += score;
|
|
88
|
+
break;
|
|
89
|
+
case 'code_execution':
|
|
90
|
+
breakdown.codeExecution += score;
|
|
91
|
+
break;
|
|
92
|
+
case 'obfuscated_directive':
|
|
93
|
+
breakdown.obfuscatedDirective += score;
|
|
94
|
+
break;
|
|
95
|
+
case 'typosquat':
|
|
96
|
+
// SMI-595: this switch has NO default case — a missing arm here would
|
|
97
|
+
// silently drop every typosquat finding from the breakdown with zero
|
|
98
|
+
// compile-time or runtime error. See the switch-case coverage test in
|
|
99
|
+
// SecurityScanner.scoring.test.ts, added specifically to guard this.
|
|
100
|
+
breakdown.typosquat += score;
|
|
101
|
+
break;
|
|
102
|
+
case 'gatekeeper_bypass':
|
|
103
|
+
breakdown.gatekeeperBypass += score;
|
|
104
|
+
break;
|
|
105
|
+
case 'archive_evasion':
|
|
106
|
+
breakdown.archiveEvasion += score;
|
|
107
|
+
break;
|
|
108
|
+
case 'paste_host_fetch':
|
|
109
|
+
breakdown.pasteHostFetch += score;
|
|
110
|
+
break;
|
|
111
|
+
case 'encoded_payload':
|
|
112
|
+
breakdown.encodedPayload += score;
|
|
113
|
+
break;
|
|
114
|
+
case 'decoy_misdirection':
|
|
115
|
+
breakdown.decoyMisdirection += score;
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Cap each category at 100
|
|
120
|
+
breakdown.jailbreak = Math.min(100, breakdown.jailbreak);
|
|
121
|
+
breakdown.socialEngineering = Math.min(100, breakdown.socialEngineering);
|
|
122
|
+
breakdown.promptLeaking = Math.min(100, breakdown.promptLeaking);
|
|
123
|
+
breakdown.dataExfiltration = Math.min(100, breakdown.dataExfiltration);
|
|
124
|
+
breakdown.privilegeEscalation = Math.min(100, breakdown.privilegeEscalation);
|
|
125
|
+
breakdown.suspiciousCode = Math.min(100, breakdown.suspiciousCode);
|
|
126
|
+
breakdown.sensitivePaths = Math.min(100, breakdown.sensitivePaths);
|
|
127
|
+
breakdown.externalUrls = Math.min(100, breakdown.externalUrls);
|
|
128
|
+
breakdown.aiDefence = Math.min(100, breakdown.aiDefence);
|
|
129
|
+
breakdown.ssrf = Math.min(100, breakdown.ssrf);
|
|
130
|
+
breakdown.pii = Math.min(100, breakdown.pii);
|
|
131
|
+
breakdown.codeExecution = Math.min(100, breakdown.codeExecution);
|
|
132
|
+
breakdown.obfuscatedDirective = Math.min(100, breakdown.obfuscatedDirective);
|
|
133
|
+
breakdown.typosquat = Math.min(100, breakdown.typosquat);
|
|
134
|
+
breakdown.gatekeeperBypass = Math.min(100, breakdown.gatekeeperBypass);
|
|
135
|
+
breakdown.archiveEvasion = Math.min(100, breakdown.archiveEvasion);
|
|
136
|
+
breakdown.pasteHostFetch = Math.min(100, breakdown.pasteHostFetch);
|
|
137
|
+
breakdown.encodedPayload = Math.min(100, breakdown.encodedPayload);
|
|
138
|
+
breakdown.decoyMisdirection = Math.min(100, breakdown.decoyMisdirection);
|
|
139
|
+
// SMI-5359 Wave 4.2: the two new categories use a 0.40 coefficient and are ADDITIVE
|
|
140
|
+
// (the original eleven coefficients sum to 1.0; these add on top). No code assumes the
|
|
141
|
+
// coefficients sum to 1.0, and the final Math.min(100, ...) cap is preserved — so a skill
|
|
142
|
+
// that triggers neither new category scores exactly as before (both breakdown entries 0).
|
|
143
|
+
// SMI-595: typosquat is likewise ADDITIVE, at the 0.04 coefficient already used for
|
|
144
|
+
// sensitivePaths/externalUrls/ssrf (the "advisory tier" of this second-stage formula) —
|
|
145
|
+
// NOT folded into the eleven-category budget that sums to 1.0. See weights.ts's
|
|
146
|
+
// CATEGORY_WEIGHTS.typosquat comment for the worked calculation this pairs with.
|
|
147
|
+
// SMI-6033 Wave 2: gatekeeperBypass/archiveEvasion/pasteHostFetch are
|
|
148
|
+
// ADDITIVE at the 0.40 coefficient (matching codeExecution/
|
|
149
|
+
// obfuscatedDirective's top tier) — see weights.ts's
|
|
150
|
+
// CATEGORY_WEIGHTS.gatekeeper_bypass comment for the worked calculation
|
|
151
|
+
// showing how ONE weight/coefficient pair produces both the
|
|
152
|
+
// standalone-critical and advisory-medium outcomes via severity alone.
|
|
153
|
+
// encodedPayload is likewise ADDITIVE, at the 0.04 coefficient (the same
|
|
154
|
+
// advisory tier as sensitivePaths/externalUrls/ssrf/typosquat) — see
|
|
155
|
+
// weights.ts's CATEGORY_WEIGHTS.encoded_payload comment.
|
|
156
|
+
const total = Math.min(100, Math.round(breakdown.jailbreak * 0.2 +
|
|
157
|
+
breakdown.socialEngineering * 0.11 +
|
|
158
|
+
breakdown.promptLeaking * 0.11 +
|
|
159
|
+
breakdown.dataExfiltration * 0.08 +
|
|
160
|
+
breakdown.privilegeEscalation * 0.11 +
|
|
161
|
+
breakdown.suspiciousCode * 0.07 +
|
|
162
|
+
breakdown.sensitivePaths * 0.04 +
|
|
163
|
+
breakdown.externalUrls * 0.04 +
|
|
164
|
+
breakdown.aiDefence * 0.12 +
|
|
165
|
+
breakdown.ssrf * 0.04 +
|
|
166
|
+
breakdown.pii * 0.08 +
|
|
167
|
+
breakdown.codeExecution * 0.4 +
|
|
168
|
+
breakdown.obfuscatedDirective * 0.4 +
|
|
169
|
+
breakdown.typosquat * 0.04 +
|
|
170
|
+
breakdown.gatekeeperBypass * 0.4 +
|
|
171
|
+
breakdown.archiveEvasion * 0.4 +
|
|
172
|
+
breakdown.pasteHostFetch * 0.4 +
|
|
173
|
+
breakdown.encodedPayload * 0.04 +
|
|
174
|
+
// SMI-6033 Wave 4 (Gap 6): additive 0.04, same advisory tier as encodedPayload/typosquat.
|
|
175
|
+
breakdown.decoyMisdirection * 0.04));
|
|
176
|
+
return { total, breakdown };
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=SecurityScanner.risk-score.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SecurityScanner.risk-score.js","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.risk-score.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security Scanner — URL extraction
|
|
3
|
+
* @module @skillsmith/core/security/scanner/SecurityScanner.urls
|
|
4
|
+
*
|
|
5
|
+
* SMI-6033 Wave 2 (Gap 4): split out of SecurityScanner.helpers.ts, which the
|
|
6
|
+
* promotion below pushed over the 500-line audit:standards gate. Promoted
|
|
7
|
+
* out of a private SecurityScanner.ts method (`extractUrls`) so `scanUrls`
|
|
8
|
+
* and the new `scanPasteHostFetch` detector (SecurityScanner.paste-host.ts)
|
|
9
|
+
* share ONE extraction implementation instead of two
|
|
10
|
+
* independently-drifting regex sweeps over the same content. Pure
|
|
11
|
+
* extraction — behavior is unchanged from the prior private method (same
|
|
12
|
+
* regex, same per-line scan, same return shape).
|
|
13
|
+
*/
|
|
14
|
+
/** A URL found in scanned content, plus the (1-indexed) line it appeared on. */
|
|
15
|
+
export interface ExtractedUrl {
|
|
16
|
+
url: string;
|
|
17
|
+
line: number;
|
|
18
|
+
}
|
|
19
|
+
export declare function extractUrls(content: string): ExtractedUrl[];
|
|
20
|
+
//# sourceMappingURL=SecurityScanner.urls.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SecurityScanner.urls.d.ts","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.urls.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,gFAAgF;AAChF,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;CACb;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,EAAE,CAa3D"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security Scanner — URL extraction
|
|
3
|
+
* @module @skillsmith/core/security/scanner/SecurityScanner.urls
|
|
4
|
+
*
|
|
5
|
+
* SMI-6033 Wave 2 (Gap 4): split out of SecurityScanner.helpers.ts, which the
|
|
6
|
+
* promotion below pushed over the 500-line audit:standards gate. Promoted
|
|
7
|
+
* out of a private SecurityScanner.ts method (`extractUrls`) so `scanUrls`
|
|
8
|
+
* and the new `scanPasteHostFetch` detector (SecurityScanner.paste-host.ts)
|
|
9
|
+
* share ONE extraction implementation instead of two
|
|
10
|
+
* independently-drifting regex sweeps over the same content. Pure
|
|
11
|
+
* extraction — behavior is unchanged from the prior private method (same
|
|
12
|
+
* regex, same per-line scan, same return shape).
|
|
13
|
+
*/
|
|
14
|
+
export function extractUrls(content) {
|
|
15
|
+
const urlPattern = /https?:\/\/[^\s<>"')\]]+/gi;
|
|
16
|
+
const lines = content.split('\n');
|
|
17
|
+
const results = [];
|
|
18
|
+
lines.forEach((line, index) => {
|
|
19
|
+
let match;
|
|
20
|
+
while ((match = urlPattern.exec(line)) !== null) {
|
|
21
|
+
results.push({ url: match[0], line: index + 1 });
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
return results;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=SecurityScanner.urls.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SecurityScanner.urls.js","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.urls.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAQH,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,MAAM,UAAU,GAAG,4BAA4B,CAAA;IAC/C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IACjC,MAAM,OAAO,GAAmB,EAAE,CAAA;IAElC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QAC5B,IAAI,KAAK,CAAA;QACT,OAAO,CAAC,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YAChD,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,GAAG,CAAC,EAAE,CAAC,CAAA;QAClD,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,OAAO,OAAO,CAAA;AAChB,CAAC"}
|
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
* Re-exports for security scanning functionality.
|
|
5
5
|
*/
|
|
6
6
|
export type { SecurityFindingType, SecuritySeverity, SecurityFinding, RiskScoreBreakdown, ScanReport, ScannerOptions, HostileUpdateVerdict, TyposquatEnforcementMode, EvidenceType, } from './types.js';
|
|
7
|
-
export { DEFAULT_ALLOWED_DOMAINS, SENSITIVE_PATH_PATTERNS, JAILBREAK_PATTERNS, SUSPICIOUS_PATTERNS, SOCIAL_ENGINEERING_PATTERNS, PROMPT_LEAKING_PATTERNS, DATA_EXFILTRATION_PATTERNS, PRIVILEGE_ESCALATION_PATTERNS, AI_DEFENCE_PATTERNS, SSRF_INSTRUCTION_PATTERNS, PII_PATTERNS, CODE_EXECUTION_PATTERNS, SCANNER_RULESET_VERSION, } from './patterns.js';
|
|
7
|
+
export { DEFAULT_ALLOWED_DOMAINS, SENSITIVE_PATH_PATTERNS, JAILBREAK_PATTERNS, SUSPICIOUS_PATTERNS, SOCIAL_ENGINEERING_PATTERNS, PROMPT_LEAKING_PATTERNS, DATA_EXFILTRATION_PATTERNS, PRIVILEGE_ESCALATION_PATTERNS, AI_DEFENCE_PATTERNS, SSRF_INSTRUCTION_PATTERNS, PII_PATTERNS, CODE_EXECUTION_PATTERNS, IMPERATIVE_FETCH_EXEC_PROSE, SCANNER_RULESET_VERSION, } from './patterns.js';
|
|
8
8
|
export { SEVERITY_WEIGHTS, CATEGORY_WEIGHTS } from './weights.js';
|
|
9
9
|
export { MAX_LINE_LENGTH_FOR_REGEX, MAX_CONTENT_LENGTH_FOR_REGEX, safeRegexTest, safeRegexCheck, } from './regex-utils.js';
|
|
10
10
|
export { stripInvisible } from './SecurityScanner.exec.js';
|
|
11
11
|
export { CONFUSABLES, isFullwidthLatin, isMathAlphanumeric, confusableSkeleton, } from './confusables.js';
|
|
12
|
-
export { BRAND_ALIASES, levenshteinDistance, scanTyposquat, detectTyposquat, applyTyposquatEnforcementMode, resolveTyposquatEnforcementMode, DEFAULT_TYPOSQUAT_ENFORCEMENT_MODE, } from './typosquat.js';
|
|
12
|
+
export { BRAND_ALIASES, AUTHORITY_CLAIMING_AFFIXES, levenshteinDistance, scanTyposquat, detectTyposquat, applyTyposquatEnforcementMode, resolveTyposquatEnforcementMode, DEFAULT_TYPOSQUAT_ENFORCEMENT_MODE, } from './typosquat.js';
|
|
13
13
|
export { buildTyposquatReferenceList, DEFAULT_TOP_INSTALLED_LIMIT, } from './typosquat-reference-list.js';
|
|
14
14
|
export type { ReferenceSkillEntry, InstalledSkillEntry, BuildTyposquatReferenceListOptions, } from './typosquat-reference-list.js';
|
|
15
15
|
export { describeSignals } from './SecurityScanner.formatters.js';
|