fullshield 0.2.6 → 0.4.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.
@@ -73,14 +73,22 @@ 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"),
79
80
  ROW("SUPPLY-WORKFLOW-001", "A08:2021 Integrity Failures", "CWE-829", "V14.2.3", "PCI DSS 6.3.3"),
81
+ ROW("SUPPLY-CHAINC2-001", "A06:2021 Vulnerable Components", "CWE-506", "V14.2.1", "PCI DSS 6.3.3"),
82
+ ROW("SUPPLY-STEALER-001", "A06:2021 Vulnerable Components", "CWE-506", "V14.2.1", "PCI DSS 12.10"),
83
+ ROW("SUPPLY-PLATFORMGATE-001", "A06:2021 Vulnerable Components", "CWE-829", "V14.2.1", "PCI DSS 6.3.3"),
84
+ ROW("SUPPLY-SECHOLDER-001", "A06:2021 Vulnerable Components", "CWE-506", "V14.2.1", "PCI DSS 6.3.3"),
80
85
  ROW("IAC-GHA-UNPINNED-001", "A08:2021 Integrity Failures", "CWE-494", "V14.2.3", "PCI DSS 6.3.3"),
81
86
  ROW("IAC-GHA-PRTARGET-001", "A01:2021 Broken Access Control", "CWE-269", "V4.1.1", "PCI DSS 7.2"),
82
87
  ROW("IAC-GHA-PERMS-001", "A01:2021 Broken Access Control", "CWE-250", "V4.1.1", "PCI DSS 7.2"),
83
88
  ROW("IAC-GHA-SECRETLOG-001", "A09:2021 Security Logging Failures", "CWE-532", "V7.1.1", "PCI DSS 3.2 / 10.2"),
89
+ ROW("IAC-GHA-INJECT-001", "A03:2021 Injection", "CWE-94", "V5.2.4", "PCI DSS 6.2.4"),
90
+ ROW("IAC-GHA-PRCHECKOUT-001", "A01:2021 Broken Access Control", "CWE-829", "V4.1.1", "PCI DSS 6.2.4"),
91
+ ROW("DEPS-LICENSE-001", "A06:2021 Vulnerable Components", "CWE-1104", "V14.2.1", "PCI DSS 6.3.3"),
84
92
  ROW("IAC-TF-S3PUBLIC-001", "A01:2021 Broken Access Control", "CWE-284", "V12.1.1", "PCI DSS 1.3 / 3.2"),
85
93
  ROW("IAC-TF-SGOPEN-001", "A05:2021 Security Misconfiguration", "CWE-284", "V1.4.1", "PCI DSS 1.2 / 1.3"),
86
94
  ROW("IAC-TF-UNENCRYPTED-001", "A02:2021 Cryptographic Failures", "CWE-311", "V6.2.1", "PCI DSS 3.4 / 4.2"),
