@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
|
@@ -64,6 +64,16 @@ export type { TyposquatEnforcementMode };
|
|
|
64
64
|
* cross-boundary-import constraint.
|
|
65
65
|
*/
|
|
66
66
|
export declare const BRAND_ALIASES: Readonly<Record<string, string>>;
|
|
67
|
+
/**
|
|
68
|
+
* A small, curated "claims official status" affix list. Deliberately narrow —
|
|
69
|
+
* benign functional affixes (`mcp`, `tools`, `community`, `helper`, ...)
|
|
70
|
+
* simply aren't in this list, so they never trigger this check.
|
|
71
|
+
*
|
|
72
|
+
* SMI-6033 Wave 1 (Gap 6): exported (was module-private) so the decoy/
|
|
73
|
+
* misdirection detector (`SecurityScanner.decoy.ts`, Wave 4) can reuse the
|
|
74
|
+
* same curated affix corpus rather than duplicating it.
|
|
75
|
+
*/
|
|
76
|
+
export declare const AUTHORITY_CLAIMING_AFFIXES: ReadonlySet<string>;
|
|
67
77
|
/**
|
|
68
78
|
* Standard iterative-DP Levenshtein edit distance (insertion/deletion/
|
|
69
79
|
* substitution). Inputs here are short skill-name strings, so an O(n*m) table
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typosquat.d.ts","sourceRoot":"","sources":["../../../../src/security/scanner/typosquat.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAA;AAG3E,YAAY,EAAE,wBAAwB,EAAE,CAAA;AAMxC;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAO1D,CAAA;
|
|
1
|
+
{"version":3,"file":"typosquat.d.ts","sourceRoot":"","sources":["../../../../src/security/scanner/typosquat.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAA;AAG3E,YAAY,EAAE,wBAAwB,EAAE,CAAA;AAMxC;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAO1D,CAAA;AAMD;;;;;;;;GAQG;AACH,eAAO,MAAM,0BAA0B,EAAE,WAAW,CAAC,MAAM,CAKzD,CAAA;AAmCF;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAyBhE;AAMD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,aAAa,EAAE,MAAM,EACrB,cAAc,EAAE,WAAW,CAAC,MAAM,CAAC,GAClC,eAAe,EAAE,CAgEnB;AAMD;gFACgF;AAChF,eAAO,MAAM,kCAAkC,EAAE,wBAAiC,CAAA;AAElF,+EAA+E;AAC/E,wBAAgB,+BAA+B,CAC7C,IAAI,CAAC,EAAE,wBAAwB,GAC9B,wBAAwB,CAE1B;AAID;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,eAAe,EAAE,EAC3B,IAAI,GAAE,wBAA6D,GAClE,eAAe,EAAE,CAQnB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,aAAa,EAAE,MAAM,EACrB,cAAc,EAAE,WAAW,CAAC,MAAM,CAAC,EACnC,IAAI,GAAE,wBAA6D,GAClE,eAAe,EAAE,CAEnB"}
|
|
@@ -80,8 +80,12 @@ export const BRAND_ALIASES = {
|
|
|
80
80
|
* A small, curated "claims official status" affix list. Deliberately narrow —
|
|
81
81
|
* benign functional affixes (`mcp`, `tools`, `community`, `helper`, ...)
|
|
82
82
|
* simply aren't in this list, so they never trigger this check.
|
|
83
|
+
*
|
|
84
|
+
* SMI-6033 Wave 1 (Gap 6): exported (was module-private) so the decoy/
|
|
85
|
+
* misdirection detector (`SecurityScanner.decoy.ts`, Wave 4) can reuse the
|
|
86
|
+
* same curated affix corpus rather than duplicating it.
|
|
83
87
|
*/
|
|
84
|
-
const AUTHORITY_CLAIMING_AFFIXES = new Set([
|
|
88
|
+
export const AUTHORITY_CLAIMING_AFFIXES = new Set([
|
|
85
89
|
'official',
|
|
86
90
|
'verified',
|
|
87
91
|
'authentic',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typosquat.js","sourceRoot":"","sources":["../../../../src/security/scanner/typosquat.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAGH,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAIrD,+EAA+E;AAC/E,qBAAqB;AACrB,+EAA+E;AAE/E;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,aAAa,GAAqC;IAC7D,SAAS,EAAE,YAAY;IACvB,MAAM,EAAE,YAAY;IACpB,MAAM,EAAE,eAAe;IACvB,OAAO,EAAE,WAAW;IACpB,MAAM,EAAE,aAAa;IACrB,UAAU,EAAE,yBAAyB;CACtC,CAAA;AAED,+EAA+E;AAC/E,qDAAqD;AACrD,+EAA+E;AAE/E
|
|
1
|
+
{"version":3,"file":"typosquat.js","sourceRoot":"","sources":["../../../../src/security/scanner/typosquat.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,13 +3,22 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Weight constants for risk score calculation.
|
|
5
5
|
*/
|
|
6
|
-
import type { SecuritySeverity } from './types.js';
|
|
6
|
+
import type { SecurityFindingType, SecuritySeverity } from './types.js';
|
|
7
7
|
/**
|
|
8
8
|
* Severity weights for risk score calculation
|
|
9
9
|
*/
|
|
10
10
|
export declare const SEVERITY_WEIGHTS: Record<SecuritySeverity, number>;
|
|
11
11
|
/**
|
|
12
|
-
* Category weights for risk score calculation
|
|
12
|
+
* Category weights for risk score calculation.
|
|
13
|
+
*
|
|
14
|
+
* SMI-6033 Wave 2 gap-closing pass: typed against `SecurityFindingType`
|
|
15
|
+
* (was `Record<string, number>`) so a future new finding type that ships
|
|
16
|
+
* without a weight entry here is a TypeScript compile error, not a silent
|
|
17
|
+
* runtime `?? 1.0` fallback in SecurityScanner.helpers.ts's
|
|
18
|
+
* `calculateRiskScore` — the exact class of gap
|
|
19
|
+
* scripts/tests/indexer/parity.test.ts's new consolidated core<->edge
|
|
20
|
+
* coverage test (SMI-6033 Wave 2) was added to catch on the edge side; this
|
|
21
|
+
* closes the equivalent hole on the core side, one layer earlier.
|
|
13
22
|
*/
|
|
14
|
-
export declare const CATEGORY_WEIGHTS: Record<
|
|
23
|
+
export declare const CATEGORY_WEIGHTS: Record<SecurityFindingType, number>;
|
|
15
24
|
//# sourceMappingURL=weights.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"weights.d.ts","sourceRoot":"","sources":["../../../../src/security/scanner/weights.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"weights.d.ts","sourceRoot":"","sources":["../../../../src/security/scanner/weights.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAEvE;;GAEG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,gBAAgB,EAAE,MAAM,CAK7D,CAAA;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,mBAAmB,EAAE,MAAM,CAoEhE,CAAA"}
|
|
@@ -13,7 +13,16 @@ export const SEVERITY_WEIGHTS = {
|
|
|
13
13
|
critical: 50,
|
|
14
14
|
};
|
|
15
15
|
/**
|
|
16
|
-
* Category weights for risk score calculation
|
|
16
|
+
* Category weights for risk score calculation.
|
|
17
|
+
*
|
|
18
|
+
* SMI-6033 Wave 2 gap-closing pass: typed against `SecurityFindingType`
|
|
19
|
+
* (was `Record<string, number>`) so a future new finding type that ships
|
|
20
|
+
* without a weight entry here is a TypeScript compile error, not a silent
|
|
21
|
+
* runtime `?? 1.0` fallback in SecurityScanner.helpers.ts's
|
|
22
|
+
* `calculateRiskScore` — the exact class of gap
|
|
23
|
+
* scripts/tests/indexer/parity.test.ts's new consolidated core<->edge
|
|
24
|
+
* coverage test (SMI-6033 Wave 2) was added to catch on the edge side; this
|
|
25
|
+
* closes the equivalent hole on the core side, one layer earlier.
|
|
17
26
|
*/
|
|
18
27
|
export const CATEGORY_WEIGHTS = {
|
|
19
28
|
jailbreak: 2.0,
|
|
@@ -43,5 +52,45 @@ export const CATEGORY_WEIGHTS = {
|
|
|
43
52
|
// comfortably under the riskThreshold: 40 quarantine cutoff on its own. See the
|
|
44
53
|
// stacked-risk test in SecurityScanner.scoring.test.ts.
|
|
45
54
|
typosquat: 1.2,
|
|
55
|
+
// SMI-6033 Wave 2 (Gap 5/Gap 3): top-tier weight matching code_execution/
|
|
56
|
+
// obfuscated_directive — the SAME single weight is used for both the
|
|
57
|
+
// critical (standalone-quarantining) and medium (advisory, co-signal-
|
|
58
|
+
// eligible) forms of each type; severity alone (SEVERITY_WEIGHTS 50 vs 15)
|
|
59
|
+
// does the two-tier split, exactly as scanChmodFetchCompound's own
|
|
60
|
+
// privilege_escalation compound signal already does with ONE weight
|
|
61
|
+
// (1.9/0.11) across its high/low severities. Paired with the 0.40
|
|
62
|
+
// coefficient in calculateRiskScore: a single CRITICAL finding reaches
|
|
63
|
+
// exactly the 40 quarantine threshold on its own (50 * 2.0 * 1.0 = 100 ->
|
|
64
|
+
// capped 100 -> * 0.40 = 40); a single MEDIUM finding contributes 12
|
|
65
|
+
// (15 * 2.0 * 1.0 = 30 -> * 0.40 = 12) — well under threshold alone.
|
|
66
|
+
gatekeeper_bypass: 2.0,
|
|
67
|
+
archive_evasion: 2.0,
|
|
68
|
+
// SMI-6033 Wave 2 (Gap 4): same top-tier weight as gatekeeper_bypass/
|
|
69
|
+
// archive_evasion, for the same reason — a paste-host URL that is the
|
|
70
|
+
// TARGET of a fetch command reaches exactly the 40 quarantine threshold
|
|
71
|
+
// standalone (50 * 2.0 * 1.0 = 100 -> capped 100 -> * 0.40 = 40), while a
|
|
72
|
+
// paste-host URL that is merely linked never reaches this detector at all
|
|
73
|
+
// (see SecurityScanner.paste-host.ts) and stays covered by the existing
|
|
74
|
+
// url:medium finding instead.
|
|
75
|
+
paste_host_fetch: 2.0,
|
|
76
|
+
// SMI-6033 Wave 2 (Gap 2): the sensitive_path/typosquat tier (1.2/0.04 —
|
|
77
|
+
// NOT the 2.0/0.40 tier every other Wave 2 detector above uses). This
|
|
78
|
+
// detector's wrapper finding is deliberately advisory-only: the escalation
|
|
79
|
+
// Gap 2 achieves comes for free from the decoded content's OWN findings
|
|
80
|
+
// (e.g. a decoded `curl|bash` natively trips code_execution at ITS OWN
|
|
81
|
+
// top-tier weight), not from encoded_payload itself. A single medium,
|
|
82
|
+
// high-confidence finding scores 15 * 1.2 * 1.0 = 18 -> * 0.04 = 0.72
|
|
83
|
+
// (rounds to ~1); a saturated breakdown (capped at 100) contributes 4 —
|
|
84
|
+
// comfortably under the riskThreshold: 40 quarantine cutoff on its own.
|
|
85
|
+
encoded_payload: 1.2,
|
|
86
|
+
// SMI-6033 Wave 4 (Gap 6): same advisory tier as typosquat/encoded_payload/
|
|
87
|
+
// sensitive_path (1.2/0.04) — NOT the 2.0/0.40 top tier gatekeeper_bypass/
|
|
88
|
+
// archive_evasion/paste_host_fetch use, since this finding type must never
|
|
89
|
+
// be standalone-critical (plan §9: "N/A — never standalone... Approximate
|
|
90
|
+
// NL heuristic by construction; co-signal required"). A single medium,
|
|
91
|
+
// high-confidence finding scores 15 * 1.2 * 1.0 = 18 -> * 0.04 = 0.72
|
|
92
|
+
// (rounds to ~1); a saturated breakdown (capped at 100) contributes 4 —
|
|
93
|
+
// comfortably under the riskThreshold: 40 quarantine cutoff on its own.
|
|
94
|
+
decoy_misdirection: 1.2,
|
|
46
95
|
};
|
|
47
96
|
//# sourceMappingURL=weights.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"weights.js","sourceRoot":"","sources":["../../../../src/security/scanner/weights.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH;;GAEG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAqC;IAChE,GAAG,EAAE,CAAC;IACN,MAAM,EAAE,EAAE;IACV,IAAI,EAAE,EAAE;IACR,QAAQ,EAAE,EAAE;CACb,CAAA;AAED
|
|
1
|
+
{"version":3,"file":"weights.js","sourceRoot":"","sources":["../../../../src/security/scanner/weights.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH;;GAEG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAqC;IAChE,GAAG,EAAE,CAAC;IACN,MAAM,EAAE,EAAE;IACV,IAAI,EAAE,EAAE;IACR,QAAQ,EAAE,EAAE;CACb,CAAA;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAwC;IACnE,SAAS,EAAE,GAAG;IACd,kBAAkB,EAAE,GAAG;IACvB,cAAc,EAAE,GAAG;IACnB,iBAAiB,EAAE,GAAG;IACtB,oBAAoB,EAAE,GAAG;IACzB,kBAAkB,EAAE,GAAG;IACvB,cAAc,EAAE,GAAG;IACnB,GAAG,EAAE,GAAG;IACR,UAAU,EAAE,GAAG,EAAE,iDAAiD;IAClE,IAAI,EAAE,GAAG,EAAE,uCAAuC;IAClD,GAAG,EAAE,GAAG,EAAE,8CAA8C;IACxD,8EAA8E;IAC9E,iFAAiF;IACjF,kFAAkF;IAClF,kEAAkE;IAClE,cAAc,EAAE,GAAG;IACnB,oBAAoB,EAAE,GAAG;IACzB,+EAA+E;IAC/E,8EAA8E;IAC9E,8EAA8E;IAC9E,+DAA+D;IAC/D,+EAA+E;IAC/E,gFAAgF;IAChF,0EAA0E;IAC1E,gFAAgF;IAChF,wDAAwD;IACxD,SAAS,EAAE,GAAG;IACd,0EAA0E;IAC1E,qEAAqE;IACrE,sEAAsE;IACtE,2EAA2E;IAC3E,mEAAmE;IACnE,oEAAoE;IACpE,kEAAkE;IAClE,uEAAuE;IACvE,0EAA0E;IAC1E,qEAAqE;IACrE,qEAAqE;IACrE,iBAAiB,EAAE,GAAG;IACtB,eAAe,EAAE,GAAG;IACpB,sEAAsE;IACtE,sEAAsE;IACtE,wEAAwE;IACxE,0EAA0E;IAC1E,0EAA0E;IAC1E,wEAAwE;IACxE,8BAA8B;IAC9B,gBAAgB,EAAE,GAAG;IACrB,yEAAyE;IACzE,sEAAsE;IACtE,2EAA2E;IAC3E,wEAAwE;IACxE,uEAAuE;IACvE,sEAAsE;IACtE,sEAAsE;IACtE,wEAAwE;IACxE,wEAAwE;IACxE,eAAe,EAAE,GAAG;IACpB,4EAA4E;IAC5E,2EAA2E;IAC3E,2EAA2E;IAC3E,0EAA0E;IAC1E,uEAAuE;IACvE,sEAAsE;IACtE,wEAAwE;IACxE,wEAAwE;IACxE,kBAAkB,EAAE,GAAG;CACxB,CAAA"}
|
|
@@ -155,7 +155,7 @@ export const PAYWALL_TRIGGERS = [
|
|
|
155
155
|
{
|
|
156
156
|
id: 'T2',
|
|
157
157
|
title: 'T2 - quota forecast (to Individual)',
|
|
158
|
-
body: 'When usage is on track to exhaust the free
|
|
158
|
+
body: 'When usage is on track to exhaust the free 100-call monthly quota, you may note the forecast ("at this pace you reach the cap in about K days") and mention Individual\'s 1,000 calls. Use this sparingly; a quota nag reads as a tax.',
|
|
159
159
|
},
|
|
160
160
|
{
|
|
161
161
|
id: 'T4',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompt-source.js","sourceRoot":"","sources":["../../../../src/services/agent-pack/prompt-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,mFAAmF;AACnF,MAAM,CAAC,MAAM,gBAAgB,GAAsB;IACjD,gMAAgM;IAChM,kYAAkY;IAClY,qUAAqU;CACtU,CAAA;AAED,0DAA0D;AAC1D,MAAM,CAAC,MAAM,oBAAoB,GAAsB;IACrD,kYAAkY;IAClY,wMAAwM;IACxM,2JAA2J;CAC5J,CAAA;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,aAAa,GAA2B;IACnD;QACE,EAAE,EAAE,qBAAqB;QACzB,KAAK,EAAE,yCAAyC;QAChD,IAAI,EAAE,sUAAsU;KAC7U;IACD;QACE,EAAE,EAAE,wBAAwB;QAC5B,KAAK,EAAE,wDAAwD;QAC/D,IAAI,EAAE,qRAAqR;KAC5R;IACD;QACE,EAAE,EAAE,YAAY;QAChB,KAAK,EAAE,qCAAqC;QAC5C,IAAI,EAAE,wPAAwP;KAC/P;IACD;QACE,EAAE,EAAE,gBAAgB;QACpB,KAAK,EAAE,8BAA8B;QACrC,IAAI,EAAE,sTAAsT;KAC7T;IACD;QACE,EAAE,EAAE,aAAa;QACjB,KAAK,EAAE,iCAAiC;QACxC,IAAI,EAAE,4PAA4P;KACnQ;IACD;QACE,EAAE,EAAE,kBAAkB;QACtB,KAAK,EAAE,2CAA2C;QAClD,IAAI,EAAE,ieAAie;KACxe;CACF,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,IAAI,GAA6B;IAC5C;QACE,EAAE,EAAE,cAAc;QAClB,KAAK,EAAE,wBAAwB;QAC/B,IAAI,EAAE;YACJ,uFAAuF;YACvF,oIAAoI;YACpI,sNAAsN;YACtN,6GAA6G;YAC7G,qJAAqJ;SACtJ,CAAC,IAAI,CAAC,IAAI,CAAC;QACZ,KAAK,EAAE,CAAC,gBAAgB,EAAE,eAAe,EAAE,YAAY,EAAE,kBAAkB,CAAC;KAC7E;IACD;QACE,EAAE,EAAE,WAAW;QACf,KAAK,EAAE,iCAAiC;QACxC,IAAI,EAAE;YACJ,2GAA2G;YAC3G,kKAAkK;YAClK,uNAAuN;YACvN,qGAAqG;YACrG,mIAAmI;SACpI,CAAC,IAAI,CAAC,IAAI,CAAC;QACZ,KAAK,EAAE;YACL,uBAAuB;YACvB,wBAAwB;YACxB,wBAAwB;YACxB,YAAY;SACb;KACF;IACD;QACE,EAAE,EAAE,oBAAoB;QACxB,KAAK,EAAE,iCAAiC;QACxC,IAAI,EAAE;YACJ,oDAAoD;YACpD,2GAA2G;YAC3G,8HAA8H;YAC9H,yJAAyJ;YACzJ,oIAAoI;SACrI,CAAC,IAAI,CAAC,IAAI,CAAC;QACZ,KAAK,EAAE;YACL,QAAQ;YACR,WAAW;YACX,gBAAgB;YAChB,eAAe;YACf,aAAa;YACb,eAAe;SAChB;KACF;IACD;QACE,EAAE,EAAE,gBAAgB;QACpB,KAAK,EAAE,qCAAqC;QAC5C,IAAI,EAAE,qTAAqT;QAC3T,KAAK,EAAE,CAAC,iBAAiB,EAAE,QAAQ,CAAC;KACrC;IACD;QACE,EAAE,EAAE,gBAAgB;QACpB,KAAK,EAAE,+BAA+B;QACtC,IAAI,EAAE,+SAA+S;QACrT,KAAK,EAAE,EAAE;KACV;IACD;QACE,EAAE,EAAE,cAAc;QAClB,KAAK,EAAE,qCAAqC;QAC5C,IAAI,EAAE,6YAA6Y;QACnZ,KAAK,EAAE,EAAE;KACV;CACF,CAAA;AAED,mEAAmE;AACnE,MAAM,CAAC,MAAM,kBAAkB,GAAsB;IACnD,qOAAqO;IACrO,iQAAiQ;IACjQ,6QAA6Q;CAC9Q,CAAA;AAED,0FAA0F;AAC1F,MAAM,CAAC,MAAM,gBAAgB,GAA8B;IACzD;QACE,EAAE,EAAE,IAAI;QACR,KAAK,EAAE,uCAAuC;QAC9C,IAAI,EAAE,sTAAsT;KAC7T;IACD;QACE,EAAE,EAAE,IAAI;QACR,KAAK,EAAE,qCAAqC;QAC5C,IAAI,EAAE,
|
|
1
|
+
{"version":3,"file":"prompt-source.js","sourceRoot":"","sources":["../../../../src/services/agent-pack/prompt-source.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Walks an installed skill's bundle directory and scans its sibling bundled
|
|
6
6
|
* files (`.mcp.json`, `.claude/settings*.json`, `package.json` lifecycle hooks,
|
|
7
|
-
* `config.json`,
|
|
7
|
+
* `config.json`, and executable-code files under `scripts/`/`src/`/`bin/`
|
|
8
|
+
* plus the skill's top level) so `skill_rescan` can
|
|
8
9
|
* quarantine a skill whose MALICIOUS sibling — not its `SKILL.md` — carries the
|
|
9
10
|
* threat (CVE-2025-59536 hook execution, `curl|bash` postinstall, a
|
|
10
11
|
* remote-fetch-execute install script).
|
|
@@ -35,13 +36,35 @@
|
|
|
35
36
|
* INHERITED DETECTION GAPS (FN, tracked SMI-5424): bare-interpreter hook
|
|
36
37
|
* payloads (`node evil.js`, `python evil.py`, `bun`/`deno`), `&&`/`;`-chained
|
|
37
38
|
* fetch-then-exec, `npx`, and JSON `\uXXXX`-escaped commands inside raw-scanned
|
|
38
|
-
* structured files are NOT detected.
|
|
39
|
-
*
|
|
39
|
+
* structured files are NOT detected.
|
|
40
|
+
*
|
|
41
|
+
* SMI-6033 Wave 2 (Gap 8) CLOSED PART OF THE PHASE-3 TODO: the glob is no
|
|
42
|
+
* longer `.sh`-only and no longer `scripts/`-only. It now covers every
|
|
43
|
+
* {@link EXECUTABLE_CODE_EXTENSIONS} file at the skill's top level and as a
|
|
44
|
+
* DIRECT child of `scripts/`, `src/` or `bin/` — the same SCOPE, RANKING and
|
|
45
|
+
* COUNT cap the indexer applies registry-side (`scripts/indexer/
|
|
46
|
+
* skill-processor.security.tree.ts`'s `MAX_EXTENDED_SIBLING_FILES = 20`), so
|
|
47
|
+
* a skill scanned locally and the same skill scanned in the registry select
|
|
48
|
+
* the same candidate FILES. The per-file BYTE cap is deliberately NOT
|
|
49
|
+
* unified: {@link MAX_SIBLING_FILE_BYTES} (512 KB) here vs the indexer's
|
|
50
|
+
* `MAX_SIBLING_CONTENT_BYTES` (256 KB, `skill-processor.security.ts`) — a
|
|
51
|
+
* pre-existing divergence this wave did not introduce and did not reconcile
|
|
52
|
+
* (adversarial review, 2026-08-16). A file between 256 KB and 512 KB is
|
|
53
|
+
* therefore scanned locally but `size_cap`-dropped registry-side; harmless
|
|
54
|
+
* (fail-open, surfaced via `scan_coverage_incomplete`/`skippedOversize`, never
|
|
55
|
+
* silent) but worth knowing before assuming the two verdicts always agree.
|
|
56
|
+
* Still deliberately shallow: a fully recursive bundle walk remains out of
|
|
57
|
+
* scope.
|
|
58
|
+
*
|
|
40
59
|
* COUNT-CAP DECOY-PADDING (FN): the fixed bundled files are cap-exempt, but the
|
|
41
|
-
*
|
|
42
|
-
* and pad
|
|
43
|
-
* and is never scanned.
|
|
44
|
-
*
|
|
60
|
+
* executable-code glob is capped — an author can name the malicious script to
|
|
61
|
+
* rank last and pad the scanned directories with cap-many benign decoys so it
|
|
62
|
+
* lands in `droppedForCount` and is never scanned. Wave 2's four-tier ranking
|
|
63
|
+
* (SKILL.md-referenced, then entry-point-named, then shallow, then
|
|
64
|
+
* lexicographic) raises the bar — to land past the cap the payload must also
|
|
65
|
+
* be unreferenced from SKILL.md and not entry-point-named, which cuts against
|
|
66
|
+
* it actually being executed — but does not close the gap. Surfaced (never a
|
|
67
|
+
* silent drop), not blocked; a cumulative-resource bound is a follow-up.
|
|
45
68
|
*
|
|
46
69
|
* config.json IS scanned here (only doc-class siblings are skipped), whereas the
|
|
47
70
|
* Phase-1 validate helper (validate-bundled-scan.ts) also skips `config` — rescan
|
|
@@ -49,16 +72,47 @@
|
|
|
49
72
|
*/
|
|
50
73
|
import type { SecurityFinding } from '../security/scanner/types.js';
|
|
51
74
|
import type { SecurityScanner } from '../security/index.js';
|
|
52
|
-
/**
|
|
53
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Operational-code extensions scanned by the glob (SMI-6033 Wave 2, Gap 8).
|
|
77
|
+
*
|
|
78
|
+
* Deliberately duplicated from `scripts/indexer/skill-processor.security.tree.ts`
|
|
79
|
+
* rather than shared: that module lives in the Node/Deno indexer trees, which
|
|
80
|
+
* cannot import `@skillsmith/core` (git-crypt boundary + Deno bundling — see
|
|
81
|
+
* that file's twin header). `parity.test.ts` pins the two lists equal, so the
|
|
82
|
+
* duplication cannot drift silently.
|
|
83
|
+
*/
|
|
84
|
+
export declare const EXECUTABLE_CODE_EXTENSIONS: readonly [".sh", ".py", ".js", ".mjs", ".cjs", ".ts", ".rb", ".php", ".ps1", ".pl"];
|
|
85
|
+
/** Directories globbed for operational code, relative to the skill directory. */
|
|
86
|
+
export declare const EXTENDED_SCAN_DIRS: readonly ["scripts", "src", "bin"];
|
|
87
|
+
/** Basenames (extension-stripped, lowercased) that rank as entry points (tier 2). */
|
|
88
|
+
export declare const ENTRY_POINT_BASENAMES: Set<string>;
|
|
89
|
+
/**
|
|
90
|
+
* Max globbed operational-code files scanned per skill (fixed bundled files
|
|
91
|
+
* are exempt).
|
|
92
|
+
*
|
|
93
|
+
* SMI-6033 Wave 2 (Gap 8): narrowed from the previous `.sh`-only cap of 50 to
|
|
94
|
+
* 20, matching `MAX_EXTENDED_SIBLING_FILES` on the indexer side. Intentional
|
|
95
|
+
* behaviour change: the glob now covers ten extensions across four directories
|
|
96
|
+
* instead of one extension across two, so an uncapped-at-50 policy would fetch
|
|
97
|
+
* and scan far more per skill than before. 20 ranked files is the plan's
|
|
98
|
+
* chosen budget on BOTH surfaces, and keeping the two equal is what makes the
|
|
99
|
+
* local and registry verdicts comparable.
|
|
100
|
+
*/
|
|
101
|
+
export declare const MAX_EXTENDED_SIBLING_FILES = 20;
|
|
54
102
|
/** Per-file byte ceiling; larger siblings are skipped (recorded, never silent). */
|
|
55
103
|
export declare const MAX_SIBLING_FILE_BYTES: number;
|
|
56
104
|
/** Tunable caps for {@link scanLocalBundleSiblings}. */
|
|
57
105
|
export interface BundledSiblingScanOptions {
|
|
58
|
-
/** Override {@link
|
|
59
|
-
|
|
106
|
+
/** Override {@link MAX_EXTENDED_SIBLING_FILES}. */
|
|
107
|
+
maxExtendedFiles?: number;
|
|
60
108
|
/** Override {@link MAX_SIBLING_FILE_BYTES}. */
|
|
61
109
|
maxBytesPerFile?: number;
|
|
110
|
+
/**
|
|
111
|
+
* The skill's own `SKILL.md` text, used only for the tier-1 ranking check
|
|
112
|
+
* ("is this file referenced by path from SKILL.md?"). Omitted, tier 1 simply
|
|
113
|
+
* never matches and ranking falls through to tiers 2-4 — never an error.
|
|
114
|
+
*/
|
|
115
|
+
primaryContent?: string;
|
|
62
116
|
}
|
|
63
117
|
/**
|
|
64
118
|
* Result of scanning a skill bundle's sibling files. All path fields are
|
|
@@ -83,19 +137,39 @@ export interface BundledSiblingScanResult {
|
|
|
83
137
|
* mis-attribute a quarantine's surfaced score.
|
|
84
138
|
*/
|
|
85
139
|
maxSiblingRiskScore: number;
|
|
86
|
-
/**
|
|
140
|
+
/** Executable-code files beyond the count cap — surfaced, never silently dropped. */
|
|
87
141
|
droppedForCount: string[];
|
|
88
142
|
/** Files skipped for exceeding the byte cap. */
|
|
89
143
|
skippedOversize: string[];
|
|
90
144
|
/** Symlink siblings resolving outside the skill dir (SMI-4287 guard). */
|
|
91
145
|
skippedSymlinkEscape: string[];
|
|
92
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* SMI-6033 Wave 2 (Gap 8): rank operational-code candidates so the count cap
|
|
149
|
+
* keeps the files most likely to actually be executed.
|
|
150
|
+
*
|
|
151
|
+
* Four tiers, in order: (1) referenced literally by path from `SKILL.md`,
|
|
152
|
+
* (2) entry-point basename, (3) shallower path first, (4) lexicographic.
|
|
153
|
+
*
|
|
154
|
+
* DETERMINISM is a required property, not incidental: the same candidate set
|
|
155
|
+
* must always produce the same order. Tier 4 is a TOTAL order over unique
|
|
156
|
+
* relative paths, so the result never depends on `Array.prototype.sort` being
|
|
157
|
+
* stable; and the compare uses raw `<`/`>` rather than `localeCompare`, whose
|
|
158
|
+
* collation is ICU/locale dependent. This mirrors the identical comparator in
|
|
159
|
+
* the indexer's `enumerateExtendedSiblingTargets`, so the two surfaces select
|
|
160
|
+
* the same files in the same order from the same bundle.
|
|
161
|
+
*
|
|
162
|
+
* NOTE: ranking does NOT defeat decoy-padding — see the module header. The
|
|
163
|
+
* dropped names are always surfaced in `droppedForCount` (no silent drop).
|
|
164
|
+
*/
|
|
165
|
+
export declare function rankExecutableCodeFiles(relPaths: readonly string[], primaryContent: string): string[];
|
|
93
166
|
/**
|
|
94
167
|
* Scan a skill bundle's sibling files and return the structured result.
|
|
95
168
|
*
|
|
96
169
|
* Fixed {@link BUNDLED_SCAN_FILES} are always scanned (exempt from the count
|
|
97
170
|
* cap, so a decoy-padding attack on `scripts/` cannot push the primary hook /
|
|
98
|
-
* postinstall surface out of the scan window). The
|
|
171
|
+
* postinstall surface out of the scan window). The executable-code glob is
|
|
172
|
+
* ranked then capped, and
|
|
99
173
|
* any overflow is reported in `droppedForCount`.
|
|
100
174
|
*
|
|
101
175
|
* Doc-class siblings (`README.md`, `examples.md`) are intentionally NOT scanned:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bundled-sibling-scan.d.ts","sourceRoot":"","sources":["../../../src/services/bundled-sibling-scan.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"bundled-sibling-scan.d.ts","sourceRoot":"","sources":["../../../src/services/bundled-sibling-scan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuEG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAc,MAAM,8BAA8B,CAAA;AAI/E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAQ3D;;;;;;;;GAQG;AACH,eAAO,MAAM,0BAA0B,qFAW7B,CAAA;AAEV,iFAAiF;AACjF,eAAO,MAAM,kBAAkB,oCAAqC,CAAA;AAEpE,qFAAqF;AACrF,eAAO,MAAM,qBAAqB,aAOhC,CAAA;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,0BAA0B,KAAK,CAAA;AAC5C,mFAAmF;AACnF,eAAO,MAAM,sBAAsB,QAAa,CAAA;AAEhD,wDAAwD;AACxD,MAAM,WAAW,yBAAyB;IACxC,mDAAmD;IACnD,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,+CAA+C;IAC/C,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC,2EAA2E;IAC3E,QAAQ,EAAE,eAAe,EAAE,CAAA;IAC3B,+EAA+E;IAC/E,UAAU,EAAE,OAAO,CAAA;IACnB,2DAA2D;IAC3D,kBAAkB,EAAE,eAAe,EAAE,CAAA;IACrC,2CAA2C;IAC3C,eAAe,EAAE,MAAM,EAAE,CAAA;IACzB,uCAAuC;IACvC,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB;;;;;OAKG;IACH,mBAAmB,EAAE,MAAM,CAAA;IAC3B,qFAAqF;IACrF,eAAe,EAAE,MAAM,EAAE,CAAA;IACzB,gDAAgD;IAChD,eAAe,EAAE,MAAM,EAAE,CAAA;IACzB,yEAAyE;IACzE,oBAAoB,EAAE,MAAM,EAAE,CAAA;CAC/B;AAqCD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,cAAc,EAAE,MAAM,GACrB,MAAM,EAAE,CAcV;AA8BD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,eAAe,EACxB,IAAI,GAAE,yBAA8B,GACnC,OAAO,CAAC,wBAAwB,CAAC,CAqFnC"}
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Walks an installed skill's bundle directory and scans its sibling bundled
|
|
6
6
|
* files (`.mcp.json`, `.claude/settings*.json`, `package.json` lifecycle hooks,
|
|
7
|
-
* `config.json`,
|
|
7
|
+
* `config.json`, and executable-code files under `scripts/`/`src/`/`bin/`
|
|
8
|
+
* plus the skill's top level) so `skill_rescan` can
|
|
8
9
|
* quarantine a skill whose MALICIOUS sibling — not its `SKILL.md` — carries the
|
|
9
10
|
* threat (CVE-2025-59536 hook execution, `curl|bash` postinstall, a
|
|
10
11
|
* remote-fetch-execute install script).
|
|
@@ -35,13 +36,35 @@
|
|
|
35
36
|
* INHERITED DETECTION GAPS (FN, tracked SMI-5424): bare-interpreter hook
|
|
36
37
|
* payloads (`node evil.js`, `python evil.py`, `bun`/`deno`), `&&`/`;`-chained
|
|
37
38
|
* fetch-then-exec, `npx`, and JSON `\uXXXX`-escaped commands inside raw-scanned
|
|
38
|
-
* structured files are NOT detected.
|
|
39
|
-
*
|
|
39
|
+
* structured files are NOT detected.
|
|
40
|
+
*
|
|
41
|
+
* SMI-6033 Wave 2 (Gap 8) CLOSED PART OF THE PHASE-3 TODO: the glob is no
|
|
42
|
+
* longer `.sh`-only and no longer `scripts/`-only. It now covers every
|
|
43
|
+
* {@link EXECUTABLE_CODE_EXTENSIONS} file at the skill's top level and as a
|
|
44
|
+
* DIRECT child of `scripts/`, `src/` or `bin/` — the same SCOPE, RANKING and
|
|
45
|
+
* COUNT cap the indexer applies registry-side (`scripts/indexer/
|
|
46
|
+
* skill-processor.security.tree.ts`'s `MAX_EXTENDED_SIBLING_FILES = 20`), so
|
|
47
|
+
* a skill scanned locally and the same skill scanned in the registry select
|
|
48
|
+
* the same candidate FILES. The per-file BYTE cap is deliberately NOT
|
|
49
|
+
* unified: {@link MAX_SIBLING_FILE_BYTES} (512 KB) here vs the indexer's
|
|
50
|
+
* `MAX_SIBLING_CONTENT_BYTES` (256 KB, `skill-processor.security.ts`) — a
|
|
51
|
+
* pre-existing divergence this wave did not introduce and did not reconcile
|
|
52
|
+
* (adversarial review, 2026-08-16). A file between 256 KB and 512 KB is
|
|
53
|
+
* therefore scanned locally but `size_cap`-dropped registry-side; harmless
|
|
54
|
+
* (fail-open, surfaced via `scan_coverage_incomplete`/`skippedOversize`, never
|
|
55
|
+
* silent) but worth knowing before assuming the two verdicts always agree.
|
|
56
|
+
* Still deliberately shallow: a fully recursive bundle walk remains out of
|
|
57
|
+
* scope.
|
|
58
|
+
*
|
|
40
59
|
* COUNT-CAP DECOY-PADDING (FN): the fixed bundled files are cap-exempt, but the
|
|
41
|
-
*
|
|
42
|
-
* and pad
|
|
43
|
-
* and is never scanned.
|
|
44
|
-
*
|
|
60
|
+
* executable-code glob is capped — an author can name the malicious script to
|
|
61
|
+
* rank last and pad the scanned directories with cap-many benign decoys so it
|
|
62
|
+
* lands in `droppedForCount` and is never scanned. Wave 2's four-tier ranking
|
|
63
|
+
* (SKILL.md-referenced, then entry-point-named, then shallow, then
|
|
64
|
+
* lexicographic) raises the bar — to land past the cap the payload must also
|
|
65
|
+
* be unreferenced from SKILL.md and not entry-point-named, which cuts against
|
|
66
|
+
* it actually being executed — but does not close the gap. Surfaced (never a
|
|
67
|
+
* silent drop), not blocked; a cumulative-resource bound is a follow-up.
|
|
45
68
|
*
|
|
46
69
|
* config.json IS scanned here (only doc-class siblings are skipped), whereas the
|
|
47
70
|
* Phase-1 validate helper (validate-bundled-scan.ts) also skips `config` — rescan
|
|
@@ -50,46 +73,158 @@
|
|
|
50
73
|
import { join } from 'path';
|
|
51
74
|
import { safeFs, resolveSafeRealpath } from '../sources/LocalFilesystemAdapter.helpers.js';
|
|
52
75
|
import { BUNDLED_SCAN_FILES, classifyBundledFile, extractPackageJsonLifecycleScripts, } from './skill-installation.policy.js';
|
|
53
|
-
/**
|
|
54
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Operational-code extensions scanned by the glob (SMI-6033 Wave 2, Gap 8).
|
|
78
|
+
*
|
|
79
|
+
* Deliberately duplicated from `scripts/indexer/skill-processor.security.tree.ts`
|
|
80
|
+
* rather than shared: that module lives in the Node/Deno indexer trees, which
|
|
81
|
+
* cannot import `@skillsmith/core` (git-crypt boundary + Deno bundling — see
|
|
82
|
+
* that file's twin header). `parity.test.ts` pins the two lists equal, so the
|
|
83
|
+
* duplication cannot drift silently.
|
|
84
|
+
*/
|
|
85
|
+
export const EXECUTABLE_CODE_EXTENSIONS = [
|
|
86
|
+
'.sh',
|
|
87
|
+
'.py',
|
|
88
|
+
'.js',
|
|
89
|
+
'.mjs',
|
|
90
|
+
'.cjs',
|
|
91
|
+
'.ts',
|
|
92
|
+
'.rb',
|
|
93
|
+
'.php',
|
|
94
|
+
'.ps1',
|
|
95
|
+
'.pl',
|
|
96
|
+
];
|
|
97
|
+
/** Directories globbed for operational code, relative to the skill directory. */
|
|
98
|
+
export const EXTENDED_SCAN_DIRS = ['scripts', 'src', 'bin'];
|
|
99
|
+
/** Basenames (extension-stripped, lowercased) that rank as entry points (tier 2). */
|
|
100
|
+
export const ENTRY_POINT_BASENAMES = new Set([
|
|
101
|
+
'install',
|
|
102
|
+
'setup',
|
|
103
|
+
'main',
|
|
104
|
+
'index',
|
|
105
|
+
'run',
|
|
106
|
+
'postinstall',
|
|
107
|
+
]);
|
|
108
|
+
/**
|
|
109
|
+
* Max globbed operational-code files scanned per skill (fixed bundled files
|
|
110
|
+
* are exempt).
|
|
111
|
+
*
|
|
112
|
+
* SMI-6033 Wave 2 (Gap 8): narrowed from the previous `.sh`-only cap of 50 to
|
|
113
|
+
* 20, matching `MAX_EXTENDED_SIBLING_FILES` on the indexer side. Intentional
|
|
114
|
+
* behaviour change: the glob now covers ten extensions across four directories
|
|
115
|
+
* instead of one extension across two, so an uncapped-at-50 policy would fetch
|
|
116
|
+
* and scan far more per skill than before. 20 ranked files is the plan's
|
|
117
|
+
* chosen budget on BOTH surfaces, and keeping the two equal is what makes the
|
|
118
|
+
* local and registry verdicts comparable.
|
|
119
|
+
*/
|
|
120
|
+
export const MAX_EXTENDED_SIBLING_FILES = 20;
|
|
55
121
|
/** Per-file byte ceiling; larger siblings are skipped (recorded, never silent). */
|
|
56
122
|
export const MAX_SIBLING_FILE_BYTES = 512 * 1024;
|
|
57
|
-
/**
|
|
58
|
-
|
|
59
|
-
|
|
123
|
+
/**
|
|
124
|
+
* A finding is a quarantine driver only if it is a direct execution threat.
|
|
125
|
+
*
|
|
126
|
+
* SMI-6033 Wave 2 (Gap 8) fix (adversarial review finding, 2026-08-16): for a
|
|
127
|
+
* file from the NEW extended (`scripts/`/`src/`/`bin/`/top-level executable
|
|
128
|
+
* code) surface, `code_execution` only drives rejection at `critical`
|
|
129
|
+
* severity — a bare `medium` code_execution finding (the severity
|
|
130
|
+
* `scanCodeExecution` assigns a lone `curl | bash` by design) must NOT
|
|
131
|
+
* standalone-quarantine an install script that uses the same idiom as
|
|
132
|
+
* rustup/Homebrew/nvm/bun. The original fixed `BUNDLED_SCAN_FILES` keep the
|
|
133
|
+
* unchanged, severity-agnostic rule — those are config/doc formats where a
|
|
134
|
+
* literal `curl | bash` string is inherently anomalous, not a real installer.
|
|
135
|
+
* `obfuscated_directive` remains rejectable at any severity on BOTH surfaces
|
|
136
|
+
* — it is delta-gated against a real decode step and has no legitimate
|
|
137
|
+
* installer-script shape.
|
|
138
|
+
*/
|
|
139
|
+
function isExecutionThreat(finding, isExtended) {
|
|
140
|
+
if (finding.type === 'obfuscated_directive')
|
|
141
|
+
return true;
|
|
142
|
+
if (finding.type !== 'code_execution')
|
|
143
|
+
return false;
|
|
144
|
+
return isExtended ? finding.severity === 'critical' : true;
|
|
145
|
+
}
|
|
146
|
+
/** True when `path`'s final extension is in {@link EXECUTABLE_CODE_EXTENSIONS}. */
|
|
147
|
+
function hasExecutableCodeExtension(path) {
|
|
148
|
+
const lower = path.toLowerCase();
|
|
149
|
+
return EXECUTABLE_CODE_EXTENSIONS.some((ext) => lower.endsWith(ext));
|
|
150
|
+
}
|
|
151
|
+
/** Lowercased basename with its final extension removed (`Setup.SH` -> `setup`). */
|
|
152
|
+
function entryPointKey(path) {
|
|
153
|
+
const base = (path.split('/').pop() ?? path).toLowerCase();
|
|
154
|
+
const dotIdx = base.lastIndexOf('.');
|
|
155
|
+
return dotIdx > 0 ? base.slice(0, dotIdx) : base;
|
|
60
156
|
}
|
|
61
157
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
158
|
+
* SMI-6033 Wave 2 (Gap 8): rank operational-code candidates so the count cap
|
|
159
|
+
* keeps the files most likely to actually be executed.
|
|
160
|
+
*
|
|
161
|
+
* Four tiers, in order: (1) referenced literally by path from `SKILL.md`,
|
|
162
|
+
* (2) entry-point basename, (3) shallower path first, (4) lexicographic.
|
|
163
|
+
*
|
|
164
|
+
* DETERMINISM is a required property, not incidental: the same candidate set
|
|
165
|
+
* must always produce the same order. Tier 4 is a TOTAL order over unique
|
|
166
|
+
* relative paths, so the result never depends on `Array.prototype.sort` being
|
|
167
|
+
* stable; and the compare uses raw `<`/`>` rather than `localeCompare`, whose
|
|
168
|
+
* collation is ICU/locale dependent. This mirrors the identical comparator in
|
|
169
|
+
* the indexer's `enumerateExtendedSiblingTargets`, so the two surfaces select
|
|
170
|
+
* the same files in the same order from the same bundle.
|
|
171
|
+
*
|
|
172
|
+
* NOTE: ranking does NOT defeat decoy-padding — see the module header. The
|
|
67
173
|
* dropped names are always surfaced in `droppedForCount` (no silent drop).
|
|
68
174
|
*/
|
|
69
|
-
|
|
175
|
+
export function rankExecutableCodeFiles(relPaths, primaryContent) {
|
|
176
|
+
return [...relPaths].sort((a, b) => {
|
|
177
|
+
const aRef = primaryContent.includes(a);
|
|
178
|
+
const bRef = primaryContent.includes(b);
|
|
179
|
+
if (aRef !== bRef)
|
|
180
|
+
return aRef ? -1 : 1;
|
|
181
|
+
const aEntry = ENTRY_POINT_BASENAMES.has(entryPointKey(a));
|
|
182
|
+
const bEntry = ENTRY_POINT_BASENAMES.has(entryPointKey(b));
|
|
183
|
+
if (aEntry !== bEntry)
|
|
184
|
+
return aEntry ? -1 : 1;
|
|
185
|
+
const aDepth = a.split('/').length;
|
|
186
|
+
const bDepth = b.split('/').length;
|
|
187
|
+
if (aDepth !== bDepth)
|
|
188
|
+
return aDepth - bDepth;
|
|
189
|
+
if (a === b)
|
|
190
|
+
return 0;
|
|
191
|
+
return a < b ? -1 : 1;
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Collect executable-code candidates: the skill's top level, then direct
|
|
196
|
+
* children of `scripts/`, `src/` and `bin/` — regular files only (symlinked
|
|
197
|
+
* scripts are not followed by the glob). Ranked by
|
|
198
|
+
* {@link rankExecutableCodeFiles} so the cap and `droppedForCount` are
|
|
199
|
+
* reproducible rather than dependent on readdir order.
|
|
200
|
+
*/
|
|
201
|
+
async function collectExecutableCodeFiles(skillDir, primaryContent) {
|
|
70
202
|
const out = [];
|
|
71
203
|
const top = await safeFs.readdir(skillDir);
|
|
72
204
|
if (top.ok) {
|
|
73
205
|
for (const e of top.value) {
|
|
74
|
-
if (e.isFile() && e.name
|
|
206
|
+
if (e.isFile() && hasExecutableCodeExtension(e.name))
|
|
75
207
|
out.push(e.name);
|
|
76
208
|
}
|
|
77
209
|
}
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
210
|
+
for (const sub of EXTENDED_SCAN_DIRS) {
|
|
211
|
+
const entries = await safeFs.readdir(join(skillDir, sub));
|
|
212
|
+
if (!entries.ok)
|
|
213
|
+
continue;
|
|
214
|
+
for (const e of entries.value) {
|
|
215
|
+
if (e.isFile() && hasExecutableCodeExtension(e.name))
|
|
216
|
+
out.push(`${sub}/${e.name}`);
|
|
83
217
|
}
|
|
84
218
|
}
|
|
85
|
-
return out
|
|
219
|
+
return rankExecutableCodeFiles(out, primaryContent);
|
|
86
220
|
}
|
|
87
221
|
/**
|
|
88
222
|
* Scan a skill bundle's sibling files and return the structured result.
|
|
89
223
|
*
|
|
90
224
|
* Fixed {@link BUNDLED_SCAN_FILES} are always scanned (exempt from the count
|
|
91
225
|
* cap, so a decoy-padding attack on `scripts/` cannot push the primary hook /
|
|
92
|
-
* postinstall surface out of the scan window). The
|
|
226
|
+
* postinstall surface out of the scan window). The executable-code glob is
|
|
227
|
+
* ranked then capped, and
|
|
93
228
|
* any overflow is reported in `droppedForCount`.
|
|
94
229
|
*
|
|
95
230
|
* Doc-class siblings (`README.md`, `examples.md`) are intentionally NOT scanned:
|
|
@@ -102,7 +237,7 @@ async function collectShFiles(skillDir) {
|
|
|
102
237
|
* @param opts optional caps
|
|
103
238
|
*/
|
|
104
239
|
export async function scanLocalBundleSiblings(skillDir, scanner, opts = {}) {
|
|
105
|
-
const
|
|
240
|
+
const maxExtendedFiles = opts.maxExtendedFiles ?? MAX_EXTENDED_SIBLING_FILES;
|
|
106
241
|
const maxBytes = opts.maxBytesPerFile ?? MAX_SIBLING_FILE_BYTES;
|
|
107
242
|
const result = {
|
|
108
243
|
findings: [],
|
|
@@ -115,10 +250,15 @@ export async function scanLocalBundleSiblings(skillDir, scanner, opts = {}) {
|
|
|
115
250
|
skippedOversize: [],
|
|
116
251
|
skippedSymlinkEscape: [],
|
|
117
252
|
};
|
|
118
|
-
const
|
|
119
|
-
result.droppedForCount =
|
|
120
|
-
|
|
121
|
-
|
|
253
|
+
const codeFiles = await collectExecutableCodeFiles(skillDir, opts.primaryContent ?? '');
|
|
254
|
+
result.droppedForCount = codeFiles.slice(maxExtendedFiles);
|
|
255
|
+
const extendedCandidates = codeFiles.slice(0, maxExtendedFiles);
|
|
256
|
+
// Fixed files first (cap-exempt), then the capped, ranked executable-code glob.
|
|
257
|
+
const candidates = [...BUNDLED_SCAN_FILES, ...extendedCandidates];
|
|
258
|
+
// SMI-6033 Wave 2 (Gap 8) fix: which candidates came from the new extended
|
|
259
|
+
// surface, so isExecutionThreat can apply its narrower rule to them without
|
|
260
|
+
// touching BUNDLED_SCAN_FILES's unchanged behavior at all.
|
|
261
|
+
const extendedCandidateSet = new Set(extendedCandidates);
|
|
122
262
|
for (const rel of candidates) {
|
|
123
263
|
const fileClass = classifyBundledFile(rel);
|
|
124
264
|
if (fileClass === 'doc')
|
|
@@ -150,12 +290,16 @@ export async function scanLocalBundleSiblings(skillDir, scanner, opts = {}) {
|
|
|
150
290
|
continue; // no install-time hooks — nothing risky
|
|
151
291
|
textToScan = lifecycle;
|
|
152
292
|
}
|
|
153
|
-
|
|
293
|
+
// SMI-6033 Wave 2 (Gap 8) fix: extended candidates are real source files,
|
|
294
|
+
// not markdown — see SecurityScanner.scan()'s own header for why the
|
|
295
|
+
// markdown-only indented-code-block heuristic must be disabled for them.
|
|
296
|
+
// BUNDLED_SCAN_FILES keep the default (isMarkdown=true) unchanged.
|
|
297
|
+
const report = scanner.scan(`${skillDir}/${rel}`, textToScan, false, !extendedCandidateSet.has(rel));
|
|
154
298
|
result.scannedFiles.push(rel);
|
|
155
299
|
// Fresh objects (no mutation of the report's findings array) tagged with the file.
|
|
156
300
|
const tagged = report.findings.map((f) => ({ ...f, location: rel }));
|
|
157
301
|
result.findings.push(...tagged);
|
|
158
|
-
const drivers = tagged.filter(isExecutionThreat);
|
|
302
|
+
const drivers = tagged.filter((f) => isExecutionThreat(f, extendedCandidateSet.has(rel)));
|
|
159
303
|
if (drivers.length > 0) {
|
|
160
304
|
result.rejectable = true;
|
|
161
305
|
result.rejectableFindings.push(...drivers);
|