@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,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security Scanner — password-protected archive evasion detector
|
|
3
|
+
* @module @skillsmith/core/security/scanner/SecurityScanner.archive
|
|
4
|
+
*
|
|
5
|
+
* SMI-6033 Wave 2 (Gap 3): zero zip/archive/password logic existed anywhere
|
|
6
|
+
* in the scanner before this file. Two sub-signals, single finding type
|
|
7
|
+
* (`archive_evasion`), two-tier severity within that one type:
|
|
8
|
+
*
|
|
9
|
+
* - CLI invocation syntax (`unzip -P`, `unrar x -p<pw>`, `7z x -p<pw>`,
|
|
10
|
+
* `zip -P <pw> ... -e`) — precise, low-FP.
|
|
11
|
+
* - Prose co-occurrence (an archive noun + a password noun within a bounded
|
|
12
|
+
* ±2-line window) — the FP-prone fuzzy case, kept as a SEPARATE code path
|
|
13
|
+
* from the CLI-syntax case so its confidence/severity can stay capped
|
|
14
|
+
* independently.
|
|
15
|
+
*
|
|
16
|
+
* Per the plan's §9 provenance-conditioned quarantine policy: a signal may
|
|
17
|
+
* quarantine alone only when a provenance condition specific to THIS skill's
|
|
18
|
+
* own content removes the legitimate interpretations. For archive passwords
|
|
19
|
+
* that condition is "inline literal password AND correlated with a fetch
|
|
20
|
+
* destination in the same content" — every legitimate counter-example
|
|
21
|
+
* (licensed SDK, commercial font/media pack, encrypted export, vendor
|
|
22
|
+
* support bundle) delivers the password out-of-band; shipping it inline next
|
|
23
|
+
* to the fetched archive nullifies the encryption's only non-evasion
|
|
24
|
+
* purpose. Every other shape (out-of-band `$VAR`/placeholder password,
|
|
25
|
+
* uncorrelated CLI usage, or prose-only mention) stays medium (advisory,
|
|
26
|
+
* co-signal-eligible) — never split into a second finding type, per the
|
|
27
|
+
* plan's explicit instruction; severity alone carries the two-tier design,
|
|
28
|
+
* mirroring how scanChmodFetchCompound's own compound signal uses ONE type
|
|
29
|
+
* (privilege_escalation) with severity (not weight) doing the tiering.
|
|
30
|
+
*/
|
|
31
|
+
import type { SecurityFinding } from './types.js';
|
|
32
|
+
import type { LineContext } from './SecurityScanner.helpers.js';
|
|
33
|
+
export declare function scanArchiveEvasion(content: string, lineContexts?: LineContext[]): SecurityFinding[];
|
|
34
|
+
//# sourceMappingURL=SecurityScanner.archive.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SecurityScanner.archive.d.ts","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.archive.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AA2J/D,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,YAAY,CAAC,EAAE,WAAW,EAAE,GAC3B,eAAe,EAAE,CAyDnB"}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security Scanner — password-protected archive evasion detector
|
|
3
|
+
* @module @skillsmith/core/security/scanner/SecurityScanner.archive
|
|
4
|
+
*
|
|
5
|
+
* SMI-6033 Wave 2 (Gap 3): zero zip/archive/password logic existed anywhere
|
|
6
|
+
* in the scanner before this file. Two sub-signals, single finding type
|
|
7
|
+
* (`archive_evasion`), two-tier severity within that one type:
|
|
8
|
+
*
|
|
9
|
+
* - CLI invocation syntax (`unzip -P`, `unrar x -p<pw>`, `7z x -p<pw>`,
|
|
10
|
+
* `zip -P <pw> ... -e`) — precise, low-FP.
|
|
11
|
+
* - Prose co-occurrence (an archive noun + a password noun within a bounded
|
|
12
|
+
* ±2-line window) — the FP-prone fuzzy case, kept as a SEPARATE code path
|
|
13
|
+
* from the CLI-syntax case so its confidence/severity can stay capped
|
|
14
|
+
* independently.
|
|
15
|
+
*
|
|
16
|
+
* Per the plan's §9 provenance-conditioned quarantine policy: a signal may
|
|
17
|
+
* quarantine alone only when a provenance condition specific to THIS skill's
|
|
18
|
+
* own content removes the legitimate interpretations. For archive passwords
|
|
19
|
+
* that condition is "inline literal password AND correlated with a fetch
|
|
20
|
+
* destination in the same content" — every legitimate counter-example
|
|
21
|
+
* (licensed SDK, commercial font/media pack, encrypted export, vendor
|
|
22
|
+
* support bundle) delivers the password out-of-band; shipping it inline next
|
|
23
|
+
* to the fetched archive nullifies the encryption's only non-evasion
|
|
24
|
+
* purpose. Every other shape (out-of-band `$VAR`/placeholder password,
|
|
25
|
+
* uncorrelated CLI usage, or prose-only mention) stays medium (advisory,
|
|
26
|
+
* co-signal-eligible) — never split into a second finding type, per the
|
|
27
|
+
* plan's explicit instruction; severity alone carries the two-tier design,
|
|
28
|
+
* mirroring how scanChmodFetchCompound's own compound signal uses ONE type
|
|
29
|
+
* (privilege_escalation) with severity (not weight) doing the tiering.
|
|
30
|
+
*/
|
|
31
|
+
import { analyzeMarkdownContext, isDocumentationContext, isWithinInlineCode, } from './SecurityScanner.helpers.js';
|
|
32
|
+
import { safeRegexTest, safeRegexCheck } from './regex-utils.js';
|
|
33
|
+
import { looksLikePlaceholderSecret } from './SecurityScanner.pii.js';
|
|
34
|
+
import { FETCH_COMMAND_PATTERN, correlationTargetBasename, isCorrelatedWithFetchDestination, } from './SecurityScanner.fetch-correlation.js';
|
|
35
|
+
// ============================================================================
|
|
36
|
+
// Sub-signal A: CLI invocation syntax
|
|
37
|
+
// ============================================================================
|
|
38
|
+
/**
|
|
39
|
+
* Bounded per-tool invocation captures (mirrors CHMOD_TARGET's
|
|
40
|
+
* capture-then-inspect style rather than one large combined alternation —
|
|
41
|
+
* simpler to reason about and to keep ReDoS-safe). The password itself is
|
|
42
|
+
* extracted from the captured (bounded-length) segment via a lightweight
|
|
43
|
+
* follow-up regex, per tool syntax:
|
|
44
|
+
* - unzip: `-P <password>` (space-separated; real unzip CLI syntax).
|
|
45
|
+
* - unrar/7z: `-p<password>` (ATTACHED, no space — real unrar/7z CLI
|
|
46
|
+
* syntax; a bare `-p` with nothing attached means "prompt
|
|
47
|
+
* interactively," i.e. no inline password at all, so it is correctly
|
|
48
|
+
* NOT matched by the one-or-more `\S+` capture below).
|
|
49
|
+
* - zip: `-P <password>` (space-separated) AND the `-e` encrypt flag
|
|
50
|
+
* present somewhere on the same bounded segment (zip's `-P` only makes
|
|
51
|
+
* sense alongside `-e`).
|
|
52
|
+
*/
|
|
53
|
+
const UNZIP_INVOCATION = /\bunzip\b[^\n]{0,200}/i;
|
|
54
|
+
const UNRAR_INVOCATION = /\bunrar\s+x\b[^\n]{0,200}/i;
|
|
55
|
+
const SEVENZIP_INVOCATION = /\b7z\s+x\b[^\n]{0,200}/i;
|
|
56
|
+
const ZIP_INVOCATION = /\bzip\b[^\n]{0,200}/i;
|
|
57
|
+
const UNZIP_PASSWORD_ARG = /-P\s+(\S{1,200})/;
|
|
58
|
+
const UNRAR_SEVENZIP_PASSWORD_ARG = /-p(\S{1,200})/i;
|
|
59
|
+
const ZIP_PASSWORD_ARG = /-P\s+(\S{1,200})/;
|
|
60
|
+
const ZIP_ENCRYPT_FLAG = /(?:^|\s)-e(?:\s|$)/;
|
|
61
|
+
/** First archive-extension-bearing token on the line — the CLI's target file. */
|
|
62
|
+
const ARCHIVE_FILENAME = /(\S+\.(?:zip|rar|7z|tar\.gz|tgz))\b/i;
|
|
63
|
+
function findArchiveCliPassword(line) {
|
|
64
|
+
const unzip = safeRegexTest(UNZIP_INVOCATION, line);
|
|
65
|
+
if (unzip) {
|
|
66
|
+
const pw = safeRegexTest(UNZIP_PASSWORD_ARG, unzip[0]);
|
|
67
|
+
if (pw)
|
|
68
|
+
return { tool: 'unzip', password: pw[1], index: unzip.index ?? 0 };
|
|
69
|
+
}
|
|
70
|
+
const unrar = safeRegexTest(UNRAR_INVOCATION, line);
|
|
71
|
+
if (unrar) {
|
|
72
|
+
const pw = safeRegexTest(UNRAR_SEVENZIP_PASSWORD_ARG, unrar[0]);
|
|
73
|
+
if (pw)
|
|
74
|
+
return { tool: 'unrar', password: pw[1], index: unrar.index ?? 0 };
|
|
75
|
+
}
|
|
76
|
+
const sevenZip = safeRegexTest(SEVENZIP_INVOCATION, line);
|
|
77
|
+
if (sevenZip) {
|
|
78
|
+
const pw = safeRegexTest(UNRAR_SEVENZIP_PASSWORD_ARG, sevenZip[0]);
|
|
79
|
+
if (pw)
|
|
80
|
+
return { tool: '7z', password: pw[1], index: sevenZip.index ?? 0 };
|
|
81
|
+
}
|
|
82
|
+
const zip = safeRegexTest(ZIP_INVOCATION, line);
|
|
83
|
+
if (zip) {
|
|
84
|
+
const pw = safeRegexTest(ZIP_PASSWORD_ARG, zip[0]);
|
|
85
|
+
if (pw && safeRegexCheck(ZIP_ENCRYPT_FLAG, zip[0])) {
|
|
86
|
+
return { tool: 'zip', password: pw[1], index: zip.index ?? 0 };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* SMI-6033 Wave 3: returns the FULL captured path (quotes stripped), not a
|
|
93
|
+
* bare basename — the shared correlation utility is now directory-aware and
|
|
94
|
+
* must not be handed a path-stripped target.
|
|
95
|
+
*/
|
|
96
|
+
function extractArchiveTargetPath(line) {
|
|
97
|
+
const m = safeRegexTest(ARCHIVE_FILENAME, line);
|
|
98
|
+
if (!m)
|
|
99
|
+
return '';
|
|
100
|
+
return m[1].replace(/['"]/g, '');
|
|
101
|
+
}
|
|
102
|
+
/** `$VAR` / `${VAR}` bare shell-variable reference — an out-of-band password. */
|
|
103
|
+
const SHELL_VAR_REF = /^\$\{?[A-Za-z_][A-Za-z0-9_]*\}?$/;
|
|
104
|
+
/**
|
|
105
|
+
* Is `password` an inline LITERAL secret (not a shell-variable reference,
|
|
106
|
+
* not a placeholder/low-entropy example value)? Reuses the same
|
|
107
|
+
* looksLikePlaceholderSecret gate the sensitive_path/PII detectors use — the
|
|
108
|
+
* value has no `key:`/`key=` prefix to strip here (it's a bare CLI
|
|
109
|
+
* argument), which looksLikePlaceholderSecret's internal extraction already
|
|
110
|
+
* tolerates (no `:`/`=` present means the whole string passes through
|
|
111
|
+
* unchanged).
|
|
112
|
+
*
|
|
113
|
+
* SMI-6033 Wave 4 bugfix (found during Wave 4's own verification, fixed here
|
|
114
|
+
* since PR #2371/Wave 3 — the file this bug shipped in — was still open):
|
|
115
|
+
* `\S{1,200}` in UNZIP_PASSWORD_ARG/ZIP_PASSWORD_ARG/UNRAR_SEVENZIP_PASSWORD_ARG
|
|
116
|
+
* captures the CLI argument RAW, quotes included, so a perfectly ordinary,
|
|
117
|
+
* shell-safe out-of-band reference like `unzip -P "$TOOLKIT_PASSWORD" x.zip`
|
|
118
|
+
* arrived here as `"$TOOLKIT_PASSWORD"` (with literal quote characters) —
|
|
119
|
+
* `SHELL_VAR_REF` never matched (it anchors on a bare `$`, not a leading
|
|
120
|
+
* quote), so this fell through to `looksLikePlaceholderSecret`, which also
|
|
121
|
+
* doesn't recognize it, and the whole thing was misclassified as an inline
|
|
122
|
+
* LITERAL secret. Combined with a correlated fetch target, that reached
|
|
123
|
+
* standalone-`critical` on a completely benign shell idiom — a real
|
|
124
|
+
* quarantine-level false positive, confirmed empirically. Strip surrounding
|
|
125
|
+
* quotes first (mirrors `extractArchiveTargetPath`'s own
|
|
126
|
+
* `.replace(/['"]/g, '')` treatment of the same class of CLI capture) so
|
|
127
|
+
* `SHELL_VAR_REF` sees the actual `$VAR` shape underneath.
|
|
128
|
+
*/
|
|
129
|
+
function isInlineLiteralPassword(password) {
|
|
130
|
+
if (!password)
|
|
131
|
+
return false;
|
|
132
|
+
const unquoted = password.replace(/^['"]|['"]$/g, '');
|
|
133
|
+
if (safeRegexCheck(SHELL_VAR_REF, unquoted))
|
|
134
|
+
return false;
|
|
135
|
+
return !looksLikePlaceholderSecret(unquoted);
|
|
136
|
+
}
|
|
137
|
+
// ============================================================================
|
|
138
|
+
// Sub-signal B: prose co-occurrence (fuzzy, medium-only)
|
|
139
|
+
// ============================================================================
|
|
140
|
+
const ARCHIVE_NOUN = /\b(?:zip|rar|7z|tar\.gz|tgz|archive)\b/i;
|
|
141
|
+
const PASSWORD_NOUN = /\bpassword\b|\bpasscode\b|\bpassphrase\b/i;
|
|
142
|
+
/**
|
|
143
|
+
* Line numbers (1-indexed) where an archive noun co-occurs with a password
|
|
144
|
+
* noun within a bounded ±2-line window (same line or 2 lines either side).
|
|
145
|
+
*/
|
|
146
|
+
function findArchivePasswordProseLines(lines) {
|
|
147
|
+
const flagged = new Set();
|
|
148
|
+
lines.forEach((line, index) => {
|
|
149
|
+
if (!safeRegexCheck(ARCHIVE_NOUN, line))
|
|
150
|
+
return;
|
|
151
|
+
const start = Math.max(0, index - 2);
|
|
152
|
+
const end = Math.min(lines.length - 1, index + 2);
|
|
153
|
+
for (let i = start; i <= end; i++) {
|
|
154
|
+
if (safeRegexCheck(PASSWORD_NOUN, lines[i])) {
|
|
155
|
+
flagged.add(index + 1);
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
return Array.from(flagged);
|
|
161
|
+
}
|
|
162
|
+
// ============================================================================
|
|
163
|
+
// Detector
|
|
164
|
+
// ============================================================================
|
|
165
|
+
export function scanArchiveEvasion(content, lineContexts) {
|
|
166
|
+
const findings = [];
|
|
167
|
+
const lines = content.split('\n');
|
|
168
|
+
const contexts = lineContexts ?? analyzeMarkdownContext(content);
|
|
169
|
+
const fetchLines = lines.filter((l) => safeRegexTest(FETCH_COMMAND_PATTERN, l) !== null);
|
|
170
|
+
const emittedLines = new Set();
|
|
171
|
+
// Sub-signal A: CLI invocation syntax.
|
|
172
|
+
lines.forEach((line, index) => {
|
|
173
|
+
const lineNumber = index + 1;
|
|
174
|
+
const cli = findArchiveCliPassword(line);
|
|
175
|
+
if (!cli)
|
|
176
|
+
return;
|
|
177
|
+
const ctx = contexts[index];
|
|
178
|
+
const inInlineCode = ctx?.isInlineCode && isWithinInlineCode(line, cli.index);
|
|
179
|
+
const inDocContext = ctx ? isDocumentationContext(ctx) || inInlineCode : false;
|
|
180
|
+
const inlineLiteral = isInlineLiteralPassword(cli.password);
|
|
181
|
+
const targetPath = extractArchiveTargetPath(line);
|
|
182
|
+
const correlated = correlationTargetBasename(targetPath).length >= 3 &&
|
|
183
|
+
isCorrelatedWithFetchDestination(targetPath, fetchLines);
|
|
184
|
+
const critical = !inDocContext && inlineLiteral && correlated;
|
|
185
|
+
findings.push({
|
|
186
|
+
type: 'archive_evasion',
|
|
187
|
+
severity: inDocContext ? 'low' : critical ? 'critical' : 'medium',
|
|
188
|
+
message: `Password-protected archive CLI usage (${cli.tool}): "${line.trim().slice(0, 100)}"`,
|
|
189
|
+
location: line.trim().slice(0, 100),
|
|
190
|
+
lineNumber,
|
|
191
|
+
category: 'archive_evasion',
|
|
192
|
+
inDocumentationContext: inDocContext,
|
|
193
|
+
confidence: inDocContext ? 'low' : 'high',
|
|
194
|
+
});
|
|
195
|
+
emittedLines.add(lineNumber);
|
|
196
|
+
});
|
|
197
|
+
// Sub-signal B: prose co-occurrence — the fuzzy, FP-prone case. NEVER
|
|
198
|
+
// critical (no provenance condition can apply to a bare noun pairing), and
|
|
199
|
+
// capped at 'medium' confidence to reflect the heuristic's own uncertainty.
|
|
200
|
+
for (const lineNumber of findArchivePasswordProseLines(lines)) {
|
|
201
|
+
if (emittedLines.has(lineNumber))
|
|
202
|
+
continue; // CLI-syntax already emitted for this line
|
|
203
|
+
const ctx = contexts[lineNumber - 1];
|
|
204
|
+
const inDocContext = ctx ? isDocumentationContext(ctx) : false;
|
|
205
|
+
findings.push({
|
|
206
|
+
type: 'archive_evasion',
|
|
207
|
+
severity: inDocContext ? 'low' : 'medium',
|
|
208
|
+
message: `Archive and password mentioned in close proximity: "${lines[lineNumber - 1].trim().slice(0, 100)}"`,
|
|
209
|
+
location: lines[lineNumber - 1].trim().slice(0, 100),
|
|
210
|
+
lineNumber,
|
|
211
|
+
category: 'archive_evasion',
|
|
212
|
+
inDocumentationContext: inDocContext,
|
|
213
|
+
confidence: inDocContext ? 'low' : 'medium',
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
return findings;
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=SecurityScanner.archive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SecurityScanner.archive.js","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.archive.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,4 +10,5 @@
|
|
|
10
10
|
import type { SecurityFinding } from './types.js';
|
|
11
11
|
import type { LineContext } from './SecurityScanner.helpers.js';
|
|
12
12
|
export declare function scanChmodFetchCompound(content: string, alreadyFlaggedLines: ReadonlySet<number>, lineContexts?: LineContext[]): SecurityFinding[];
|
|
13
|
+
export declare function scanGatekeeperBypass(content: string, lineContexts?: LineContext[], isHighTrustAuthor?: boolean): SecurityFinding[];
|
|
13
14
|
//# sourceMappingURL=SecurityScanner.compound.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SecurityScanner.compound.d.ts","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.compound.ts"],"names":[],"mappings":"AACA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;
|
|
1
|
+
{"version":3,"file":"SecurityScanner.compound.d.ts","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.compound.ts"],"names":[],"mappings":"AACA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AA4C/D,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,EACf,mBAAmB,EAAE,WAAW,CAAC,MAAM,CAAC,EACxC,YAAY,CAAC,EAAE,WAAW,EAAE,GAC3B,eAAe,EAAE,CAwDnB;AA8ED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,YAAY,CAAC,EAAE,WAAW,EAAE,EAC5B,iBAAiB,UAAQ,GACxB,eAAe,EAAE,CAwCnB"}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { analyzeMarkdownContext, isDocumentationContext, isWithinInlineCode, } from './SecurityScanner.helpers.js';
|
|
12
12
|
import { safeRegexTest } from './regex-utils.js';
|
|
13
|
+
import { FETCH_COMMAND_PATTERN, correlationTargetBasename, isCorrelatedWithFetchDestination, } from './SecurityScanner.fetch-correlation.js';
|
|
13
14
|
/**
|
|
14
15
|
* SMI-5424 PR2: owner-permission chmod is a COMPOUND signal, not standalone —
|
|
15
16
|
* `chmod 755 ./bin/cli` / `chmod +x build.sh` previously false-fired
|
|
@@ -34,49 +35,19 @@ import { safeRegexTest } from './regex-utils.js';
|
|
|
34
35
|
// that bare `chmod +x foo` (no u/g/o/a prefix — the most common make-executable form
|
|
35
36
|
// in install scripts and malicious droppers) still matches.
|
|
36
37
|
const OWNER_PERM_CHMOD = /\bchmod\s+(?:-[A-Za-z]+\s+)?(?:[0-7]{3,4}|[ugoa]*(?:[+\-=][rwxXstugo]+(?:,[ugoa]*[+\-=][rwxXstugo]*)*)+)/i;
|
|
37
|
-
// FIX-1: actual fetch COMMANDS only — bare prose tokens (`# downloaded`, `See https://…`,
|
|
38
|
-
// `npx tool init`) false-fired next to a chmod. curl/wget/git-clone, and npx only with a URL.
|
|
39
|
-
const CHMOD_FETCH_CONTEXT = /\b(?:curl|wget)\b|\bgit\s+clone\b|\bnpx\b[^\n]{0,80}https?:\/\//i;
|
|
40
38
|
// FIX-2: capture the chmod target path so a fetch command anywhere correlates by basename.
|
|
41
39
|
// SMI-5433: prefix widened to match the same extended forms as OWNER_PERM_CHMOD; capture
|
|
42
40
|
// group (\S+) (the target path) is unchanged.
|
|
43
41
|
const CHMOD_TARGET = /\bchmod\s+(?:-[A-Za-z]+\s+)?(?:[0-7]{3,4}|[ugoa]*(?:[+\-=][rwxXstugo]+(?:,[ugoa]*[+\-=][rwxXstugo]*)*)+)\s+(\S+)/i;
|
|
44
|
-
function escapeRegExp(s) {
|
|
45
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
46
|
-
}
|
|
47
|
-
// SMI-5431: the IMPLICIT download destination of a fetch command — the file written with
|
|
48
|
-
// NO explicit -o/-O/--output<space>/> redirect: `wget <url>` (no -O/-o) → URL last segment;
|
|
49
|
-
// `git clone <url>` → repo dir (minus `.git`); `curl --output=<file>` (equals form, missed by
|
|
50
|
-
// the explicit regex). A bare `curl <url>` GET writes to STDOUT → '' (never correlates). ReDoS-safe.
|
|
51
|
-
function implicitDownloadBasename(line) {
|
|
52
|
-
const lastSegment = (urlAfterScheme) => {
|
|
53
|
-
const noFrag = urlAfterScheme.split(/[?#]/)[0];
|
|
54
|
-
const slash = noFrag.indexOf('/'); // first slash = end of host
|
|
55
|
-
if (slash < 0)
|
|
56
|
-
return ''; // host only -> wget writes index.html
|
|
57
|
-
const path = noFrag.slice(slash + 1).replace(/\/+$/, '');
|
|
58
|
-
return path === '' ? '' : (path.split('/').pop() ?? '');
|
|
59
|
-
};
|
|
60
|
-
const wget = line.match(/\bwget\b(?![^\n]{0,200}\s-[oO]\b)[^\n]{0,200}?https?:\/\/(\S{1,400})/i);
|
|
61
|
-
if (wget)
|
|
62
|
-
return lastSegment(wget[1]);
|
|
63
|
-
const clone = line.match(/\bgit\s+clone\b[^\n]{0,200}?https?:\/\/(\S{1,400})/i);
|
|
64
|
-
if (clone)
|
|
65
|
-
return lastSegment(clone[1]).replace(/\.git$/i, '');
|
|
66
|
-
const curlEq = line.match(/\bcurl\b[^\n]{0,200}?--output=['"]?(\S{1,400})/i);
|
|
67
|
-
if (curlEq)
|
|
68
|
-
return curlEq[1].replace(/['"]/g, '').split('/').pop() ?? '';
|
|
69
|
-
return '';
|
|
70
|
-
}
|
|
71
42
|
export function scanChmodFetchCompound(content, alreadyFlaggedLines, lineContexts) {
|
|
72
43
|
const findings = [];
|
|
73
44
|
const lines = content.split('\n');
|
|
74
45
|
const contexts = lineContexts ?? analyzeMarkdownContext(content);
|
|
75
46
|
// FIX-2: lines carrying a fetch command, for distance-independent correlation.
|
|
76
47
|
// H-6 (SMI-5433): route through safeRegexTest so the 10,000-char cap applies uniformly
|
|
77
|
-
// to the per-line filter too (
|
|
48
|
+
// to the per-line filter too (FETCH_COMMAND_PATTERN is provably linear, so this is
|
|
78
49
|
// defense-in-depth consistency, not a ReDoS fix).
|
|
79
|
-
const fetchLines = lines.filter((l) => safeRegexTest(
|
|
50
|
+
const fetchLines = lines.filter((l) => safeRegexTest(FETCH_COMMAND_PATTERN, l) !== null);
|
|
80
51
|
lines.forEach((line, index) => {
|
|
81
52
|
const lineNumber = index + 1;
|
|
82
53
|
// World-writable / setuid already emitted critical for this line — skip.
|
|
@@ -88,20 +59,21 @@ export function scanChmodFetchCompound(content, alreadyFlaggedLines, lineContext
|
|
|
88
59
|
// Bounded ±1-line window for the download-then-chmod adjacency.
|
|
89
60
|
const window = [lines[index - 1] ?? '', line, lines[index + 1] ?? ''].join('\n');
|
|
90
61
|
// H-6 (SMI-5433): route through safeRegexTest for the 10,000-char cap.
|
|
91
|
-
const adjacentFetch = safeRegexTest(
|
|
92
|
-
// FIX-2 + SMI-5431: correlate the chmod target
|
|
62
|
+
const adjacentFetch = safeRegexTest(FETCH_COMMAND_PATTERN, window) !== null;
|
|
63
|
+
// FIX-2 + SMI-5431: correlate the chmod target (final segment ≥3 chars) against a fetch
|
|
93
64
|
// command's DOWNLOAD DESTINATION anywhere — explicit (-o/-O/--output<space>/>, with an
|
|
94
65
|
// optional leading path) via regex, OR implicit (wget/git-clone/curl --output=) via
|
|
95
66
|
// exact-token equality. Anchored on the destination, NOT basename-anywhere, so a URL
|
|
96
67
|
// path / query / header value (governance FP class) and a bare curl GET do not correlate.
|
|
68
|
+
// SMI-6033 Wave 3: the FULL captured path is handed to the shared utility (it used to be
|
|
69
|
+
// reduced to a bare basename here, discarding the directory the utility now compares).
|
|
97
70
|
let correlated = false;
|
|
98
71
|
// H-6 (SMI-5433): route through safeRegexTest for the 10,000-char cap.
|
|
99
72
|
const tm = safeRegexTest(CHMOD_TARGET, line);
|
|
100
73
|
if (tm) {
|
|
101
|
-
const
|
|
102
|
-
if (
|
|
103
|
-
|
|
104
|
-
correlated = fetchLines.some((l) => re.test(l) || implicitDownloadBasename(l) === base);
|
|
74
|
+
const targetPath = tm[1].replace(/['"]/g, '');
|
|
75
|
+
if (correlationTargetBasename(targetPath).length >= 3) {
|
|
76
|
+
correlated = isCorrelatedWithFetchDestination(targetPath, fetchLines);
|
|
105
77
|
}
|
|
106
78
|
}
|
|
107
79
|
if (!adjacentFetch && !correlated)
|
|
@@ -124,4 +96,111 @@ export function scanChmodFetchCompound(content, alreadyFlaggedLines, lineContext
|
|
|
124
96
|
});
|
|
125
97
|
return findings;
|
|
126
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* SMI-6033 Wave 2/3 (Gap 5, tiered per the plan's "Product decision:
|
|
101
|
+
* Gatekeeper-bypass carve-out", resolved 2026-08-14): xattr Gatekeeper-bypass
|
|
102
|
+
* detector.
|
|
103
|
+
*
|
|
104
|
+
* `xattr -c <file>` (clear ALL extended attributes) or
|
|
105
|
+
* `xattr -d com.apple.quarantine <file>` (delete just the quarantine
|
|
106
|
+
* attribute — with or without a combined `-r` recursive flag) strips
|
|
107
|
+
* macOS's "downloaded from the internet" Gatekeeper warning from an
|
|
108
|
+
* unsigned binary.
|
|
109
|
+
*
|
|
110
|
+
* NOT unconditionally standalone-critical: `critical` requires the xattr
|
|
111
|
+
* target's path to be correlated with a fetch destination elsewhere in
|
|
112
|
+
* the content (the shared `isCorrelatedWithFetchDestination` utility, same
|
|
113
|
+
* as `scanChmodFetchCompound` above and `scanArchiveEvasion`'s inline-literal
|
|
114
|
+
* form) — the "download a payload, strip its quarantine bit" attack shape.
|
|
115
|
+
* Uncorrelated usage (e.g. a general macOS-troubleshooting doc mentioning
|
|
116
|
+
* `xattr -c` with no fetched file in scope) stays `medium`, regardless of
|
|
117
|
+
* `isHighTrustAuthor`.
|
|
118
|
+
*
|
|
119
|
+
* `isHighTrustAuthor` (default `false`) is the trust-tier carve-out: when the
|
|
120
|
+
* correlated (would-be-critical) form fires AND the caller has verified the
|
|
121
|
+
* skill's author is high-trust, severity downgrades to `medium` instead of
|
|
122
|
+
* `critical` — a downgrade, not a full exemption, since even a high-trust
|
|
123
|
+
* account can be compromised (a `medium` finding still counts toward the
|
|
124
|
+
* two-distinct-medium-signal escalation rule). This parameter must be sourced
|
|
125
|
+
* from a VERIFIED author signal — the indexer's own resolved GitHub repo
|
|
126
|
+
* owner, never a self-declared `SKILL.md` frontmatter field, which is
|
|
127
|
+
* trivially spoofable. `skill_validate`'s offline call site has no such
|
|
128
|
+
* verified signal (a network call to check it would break the tool's
|
|
129
|
+
* deliberately-offline design, per Gap 7) and so must never pass this
|
|
130
|
+
* parameter — it stays defaulted `false`, keeping correlated xattr
|
|
131
|
+
* always-critical there, full stop. Checksum/signature-verification prose
|
|
132
|
+
* near the command does NOT downgrade a non-high-trust correlated form: it's
|
|
133
|
+
* attacker-authored text and proves nothing about the artifact's safety, so
|
|
134
|
+
* no such logic exists here.
|
|
135
|
+
*
|
|
136
|
+
* Two bounded, ReDoS-safe TRIGGER patterns rather than one combined
|
|
137
|
+
* alternation (mirrors CHMOD_TARGET's capture-then-inspect style):
|
|
138
|
+
* XATTR_CLEAR_ALL matches any `-c`-bearing flag cluster within 40 chars of
|
|
139
|
+
* `xattr` (`-c`, `-cr`, `-rc`, ...); XATTR_DELETE_QUARANTINE matches a
|
|
140
|
+
* `-d`-bearing flag cluster immediately followed by the literal
|
|
141
|
+
* `com.apple.quarantine` attribute name (covers both a combined `-dr`/`-rd`
|
|
142
|
+
* cluster and two separate `-r -d com.apple.quarantine` tokens, since the
|
|
143
|
+
* attribute name always immediately follows whichever token carries `-d`).
|
|
144
|
+
* Reading or writing a DIFFERENT attribute (`-l`, `-p <name>`, `-w <name>
|
|
145
|
+
* <value>`) is not a bypass and does not match either pattern. A second,
|
|
146
|
+
* TARGET-capturing sibling of each pattern (below) exists ONLY to extract the
|
|
147
|
+
* xattr'd file's basename for the correlation check — it never changes
|
|
148
|
+
* whether a line trips the detector.
|
|
149
|
+
*/
|
|
150
|
+
const XATTR_CLEAR_ALL = /\bxattr\b[^\n]{0,40}-[a-zA-Z]*c[a-zA-Z]*\b/i;
|
|
151
|
+
const XATTR_DELETE_QUARANTINE = /\bxattr\b[^\n]{0,40}-[a-zA-Z]*d[a-zA-Z]*\s+['"]?com\.apple\.quarantine\b/i;
|
|
152
|
+
const XATTR_CLEAR_ALL_TARGET = /\bxattr\b[^\n]{0,40}-[a-zA-Z]*c[a-zA-Z]*\b\s+['"]?(\S+)/i;
|
|
153
|
+
const XATTR_DELETE_QUARANTINE_TARGET = /\bxattr\b[^\n]{0,40}-[a-zA-Z]*d[a-zA-Z]*\s+['"]?com\.apple\.quarantine\b['"]?\s+['"]?(\S+)/i;
|
|
154
|
+
/**
|
|
155
|
+
* The FILE target of an xattr Gatekeeper-bypass command (real xattr CLI
|
|
156
|
+
* syntax is `xattr [-flags] [attr_name] target...`, so the target always
|
|
157
|
+
* comes right after whichever trigger variant matched) — used only for the
|
|
158
|
+
* fetch-correlation trust-tier check. Extraction failure (no trailing token)
|
|
159
|
+
* simply yields '' (never correlates); it does not affect whether the line
|
|
160
|
+
* trips the underlying detector. SMI-6033 Wave 3: returns the FULL path
|
|
161
|
+
* (quotes stripped), not a bare basename — the shared correlation utility is
|
|
162
|
+
* now directory-aware and must not be handed a path-stripped target.
|
|
163
|
+
*/
|
|
164
|
+
function extractXattrTargetPath(line) {
|
|
165
|
+
const m = safeRegexTest(XATTR_CLEAR_ALL_TARGET, line) ??
|
|
166
|
+
safeRegexTest(XATTR_DELETE_QUARANTINE_TARGET, line);
|
|
167
|
+
if (!m)
|
|
168
|
+
return '';
|
|
169
|
+
return m[1].replace(/['"]/g, '');
|
|
170
|
+
}
|
|
171
|
+
export function scanGatekeeperBypass(content, lineContexts, isHighTrustAuthor = false) {
|
|
172
|
+
const findings = [];
|
|
173
|
+
const lines = content.split('\n');
|
|
174
|
+
const contexts = lineContexts ?? analyzeMarkdownContext(content);
|
|
175
|
+
// Fetch-carrying lines, for the correlation precondition (mirrors
|
|
176
|
+
// scanChmodFetchCompound/scanArchiveEvasion's own fetchLines computation).
|
|
177
|
+
const fetchLines = lines.filter((l) => safeRegexTest(FETCH_COMMAND_PATTERN, l) !== null);
|
|
178
|
+
lines.forEach((line, index) => {
|
|
179
|
+
const match = safeRegexTest(XATTR_CLEAR_ALL, line) ?? safeRegexTest(XATTR_DELETE_QUARANTINE, line);
|
|
180
|
+
if (!match)
|
|
181
|
+
return;
|
|
182
|
+
const ctx = contexts[index];
|
|
183
|
+
const inInlineCode = ctx?.isInlineCode && isWithinInlineCode(line, match.index ?? 0);
|
|
184
|
+
const inDocContext = ctx ? isDocumentationContext(ctx) || inInlineCode : false;
|
|
185
|
+
const targetPath = extractXattrTargetPath(line);
|
|
186
|
+
const correlated = correlationTargetBasename(targetPath).length >= 3 &&
|
|
187
|
+
isCorrelatedWithFetchDestination(targetPath, fetchLines);
|
|
188
|
+
// Product decision (2026-08-14): correlated form is standalone-critical
|
|
189
|
+
// UNLESS the caller has verified the author is high-trust (indexer path
|
|
190
|
+
// only — see this function's header). Uncorrelated usage never reaches
|
|
191
|
+
// 'critical' regardless of trust tier.
|
|
192
|
+
const critical = !inDocContext && correlated && !isHighTrustAuthor;
|
|
193
|
+
findings.push({
|
|
194
|
+
type: 'gatekeeper_bypass',
|
|
195
|
+
severity: inDocContext ? 'low' : critical ? 'critical' : 'medium',
|
|
196
|
+
message: `xattr command strips the macOS Gatekeeper quarantine attribute: "${match[0].trim().slice(0, 100)}"`,
|
|
197
|
+
location: line.trim().slice(0, 100),
|
|
198
|
+
lineNumber: index + 1,
|
|
199
|
+
category: 'gatekeeper_bypass',
|
|
200
|
+
inDocumentationContext: inDocContext,
|
|
201
|
+
confidence: inDocContext ? 'low' : 'high',
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
return findings;
|
|
205
|
+
}
|
|
127
206
|
//# sourceMappingURL=SecurityScanner.compound.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SecurityScanner.compound.js","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.compound.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF;;;;;;;;GAQG;AAIH,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,8BAA8B,CAAA;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"SecurityScanner.compound.js","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.compound.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,10 +5,12 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { ScanReport, ScannerOptions } from './types.js';
|
|
7
7
|
import type { LineContext } from './SecurityScanner.helpers.js';
|
|
8
|
-
import { analyzeMarkdownContext, isDocumentationContext, isWithinInlineCode
|
|
8
|
+
import { analyzeMarkdownContext, isDocumentationContext, isWithinInlineCode } from './SecurityScanner.helpers.js';
|
|
9
|
+
import { extractUrls } from './SecurityScanner.urls.js';
|
|
10
|
+
import { calculateRiskScore } from './SecurityScanner.risk-score.js';
|
|
9
11
|
import { scanSsrfPatterns } from './SecurityScanner.ssrf.js';
|
|
10
12
|
import { toMinimalRefs, toSARIF, toGitHubAnnotations, toSummary } from './SecurityScanner.formatters.js';
|
|
11
|
-
export { LineContext, analyzeMarkdownContext, isDocumentationContext, isWithinInlineCode, calculateRiskScore, };
|
|
13
|
+
export { LineContext, analyzeMarkdownContext, isDocumentationContext, isWithinInlineCode, calculateRiskScore, extractUrls, };
|
|
12
14
|
export { scanSsrfPatterns };
|
|
13
15
|
export { toMinimalRefs, toSARIF, toGitHubAnnotations, toSummary };
|
|
14
16
|
export declare class SecurityScanner {
|
|
@@ -17,7 +19,6 @@ export declare class SecurityScanner {
|
|
|
17
19
|
private maxContentLength;
|
|
18
20
|
private riskThreshold;
|
|
19
21
|
constructor(options?: ScannerOptions);
|
|
20
|
-
private extractUrls;
|
|
21
22
|
private isAllowedDomain;
|
|
22
23
|
private scanUrls;
|
|
23
24
|
private scanJailbreakPatterns;
|
|
@@ -25,7 +26,43 @@ export declare class SecurityScanner {
|
|
|
25
26
|
private scanAIDefenceVulnerabilities;
|
|
26
27
|
/** @deprecated Use standalone calculateRiskScore function for new code */
|
|
27
28
|
calculateRiskScore: typeof calculateRiskScore;
|
|
28
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Run every content-scanning detector against `content` and return the
|
|
31
|
+
* combined findings. Factored out of `scan()` (SMI-6033 Wave 2, Gap 2) so
|
|
32
|
+
* the encoded-payload detector's recursive rescan of DECODED content can
|
|
33
|
+
* reuse the exact same detector suite instead of a parallel, narrower
|
|
34
|
+
* reimplementation.
|
|
35
|
+
*
|
|
36
|
+
* `skipEncodedPayload` is the STRUCTURAL depth-1 recursion guarantee: the
|
|
37
|
+
* recursive callback passed to `scanEncodedPayload` below always calls this
|
|
38
|
+
* method with `skipEncodedPayload: true`, so a base64 blob discovered
|
|
39
|
+
* INSIDE already-decoded content can never itself be decoded — the inner
|
|
40
|
+
* call cannot reach `scanEncodedPayload` again no matter what the decoded
|
|
41
|
+
* text contains. This disables ONLY the encoded-payload detector on the
|
|
42
|
+
* inner call, not the rest of the suite — a decoded `curl|bash` still
|
|
43
|
+
* trips `code_execution`, decoded secrets still trip `sensitive_path`, etc.
|
|
44
|
+
*/
|
|
45
|
+
private runDetectors;
|
|
46
|
+
/**
|
|
47
|
+
* SMI-6033 Wave 3 (Gap 5): `isHighTrustAuthor` (default `false`) is the
|
|
48
|
+
* Gatekeeper-bypass trust-tier carve-out — see `scanGatekeeperBypass`'s own
|
|
49
|
+
* header (`SecurityScanner.compound.ts`) for the full policy. No in-repo
|
|
50
|
+
* caller of this method currently has a verified author signal to pass
|
|
51
|
+
* here (the indexer scans via the edge twin, not this core class); the
|
|
52
|
+
* parameter exists so a future verified-author caller can opt in, and so
|
|
53
|
+
* every existing call site (skill_validate, skill_rescan,
|
|
54
|
+
* bundled-sibling-scan, skill-installation.*) defaults closed by
|
|
55
|
+
* construction, not by convention.
|
|
56
|
+
*
|
|
57
|
+
* SMI-6033 Wave 2 (Gap 8) fix (2026-08-17): `isMarkdown` defaults `true`,
|
|
58
|
+
* preserving byte-identical behavior for every existing caller. Pass
|
|
59
|
+
* `false` when `content` is a real source file, not markdown — see
|
|
60
|
+
* `analyzeMarkdownContext`'s own header (SecurityScanner.helpers.ts) for
|
|
61
|
+
* why the markdown-only indented-code-block heuristic must never apply to
|
|
62
|
+
* non-markdown content. `bundled-sibling-scan.ts`'s
|
|
63
|
+
* `collectExecutableCodeFiles` candidates are the first real caller.
|
|
64
|
+
*/
|
|
65
|
+
scan(skillId: string, content: string, isHighTrustAuthor?: boolean, isMarkdown?: boolean): ScanReport;
|
|
29
66
|
quickCheck(content: string): boolean;
|
|
30
67
|
addAllowedDomain(domain: string): void;
|
|
31
68
|
addBlockedPattern(pattern: RegExp): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SecurityScanner.d.ts","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAmB,UAAU,EAAE,cAAc,EAAqB,MAAM,YAAY,CAAA;AAUhG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AAC/D,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,kBAAkB,
|
|
1
|
+
{"version":3,"file":"SecurityScanner.d.ts","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAmB,UAAU,EAAE,cAAc,EAAqB,MAAM,YAAY,CAAA;AAUhG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AAC/D,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,kBAAkB,EAEnB,MAAM,8BAA8B,CAAA;AAErC,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AAEvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAA;AAGpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAA;AAsC5D,OAAO,EACL,aAAa,EACb,OAAO,EACP,mBAAmB,EACnB,SAAS,EACV,MAAM,iCAAiC,CAAA;AAGxC,OAAO,EACL,WAAW,EACX,sBAAsB,EACtB,sBAAsB,EACtB,kBAAkB,EAClB,kBAAkB,EAClB,WAAW,GACZ,CAAA;AACD,OAAO,EAAE,gBAAgB,EAAE,CAAA;AAC3B,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,mBAAmB,EAAE,SAAS,EAAE,CAAA;AAEjE,qBAAa,eAAe;IAC1B,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,eAAe,CAAU;IACjC,OAAO,CAAC,gBAAgB,CAAQ;IAChC,OAAO,CAAC,aAAa,CAAQ;gBAEjB,OAAO,GAAE,cAAmB;IAOxC,OAAO,CAAC,eAAe;IAYvB,OAAO,CAAC,QAAQ;IAmBhB,OAAO,CAAC,qBAAqB;IAkB7B,OAAO,CAAC,sBAAsB;IA6D9B,OAAO,CAAC,4BAA4B;IAkBpC,0EAA0E;IAC1E,kBAAkB,4BAAqB;IAEvC;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,YAAY;IAwGpB;;;;;;;;;;;;;;;;;;OAkBG;IACH,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,iBAAiB,UAAQ,EAAE,UAAU,UAAO,GAAG,UAAU;IA+ChG,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAOpC,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAItC,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAKxC,MAAM,CAAC,aAAa,uBAAgB;IACpC,MAAM,CAAC,OAAO,iBAAU;IACxB,MAAM,CAAC,mBAAmB,6BAAsB;IAChD,MAAM,CAAC,SAAS,mBAAY;CAC7B;AAED,eAAe,eAAe,CAAA"}
|