@panguard-ai/scan-core 0.1.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/dist/__tests__/atr-engine.test.d.ts +2 -0
- package/dist/__tests__/atr-engine.test.d.ts.map +1 -0
- package/dist/__tests__/atr-engine.test.js +360 -0
- package/dist/__tests__/atr-engine.test.js.map +1 -0
- package/dist/__tests__/context-signals.test.d.ts +2 -0
- package/dist/__tests__/context-signals.test.d.ts.map +1 -0
- package/dist/__tests__/context-signals.test.js +373 -0
- package/dist/__tests__/context-signals.test.js.map +1 -0
- package/dist/__tests__/hash-utils.test.d.ts +2 -0
- package/dist/__tests__/hash-utils.test.d.ts.map +1 -0
- package/dist/__tests__/hash-utils.test.js +89 -0
- package/dist/__tests__/hash-utils.test.js.map +1 -0
- package/dist/__tests__/manifest-parser.test.d.ts +2 -0
- package/dist/__tests__/manifest-parser.test.d.ts.map +1 -0
- package/dist/__tests__/manifest-parser.test.js +246 -0
- package/dist/__tests__/manifest-parser.test.js.map +1 -0
- package/dist/__tests__/risk-scorer.test.d.ts +2 -0
- package/dist/__tests__/risk-scorer.test.d.ts.map +1 -0
- package/dist/__tests__/risk-scorer.test.js +189 -0
- package/dist/__tests__/risk-scorer.test.js.map +1 -0
- package/dist/__tests__/scanner.test.d.ts +2 -0
- package/dist/__tests__/scanner.test.d.ts.map +1 -0
- package/dist/__tests__/scanner.test.js +442 -0
- package/dist/__tests__/scanner.test.js.map +1 -0
- package/dist/atr-engine.d.ts +31 -0
- package/dist/atr-engine.d.ts.map +1 -0
- package/dist/atr-engine.js +149 -0
- package/dist/atr-engine.js.map +1 -0
- package/dist/context-signals.d.ts +33 -0
- package/dist/context-signals.d.ts.map +1 -0
- package/dist/context-signals.js +162 -0
- package/dist/context-signals.js.map +1 -0
- package/dist/hash-utils.d.ts +20 -0
- package/dist/hash-utils.d.ts.map +1 -0
- package/dist/hash-utils.js +28 -0
- package/dist/hash-utils.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/instruction-patterns.d.ts +18 -0
- package/dist/instruction-patterns.d.ts.map +1 -0
- package/dist/instruction-patterns.js +291 -0
- package/dist/instruction-patterns.js.map +1 -0
- package/dist/manifest-parser.d.ts +19 -0
- package/dist/manifest-parser.d.ts.map +1 -0
- package/dist/manifest-parser.js +73 -0
- package/dist/manifest-parser.js.map +1 -0
- package/dist/markdown-utils.d.ts +16 -0
- package/dist/markdown-utils.d.ts.map +1 -0
- package/dist/markdown-utils.js +41 -0
- package/dist/markdown-utils.js.map +1 -0
- package/dist/risk-scorer.d.ts +21 -0
- package/dist/risk-scorer.d.ts.map +1 -0
- package/dist/risk-scorer.js +63 -0
- package/dist/risk-scorer.js.map +1 -0
- package/dist/scanner.d.ts +24 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +131 -0
- package/dist/scanner.js.map +1 -0
- package/dist/secret-detection.d.ts +16 -0
- package/dist/secret-detection.d.ts.map +1 -0
- package/dist/secret-detection.js +42 -0
- package/dist/secret-detection.js.map +1 -0
- package/dist/types.d.ts +106 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/package.json +47 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* String-based SKILL.md manifest parser.
|
|
3
|
+
*
|
|
4
|
+
* Parses YAML frontmatter from raw content string.
|
|
5
|
+
* No filesystem dependencies - caller is responsible for reading the file.
|
|
6
|
+
*/
|
|
7
|
+
import type { SkillManifest } from './types.js';
|
|
8
|
+
/**
|
|
9
|
+
* Parse SKILL.md content string and extract manifest + instructions.
|
|
10
|
+
*
|
|
11
|
+
* @param content - Raw SKILL.md content
|
|
12
|
+
* @param fallbackName - Name to use if no name field in frontmatter (e.g. directory name)
|
|
13
|
+
*/
|
|
14
|
+
export declare function parseManifestFromString(content: string, fallbackName?: string): SkillManifest;
|
|
15
|
+
/**
|
|
16
|
+
* Quick skill name extraction from raw content (no full parse).
|
|
17
|
+
*/
|
|
18
|
+
export declare function parseSkillName(content: string): string | null;
|
|
19
|
+
//# sourceMappingURL=manifest-parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest-parser.d.ts","sourceRoot":"","sources":["../src/manifest-parser.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAiB,MAAM,YAAY,CAAC;AAI/D;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,MAAM,EACf,YAAY,GAAE,MAAkB,GAC/B,aAAa,CAmDf;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG7D"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* String-based SKILL.md manifest parser.
|
|
3
|
+
*
|
|
4
|
+
* Parses YAML frontmatter from raw content string.
|
|
5
|
+
* No filesystem dependencies - caller is responsible for reading the file.
|
|
6
|
+
*/
|
|
7
|
+
import yaml from 'js-yaml';
|
|
8
|
+
const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
|
|
9
|
+
/**
|
|
10
|
+
* Parse SKILL.md content string and extract manifest + instructions.
|
|
11
|
+
*
|
|
12
|
+
* @param content - Raw SKILL.md content
|
|
13
|
+
* @param fallbackName - Name to use if no name field in frontmatter (e.g. directory name)
|
|
14
|
+
*/
|
|
15
|
+
export function parseManifestFromString(content, fallbackName = 'unknown') {
|
|
16
|
+
const match = FRONTMATTER_RE.exec(content);
|
|
17
|
+
if (!match) {
|
|
18
|
+
// No frontmatter - treat entire content as instructions
|
|
19
|
+
return {
|
|
20
|
+
name: fallbackName,
|
|
21
|
+
description: '',
|
|
22
|
+
instructions: content,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const [, frontmatterRaw, instructions] = match;
|
|
26
|
+
let parsed;
|
|
27
|
+
try {
|
|
28
|
+
parsed =
|
|
29
|
+
yaml.load(frontmatterRaw ?? '', { schema: yaml.JSON_SCHEMA }) ??
|
|
30
|
+
{};
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return {
|
|
34
|
+
name: fallbackName,
|
|
35
|
+
description: '',
|
|
36
|
+
instructions: instructions ?? content,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
// Parse metadata - can be a JSON string or an object
|
|
40
|
+
let metadata;
|
|
41
|
+
if (typeof parsed['metadata'] === 'string') {
|
|
42
|
+
try {
|
|
43
|
+
metadata = JSON.parse(parsed['metadata']);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
metadata = undefined;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else if (typeof parsed['metadata'] === 'object' && parsed['metadata'] !== null) {
|
|
50
|
+
metadata = parsed['metadata'];
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
name: parsed['name'] ?? fallbackName,
|
|
54
|
+
description: parsed['description'] ?? '',
|
|
55
|
+
license: parsed['license'],
|
|
56
|
+
homepage: parsed['homepage'],
|
|
57
|
+
userInvocable: parsed['user-invocable'],
|
|
58
|
+
disableModelInvocation: parsed['disable-model-invocation'],
|
|
59
|
+
commandDispatch: parsed['command-dispatch'],
|
|
60
|
+
commandTool: parsed['command-tool'],
|
|
61
|
+
metadata,
|
|
62
|
+
'allowed-tools': parsed['allowed-tools'],
|
|
63
|
+
instructions: instructions ?? '',
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Quick skill name extraction from raw content (no full parse).
|
|
68
|
+
*/
|
|
69
|
+
export function parseSkillName(content) {
|
|
70
|
+
const match = content.match(/^---\n[\s\S]*?^name:\s*(.+)$/m);
|
|
71
|
+
return match?.[1]?.trim() ?? null;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=manifest-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest-parser.js","sourceRoot":"","sources":["../src/manifest-parser.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,IAAI,MAAM,SAAS,CAAC;AAG3B,MAAM,cAAc,GAAG,6CAA6C,CAAC;AAErE;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CACrC,OAAe,EACf,eAAuB,SAAS;IAEhC,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC3C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,wDAAwD;QACxD,OAAO;YACL,IAAI,EAAE,YAAY;YAClB,WAAW,EAAE,EAAE;YACf,YAAY,EAAE,OAAO;SACtB,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,EAAE,cAAc,EAAE,YAAY,CAAC,GAAG,KAAK,CAAC;IAE/C,IAAI,MAA+B,CAAC;IACpC,IAAI,CAAC;QACH,MAAM;YACH,IAAI,CAAC,IAAI,CAAC,cAAc,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,WAAW,EAAE,CAA6B;gBAC1F,EAAE,CAAC;IACP,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;YACL,IAAI,EAAE,YAAY;YAClB,WAAW,EAAE,EAAE;YACf,YAAY,EAAE,YAAY,IAAI,OAAO;SACtC,CAAC;IACJ,CAAC;IAED,qDAAqD;IACrD,IAAI,QAAmC,CAAC;IACxC,IAAI,OAAO,MAAM,CAAC,UAAU,CAAC,KAAK,QAAQ,EAAE,CAAC;QAC3C,IAAI,CAAC;YACH,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,CAAkB,CAAC;QAC7D,CAAC;QAAC,MAAM,CAAC;YACP,QAAQ,GAAG,SAAS,CAAC;QACvB,CAAC;IACH,CAAC;SAAM,IAAI,OAAO,MAAM,CAAC,UAAU,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,UAAU,CAAC,KAAK,IAAI,EAAE,CAAC;QACjF,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAkB,CAAC;IACjD,CAAC;IAED,OAAO;QACL,IAAI,EAAG,MAAM,CAAC,MAAM,CAAY,IAAI,YAAY;QAChD,WAAW,EAAG,MAAM,CAAC,aAAa,CAAY,IAAI,EAAE;QACpD,OAAO,EAAE,MAAM,CAAC,SAAS,CAAuB;QAChD,QAAQ,EAAE,MAAM,CAAC,UAAU,CAAuB;QAClD,aAAa,EAAE,MAAM,CAAC,gBAAgB,CAAwB;QAC9D,sBAAsB,EAAE,MAAM,CAAC,0BAA0B,CAAwB;QACjF,eAAe,EAAE,MAAM,CAAC,kBAAkB,CAAuB;QACjE,WAAW,EAAE,MAAM,CAAC,cAAc,CAAuB;QACzD,QAAQ;QACR,eAAe,EAAE,MAAM,CAAC,eAAe,CAAkC;QACzE,YAAY,EAAE,YAAY,IAAI,EAAE;KACjC,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAC;IAC7D,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC;AACpC,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown processing utilities for scan-core.
|
|
3
|
+
*
|
|
4
|
+
* Pure string operations, no I/O.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Strip markdown code blocks, inline code, blockquotes, HTML tags, and link URLs.
|
|
8
|
+
* Used for two-pass ATR scanning: raw content catches hidden attacks,
|
|
9
|
+
* stripped content catches attacks in prose.
|
|
10
|
+
*/
|
|
11
|
+
export declare function stripMarkdownNoise(raw: string): string;
|
|
12
|
+
/** Extract content from fenced code blocks */
|
|
13
|
+
export declare function extractCodeBlocks(content: string): string;
|
|
14
|
+
/** Remove fenced code blocks from content */
|
|
15
|
+
export declare function stripCodeBlocks(content: string): string;
|
|
16
|
+
//# sourceMappingURL=markdown-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown-utils.d.ts","sourceRoot":"","sources":["../src/markdown-utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAetD;AAED,8CAA8C;AAC9C,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAQzD;AAED,6CAA6C;AAC7C,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEvD"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown processing utilities for scan-core.
|
|
3
|
+
*
|
|
4
|
+
* Pure string operations, no I/O.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Strip markdown code blocks, inline code, blockquotes, HTML tags, and link URLs.
|
|
8
|
+
* Used for two-pass ATR scanning: raw content catches hidden attacks,
|
|
9
|
+
* stripped content catches attacks in prose.
|
|
10
|
+
*/
|
|
11
|
+
export function stripMarkdownNoise(raw) {
|
|
12
|
+
let cleaned = raw;
|
|
13
|
+
// Remove fenced code blocks (```...```)
|
|
14
|
+
cleaned = cleaned.replace(/```[\s\S]*?```/g, ' ');
|
|
15
|
+
// Remove indented code blocks (4 spaces or 1 tab)
|
|
16
|
+
cleaned = cleaned.replace(/^(?: |\t).+$/gm, ' ');
|
|
17
|
+
// Remove inline code (`...`)
|
|
18
|
+
cleaned = cleaned.replace(/`[^`]+`/g, ' ');
|
|
19
|
+
// Remove blockquotes
|
|
20
|
+
cleaned = cleaned.replace(/^>\s?.+$/gm, ' ');
|
|
21
|
+
// Remove markdown link URLs (keep link text)
|
|
22
|
+
cleaned = cleaned.replace(/\[([^\]]*)\]\([^)]+\)/g, '$1');
|
|
23
|
+
// Remove HTML tags
|
|
24
|
+
cleaned = cleaned.replace(/<[^>]+>/g, ' ');
|
|
25
|
+
return cleaned;
|
|
26
|
+
}
|
|
27
|
+
/** Extract content from fenced code blocks */
|
|
28
|
+
export function extractCodeBlocks(content) {
|
|
29
|
+
const blocks = [];
|
|
30
|
+
const re = /```[\s\S]*?```/g;
|
|
31
|
+
let match;
|
|
32
|
+
while ((match = re.exec(content)) !== null) {
|
|
33
|
+
blocks.push(match[0]);
|
|
34
|
+
}
|
|
35
|
+
return blocks.join('\n');
|
|
36
|
+
}
|
|
37
|
+
/** Remove fenced code blocks from content */
|
|
38
|
+
export function stripCodeBlocks(content) {
|
|
39
|
+
return content.replace(/```[\s\S]*?```/g, ' ');
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=markdown-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown-utils.js","sourceRoot":"","sources":["../src/markdown-utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAW;IAC5C,IAAI,OAAO,GAAG,GAAG,CAAC;IAClB,wCAAwC;IACxC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC;IAClD,kDAAkD;IAClD,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,mBAAmB,EAAE,GAAG,CAAC,CAAC;IACpD,6BAA6B;IAC7B,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;IAC3C,qBAAqB;IACrB,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC;IAC7C,6CAA6C;IAC7C,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,wBAAwB,EAAE,IAAI,CAAC,CAAC;IAC1D,mBAAmB;IACnB,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;IAC3C,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,8CAA8C;AAC9C,MAAM,UAAU,iBAAiB,CAAC,OAAe;IAC/C,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,EAAE,GAAG,iBAAiB,CAAC;IAC7B,IAAI,KAA6B,CAAC;IAClC,OAAO,CAAC,KAAK,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC3C,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACxB,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC3B,CAAC;AAED,6CAA6C;AAC7C,MAAM,UAAU,eAAe,CAAC,OAAe;IAC7C,OAAO,OAAO,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC;AACjD,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Risk scoring engine
|
|
3
|
+
*
|
|
4
|
+
* Calculates a 0-100 risk score from findings.
|
|
5
|
+
* Deduplicates findings by ID - only the highest severity instance counts.
|
|
6
|
+
*/
|
|
7
|
+
import type { Finding, RiskLevel } from './types.js';
|
|
8
|
+
/**
|
|
9
|
+
* Calculate risk score (0-100) from findings.
|
|
10
|
+
* Deduplicates by finding ID - keeps the highest severity instance.
|
|
11
|
+
*
|
|
12
|
+
* @param findings - Scan findings to score
|
|
13
|
+
* @param contextMultiplier - Multiplier from context signals (default 1.0).
|
|
14
|
+
* >1 = malicious context (boosts score).
|
|
15
|
+
* <1 = legitimate context (reduces score).
|
|
16
|
+
*/
|
|
17
|
+
export declare function calculateRiskScore(findings: readonly Finding[], contextMultiplier?: number): {
|
|
18
|
+
score: number;
|
|
19
|
+
level: RiskLevel;
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=risk-scorer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"risk-scorer.d.ts","sourceRoot":"","sources":["../src/risk-scorer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAY,MAAM,YAAY,CAAC;AAkB/D;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,SAAS,OAAO,EAAE,EAC5B,iBAAiB,GAAE,MAAY,GAC9B;IACD,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,SAAS,CAAC;CAClB,CAoCA"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Risk scoring engine
|
|
3
|
+
*
|
|
4
|
+
* Calculates a 0-100 risk score from findings.
|
|
5
|
+
* Deduplicates findings by ID - only the highest severity instance counts.
|
|
6
|
+
*/
|
|
7
|
+
const SEVERITY_WEIGHTS = {
|
|
8
|
+
critical: 25,
|
|
9
|
+
high: 15,
|
|
10
|
+
medium: 5,
|
|
11
|
+
low: 1,
|
|
12
|
+
info: 0,
|
|
13
|
+
};
|
|
14
|
+
const SEVERITY_RANK = {
|
|
15
|
+
critical: 4,
|
|
16
|
+
high: 3,
|
|
17
|
+
medium: 2,
|
|
18
|
+
low: 1,
|
|
19
|
+
info: 0,
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Calculate risk score (0-100) from findings.
|
|
23
|
+
* Deduplicates by finding ID - keeps the highest severity instance.
|
|
24
|
+
*
|
|
25
|
+
* @param findings - Scan findings to score
|
|
26
|
+
* @param contextMultiplier - Multiplier from context signals (default 1.0).
|
|
27
|
+
* >1 = malicious context (boosts score).
|
|
28
|
+
* <1 = legitimate context (reduces score).
|
|
29
|
+
*/
|
|
30
|
+
export function calculateRiskScore(findings, contextMultiplier = 1.0) {
|
|
31
|
+
// Deduplicate: keep highest severity per finding ID
|
|
32
|
+
const deduped = new Map();
|
|
33
|
+
for (const finding of findings) {
|
|
34
|
+
const existing = deduped.get(finding.id);
|
|
35
|
+
if (!existing ||
|
|
36
|
+
(SEVERITY_RANK[finding.severity] ?? 0) > (SEVERITY_RANK[existing.severity] ?? 0)) {
|
|
37
|
+
deduped.set(finding.id, finding);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
let rawScore = 0;
|
|
41
|
+
for (const finding of deduped.values()) {
|
|
42
|
+
rawScore += SEVERITY_WEIGHTS[finding.severity] ?? 0;
|
|
43
|
+
}
|
|
44
|
+
// Apply context multiplier
|
|
45
|
+
const adjustedScore = Math.round(rawScore * contextMultiplier);
|
|
46
|
+
const score = Math.min(100, adjustedScore);
|
|
47
|
+
const hasCritical = [...deduped.values()].some((f) => f.severity === 'critical');
|
|
48
|
+
// Critical-override behavior depends on context:
|
|
49
|
+
// - Normal context (multiplier >= 0.6): critical finding forces at least HIGH
|
|
50
|
+
// - Strong legitimate context (multiplier < 0.6): critical finding forces MEDIUM only
|
|
51
|
+
const weakenedCriticalOverride = contextMultiplier < 0.6;
|
|
52
|
+
let level;
|
|
53
|
+
if (score >= 70 || (hasCritical && !weakenedCriticalOverride && score >= 25))
|
|
54
|
+
level = 'CRITICAL';
|
|
55
|
+
else if (score >= 40 || (hasCritical && !weakenedCriticalOverride))
|
|
56
|
+
level = 'HIGH';
|
|
57
|
+
else if (score >= 15 || (hasCritical && weakenedCriticalOverride))
|
|
58
|
+
level = 'MEDIUM';
|
|
59
|
+
else
|
|
60
|
+
level = 'LOW';
|
|
61
|
+
return { score, level };
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=risk-scorer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"risk-scorer.js","sourceRoot":"","sources":["../src/risk-scorer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,MAAM,gBAAgB,GAA2B;IAC/C,QAAQ,EAAE,EAAE;IACZ,IAAI,EAAE,EAAE;IACR,MAAM,EAAE,CAAC;IACT,GAAG,EAAE,CAAC;IACN,IAAI,EAAE,CAAC;CACR,CAAC;AAEF,MAAM,aAAa,GAA2B;IAC5C,QAAQ,EAAE,CAAC;IACX,IAAI,EAAE,CAAC;IACP,MAAM,EAAE,CAAC;IACT,GAAG,EAAE,CAAC;IACN,IAAI,EAAE,CAAC;CACR,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAChC,QAA4B,EAC5B,oBAA4B,GAAG;IAK/B,oDAAoD;IACpD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAmB,CAAC;IAC3C,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACzC,IACE,CAAC,QAAQ;YACT,CAAC,aAAa,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EAChF,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QACnC,CAAC;IACH,CAAC;IAED,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,KAAK,MAAM,OAAO,IAAI,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;QACvC,QAAQ,IAAI,gBAAgB,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACtD,CAAC;IAED,2BAA2B;IAC3B,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,iBAAiB,CAAC,CAAC;IAC/D,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;IAE3C,MAAM,WAAW,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC;IAEjF,iDAAiD;IACjD,8EAA8E;IAC9E,sFAAsF;IACtF,MAAM,wBAAwB,GAAG,iBAAiB,GAAG,GAAG,CAAC;IAEzD,IAAI,KAAgB,CAAC;IACrB,IAAI,KAAK,IAAI,EAAE,IAAI,CAAC,WAAW,IAAI,CAAC,wBAAwB,IAAI,KAAK,IAAI,EAAE,CAAC;QAAE,KAAK,GAAG,UAAU,CAAC;SAC5F,IAAI,KAAK,IAAI,EAAE,IAAI,CAAC,WAAW,IAAI,CAAC,wBAAwB,CAAC;QAAE,KAAK,GAAG,MAAM,CAAC;SAC9E,IAAI,KAAK,IAAI,EAAE,IAAI,CAAC,WAAW,IAAI,wBAAwB,CAAC;QAAE,KAAK,GAAG,QAAQ,CAAC;;QAC/E,KAAK,GAAG,KAAK,CAAC;IAEnB,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AAC1B,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified scan entry point.
|
|
3
|
+
*
|
|
4
|
+
* scanContent() is the single function both CLI and Website call.
|
|
5
|
+
* Pure function: takes content string + options, returns ScanResult.
|
|
6
|
+
* No I/O, no network, no filesystem.
|
|
7
|
+
*/
|
|
8
|
+
import type { ScanOptions, ScanResult } from './types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Scan skill content for security threats.
|
|
11
|
+
*
|
|
12
|
+
* Composes all detection layers:
|
|
13
|
+
* 1. Manifest parsing
|
|
14
|
+
* 2. Context signal detection (boosters/reducers)
|
|
15
|
+
* 3. ATR rule matching (two-pass: raw + stripped)
|
|
16
|
+
* 4. Instruction pattern matching (prompt injection, tool poisoning, encoding attacks)
|
|
17
|
+
* 5. Secret detection
|
|
18
|
+
* 6. Risk scoring with context multiplier
|
|
19
|
+
*
|
|
20
|
+
* @param content - Raw SKILL.md or README.md content
|
|
21
|
+
* @param options - Scan configuration
|
|
22
|
+
*/
|
|
23
|
+
export declare function scanContent(content: string, options?: ScanOptions): ScanResult;
|
|
24
|
+
//# sourceMappingURL=scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACV,WAAW,EACX,UAAU,EAIX,MAAM,YAAY,CAAC;AASpB;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,UAAU,CAsHlF"}
|
package/dist/scanner.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified scan entry point.
|
|
3
|
+
*
|
|
4
|
+
* scanContent() is the single function both CLI and Website call.
|
|
5
|
+
* Pure function: takes content string + options, returns ScanResult.
|
|
6
|
+
* No I/O, no network, no filesystem.
|
|
7
|
+
*/
|
|
8
|
+
import { parseManifestFromString, parseSkillName } from './manifest-parser.js';
|
|
9
|
+
import { checkInstructions } from './instruction-patterns.js';
|
|
10
|
+
import { detectSecrets } from './secret-detection.js';
|
|
11
|
+
import { detectContextSignals } from './context-signals.js';
|
|
12
|
+
import { scanWithATR } from './atr-engine.js';
|
|
13
|
+
import { calculateRiskScore } from './risk-scorer.js';
|
|
14
|
+
import { contentHash, patternHash } from './hash-utils.js';
|
|
15
|
+
/**
|
|
16
|
+
* Scan skill content for security threats.
|
|
17
|
+
*
|
|
18
|
+
* Composes all detection layers:
|
|
19
|
+
* 1. Manifest parsing
|
|
20
|
+
* 2. Context signal detection (boosters/reducers)
|
|
21
|
+
* 3. ATR rule matching (two-pass: raw + stripped)
|
|
22
|
+
* 4. Instruction pattern matching (prompt injection, tool poisoning, encoding attacks)
|
|
23
|
+
* 5. Secret detection
|
|
24
|
+
* 6. Risk scoring with context multiplier
|
|
25
|
+
*
|
|
26
|
+
* @param content - Raw SKILL.md or README.md content
|
|
27
|
+
* @param options - Scan configuration
|
|
28
|
+
*/
|
|
29
|
+
export function scanContent(content, options = {}) {
|
|
30
|
+
const start = Date.now();
|
|
31
|
+
// Early return for empty content
|
|
32
|
+
if (!content || content.trim().length === 0) {
|
|
33
|
+
return {
|
|
34
|
+
skillName: options.skillName ?? null,
|
|
35
|
+
manifest: null,
|
|
36
|
+
findings: [],
|
|
37
|
+
checks: [{ status: 'info', label: 'No content to scan' }],
|
|
38
|
+
riskScore: 0,
|
|
39
|
+
riskLevel: 'LOW',
|
|
40
|
+
contextSignals: { signals: [], multiplier: 1.0 },
|
|
41
|
+
atrRulesEvaluated: 0,
|
|
42
|
+
atrPatternsMatched: 0,
|
|
43
|
+
contentHash: contentHash(content || ''),
|
|
44
|
+
patternHash: '',
|
|
45
|
+
durationMs: Date.now() - start,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const findings = [];
|
|
49
|
+
const checks = [];
|
|
50
|
+
const sourceType = options.sourceType ?? 'skill';
|
|
51
|
+
const isReadme = sourceType === 'documentation';
|
|
52
|
+
// -- Parse manifest --
|
|
53
|
+
const skillName = options.skillName ?? parseSkillName(content);
|
|
54
|
+
const manifest = parseManifestFromString(content, skillName ?? 'unknown');
|
|
55
|
+
// -- Context signals (pre-compute for ATR severity adjustments) --
|
|
56
|
+
const ctx = detectContextSignals(content, manifest);
|
|
57
|
+
const hasStrongReducers = ctx.multiplier < 0.7;
|
|
58
|
+
const allReducers = ctx.signals.every(s => s.type === 'reducer');
|
|
59
|
+
// -- ATR pattern detection --
|
|
60
|
+
const atrRules = options.atrRules ?? [];
|
|
61
|
+
let atrMatchedCount = 0;
|
|
62
|
+
if (atrRules.length > 0) {
|
|
63
|
+
const atrResult = scanWithATR(content, atrRules, {
|
|
64
|
+
isReadme,
|
|
65
|
+
hasStrongReducers,
|
|
66
|
+
allReducers,
|
|
67
|
+
});
|
|
68
|
+
findings.push(...atrResult.findings);
|
|
69
|
+
checks.push(atrResult.check);
|
|
70
|
+
atrMatchedCount = atrResult.matchedCount;
|
|
71
|
+
}
|
|
72
|
+
// -- Instruction pattern detection --
|
|
73
|
+
const instrResult = checkInstructions(manifest.instructions || content, sourceType);
|
|
74
|
+
findings.push(...instrResult.findings);
|
|
75
|
+
checks.push({ status: instrResult.status, label: instrResult.label, findings: instrResult.findings });
|
|
76
|
+
// -- Secret detection --
|
|
77
|
+
const secretResult = detectSecrets(content);
|
|
78
|
+
findings.push(...secretResult.findings);
|
|
79
|
+
checks.push(secretResult.check);
|
|
80
|
+
// -- Manifest validation --
|
|
81
|
+
const hasFrontmatter = /^---\n[\s\S]*?\n---/.test(content);
|
|
82
|
+
const hasName = /^name:\s*.+/m.test(content);
|
|
83
|
+
if (isReadme) {
|
|
84
|
+
checks.push({ status: 'info', label: 'Manifest: no SKILL.md found, analyzed README.md' });
|
|
85
|
+
}
|
|
86
|
+
else if (!hasFrontmatter || !hasName) {
|
|
87
|
+
checks.push({ status: 'warn', label: 'Manifest: incomplete structure' });
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
checks.push({ status: 'pass', label: 'Manifest: valid' });
|
|
91
|
+
}
|
|
92
|
+
// -- Content size --
|
|
93
|
+
checks.push({
|
|
94
|
+
status: content.length > 50_000 ? 'warn' : 'pass',
|
|
95
|
+
label: `Size: ${(content.length / 1024).toFixed(1)}KB`,
|
|
96
|
+
});
|
|
97
|
+
// -- Context signals report --
|
|
98
|
+
if (ctx.signals.length > 0) {
|
|
99
|
+
const boosterCount = ctx.signals.filter(s => s.type === 'booster').length;
|
|
100
|
+
const reducerCount = ctx.signals.filter(s => s.type === 'reducer').length;
|
|
101
|
+
checks.push({
|
|
102
|
+
status: boosterCount > 0 ? 'warn' : 'pass',
|
|
103
|
+
label: `Context: ${boosterCount} risk booster(s), ${reducerCount} reducer(s), multiplier ${ctx.multiplier.toFixed(2)}x`,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
// -- Risk scoring --
|
|
107
|
+
const { score, level } = calculateRiskScore(findings, ctx.multiplier);
|
|
108
|
+
// -- Hashes --
|
|
109
|
+
const cHash = contentHash(content);
|
|
110
|
+
const highFindings = findings
|
|
111
|
+
.filter((f) => f.severity === 'critical' || f.severity === 'high')
|
|
112
|
+
.slice(0, 5);
|
|
113
|
+
const findingSummary = highFindings.map((f) => f.title).join('; ');
|
|
114
|
+
const pHash = patternHash(skillName ?? cHash, findingSummary);
|
|
115
|
+
const durationMs = Date.now() - start;
|
|
116
|
+
return {
|
|
117
|
+
skillName,
|
|
118
|
+
manifest,
|
|
119
|
+
findings,
|
|
120
|
+
checks,
|
|
121
|
+
riskScore: score,
|
|
122
|
+
riskLevel: level,
|
|
123
|
+
contextSignals: ctx,
|
|
124
|
+
atrRulesEvaluated: atrRules.length,
|
|
125
|
+
atrPatternsMatched: atrMatchedCount,
|
|
126
|
+
contentHash: cHash,
|
|
127
|
+
patternHash: pHash,
|
|
128
|
+
durationMs,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.js","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hardcoded secret detection.
|
|
3
|
+
*
|
|
4
|
+
* Detects exposed API keys, tokens, and private keys in skill content.
|
|
5
|
+
*/
|
|
6
|
+
import type { Finding, CheckResult } from './types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Scan content for hardcoded secrets.
|
|
9
|
+
*
|
|
10
|
+
* @returns Findings for each detected secret type + a check summary.
|
|
11
|
+
*/
|
|
12
|
+
export declare function detectSecrets(content: string): {
|
|
13
|
+
findings: Finding[];
|
|
14
|
+
check: CheckResult;
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=secret-detection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secret-detection.d.ts","sourceRoot":"","sources":["../src/secret-detection.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAmBvD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG;IAC9C,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,KAAK,EAAE,WAAW,CAAC;CACpB,CAsBA"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hardcoded secret detection.
|
|
3
|
+
*
|
|
4
|
+
* Detects exposed API keys, tokens, and private keys in skill content.
|
|
5
|
+
*/
|
|
6
|
+
const SECRET_PATTERNS = [
|
|
7
|
+
{ id: 'secret-aws', pattern: /AKIA[0-9A-Z]{16}/, title: 'AWS Access Key exposed' },
|
|
8
|
+
{ id: 'secret-github', pattern: /gh[pousr]_[A-Za-z0-9_]{36,}/, title: 'GitHub token exposed' },
|
|
9
|
+
{ id: 'secret-sk', pattern: /sk-[A-Za-z0-9]{20,}/, title: 'API secret key exposed' },
|
|
10
|
+
{
|
|
11
|
+
id: 'secret-private-key',
|
|
12
|
+
pattern: /-----BEGIN (?:RSA |EC |DSA )?PRIVATE KEY-----/,
|
|
13
|
+
title: 'Private key exposed',
|
|
14
|
+
},
|
|
15
|
+
];
|
|
16
|
+
/**
|
|
17
|
+
* Scan content for hardcoded secrets.
|
|
18
|
+
*
|
|
19
|
+
* @returns Findings for each detected secret type + a check summary.
|
|
20
|
+
*/
|
|
21
|
+
export function detectSecrets(content) {
|
|
22
|
+
const findings = [];
|
|
23
|
+
for (const p of SECRET_PATTERNS) {
|
|
24
|
+
if (p.pattern.test(content)) {
|
|
25
|
+
findings.push({
|
|
26
|
+
id: p.id,
|
|
27
|
+
title: p.title,
|
|
28
|
+
description: 'Hardcoded secret found in content',
|
|
29
|
+
severity: 'critical',
|
|
30
|
+
category: 'secrets',
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
findings,
|
|
36
|
+
check: {
|
|
37
|
+
status: findings.length > 0 ? 'fail' : 'pass',
|
|
38
|
+
label: findings.length > 0 ? `Secrets: ${findings.length} exposed` : 'Secrets: none found',
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=secret-detection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secret-detection.js","sourceRoot":"","sources":["../src/secret-detection.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAUH,MAAM,eAAe,GAA6B;IAChD,EAAE,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,kBAAkB,EAAE,KAAK,EAAE,wBAAwB,EAAE;IAClF,EAAE,EAAE,EAAE,eAAe,EAAE,OAAO,EAAE,6BAA6B,EAAE,KAAK,EAAE,sBAAsB,EAAE;IAC9F,EAAE,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE,qBAAqB,EAAE,KAAK,EAAE,wBAAwB,EAAE;IACpF;QACE,EAAE,EAAE,oBAAoB;QACxB,OAAO,EAAE,+CAA+C;QACxD,KAAK,EAAE,qBAAqB;KAC7B;CACF,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,OAAe;IAI3C,MAAM,QAAQ,GAAc,EAAE,CAAC;IAE/B,KAAK,MAAM,CAAC,IAAI,eAAe,EAAE,CAAC;QAChC,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YAC5B,QAAQ,CAAC,IAAI,CAAC;gBACZ,EAAE,EAAE,CAAC,CAAC,EAAE;gBACR,KAAK,EAAE,CAAC,CAAC,KAAK;gBACd,WAAW,EAAE,mCAAmC;gBAChD,QAAQ,EAAE,UAAU;gBACpB,QAAQ,EAAE,SAAS;aACpB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO;QACL,QAAQ;QACR,KAAK,EAAE;YACL,MAAM,EAAE,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;YAC7C,KAAK,EAAE,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,QAAQ,CAAC,MAAM,UAAU,CAAC,CAAC,CAAC,qBAAqB;SAC3F;KACF,CAAC;AACJ,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @panguard-ai/scan-core - Unified type definitions
|
|
3
|
+
*
|
|
4
|
+
* Shared between CLI Skill Auditor and Website scanner.
|
|
5
|
+
* These are the canonical types for all scan operations.
|
|
6
|
+
*/
|
|
7
|
+
export type Severity = 'info' | 'low' | 'medium' | 'high' | 'critical';
|
|
8
|
+
export type FindingCategory = 'manifest' | 'prompt-injection' | 'tool-poisoning' | 'context-exfiltration' | 'agent-manipulation' | 'privilege-escalation' | 'excessive-autonomy' | 'data-poisoning' | 'model-abuse' | 'skill-compromise' | 'code' | 'secrets' | 'dependency' | 'permission' | 'ai-analysis' | 'atr';
|
|
9
|
+
/** Single scan finding */
|
|
10
|
+
export interface Finding {
|
|
11
|
+
readonly id: string;
|
|
12
|
+
readonly title: string;
|
|
13
|
+
readonly description: string;
|
|
14
|
+
readonly severity: Severity;
|
|
15
|
+
readonly category: FindingCategory | string;
|
|
16
|
+
readonly location?: string;
|
|
17
|
+
}
|
|
18
|
+
/** Result of a single check category */
|
|
19
|
+
export interface CheckResult {
|
|
20
|
+
readonly status: 'pass' | 'warn' | 'fail' | 'info';
|
|
21
|
+
readonly label: string;
|
|
22
|
+
readonly findings?: readonly Finding[];
|
|
23
|
+
}
|
|
24
|
+
export type RiskLevel = 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
|
|
25
|
+
export interface ContextSignal {
|
|
26
|
+
readonly id: string;
|
|
27
|
+
readonly type: 'booster' | 'reducer';
|
|
28
|
+
readonly label: string;
|
|
29
|
+
readonly weight: number;
|
|
30
|
+
}
|
|
31
|
+
export interface ContextSignals {
|
|
32
|
+
readonly signals: readonly ContextSignal[];
|
|
33
|
+
readonly multiplier: number;
|
|
34
|
+
}
|
|
35
|
+
export interface SkillMetadata {
|
|
36
|
+
readonly author?: string;
|
|
37
|
+
readonly version?: string;
|
|
38
|
+
readonly tags?: readonly string[];
|
|
39
|
+
readonly triggers?: readonly string[];
|
|
40
|
+
readonly openclaw?: {
|
|
41
|
+
readonly requires?: {
|
|
42
|
+
readonly bins?: readonly string[];
|
|
43
|
+
readonly env?: readonly string[];
|
|
44
|
+
readonly config?: readonly string[];
|
|
45
|
+
};
|
|
46
|
+
readonly primaryEnv?: string;
|
|
47
|
+
readonly os?: readonly string[];
|
|
48
|
+
readonly always?: boolean;
|
|
49
|
+
readonly homepage?: string;
|
|
50
|
+
};
|
|
51
|
+
readonly [key: string]: unknown;
|
|
52
|
+
}
|
|
53
|
+
export interface SkillManifest {
|
|
54
|
+
readonly name: string;
|
|
55
|
+
readonly description: string;
|
|
56
|
+
readonly license?: string;
|
|
57
|
+
readonly homepage?: string;
|
|
58
|
+
readonly userInvocable?: boolean;
|
|
59
|
+
readonly disableModelInvocation?: boolean;
|
|
60
|
+
readonly commandDispatch?: string;
|
|
61
|
+
readonly commandTool?: string;
|
|
62
|
+
readonly metadata?: SkillMetadata;
|
|
63
|
+
readonly 'allowed-tools'?: readonly string[];
|
|
64
|
+
/** Raw instruction body (after frontmatter) */
|
|
65
|
+
readonly instructions: string;
|
|
66
|
+
}
|
|
67
|
+
export interface ATRRuleCompiled {
|
|
68
|
+
readonly id: string;
|
|
69
|
+
readonly title: string;
|
|
70
|
+
readonly severity: string;
|
|
71
|
+
readonly category: string;
|
|
72
|
+
readonly patterns: ReadonlyArray<{
|
|
73
|
+
readonly field: string;
|
|
74
|
+
readonly pattern: string;
|
|
75
|
+
readonly desc: string;
|
|
76
|
+
}>;
|
|
77
|
+
}
|
|
78
|
+
export interface CompiledRule extends ATRRuleCompiled {
|
|
79
|
+
readonly compiled: ReadonlyArray<{
|
|
80
|
+
readonly regex: RegExp;
|
|
81
|
+
readonly desc: string;
|
|
82
|
+
}>;
|
|
83
|
+
}
|
|
84
|
+
export interface ScanOptions {
|
|
85
|
+
/** Source type hint: 'skill' (SKILL.md) or 'documentation' (README, docs) */
|
|
86
|
+
readonly sourceType?: 'skill' | 'documentation';
|
|
87
|
+
/** Pre-compiled ATR rules (if omitted, no ATR scanning) */
|
|
88
|
+
readonly atrRules?: readonly CompiledRule[];
|
|
89
|
+
/** Skill name override (auto-detected from frontmatter if omitted) */
|
|
90
|
+
readonly skillName?: string;
|
|
91
|
+
}
|
|
92
|
+
export interface ScanResult {
|
|
93
|
+
readonly skillName: string | null;
|
|
94
|
+
readonly manifest: SkillManifest | null;
|
|
95
|
+
readonly findings: readonly Finding[];
|
|
96
|
+
readonly checks: readonly CheckResult[];
|
|
97
|
+
readonly riskScore: number;
|
|
98
|
+
readonly riskLevel: RiskLevel;
|
|
99
|
+
readonly contextSignals: ContextSignals;
|
|
100
|
+
readonly atrRulesEvaluated: number;
|
|
101
|
+
readonly atrPatternsMatched: number;
|
|
102
|
+
readonly contentHash: string;
|
|
103
|
+
readonly patternHash: string;
|
|
104
|
+
readonly durationMs: number;
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
|