@@ -107,6 +115,7 @@ var RULE_MAP = [
107
115
  ROW("SAST-TIMING-001", "A07:2021 Identification Failures", "CWE-208", "V2.10.3", "PCI DSS 8.3"),
108
116
  ROW("SAST-HASH-001", "A02:2021 Cryptographic Failures", "CWE-916", "V6.2.2", "PCI DSS 8.3.1"),
109
117
  ROW("SAST-GQL-001", "A04:2021 Insecure Design", "CWE-770/200", "V13.1.1", "PCI DSS 6.2.4"),
118
+ ROW("SAST-SKIP-001", "A05:2021 Security Misconfiguration", "CWE-693", "V1.1.1", "PCI DSS 6.2.4"),
110
119
  ROW("CONFIG-TRUSTPROXY-001", "A07:2021 Identification Failures", "CWE-290", "V2.2.1", "PCI DSS 8.3.4"),
111
120
  ROW("SECRET-BUNDLE-001", "A02:2021 Cryptographic Failures", "CWE-798", "V6.4.1", "PCI DSS 3.2 / 8.3"),
112
121
  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 } = require2("node:sqlite");
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
@@ -45,6 +45,8 @@ interface Finding {
45
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';
@@ -57,6 +59,21 @@ interface Finding {
57
59
  stillAtHead?: boolean;
58
60
  /** Read-only identity check (`scan --verify-secrets`). Never includes the secret. */
59
61
  verification?: 'live' | 'revoked' | 'unknown';
62
+ /** Why the reachability class was chosen. Present only with FULLSHIELD_EXPERIMENTAL_REACH_EVIDENCE=1. */
63
+ reachabilityEvidence?: {
64
+ imports: Array<{
65
+ file: string;
66
+ line: number;
67
+ specifier: string;
68
+ }>;
69
+ calls?: Array<{
70
+ file: string;
71
+ line: number;
72
+ symbol: string;
73
+ }>;
74
+ dependencyPath?: string[];
75
+ reason: string;
76
+ };
60
77
  }
61
78
  interface FindingSummary {
62
79
  total: number;
@@ -93,14 +110,34 @@ interface AiConfig {
93
110
  /** Highest-severity findings sent to the model first. */
94
111
  maxFindings: number;
95
112
  }
113
+ interface SastSourceConfig {
114
+ callee: string;
115
+ returns: 'tainted';
116
+ }
117
+ interface SastSanitizerConfig {
118
+ callee: string;
119
+ clears: string[];
120
+ }
121
+ interface SastConfig {
122
+ /** Files larger than this are not parsed. Reported as SAST-SKIP-001. */
123
+ maxFileBytes: number;
124
+ /** Present only when FULLSHIELD_EXPERIMENTAL_CUSTOM_SANITIZERS=1. */
125
+ sources?: SastSourceConfig[];
126
+ sanitizers?: SastSanitizerConfig[];
127
+ }
96
128
  interface ResolvedConfig {
97
129
  modules: ModuleName[];
98
130
  failOn: Severity;
99
131
  ignore: string[];
132
+ /** When false, generated/vendored/minified excludes are not applied. */
133
+ defaultExcludes: boolean;
134
+ /** Replaces the built-in exclude list when set. */
135
+ excludes?: string[];
100
136
  baseline: string;
101
137
  notify: NotifyConfig;
102
138
  deps: DepsConfig;
103
139
  ai: AiConfig;
140
+ sast: SastConfig;
104
141
  }
105
142
  declare const DEFAULT_CONFIG: ResolvedConfig;
106
143
  declare class ConfigError extends Error {
@@ -221,6 +258,25 @@ interface ScanMetadata {
221
258
  modulesRequested: ModuleName[];
222
259
  /** Requested modules that are actually implemented and ran. */
223
260
  modulesRun: ModuleName[];
261
+ /** Set when `scan --history` stops at the commit cap. Exact text: `scanned N of M commits`. */
262
+ history?: string;
263
+ /** Phase 48. Direct dependencies no project file imports. Absent when the list is empty. */
264
+ unusedDirect?: {
265
+ packages: string[];
266
+ advisories: number;
267
+ };
268
+ /** Phase 49. Direct version bumps taken from OSV `fixed` versions. Absent when there is nothing to say. */
269
+ upgradePlan?: {
270
+ bumps: Array<{
271
+ name: string;
272
+ from: string;
273
+ to: string;
274
+ safe: boolean;
275
+ findings: number;
276
+ }>;
277
+ unresolved: string[];
278
+ sentence: string;
279
+ };
224
280
  }
225
281
  interface PolicyScanResult {
226
282
  enabled: true;
@@ -236,6 +292,8 @@ interface ScanResult {
236
292
  config: ResolvedConfig;
237
293
  /** Present only when experimental policy was loaded from `fullshield.policy.json`. */
238
294
  policy?: PolicyScanResult;
295
+ /** Set when a baseline suppression is missing fields or has expired. */
296
+ suppressionErrors?: string[];
239
297
  }
240
298
  /**
241
299
  * Scanner modules register here as their phases are built.
@@ -256,6 +314,13 @@ interface WalkOptions {
256
314
  cwd: string;
257
315
  /** Extra gitignore-style patterns (from config) on top of .gitignore. */
258
316
  ignore?: string[];
317
+ /**
318
+ * When false, `excludes` / `DEFAULT_EXCLUDES` are not applied.
319
+ * `.git/` and `.fullshield/` are still ignored.
320
+ */
321
+ defaultExcludes?: boolean;
322
+ /** Replaces `DEFAULT_EXCLUDES` when set. Ignored when `defaultExcludes` is false. */
323
+ excludes?: string[];
259
324
  }
260
325
  /**
261
326
  * Recursively list files under `cwd`, respecting the project's .gitignore and
@@ -388,6 +453,14 @@ declare function runAiFix(options: {
388
453
  client: AiClient;
389
454
  }): Promise<AiFixResult>;
390
455
 
456
+ interface Suppression {
457
+ ruleId: string;
458
+ file?: string;
459
+ owner: string;
460
+ reason: string;
461
+ /** ISO date (`YYYY-MM-DD`) or datetime. */
462
+ expires: string;
463
+ }
391
464
  interface BaselineFile {
392
465
  version: 1;
393
466
  findings: Array<{
@@ -396,6 +469,7 @@ interface BaselineFile {
396
469
  file?: string;
397
470
  line?: number;
398
471
  }>;
472
+ suppressions?: Suppression[];
399
473
  }
400
474
  declare function readBaseline(path: string): Promise<BaselineFile | undefined>;
401
475
  declare function writeBaseline(path: string, findings: readonly Finding[]): Promise<void>;
@@ -412,27 +486,65 @@ declare function parseIgnore(line: string): {
412
486
  */
413
487
  declare function applyInlineSuppressions(findings: readonly Finding[], sources: Readonly<Record<string, string>>): Finding[];
414
488
 
415
- /** Human-readable report: findings grouped by severity, highest first. */
416
- declare function renderConsoleReport(result: ScanResult): string;
489
+ interface ConsoleReportOptions {
490
+ /** Print every finding, with no 20-row cap. */
491
+ all?: boolean;
492
+ }
493
+ /** Human-readable report. Default view is the actionable rows. JSON is unchanged. */
494
+ declare function renderConsoleReport(input: ScanResult, options?: ConsoleReportOptions): string;
417
495
 
418
496
  /** Standalone HTML report. Finding text is escaped; secrets are already masked. */
419
- declare function renderHtmlReport(result: ScanResult): string;
497
+ declare function renderHtmlReport(input: ScanResult): string;
420
498
 
421
499
  /** Machine-readable report: full findings plus summary and scan metadata. */
422
500
  declare function renderJsonReport(result: ScanResult): string;
423
501
 
424
502
  /** Shareable Markdown report grouped by severity (highest first). */
425
- declare function renderMarkdownReport(result: ScanResult): string;
503
+ declare function renderMarkdownReport(input: ScanResult): string;
426
504
 
427
505
  /** SARIF 2.1.0 report for GitHub Code Scanning and other consumers. */
428
- declare function renderSarifReport(result: ScanResult): string;
506
+ declare function renderSarifReport(input: ScanResult): string;
429
507
  /**
430
508
  * Structural SARIF 2.1.0 schema check for the documents we emit.
431
509
  * Returns an empty array when valid; otherwise human-readable errors.
432
510
  */
433
511
  declare function validateSarif21(doc: unknown): string[];
434
512
 
435
- declare function buildSbom(cwd: string): Promise<Record<string, unknown>>;
513
+ /**
514
+ * GitHub Actions workflow commands.
515
+ * Critical and high are `error`, medium is `warning`, low and info are `notice`.
516
+ */
517
+ declare function renderGithubAnnotations(input: ScanResult): string;
518
+ /**
519
+ * JUnit XML. Medium and above are failures. Low and info are skipped.
520
+ * An empty scan is one passing testcase.
521
+ */
522
+ declare function renderJunitReport(input: ScanResult): string;
523
+ /** GitLab Code Quality report. Severities map to info, minor, major, critical, blocker. */
524
+ declare function renderGitlabCodeQuality(input: ScanResult): string;
525
+
526
+ /** Compare two scan JSON files. Off unless this is `1`. */
527
+ declare const DIFF_FLAG = "FULLSHIELD_EXPERIMENTAL_DIFF";
528
+
529
+ type DiffFinding = Finding;
530
+ interface ScanDiff {
531
+ unchanged: DiffFinding[];
532
+ added: DiffFinding[];
533
+ removed: DiffFinding[];
534
+ moved: Array<{
535
+ before: DiffFinding;
536
+ after: DiffFinding;
537
+ }>;
538
+ }
539
+ /** Compare two finding lists. Does not invent findings. */
540
+ declare function diffScans(before: readonly DiffFinding[], after: readonly DiffFinding[]): ScanDiff;
541
+ declare function parseScanDocument(text: string): DiffFinding[];
542
+ /** Human summary. Lists at most 20 added findings. */
543
+ declare function renderDiffText(diff: ScanDiff): string;
544
+
545
+ declare function buildSbom(cwd: string, options?: {
546
+ includeReach?: boolean;
547
+ }): Promise<Record<string, unknown>>;
436
548
 
437
549
  interface SbomComponent {
438
550
  name: string;
@@ -445,6 +557,8 @@ interface SbomInput {
445
557
  version: string;
446
558
  components: SbomComponent[];
447
559
  findings?: Finding[];
560
+ /** Phase 66. Off unless the caller sets it. */
561
+ includeReach?: boolean;
448
562
  }
449
563
  declare function validateCycloneDx15(doc: unknown): string[];
450
564
  declare function renderCycloneDx(input: SbomInput): Record<string, unknown>;
@@ -481,4 +595,4 @@ declare function provenanceCoverage(packages: readonly {
481
595
  version: string;
482
596
  }[], known: ReadonlyMap<string, boolean>): ProvenanceScore;
483
597
 
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 };
598
+ 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 };