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.
@@ -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 } = 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
@@ -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(result: ScanResult): string;
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(result: ScanResult): string;
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(result: ScanResult): string;
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(result: ScanResult): string;
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 };