fullshield 0.1.3 → 0.3.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 +112 -1
- package/README.md +27 -3
- package/dist/agent.js +934 -96
- package/dist/cli/index.js +1550 -139
- package/dist/compliance.js +5 -0
- package/dist/dashboard.js +18 -1
- package/dist/iac.js +48 -0
- package/dist/index.d.ts +69 -6
- package/dist/index.js +1227 -103
- package/dist/mcp.js +934 -96
- package/dist/supply.js +13 -3
- package/package.json +2 -2
- package/rules/SAST-SKIP-001.md +9 -0
package/dist/compliance.js
CHANGED
|
@@ -73,6 +73,7 @@ var RULE_MAP = [
|
|
|
73
73
|
ROW("SUPPLY-BEHAVIOR-001", "A06:2021 Vulnerable Components", "CWE-829", "V14.2.1", "PCI DSS 6.3.3"),
|
|
74
74
|
ROW("SUPPLY-COOLDOWN-001", "A06:2021 Vulnerable Components", "CWE-1104", "V14.2.1", "PCI DSS 6.3.3"),
|
|
75
75
|
ROW("SUPPLY-LOCKURL-001", "A08:2021 Integrity Failures", "CWE-494", "V14.2.3", "PCI DSS 6.3.3"),
|
|
76
|
+
ROW("SUPPLY-LOCKINTEGRITY-001", "A08:2021 Integrity Failures", "CWE-353", "V14.2.3", "PCI DSS 6.3.3"),
|
|
76
77
|
ROW("SUPPLY-CONFUSION-001", "A08:2021 Integrity Failures", "CWE-494", "V14.2.3", "PCI DSS 6.3.3"),
|
|
77
78
|
ROW("SUPPLY-PROVENANCE-001", "A08:2021 Integrity Failures", "CWE-494", "V14.2.3", "PCI DSS 6.3.3"),
|
|
78
79
|
ROW("SUPPLY-WORM-001", "A06:2021 Vulnerable Components", "CWE-506", "V14.2.1", "PCI DSS 6.3.3 / 12.10"),
|
|
@@ -81,6 +82,9 @@ var RULE_MAP = [
|
|
|
81
82
|
ROW("IAC-GHA-PRTARGET-001", "A01:2021 Broken Access Control", "CWE-269", "V4.1.1", "PCI DSS 7.2"),
|
|
82
83
|
ROW("IAC-GHA-PERMS-001", "A01:2021 Broken Access Control", "CWE-250", "V4.1.1", "PCI DSS 7.2"),
|
|
83
84
|
ROW("IAC-GHA-SECRETLOG-001", "A09:2021 Security Logging Failures", "CWE-532", "V7.1.1", "PCI DSS 3.2 / 10.2"),
|
|
85
|
+
ROW("IAC-GHA-INJECT-001", "A03:2021 Injection", "CWE-94", "V5.2.4", "PCI DSS 6.2.4"),
|
|
86
|
+
ROW("IAC-GHA-PRCHECKOUT-001", "A01:2021 Broken Access Control", "CWE-829", "V4.1.1", "PCI DSS 6.2.4"),
|
|
87
|
+
ROW("DEPS-LICENSE-001", "A06:2021 Vulnerable Components", "CWE-1104", "V14.2.1", "PCI DSS 6.3.3"),
|
|
84
88
|
ROW("IAC-TF-S3PUBLIC-001", "A01:2021 Broken Access Control", "CWE-284", "V12.1.1", "PCI DSS 1.3 / 3.2"),
|
|
85
89
|
ROW("IAC-TF-SGOPEN-001", "A05:2021 Security Misconfiguration", "CWE-284", "V1.4.1", "PCI DSS 1.2 / 1.3"),
|
|
86
90
|
ROW("IAC-TF-UNENCRYPTED-001", "A02:2021 Cryptographic Failures", "CWE-311", "V6.2.1", "PCI DSS 3.4 / 4.2"),
|
|
@@ -107,6 +111,7 @@ var RULE_MAP = [
|
|
|
107
111
|
ROW("SAST-TIMING-001", "A07:2021 Identification Failures", "CWE-208", "V2.10.3", "PCI DSS 8.3"),
|
|
108
112
|
ROW("SAST-HASH-001", "A02:2021 Cryptographic Failures", "CWE-916", "V6.2.2", "PCI DSS 8.3.1"),
|
|
109
113
|
ROW("SAST-GQL-001", "A04:2021 Insecure Design", "CWE-770/200", "V13.1.1", "PCI DSS 6.2.4"),
|
|
114
|
+
ROW("SAST-SKIP-001", "A05:2021 Security Misconfiguration", "CWE-693", "V1.1.1", "PCI DSS 6.2.4"),
|
|
110
115
|
ROW("CONFIG-TRUSTPROXY-001", "A07:2021 Identification Failures", "CWE-290", "V2.2.1", "PCI DSS 8.3.4"),
|
|
111
116
|
ROW("SECRET-BUNDLE-001", "A02:2021 Cryptographic Failures", "CWE-798", "V6.4.1", "PCI DSS 3.2 / 8.3"),
|
|
112
117
|
ROW("DLP-SECRET-001", "A02:2021 Cryptographic Failures", "CWE-200", "V8.3.4", "PCI DSS 3.2 / 8.3"),
|
package/dist/dashboard.js
CHANGED
|
@@ -5,8 +5,22 @@ import { join } from 'path';
|
|
|
5
5
|
|
|
6
6
|
// src/dashboard/index.ts
|
|
7
7
|
var require2 = createRequire(import.meta.url);
|
|
8
|
+
function nodeSqliteAvailable() {
|
|
9
|
+
try {
|
|
10
|
+
require2("node:sqlite");
|
|
11
|
+
return true;
|
|
12
|
+
} catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function nodeSqliteUnavailableMessage(feature) {
|
|
17
|
+
return `${feature} requires Node 22.13+ (node:sqlite is not available on Node ${process.versions.node})`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// src/dashboard/index.ts
|
|
21
|
+
var require3 = createRequire(import.meta.url);
|
|
8
22
|
function sqliteDb(file) {
|
|
9
|
-
const { DatabaseSync } =
|
|
23
|
+
const { DatabaseSync } = require3("node:sqlite");
|
|
10
24
|
let db;
|
|
11
25
|
try {
|
|
12
26
|
db = new DatabaseSync(file);
|
|
@@ -57,6 +71,9 @@ function sqliteDb(file) {
|
|
|
57
71
|
};
|
|
58
72
|
}
|
|
59
73
|
function openHistory(dir) {
|
|
74
|
+
if (!nodeSqliteAvailable()) {
|
|
75
|
+
throw new Error(nodeSqliteUnavailableMessage("history"));
|
|
76
|
+
}
|
|
60
77
|
const root = join(dir, ".fullshield");
|
|
61
78
|
mkdirSync(root, { recursive: true });
|
|
62
79
|
return sqliteDb(join(root, "history.db"));
|
package/dist/iac.js
CHANGED
|
@@ -22,6 +22,32 @@ function finding(ruleId, title, message, file, severity = "high") {
|
|
|
22
22
|
remediation: "Pin actions by SHA, drop pull_request_target, tighten permissions, close 0.0.0.0/0, encrypt storage, drop privileged."
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
|
+
function runBodies(text) {
|
|
26
|
+
const bodies = [];
|
|
27
|
+
const lines = text.split(/\r?\n/);
|
|
28
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
29
|
+
const match = /^(\s*)(?:-\s+)?run:\s*(.*)$/.exec(lines[i] ?? "");
|
|
30
|
+
if (match === null) continue;
|
|
31
|
+
const indent = match[1]?.length ?? 0;
|
|
32
|
+
let body = match[2] ?? "";
|
|
33
|
+
if (body === "|" || body === ">") {
|
|
34
|
+
const block = [];
|
|
35
|
+
for (let j = i + 1; j < lines.length; j += 1) {
|
|
36
|
+
const line = lines[j] ?? "";
|
|
37
|
+
if (line.trim() === "") {
|
|
38
|
+
block.push(line);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const lead = line.match(/^\s*/)?.[0].length ?? 0;
|
|
42
|
+
if (lead <= indent) break;
|
|
43
|
+
block.push(line);
|
|
44
|
+
}
|
|
45
|
+
body = block.join("\n");
|
|
46
|
+
}
|
|
47
|
+
bodies.push(body);
|
|
48
|
+
}
|
|
49
|
+
return bodies;
|
|
50
|
+
}
|
|
25
51
|
function walk(dir, out = []) {
|
|
26
52
|
if (!existsSync(dir)) return out;
|
|
27
53
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
@@ -49,6 +75,28 @@ function scanIac(dir, opts = {}) {
|
|
|
49
75
|
if (/pull_request_target/.test(text)) {
|
|
50
76
|
findings.push(finding("IAC-GHA-PRTARGET-001", "pull_request_target workflow", "untrusted PR can run with base secrets", rel, "critical"));
|
|
51
77
|
}
|
|
78
|
+
if (/pull_request_target/.test(text) && /actions\/checkout/.test(text) && /github\.event\.pull_request/.test(text)) {
|
|
79
|
+
findings.push(
|
|
80
|
+
finding(
|
|
81
|
+
"IAC-GHA-PRCHECKOUT-001",
|
|
82
|
+
"pull_request_target checks out pull-request code",
|
|
83
|
+
"untrusted head can run with the base repository token",
|
|
84
|
+
rel,
|
|
85
|
+
"critical"
|
|
86
|
+
)
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
if (runBodies(text).some((body) => /\$\{\{\s*github\.event\./.test(body))) {
|
|
90
|
+
findings.push(
|
|
91
|
+
finding(
|
|
92
|
+
"IAC-GHA-INJECT-001",
|
|
93
|
+
"Workflow run step interpolates github.event",
|
|
94
|
+
"untrusted event text is passed to the shell",
|
|
95
|
+
rel,
|
|
96
|
+
"critical"
|
|
97
|
+
)
|
|
98
|
+
);
|
|
99
|
+
}
|
|
52
100
|
if (/permissions:\s*\n\s+contents:\s*write/.test(text) || /permissions:\s+write-all/.test(text)) {
|
|
53
101
|
findings.push(finding("IAC-GHA-PERMS-001", "Overly broad GitHub Actions permissions", "write-all or contents: write", rel));
|
|
54
102
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -42,9 +42,11 @@ interface Finding {
|
|
|
42
42
|
note: string;
|
|
43
43
|
}>;
|
|
44
44
|
/** Dependency reachability (Phase 13 / harden-3). */
|
|
45
|
-
reachability?: 'reachable' | 'imported-not-called' | 'reachable-via-dependency' | 'not-imported' | 'reachability-unknown';
|
|
45
|
+
reachability?: 'reachable' | 'imported-not-called' | 'reachable-via-dependency' | 'not-imported' | 'reachability-unknown' | 'symbol-reachable' | 'symbol-not-called';
|
|
46
46
|
/** Lockfile path from an imported direct dependency, e.g. express > body-parser > qs. */
|
|
47
47
|
dependencyPath?: string[];
|
|
48
|
+
/** Every retained dependency path for this package+advisory (shortest first). */
|
|
49
|
+
dependencyPaths?: string[][];
|
|
48
50
|
/** Deterministic (default) vs AI-added. AI findings never fail CI. */
|
|
49
51
|
source?: 'deterministic' | 'ai';
|
|
50
52
|
aiVerdict?: 'true-positive' | 'likely-false-positive' | 'needs-review';
|
|
@@ -93,14 +95,23 @@ interface AiConfig {
|
|
|
93
95
|
/** Highest-severity findings sent to the model first. */
|
|
94
96
|
maxFindings: number;
|
|
95
97
|
}
|
|
98
|
+
interface SastConfig {
|
|
99
|
+
/** Files larger than this are not parsed. Reported as SAST-SKIP-001. */
|
|
100
|
+
maxFileBytes: number;
|
|
101
|
+
}
|
|
96
102
|
interface ResolvedConfig {
|
|
97
103
|
modules: ModuleName[];
|
|
98
104
|
failOn: Severity;
|
|
99
105
|
ignore: string[];
|
|
106
|
+
/** When false, generated/vendored/minified excludes are not applied. */
|
|
107
|
+
defaultExcludes: boolean;
|
|
108
|
+
/** Replaces the built-in exclude list when set. */
|
|
109
|
+
excludes?: string[];
|
|
100
110
|
baseline: string;
|
|
101
111
|
notify: NotifyConfig;
|
|
102
112
|
deps: DepsConfig;
|
|
103
113
|
ai: AiConfig;
|
|
114
|
+
sast: SastConfig;
|
|
104
115
|
}
|
|
105
116
|
declare const DEFAULT_CONFIG: ResolvedConfig;
|
|
106
117
|
declare class ConfigError extends Error {
|
|
@@ -221,6 +232,8 @@ interface ScanMetadata {
|
|
|
221
232
|
modulesRequested: ModuleName[];
|
|
222
233
|
/** Requested modules that are actually implemented and ran. */
|
|
223
234
|
modulesRun: ModuleName[];
|
|
235
|
+
/** Set when `scan --history` stops at the commit cap. Exact text: `scanned N of M commits`. */
|
|
236
|
+
history?: string;
|
|
224
237
|
}
|
|
225
238
|
interface PolicyScanResult {
|
|
226
239
|
enabled: true;
|
|
@@ -236,6 +249,8 @@ interface ScanResult {
|
|
|
236
249
|
config: ResolvedConfig;
|
|
237
250
|
/** Present only when experimental policy was loaded from `fullshield.policy.json`. */
|
|
238
251
|
policy?: PolicyScanResult;
|
|
252
|
+
/** Set when a baseline suppression is missing fields or has expired. */
|
|
253
|
+
suppressionErrors?: string[];
|
|
239
254
|
}
|
|
240
255
|
/**
|
|
241
256
|
* Scanner modules register here as their phases are built.
|
|
@@ -256,6 +271,13 @@ interface WalkOptions {
|
|
|
256
271
|
cwd: string;
|
|
257
272
|
/** Extra gitignore-style patterns (from config) on top of .gitignore. */
|
|
258
273
|
ignore?: string[];
|
|
274
|
+
/**
|
|
275
|
+
* When false, `excludes` / `DEFAULT_EXCLUDES` are not applied.
|
|
276
|
+
* `.git/` and `.fullshield/` are still ignored.
|
|
277
|
+
*/
|
|
278
|
+
defaultExcludes?: boolean;
|
|
279
|
+
/** Replaces `DEFAULT_EXCLUDES` when set. Ignored when `defaultExcludes` is false. */
|
|
280
|
+
excludes?: string[];
|
|
259
281
|
}
|
|
260
282
|
/**
|
|
261
283
|
* Recursively list files under `cwd`, respecting the project's .gitignore and
|
|
@@ -388,6 +410,14 @@ declare function runAiFix(options: {
|
|
|
388
410
|
client: AiClient;
|
|
389
411
|
}): Promise<AiFixResult>;
|
|
390
412
|
|
|
413
|
+
interface Suppression {
|
|
414
|
+
ruleId: string;
|
|
415
|
+
file?: string;
|
|
416
|
+
owner: string;
|
|
417
|
+
reason: string;
|
|
418
|
+
/** ISO date (`YYYY-MM-DD`) or datetime. */
|
|
419
|
+
expires: string;
|
|
420
|
+
}
|
|
391
421
|
interface BaselineFile {
|
|
392
422
|
version: 1;
|
|
393
423
|
findings: Array<{
|
|
@@ -396,6 +426,7 @@ interface BaselineFile {
|
|
|
396
426
|
file?: string;
|
|
397
427
|
line?: number;
|
|
398
428
|
}>;
|
|
429
|
+
suppressions?: Suppression[];
|
|
399
430
|
}
|
|
400
431
|
declare function readBaseline(path: string): Promise<BaselineFile | undefined>;
|
|
401
432
|
declare function writeBaseline(path: string, findings: readonly Finding[]): Promise<void>;
|
|
@@ -413,25 +444,57 @@ declare function parseIgnore(line: string): {
|
|
|
413
444
|
declare function applyInlineSuppressions(findings: readonly Finding[], sources: Readonly<Record<string, string>>): Finding[];
|
|
414
445
|
|
|
415
446
|
/** Human-readable report: findings grouped by severity, highest first. */
|
|
416
|
-
declare function renderConsoleReport(
|
|
447
|
+
declare function renderConsoleReport(input: ScanResult): string;
|
|
417
448
|
|
|
418
449
|
/** Standalone HTML report. Finding text is escaped; secrets are already masked. */
|
|
419
|
-
declare function renderHtmlReport(
|
|
450
|
+
declare function renderHtmlReport(input: ScanResult): string;
|
|
420
451
|
|
|
421
452
|
/** Machine-readable report: full findings plus summary and scan metadata. */
|
|
422
453
|
declare function renderJsonReport(result: ScanResult): string;
|
|
423
454
|
|
|
424
455
|
/** Shareable Markdown report grouped by severity (highest first). */
|
|
425
|
-
declare function renderMarkdownReport(
|
|
456
|
+
declare function renderMarkdownReport(input: ScanResult): string;
|
|
426
457
|
|
|
427
458
|
/** SARIF 2.1.0 report for GitHub Code Scanning and other consumers. */
|
|
428
|
-
declare function renderSarifReport(
|
|
459
|
+
declare function renderSarifReport(input: ScanResult): string;
|
|
429
460
|
/**
|
|
430
461
|
* Structural SARIF 2.1.0 schema check for the documents we emit.
|
|
431
462
|
* Returns an empty array when valid; otherwise human-readable errors.
|
|
432
463
|
*/
|
|
433
464
|
declare function validateSarif21(doc: unknown): string[];
|
|
434
465
|
|
|
466
|
+
/**
|
|
467
|
+
* GitHub Actions workflow commands.
|
|
468
|
+
* Critical and high are `error`, medium is `warning`, low and info are `notice`.
|
|
469
|
+
*/
|
|
470
|
+
declare function renderGithubAnnotations(input: ScanResult): string;
|
|
471
|
+
/**
|
|
472
|
+
* JUnit XML. Medium and above are failures. Low and info are skipped.
|
|
473
|
+
* An empty scan is one passing testcase.
|
|
474
|
+
*/
|
|
475
|
+
declare function renderJunitReport(input: ScanResult): string;
|
|
476
|
+
/** GitLab Code Quality report. Severities map to info, minor, major, critical, blocker. */
|
|
477
|
+
declare function renderGitlabCodeQuality(input: ScanResult): string;
|
|
478
|
+
|
|
479
|
+
/** Compare two scan JSON files. Off unless this is `1`. */
|
|
480
|
+
declare const DIFF_FLAG = "FULLSHIELD_EXPERIMENTAL_DIFF";
|
|
481
|
+
|
|
482
|
+
type DiffFinding = Finding;
|
|
483
|
+
interface ScanDiff {
|
|
484
|
+
unchanged: DiffFinding[];
|
|
485
|
+
added: DiffFinding[];
|
|
486
|
+
removed: DiffFinding[];
|
|
487
|
+
moved: Array<{
|
|
488
|
+
before: DiffFinding;
|
|
489
|
+
after: DiffFinding;
|
|
490
|
+
}>;
|
|
491
|
+
}
|
|
492
|
+
/** Compare two finding lists. Does not invent findings. */
|
|
493
|
+
declare function diffScans(before: readonly DiffFinding[], after: readonly DiffFinding[]): ScanDiff;
|
|
494
|
+
declare function parseScanDocument(text: string): DiffFinding[];
|
|
495
|
+
/** Human summary. Lists at most 20 added findings. */
|
|
496
|
+
declare function renderDiffText(diff: ScanDiff): string;
|
|
497
|
+
|
|
435
498
|
declare function buildSbom(cwd: string): Promise<Record<string, unknown>>;
|
|
436
499
|
|
|
437
500
|
interface SbomComponent {
|
|
@@ -481,4 +544,4 @@ declare function provenanceCoverage(packages: readonly {
|
|
|
481
544
|
version: string;
|
|
482
545
|
}[], known: ReadonlyMap<string, boolean>): ProvenanceScore;
|
|
483
546
|
|
|
484
|
-
export { type AiConfig, type AppliedPolicy, type BaselineFile, CONFIG_FILE_NAME, type Confidence, ConfigError, DEFAULT_CONFIG, type DepsConfig, type Finding, type FindingSummary, type FixOptions, type FixPatch, type FixProposal, type FixResult, type FullShieldPolicy, type GuardTool, type GuardToolCall, IMPLEMENTED_MODULES, MODULE_NAMES, type McpScanOptions, type ModuleContext, type ModuleName, type NotifyConfig, POLICY_FILE_NAME, POLICY_FLAG, POLICY_PEER_ERROR, PolicyError, type PolicyException, type PolicyScanResult, type ResolvedConfig, SEVERITIES, type SbomComponent, type SbomInput, type ScanMetadata, type ScanModule, type ScanOptions, type ScanResult, type Severity, type WalkOptions, analyzeWorkflowTampering, analyzeWormIndicators, applyBaseline, applyInlineSuppressions, applyPolicy, buildCycloneDxVex, buildOpenVex, buildSbom, buildVex, builtinModules, createAiClient, experimentalVex, failsCi, guardTools, isInternalAi, isLiveAi, isModuleName, isPolicyEnabled, isSeverity, loadConfig, loadPolicy, makeFindingId, mcpCfgEnabled, mcpHomeConfigPaths, meetsThreshold, packageVersion, parseIgnore, parsePolicy, provenanceCoverage, readBaseline, renderConsoleReport, renderCycloneDx, renderHtmlReport, renderJsonReport, renderMarkdownReport, renderPrSummary, renderSarifReport, ruleDocsUrl, runAiFix, runFix, runScan, scan, scanMcpConfig, scanTextForSecrets, severityRank, sortFindings, summarize, validateCycloneDx15, validateCycloneDxVex, validateOpenVex, validateSarif21, walkFiles, writeBaseline };
|
|
547
|
+
export { type AiConfig, type AppliedPolicy, type BaselineFile, CONFIG_FILE_NAME, type Confidence, ConfigError, DEFAULT_CONFIG, DIFF_FLAG, type DepsConfig, type Finding, type FindingSummary, type FixOptions, type FixPatch, type FixProposal, type FixResult, type FullShieldPolicy, type GuardTool, type GuardToolCall, IMPLEMENTED_MODULES, MODULE_NAMES, type McpScanOptions, type ModuleContext, type ModuleName, type NotifyConfig, POLICY_FILE_NAME, POLICY_FLAG, POLICY_PEER_ERROR, PolicyError, type PolicyException, type PolicyScanResult, type ResolvedConfig, SEVERITIES, type SbomComponent, type SbomInput, type ScanDiff, type ScanMetadata, type ScanModule, type ScanOptions, type ScanResult, type Severity, type WalkOptions, analyzeWorkflowTampering, analyzeWormIndicators, applyBaseline, applyInlineSuppressions, applyPolicy, buildCycloneDxVex, buildOpenVex, buildSbom, buildVex, builtinModules, createAiClient, diffScans, experimentalVex, failsCi, guardTools, isInternalAi, isLiveAi, isModuleName, isPolicyEnabled, isSeverity, loadConfig, loadPolicy, makeFindingId, mcpCfgEnabled, mcpHomeConfigPaths, meetsThreshold, packageVersion, parseIgnore, parsePolicy, parseScanDocument, provenanceCoverage, readBaseline, renderConsoleReport, renderCycloneDx, renderDiffText, renderGithubAnnotations, renderGitlabCodeQuality, renderHtmlReport, renderJsonReport, renderJunitReport, renderMarkdownReport, renderPrSummary, renderSarifReport, ruleDocsUrl, runAiFix, runFix, runScan, scan, scanMcpConfig, scanTextForSecrets, severityRank, sortFindings, summarize, validateCycloneDx15, validateCycloneDxVex, validateOpenVex, validateSarif21, walkFiles, writeBaseline };
|