fullshield 0.0.1 → 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/CHANGELOG.md +77 -0
- package/LICENSE +21 -0
- package/README.md +155 -1
- package/dist/agent.js +5737 -0
- package/dist/cli/index.js +8540 -0
- package/dist/compliance.js +126 -0
- package/dist/confirm.js +146 -0
- package/dist/dashboard.js +121 -0
- package/dist/iac.js +84 -0
- package/dist/incremental.js +13 -0
- package/dist/index.d.ts +334 -0
- package/dist/index.js +6180 -0
- package/dist/llm.js +105 -0
- package/dist/mcp.js +5831 -0
- package/dist/runtime/auto.js +1163 -0
- package/dist/runtime/express.js +229 -0
- package/dist/runtime/fastify.js +241 -0
- package/dist/runtime/index.js +216 -0
- package/dist/runtime/rasp/loader.js +14 -0
- package/dist/runtime/rasp.js +1339 -0
- package/dist/runtime/register.js +1171 -0
- package/dist/supply.js +228 -0
- package/dist/threat.js +63 -0
- package/dist/workspaces.js +23 -0
- package/package.json +85 -8
- package/rules/CONFIG-BODYLIMIT-001.md +9 -0
- package/rules/CONFIG-CORS-001.md +9 -0
- package/rules/CONFIG-DOCKER-ENV-001.md +9 -0
- package/rules/CONFIG-DOCKER-LATEST-001.md +9 -0
- package/rules/CONFIG-DOCKER-ROOT-001.md +9 -0
- package/rules/CONFIG-ERRSTACK-001.md +9 -0
- package/rules/CONFIG-HELMET-001.md +9 -0
- package/rules/CONFIG-INSTALLSCRIPT-001.md +9 -0
- package/rules/CONFIG-RATELIMIT-001.md +9 -0
- package/rules/DEPS-DEPRECATED-001.md +9 -0
- package/rules/DEPS-NOLOCK-001.md +9 -0
- package/rules/DEPS-TYPOSQUAT-001.md +9 -0
- package/rules/DEPS-VULN-001.md +9 -0
- package/rules/README.md +60 -0
- package/rules/SAST-AUTH-001.md +9 -0
- package/rules/SAST-CMDI-001.md +9 -0
- package/rules/SAST-CRYPTO-001.md +9 -0
- package/rules/SAST-EVAL-001.md +9 -0
- package/rules/SAST-JWT-001.md +9 -0
- package/rules/SAST-NOSQLI-001.md +9 -0
- package/rules/SAST-PATH-001.md +9 -0
- package/rules/SAST-PROTO-001.md +9 -0
- package/rules/SAST-REDIRECT-001.md +9 -0
- package/rules/SAST-SQLI-001.md +9 -0
- package/rules/SAST-SSRF-001.md +9 -0
- package/rules/SAST-XSS-001.md +9 -0
- package/rules/SECRET-AIKEY-001.md +9 -0
- package/rules/SECRET-AWS-001.md +9 -0
- package/rules/SECRET-DBURL-001.md +9 -0
- package/rules/SECRET-ENTROPY-001.md +9 -0
- package/rules/SECRET-ENVFILE-001.md +9 -0
- package/rules/SECRET-GITHUB-001.md +9 -0
- package/rules/SECRET-GOOGLE-001.md +9 -0
- package/rules/SECRET-JWT-001.md +9 -0
- package/rules/SECRET-PRIVKEY-001.md +9 -0
- package/rules/SECRET-SLACK-001.md +9 -0
- package/rules/SECRET-STRIPE-001.md +9 -0
- package/templates/fullshield.config.json +7 -0
- package/templates/github-watch.yml +15 -0
- package/templates/github-workflow.yml +19 -0
- package/templates/gitlab-ci.yml +11 -0
- package/templates/pre-commit.sh +11 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
declare const SEVERITIES: readonly ["info", "low", "medium", "high", "critical"];
|
|
2
|
+
type Severity = (typeof SEVERITIES)[number];
|
|
3
|
+
declare const MODULE_NAMES: readonly ["deps", "secrets", "sast", "config"];
|
|
4
|
+
type ModuleName = (typeof MODULE_NAMES)[number];
|
|
5
|
+
type Confidence = 'low' | 'medium' | 'high';
|
|
6
|
+
interface FixProposal {
|
|
7
|
+
kind: 'dependency-bump' | 'code-patch' | 'config-add';
|
|
8
|
+
/** Only safe fixes apply without --unsafe. */
|
|
9
|
+
safe: boolean;
|
|
10
|
+
/** Unified diff. */
|
|
11
|
+
diff: string;
|
|
12
|
+
}
|
|
13
|
+
interface Finding {
|
|
14
|
+
/** Stable hash of ruleId + file + normalized snippet. */
|
|
15
|
+
id: string;
|
|
16
|
+
/** e.g. "SAST-SQLI-001" */
|
|
17
|
+
ruleId: string;
|
|
18
|
+
module: ModuleName;
|
|
19
|
+
title: string;
|
|
20
|
+
severity: Severity;
|
|
21
|
+
confidence: Confidence;
|
|
22
|
+
/** e.g. "CWE-89" */
|
|
23
|
+
cwe?: string;
|
|
24
|
+
file?: string;
|
|
25
|
+
line?: number;
|
|
26
|
+
column?: number;
|
|
27
|
+
/** Secrets MUST be masked: first 4 chars + **** */
|
|
28
|
+
snippet?: string;
|
|
29
|
+
/** What's wrong, in plain English. */
|
|
30
|
+
message: string;
|
|
31
|
+
/** How to fix. */
|
|
32
|
+
remediation: string;
|
|
33
|
+
fix?: FixProposal;
|
|
34
|
+
/** OSV id, CVE, docs. */
|
|
35
|
+
references?: string[];
|
|
36
|
+
/** Cross-file taint steps (Phase 13). */
|
|
37
|
+
trace?: Array<{
|
|
38
|
+
file: string;
|
|
39
|
+
line?: number;
|
|
40
|
+
note: string;
|
|
41
|
+
}>;
|
|
42
|
+
/** Dependency reachability (Phase 13 / harden-3). */
|
|
43
|
+
reachability?: 'reachable' | 'imported-not-called' | 'reachable-via-dependency' | 'not-imported' | 'reachability-unknown';
|
|
44
|
+
/** Lockfile path from an imported direct dependency, e.g. express > body-parser > qs. */
|
|
45
|
+
dependencyPath?: string[];
|
|
46
|
+
/** Deterministic (default) vs AI-added. AI findings never fail CI. */
|
|
47
|
+
source?: 'deterministic' | 'ai';
|
|
48
|
+
aiVerdict?: 'true-positive' | 'likely-false-positive' | 'needs-review';
|
|
49
|
+
aiReasoning?: string;
|
|
50
|
+
exploitScenario?: string;
|
|
51
|
+
explanation?: string;
|
|
52
|
+
}
|
|
53
|
+
interface FindingSummary {
|
|
54
|
+
total: number;
|
|
55
|
+
bySeverity: Record<Severity, number>;
|
|
56
|
+
}
|
|
57
|
+
declare function isSeverity(value: string): value is Severity;
|
|
58
|
+
declare function isModuleName(value: string): value is ModuleName;
|
|
59
|
+
declare function severityRank(severity: Severity): number;
|
|
60
|
+
/** True when `severity` is at or above `threshold`. */
|
|
61
|
+
declare function meetsThreshold(severity: Severity, threshold: Severity): boolean;
|
|
62
|
+
/** CI gate: deterministic findings only. `source: 'ai'` never fails a build. */
|
|
63
|
+
declare function failsCi(finding: Finding, threshold: Severity): boolean;
|
|
64
|
+
/** Stable finding id: sha256 of ruleId + file + whitespace-normalized snippet. */
|
|
65
|
+
declare function makeFindingId(ruleId: string, file: string | undefined, snippet: string | undefined): string;
|
|
66
|
+
/** Sort by severity (desc), then file, line, ruleId for stable output. */
|
|
67
|
+
declare function sortFindings(findings: readonly Finding[]): Finding[];
|
|
68
|
+
declare function summarize(findings: readonly Finding[]): FindingSummary;
|
|
69
|
+
|
|
70
|
+
declare const CONFIG_FILE_NAME = "fullshield.config.json";
|
|
71
|
+
interface NotifyConfig {
|
|
72
|
+
slackWebhookEnv: string;
|
|
73
|
+
}
|
|
74
|
+
interface DepsConfig {
|
|
75
|
+
/** When true, OSV is never contacted; cache only. */
|
|
76
|
+
offline: boolean;
|
|
77
|
+
/** Cache root (contains an `osv/` directory). Relative paths are resolved from cwd. */
|
|
78
|
+
cacheDir?: string;
|
|
79
|
+
}
|
|
80
|
+
interface AiConfig {
|
|
81
|
+
/** Off by default. Also enabled by `--ai`. */
|
|
82
|
+
enabled: boolean;
|
|
83
|
+
/** Hard cap per scan (spec §16.1). */
|
|
84
|
+
maxCostUsd: number;
|
|
85
|
+
/** Highest-severity findings sent to the model first. */
|
|
86
|
+
maxFindings: number;
|
|
87
|
+
}
|
|
88
|
+
interface ResolvedConfig {
|
|
89
|
+
modules: ModuleName[];
|
|
90
|
+
failOn: Severity;
|
|
91
|
+
ignore: string[];
|
|
92
|
+
baseline: string;
|
|
93
|
+
notify: NotifyConfig;
|
|
94
|
+
deps: DepsConfig;
|
|
95
|
+
ai: AiConfig;
|
|
96
|
+
}
|
|
97
|
+
declare const DEFAULT_CONFIG: ResolvedConfig;
|
|
98
|
+
declare class ConfigError extends Error {
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Load fullshield.config.json from `configPath` (must exist) or from
|
|
102
|
+
* `<cwd>/fullshield.config.json` (optional), merged over defaults.
|
|
103
|
+
*/
|
|
104
|
+
declare function loadConfig(cwd: string, configPath?: string): Promise<ResolvedConfig>;
|
|
105
|
+
|
|
106
|
+
interface ModuleContext {
|
|
107
|
+
cwd: string;
|
|
108
|
+
/** Posix-style relative paths of every scannable file under cwd. */
|
|
109
|
+
files: string[];
|
|
110
|
+
config: ResolvedConfig;
|
|
111
|
+
}
|
|
112
|
+
interface ScanModule {
|
|
113
|
+
name: ModuleName;
|
|
114
|
+
run(ctx: ModuleContext): Promise<Finding[]>;
|
|
115
|
+
}
|
|
116
|
+
interface ScanOptions {
|
|
117
|
+
/** Directory to scan. Defaults to process.cwd(). */
|
|
118
|
+
cwd?: string;
|
|
119
|
+
/** Subset of modules to run. Defaults to config.modules. */
|
|
120
|
+
modules?: ModuleName[];
|
|
121
|
+
/** Explicit path to fullshield.config.json. */
|
|
122
|
+
configPath?: string;
|
|
123
|
+
/** Scan only files staged in git (pre-commit mode). */
|
|
124
|
+
staged?: boolean;
|
|
125
|
+
/** Cache-only OSV lookups (no network). */
|
|
126
|
+
offline?: boolean;
|
|
127
|
+
/** Write the current (post-suppression) findings to the baseline file. */
|
|
128
|
+
updateBaseline?: boolean;
|
|
129
|
+
/** Opt-in AI: `true` enables the client; a string selects a mode. */
|
|
130
|
+
ai?: boolean | 'triage' | 'explain' | 'logic';
|
|
131
|
+
/** Phase 22 incremental (requires experimentalIncremental or FULLSHIELD_EXPERIMENTAL_PLATFORM=1). */
|
|
132
|
+
changed?: boolean;
|
|
133
|
+
since?: string;
|
|
134
|
+
experimentalIncremental?: boolean;
|
|
135
|
+
changedFiles?: string[];
|
|
136
|
+
}
|
|
137
|
+
interface ScanMetadata {
|
|
138
|
+
version: string;
|
|
139
|
+
startedAt: string;
|
|
140
|
+
durationMs: number;
|
|
141
|
+
cwd: string;
|
|
142
|
+
filesScanned: number;
|
|
143
|
+
modulesRequested: ModuleName[];
|
|
144
|
+
/** Requested modules that are actually implemented and ran. */
|
|
145
|
+
modulesRun: ModuleName[];
|
|
146
|
+
}
|
|
147
|
+
interface ScanResult {
|
|
148
|
+
findings: Finding[];
|
|
149
|
+
summary: FindingSummary;
|
|
150
|
+
metadata: ScanMetadata;
|
|
151
|
+
config: ResolvedConfig;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Scanner modules register here as their phases are built.
|
|
155
|
+
* Phase 5: deps + secrets + sast + config.
|
|
156
|
+
*/
|
|
157
|
+
declare const builtinModules: readonly ScanModule[];
|
|
158
|
+
declare const IMPLEMENTED_MODULES: readonly ModuleName[];
|
|
159
|
+
/**
|
|
160
|
+
* Core scan: load config, walk files, run the requested modules that exist,
|
|
161
|
+
* then dedupe, sort, and summarize findings. `modules` is injectable so the
|
|
162
|
+
* engine's orchestration is testable independently of built-in modules.
|
|
163
|
+
*/
|
|
164
|
+
declare function runScan(options?: ScanOptions, modules?: readonly ScanModule[]): Promise<ScanResult>;
|
|
165
|
+
/** Public library API: scan with the built-in modules. */
|
|
166
|
+
declare function scan(options?: ScanOptions): Promise<ScanResult>;
|
|
167
|
+
|
|
168
|
+
interface WalkOptions {
|
|
169
|
+
cwd: string;
|
|
170
|
+
/** Extra gitignore-style patterns (from config) on top of .gitignore. */
|
|
171
|
+
ignore?: string[];
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Recursively list files under `cwd`, respecting the project's .gitignore and
|
|
175
|
+
* the config `ignore` globs. Returns sorted, posix-style relative paths.
|
|
176
|
+
* Symlinks are never followed (a scanner must not escape the scan root).
|
|
177
|
+
*/
|
|
178
|
+
declare function walkFiles(options: WalkOptions): Promise<string[]>;
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Resolve the fullshield package version by walking up from this module
|
|
182
|
+
* (works from both src/ during tests and dist/ in the built package).
|
|
183
|
+
*/
|
|
184
|
+
declare function packageVersion(): string;
|
|
185
|
+
|
|
186
|
+
interface FixOptions {
|
|
187
|
+
cwd?: string;
|
|
188
|
+
modules?: ModuleName[];
|
|
189
|
+
apply?: boolean;
|
|
190
|
+
force?: boolean;
|
|
191
|
+
unsafe?: boolean;
|
|
192
|
+
noBranch?: boolean;
|
|
193
|
+
}
|
|
194
|
+
interface FixPatch {
|
|
195
|
+
file: string;
|
|
196
|
+
ruleId: string;
|
|
197
|
+
safe: boolean;
|
|
198
|
+
diff: string;
|
|
199
|
+
}
|
|
200
|
+
interface FixResult {
|
|
201
|
+
applied: boolean;
|
|
202
|
+
reverted: boolean;
|
|
203
|
+
branch?: string;
|
|
204
|
+
patches: FixPatch[];
|
|
205
|
+
findings: Finding[];
|
|
206
|
+
}
|
|
207
|
+
declare function runFix(options?: FixOptions): Promise<FixResult>;
|
|
208
|
+
|
|
209
|
+
type AiProviderName = 'anthropic' | 'openai' | 'ollama' | 'fixture';
|
|
210
|
+
type AiMode = 'triage' | 'explain' | 'logic' | 'fix';
|
|
211
|
+
interface AiCompleteRequest {
|
|
212
|
+
mode: AiMode;
|
|
213
|
+
prompt: string;
|
|
214
|
+
model?: string;
|
|
215
|
+
}
|
|
216
|
+
interface AiCompleteResponse {
|
|
217
|
+
json: unknown;
|
|
218
|
+
costUsd: number;
|
|
219
|
+
cached: boolean;
|
|
220
|
+
redactedPrompt: string;
|
|
221
|
+
}
|
|
222
|
+
interface AiClientOptions {
|
|
223
|
+
enabled: boolean;
|
|
224
|
+
config?: AiConfig;
|
|
225
|
+
/** Injected fetch for tests. Never used when fixtures are on. */
|
|
226
|
+
fetchImpl?: typeof fetch;
|
|
227
|
+
provider?: AiProviderName;
|
|
228
|
+
apiKey?: string;
|
|
229
|
+
model?: string;
|
|
230
|
+
/** When true (default), only recorded fixtures are used — no network. */
|
|
231
|
+
useFixtures?: boolean;
|
|
232
|
+
}
|
|
233
|
+
interface AiClient {
|
|
234
|
+
enabled: boolean;
|
|
235
|
+
useFixtures: boolean;
|
|
236
|
+
networkCalls: number;
|
|
237
|
+
spentUsd: number;
|
|
238
|
+
complete(req: AiCompleteRequest): Promise<AiCompleteResponse | null>;
|
|
239
|
+
}
|
|
240
|
+
/** Live provider is opt-in via FULLSHIELD_AI_LIVE=1 (used by `npm run test:live-ai`). */
|
|
241
|
+
declare function isLiveAi(): boolean;
|
|
242
|
+
/**
|
|
243
|
+
* Triage and logic stay in-tree for fixtures/tests but are not a 0.1.0 CLI
|
|
244
|
+
* surface. Library callers must set FULLSHIELD_AI_INTERNAL=1.
|
|
245
|
+
*/
|
|
246
|
+
declare function isInternalAi(): boolean;
|
|
247
|
+
/**
|
|
248
|
+
* AI provider. Off by default. CI and the default path use recorded fixtures
|
|
249
|
+
* and never open a socket. A live provider is used only when `useFixtures`
|
|
250
|
+
* is false — tests inject `fetchImpl`; `test:live-ai` talks to local Ollama.
|
|
251
|
+
*/
|
|
252
|
+
declare function createAiClient(options: AiClientOptions): AiClient;
|
|
253
|
+
|
|
254
|
+
/** Scan one file's text with every secrets rule. Secrets are always masked. */
|
|
255
|
+
declare function scanTextForSecrets(path: string, text: string): Finding[];
|
|
256
|
+
|
|
257
|
+
interface AiFixResult {
|
|
258
|
+
accepted: boolean;
|
|
259
|
+
attempts: number;
|
|
260
|
+
diff: string;
|
|
261
|
+
file?: string;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Draft a patch for findings that have no rule-based fix. Apply in a temp
|
|
265
|
+
* copy, rescan, optionally run tests. Keep only if the finding disappears
|
|
266
|
+
* and no new findings appear. Max 2 attempts. Never writes the user's tree.
|
|
267
|
+
*/
|
|
268
|
+
declare function runAiFix(options: {
|
|
269
|
+
cwd: string;
|
|
270
|
+
client: AiClient;
|
|
271
|
+
}): Promise<AiFixResult>;
|
|
272
|
+
|
|
273
|
+
interface BaselineFile {
|
|
274
|
+
version: 1;
|
|
275
|
+
findings: Array<{
|
|
276
|
+
id: string;
|
|
277
|
+
ruleId: string;
|
|
278
|
+
file?: string;
|
|
279
|
+
line?: number;
|
|
280
|
+
}>;
|
|
281
|
+
}
|
|
282
|
+
declare function readBaseline(path: string): Promise<BaselineFile | undefined>;
|
|
283
|
+
declare function writeBaseline(path: string, findings: readonly Finding[]): Promise<void>;
|
|
284
|
+
declare function applyBaseline(findings: readonly Finding[], baseline: BaselineFile | undefined): Finding[];
|
|
285
|
+
|
|
286
|
+
declare function parseIgnore(line: string): {
|
|
287
|
+
ruleId: string;
|
|
288
|
+
reason: string;
|
|
289
|
+
} | undefined;
|
|
290
|
+
/**
|
|
291
|
+
* Drop findings whose file:line is immediately preceded by
|
|
292
|
+
* `fullshield-ignore RULE-ID: reason` (reason required). Same-line comments
|
|
293
|
+
* also count.
|
|
294
|
+
*/
|
|
295
|
+
declare function applyInlineSuppressions(findings: readonly Finding[], sources: Readonly<Record<string, string>>): Finding[];
|
|
296
|
+
|
|
297
|
+
/** Human-readable report: findings grouped by severity, highest first. */
|
|
298
|
+
declare function renderConsoleReport(result: ScanResult): string;
|
|
299
|
+
|
|
300
|
+
/** Standalone HTML report. Finding text is escaped; secrets are already masked. */
|
|
301
|
+
declare function renderHtmlReport(result: ScanResult): string;
|
|
302
|
+
|
|
303
|
+
/** Machine-readable report: full findings plus summary and scan metadata. */
|
|
304
|
+
declare function renderJsonReport(result: ScanResult): string;
|
|
305
|
+
|
|
306
|
+
/** Shareable Markdown report grouped by severity (highest first). */
|
|
307
|
+
declare function renderMarkdownReport(result: ScanResult): string;
|
|
308
|
+
|
|
309
|
+
/** SARIF 2.1.0 report for GitHub Code Scanning and other consumers. */
|
|
310
|
+
declare function renderSarifReport(result: ScanResult): string;
|
|
311
|
+
/**
|
|
312
|
+
* Structural SARIF 2.1.0 schema check for the documents we emit.
|
|
313
|
+
* Returns an empty array when valid; otherwise human-readable errors.
|
|
314
|
+
*/
|
|
315
|
+
declare function validateSarif21(doc: unknown): string[];
|
|
316
|
+
|
|
317
|
+
declare function buildSbom(cwd: string): Promise<Record<string, unknown>>;
|
|
318
|
+
|
|
319
|
+
interface SbomComponent {
|
|
320
|
+
name: string;
|
|
321
|
+
version: string;
|
|
322
|
+
license?: string;
|
|
323
|
+
purl?: string;
|
|
324
|
+
}
|
|
325
|
+
interface SbomInput {
|
|
326
|
+
name: string;
|
|
327
|
+
version: string;
|
|
328
|
+
components: SbomComponent[];
|
|
329
|
+
findings?: Finding[];
|
|
330
|
+
}
|
|
331
|
+
declare function validateCycloneDx15(doc: unknown): string[];
|
|
332
|
+
declare function renderCycloneDx(input: SbomInput): Record<string, unknown>;
|
|
333
|
+
|
|
334
|
+
export { type AiConfig, type BaselineFile, CONFIG_FILE_NAME, type Confidence, ConfigError, DEFAULT_CONFIG, type DepsConfig, type Finding, type FindingSummary, type FixOptions, type FixPatch, type FixProposal, type FixResult, IMPLEMENTED_MODULES, MODULE_NAMES, type ModuleContext, type ModuleName, type NotifyConfig, type ResolvedConfig, SEVERITIES, type SbomComponent, type SbomInput, type ScanMetadata, type ScanModule, type ScanOptions, type ScanResult, type Severity, type WalkOptions, applyBaseline, applyInlineSuppressions, buildSbom, builtinModules, createAiClient, failsCi, isInternalAi, isLiveAi, isModuleName, isSeverity, loadConfig, makeFindingId, meetsThreshold, packageVersion, parseIgnore, readBaseline, renderConsoleReport, renderCycloneDx, renderHtmlReport, renderJsonReport, renderMarkdownReport, renderSarifReport, runAiFix, runFix, runScan, scan, scanTextForSecrets, severityRank, sortFindings, summarize, validateCycloneDx15, validateSarif21, walkFiles, writeBaseline };
|