@securitychecks/cli 0.1.1-rc.1 → 0.2.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/dist/index.js +407 -266
- package/dist/index.js.map +1 -1
- package/dist/lib.d.ts +23 -1
- package/dist/lib.js +36 -6
- package/dist/lib.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander';
|
|
3
|
-
import
|
|
3
|
+
import pc2 from 'picocolors';
|
|
4
4
|
import { mkdir, writeFile, readFile, chmod, unlink } from 'fs/promises';
|
|
5
5
|
import { join, resolve, dirname, extname, relative } from 'path';
|
|
6
6
|
import { resolveTargetPath, getInvariantById, ALL_INVARIANTS, collect, loadConfig, DEFAULT_PATTERN_CONFIG, ARTIFACT_SCHEMA_VERSION } from '@securitychecks/collector';
|
|
@@ -3311,7 +3311,7 @@ function generateFindingId(finding) {
|
|
|
3311
3311
|
}
|
|
3312
3312
|
|
|
3313
3313
|
// src/baseline/storage.ts
|
|
3314
|
-
var CLI_VERSION3 = "0.
|
|
3314
|
+
var CLI_VERSION3 = "0.2.0";
|
|
3315
3315
|
var SCHECK_DIR = ".scheck";
|
|
3316
3316
|
var BASELINE_FILE = "baseline.json";
|
|
3317
3317
|
var WAIVER_FILE = "waivers.json";
|
|
@@ -4045,7 +4045,7 @@ function toObservation(correlation, framework) {
|
|
|
4045
4045
|
signals: correlation.compoundingEffect.signals
|
|
4046
4046
|
},
|
|
4047
4047
|
meta: {
|
|
4048
|
-
clientVersion: "0.
|
|
4048
|
+
clientVersion: "0.2.0",
|
|
4049
4049
|
requestId: randomUUID(),
|
|
4050
4050
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4051
4051
|
}
|
|
@@ -4063,7 +4063,7 @@ async function reportCorrelations(result, config, framework) {
|
|
|
4063
4063
|
correlations: observations,
|
|
4064
4064
|
summary: result.stats,
|
|
4065
4065
|
meta: {
|
|
4066
|
-
clientVersion: "0.
|
|
4066
|
+
clientVersion: "0.2.0",
|
|
4067
4067
|
framework
|
|
4068
4068
|
}
|
|
4069
4069
|
};
|
|
@@ -4075,7 +4075,7 @@ async function reportCorrelations(result, config, framework) {
|
|
|
4075
4075
|
headers: {
|
|
4076
4076
|
"Content-Type": "application/json",
|
|
4077
4077
|
...config.apiKey && { Authorization: `Bearer ${config.apiKey}` },
|
|
4078
|
-
"X-Client-Version": "0.
|
|
4078
|
+
"X-Client-Version": "0.2.0"
|
|
4079
4079
|
},
|
|
4080
4080
|
body: JSON.stringify(payload),
|
|
4081
4081
|
signal: controller.signal
|
|
@@ -4131,7 +4131,7 @@ function buildTelemetry(result, options) {
|
|
|
4131
4131
|
} : void 0,
|
|
4132
4132
|
meta: {
|
|
4133
4133
|
duration: result.duration,
|
|
4134
|
-
clientVersion: "0.
|
|
4134
|
+
clientVersion: "0.2.0",
|
|
4135
4135
|
mode: options.mode ?? (ciProvider ? "ci" : "manual"),
|
|
4136
4136
|
ciProvider
|
|
4137
4137
|
},
|
|
@@ -4920,6 +4920,35 @@ function indent(text, spaces) {
|
|
|
4920
4920
|
function escapeString(text) {
|
|
4921
4921
|
return text.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
4922
4922
|
}
|
|
4923
|
+
function getScoreGrade(score) {
|
|
4924
|
+
if (score >= 90) return "A";
|
|
4925
|
+
if (score >= 70) return "B";
|
|
4926
|
+
if (score >= 50) return "C";
|
|
4927
|
+
return "F";
|
|
4928
|
+
}
|
|
4929
|
+
function computeReadinessScore(summary) {
|
|
4930
|
+
const { total, passed, failed, byPriority } = summary;
|
|
4931
|
+
const hasP0 = byPriority.P0 > 0;
|
|
4932
|
+
if (total === 0) {
|
|
4933
|
+
return { score: 100, grade: "A", total: 0, passed: 0, failed: 0, hasP0: false };
|
|
4934
|
+
}
|
|
4935
|
+
let score = Math.round(100 * passed / total);
|
|
4936
|
+
if (hasP0 && score > 49) {
|
|
4937
|
+
score = 49;
|
|
4938
|
+
}
|
|
4939
|
+
return {
|
|
4940
|
+
score,
|
|
4941
|
+
grade: getScoreGrade(score),
|
|
4942
|
+
total,
|
|
4943
|
+
passed,
|
|
4944
|
+
failed,
|
|
4945
|
+
hasP0
|
|
4946
|
+
};
|
|
4947
|
+
}
|
|
4948
|
+
function formatScoreForCli(rs) {
|
|
4949
|
+
const gradeColor = rs.grade === "A" ? pc2.green : rs.grade === "B" ? pc2.yellow : rs.grade === "C" ? pc2.red : pc2.red;
|
|
4950
|
+
return gradeColor(`Score: ${rs.score}/100 (${rs.grade})`);
|
|
4951
|
+
}
|
|
4923
4952
|
|
|
4924
4953
|
// src/commands/run.ts
|
|
4925
4954
|
var DEFAULT_ARTIFACT_PATH = ".securitychecks/artifacts.json";
|
|
@@ -5084,14 +5113,14 @@ For air-gapped environments, contact sales for an enterprise on-premise license.
|
|
|
5084
5113
|
);
|
|
5085
5114
|
}
|
|
5086
5115
|
if (!options.quiet) {
|
|
5087
|
-
console.log(
|
|
5116
|
+
console.log(pc2.dim(`Mode: cloud evaluation (IP protected)`));
|
|
5088
5117
|
if (isLocalEndpoint(cloudBaseUrl)) {
|
|
5089
|
-
console.log(
|
|
5118
|
+
console.log(pc2.yellow("\u26A0 Using local cloud endpoint (dev/test)"));
|
|
5090
5119
|
}
|
|
5091
5120
|
}
|
|
5092
5121
|
const auditArtifact = toArtifact(artifact);
|
|
5093
5122
|
if (!options.quiet) {
|
|
5094
|
-
console.log(
|
|
5123
|
+
console.log(pc2.dim(`Evaluating via cloud API...`));
|
|
5095
5124
|
}
|
|
5096
5125
|
const cloudEvalResult = await evaluateCloud(artifact, {
|
|
5097
5126
|
apiKey: cloudApiKey,
|
|
@@ -5104,13 +5133,13 @@ For air-gapped environments, contact sales for an enterprise on-premise license.
|
|
|
5104
5133
|
let findings = cloudEvalResult.findings;
|
|
5105
5134
|
if (!options.quiet) {
|
|
5106
5135
|
console.log(
|
|
5107
|
-
|
|
5136
|
+
pc2.dim(
|
|
5108
5137
|
`Cloud: ${cloudEvalResult.stats.invariantsRun} invariants, ${cloudEvalResult.stats.findingsCount} findings in ${cloudEvalResult.stats.executionMs}ms`
|
|
5109
5138
|
)
|
|
5110
5139
|
);
|
|
5111
5140
|
if (cloudEvalResult.usage.scansRemaining !== Infinity) {
|
|
5112
5141
|
console.log(
|
|
5113
|
-
|
|
5142
|
+
pc2.dim(`Usage: ${cloudEvalResult.usage.scansUsed} scans used, ${cloudEvalResult.usage.scansRemaining} remaining`)
|
|
5114
5143
|
);
|
|
5115
5144
|
}
|
|
5116
5145
|
}
|
|
@@ -5150,7 +5179,7 @@ For air-gapped environments, contact sales for an enterprise on-premise license.
|
|
|
5150
5179
|
};
|
|
5151
5180
|
const frameworks = resolveFrameworks(artifact, artifactForPatterns);
|
|
5152
5181
|
if (!options.quiet && frameworks.length > 0) {
|
|
5153
|
-
console.log(
|
|
5182
|
+
console.log(pc2.dim(`Frameworks detected: ${frameworks.join(", ")}`));
|
|
5154
5183
|
}
|
|
5155
5184
|
try {
|
|
5156
5185
|
const localScanStart = Date.now();
|
|
@@ -5158,13 +5187,13 @@ For air-gapped environments, contact sales for an enterprise on-premise license.
|
|
|
5158
5187
|
let patternSource = "bundled";
|
|
5159
5188
|
if (options.patternsFile) {
|
|
5160
5189
|
if (!options.quiet) {
|
|
5161
|
-
console.log(
|
|
5190
|
+
console.log(pc2.dim(`Loading patterns from ${options.patternsFile}...`));
|
|
5162
5191
|
}
|
|
5163
5192
|
patterns = await loadPatternsFromFile(options.patternsFile);
|
|
5164
5193
|
patternSource = "file";
|
|
5165
5194
|
} else if (shouldUseDevPatterns()) {
|
|
5166
5195
|
if (!options.quiet) {
|
|
5167
|
-
console.log(
|
|
5196
|
+
console.log(pc2.yellow("\u26A0 Using dev test patterns (SECURITYCHECKS_DEV_PATTERNS=1)"));
|
|
5168
5197
|
}
|
|
5169
5198
|
patterns = getTestPatternsForFrameworks(frameworks);
|
|
5170
5199
|
patternSource = "dev";
|
|
@@ -5175,7 +5204,7 @@ For air-gapped environments, contact sales for an enterprise on-premise license.
|
|
|
5175
5204
|
if (patternConfig && !patternConfig.offlineMode) {
|
|
5176
5205
|
try {
|
|
5177
5206
|
if (!options.quiet) {
|
|
5178
|
-
console.log(
|
|
5207
|
+
console.log(pc2.dim(`Fetching Pro Patterns from ${patternConfig.endpoint}...`));
|
|
5179
5208
|
}
|
|
5180
5209
|
const apiPatterns = await fetchPatterns(
|
|
5181
5210
|
artifactForPatterns,
|
|
@@ -5206,7 +5235,7 @@ For air-gapped environments, contact sales for an enterprise on-premise license.
|
|
|
5206
5235
|
const sourceLabel = patternSource === "bundled" ? "" : ` (${patternSource})`;
|
|
5207
5236
|
const localScanMs = Date.now() - localScanStart;
|
|
5208
5237
|
console.log(
|
|
5209
|
-
|
|
5238
|
+
pc2.dim(`Local scan: ${stats.patternsLoaded} patterns${sourceLabel}, ${stats.matchesFound} match(es) found in ${localScanMs}ms`)
|
|
5210
5239
|
);
|
|
5211
5240
|
}
|
|
5212
5241
|
const existingKeys = new Set(
|
|
@@ -5220,11 +5249,11 @@ For air-gapped environments, contact sales for an enterprise on-premise license.
|
|
|
5220
5249
|
}
|
|
5221
5250
|
}
|
|
5222
5251
|
} else if (!options.quiet) {
|
|
5223
|
-
console.log(
|
|
5252
|
+
console.log(pc2.dim(`Local scan: No applicable patterns for detected frameworks`));
|
|
5224
5253
|
}
|
|
5225
5254
|
} catch {
|
|
5226
5255
|
if (!options.quiet) {
|
|
5227
|
-
console.log(
|
|
5256
|
+
console.log(pc2.dim(`Local scan: Pattern loading failed, using cloud findings only`));
|
|
5228
5257
|
}
|
|
5229
5258
|
} finally {
|
|
5230
5259
|
clearPatternCaches();
|
|
@@ -5249,13 +5278,15 @@ For air-gapped environments, contact sales for an enterprise on-premise license.
|
|
|
5249
5278
|
P1: findings.filter((f) => f.severity === "P1").length,
|
|
5250
5279
|
P2: findings.filter((f) => f.severity === "P2").length
|
|
5251
5280
|
};
|
|
5281
|
+
const summaryData = { total: updatedResults.length, passed, failed, waived, byPriority };
|
|
5282
|
+
const readiness = computeReadinessScore(summaryData);
|
|
5252
5283
|
return {
|
|
5253
5284
|
result: {
|
|
5254
5285
|
version: "1.0",
|
|
5255
5286
|
targetPath,
|
|
5256
5287
|
runAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5257
5288
|
duration: Date.now() - startTime,
|
|
5258
|
-
summary: {
|
|
5289
|
+
summary: { ...summaryData, score: readiness.score, grade: readiness.grade },
|
|
5259
5290
|
results: updatedResults,
|
|
5260
5291
|
artifact: auditArtifact
|
|
5261
5292
|
},
|
|
@@ -5269,19 +5300,19 @@ For air-gapped environments, contact sales for an enterprise on-premise license.
|
|
|
5269
5300
|
}
|
|
5270
5301
|
async function runSingleScan(options) {
|
|
5271
5302
|
if (!options.quiet) {
|
|
5272
|
-
console.log(
|
|
5303
|
+
console.log(pc2.bold("\n\u{1F50D} Scanning for invariants AI misses...\n"));
|
|
5273
5304
|
}
|
|
5274
5305
|
try {
|
|
5275
5306
|
let artifact;
|
|
5276
5307
|
if (options.artifact) {
|
|
5277
5308
|
if (!options.quiet) {
|
|
5278
|
-
console.log(
|
|
5309
|
+
console.log(pc2.dim(`Loading artifact from ${options.artifact}...`));
|
|
5279
5310
|
}
|
|
5280
5311
|
artifact = await loadArtifact(options.artifact);
|
|
5281
5312
|
} else {
|
|
5282
5313
|
const targetPath2 = resolveTargetPath(options.path);
|
|
5283
5314
|
if (!options.quiet) {
|
|
5284
|
-
console.log(
|
|
5315
|
+
console.log(pc2.dim(`Collecting artifacts from ${targetPath2}...`));
|
|
5285
5316
|
}
|
|
5286
5317
|
artifact = await collect({
|
|
5287
5318
|
targetPath: targetPath2,
|
|
@@ -5292,16 +5323,16 @@ async function runSingleScan(options) {
|
|
|
5292
5323
|
await mkdir(dirname(artifactPath), { recursive: true });
|
|
5293
5324
|
await writeFile(artifactPath, JSON.stringify(artifact, null, 2), "utf-8");
|
|
5294
5325
|
if (!options.quiet) {
|
|
5295
|
-
console.log(
|
|
5326
|
+
console.log(pc2.dim(`Artifact cached at ${DEFAULT_ARTIFACT_PATH}`));
|
|
5296
5327
|
}
|
|
5297
5328
|
} catch {
|
|
5298
5329
|
}
|
|
5299
5330
|
}
|
|
5300
5331
|
if (!options.quiet) {
|
|
5301
5332
|
const schemaVer = artifact.schemaVersion ?? "1.0.0";
|
|
5302
|
-
console.log(
|
|
5303
|
-
console.log(
|
|
5304
|
-
console.log(
|
|
5333
|
+
console.log(pc2.dim(`Artifact: v${artifact.version}, schema=${schemaVer}, profile=${artifact.profile}`));
|
|
5334
|
+
console.log(pc2.dim(`Codebase: ${artifact.codebase.root}`));
|
|
5335
|
+
console.log(pc2.dim(`Files: ${artifact.codebase.filesScanned}, Services: ${artifact.services.length}`));
|
|
5305
5336
|
console.log("");
|
|
5306
5337
|
}
|
|
5307
5338
|
const {
|
|
@@ -5322,7 +5353,7 @@ async function runSingleScan(options) {
|
|
|
5322
5353
|
const categorization = categorizeFindings(allFindings, baseline, waivers);
|
|
5323
5354
|
const correlationResult = correlateFindings(result.results, result.artifact);
|
|
5324
5355
|
if (!options.quiet && correlationResult.correlations.length > 0) {
|
|
5325
|
-
console.log(
|
|
5356
|
+
console.log(pc2.dim(`Correlation: ${correlationResult.stats.correlationGroups} compounding risk group(s) detected`));
|
|
5326
5357
|
}
|
|
5327
5358
|
const artifactForFrameworks = {
|
|
5328
5359
|
version: "1.0",
|
|
@@ -5361,18 +5392,18 @@ async function runSingleScan(options) {
|
|
|
5361
5392
|
if (calibrationResult.data) {
|
|
5362
5393
|
aggregateCalibration = calibrationResult.data;
|
|
5363
5394
|
if (!options.quiet && calibrationResult.data.meta.totalScansAnalyzed > 0) {
|
|
5364
|
-
console.log(
|
|
5395
|
+
console.log(pc2.dim(`Calibration: Loaded baseline from ${calibrationResult.data.meta.totalScansAnalyzed} scans${calibrationResult.fromCache ? " (cached)" : ""}`));
|
|
5365
5396
|
}
|
|
5366
5397
|
}
|
|
5367
5398
|
} catch {
|
|
5368
5399
|
}
|
|
5369
5400
|
}
|
|
5370
5401
|
if (options.sarif) {
|
|
5371
|
-
const cliVersion = "0.
|
|
5402
|
+
const cliVersion = "0.2.0";
|
|
5372
5403
|
const sarifOutput = toSarif(result, cliVersion);
|
|
5373
5404
|
await writeFile(options.sarif, JSON.stringify(sarifOutput, null, 2), "utf-8");
|
|
5374
5405
|
if (!options.quiet) {
|
|
5375
|
-
console.log(
|
|
5406
|
+
console.log(pc2.green(`\u2713 SARIF report written to ${options.sarif}`));
|
|
5376
5407
|
}
|
|
5377
5408
|
}
|
|
5378
5409
|
if (options.json) {
|
|
@@ -5415,12 +5446,12 @@ async function runSingleScan(options) {
|
|
|
5415
5446
|
const isLow = scansRemaining / total <= 0.2;
|
|
5416
5447
|
const isNthScan = scansUsed % 5 === 0;
|
|
5417
5448
|
if (isLow || isNthScan) {
|
|
5418
|
-
const color = isLow ?
|
|
5449
|
+
const color = isLow ? pc2.red : pc2.yellow;
|
|
5419
5450
|
const label = isLow ? "Low quota" : "Quota";
|
|
5420
5451
|
console.log(color(`\u26A0 ${label}: ${scansUsed} of ${total} scans used this month (${scansRemaining} remaining)`));
|
|
5421
|
-
console.log(
|
|
5452
|
+
console.log(pc2.dim(" Upgrade at https://securitychecks.ai/pricing"));
|
|
5422
5453
|
if (!isLow) {
|
|
5423
|
-
console.log(
|
|
5454
|
+
console.log(pc2.dim(" Suppress with --no-usage-banner or SECURITYCHECKS_NO_USAGE_BANNER=1"));
|
|
5424
5455
|
}
|
|
5425
5456
|
}
|
|
5426
5457
|
}
|
|
@@ -5446,13 +5477,13 @@ async function runSingleScan(options) {
|
|
|
5446
5477
|
if (options.ci) {
|
|
5447
5478
|
const exitCode = getCIExitCode(categorization);
|
|
5448
5479
|
if (exitCode !== 0) {
|
|
5449
|
-
console.log(
|
|
5480
|
+
console.log(pc2.red(`
|
|
5450
5481
|
${getCISummary(categorization)}`));
|
|
5451
|
-
console.log(
|
|
5452
|
-
console.log(
|
|
5482
|
+
console.log(pc2.dim("Use `scheck baseline --update` to baseline known issues."));
|
|
5483
|
+
console.log(pc2.dim("Use `scheck waive <findingId>` to temporarily waive issues.\n"));
|
|
5453
5484
|
process.exit(exitCode);
|
|
5454
5485
|
} else {
|
|
5455
|
-
console.log(
|
|
5486
|
+
console.log(pc2.green(`
|
|
5456
5487
|
${getCISummary(categorization)}
|
|
5457
5488
|
`));
|
|
5458
5489
|
}
|
|
@@ -5462,22 +5493,22 @@ ${getCISummary(categorization)}
|
|
|
5462
5493
|
if (options.json) {
|
|
5463
5494
|
console.error(JSON.stringify(cliError.toJSON(), null, 2));
|
|
5464
5495
|
} else {
|
|
5465
|
-
console.error(
|
|
5496
|
+
console.error(pc2.red(`
|
|
5466
5497
|
${cliError.toUserString()}
|
|
5467
5498
|
`));
|
|
5468
5499
|
const remediation = cliError.getRemediation();
|
|
5469
5500
|
if (remediation) {
|
|
5470
|
-
console.error(
|
|
5501
|
+
console.error(pc2.yellow("How to fix:"));
|
|
5471
5502
|
for (const line of remediation.split("\n")) {
|
|
5472
|
-
console.error(
|
|
5503
|
+
console.error(pc2.dim(` ${line}`));
|
|
5473
5504
|
}
|
|
5474
5505
|
console.error("");
|
|
5475
5506
|
}
|
|
5476
5507
|
if (cliError.cause) {
|
|
5477
|
-
console.error(
|
|
5508
|
+
console.error(pc2.dim(`Caused by: ${cliError.cause.message}`));
|
|
5478
5509
|
console.error("");
|
|
5479
5510
|
}
|
|
5480
|
-
console.error(
|
|
5511
|
+
console.error(pc2.dim("Need help? https://securitychecks.ai/docs/troubleshooting"));
|
|
5481
5512
|
console.error("");
|
|
5482
5513
|
}
|
|
5483
5514
|
process.exit(1);
|
|
@@ -5489,35 +5520,35 @@ function isCLIEnabled() {
|
|
|
5489
5520
|
}
|
|
5490
5521
|
async function runCommand(options) {
|
|
5491
5522
|
if (!isCLIEnabled()) {
|
|
5492
|
-
console.log(
|
|
5523
|
+
console.log(pc2.bold("\nSecurityChecks CLI is currently in private beta.\n"));
|
|
5493
5524
|
console.log("For public access, use our GitHub App instead:");
|
|
5494
|
-
console.log(
|
|
5525
|
+
console.log(pc2.cyan(" https://securitychecks.ai\n"));
|
|
5495
5526
|
console.log("Benefits of the GitHub App:");
|
|
5496
5527
|
console.log(" - No installation required");
|
|
5497
5528
|
console.log(" - Automatic PR checks");
|
|
5498
5529
|
console.log(" - Evidence directly in your PRs");
|
|
5499
5530
|
console.log(" - Free tier available\n");
|
|
5500
|
-
console.log(
|
|
5501
|
-
console.log(
|
|
5531
|
+
console.log(pc2.dim("Enterprise or beta access? Contact sales@securitychecks.ai"));
|
|
5532
|
+
console.log(pc2.dim("Set SECURITYCHECKS_CLI_ENABLED=1 if you have approved access.\n"));
|
|
5502
5533
|
process.exit(0);
|
|
5503
5534
|
}
|
|
5504
5535
|
if (options.watch) {
|
|
5505
5536
|
if (options.ci) {
|
|
5506
|
-
console.error(
|
|
5537
|
+
console.error(pc2.red("Error: --watch cannot be used with --ci mode"));
|
|
5507
5538
|
process.exit(1);
|
|
5508
5539
|
}
|
|
5509
5540
|
if (options.artifact) {
|
|
5510
|
-
console.error(
|
|
5541
|
+
console.error(pc2.red("Error: --watch cannot be used with --artifact"));
|
|
5511
5542
|
process.exit(1);
|
|
5512
5543
|
}
|
|
5513
5544
|
if (options.json) {
|
|
5514
|
-
console.error(
|
|
5545
|
+
console.error(pc2.red("Error: --watch cannot be used with --json output"));
|
|
5515
5546
|
process.exit(1);
|
|
5516
5547
|
}
|
|
5517
5548
|
const targetPath = resolveTargetPath(options.path);
|
|
5518
|
-
console.log(
|
|
5519
|
-
console.log(
|
|
5520
|
-
console.log(
|
|
5549
|
+
console.log(pc2.bold("\n\u{1F440} Watch mode enabled\n"));
|
|
5550
|
+
console.log(pc2.dim(`Watching: ${targetPath}`));
|
|
5551
|
+
console.log(pc2.dim("Press Ctrl+C to stop\n"));
|
|
5521
5552
|
await runSingleScan(options);
|
|
5522
5553
|
let runCount = 1;
|
|
5523
5554
|
const watcher = new FileWatcher({
|
|
@@ -5526,28 +5557,28 @@ async function runCommand(options) {
|
|
|
5526
5557
|
onChanged: async () => {
|
|
5527
5558
|
runCount++;
|
|
5528
5559
|
console.clear();
|
|
5529
|
-
console.log(
|
|
5560
|
+
console.log(pc2.bold(`
|
|
5530
5561
|
\u{1F440} Watch mode (run #${runCount})
|
|
5531
5562
|
`));
|
|
5532
|
-
console.log(
|
|
5533
|
-
console.log(
|
|
5563
|
+
console.log(pc2.dim(`Watching: ${targetPath}`));
|
|
5564
|
+
console.log(pc2.dim("Press Ctrl+C to stop\n"));
|
|
5534
5565
|
try {
|
|
5535
5566
|
await runSingleScan(options);
|
|
5536
5567
|
} catch {
|
|
5537
|
-
console.log(
|
|
5568
|
+
console.log(pc2.dim("\nWaiting for changes...\n"));
|
|
5538
5569
|
}
|
|
5539
5570
|
}
|
|
5540
5571
|
});
|
|
5541
5572
|
watcher.on("change", (filename) => {
|
|
5542
5573
|
if (!options.quiet) {
|
|
5543
|
-
console.log(
|
|
5574
|
+
console.log(pc2.dim(`
|
|
5544
5575
|
File changed: ${filename}`));
|
|
5545
|
-
console.log(
|
|
5576
|
+
console.log(pc2.dim("Re-running scan...\n"));
|
|
5546
5577
|
}
|
|
5547
5578
|
});
|
|
5548
5579
|
watcher.start();
|
|
5549
5580
|
process.on("SIGINT", () => {
|
|
5550
|
-
console.log(
|
|
5581
|
+
console.log(pc2.dim("\n\nStopping watch mode..."));
|
|
5551
5582
|
watcher.stop();
|
|
5552
5583
|
process.exit(0);
|
|
5553
5584
|
});
|
|
@@ -5591,19 +5622,19 @@ function displayResults(result, categorization, correlation, options, calibratio
|
|
|
5591
5622
|
}
|
|
5592
5623
|
for (const [invariantId, findings] of byInvariant) {
|
|
5593
5624
|
const severity = findings[0]?.severity;
|
|
5594
|
-
const severityColor = severity === "P0" ?
|
|
5625
|
+
const severityColor = severity === "P0" ? pc2.red : severity === "P1" ? pc2.yellow : pc2.blue;
|
|
5595
5626
|
console.log(severityColor(`[${severity}] ${invariantId}`));
|
|
5596
5627
|
console.log("");
|
|
5597
5628
|
const staffQuestion = getStaffQuestion(invariantId);
|
|
5598
5629
|
if (staffQuestion) {
|
|
5599
5630
|
console.log(` A staff engineer would ask:`);
|
|
5600
|
-
console.log(` ${
|
|
5631
|
+
console.log(` ${pc2.cyan(`"${staffQuestion}"`)}`);
|
|
5601
5632
|
console.log("");
|
|
5602
5633
|
}
|
|
5603
5634
|
for (const finding of findings) {
|
|
5604
5635
|
for (const evidence of finding.evidence) {
|
|
5605
|
-
console.log(` \u2192 ${
|
|
5606
|
-
console.log(` ${
|
|
5636
|
+
console.log(` \u2192 ${pc2.white(`${evidence.file}:${evidence.line}`)}`);
|
|
5637
|
+
console.log(` ${pc2.dim(finding.message)}`);
|
|
5607
5638
|
}
|
|
5608
5639
|
}
|
|
5609
5640
|
console.log("");
|
|
@@ -5611,46 +5642,48 @@ function displayResults(result, categorization, correlation, options, calibratio
|
|
|
5611
5642
|
}
|
|
5612
5643
|
const duration = (result.duration / 1e3).toFixed(1);
|
|
5613
5644
|
const filesScanned = result.artifact?.services?.length ?? 0;
|
|
5614
|
-
console.log(
|
|
5645
|
+
console.log(pc2.dim(`Scanned ${filesScanned} files in ${duration}s`));
|
|
5646
|
+
const readiness = computeReadinessScore(result.summary);
|
|
5647
|
+
console.log(formatScoreForCli(readiness));
|
|
5615
5648
|
console.log("");
|
|
5616
5649
|
if (stoppedEarly) {
|
|
5617
|
-
console.log(
|
|
5650
|
+
console.log(pc2.red(`\u2716 Found critical issue. Stopping early.`));
|
|
5618
5651
|
console.log("");
|
|
5619
|
-
console.log(
|
|
5620
|
-
console.log(
|
|
5652
|
+
console.log(pc2.dim(`Run \`scheck run --all\` to see all findings.`));
|
|
5653
|
+
console.log(pc2.dim(`Run \`scheck explain ${displayFindings[0]?.invariantId}\` for details.`));
|
|
5621
5654
|
} else if (totalVisible === 0) {
|
|
5622
|
-
console.log(
|
|
5655
|
+
console.log(pc2.green(`\u2713 No issues found. Ship it.`));
|
|
5623
5656
|
} else {
|
|
5624
5657
|
const issueWord = totalVisible === 1 ? "issue" : "issues";
|
|
5625
5658
|
const criticalNote = criticalCount > 0 ? ` (${criticalCount} critical)` : "";
|
|
5626
|
-
console.log(
|
|
5659
|
+
console.log(pc2.red(`\u2716 ${totalVisible} ${issueWord} require attention${criticalNote}`));
|
|
5627
5660
|
console.log("");
|
|
5628
5661
|
const firstInvariant = displayFindings[0]?.invariantId;
|
|
5629
5662
|
if (firstInvariant) {
|
|
5630
|
-
console.log(
|
|
5663
|
+
console.log(pc2.dim(`Run \`scheck explain ${firstInvariant}\` for details.`));
|
|
5631
5664
|
}
|
|
5632
5665
|
}
|
|
5633
5666
|
if (!showP2 && p2Count > 0 && !stoppedEarly) {
|
|
5634
|
-
console.log(
|
|
5667
|
+
console.log(pc2.dim(`(${p2Count} P2 finding(s) hidden. Run with --all to see them.)`));
|
|
5635
5668
|
}
|
|
5636
5669
|
if (!options.quiet) {
|
|
5637
5670
|
if (counts.baselined > 0 || counts.waived > 0) {
|
|
5638
5671
|
console.log("");
|
|
5639
5672
|
if (counts.baselined > 0) {
|
|
5640
|
-
console.log(
|
|
5673
|
+
console.log(pc2.dim(`Baselined: ${counts.baselined} finding(s) in baseline`));
|
|
5641
5674
|
}
|
|
5642
5675
|
if (counts.waived > 0) {
|
|
5643
|
-
console.log(
|
|
5676
|
+
console.log(pc2.dim(`Waived: ${counts.waived} finding(s) temporarily waived`));
|
|
5644
5677
|
}
|
|
5645
5678
|
}
|
|
5646
5679
|
}
|
|
5647
5680
|
if (correlation.correlations.length > 0 && !options.quiet && options.all) {
|
|
5648
5681
|
console.log("");
|
|
5649
|
-
console.log(
|
|
5682
|
+
console.log(pc2.bold(pc2.magenta("Compounding Risks Detected:")));
|
|
5650
5683
|
for (const corr of correlation.correlations) {
|
|
5651
5684
|
console.log(formatCorrelatedFinding(corr));
|
|
5652
5685
|
}
|
|
5653
|
-
console.log(
|
|
5686
|
+
console.log(pc2.dim(formatCorrelationStats(correlation)));
|
|
5654
5687
|
}
|
|
5655
5688
|
if (calibration?.aggregateCalibration && calibration.frameworks.length > 0 && options.all) {
|
|
5656
5689
|
const findings = {
|
|
@@ -5666,9 +5699,9 @@ function displayResults(result, categorization, correlation, options, calibratio
|
|
|
5666
5699
|
);
|
|
5667
5700
|
if (comparisonSummary) {
|
|
5668
5701
|
console.log("");
|
|
5669
|
-
console.log(
|
|
5702
|
+
console.log(pc2.bold("Framework Comparison:"));
|
|
5670
5703
|
for (const line of comparisonSummary.split("\n")) {
|
|
5671
|
-
console.log(` ${
|
|
5704
|
+
console.log(` ${pc2.dim(line)}`);
|
|
5672
5705
|
}
|
|
5673
5706
|
}
|
|
5674
5707
|
}
|
|
@@ -5677,34 +5710,34 @@ function displayResults(result, categorization, correlation, options, calibratio
|
|
|
5677
5710
|
async function explainCommand(invariantId) {
|
|
5678
5711
|
const invariant = getInvariantById(invariantId);
|
|
5679
5712
|
if (!invariant) {
|
|
5680
|
-
console.log(
|
|
5713
|
+
console.log(pc2.red(`
|
|
5681
5714
|
Unknown invariant: ${invariantId}
|
|
5682
5715
|
`));
|
|
5683
|
-
console.log(
|
|
5716
|
+
console.log(pc2.bold("Available invariants:\n"));
|
|
5684
5717
|
for (const inv of ALL_INVARIANTS) {
|
|
5685
|
-
const severityColor2 = inv.severity === "P0" ?
|
|
5718
|
+
const severityColor2 = inv.severity === "P0" ? pc2.red : inv.severity === "P1" ? pc2.yellow : pc2.blue;
|
|
5686
5719
|
console.log(` ${severityColor2(`[${inv.severity}]`)} ${inv.id}`);
|
|
5687
|
-
console.log(
|
|
5720
|
+
console.log(pc2.dim(` ${inv.name}`));
|
|
5688
5721
|
}
|
|
5689
5722
|
console.log("");
|
|
5690
5723
|
return;
|
|
5691
5724
|
}
|
|
5692
|
-
const severityColor = invariant.severity === "P0" ?
|
|
5725
|
+
const severityColor = invariant.severity === "P0" ? pc2.red : invariant.severity === "P1" ? pc2.yellow : pc2.blue;
|
|
5693
5726
|
console.log("");
|
|
5694
|
-
console.log(severityColor(
|
|
5695
|
-
console.log(
|
|
5727
|
+
console.log(severityColor(pc2.bold(`[${invariant.severity}] ${invariant.id}`)));
|
|
5728
|
+
console.log(pc2.bold(invariant.name));
|
|
5696
5729
|
console.log("");
|
|
5697
|
-
console.log(
|
|
5730
|
+
console.log(pc2.bold("Description:"));
|
|
5698
5731
|
console.log(wrapText(invariant.description, 80, 2));
|
|
5699
5732
|
console.log("");
|
|
5700
|
-
console.log(
|
|
5733
|
+
console.log(pc2.bold("Category:"));
|
|
5701
5734
|
console.log(` ${invariant.category}`);
|
|
5702
5735
|
console.log("");
|
|
5703
|
-
console.log(
|
|
5736
|
+
console.log(pc2.bold("Required Proof:"));
|
|
5704
5737
|
console.log(wrapText(invariant.requiredProof, 80, 2));
|
|
5705
5738
|
console.log("");
|
|
5706
5739
|
if (invariant.documentationUrl) {
|
|
5707
|
-
console.log(
|
|
5740
|
+
console.log(pc2.bold("Documentation:"));
|
|
5708
5741
|
console.log(` ${invariant.documentationUrl}`);
|
|
5709
5742
|
console.log("");
|
|
5710
5743
|
}
|
|
@@ -5838,7 +5871,7 @@ jobs:
|
|
|
5838
5871
|
async function installPreCommitHook(targetPath) {
|
|
5839
5872
|
const gitDir = join(targetPath, ".git");
|
|
5840
5873
|
if (!existsSync(gitDir)) {
|
|
5841
|
-
console.log(
|
|
5874
|
+
console.log(pc2.yellow("\u26A0") + " Not a git repository, skipping pre-commit hook");
|
|
5842
5875
|
return;
|
|
5843
5876
|
}
|
|
5844
5877
|
const hooksDir = join(gitDir, "hooks");
|
|
@@ -5851,33 +5884,33 @@ async function installPreCommitHook(targetPath) {
|
|
|
5851
5884
|
(fs) => fs.promises.readFile(hookPath, "utf-8")
|
|
5852
5885
|
);
|
|
5853
5886
|
if (existingHook.includes("SecurityChecks pre-commit hook")) {
|
|
5854
|
-
console.log(
|
|
5887
|
+
console.log(pc2.yellow("\u26A0") + " Pre-commit hook already installed");
|
|
5855
5888
|
return;
|
|
5856
5889
|
}
|
|
5857
|
-
console.log(
|
|
5858
|
-
console.log(
|
|
5859
|
-
console.log(
|
|
5890
|
+
console.log(pc2.yellow("\u26A0") + " Existing pre-commit hook found");
|
|
5891
|
+
console.log(pc2.dim(" To integrate SecurityChecks, add this to your hook:"));
|
|
5892
|
+
console.log(pc2.cyan(" scheck run --changed --ci"));
|
|
5860
5893
|
return;
|
|
5861
5894
|
}
|
|
5862
5895
|
await writeFile(hookPath, PRE_COMMIT_HOOK);
|
|
5863
5896
|
await chmod(hookPath, 493);
|
|
5864
|
-
console.log(
|
|
5897
|
+
console.log(pc2.green("\u2713") + " Installed pre-commit hook");
|
|
5865
5898
|
}
|
|
5866
5899
|
async function initCommand(options) {
|
|
5867
5900
|
const targetPath = resolveTargetPath(options.path);
|
|
5868
|
-
console.log(
|
|
5901
|
+
console.log(pc2.bold("\n\u{1F527} Initializing SecurityChecks for your project\n"));
|
|
5869
5902
|
try {
|
|
5870
5903
|
const scheckDir = join(targetPath, ".scheck");
|
|
5871
5904
|
if (!existsSync(scheckDir)) {
|
|
5872
5905
|
await mkdir(scheckDir, { recursive: true });
|
|
5873
|
-
console.log(
|
|
5906
|
+
console.log(pc2.green("\u2713") + " Created .scheck directory");
|
|
5874
5907
|
}
|
|
5875
5908
|
const configPath = join(scheckDir, "config.yaml");
|
|
5876
5909
|
if (!existsSync(configPath)) {
|
|
5877
5910
|
await writeFile(configPath, DEFAULT_CONFIG);
|
|
5878
|
-
console.log(
|
|
5911
|
+
console.log(pc2.green("\u2713") + " Created .scheck/config.yaml");
|
|
5879
5912
|
} else {
|
|
5880
|
-
console.log(
|
|
5913
|
+
console.log(pc2.yellow("\u26A0") + " Config already exists, skipping");
|
|
5881
5914
|
}
|
|
5882
5915
|
const baselinePath = join(scheckDir, "baseline.json");
|
|
5883
5916
|
if (!existsSync(baselinePath)) {
|
|
@@ -5888,18 +5921,18 @@ async function initCommand(options) {
|
|
|
5888
5921
|
entries: []
|
|
5889
5922
|
};
|
|
5890
5923
|
await writeFile(baselinePath, JSON.stringify(baseline, null, 2));
|
|
5891
|
-
console.log(
|
|
5924
|
+
console.log(pc2.green("\u2713") + " Created .scheck/baseline.json");
|
|
5892
5925
|
} else {
|
|
5893
|
-
console.log(
|
|
5926
|
+
console.log(pc2.yellow("\u26A0") + " Baseline already exists, skipping");
|
|
5894
5927
|
}
|
|
5895
5928
|
const workflowDir = join(targetPath, ".github", "workflows");
|
|
5896
5929
|
const workflowPath = join(workflowDir, "scheck.yml");
|
|
5897
5930
|
if (!existsSync(workflowPath)) {
|
|
5898
5931
|
await mkdir(workflowDir, { recursive: true });
|
|
5899
5932
|
await writeFile(workflowPath, GITHUB_ACTION);
|
|
5900
|
-
console.log(
|
|
5933
|
+
console.log(pc2.green("\u2713") + " Created .github/workflows/scheck.yml");
|
|
5901
5934
|
} else {
|
|
5902
|
-
console.log(
|
|
5935
|
+
console.log(pc2.yellow("\u26A0") + " GitHub Action already exists, skipping");
|
|
5903
5936
|
}
|
|
5904
5937
|
if (options.hooks) {
|
|
5905
5938
|
await installPreCommitHook(targetPath);
|
|
@@ -5913,25 +5946,25 @@ async function initCommand(options) {
|
|
|
5913
5946
|
await import('fs').then(
|
|
5914
5947
|
(fs) => fs.promises.appendFile(gitignorePath, "\n# SecurityChecks\n.scheck/cache/\n")
|
|
5915
5948
|
);
|
|
5916
|
-
console.log(
|
|
5949
|
+
console.log(pc2.green("\u2713") + " Updated .gitignore");
|
|
5917
5950
|
}
|
|
5918
5951
|
}
|
|
5919
5952
|
console.log("");
|
|
5920
|
-
console.log(
|
|
5921
|
-
console.log(
|
|
5922
|
-
console.log(
|
|
5953
|
+
console.log(pc2.green("\u2713 SecurityChecks is ready."));
|
|
5954
|
+
console.log(pc2.dim(" Catch what Copilot misses in your codebase.\n"));
|
|
5955
|
+
console.log(pc2.bold("Next steps:"));
|
|
5923
5956
|
console.log("");
|
|
5924
5957
|
console.log(" 1. Review the config at .scheck/config.yaml");
|
|
5925
5958
|
console.log(" 2. Run your first scan:");
|
|
5926
|
-
console.log(
|
|
5959
|
+
console.log(pc2.cyan(" scheck run"));
|
|
5927
5960
|
console.log("");
|
|
5928
5961
|
console.log(" 3. Fix any P0/P1 violations or add waivers");
|
|
5929
5962
|
console.log(" 4. Commit the .scheck directory");
|
|
5930
5963
|
console.log("");
|
|
5931
|
-
console.log(
|
|
5964
|
+
console.log(pc2.dim("Learn more: https://securitychecks.ai/docs"));
|
|
5932
5965
|
console.log("");
|
|
5933
5966
|
} catch (error2) {
|
|
5934
|
-
console.error(
|
|
5967
|
+
console.error(pc2.red("\nError initializing:"), error2);
|
|
5935
5968
|
process.exit(1);
|
|
5936
5969
|
}
|
|
5937
5970
|
}
|
|
@@ -6034,14 +6067,14 @@ async function showBaseline(targetPath) {
|
|
|
6034
6067
|
const baseline = await loadBaseline(targetPath);
|
|
6035
6068
|
const entries = Object.values(baseline.entries);
|
|
6036
6069
|
if (entries.length === 0) {
|
|
6037
|
-
console.log(
|
|
6038
|
-
console.log(
|
|
6070
|
+
console.log(pc2.dim("No baseline entries."));
|
|
6071
|
+
console.log(pc2.dim(`Run ${pc2.bold("scheck baseline --update")} to add current findings.`));
|
|
6039
6072
|
return;
|
|
6040
6073
|
}
|
|
6041
|
-
console.log(
|
|
6042
|
-
console.log(
|
|
6043
|
-
console.log(
|
|
6044
|
-
console.log(
|
|
6074
|
+
console.log(pc2.bold(`Baseline: ${entries.length} entries`));
|
|
6075
|
+
console.log(pc2.dim(`Schema version: ${baseline.schemaVersion}`));
|
|
6076
|
+
console.log(pc2.dim(`Last updated: ${baseline.updatedAt}`));
|
|
6077
|
+
console.log(pc2.dim(`Path: ${getBaselinePath(targetPath)}`));
|
|
6045
6078
|
console.log();
|
|
6046
6079
|
const byInvariant = /* @__PURE__ */ new Map();
|
|
6047
6080
|
for (const entry of entries) {
|
|
@@ -6050,28 +6083,28 @@ async function showBaseline(targetPath) {
|
|
|
6050
6083
|
byInvariant.set(entry.invariantId, list);
|
|
6051
6084
|
}
|
|
6052
6085
|
for (const [invariantId, invariantEntries] of byInvariant) {
|
|
6053
|
-
console.log(
|
|
6086
|
+
console.log(pc2.cyan(`${invariantId} (${invariantEntries.length})`));
|
|
6054
6087
|
for (const entry of invariantEntries.slice(0, 5)) {
|
|
6055
6088
|
const symbol = entry.symbol ? `:${entry.symbol}` : "";
|
|
6056
|
-
console.log(` ${
|
|
6089
|
+
console.log(` ${pc2.dim("\u2022")} ${entry.file}${symbol}`);
|
|
6057
6090
|
if (entry.notes) {
|
|
6058
|
-
console.log(` ${
|
|
6091
|
+
console.log(` ${pc2.dim(entry.notes)}`);
|
|
6059
6092
|
}
|
|
6060
6093
|
}
|
|
6061
6094
|
if (invariantEntries.length > 5) {
|
|
6062
|
-
console.log(
|
|
6095
|
+
console.log(pc2.dim(` ... and ${invariantEntries.length - 5} more`));
|
|
6063
6096
|
}
|
|
6064
6097
|
}
|
|
6065
6098
|
}
|
|
6066
6099
|
async function updateBaseline(targetPath, notes, skipConfirmation = false) {
|
|
6067
|
-
console.log(
|
|
6100
|
+
console.log(pc2.dim("Running audit to collect current findings..."));
|
|
6068
6101
|
const result = await audit({
|
|
6069
6102
|
targetPath
|
|
6070
6103
|
});
|
|
6071
6104
|
const baseline = await loadBaseline(targetPath);
|
|
6072
6105
|
const findings = result.results.flatMap((r) => r.findings);
|
|
6073
6106
|
if (findings.length === 0) {
|
|
6074
|
-
console.log(
|
|
6107
|
+
console.log(pc2.green("No findings to baseline."));
|
|
6075
6108
|
return;
|
|
6076
6109
|
}
|
|
6077
6110
|
const newFindings = findings.filter((f) => {
|
|
@@ -6079,11 +6112,11 @@ async function updateBaseline(targetPath, notes, skipConfirmation = false) {
|
|
|
6079
6112
|
return !(findingId2 in baseline.entries);
|
|
6080
6113
|
});
|
|
6081
6114
|
if (newFindings.length === 0) {
|
|
6082
|
-
console.log(
|
|
6115
|
+
console.log(pc2.green("All findings are already in the baseline."));
|
|
6083
6116
|
return;
|
|
6084
6117
|
}
|
|
6085
6118
|
console.log();
|
|
6086
|
-
console.log(
|
|
6119
|
+
console.log(pc2.yellow(`About to add ${newFindings.length} finding(s) to the baseline:`));
|
|
6087
6120
|
console.log();
|
|
6088
6121
|
const byInvariant = /* @__PURE__ */ new Map();
|
|
6089
6122
|
for (const finding of newFindings) {
|
|
@@ -6092,14 +6125,14 @@ async function updateBaseline(targetPath, notes, skipConfirmation = false) {
|
|
|
6092
6125
|
byInvariant.set(finding.invariantId, list);
|
|
6093
6126
|
}
|
|
6094
6127
|
for (const [invariantId, invariantFindings] of byInvariant) {
|
|
6095
|
-
console.log(
|
|
6128
|
+
console.log(pc2.cyan(` ${invariantId} (${invariantFindings.length})`));
|
|
6096
6129
|
for (const finding of invariantFindings.slice(0, 3)) {
|
|
6097
6130
|
const evidence = finding.evidence[0];
|
|
6098
6131
|
const location = evidence ? `${evidence.file}:${evidence.line}` : "unknown";
|
|
6099
|
-
console.log(
|
|
6132
|
+
console.log(pc2.dim(` \u2022 ${location}`));
|
|
6100
6133
|
}
|
|
6101
6134
|
if (invariantFindings.length > 3) {
|
|
6102
|
-
console.log(
|
|
6135
|
+
console.log(pc2.dim(` ... and ${invariantFindings.length - 3} more`));
|
|
6103
6136
|
}
|
|
6104
6137
|
}
|
|
6105
6138
|
console.log();
|
|
@@ -6108,16 +6141,16 @@ async function updateBaseline(targetPath, notes, skipConfirmation = false) {
|
|
|
6108
6141
|
`Add ${newFindings.length} finding(s) to baseline? This will suppress them in CI. [y/N] `
|
|
6109
6142
|
);
|
|
6110
6143
|
if (!confirmed) {
|
|
6111
|
-
console.log(
|
|
6144
|
+
console.log(pc2.dim("Cancelled."));
|
|
6112
6145
|
return;
|
|
6113
6146
|
}
|
|
6114
6147
|
}
|
|
6115
6148
|
const added = addToBaseline(baseline, findings, notes);
|
|
6116
6149
|
await saveBaseline(targetPath, baseline);
|
|
6117
|
-
console.log(
|
|
6118
|
-
console.log(` ${
|
|
6119
|
-
console.log(` ${
|
|
6120
|
-
console.log(` ${
|
|
6150
|
+
console.log(pc2.green(`\u2713 Baseline updated`));
|
|
6151
|
+
console.log(` ${pc2.bold(added.toString())} new entries added`);
|
|
6152
|
+
console.log(` ${pc2.dim(Object.keys(baseline.entries).length.toString())} total entries`);
|
|
6153
|
+
console.log(` ${pc2.dim(`Path: ${getBaselinePath(targetPath)}`)}`);
|
|
6121
6154
|
}
|
|
6122
6155
|
function isNonInteractive() {
|
|
6123
6156
|
return !process.stdin.isTTY || !!process.env["CI"];
|
|
@@ -6139,12 +6172,12 @@ async function pruneBaselineEntries(targetPath, staleDays) {
|
|
|
6139
6172
|
const beforeCount = Object.keys(baseline.entries).length;
|
|
6140
6173
|
const removed = pruneBaseline(baseline, staleDays);
|
|
6141
6174
|
if (removed === 0) {
|
|
6142
|
-
console.log(
|
|
6175
|
+
console.log(pc2.dim(`No entries older than ${staleDays} days.`));
|
|
6143
6176
|
return;
|
|
6144
6177
|
}
|
|
6145
6178
|
await saveBaseline(targetPath, baseline);
|
|
6146
|
-
console.log(
|
|
6147
|
-
console.log(` ${
|
|
6179
|
+
console.log(pc2.green(`\u2713 Pruned ${removed} stale entries`));
|
|
6180
|
+
console.log(` ${pc2.dim(`${beforeCount} \u2192 ${Object.keys(baseline.entries).length} entries`)}`);
|
|
6148
6181
|
}
|
|
6149
6182
|
async function waiverCommand(options) {
|
|
6150
6183
|
const targetPath = resolve(options.path ?? process.cwd());
|
|
@@ -6159,24 +6192,24 @@ async function waiverCommand(options) {
|
|
|
6159
6192
|
async function waiveCommand(findingIdOrInvariant, options) {
|
|
6160
6193
|
const targetPath = resolve(options.path ?? process.cwd());
|
|
6161
6194
|
if (!options.reason) {
|
|
6162
|
-
console.error(
|
|
6195
|
+
console.error(pc2.red("Error: --reason is required"));
|
|
6163
6196
|
console.log(
|
|
6164
|
-
|
|
6197
|
+
pc2.dim('Example: scheck waive WEBHOOK.IDEMPOTENT:abc123 --reason-key will_fix_later --reason "Fixing in sprint 42"')
|
|
6165
6198
|
);
|
|
6166
6199
|
process.exit(1);
|
|
6167
6200
|
}
|
|
6168
6201
|
let reasonKey;
|
|
6169
6202
|
if (options.reasonKey) {
|
|
6170
6203
|
if (!isValidWaiverReasonKey(options.reasonKey)) {
|
|
6171
|
-
console.error(
|
|
6172
|
-
console.log(
|
|
6204
|
+
console.error(pc2.red(`Error: Invalid --reason-key "${options.reasonKey}"`));
|
|
6205
|
+
console.log(pc2.dim(`Valid reason keys: ${WAIVER_REASON_KEYS.join(", ")}`));
|
|
6173
6206
|
process.exit(1);
|
|
6174
6207
|
}
|
|
6175
6208
|
reasonKey = options.reasonKey;
|
|
6176
6209
|
}
|
|
6177
6210
|
const expiresInDays = parseExpiration(options.expires ?? "30d");
|
|
6178
6211
|
if (expiresInDays === null) {
|
|
6179
|
-
console.error(
|
|
6212
|
+
console.error(pc2.red("Error: Invalid --expires format. Use: 7d, 30d, 90d, etc."));
|
|
6180
6213
|
process.exit(1);
|
|
6181
6214
|
}
|
|
6182
6215
|
const owner = options.owner ?? process.env["USER"] ?? process.env["USERNAME"] ?? "unknown";
|
|
@@ -6200,25 +6233,25 @@ async function showWaivers(targetPath) {
|
|
|
6200
6233
|
const waivers = await loadWaivers(targetPath);
|
|
6201
6234
|
const entries = Object.values(waivers.entries);
|
|
6202
6235
|
if (entries.length === 0) {
|
|
6203
|
-
console.log(
|
|
6236
|
+
console.log(pc2.dim("No active waivers."));
|
|
6204
6237
|
return;
|
|
6205
6238
|
}
|
|
6206
|
-
console.log(
|
|
6207
|
-
console.log(
|
|
6208
|
-
console.log(
|
|
6239
|
+
console.log(pc2.bold(`Waivers: ${entries.length} active`));
|
|
6240
|
+
console.log(pc2.dim(`Schema version: ${waivers.schemaVersion}`));
|
|
6241
|
+
console.log(pc2.dim(`Path: ${getWaiverPath(targetPath)}`));
|
|
6209
6242
|
console.log();
|
|
6210
6243
|
entries.sort((a, b) => a.expiresAt.localeCompare(b.expiresAt));
|
|
6211
6244
|
for (const entry of entries) {
|
|
6212
6245
|
const expiresIn = getExpiresInLabel(entry.expiresAt);
|
|
6213
6246
|
const isExpiringSoon = new Date(entry.expiresAt) < new Date(Date.now() + 7 * 24 * 60 * 60 * 1e3);
|
|
6214
|
-
console.log(
|
|
6215
|
-
console.log(` ${
|
|
6216
|
-
console.log(` ${
|
|
6247
|
+
console.log(pc2.cyan(entry.findingId));
|
|
6248
|
+
console.log(` ${pc2.dim("File:")} ${entry.file}${entry.symbol ? `:${entry.symbol}` : ""}`);
|
|
6249
|
+
console.log(` ${pc2.dim("Reason:")} ${entry.reason}`);
|
|
6217
6250
|
if (entry.reasonKey) {
|
|
6218
|
-
console.log(` ${
|
|
6251
|
+
console.log(` ${pc2.dim("Reason Key:")} ${entry.reasonKey}`);
|
|
6219
6252
|
}
|
|
6220
|
-
console.log(` ${
|
|
6221
|
-
console.log(` ${
|
|
6253
|
+
console.log(` ${pc2.dim("Owner:")} ${entry.owner}`);
|
|
6254
|
+
console.log(` ${pc2.dim("Expires:")} ${isExpiringSoon ? pc2.yellow(expiresIn) : expiresIn}`);
|
|
6222
6255
|
console.log();
|
|
6223
6256
|
}
|
|
6224
6257
|
}
|
|
@@ -6226,17 +6259,17 @@ async function showExpiringWaivers(targetPath, withinDays) {
|
|
|
6226
6259
|
const waivers = await loadWaivers(targetPath);
|
|
6227
6260
|
const expiring = getExpiringWaivers(waivers, withinDays);
|
|
6228
6261
|
if (expiring.length === 0) {
|
|
6229
|
-
console.log(
|
|
6262
|
+
console.log(pc2.dim(`No waivers expiring within ${withinDays} days.`));
|
|
6230
6263
|
return;
|
|
6231
6264
|
}
|
|
6232
|
-
console.log(
|
|
6265
|
+
console.log(pc2.yellow(`\u26A0 ${expiring.length} waiver(s) expiring within ${withinDays} days:`));
|
|
6233
6266
|
console.log();
|
|
6234
6267
|
for (const entry of expiring) {
|
|
6235
6268
|
const expiresIn = getExpiresInLabel(entry.expiresAt);
|
|
6236
|
-
console.log(
|
|
6237
|
-
console.log(` ${
|
|
6238
|
-
console.log(` ${
|
|
6239
|
-
console.log(` ${
|
|
6269
|
+
console.log(pc2.yellow(entry.findingId));
|
|
6270
|
+
console.log(` ${pc2.dim("File:")} ${entry.file}`);
|
|
6271
|
+
console.log(` ${pc2.dim("Owner:")} ${entry.owner}`);
|
|
6272
|
+
console.log(` ${pc2.dim("Expires:")} ${expiresIn}`);
|
|
6240
6273
|
console.log();
|
|
6241
6274
|
}
|
|
6242
6275
|
}
|
|
@@ -6245,43 +6278,43 @@ async function pruneWaivers(targetPath) {
|
|
|
6245
6278
|
const beforeCount = Object.keys(waivers.entries).length;
|
|
6246
6279
|
const removed = pruneExpiredWaivers(waivers);
|
|
6247
6280
|
if (removed === 0) {
|
|
6248
|
-
console.log(
|
|
6281
|
+
console.log(pc2.dim("No expired waivers to remove."));
|
|
6249
6282
|
return;
|
|
6250
6283
|
}
|
|
6251
6284
|
await saveWaivers(targetPath, waivers);
|
|
6252
|
-
console.log(
|
|
6253
|
-
console.log(` ${
|
|
6285
|
+
console.log(pc2.green(`\u2713 Removed ${removed} expired waivers`));
|
|
6286
|
+
console.log(` ${pc2.dim(`${beforeCount} \u2192 ${Object.keys(waivers.entries).length} waivers`)}`);
|
|
6254
6287
|
}
|
|
6255
6288
|
async function waiveByFindingId(targetPath, findingId2, options) {
|
|
6256
|
-
console.log(
|
|
6289
|
+
console.log(pc2.dim("Finding matching issue..."));
|
|
6257
6290
|
const result = await audit({ targetPath });
|
|
6258
6291
|
const findings = result.results.flatMap((r) => r.findings);
|
|
6259
6292
|
const normalizedId = findingId2.split(":").slice(0, 2).join(":");
|
|
6260
6293
|
const matching = findings.find((f) => generateFindingId(f) === normalizedId);
|
|
6261
6294
|
if (!matching) {
|
|
6262
|
-
console.error(
|
|
6263
|
-
console.log(
|
|
6295
|
+
console.error(pc2.red(`Error: No finding found matching ${findingId2}`));
|
|
6296
|
+
console.log(pc2.dim("Run `scheck run` to see current findings."));
|
|
6264
6297
|
process.exit(1);
|
|
6265
6298
|
}
|
|
6266
6299
|
const waivers = await loadWaivers(targetPath);
|
|
6267
6300
|
const entry = addWaiver(waivers, matching, options);
|
|
6268
6301
|
await saveWaivers(targetPath, waivers);
|
|
6269
|
-
console.log(
|
|
6270
|
-
console.log(` ${
|
|
6271
|
-
console.log(` ${
|
|
6302
|
+
console.log(pc2.green(`\u2713 Waiver created`));
|
|
6303
|
+
console.log(` ${pc2.dim("Finding:")} ${entry.findingId}`);
|
|
6304
|
+
console.log(` ${pc2.dim("Reason:")} ${entry.reason}`);
|
|
6272
6305
|
if (entry.reasonKey) {
|
|
6273
|
-
console.log(` ${
|
|
6306
|
+
console.log(` ${pc2.dim("Reason Key:")} ${entry.reasonKey}`);
|
|
6274
6307
|
}
|
|
6275
|
-
console.log(` ${
|
|
6308
|
+
console.log(` ${pc2.dim("Expires:")} ${getExpiresInLabel(entry.expiresAt)}`);
|
|
6276
6309
|
}
|
|
6277
6310
|
async function waiveByInvariant(targetPath, invariantId, options) {
|
|
6278
|
-
console.log(
|
|
6311
|
+
console.log(pc2.dim("Finding matching issues..."));
|
|
6279
6312
|
const result = await audit({ targetPath });
|
|
6280
6313
|
const findings = result.results.flatMap((r) => r.findings);
|
|
6281
6314
|
const matching = findings.filter((f) => f.invariantId === invariantId);
|
|
6282
6315
|
if (matching.length === 0) {
|
|
6283
|
-
console.error(
|
|
6284
|
-
console.log(
|
|
6316
|
+
console.error(pc2.red(`Error: No findings found for ${invariantId}`));
|
|
6317
|
+
console.log(pc2.dim("Run `scheck run` to see current findings."));
|
|
6285
6318
|
process.exit(1);
|
|
6286
6319
|
}
|
|
6287
6320
|
const waivers = await loadWaivers(targetPath);
|
|
@@ -6289,12 +6322,12 @@ async function waiveByInvariant(targetPath, invariantId, options) {
|
|
|
6289
6322
|
addWaiver(waivers, finding, options);
|
|
6290
6323
|
}
|
|
6291
6324
|
await saveWaivers(targetPath, waivers);
|
|
6292
|
-
console.log(
|
|
6293
|
-
console.log(` ${
|
|
6325
|
+
console.log(pc2.green(`\u2713 Created ${matching.length} waiver(s) for ${invariantId}`));
|
|
6326
|
+
console.log(` ${pc2.dim("Reason:")} ${options.reason}`);
|
|
6294
6327
|
if (options.reasonKey) {
|
|
6295
|
-
console.log(` ${
|
|
6328
|
+
console.log(` ${pc2.dim("Reason Key:")} ${options.reasonKey}`);
|
|
6296
6329
|
}
|
|
6297
|
-
console.log(` ${
|
|
6330
|
+
console.log(` ${pc2.dim("Expires:")} in ${options.expiresInDays} days`);
|
|
6298
6331
|
}
|
|
6299
6332
|
function parseExpiration(value) {
|
|
6300
6333
|
const match = value.match(/^(\d+)d$/i);
|
|
@@ -6336,7 +6369,7 @@ var CloudApiClient = class {
|
|
|
6336
6369
|
const headers = {
|
|
6337
6370
|
Authorization: `Bearer ${this.apiKey}`,
|
|
6338
6371
|
"Content-Type": "application/json",
|
|
6339
|
-
"User-Agent": `scheck-cli/${"0.
|
|
6372
|
+
"User-Agent": `scheck-cli/${"0.2.0"}`
|
|
6340
6373
|
};
|
|
6341
6374
|
const bypassSecret = process.env["VERCEL_AUTOMATION_BYPASS_SECRET"];
|
|
6342
6375
|
if (bypassSecret) {
|
|
@@ -6481,8 +6514,8 @@ function createCloudClient(apiUrl, apiKey) {
|
|
|
6481
6514
|
// src/commands/login.ts
|
|
6482
6515
|
async function promptForApiKey() {
|
|
6483
6516
|
const rl = readline.createInterface({ input: stdin, output: stdout });
|
|
6484
|
-
console.log(
|
|
6485
|
-
console.log(
|
|
6517
|
+
console.log(pc2.bold("\nSecurityChecks Cloud Login\n"));
|
|
6518
|
+
console.log(pc2.dim("Get your API key at: https://securitychecks.ai/dashboard/settings/api-keys\n"));
|
|
6486
6519
|
try {
|
|
6487
6520
|
const apiKey = await rl.question("API Key: ");
|
|
6488
6521
|
return apiKey.trim();
|
|
@@ -6495,7 +6528,7 @@ async function loginCommand(options) {
|
|
|
6495
6528
|
const config = await loadCloudConfig();
|
|
6496
6529
|
if (options.check) {
|
|
6497
6530
|
if (!config.apiKey) {
|
|
6498
|
-
console.log(
|
|
6531
|
+
console.log(pc2.yellow("Not logged in to SecurityChecks cloud.\n"));
|
|
6499
6532
|
console.log("Run `scheck login` to authenticate.");
|
|
6500
6533
|
return;
|
|
6501
6534
|
}
|
|
@@ -6504,18 +6537,18 @@ async function loginCommand(options) {
|
|
|
6504
6537
|
try {
|
|
6505
6538
|
const result2 = await client2.validateKey();
|
|
6506
6539
|
if (result2.valid) {
|
|
6507
|
-
console.log(
|
|
6540
|
+
console.log(pc2.green("\u2713 Logged in to SecurityChecks cloud\n"));
|
|
6508
6541
|
console.log(formatConfig(config));
|
|
6509
6542
|
} else {
|
|
6510
|
-
console.log(
|
|
6543
|
+
console.log(pc2.yellow("API key is invalid or expired.\n"));
|
|
6511
6544
|
console.log("Run `scheck login` to re-authenticate.");
|
|
6512
6545
|
}
|
|
6513
6546
|
} catch (error2) {
|
|
6514
6547
|
if (error2 instanceof CLIError) {
|
|
6515
|
-
console.log(
|
|
6548
|
+
console.log(pc2.red(`\u2717 ${error2.message}
|
|
6516
6549
|
`));
|
|
6517
6550
|
} else {
|
|
6518
|
-
console.log(
|
|
6551
|
+
console.log(pc2.red("\u2717 Failed to validate API key\n"));
|
|
6519
6552
|
}
|
|
6520
6553
|
}
|
|
6521
6554
|
return;
|
|
@@ -6525,7 +6558,7 @@ async function loginCommand(options) {
|
|
|
6525
6558
|
apiKey = await promptForApiKey();
|
|
6526
6559
|
}
|
|
6527
6560
|
if (!apiKey) {
|
|
6528
|
-
console.log(
|
|
6561
|
+
console.log(pc2.red("\n\u2717 No API key provided.\n"));
|
|
6529
6562
|
process.exit(1);
|
|
6530
6563
|
}
|
|
6531
6564
|
if (!isValidApiKey(apiKey)) {
|
|
@@ -6535,7 +6568,7 @@ async function loginCommand(options) {
|
|
|
6535
6568
|
);
|
|
6536
6569
|
}
|
|
6537
6570
|
const apiUrl = options.apiUrl || getApiUrl(config);
|
|
6538
|
-
console.log(
|
|
6571
|
+
console.log(pc2.dim(`
|
|
6539
6572
|
Connecting to ${apiUrl}...`));
|
|
6540
6573
|
const client = createCloudClient(apiUrl, apiKey);
|
|
6541
6574
|
const result = await client.validateKey();
|
|
@@ -6552,7 +6585,7 @@ Connecting to ${apiUrl}...`));
|
|
|
6552
6585
|
cloudEnabled: true,
|
|
6553
6586
|
lastSync: (/* @__PURE__ */ new Date()).toISOString()
|
|
6554
6587
|
});
|
|
6555
|
-
console.log(
|
|
6588
|
+
console.log(pc2.green("\n\u2713 Successfully logged in!\n"));
|
|
6556
6589
|
console.log(` Email: ${userInfo.email}`);
|
|
6557
6590
|
if (userInfo.name) {
|
|
6558
6591
|
console.log(` Name: ${userInfo.name}`);
|
|
@@ -6561,24 +6594,24 @@ Connecting to ${apiUrl}...`));
|
|
|
6561
6594
|
console.log(` Organization: ${userInfo.organizations[0]?.name}`);
|
|
6562
6595
|
}
|
|
6563
6596
|
console.log("");
|
|
6564
|
-
console.log(
|
|
6565
|
-
console.log(
|
|
6597
|
+
console.log(pc2.dim("Cloud mode is now enabled by default."));
|
|
6598
|
+
console.log(pc2.dim("Run `scheck run --cloud` to sync findings."));
|
|
6566
6599
|
console.log("");
|
|
6567
6600
|
} catch (error2) {
|
|
6568
6601
|
if (error2 instanceof CLIError) {
|
|
6569
|
-
console.error(
|
|
6602
|
+
console.error(pc2.red(`
|
|
6570
6603
|
\u2717 ${error2.message}
|
|
6571
6604
|
`));
|
|
6572
6605
|
const remediation = error2.getRemediation();
|
|
6573
6606
|
if (remediation) {
|
|
6574
|
-
console.error(
|
|
6607
|
+
console.error(pc2.yellow("How to fix:"));
|
|
6575
6608
|
for (const line of remediation.split("\n")) {
|
|
6576
|
-
console.error(
|
|
6609
|
+
console.error(pc2.dim(` ${line}`));
|
|
6577
6610
|
}
|
|
6578
6611
|
console.error("");
|
|
6579
6612
|
}
|
|
6580
6613
|
} else {
|
|
6581
|
-
console.error(
|
|
6614
|
+
console.error(pc2.red(`
|
|
6582
6615
|
\u2717 Login failed: ${error2 instanceof Error ? error2.message : "Unknown error"}
|
|
6583
6616
|
`));
|
|
6584
6617
|
}
|
|
@@ -6588,7 +6621,7 @@ Connecting to ${apiUrl}...`));
|
|
|
6588
6621
|
async function logoutCommand() {
|
|
6589
6622
|
const config = await loadCloudConfig();
|
|
6590
6623
|
if (!config.apiKey) {
|
|
6591
|
-
console.log(
|
|
6624
|
+
console.log(pc2.yellow("Not logged in to SecurityChecks cloud.\n"));
|
|
6592
6625
|
return;
|
|
6593
6626
|
}
|
|
6594
6627
|
await updateCloudConfig({
|
|
@@ -6598,8 +6631,8 @@ async function logoutCommand() {
|
|
|
6598
6631
|
cloudEnabled: false,
|
|
6599
6632
|
lastSync: void 0
|
|
6600
6633
|
});
|
|
6601
|
-
console.log(
|
|
6602
|
-
console.log(
|
|
6634
|
+
console.log(pc2.green("\u2713 Successfully logged out.\n"));
|
|
6635
|
+
console.log(pc2.dim("Cloud mode has been disabled."));
|
|
6603
6636
|
console.log("");
|
|
6604
6637
|
}
|
|
6605
6638
|
var ALLOWED_KEYS = ["project", "apiUrl", "cloudEnabled"];
|
|
@@ -6613,13 +6646,13 @@ async function configCommand(options) {
|
|
|
6613
6646
|
console.log("");
|
|
6614
6647
|
console.log(formatConfig(config));
|
|
6615
6648
|
console.log("");
|
|
6616
|
-
console.log(
|
|
6649
|
+
console.log(pc2.dim(`Config file: ${CONFIG_FILE}`));
|
|
6617
6650
|
console.log("");
|
|
6618
6651
|
return;
|
|
6619
6652
|
}
|
|
6620
6653
|
if (options.clear) {
|
|
6621
6654
|
await clearCloudConfig();
|
|
6622
|
-
console.log(
|
|
6655
|
+
console.log(pc2.green("\u2713 Configuration cleared.\n"));
|
|
6623
6656
|
return;
|
|
6624
6657
|
}
|
|
6625
6658
|
if (options.set) {
|
|
@@ -6642,7 +6675,7 @@ async function configCommand(options) {
|
|
|
6642
6675
|
parsedValue = value === "true" || value === "1";
|
|
6643
6676
|
}
|
|
6644
6677
|
await updateCloudConfig({ [key]: parsedValue });
|
|
6645
|
-
console.log(
|
|
6678
|
+
console.log(pc2.green(`\u2713 Set ${key} = ${parsedValue}
|
|
6646
6679
|
`));
|
|
6647
6680
|
return;
|
|
6648
6681
|
}
|
|
@@ -6655,40 +6688,40 @@ async function configCommand(options) {
|
|
|
6655
6688
|
);
|
|
6656
6689
|
}
|
|
6657
6690
|
await updateCloudConfig({ [key]: void 0 });
|
|
6658
|
-
console.log(
|
|
6691
|
+
console.log(pc2.green(`\u2713 Unset ${key}
|
|
6659
6692
|
`));
|
|
6660
6693
|
return;
|
|
6661
6694
|
}
|
|
6662
6695
|
if (options.project !== void 0) {
|
|
6663
6696
|
await updateCloudConfig({ project: options.project });
|
|
6664
|
-
console.log(
|
|
6697
|
+
console.log(pc2.green(`\u2713 Set project = ${options.project}
|
|
6665
6698
|
`));
|
|
6666
6699
|
}
|
|
6667
6700
|
if (options.apiUrl !== void 0) {
|
|
6668
6701
|
await updateCloudConfig({ apiUrl: options.apiUrl });
|
|
6669
|
-
console.log(
|
|
6702
|
+
console.log(pc2.green(`\u2713 Set apiUrl = ${options.apiUrl}
|
|
6670
6703
|
`));
|
|
6671
6704
|
}
|
|
6672
6705
|
if (options.cloudEnabled !== void 0) {
|
|
6673
6706
|
await updateCloudConfig({ cloudEnabled: options.cloudEnabled });
|
|
6674
|
-
console.log(
|
|
6707
|
+
console.log(pc2.green(`\u2713 Set cloudEnabled = ${options.cloudEnabled}
|
|
6675
6708
|
`));
|
|
6676
6709
|
}
|
|
6677
6710
|
} catch (error2) {
|
|
6678
6711
|
if (error2 instanceof CLIError) {
|
|
6679
|
-
console.error(
|
|
6712
|
+
console.error(pc2.red(`
|
|
6680
6713
|
\u2717 ${error2.message}
|
|
6681
6714
|
`));
|
|
6682
6715
|
const remediation = error2.getRemediation();
|
|
6683
6716
|
if (remediation) {
|
|
6684
|
-
console.error(
|
|
6717
|
+
console.error(pc2.yellow("How to fix:"));
|
|
6685
6718
|
for (const line of remediation.split("\n")) {
|
|
6686
|
-
console.error(
|
|
6719
|
+
console.error(pc2.dim(` ${line}`));
|
|
6687
6720
|
}
|
|
6688
6721
|
console.error("");
|
|
6689
6722
|
}
|
|
6690
6723
|
} else {
|
|
6691
|
-
console.error(
|
|
6724
|
+
console.error(pc2.red(`
|
|
6692
6725
|
\u2717 Config failed: ${error2 instanceof Error ? error2.message : "Unknown error"}
|
|
6693
6726
|
`));
|
|
6694
6727
|
}
|
|
@@ -6750,14 +6783,14 @@ async function syncCommand(options) {
|
|
|
6750
6783
|
);
|
|
6751
6784
|
}
|
|
6752
6785
|
const findingsPath = options.findings || DEFAULT_FINDINGS_PATH;
|
|
6753
|
-
console.log(
|
|
6786
|
+
console.log(pc2.dim(`Loading results from ${findingsPath}...`));
|
|
6754
6787
|
const results = await loadResults(findingsPath);
|
|
6755
6788
|
const allFindings = results.results.flatMap((r) => r.findings);
|
|
6756
6789
|
const gitInfo = await detectGitInfo(results.targetPath);
|
|
6757
6790
|
const branch = options.branch || gitInfo.branch;
|
|
6758
6791
|
const commitSha = options.commit || gitInfo.commit;
|
|
6759
6792
|
console.log("");
|
|
6760
|
-
console.log(
|
|
6793
|
+
console.log(pc2.bold("Sync Summary:"));
|
|
6761
6794
|
console.log(` Project: ${projectSlug}`);
|
|
6762
6795
|
console.log(` Branch: ${branch || "(none)"}`);
|
|
6763
6796
|
console.log(` Commit: ${commitSha ? commitSha.substring(0, 8) : "(none)"}`);
|
|
@@ -6765,24 +6798,24 @@ async function syncCommand(options) {
|
|
|
6765
6798
|
console.log(` P0: ${results.summary.byPriority.P0}, P1: ${results.summary.byPriority.P1}, P2: ${results.summary.byPriority.P2}`);
|
|
6766
6799
|
console.log("");
|
|
6767
6800
|
if (options.dryRun) {
|
|
6768
|
-
console.log(
|
|
6801
|
+
console.log(pc2.yellow("Dry run - no changes made.\n"));
|
|
6769
6802
|
return;
|
|
6770
6803
|
}
|
|
6771
6804
|
const apiUrl = getApiUrl(config);
|
|
6772
6805
|
const client = createCloudClient(apiUrl, apiKey);
|
|
6773
|
-
console.log(
|
|
6806
|
+
console.log(pc2.dim("Creating scan..."));
|
|
6774
6807
|
const scan = await client.createScan({
|
|
6775
6808
|
projectSlug,
|
|
6776
6809
|
branch,
|
|
6777
6810
|
commitSha
|
|
6778
6811
|
});
|
|
6779
|
-
console.log(
|
|
6812
|
+
console.log(pc2.dim(`Scan created: ${scan.id}`));
|
|
6780
6813
|
if (allFindings.length > 0) {
|
|
6781
|
-
console.log(
|
|
6814
|
+
console.log(pc2.dim("Submitting findings..."));
|
|
6782
6815
|
const submitResult = await client.submitFindings(scan.id, allFindings);
|
|
6783
|
-
console.log(
|
|
6816
|
+
console.log(pc2.dim(`Created: ${submitResult.created}, Updated: ${submitResult.updated}`));
|
|
6784
6817
|
}
|
|
6785
|
-
console.log(
|
|
6818
|
+
console.log(pc2.dim("Completing scan..."));
|
|
6786
6819
|
await client.updateScan(scan.id, {
|
|
6787
6820
|
status: "COMPLETED",
|
|
6788
6821
|
summary: {
|
|
@@ -6793,24 +6826,24 @@ async function syncCommand(options) {
|
|
|
6793
6826
|
duration: results.duration
|
|
6794
6827
|
});
|
|
6795
6828
|
await updateCloudConfig({ lastSync: (/* @__PURE__ */ new Date()).toISOString() });
|
|
6796
|
-
console.log(
|
|
6797
|
-
console.log(
|
|
6829
|
+
console.log(pc2.green("\n\u2713 Sync complete!\n"));
|
|
6830
|
+
console.log(pc2.dim(`View at: https://securitychecks.ai/dashboard/scans/${scan.id}`));
|
|
6798
6831
|
console.log("");
|
|
6799
6832
|
} catch (error2) {
|
|
6800
6833
|
if (error2 instanceof CLIError) {
|
|
6801
|
-
console.error(
|
|
6834
|
+
console.error(pc2.red(`
|
|
6802
6835
|
\u2717 ${error2.message}
|
|
6803
6836
|
`));
|
|
6804
6837
|
const remediation = error2.getRemediation();
|
|
6805
6838
|
if (remediation) {
|
|
6806
|
-
console.error(
|
|
6839
|
+
console.error(pc2.yellow("How to fix:"));
|
|
6807
6840
|
for (const line of remediation.split("\n")) {
|
|
6808
|
-
console.error(
|
|
6841
|
+
console.error(pc2.dim(` ${line}`));
|
|
6809
6842
|
}
|
|
6810
6843
|
console.error("");
|
|
6811
6844
|
}
|
|
6812
6845
|
} else {
|
|
6813
|
-
console.error(
|
|
6846
|
+
console.error(pc2.red(`
|
|
6814
6847
|
\u2717 Sync failed: ${error2 instanceof Error ? error2.message : "Unknown error"}
|
|
6815
6848
|
`));
|
|
6816
6849
|
}
|
|
@@ -6857,7 +6890,7 @@ async function hooksCommand(options) {
|
|
|
6857
6890
|
const targetPath = resolveTargetPath(options.path);
|
|
6858
6891
|
const gitDir = join(targetPath, ".git");
|
|
6859
6892
|
if (!existsSync(gitDir)) {
|
|
6860
|
-
console.error(
|
|
6893
|
+
console.error(pc2.red("Error:") + " Not a git repository");
|
|
6861
6894
|
process.exit(1);
|
|
6862
6895
|
}
|
|
6863
6896
|
const hooksDir = join(gitDir, "hooks");
|
|
@@ -6866,19 +6899,19 @@ async function hooksCommand(options) {
|
|
|
6866
6899
|
options.show = true;
|
|
6867
6900
|
}
|
|
6868
6901
|
if (options.show) {
|
|
6869
|
-
console.log(
|
|
6902
|
+
console.log(pc2.bold("\nSecurityChecks Git Hooks\n"));
|
|
6870
6903
|
if (existsSync(hookPath)) {
|
|
6871
6904
|
const hookContent = await readFile(hookPath, "utf-8");
|
|
6872
6905
|
if (hookContent.includes("SecurityChecks pre-commit hook")) {
|
|
6873
|
-
console.log(
|
|
6874
|
-
console.log(
|
|
6906
|
+
console.log(pc2.green("\u2713") + " Pre-commit hook is installed");
|
|
6907
|
+
console.log(pc2.dim(` Location: ${hookPath}`));
|
|
6875
6908
|
} else {
|
|
6876
|
-
console.log(
|
|
6877
|
-
console.log(
|
|
6909
|
+
console.log(pc2.yellow("\u26A0") + " Pre-commit hook exists but is not SecurityChecks");
|
|
6910
|
+
console.log(pc2.dim(" Use --install to add SecurityChecks to your workflow"));
|
|
6878
6911
|
}
|
|
6879
6912
|
} else {
|
|
6880
|
-
console.log(
|
|
6881
|
-
console.log(
|
|
6913
|
+
console.log(pc2.dim("\u25CB") + " Pre-commit hook is not installed");
|
|
6914
|
+
console.log(pc2.dim(" Use --install to install it"));
|
|
6882
6915
|
}
|
|
6883
6916
|
console.log("");
|
|
6884
6917
|
return;
|
|
@@ -6890,36 +6923,36 @@ async function hooksCommand(options) {
|
|
|
6890
6923
|
if (existsSync(hookPath)) {
|
|
6891
6924
|
const existingHook = await readFile(hookPath, "utf-8");
|
|
6892
6925
|
if (existingHook.includes("SecurityChecks pre-commit hook")) {
|
|
6893
|
-
console.log(
|
|
6926
|
+
console.log(pc2.yellow("\u26A0") + " Pre-commit hook already installed");
|
|
6894
6927
|
return;
|
|
6895
6928
|
}
|
|
6896
|
-
console.log(
|
|
6897
|
-
console.log(
|
|
6898
|
-
console.log(
|
|
6929
|
+
console.log(pc2.yellow("\u26A0") + " Existing pre-commit hook found");
|
|
6930
|
+
console.log(pc2.dim(" To manually integrate, add this to your hook:"));
|
|
6931
|
+
console.log(pc2.cyan(" scheck run --changed --ci"));
|
|
6899
6932
|
console.log("");
|
|
6900
|
-
console.log(
|
|
6933
|
+
console.log(pc2.dim(" Or use --uninstall first to replace the existing hook"));
|
|
6901
6934
|
return;
|
|
6902
6935
|
}
|
|
6903
6936
|
await writeFile(hookPath, PRE_COMMIT_HOOK2);
|
|
6904
6937
|
await chmod(hookPath, 493);
|
|
6905
|
-
console.log(
|
|
6906
|
-
console.log(
|
|
6907
|
-
console.log(
|
|
6938
|
+
console.log(pc2.green("\u2713") + " Installed pre-commit hook");
|
|
6939
|
+
console.log(pc2.dim(" SecurityChecks will run on staged files before each commit"));
|
|
6940
|
+
console.log(pc2.dim(" To skip: git commit --no-verify"));
|
|
6908
6941
|
return;
|
|
6909
6942
|
}
|
|
6910
6943
|
if (options.uninstall) {
|
|
6911
6944
|
if (!existsSync(hookPath)) {
|
|
6912
|
-
console.log(
|
|
6945
|
+
console.log(pc2.yellow("\u26A0") + " No pre-commit hook found");
|
|
6913
6946
|
return;
|
|
6914
6947
|
}
|
|
6915
6948
|
const hookContent = await readFile(hookPath, "utf-8");
|
|
6916
6949
|
if (!hookContent.includes("SecurityChecks pre-commit hook")) {
|
|
6917
|
-
console.log(
|
|
6918
|
-
console.log(
|
|
6950
|
+
console.log(pc2.yellow("\u26A0") + " Pre-commit hook is not a SecurityChecks hook");
|
|
6951
|
+
console.log(pc2.dim(" Not removing to avoid breaking your existing hook"));
|
|
6919
6952
|
return;
|
|
6920
6953
|
}
|
|
6921
6954
|
await unlink(hookPath);
|
|
6922
|
-
console.log(
|
|
6955
|
+
console.log(pc2.green("\u2713") + " Uninstalled pre-commit hook");
|
|
6923
6956
|
return;
|
|
6924
6957
|
}
|
|
6925
6958
|
}
|
|
@@ -6991,20 +7024,20 @@ var VERDICT_MAP = {
|
|
|
6991
7024
|
async function feedbackCommand(invariantIdOrFindingId, options) {
|
|
6992
7025
|
const verdict = VERDICT_MAP[options.verdict];
|
|
6993
7026
|
if (!verdict) {
|
|
6994
|
-
console.error(
|
|
6995
|
-
console.log(
|
|
7027
|
+
console.error(pc2.red(`Error: Invalid verdict "${options.verdict}"`));
|
|
7028
|
+
console.log(pc2.dim("Valid values: tp, fp, true_positive, false_positive"));
|
|
6996
7029
|
process.exit(1);
|
|
6997
7030
|
}
|
|
6998
7031
|
if (options.reason && !isValidReason(options.reason)) {
|
|
6999
|
-
console.error(
|
|
7000
|
-
console.log(
|
|
7032
|
+
console.error(pc2.red(`Error: Invalid reason "${options.reason}"`));
|
|
7033
|
+
console.log(pc2.dim(`Valid reasons: ${VALID_REASONS.join(", ")}`));
|
|
7001
7034
|
process.exit(1);
|
|
7002
7035
|
}
|
|
7003
7036
|
const reason = options.reason;
|
|
7004
7037
|
const invariantId = invariantIdOrFindingId.includes(":") ? invariantIdOrFindingId.split(":").slice(0, -1).join(":") : invariantIdOrFindingId;
|
|
7005
7038
|
if (isTelemetryDisabled()) {
|
|
7006
|
-
console.log(
|
|
7007
|
-
console.log(
|
|
7039
|
+
console.log(pc2.yellow("Telemetry is disabled. Feedback will not be sent."));
|
|
7040
|
+
console.log(pc2.dim("Unset SECURITYCHECKS_TELEMETRY=false or DO_NOT_TRACK=1 to enable."));
|
|
7008
7041
|
return;
|
|
7009
7042
|
}
|
|
7010
7043
|
const cloudConfig = await loadCloudConfig();
|
|
@@ -7023,8 +7056,8 @@ async function feedbackCommand(invariantIdOrFindingId, options) {
|
|
|
7023
7056
|
endpoint,
|
|
7024
7057
|
timeout: 5e3
|
|
7025
7058
|
};
|
|
7026
|
-
const clientVersion = "0.
|
|
7027
|
-
console.log(
|
|
7059
|
+
const clientVersion = "0.2.0";
|
|
7060
|
+
console.log(pc2.dim("Sending feedback..."));
|
|
7028
7061
|
const success2 = await reportFeedback(
|
|
7029
7062
|
{
|
|
7030
7063
|
invariantId,
|
|
@@ -7035,15 +7068,15 @@ async function feedbackCommand(invariantIdOrFindingId, options) {
|
|
|
7035
7068
|
feedbackConfig
|
|
7036
7069
|
);
|
|
7037
7070
|
if (success2) {
|
|
7038
|
-
const verdictLabel = verdict === "true_positive" ?
|
|
7039
|
-
console.log(
|
|
7040
|
-
console.log(` ${
|
|
7041
|
-
console.log(` ${
|
|
7071
|
+
const verdictLabel = verdict === "true_positive" ? pc2.green("true positive") : pc2.red("false positive");
|
|
7072
|
+
console.log(pc2.green("\u2713 Feedback recorded"));
|
|
7073
|
+
console.log(` ${pc2.dim("Invariant:")} ${invariantId}`);
|
|
7074
|
+
console.log(` ${pc2.dim("Verdict:")} ${verdictLabel}`);
|
|
7042
7075
|
if (reason) {
|
|
7043
|
-
console.log(` ${
|
|
7076
|
+
console.log(` ${pc2.dim("Reason:")} ${reason}`);
|
|
7044
7077
|
}
|
|
7045
7078
|
} else {
|
|
7046
|
-
console.error(
|
|
7079
|
+
console.error(pc2.red("Failed to send feedback. The API may be unreachable."));
|
|
7047
7080
|
process.exit(1);
|
|
7048
7081
|
}
|
|
7049
7082
|
}
|
|
@@ -7070,16 +7103,16 @@ async function listInvariantsCommand(options) {
|
|
|
7070
7103
|
return;
|
|
7071
7104
|
}
|
|
7072
7105
|
if (category || severity) {
|
|
7073
|
-
console.log(
|
|
7106
|
+
console.log(pc2.dim(`Filters: ${category ? `category=${category}` : ""}${category && severity ? ", " : ""}${severity ? `severity=${severity}` : ""}`));
|
|
7074
7107
|
console.log("");
|
|
7075
7108
|
}
|
|
7076
7109
|
for (const inv of invariants) {
|
|
7077
|
-
const severityColor = inv.severity === "P0" ?
|
|
7110
|
+
const severityColor = inv.severity === "P0" ? pc2.red : inv.severity === "P1" ? pc2.yellow : pc2.blue;
|
|
7078
7111
|
console.log(` ${severityColor(`[${inv.severity}]`)} ${inv.id}`);
|
|
7079
|
-
console.log(
|
|
7112
|
+
console.log(pc2.dim(` ${inv.name}`));
|
|
7080
7113
|
const question = getStaffQuestion(inv.id);
|
|
7081
7114
|
if (question) {
|
|
7082
|
-
console.log(
|
|
7115
|
+
console.log(pc2.dim(` Staff asks: ${question}`));
|
|
7083
7116
|
}
|
|
7084
7117
|
}
|
|
7085
7118
|
console.log("");
|
|
@@ -7087,13 +7120,117 @@ async function listInvariantsCommand(options) {
|
|
|
7087
7120
|
async function generateTestCommand(invariantId, options) {
|
|
7088
7121
|
const invariant = getInvariantById(invariantId);
|
|
7089
7122
|
if (!invariant) {
|
|
7090
|
-
console.error(
|
|
7123
|
+
console.error(pc2.red(`Unknown invariant: ${invariantId}`));
|
|
7091
7124
|
process.exit(1);
|
|
7092
7125
|
}
|
|
7093
7126
|
const framework = options.framework ?? "vitest";
|
|
7094
7127
|
const test = generateTestSkeleton(invariant, framework, options.context);
|
|
7095
7128
|
console.log(test);
|
|
7096
7129
|
}
|
|
7130
|
+
var PREFLIGHT_INVARIANTS = [
|
|
7131
|
+
"AUTHZ.SERVICE_LAYER.ENFORCED",
|
|
7132
|
+
"WEBHOOK.IDEMPOTENT",
|
|
7133
|
+
"WEBHOOK.SIGNATURE.VERIFIED",
|
|
7134
|
+
"TRANSACTION.POST_COMMIT.SIDE_EFFECTS",
|
|
7135
|
+
"CONFIG.ENV_HARDCODED",
|
|
7136
|
+
"CONFIG.HEALTH_CHECK_MISSING",
|
|
7137
|
+
"CONFIG.ERROR_STACK_LEAK",
|
|
7138
|
+
"CONFIG.GRACEFUL_SHUTDOWN_MISSING",
|
|
7139
|
+
"SECRETS.HARDCODED",
|
|
7140
|
+
"DATAFLOW.UNTRUSTED.SQL_QUERY"
|
|
7141
|
+
];
|
|
7142
|
+
var INVARIANT_LABELS = {
|
|
7143
|
+
"AUTHZ.SERVICE_LAYER.ENFORCED": "Auth at service layer",
|
|
7144
|
+
"WEBHOOK.IDEMPOTENT": "Webhook idempotency",
|
|
7145
|
+
"WEBHOOK.SIGNATURE.VERIFIED": "Webhook signature verification",
|
|
7146
|
+
"TRANSACTION.POST_COMMIT.SIDE_EFFECTS": "No side effects in transactions",
|
|
7147
|
+
"CONFIG.ENV_HARDCODED": "No hardcoded env values",
|
|
7148
|
+
"CONFIG.HEALTH_CHECK_MISSING": "Health endpoint exists",
|
|
7149
|
+
"CONFIG.ERROR_STACK_LEAK": "No stack traces leaked",
|
|
7150
|
+
"CONFIG.GRACEFUL_SHUTDOWN_MISSING": "Graceful shutdown",
|
|
7151
|
+
"SECRETS.HARDCODED": "No hardcoded secrets",
|
|
7152
|
+
"DATAFLOW.UNTRUSTED.SQL_QUERY": "No SQL injection"
|
|
7153
|
+
};
|
|
7154
|
+
async function preflightCommand(options) {
|
|
7155
|
+
if (!options.quiet) {
|
|
7156
|
+
console.log(pc2.bold("\nPreflight Check\n"));
|
|
7157
|
+
}
|
|
7158
|
+
try {
|
|
7159
|
+
const result = await audit({
|
|
7160
|
+
targetPath: options.path,
|
|
7161
|
+
only: PREFLIGHT_INVARIANTS
|
|
7162
|
+
});
|
|
7163
|
+
const readiness = computeReadinessScore(result.summary);
|
|
7164
|
+
const failedInvariants = /* @__PURE__ */ new Map();
|
|
7165
|
+
for (const r of result.results) {
|
|
7166
|
+
if (!r.passed) {
|
|
7167
|
+
const evidence = r.findings.map((f) => ({
|
|
7168
|
+
file: f.evidence?.[0]?.file ?? "unknown",
|
|
7169
|
+
line: f.evidence?.[0]?.line ?? 0,
|
|
7170
|
+
message: f.message
|
|
7171
|
+
}));
|
|
7172
|
+
failedInvariants.set(r.invariantId, evidence);
|
|
7173
|
+
}
|
|
7174
|
+
}
|
|
7175
|
+
if (options.json) {
|
|
7176
|
+
const jsonOutput = {
|
|
7177
|
+
passed: readiness.passed,
|
|
7178
|
+
failed: readiness.failed,
|
|
7179
|
+
total: readiness.total,
|
|
7180
|
+
score: readiness.score,
|
|
7181
|
+
grade: readiness.grade,
|
|
7182
|
+
hasP0: readiness.hasP0,
|
|
7183
|
+
checks: PREFLIGHT_INVARIANTS.map((id) => ({
|
|
7184
|
+
invariantId: id,
|
|
7185
|
+
label: INVARIANT_LABELS[id] ?? id,
|
|
7186
|
+
passed: !failedInvariants.has(id),
|
|
7187
|
+
findings: failedInvariants.get(id) ?? []
|
|
7188
|
+
}))
|
|
7189
|
+
};
|
|
7190
|
+
console.log(JSON.stringify(jsonOutput, null, 2));
|
|
7191
|
+
return;
|
|
7192
|
+
}
|
|
7193
|
+
if (options.quiet) {
|
|
7194
|
+
if (readiness.failed > 0) {
|
|
7195
|
+
process.exit(1);
|
|
7196
|
+
}
|
|
7197
|
+
return;
|
|
7198
|
+
}
|
|
7199
|
+
console.log(`Preflight Check \u2014 ${readiness.passed}/${readiness.total} passed
|
|
7200
|
+
`);
|
|
7201
|
+
for (const id of PREFLIGHT_INVARIANTS) {
|
|
7202
|
+
const label = INVARIANT_LABELS[id] ?? id;
|
|
7203
|
+
const evidence = failedInvariants.get(id);
|
|
7204
|
+
if (evidence) {
|
|
7205
|
+
console.log(pc2.red(` \u2717 ${label}`));
|
|
7206
|
+
for (const e of evidence) {
|
|
7207
|
+
console.log(pc2.dim(` \u2192 ${e.file}:${e.line} \u2014 ${e.message}`));
|
|
7208
|
+
}
|
|
7209
|
+
} else {
|
|
7210
|
+
console.log(pc2.green(` \u2713 ${label}`));
|
|
7211
|
+
}
|
|
7212
|
+
}
|
|
7213
|
+
console.log("");
|
|
7214
|
+
console.log(` ${formatScoreForCli(readiness)}`);
|
|
7215
|
+
if (readiness.failed > 0) {
|
|
7216
|
+
const issueWord = readiness.failed === 1 ? "issue" : "issues";
|
|
7217
|
+
console.log(` ${readiness.failed} ${issueWord} to fix before deploy.`);
|
|
7218
|
+
} else {
|
|
7219
|
+
console.log(pc2.green(" Ready to deploy."));
|
|
7220
|
+
}
|
|
7221
|
+
console.log("");
|
|
7222
|
+
} catch (error2) {
|
|
7223
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
7224
|
+
if (options.json) {
|
|
7225
|
+
console.error(JSON.stringify({ error: message }, null, 2));
|
|
7226
|
+
} else {
|
|
7227
|
+
console.error(pc2.red(`
|
|
7228
|
+
Preflight failed: ${message}
|
|
7229
|
+
`));
|
|
7230
|
+
}
|
|
7231
|
+
process.exit(1);
|
|
7232
|
+
}
|
|
7233
|
+
}
|
|
7097
7234
|
var SENSITIVE_PATTERNS = [
|
|
7098
7235
|
/api[_-]?key/i,
|
|
7099
7236
|
/secret/i,
|
|
@@ -7132,20 +7269,20 @@ var Logger = class {
|
|
|
7132
7269
|
*/
|
|
7133
7270
|
step(step2, total, message) {
|
|
7134
7271
|
if (this.silent) return;
|
|
7135
|
-
console.log(
|
|
7272
|
+
console.log(pc2.dim(`[${step2}/${total}]`), message);
|
|
7136
7273
|
}
|
|
7137
7274
|
/**
|
|
7138
7275
|
* Log a success message
|
|
7139
7276
|
*/
|
|
7140
7277
|
success(message) {
|
|
7141
7278
|
if (this.silent) return;
|
|
7142
|
-
console.log(
|
|
7279
|
+
console.log(pc2.green("\u2713"), message);
|
|
7143
7280
|
}
|
|
7144
7281
|
/**
|
|
7145
7282
|
* Log a failure message
|
|
7146
7283
|
*/
|
|
7147
7284
|
fail(message) {
|
|
7148
|
-
console.log(
|
|
7285
|
+
console.log(pc2.red("\u2717"), message);
|
|
7149
7286
|
}
|
|
7150
7287
|
log(level, message, data) {
|
|
7151
7288
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -7169,19 +7306,19 @@ var Logger = class {
|
|
|
7169
7306
|
console.log(formattedMessage);
|
|
7170
7307
|
}
|
|
7171
7308
|
if (this.verbose && redactedData) {
|
|
7172
|
-
console.log(
|
|
7309
|
+
console.log(pc2.dim(JSON.stringify(redactedData, null, 2)));
|
|
7173
7310
|
}
|
|
7174
7311
|
}
|
|
7175
7312
|
getPrefix(level) {
|
|
7176
7313
|
switch (level) {
|
|
7177
7314
|
case "debug":
|
|
7178
|
-
return
|
|
7315
|
+
return pc2.dim("[DEBUG]");
|
|
7179
7316
|
case "info":
|
|
7180
|
-
return
|
|
7317
|
+
return pc2.blue("[INFO]");
|
|
7181
7318
|
case "warn":
|
|
7182
|
-
return
|
|
7319
|
+
return pc2.yellow("[WARN]");
|
|
7183
7320
|
case "error":
|
|
7184
|
-
return
|
|
7321
|
+
return pc2.red("[ERROR]");
|
|
7185
7322
|
}
|
|
7186
7323
|
}
|
|
7187
7324
|
/**
|
|
@@ -7225,7 +7362,7 @@ logger.success.bind(logger);
|
|
|
7225
7362
|
logger.fail.bind(logger);
|
|
7226
7363
|
|
|
7227
7364
|
// src/index.ts
|
|
7228
|
-
var version = "0.
|
|
7365
|
+
var version = "0.2.0";
|
|
7229
7366
|
var program = new Command();
|
|
7230
7367
|
program.name("scheck").description("Enforce backend invariants in your codebase").version(version);
|
|
7231
7368
|
program.command("run").description("Scan for security invariants").option("-p, --path <path>", "Target path to audit (default: current directory)").option("-a, --artifact <path>", "Use pre-collected artifact from scc (skips collection)").option("--changed", "Only check changed files (requires git)").option("--ci", "CI mode - fail on new violations").option("--all", "Show all findings (don't stop early, include P2)").option("--include-p2", "Include P2 (medium) findings").option("--only <invariants...>", "Only run specific invariant checks").option("--skip <invariants...>", "Skip specific invariant checks").option("--json", "Output results as JSON").option("--sarif <path>", "Write SARIF report to file (for GitHub Code Scanning)").option("--quiet", "Suppress output except errors").option("-v, --verbose", "Enable verbose output").option("--calibrate", "Enable calibration API (default: enabled)").option("--offline", "Disable all API calls (not supported - shows error)").option("--calibration-endpoint <url>", "Override calibration API endpoint").option("--patterns", "Enable Pro Patterns fetching (default: enabled)").option("--no-patterns", "Disable Pro Patterns fetching").option("--pattern-endpoint <url>", "Override patterns API endpoint").option("--patterns-file <path>", "Load patterns from local JSON file (dev/testing)").option("-w, --watch", "Watch for file changes and re-run").option("--no-local-scan", "Skip local source-level pattern scanning").option("--no-usage-banner", "Suppress periodic API usage reminders").action((options) => {
|
|
@@ -7239,6 +7376,10 @@ program.command("run").description("Scan for security invariants").option("-p, -
|
|
|
7239
7376
|
program.command("explain <invariant>").description("Deep-dive on any invariant (why it matters, what good looks like)").action(explainCommand);
|
|
7240
7377
|
program.command("list-invariants").description("List all invariants (optionally filtered)").option("--category <category>", "Filter by category (authz, webhooks, transactions, etc.)").option("--severity <severity>", "Filter by severity (P0, P1, P2)").option("--json", "Output as JSON").action((options) => listInvariantsCommand(options));
|
|
7241
7378
|
program.command("generate-test <invariantId>").description("Generate a test skeleton proving an invariant is enforced").option("--framework <framework>", "Test framework: vitest, jest, playwright", "vitest").option("--context <context>", "Extra context to make the test more targeted").action((invariantId, options) => generateTestCommand(invariantId, options));
|
|
7379
|
+
program.command("preflight").description("Quick deployment readiness check (curated invariant subset)").option("-p, --path <path>", "Target path to audit (default: current directory)").option("--json", "Output results as JSON").option("--quiet", "Suppress output except errors (exit code only)").action((options) => {
|
|
7380
|
+
logger.configure({ silent: options.quiet, json: options.json });
|
|
7381
|
+
return preflightCommand(options);
|
|
7382
|
+
});
|
|
7242
7383
|
program.command("init").description("Initialize SecurityChecks in a project").option("-p, --path <path>", "Target path (default: current directory)").option("--hooks", "Install git pre-commit hook to run checks before commits").action(initCommand);
|
|
7243
7384
|
program.command("hooks").description("Manage git pre-commit hooks").option("-p, --path <path>", "Target path (default: current directory)").option("--install", "Install pre-commit hook").option("--uninstall", "Uninstall pre-commit hook").option("--show", "Show current hook status (default)").action(hooksCommand);
|
|
7244
7385
|
program.command("baseline").description("Manage the baseline of known issues").option("-p, --path <path>", "Target path (default: current directory)").option("--update", "Update baseline with current findings").option("--show", "Show current baseline").option("--prune", "Remove stale entries not seen recently").option("--prune-days <days>", "Days before considering stale (default: 90)").option("--notes <notes>", "Notes to attach to new baseline entries").option("-y, --yes", "Skip confirmation prompt (for CI/automation)").action(baselineCommand);
|