@flagrix/scanner-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.
Files changed (36) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +76 -0
  3. package/dist/github/api-error.d.ts +8 -0
  4. package/dist/github/api-error.js +36 -0
  5. package/dist/github/api-error.js.map +1 -0
  6. package/dist/github/repo-scanner.d.ts +15 -0
  7. package/dist/github/repo-scanner.js +1017 -0
  8. package/dist/github/repo-scanner.js.map +1 -0
  9. package/dist/github/user-profile-ruleset.d.ts +12 -0
  10. package/dist/github/user-profile-ruleset.js +155 -0
  11. package/dist/github/user-profile-ruleset.js.map +1 -0
  12. package/dist/github/user-scanner.d.ts +11 -0
  13. package/dist/github/user-scanner.js +141 -0
  14. package/dist/github/user-scanner.js.map +1 -0
  15. package/dist/index.d.ts +10 -0
  16. package/dist/index.js +13 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/linkedin/profile-scorer.d.ts +8 -0
  19. package/dist/linkedin/profile-scorer.js +115 -0
  20. package/dist/linkedin/profile-scorer.js.map +1 -0
  21. package/dist/pdf/pdf-scanner.d.ts +36 -0
  22. package/dist/pdf/pdf-scanner.js +148 -0
  23. package/dist/pdf/pdf-scanner.js.map +1 -0
  24. package/dist/rules/rule-matcher.d.ts +3 -0
  25. package/dist/rules/rule-matcher.js +51 -0
  26. package/dist/rules/rule-matcher.js.map +1 -0
  27. package/dist/types/index.d.ts +234 -0
  28. package/dist/types/index.js +17 -0
  29. package/dist/types/index.js.map +1 -0
  30. package/dist/utils/evidence.d.ts +8 -0
  31. package/dist/utils/evidence.js +28 -0
  32. package/dist/utils/evidence.js.map +1 -0
  33. package/dist/utils/risk-calculator.d.ts +11 -0
  34. package/dist/utils/risk-calculator.js +35 -0
  35. package/dist/utils/risk-calculator.js.map +1 -0
  36. package/package.json +54 -0
@@ -0,0 +1,148 @@
1
+ /**
2
+ * PDF Scanner
3
+ *
4
+ * Client-side PDF analysis without external APIs.
5
+ * Analyzes raw PDF bytes for suspicious patterns:
6
+ * - Embedded JavaScript / auto-actions
7
+ * - Embedded files/attachments
8
+ * - Suspicious URL patterns
9
+ * - Obfuscated content
10
+ * - Social engineering markers
11
+ *
12
+ * Does NOT use pdf.js DOM parsing — works in service worker context
13
+ * by analyzing the raw PDF byte structure directly.
14
+ */
15
+ const DANGEROUS_PDF_PATTERNS = [
16
+ { pattern: /\/JavaScript\s/i, type: "EMBEDDED_JS", severity: "critical", description: "PDF contains embedded JavaScript" },
17
+ { pattern: /\/JS\s*\(/i, type: "EMBEDDED_JS", severity: "critical", description: "PDF contains JavaScript action" },
18
+ { pattern: /\/OpenAction/i, type: "AUTO_ACTION", severity: "high", description: "PDF has auto-open action (runs on open)" },
19
+ { pattern: /\/AA\s/i, type: "AUTO_ACTION", severity: "high", description: "PDF has additional automatic actions" },
20
+ { pattern: /\/Launch/i, type: "LAUNCH_ACTION", severity: "critical", description: "PDF contains Launch action (can execute programs)" },
21
+ { pattern: /\/SubmitForm/i, type: "FORM_SUBMIT", severity: "high", description: "PDF submits form data to external server" },
22
+ { pattern: /\/ImportData/i, type: "IMPORT_DATA", severity: "high", description: "PDF imports external data" },
23
+ { pattern: /\/EmbeddedFile/i, type: "EMBEDDED_FILE", severity: "high", description: "PDF contains embedded files" },
24
+ { pattern: /\/Filespec/i, type: "EMBEDDED_FILE", severity: "medium", description: "PDF references file attachments" },
25
+ { pattern: /\/AcroForm/i, type: "INTERACTIVE_FORM", severity: "low", description: "PDF contains interactive form fields" },
26
+ { pattern: /\/RichMedia/i, type: "RICH_MEDIA", severity: "medium", description: "PDF contains rich media (Flash/multimedia)" },
27
+ { pattern: /\/XFA/i, type: "XFA_FORM", severity: "high", description: "PDF uses XFA forms (common exploit vector)" },
28
+ { pattern: /app\.launchURL/i, type: "URL_LAUNCH", severity: "critical", description: "PDF launches external URL via JavaScript" },
29
+ { pattern: /this\.exportDataObject/i, type: "DATA_EXPORT", severity: "high", description: "PDF exports data objects" },
30
+ { pattern: /eval\s*\(/i, type: "JS_EVAL", severity: "critical", description: "PDF contains eval() JavaScript execution" },
31
+ { pattern: /unescape\s*\(/i, type: "JS_OBFUSCATION", severity: "high", description: "PDF uses unescape() (common obfuscation)" },
32
+ ];
33
+ const SUSPICIOUS_URLS = [
34
+ /bit\.ly/i,
35
+ /tinyurl\.com/i,
36
+ /goo\.gl/i,
37
+ /is\.gd/i,
38
+ /ngrok\.io/i,
39
+ /raw\.githubusercontent\.com.*\.(exe|bat|ps1|sh)/i,
40
+ /pastebin\.com\/raw/i,
41
+ /transfer\.sh/i,
42
+ /discord\.gg/i,
43
+ ];
44
+ const MALWARE_TOOLS = /msfvenom|metasploit|cobaltstrike|veil-evasion|empire|set\s*toolkit/i;
45
+ export async function scanPdfFromUrl(url) {
46
+ const response = await fetch(url);
47
+ if (!response.ok) {
48
+ throw new Error(`Failed to fetch PDF: ${response.status}`);
49
+ }
50
+ const arrayBuffer = await response.arrayBuffer();
51
+ return scanPdfBytes(new Uint8Array(arrayBuffer));
52
+ }
53
+ export function scanPdfBytes(bytes) {
54
+ const findings = [];
55
+ const rawText = new TextDecoder("latin1").decode(bytes);
56
+ if (!rawText.startsWith("%PDF")) {
57
+ findings.push({
58
+ severity: "high",
59
+ type: "INVALID_PDF",
60
+ description: "File does not have a valid PDF header",
61
+ });
62
+ }
63
+ let hasJavaScript = false;
64
+ let hasAutoActions = false;
65
+ let hasEmbeddedFiles = false;
66
+ for (const check of DANGEROUS_PDF_PATTERNS) {
67
+ if (check.pattern.test(rawText)) {
68
+ findings.push({
69
+ severity: check.severity,
70
+ type: check.type,
71
+ description: check.description,
72
+ });
73
+ if (check.type === "EMBEDDED_JS" || check.type === "JS_EVAL" || check.type === "JS_OBFUSCATION") {
74
+ hasJavaScript = true;
75
+ }
76
+ if (check.type === "AUTO_ACTION" || check.type === "LAUNCH_ACTION") {
77
+ hasAutoActions = true;
78
+ }
79
+ if (check.type === "EMBEDDED_FILE") {
80
+ hasEmbeddedFiles = true;
81
+ }
82
+ }
83
+ }
84
+ const urlMatches = rawText.match(/https?:\/\/[^\s)<>"]+/gi) || [];
85
+ const suspiciousUrlSet = new Set();
86
+ for (const url of urlMatches) {
87
+ for (const pattern of SUSPICIOUS_URLS) {
88
+ if (pattern.test(url)) {
89
+ suspiciousUrlSet.add(url.substring(0, 100));
90
+ }
91
+ }
92
+ if (/\.(exe|bat|cmd|ps1|sh|msi|dll|scr|vbs|wsf)(\?|$)/i.test(url)) {
93
+ findings.push({
94
+ severity: "critical",
95
+ type: "EXECUTABLE_LINK",
96
+ description: `Link to executable: ${url.substring(0, 100)}`,
97
+ });
98
+ }
99
+ }
100
+ for (const url of suspiciousUrlSet) {
101
+ findings.push({
102
+ severity: "high",
103
+ type: "SUSPICIOUS_URL",
104
+ description: `Suspicious shortened/redirect URL: ${url}`,
105
+ });
106
+ }
107
+ const producerMatch = rawText.match(/\/Producer\s*\(([^)]+)\)/i);
108
+ const creatorMatch = rawText.match(/\/Creator\s*\(([^)]+)\)/i);
109
+ const toolString = (producerMatch?.[1] || "") + (creatorMatch?.[1] || "");
110
+ if (MALWARE_TOOLS.test(toolString)) {
111
+ findings.push({
112
+ severity: "critical",
113
+ type: "MALWARE_TOOL",
114
+ description: `PDF created with known exploit framework: ${toolString.substring(0, 80)}`,
115
+ });
116
+ }
117
+ const streamCount = (rawText.match(/stream\r?\n/g) || []).length;
118
+ const encodedStreams = (rawText.match(/\/Filter\s*\[?\s*\/[A-Z]/g) || []).length;
119
+ if (streamCount > 5 && encodedStreams > streamCount * 0.8) {
120
+ findings.push({
121
+ severity: "medium",
122
+ type: "HEAVY_ENCODING",
123
+ description: `Heavily encoded: ${encodedStreams}/${streamCount} streams use filters`,
124
+ });
125
+ }
126
+ const pageCountMatch = rawText.match(/\/Count\s+(\d+)/i);
127
+ const pageCount = pageCountMatch ? parseInt(pageCountMatch[1], 10) : 0;
128
+ const criticalCount = findings.filter((f) => f.severity === "critical").length;
129
+ const highCount = findings.filter((f) => f.severity === "high").length;
130
+ const mediumCount = findings.filter((f) => f.severity === "medium").length;
131
+ const riskScore = Math.min(1, criticalCount * 0.35 + highCount * 0.15 + mediumCount * 0.05);
132
+ const riskLevel = riskScore >= 0.6 ? "high" : riskScore >= 0.3 ? "medium" : "low";
133
+ return {
134
+ riskScore,
135
+ riskLevel,
136
+ findings,
137
+ metadata: {
138
+ pageCount,
139
+ hasJavaScript,
140
+ hasEmbeddedFiles,
141
+ hasAutoActions,
142
+ fileSize: bytes.length,
143
+ },
144
+ safeToOpen: riskLevel === "low",
145
+ disclaimer: "Risk assessment only. Not a definitive malware determination. Always verify documents from unknown sources.",
146
+ };
147
+ }
148
+ //# sourceMappingURL=pdf-scanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pdf-scanner.js","sourceRoot":"","sources":["../../src/pdf/pdf-scanner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { GitHubFinding, YaraRule } from "../types/index.js";
2
+ export declare function isTestFile(filePath: string): boolean;
3
+ export declare function applyYaraRules(content: string, rules: YaraRule[], filePath: string): GitHubFinding[];
@@ -0,0 +1,51 @@
1
+ import { collectEvidence } from "../utils/evidence.js";
2
+ // Test file patterns — skip or reduce severity for these paths
3
+ const TEST_FILE_PATTERNS = [
4
+ /__tests__\//i,
5
+ /\/test\//i,
6
+ /\/tests\//i,
7
+ /\/spec\//i,
8
+ /\.test\.[jt]sx?$/i,
9
+ /\.spec\.[jt]sx?$/i,
10
+ /_test\.[jt]sx?$/i,
11
+ /\.mock\.[jt]sx?$/i,
12
+ /fixtures?\//i,
13
+ /cypress\.config\.[jt]sx?$/i,
14
+ /jest\.config\.[jt]sx?$/i,
15
+ /vitest\.config\.[jt]sx?$/i,
16
+ /playwright\.config\.[jt]sx?$/i,
17
+ /\/cypress\//i,
18
+ /\/e2e\//i,
19
+ ];
20
+ export function isTestFile(filePath) {
21
+ return TEST_FILE_PATTERNS.some((pattern) => pattern.test(filePath));
22
+ }
23
+ export function applyYaraRules(content, rules, filePath) {
24
+ const findings = [];
25
+ const isTest = isTestFile(filePath);
26
+ for (const rule of rules) {
27
+ try {
28
+ const regex = new RegExp(rule.pattern, "gi");
29
+ const matches = content.match(regex);
30
+ if (matches && matches.length > 0) {
31
+ // Skip test files for non-critical patterns (reduce false positives)
32
+ if (isTest && rule.severity !== "critical") {
33
+ continue;
34
+ }
35
+ findings.push({
36
+ severity: rule.severity,
37
+ type: "MALWARE_SIGNATURE",
38
+ file: filePath,
39
+ pattern: rule.id,
40
+ description: rule.description,
41
+ evidence: collectEvidence(content, regex),
42
+ });
43
+ }
44
+ }
45
+ catch {
46
+ // Invalid regex, skip
47
+ }
48
+ }
49
+ return findings;
50
+ }
51
+ //# sourceMappingURL=rule-matcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rule-matcher.js","sourceRoot":"","sources":["../../src/rules/rule-matcher.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAEtD,+DAA+D;AAC/D,MAAM,kBAAkB,GAAG;IACzB,cAAc;IACd,WAAW;IACX,YAAY;IACZ,WAAW;IACX,mBAAmB;IACnB,mBAAmB;IACnB,kBAAkB;IAClB,mBAAmB;IACnB,cAAc;IACd,4BAA4B;IAC5B,yBAAyB;IACzB,2BAA2B;IAC3B,+BAA+B;IAC/B,cAAc;IACd,UAAU;CACX,CAAA;AAED,MAAM,UAAU,UAAU,CAAC,QAAgB;IACzC,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAA;AACrE,CAAC;AAED,MAAM,UAAU,cAAc,CAC5B,OAAe,EACf,KAAiB,EACjB,QAAgB;IAEhB,MAAM,QAAQ,GAAoB,EAAE,CAAA;IACpC,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAA;IAEnC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAA;YAC5C,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;YACpC,IAAI,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAClC,qEAAqE;gBACrE,IAAI,MAAM,IAAI,IAAI,CAAC,QAAQ,KAAK,UAAU,EAAE,CAAC;oBAC3C,SAAQ;gBACV,CAAC;gBAED,QAAQ,CAAC,IAAI,CAAC;oBACZ,QAAQ,EAAE,IAAI,CAAC,QAAqC;oBACpD,IAAI,EAAE,mBAAmB;oBACzB,IAAI,EAAE,QAAQ;oBACd,OAAO,EAAE,IAAI,CAAC,EAAE;oBAChB,WAAW,EAAE,IAAI,CAAC,WAAW;oBAC7B,QAAQ,EAAE,eAAe,CAAC,OAAO,EAAE,KAAK,CAAC;iBAC1C,CAAC,CAAA;YACJ,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,sBAAsB;QACxB,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAA;AACjB,CAAC"}
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Core type definitions for @flagrix/scanner-core
3
+ * Shared across GitHub, LinkedIn, and PDF scanners.
4
+ */
5
+ export type RiskLevel = "low" | "medium" | "high";
6
+ export interface RiskFactor {
7
+ factor: string;
8
+ weight: number;
9
+ description: string;
10
+ }
11
+ export interface RiskAssessment {
12
+ riskScore: number;
13
+ riskLevel: RiskLevel;
14
+ factors: RiskFactor[];
15
+ disclaimer: string;
16
+ }
17
+ export interface LinkedInProfileFeatures {
18
+ profileUrl: string;
19
+ accountAgeDays: number | null;
20
+ connectionCount: number | null;
21
+ followerCount: number | null;
22
+ hasProfilePhoto: boolean;
23
+ workHistoryCount: number;
24
+ educationCount: number;
25
+ endorsementCount: number;
26
+ postCount: number;
27
+ mutualConnections: number;
28
+ profileCompleteness: number;
29
+ name: string | null;
30
+ headline: string | null;
31
+ location: string | null;
32
+ isVerified: boolean;
33
+ isTopVoice: boolean;
34
+ hasJobPostings: boolean;
35
+ hasOpenToWork: boolean;
36
+ }
37
+ export interface LinkedInScanResult extends RiskAssessment {
38
+ profile: LinkedInProfileFeatures;
39
+ scannedAt: Date;
40
+ }
41
+ export type ScanDepth = "quick" | "standard" | "deep";
42
+ export interface GitHubRepoInfo {
43
+ owner: string;
44
+ repo: string;
45
+ branch: string;
46
+ url: string;
47
+ }
48
+ export interface GitHubFinding {
49
+ severity: "critical" | "high" | "medium" | "low" | "info";
50
+ type: "MALWARE_SIGNATURE" | "OBFUSCATED_CODE" | "SUSPICIOUS_DEPENDENCY" | "POSTINSTALL_SCRIPT" | "TYPOSQUAT_PACKAGE" | "HIDDEN_FILE" | "SUSPICIOUS_URL" | "NON_ENGLISH_COMMENTS" | "SUSPICIOUS_PACKAGE_NAME" | "HARDCODED_SECRETS" | "NETWORK_COMMUNICATION" | "CRYPTO_MINER" | "DATA_EXFILTRATION" | "BACKDOOR" | "SUPPLY_CHAIN_RISK" | "SUSPICIOUS_FILE_ACCESS" | "CODE_INTEGRITY_ISSUE" | "SOCIAL_ENGINEERING";
51
+ file?: string;
52
+ line?: number;
53
+ pattern?: string;
54
+ package?: string;
55
+ description: string;
56
+ files?: string[];
57
+ codeSnippet?: string;
58
+ codeExplanation?: string;
59
+ /** Matched source lines in `file`, for display and `#L<n>` deep links. */
60
+ evidence?: FindingEvidence[];
61
+ }
62
+ export interface FindingEvidence {
63
+ /** 1-based line number in the finding's `file`. */
64
+ line: number;
65
+ /** The trimmed source line (length-capped). */
66
+ code: string;
67
+ }
68
+ export interface GitHubScanResult extends RiskAssessment {
69
+ repo: GitHubRepoInfo;
70
+ /**
71
+ * The commit the verdict applies to. Every file read during the scan is
72
+ * pinned to this SHA, so the assessment can't straddle a push (TOCTOU) —
73
+ * and consumers should surface it: a verdict is a statement about this
74
+ * commit, not about whatever the branch points to later.
75
+ */
76
+ commitSha?: string;
77
+ scanSummary: {
78
+ filesScanned: number;
79
+ patternsMatched: number;
80
+ dependenciesChecked: number;
81
+ };
82
+ findings: GitHubFinding[];
83
+ safeToClone: boolean;
84
+ scannedAt: Date;
85
+ }
86
+ export interface GitHubUserFeatures {
87
+ username: string;
88
+ accountAgeDays: number;
89
+ followers: number;
90
+ following: number;
91
+ followerFollowingRatio: number;
92
+ publicRepos: number;
93
+ ownedReposCount: number;
94
+ totalStars: number;
95
+ repoStarRatio: number;
96
+ hasProfilePhoto: boolean;
97
+ isProfileComplete: boolean;
98
+ hasCompany: boolean;
99
+ hasBio: boolean;
100
+ recentEventCount: number;
101
+ veryRecentEventCount: number;
102
+ profileUrl: string;
103
+ scannedAt: string;
104
+ }
105
+ export interface GitHubUserScanResult {
106
+ username: string;
107
+ riskLevel: RiskLevel;
108
+ riskScore: number;
109
+ riskFactors: RiskFactor[];
110
+ trustSignals: RiskFactor[];
111
+ recommendation: string;
112
+ profileUrl: string;
113
+ accountAgeDays: number;
114
+ followers: number;
115
+ publicRepos: number;
116
+ scannedAt: string;
117
+ }
118
+ export interface DocumentMetadata {
119
+ type: string;
120
+ sizeBytes: number;
121
+ pages?: number;
122
+ hash: string;
123
+ }
124
+ export interface StaticAnalysisResult {
125
+ hasJavaScript: boolean;
126
+ hasEmbeddedFiles: boolean;
127
+ hasLaunchAction: boolean;
128
+ hasSuspiciousUrls: boolean;
129
+ suspiciousUrls?: string[];
130
+ }
131
+ export interface VirusTotalResult {
132
+ detected: boolean;
133
+ enginesChecked: number;
134
+ detections: number;
135
+ scanId?: string;
136
+ }
137
+ export interface DocumentScanResult extends RiskAssessment {
138
+ metadata: DocumentMetadata;
139
+ staticAnalysis: StaticAnalysisResult;
140
+ virusTotal?: VirusTotalResult;
141
+ safeToOpen: boolean;
142
+ scannedAt: Date;
143
+ }
144
+ export interface MaliciousPackage {
145
+ name: string;
146
+ version?: string;
147
+ severity: "critical" | "high" | "medium";
148
+ source: string;
149
+ description?: string;
150
+ }
151
+ export interface YaraRule {
152
+ id: string;
153
+ name: string;
154
+ pattern: string;
155
+ description: string;
156
+ tags: string[];
157
+ severity: "critical" | "high" | "medium" | "low";
158
+ }
159
+ export interface KnownBadHash {
160
+ sha256: string;
161
+ type: "pdf" | "js" | "binary";
162
+ malwareFamily?: string;
163
+ source: string;
164
+ }
165
+ export interface SignatureDatabase {
166
+ version: string;
167
+ lastUpdated: Date;
168
+ maliciousPackages: MaliciousPackage[];
169
+ yaraRules: YaraRule[];
170
+ knownBadHashes: KnownBadHash[];
171
+ /** GitHub user-profile scoring ruleset. Optional — falls back to the
172
+ * scanner's built-in DEFAULT_USER_PROFILE_RULES when absent. */
173
+ userProfileRules?: UserProfileRuleset;
174
+ }
175
+ /** Feature fields on GitHubUserFeatures that a profile condition may test. */
176
+ export type ProfileFeatureField = "accountAgeDays" | "followers" | "following" | "followerFollowingRatio" | "ownedReposCount" | "totalStars" | "recentEventCount" | "veryRecentEventCount" | "hasProfilePhoto" | "isProfileComplete";
177
+ export type ProfileOperator = "lt" | "lte" | "gt" | "gte" | "eq";
178
+ export interface ProfileSimpleCondition {
179
+ field: ProfileFeatureField;
180
+ operator: ProfileOperator;
181
+ value: number | boolean;
182
+ }
183
+ /** Compound condition — matches only when every sub-condition matches. */
184
+ export interface ProfileCompoundCondition {
185
+ all: ProfileSimpleCondition[];
186
+ }
187
+ export type ProfileCondition = ProfileSimpleCondition | ProfileCompoundCondition;
188
+ export interface ProfileRiskRule {
189
+ id: string;
190
+ name: string;
191
+ /** Supports `{fieldName}` tokens, interpolated from the scanned features. */
192
+ description: string;
193
+ weight: number;
194
+ condition: ProfileCondition;
195
+ /** If the referenced rule id already matched, this rule is skipped. */
196
+ exclusiveWith?: string;
197
+ }
198
+ export interface ProfileRiskLevels {
199
+ /** score ≥ this ⇒ at least medium risk */
200
+ mediumMinScore: number;
201
+ /** score ≥ this ⇒ high risk */
202
+ highMinScore: number;
203
+ recommendations: {
204
+ low: string;
205
+ medium: string;
206
+ high: string;
207
+ };
208
+ }
209
+ export interface UserProfileRuleset {
210
+ riskFactors: ProfileRiskRule[];
211
+ trustSignals: ProfileRiskRule[];
212
+ riskLevels: ProfileRiskLevels;
213
+ }
214
+ export interface RepoScanOptions {
215
+ githubToken?: string;
216
+ signatures: SignatureDatabase;
217
+ }
218
+ export interface UserScanOptions {
219
+ githubToken?: string;
220
+ /** Profile-scoring ruleset (typically from the fetched SignatureDatabase).
221
+ * Falls back to DEFAULT_USER_PROFILE_RULES when omitted. */
222
+ userProfileRules?: UserProfileRuleset;
223
+ }
224
+ /**
225
+ * Score boundaries used by `getRiskLevel` (repo scans) and the LinkedIn profile
226
+ * scorer: a score below `low` is low risk, below `high` is medium risk, and at
227
+ * or above `high` is high risk. The GitHub user scanner applies its own tuned
228
+ * thresholds because profile signals distribute differently from code findings.
229
+ */
230
+ export declare const RISK_THRESHOLDS: {
231
+ readonly low: 0.3;
232
+ readonly high: 0.6;
233
+ };
234
+ export declare const DEFAULT_DISCLAIMER = "Risk assessment only. Not a definitive fraud determination. Always verify through official channels.";
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Core type definitions for @flagrix/scanner-core
3
+ * Shared across GitHub, LinkedIn, and PDF scanners.
4
+ */
5
+ // ─── Constants ───────────────────────────────────────────────────────────────
6
+ /**
7
+ * Score boundaries used by `getRiskLevel` (repo scans) and the LinkedIn profile
8
+ * scorer: a score below `low` is low risk, below `high` is medium risk, and at
9
+ * or above `high` is high risk. The GitHub user scanner applies its own tuned
10
+ * thresholds because profile signals distribute differently from code findings.
11
+ */
12
+ export const RISK_THRESHOLDS = {
13
+ low: 0.3,
14
+ high: 0.6,
15
+ };
16
+ export const DEFAULT_DISCLAIMER = "Risk assessment only. Not a definitive fraud determination. Always verify through official channels.";
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAmSH,gFAAgF;AAEhF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,GAAG,EAAE,GAAG;IACR,IAAI,EAAE,GAAG;CACD,CAAA;AAEV,MAAM,CAAC,MAAM,kBAAkB,GAC7B,sGAAsG,CAAA"}
@@ -0,0 +1,8 @@
1
+ import type { FindingEvidence } from "../types/index.js";
2
+ /**
3
+ * Locate the source lines a pattern matched, with 1-based line numbers, so
4
+ * findings can show *where* the evidence lives (and consumers can deep-link
5
+ * `#L<n>`). Best-effort: patterns that only match across line boundaries
6
+ * yield no evidence — callers keep their `codeSnippet` as the fallback.
7
+ */
8
+ export declare function collectEvidence(content: string, patterns: RegExp | RegExp[], max?: number): FindingEvidence[];
@@ -0,0 +1,28 @@
1
+ const MAX_EVIDENCE_LINES = 3;
2
+ const MAX_LINE_LENGTH = 160;
3
+ /**
4
+ * Locate the source lines a pattern matched, with 1-based line numbers, so
5
+ * findings can show *where* the evidence lives (and consumers can deep-link
6
+ * `#L<n>`). Best-effort: patterns that only match across line boundaries
7
+ * yield no evidence — callers keep their `codeSnippet` as the fallback.
8
+ */
9
+ export function collectEvidence(content, patterns, max = MAX_EVIDENCE_LINES) {
10
+ const list = (Array.isArray(patterns) ? patterns : [patterns]).map(
11
+ // Fresh non-global copies: per-line .test() with a shared /g regex would
12
+ // carry lastIndex across calls and silently skip matches.
13
+ (p) => new RegExp(p.source, p.flags.replace(/g/g, "")));
14
+ const evidence = [];
15
+ const lines = content.split("\n");
16
+ for (let i = 0; i < lines.length && evidence.length < max; i++) {
17
+ const raw = lines[i];
18
+ if (!list.some((re) => re.test(raw)))
19
+ continue;
20
+ const code = raw.trim();
21
+ evidence.push({
22
+ line: i + 1,
23
+ code: code.length > MAX_LINE_LENGTH ? code.slice(0, MAX_LINE_LENGTH) + "…" : code
24
+ });
25
+ }
26
+ return evidence;
27
+ }
28
+ //# sourceMappingURL=evidence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evidence.js","sourceRoot":"","sources":["../../src/utils/evidence.ts"],"names":[],"mappings":"AAEA,MAAM,kBAAkB,GAAG,CAAC,CAAA;AAC5B,MAAM,eAAe,GAAG,GAAG,CAAA;AAE3B;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAC7B,OAAe,EACf,QAA2B,EAC3B,GAAG,GAAG,kBAAkB;IAExB,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG;IAChE,yEAAyE;IACzE,0DAA0D;IAC1D,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CACvD,CAAA;IAED,MAAM,QAAQ,GAAsB,EAAE,CAAA;IACtC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IACjC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QAC/D,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAE,CAAA;QACrB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAAE,SAAQ;QAC9C,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAA;QACvB,QAAQ,CAAC,IAAI,CAAC;YACZ,IAAI,EAAE,CAAC,GAAG,CAAC;YACX,IAAI,EAAE,IAAI,CAAC,MAAM,GAAG,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,eAAe,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI;SAClF,CAAC,CAAA;IACJ,CAAC;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { GitHubFinding, RiskLevel } from "../types/index.js";
2
+ export declare function getSeverityWeight(severity: GitHubFinding["severity"]): number;
3
+ export declare function calculateRiskScore(findings: GitHubFinding[]): number;
4
+ /**
5
+ * A single `critical` finding (code-execution / credential-theft patterns —
6
+ * see CONTRIBUTING.md) always means "high", regardless of how the additive
7
+ * score lands. Otherwise a lone keylogger or backdoor in an unlucky
8
+ * low-file-count repo could average out to "medium" just because nothing
9
+ * else was flagged alongside it.
10
+ */
11
+ export declare function getRiskLevel(score: number, findings?: GitHubFinding[]): RiskLevel;
@@ -0,0 +1,35 @@
1
+ import { RISK_THRESHOLDS } from "../types/index.js";
2
+ export function getSeverityWeight(severity) {
3
+ const weights = {
4
+ critical: 0.4,
5
+ high: 0.25,
6
+ medium: 0.15,
7
+ low: 0.05,
8
+ info: 0.01,
9
+ };
10
+ return weights[severity];
11
+ }
12
+ export function calculateRiskScore(findings) {
13
+ let score = 0;
14
+ for (const finding of findings) {
15
+ score += getSeverityWeight(finding.severity);
16
+ }
17
+ return Math.min(1, score);
18
+ }
19
+ /**
20
+ * A single `critical` finding (code-execution / credential-theft patterns —
21
+ * see CONTRIBUTING.md) always means "high", regardless of how the additive
22
+ * score lands. Otherwise a lone keylogger or backdoor in an unlucky
23
+ * low-file-count repo could average out to "medium" just because nothing
24
+ * else was flagged alongside it.
25
+ */
26
+ export function getRiskLevel(score, findings) {
27
+ if (findings?.some((f) => f.severity === "critical"))
28
+ return "high";
29
+ if (score < RISK_THRESHOLDS.low)
30
+ return "low";
31
+ if (score < RISK_THRESHOLDS.high)
32
+ return "medium";
33
+ return "high";
34
+ }
35
+ //# sourceMappingURL=risk-calculator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"risk-calculator.js","sourceRoot":"","sources":["../../src/utils/risk-calculator.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAEnD,MAAM,UAAU,iBAAiB,CAAC,QAAmC;IACnE,MAAM,OAAO,GAA8C;QACzD,QAAQ,EAAE,GAAG;QACb,IAAI,EAAE,IAAI;QACV,MAAM,EAAE,IAAI;QACZ,GAAG,EAAE,IAAI;QACT,IAAI,EAAE,IAAI;KACX,CAAA;IACD,OAAO,OAAO,CAAC,QAAQ,CAAC,CAAA;AAC1B,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,QAAyB;IAC1D,IAAI,KAAK,GAAG,CAAC,CAAA;IACb,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,KAAK,IAAI,iBAAiB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC9C,CAAC;IACD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAA;AAC3B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,KAAa,EAAE,QAA0B;IACpE,IAAI,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,UAAU,CAAC;QAAE,OAAO,MAAM,CAAA;IACnE,IAAI,KAAK,GAAG,eAAe,CAAC,GAAG;QAAE,OAAO,KAAK,CAAA;IAC7C,IAAI,KAAK,GAAG,eAAe,CAAC,IAAI;QAAE,OAAO,QAAQ,CAAA;IACjD,OAAO,MAAM,CAAA;AACf,CAAC"}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@flagrix/scanner-core",
3
+ "version": "0.1.0",
4
+ "description": "Core security scanning logic for Flagrix — GitHub repo scanner and user profile scorer",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md",
18
+ "LICENSE"
19
+ ],
20
+ "engines": {
21
+ "node": ">=18.17"
22
+ },
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "keywords": [
27
+ "security",
28
+ "malware-detection",
29
+ "supply-chain-security",
30
+ "scam-detection",
31
+ "github",
32
+ "scanner"
33
+ ],
34
+ "homepage": "https://flagrix.io",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/flagrix-io/flagrix-scanner-core.git"
38
+ },
39
+ "scripts": {
40
+ "build": "tsc",
41
+ "test": "vitest run",
42
+ "test:watch": "vitest",
43
+ "prepare": "npm run build",
44
+ "prepublishOnly": "npm run build && npm test"
45
+ },
46
+ "dependencies": {
47
+ "franc-min": "^6.2.0"
48
+ },
49
+ "devDependencies": {
50
+ "@types/node": "^20.0.0",
51
+ "typescript": "^5.0.0",
52
+ "vitest": "^1.6.0"
53
+ }
54
+ }