@runsec/mcp 1.0.79 → 1.0.82
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 +274 -126
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -435,6 +435,45 @@ var LOCKFILE_LAYOUT_RE = /(?:poetry\.lock|package-lock\.json|pnpm-lock\.yaml|yar
|
|
|
435
435
|
function isLockfileLayoutArtifactPath(relPath) {
|
|
436
436
|
return LOCKFILE_LAYOUT_RE.test(relPath.replace(/\\/g, "/"));
|
|
437
437
|
}
|
|
438
|
+
function isStaticLayoutDumpPath(relPath) {
|
|
439
|
+
return isLockfileLayoutArtifactPath(relPath);
|
|
440
|
+
}
|
|
441
|
+
function isHexChecksumBlob(text) {
|
|
442
|
+
const compact = (text ?? "").replace(/\s+/g, "").trim();
|
|
443
|
+
if (compact.length < 40 || compact.length > 128) return false;
|
|
444
|
+
return /^[0-9a-f]+$/i.test(compact);
|
|
445
|
+
}
|
|
446
|
+
var UNVERIFIED_NOISE_DETECTORS = /* @__PURE__ */ new Set([
|
|
447
|
+
"customregex",
|
|
448
|
+
"sentrytoken",
|
|
449
|
+
"hashicorpvault"
|
|
450
|
+
]);
|
|
451
|
+
function isUnverifiedTrufflehogNoiseDetector(detectorName) {
|
|
452
|
+
const d = detectorName.trim().toLowerCase().replace(/\s+/g, "");
|
|
453
|
+
if (UNVERIFIED_NOISE_DETECTORS.has(d)) return true;
|
|
454
|
+
if (d === "sentry" || d === "sentrytoken") return true;
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
var DEV_GENERIC_CREDENTIAL_RE = /(?:postgres:postgres|root:root|root:password|admin:admin|user:password|test:test|guest:guest|changeme:changeme)/i;
|
|
458
|
+
var DEV_DB_HOST_RE = /@(?:dev_db|dev-db|localhost|127\.0\.0\.1|0\.0\.0\.0|host\.docker\.internal|\.local\b|docker\.internal)(?::|\/|$)/i;
|
|
459
|
+
var DEV_DATABASE_URI_RE = /(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis):\/\/(?:[^:@\s]+:[^@\s]+@)?(?:dev_db|dev-db|localhost|127\.0\.0\.1|host\.docker\.internal)/i;
|
|
460
|
+
function blobHasDevDatabaseSecret(text) {
|
|
461
|
+
const blob = (text ?? "").trim();
|
|
462
|
+
if (!blob) return false;
|
|
463
|
+
if (DEV_GENERIC_CREDENTIAL_RE.test(blob)) return true;
|
|
464
|
+
if (DEV_DB_HOST_RE.test(blob)) return true;
|
|
465
|
+
if (DEV_DATABASE_URI_RE.test(blob)) return true;
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
468
|
+
function findingHasDevDatabaseSecret(finding) {
|
|
469
|
+
const parts = [
|
|
470
|
+
finding.match_text ?? "",
|
|
471
|
+
finding.snippet ?? "",
|
|
472
|
+
finding.description ?? "",
|
|
473
|
+
finding.title ?? ""
|
|
474
|
+
];
|
|
475
|
+
return parts.some((p) => blobHasDevDatabaseSecret(p));
|
|
476
|
+
}
|
|
438
477
|
function findingBlobHasEnvInterpolation(finding) {
|
|
439
478
|
const parts = [
|
|
440
479
|
finding.match_text ?? "",
|
|
@@ -458,9 +497,20 @@ function applyNuclearHardDrop(finding, relPath) {
|
|
|
458
497
|
if (ENV_INTERP_RE.test(matchText) || ENV_INTERP_RE.test(snippet) || findingBlobHasEnvInterpolation(finding)) {
|
|
459
498
|
return { cap: NUCLEAR_HARD_DROP_CONFIDENCE, reason: "env_variable_interpolation" };
|
|
460
499
|
}
|
|
461
|
-
if (
|
|
500
|
+
if (findingHasDevDatabaseSecret(finding)) {
|
|
501
|
+
return { cap: NUCLEAR_HARD_DROP_CONFIDENCE, reason: "dev_database_placeholder" };
|
|
502
|
+
}
|
|
503
|
+
const secretBlob = `${matchText} ${snippet}`.trim();
|
|
504
|
+
if (!findingIsVerified(finding) && isHexChecksumBlob(secretBlob)) {
|
|
505
|
+
return { cap: NUCLEAR_HARD_DROP_CONFIDENCE, reason: "package_checksum_hex" };
|
|
506
|
+
}
|
|
507
|
+
if (!findingIsVerified(finding) && (LOCKFILE_LAYOUT_RE.test(relPath) || isLockfileOrModulesPath(relPath) || isLockfileLayoutArtifactPath(relPath) || isStaticLayoutDumpPath(relPath))) {
|
|
462
508
|
return { cap: NUCLEAR_HARD_DROP_CONFIDENCE, reason: "lockfile_or_layout_artifact" };
|
|
463
509
|
}
|
|
510
|
+
const detector = String(finding.detector_name ?? "").trim();
|
|
511
|
+
if (!findingIsVerified(finding) && isUnverifiedTrufflehogNoiseDetector(detector)) {
|
|
512
|
+
return { cap: NUCLEAR_HARD_DROP_CONFIDENCE, reason: "unverified_trufflehog_noise_detector" };
|
|
513
|
+
}
|
|
464
514
|
if (isCustomRegexFinding(finding) && !findingIsVerified(finding)) {
|
|
465
515
|
return { cap: NUCLEAR_HARD_DROP_CONFIDENCE, reason: "customregex_unverified" };
|
|
466
516
|
}
|
|
@@ -496,14 +546,18 @@ function isCustomRegexFinding(finding) {
|
|
|
496
546
|
}
|
|
497
547
|
function looksLikeHighEntropyRawToken(finding) {
|
|
498
548
|
const blob = `${finding.match_text ?? ""} ${finding.snippet ?? ""}`.trim();
|
|
499
|
-
if (blob.length < 24 || ENV_INTERP_RE.test(blob)) return false;
|
|
549
|
+
if (blob.length < 24 || ENV_INTERP_RE.test(blob) || findingHasDevDatabaseSecret(finding)) return false;
|
|
550
|
+
if (/^(?:postgres(?:ql)?|mysql|mongodb|redis):\/\//i.test(blob)) return false;
|
|
500
551
|
if (/^(?:ghp_|gho_|github_pat_|glpat-|sk-[a-zA-Z0-9]{10,}|AKIA[0-9A-Z]{16}|xox[baprs]-|eyJ[A-Za-z0-9_-]{10,}\.)/i.test(
|
|
501
552
|
blob
|
|
502
553
|
)) {
|
|
503
554
|
return true;
|
|
504
555
|
}
|
|
505
556
|
const compact = blob.replace(/\s+/g, "");
|
|
506
|
-
if (
|
|
557
|
+
if (isHexChecksumBlob(compact)) return false;
|
|
558
|
+
if (compact.length >= 40 && /^[A-Za-z0-9+/=_-]+$/.test(compact) && /[A-Z]/.test(compact) && /[a-z]/.test(compact)) {
|
|
559
|
+
return true;
|
|
560
|
+
}
|
|
507
561
|
return false;
|
|
508
562
|
}
|
|
509
563
|
function customRegexNeedsBaseClamp(finding) {
|
|
@@ -1818,10 +1872,12 @@ function mapTrufflehogFindings(rows, workspaceRoot) {
|
|
|
1818
1872
|
const display = redacted || rawSecret || "[secret redacted]";
|
|
1819
1873
|
const description = `TruffleHog: exposed ${detector}${verified ? " (verified)" : ""}`;
|
|
1820
1874
|
if (!isTrufflehogVerified(verified, description)) {
|
|
1821
|
-
if (isLockfileOrModulesPath(rel)) continue;
|
|
1822
1875
|
const blob = `${display} ${rawSecret} ${description}`;
|
|
1876
|
+
if (isLockfileOrModulesPath(rel) || isStaticLayoutDumpPath(rel)) continue;
|
|
1823
1877
|
if (hasEnvironmentInterpolation(blob)) continue;
|
|
1824
|
-
if (
|
|
1878
|
+
if (blobHasDevDatabaseSecret(blob)) continue;
|
|
1879
|
+
if (isHexChecksumBlob(display) || isHexChecksumBlob(rawSecret)) continue;
|
|
1880
|
+
if (isUnverifiedTrufflehogNoiseDetector(detector)) continue;
|
|
1825
1881
|
}
|
|
1826
1882
|
const severity = severityForSecret(detector, verified);
|
|
1827
1883
|
findings.push({
|
|
@@ -3262,8 +3318,29 @@ async function executeAudit(toolName, args) {
|
|
|
3262
3318
|
}
|
|
3263
3319
|
|
|
3264
3320
|
// src/engine/reportFormatter.ts
|
|
3321
|
+
var import_node_fs10 = __toESM(require("fs"));
|
|
3322
|
+
var import_node_path16 = __toESM(require("path"));
|
|
3323
|
+
|
|
3324
|
+
// src/version.ts
|
|
3265
3325
|
var import_node_fs9 = __toESM(require("fs"));
|
|
3266
3326
|
var import_node_path15 = __toESM(require("path"));
|
|
3327
|
+
function loadVersion() {
|
|
3328
|
+
const candidates = [
|
|
3329
|
+
import_node_path15.default.join(__dirname, "..", "package.json"),
|
|
3330
|
+
import_node_path15.default.join(__dirname, "package.json")
|
|
3331
|
+
];
|
|
3332
|
+
for (const candidate of candidates) {
|
|
3333
|
+
try {
|
|
3334
|
+
const raw = JSON.parse(import_node_fs9.default.readFileSync(candidate, "utf8"));
|
|
3335
|
+
if (raw.version?.trim()) return raw.version.trim();
|
|
3336
|
+
} catch {
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
return "unknown";
|
|
3340
|
+
}
|
|
3341
|
+
var RUNSEC_MCP_VERSION = loadVersion();
|
|
3342
|
+
|
|
3343
|
+
// src/engine/reportFormatter.ts
|
|
3267
3344
|
var REPORT_SECTION_TITLES = {
|
|
3268
3345
|
code: "Code Vulnerabilities",
|
|
3269
3346
|
secrets: "Exposed Secrets",
|
|
@@ -3356,7 +3433,7 @@ function shortRuleLabel(ruleId) {
|
|
|
3356
3433
|
return parts[parts.length - 1] || ruleId;
|
|
3357
3434
|
}
|
|
3358
3435
|
function snippetLanguage(filePath) {
|
|
3359
|
-
const ext =
|
|
3436
|
+
const ext = import_node_path16.default.extname(filePath).replace(/^\./, "").toLowerCase();
|
|
3360
3437
|
const map = {
|
|
3361
3438
|
yml: "yaml",
|
|
3362
3439
|
yaml: "yaml",
|
|
@@ -3458,9 +3535,9 @@ var TECH_STACK_BY_EXT = {
|
|
|
3458
3535
|
};
|
|
3459
3536
|
function techStackFromFilePath(filePath) {
|
|
3460
3537
|
const normalized = String(filePath ?? "").replace(/\\/g, "/");
|
|
3461
|
-
const base =
|
|
3538
|
+
const base = import_node_path16.default.basename(normalized).toLowerCase();
|
|
3462
3539
|
if (base === "dockerfile" || base.startsWith("dockerfile.")) return "Docker";
|
|
3463
|
-
const ext =
|
|
3540
|
+
const ext = import_node_path16.default.extname(normalized).toLowerCase();
|
|
3464
3541
|
return TECH_STACK_BY_EXT[ext] ?? "Other";
|
|
3465
3542
|
}
|
|
3466
3543
|
function countFindingsByTechStack(findings) {
|
|
@@ -3654,6 +3731,7 @@ function buildServerSideReportMarkdown(standard, findings, metrics) {
|
|
|
3654
3731
|
out.push(`# RunSec Unified Security Report`);
|
|
3655
3732
|
out.push("");
|
|
3656
3733
|
out.push(`**Generated at:** ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
3734
|
+
out.push(`**MCP package:** \`@runsec/mcp@${safeText(RUNSEC_MCP_VERSION)}\``);
|
|
3657
3735
|
out.push(`**Standard:** ${safeText(standard)}`);
|
|
3658
3736
|
out.push(`**X-RunSec-Verdict:** \`${safeText(verdictLabel)}\`${metrics.verdict?.is_safe === false && metrics.verdict.fail_reason ? ` \u2014 ${safeText(metrics.verdict.fail_reason)}` : ""}`);
|
|
3659
3737
|
out.push(
|
|
@@ -3723,15 +3801,15 @@ function buildServerSideReportMarkdown(standard, findings, metrics) {
|
|
|
3723
3801
|
return finalizeReportMarkdown(out.join("\n"));
|
|
3724
3802
|
}
|
|
3725
3803
|
function resolveReportPath(workspacePath) {
|
|
3726
|
-
const base = workspacePath?.trim() ?
|
|
3727
|
-
return
|
|
3804
|
+
const base = workspacePath?.trim() ? import_node_path16.default.resolve(workspacePath) : process.cwd();
|
|
3805
|
+
return import_node_path16.default.join(base, "runsec-report.md");
|
|
3728
3806
|
}
|
|
3729
3807
|
function generateMarkdownReport(standard, findings, metrics, workspacePath) {
|
|
3730
3808
|
const m = metrics || {};
|
|
3731
3809
|
const rows = Array.isArray(findings) ? findings : [];
|
|
3732
3810
|
const reportContent = finalizeReportMarkdown(buildServerSideReportMarkdown(standard, rows, m));
|
|
3733
3811
|
const reportPath = resolveReportPath(workspacePath);
|
|
3734
|
-
|
|
3812
|
+
import_node_fs10.default.writeFileSync(reportPath, reportContent, "utf-8");
|
|
3735
3813
|
console.error(`[runsec] wrote unified report to: ${reportPath}`);
|
|
3736
3814
|
console.error(`[runsec] X-RunSec-Verdict: ${m.verdict?.http_headers?.["X-RunSec-Verdict"] ?? m.verdict?.status ?? "PASS"}`);
|
|
3737
3815
|
return `
|
|
@@ -3746,17 +3824,17 @@ Simply confirm that the scan is complete and the report is saved at the path abo
|
|
|
3746
3824
|
}
|
|
3747
3825
|
|
|
3748
3826
|
// src/engine/reviewFormatter.ts
|
|
3749
|
-
var
|
|
3750
|
-
var
|
|
3827
|
+
var import_node_fs15 = __toESM(require("fs"));
|
|
3828
|
+
var import_node_path22 = __toESM(require("path"));
|
|
3751
3829
|
|
|
3752
3830
|
// src/engine/threatModelEngine.ts
|
|
3753
3831
|
var import_node_crypto4 = require("crypto");
|
|
3754
|
-
var
|
|
3755
|
-
var
|
|
3832
|
+
var import_node_fs14 = __toESM(require("fs"));
|
|
3833
|
+
var import_node_path21 = __toESM(require("path"));
|
|
3756
3834
|
|
|
3757
3835
|
// src/skills/skillsApi.ts
|
|
3758
|
-
var
|
|
3759
|
-
var
|
|
3836
|
+
var import_node_fs13 = __toESM(require("fs"));
|
|
3837
|
+
var import_node_path20 = __toESM(require("path"));
|
|
3760
3838
|
|
|
3761
3839
|
// src/skills/patternParser.ts
|
|
3762
3840
|
var METRIC_ID_RE = /^[A-Z0-9]{2,4}-[0-9A-Za-z.\-]+$/;
|
|
@@ -3829,43 +3907,43 @@ function parsePatternRows(patternsText) {
|
|
|
3829
3907
|
}
|
|
3830
3908
|
|
|
3831
3909
|
// src/skills/paths.ts
|
|
3832
|
-
var
|
|
3910
|
+
var import_node_path17 = __toESM(require("path"));
|
|
3833
3911
|
var RUNSEC_RELEASE_VERSION = "v1.0";
|
|
3834
3912
|
var RAG_CACHE_SCHEMA_VERSION = 3;
|
|
3835
3913
|
var ANTI_HALLUCINATION_PROMPT = "You MUST re-run a RunSec audit tool (runsec_audit_general or a scoped runsec_audit_*) after every remediation. Security claims without a fresh scanner PASS are invalid.";
|
|
3836
3914
|
function getSkillsDirectory() {
|
|
3837
|
-
return
|
|
3915
|
+
return import_node_path17.default.join(getDataDirectory(), "skills");
|
|
3838
3916
|
}
|
|
3839
3917
|
function getRagCachePath() {
|
|
3840
|
-
return
|
|
3918
|
+
return import_node_path17.default.join(getDataDirectory(), ".rag-cache.json");
|
|
3841
3919
|
}
|
|
3842
3920
|
|
|
3843
3921
|
// src/skills/ragIndex.ts
|
|
3844
3922
|
var import_node_crypto3 = require("crypto");
|
|
3845
|
-
var
|
|
3846
|
-
var
|
|
3923
|
+
var import_node_fs12 = __toESM(require("fs"));
|
|
3924
|
+
var import_node_path19 = __toESM(require("path"));
|
|
3847
3925
|
|
|
3848
3926
|
// src/skills/skillLoader.ts
|
|
3849
|
-
var
|
|
3850
|
-
var
|
|
3927
|
+
var import_node_fs11 = __toESM(require("fs"));
|
|
3928
|
+
var import_node_path18 = __toESM(require("path"));
|
|
3851
3929
|
function loadSkillManifests() {
|
|
3852
3930
|
const skillsDir = getSkillsDirectory();
|
|
3853
3931
|
const manifests = {};
|
|
3854
|
-
if (!
|
|
3932
|
+
if (!import_node_fs11.default.existsSync(skillsDir)) {
|
|
3855
3933
|
return manifests;
|
|
3856
3934
|
}
|
|
3857
|
-
for (const entry of
|
|
3935
|
+
for (const entry of import_node_fs11.default.readdirSync(skillsDir, { withFileTypes: true })) {
|
|
3858
3936
|
if (!entry.isDirectory()) continue;
|
|
3859
|
-
const skillJson =
|
|
3860
|
-
if (!
|
|
3861
|
-
const data = JSON.parse(
|
|
3937
|
+
const skillJson = import_node_path18.default.join(skillsDir, entry.name, "skill.json");
|
|
3938
|
+
if (!import_node_fs11.default.existsSync(skillJson)) continue;
|
|
3939
|
+
const data = JSON.parse(import_node_fs11.default.readFileSync(skillJson, "utf-8"));
|
|
3862
3940
|
const sid = String(data.skill_id ?? entry.name);
|
|
3863
3941
|
manifests[sid] = { ...data, skill_id: sid, __dir_name: entry.name };
|
|
3864
3942
|
}
|
|
3865
3943
|
return manifests;
|
|
3866
3944
|
}
|
|
3867
3945
|
function skillDirectory(manifest) {
|
|
3868
|
-
return
|
|
3946
|
+
return import_node_path18.default.join(getSkillsDirectory(), manifest.__dir_name);
|
|
3869
3947
|
}
|
|
3870
3948
|
|
|
3871
3949
|
// src/skills/ragIndex.ts
|
|
@@ -3911,28 +3989,28 @@ function cosine(a, b) {
|
|
|
3911
3989
|
}
|
|
3912
3990
|
function hashFile(filePath) {
|
|
3913
3991
|
const h = (0, import_node_crypto3.createHash)("sha256");
|
|
3914
|
-
h.update(
|
|
3992
|
+
h.update(import_node_fs12.default.readFileSync(filePath));
|
|
3915
3993
|
return h.digest("hex");
|
|
3916
3994
|
}
|
|
3917
3995
|
function listSkillSourceFiles() {
|
|
3918
3996
|
const skillsDir = getSkillsDirectory();
|
|
3919
3997
|
const files = [];
|
|
3920
3998
|
const walk = (dir) => {
|
|
3921
|
-
for (const entry of
|
|
3922
|
-
const full =
|
|
3999
|
+
for (const entry of import_node_fs12.default.readdirSync(dir, { withFileTypes: true })) {
|
|
4000
|
+
const full = import_node_path19.default.join(dir, entry.name);
|
|
3923
4001
|
if (entry.isDirectory()) walk(full);
|
|
3924
4002
|
else if (entry.isFile()) files.push(full);
|
|
3925
4003
|
}
|
|
3926
4004
|
};
|
|
3927
|
-
if (
|
|
4005
|
+
if (import_node_fs12.default.existsSync(skillsDir)) walk(skillsDir);
|
|
3928
4006
|
return files.sort();
|
|
3929
4007
|
}
|
|
3930
4008
|
function computeFilesChecksum() {
|
|
3931
4009
|
const files = listSkillSourceFiles();
|
|
3932
4010
|
const entries = [];
|
|
3933
4011
|
for (const full of files) {
|
|
3934
|
-
const st =
|
|
3935
|
-
const rel =
|
|
4012
|
+
const st = import_node_fs12.default.statSync(full);
|
|
4013
|
+
const rel = import_node_path19.default.relative(getSkillsDirectory(), full).replace(/\\/g, "/");
|
|
3936
4014
|
const mtimeNs = typeof st.mtimeNs === "bigint" ? Number(st.mtimeNs) : Math.round(st.mtimeMs * 1e6);
|
|
3937
4015
|
entries.push({
|
|
3938
4016
|
path: rel,
|
|
@@ -3963,11 +4041,11 @@ function buildRagIndex() {
|
|
|
3963
4041
|
const manifests = loadSkillManifests();
|
|
3964
4042
|
for (const [skill_id, manifest] of Object.entries(manifests)) {
|
|
3965
4043
|
const dir = skillDirectory(manifest);
|
|
3966
|
-
const indexPath =
|
|
3967
|
-
const patternsPath =
|
|
3968
|
-
if (!
|
|
3969
|
-
const indexText =
|
|
3970
|
-
const patternsText =
|
|
4044
|
+
const indexPath = import_node_path19.default.join(dir, "index.md");
|
|
4045
|
+
const patternsPath = import_node_path19.default.join(dir, "patterns.md");
|
|
4046
|
+
if (!import_node_fs12.default.existsSync(indexPath) || !import_node_fs12.default.existsSync(patternsPath)) continue;
|
|
4047
|
+
const indexText = import_node_fs12.default.readFileSync(indexPath, "utf-8");
|
|
4048
|
+
const patternsText = import_node_fs12.default.readFileSync(patternsPath, "utf-8");
|
|
3971
4049
|
const example_path = String(manifest.few_shot_examples ?? "");
|
|
3972
4050
|
for (const paragraph of indexText.split(/\n\n+/).map((p) => p.trim()).filter(Boolean)) {
|
|
3973
4051
|
chunks.push({
|
|
@@ -4008,9 +4086,9 @@ function buildRagIndex() {
|
|
|
4008
4086
|
}
|
|
4009
4087
|
function loadRagCache(current) {
|
|
4010
4088
|
const cachePath = getRagCachePath();
|
|
4011
|
-
if (!
|
|
4089
|
+
if (!import_node_fs12.default.existsSync(cachePath)) return null;
|
|
4012
4090
|
try {
|
|
4013
|
-
const payload = JSON.parse(
|
|
4091
|
+
const payload = JSON.parse(import_node_fs12.default.readFileSync(cachePath, "utf-8"));
|
|
4014
4092
|
if (payload.schema_version !== RAG_CACHE_SCHEMA_VERSION) return null;
|
|
4015
4093
|
if (JSON.stringify(payload.files_checksum) !== JSON.stringify(current)) return null;
|
|
4016
4094
|
return deserializeChunks(payload.chunks ?? []);
|
|
@@ -4025,7 +4103,7 @@ function saveRagCache(chunks, filesChecksum) {
|
|
|
4025
4103
|
files_checksum: filesChecksum,
|
|
4026
4104
|
chunks: serializeChunks(chunks)
|
|
4027
4105
|
};
|
|
4028
|
-
|
|
4106
|
+
import_node_fs12.default.writeFileSync(getRagCachePath(), JSON.stringify(payload), "utf-8");
|
|
4029
4107
|
} catch {
|
|
4030
4108
|
console.error("[runsec] RAG cache write failed; continuing in-memory only");
|
|
4031
4109
|
}
|
|
@@ -4189,7 +4267,7 @@ function selectSkillsForContext(opts) {
|
|
|
4189
4267
|
const query = [opts.question ?? "", opts.file_path ?? "", opts.file_content ?? ""].filter(Boolean).join("\n");
|
|
4190
4268
|
const semScores = query ? semanticSkillScores(query) : {};
|
|
4191
4269
|
const keywordBoosts = query ? skillBoosts(query) : {};
|
|
4192
|
-
const suffix =
|
|
4270
|
+
const suffix = import_node_path19.default.extname(opts.file_path ?? "").toLowerCase();
|
|
4193
4271
|
const hay = query.toLowerCase();
|
|
4194
4272
|
const ranked = [];
|
|
4195
4273
|
for (const [sid, manifest] of Object.entries(manifests)) {
|
|
@@ -4226,20 +4304,20 @@ function findPatternChunkByMetric(metricId) {
|
|
|
4226
4304
|
|
|
4227
4305
|
// src/skills/skillsApi.ts
|
|
4228
4306
|
function loadComplianceSnapshot() {
|
|
4229
|
-
const p =
|
|
4230
|
-
if (!
|
|
4307
|
+
const p = import_node_path20.default.join(getDataDirectory(), "rule-compliance-map.json");
|
|
4308
|
+
if (!import_node_fs13.default.existsSync(p)) return {};
|
|
4231
4309
|
try {
|
|
4232
|
-
return JSON.parse(
|
|
4310
|
+
return JSON.parse(import_node_fs13.default.readFileSync(p, "utf-8"));
|
|
4233
4311
|
} catch {
|
|
4234
4312
|
return {};
|
|
4235
4313
|
}
|
|
4236
4314
|
}
|
|
4237
4315
|
function extractTestbedExample(metricId, examplePath, skillsRoot) {
|
|
4238
4316
|
if (!examplePath) return "";
|
|
4239
|
-
const p =
|
|
4240
|
-
const resolved =
|
|
4241
|
-
if (!
|
|
4242
|
-
const lines =
|
|
4317
|
+
const p = import_node_path20.default.isAbsolute(examplePath) ? examplePath : import_node_path20.default.join(import_node_path20.default.dirname(skillsRoot), "..", "..", examplePath);
|
|
4318
|
+
const resolved = import_node_fs13.default.existsSync(p) ? p : import_node_path20.default.join(getDataDirectory(), "..", "..", examplePath);
|
|
4319
|
+
if (!import_node_fs13.default.existsSync(resolved)) return "";
|
|
4320
|
+
const lines = import_node_fs13.default.readFileSync(resolved, "utf-8").split(/\r?\n/);
|
|
4243
4321
|
const needle = `Vulnerable: ${metricId}`;
|
|
4244
4322
|
for (let idx = 0; idx < lines.length; idx += 1) {
|
|
4245
4323
|
if (lines[idx].includes(needle)) {
|
|
@@ -4291,16 +4369,16 @@ function getSkillContext(args) {
|
|
|
4291
4369
|
}
|
|
4292
4370
|
const manifest = manifests[skill_id];
|
|
4293
4371
|
const dir = skillDirectory(manifest);
|
|
4294
|
-
const indexPath =
|
|
4295
|
-
const patternsPath =
|
|
4296
|
-
if (!
|
|
4372
|
+
const indexPath = import_node_path20.default.join(dir, "index.md");
|
|
4373
|
+
const patternsPath = import_node_path20.default.join(dir, "patterns.md");
|
|
4374
|
+
if (!import_node_fs13.default.existsSync(indexPath) || !import_node_fs13.default.existsSync(patternsPath)) {
|
|
4297
4375
|
return {
|
|
4298
4376
|
error: `incomplete skill data for ${skill_id}`,
|
|
4299
|
-
index_exists:
|
|
4300
|
-
patterns_exists:
|
|
4377
|
+
index_exists: import_node_fs13.default.existsSync(indexPath),
|
|
4378
|
+
patterns_exists: import_node_fs13.default.existsSync(patternsPath)
|
|
4301
4379
|
};
|
|
4302
4380
|
}
|
|
4303
|
-
const parsedRows = parsePatternRows(
|
|
4381
|
+
const parsedRows = parsePatternRows(import_node_fs13.default.readFileSync(patternsPath, "utf-8"));
|
|
4304
4382
|
const grouped = {};
|
|
4305
4383
|
for (const row of parsedRows) {
|
|
4306
4384
|
const stack = row.stack.trim() || "Generic";
|
|
@@ -4313,13 +4391,13 @@ function getSkillContext(args) {
|
|
|
4313
4391
|
source: row.source
|
|
4314
4392
|
});
|
|
4315
4393
|
}
|
|
4316
|
-
const skillsRoot =
|
|
4394
|
+
const skillsRoot = import_node_path20.default.dirname(dir);
|
|
4317
4395
|
const response = {
|
|
4318
4396
|
skill_id,
|
|
4319
|
-
index_path:
|
|
4320
|
-
patterns_path:
|
|
4321
|
-
index_md:
|
|
4322
|
-
patterns_md:
|
|
4397
|
+
index_path: import_node_path20.default.relative(getDataDirectory(), indexPath).replace(/\\/g, "/"),
|
|
4398
|
+
patterns_path: import_node_path20.default.relative(getDataDirectory(), patternsPath).replace(/\\/g, "/"),
|
|
4399
|
+
index_md: import_node_fs13.default.readFileSync(indexPath, "utf-8"),
|
|
4400
|
+
patterns_md: import_node_fs13.default.readFileSync(patternsPath, "utf-8"),
|
|
4323
4401
|
patterns_by_stack: Object.fromEntries(Object.keys(grouped).sort().map((k) => [k, grouped[k]])),
|
|
4324
4402
|
agent_system_insert: ANTI_HALLUCINATION_PROMPT,
|
|
4325
4403
|
compliance_snapshot: loadComplianceSnapshot()
|
|
@@ -4359,7 +4437,7 @@ function askGuidance(question) {
|
|
|
4359
4437
|
if (!q) return { error: "question is required" };
|
|
4360
4438
|
const best = semanticSearch(q, 50, "pattern", true);
|
|
4361
4439
|
const manifests = loadSkillManifests();
|
|
4362
|
-
const skillsRoot =
|
|
4440
|
+
const skillsRoot = import_node_path20.default.join(getDataDirectory(), "skills");
|
|
4363
4441
|
const required = requiredMetricIds(q);
|
|
4364
4442
|
const out = [];
|
|
4365
4443
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -4422,7 +4500,7 @@ var STRIDE_LABELS = {
|
|
|
4422
4500
|
};
|
|
4423
4501
|
function readTextSafe(filePath, limit = 4e5) {
|
|
4424
4502
|
try {
|
|
4425
|
-
const data =
|
|
4503
|
+
const data = import_node_fs14.default.readFileSync(filePath, "utf-8");
|
|
4426
4504
|
return data.length > limit ? data.slice(0, limit) : data;
|
|
4427
4505
|
} catch {
|
|
4428
4506
|
return "";
|
|
@@ -4462,13 +4540,13 @@ function walkFiles2(root, opts) {
|
|
|
4462
4540
|
let dir;
|
|
4463
4541
|
try {
|
|
4464
4542
|
dir = stack.pop();
|
|
4465
|
-
const entries =
|
|
4543
|
+
const entries = import_node_fs14.default.readdirSync(dir, { withFileTypes: true });
|
|
4466
4544
|
for (const ent of entries) {
|
|
4467
4545
|
if (out.length >= max) break;
|
|
4468
|
-
const full =
|
|
4546
|
+
const full = import_node_path21.default.join(dir, ent.name);
|
|
4469
4547
|
if (ent.isDirectory()) {
|
|
4470
4548
|
if (!skip.has(ent.name)) stack.push(full);
|
|
4471
|
-
} else if (ent.isFile() && exts.has(
|
|
4549
|
+
} else if (ent.isFile() && exts.has(import_node_path21.default.extname(ent.name).toLowerCase())) {
|
|
4472
4550
|
out.push(full);
|
|
4473
4551
|
}
|
|
4474
4552
|
}
|
|
@@ -4509,7 +4587,7 @@ function syftPackages(payload) {
|
|
|
4509
4587
|
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
4510
4588
|
}
|
|
4511
4589
|
function collectRepoThreatSignals(scanRoot, syftPayload) {
|
|
4512
|
-
const root =
|
|
4590
|
+
const root = import_node_path21.default.resolve(scanRoot);
|
|
4513
4591
|
const scanRel = ".";
|
|
4514
4592
|
const signals = {
|
|
4515
4593
|
scan_root: scanRel,
|
|
@@ -4531,7 +4609,7 @@ function collectRepoThreatSignals(scanRoot, syftPayload) {
|
|
|
4531
4609
|
}
|
|
4532
4610
|
};
|
|
4533
4611
|
try {
|
|
4534
|
-
for (const ent of
|
|
4612
|
+
for (const ent of import_node_fs14.default.readdirSync(root, { withFileTypes: true })) {
|
|
4535
4613
|
if (ent.isDirectory() && !ent.name.startsWith(".")) {
|
|
4536
4614
|
signals.top_level_dirs.push(ent.name);
|
|
4537
4615
|
if (signals.top_level_dirs.length >= 40) break;
|
|
@@ -4550,16 +4628,16 @@ function collectRepoThreatSignals(scanRoot, syftPayload) {
|
|
|
4550
4628
|
const dir = stack.pop();
|
|
4551
4629
|
let entries;
|
|
4552
4630
|
try {
|
|
4553
|
-
entries =
|
|
4631
|
+
entries = import_node_fs14.default.readdirSync(dir, { withFileTypes: true });
|
|
4554
4632
|
} catch {
|
|
4555
4633
|
continue;
|
|
4556
4634
|
}
|
|
4557
4635
|
for (const ent of entries) {
|
|
4558
|
-
const full =
|
|
4636
|
+
const full = import_node_path21.default.join(dir, ent.name);
|
|
4559
4637
|
if (ent.isDirectory()) {
|
|
4560
4638
|
if (!skip.has(ent.name) && !ent.name.startsWith(".")) stack.push(full);
|
|
4561
4639
|
} else if (ent.isFile() && patternNames.includes(ent.name)) {
|
|
4562
|
-
const rel =
|
|
4640
|
+
const rel = import_node_path21.default.relative(root, full).replace(/\\/g, "/");
|
|
4563
4641
|
if (!rel.startsWith("..")) handler(full, rel);
|
|
4564
4642
|
}
|
|
4565
4643
|
}
|
|
@@ -4571,30 +4649,30 @@ function collectRepoThreatSignals(scanRoot, syftPayload) {
|
|
|
4571
4649
|
});
|
|
4572
4650
|
globWalk(["requirements.txt", "pyproject.toml", "go.mod"], (full, rel) => {
|
|
4573
4651
|
addKey(rel);
|
|
4574
|
-
if (
|
|
4652
|
+
if (import_node_path21.default.basename(full) === "requirements.txt") {
|
|
4575
4653
|
for (const d of parseRequirementsDeps(full)) deps.add(d);
|
|
4576
|
-
} else if (
|
|
4654
|
+
} else if (import_node_path21.default.basename(full) === "pyproject.toml") {
|
|
4577
4655
|
const txt = readTextSafe(full, 8e4).toLowerCase();
|
|
4578
4656
|
for (const m of txt.matchAll(/['"]([a-zA-Z0-9_.\-]+)['"]/g)) deps.add(m[1].toLowerCase());
|
|
4579
4657
|
}
|
|
4580
4658
|
});
|
|
4581
4659
|
for (const name of ["Dockerfile", "docker-compose.yml", "docker-compose.yaml"]) {
|
|
4582
|
-
const fp =
|
|
4583
|
-
if (
|
|
4660
|
+
const fp = import_node_path21.default.join(root, name);
|
|
4661
|
+
if (import_node_fs14.default.existsSync(fp)) {
|
|
4584
4662
|
addKey(name);
|
|
4585
4663
|
if (name === "Dockerfile") signals.flags.has_dockerfile = true;
|
|
4586
4664
|
else signals.flags.has_compose = true;
|
|
4587
4665
|
}
|
|
4588
4666
|
}
|
|
4589
4667
|
for (const full of walkFiles2(root, { maxFiles: 80, extensions: /* @__PURE__ */ new Set([".yaml", ".yml"]) })) {
|
|
4590
|
-
const base =
|
|
4668
|
+
const base = import_node_path21.default.basename(full).toLowerCase();
|
|
4591
4669
|
if (base.includes("deploy") || base.includes("helm") || base.includes("k8s") || base.includes("values")) {
|
|
4592
4670
|
signals.flags.has_k8s_yaml = true;
|
|
4593
|
-
addKey(
|
|
4671
|
+
addKey(import_node_path21.default.relative(root, full).replace(/\\/g, "/"));
|
|
4594
4672
|
break;
|
|
4595
4673
|
}
|
|
4596
4674
|
}
|
|
4597
|
-
if (
|
|
4675
|
+
if (import_node_fs14.default.existsSync(import_node_path21.default.join(root, ".github", "workflows"))) {
|
|
4598
4676
|
signals.flags.has_github_workflows = true;
|
|
4599
4677
|
}
|
|
4600
4678
|
for (const pkg of signals.syft_packages) {
|
|
@@ -4626,12 +4704,12 @@ ${context}
|
|
|
4626
4704
|
${repoFp}`).digest("hex");
|
|
4627
4705
|
}
|
|
4628
4706
|
function loadThreatModelCache(workspaceRoot) {
|
|
4629
|
-
const p =
|
|
4630
|
-
if (!
|
|
4707
|
+
const p = import_node_path21.default.join(workspaceRoot, THREAT_MODEL_CACHE_FILE);
|
|
4708
|
+
if (!import_node_fs14.default.existsSync(p)) {
|
|
4631
4709
|
return { schema_version: THREAT_MODEL_CACHE_SCHEMA_VERSION, entries: {} };
|
|
4632
4710
|
}
|
|
4633
4711
|
try {
|
|
4634
|
-
const data = JSON.parse(
|
|
4712
|
+
const data = JSON.parse(import_node_fs14.default.readFileSync(p, "utf-8"));
|
|
4635
4713
|
if (data.schema_version !== THREAT_MODEL_CACHE_SCHEMA_VERSION) {
|
|
4636
4714
|
return { schema_version: THREAT_MODEL_CACHE_SCHEMA_VERSION, entries: {} };
|
|
4637
4715
|
}
|
|
@@ -4642,7 +4720,7 @@ function loadThreatModelCache(workspaceRoot) {
|
|
|
4642
4720
|
}
|
|
4643
4721
|
}
|
|
4644
4722
|
function saveThreatModelCache(workspaceRoot, cacheKey, markdown, baseline) {
|
|
4645
|
-
const p =
|
|
4723
|
+
const p = import_node_path21.default.join(workspaceRoot, THREAT_MODEL_CACHE_FILE);
|
|
4646
4724
|
const payload = loadThreatModelCache(workspaceRoot);
|
|
4647
4725
|
payload.entries[cacheKey] = {
|
|
4648
4726
|
markdown,
|
|
@@ -4651,7 +4729,7 @@ function saveThreatModelCache(workspaceRoot, cacheKey, markdown, baseline) {
|
|
|
4651
4729
|
repo_fingerprint: String(baseline.repo_fingerprint ?? "")
|
|
4652
4730
|
};
|
|
4653
4731
|
payload.baseline = baseline;
|
|
4654
|
-
|
|
4732
|
+
import_node_fs14.default.writeFileSync(p, JSON.stringify(payload, null, 2), "utf-8");
|
|
4655
4733
|
return p;
|
|
4656
4734
|
}
|
|
4657
4735
|
function strideClassifyClause(clause) {
|
|
@@ -4855,7 +4933,7 @@ function loadRepoCrosscheckHaystack(scanRoot, maxChars = 35e4) {
|
|
|
4855
4933
|
let total = 0;
|
|
4856
4934
|
const files = walkFiles2(scanRoot, { maxFiles: 220 });
|
|
4857
4935
|
for (const full of files) {
|
|
4858
|
-
const rel =
|
|
4936
|
+
const rel = import_node_path21.default.relative(scanRoot, full).replace(/\\/g, "/");
|
|
4859
4937
|
const snippet = readTextSafe(full, 14e3);
|
|
4860
4938
|
const block = `
|
|
4861
4939
|
--- ${rel} ---
|
|
@@ -5065,8 +5143,8 @@ function generateThreatModelMarkdown(profile, baseline, signals, ragKeywords, ca
|
|
|
5065
5143
|
`;
|
|
5066
5144
|
}
|
|
5067
5145
|
async function runThreatModelEngine(opts) {
|
|
5068
|
-
const workspaceRoot =
|
|
5069
|
-
const projectName = (opts.project_name ??
|
|
5146
|
+
const workspaceRoot = import_node_path21.default.resolve(opts.workspace_path);
|
|
5147
|
+
const projectName = (opts.project_name ?? import_node_path21.default.basename(workspaceRoot)).trim() || "project";
|
|
5070
5148
|
const userContext = (opts.context ?? "").trim();
|
|
5071
5149
|
const profile = detectSecurityProfile(projectName, userContext);
|
|
5072
5150
|
const baseline = ARCHITECTURE_BASELINES[profile];
|
|
@@ -5081,13 +5159,13 @@ Project: ${projectName}`;
|
|
|
5081
5159
|
const cache = loadThreatModelCache(workspaceRoot);
|
|
5082
5160
|
const cached = cache.entries[cacheKey];
|
|
5083
5161
|
if (cached?.markdown) {
|
|
5084
|
-
const reportPath2 =
|
|
5085
|
-
|
|
5162
|
+
const reportPath2 = import_node_path21.default.join(workspaceRoot, THREAT_MODEL_REPORT_FILE);
|
|
5163
|
+
import_node_fs14.default.writeFileSync(reportPath2, cached.markdown, "utf-8");
|
|
5086
5164
|
return {
|
|
5087
5165
|
profile,
|
|
5088
5166
|
markdown: cached.markdown,
|
|
5089
5167
|
report_path: reportPath2,
|
|
5090
|
-
cache_path:
|
|
5168
|
+
cache_path: import_node_path21.default.join(workspaceRoot, THREAT_MODEL_CACHE_FILE),
|
|
5091
5169
|
cache_hit: true,
|
|
5092
5170
|
cache_key: cacheKey,
|
|
5093
5171
|
repo_fingerprint: repoFp,
|
|
@@ -5119,8 +5197,8 @@ Project: ${projectName}`;
|
|
|
5119
5197
|
rag_keywords: [...ragKeywords].sort().slice(0, 40)
|
|
5120
5198
|
};
|
|
5121
5199
|
const cachePath = saveThreatModelCache(workspaceRoot, cacheKey, markdown, baselinePayload);
|
|
5122
|
-
const reportPath =
|
|
5123
|
-
|
|
5200
|
+
const reportPath = import_node_path21.default.join(workspaceRoot, THREAT_MODEL_REPORT_FILE);
|
|
5201
|
+
import_node_fs14.default.writeFileSync(reportPath, markdown, "utf-8");
|
|
5124
5202
|
return {
|
|
5125
5203
|
profile,
|
|
5126
5204
|
markdown,
|
|
@@ -5335,18 +5413,18 @@ function buildSecurityReviewMarkdown(opts) {
|
|
|
5335
5413
|
return out.join("\n");
|
|
5336
5414
|
}
|
|
5337
5415
|
function resolveSecurityReviewPath(workspacePath) {
|
|
5338
|
-
const base = workspacePath?.trim() ?
|
|
5339
|
-
return
|
|
5416
|
+
const base = workspacePath?.trim() ? import_node_path22.default.resolve(workspacePath) : process.cwd();
|
|
5417
|
+
return import_node_path22.default.join(base, SECURITY_REVIEW_REPORT_FILE);
|
|
5340
5418
|
}
|
|
5341
5419
|
function writeSecurityReviewReport(markdown, workspacePath) {
|
|
5342
5420
|
const reportPath = resolveSecurityReviewPath(workspacePath);
|
|
5343
|
-
|
|
5421
|
+
import_node_fs15.default.writeFileSync(reportPath, markdown, "utf-8");
|
|
5344
5422
|
console.error(`[runsec] wrote security review to: ${reportPath}`);
|
|
5345
5423
|
return reportPath;
|
|
5346
5424
|
}
|
|
5347
5425
|
async function executeSecurityReview(opts) {
|
|
5348
|
-
const workspaceRoot =
|
|
5349
|
-
const projectName = (opts.project_name ??
|
|
5426
|
+
const workspaceRoot = import_node_path22.default.resolve(opts.workspace_path);
|
|
5427
|
+
const projectName = (opts.project_name ?? import_node_path22.default.basename(workspaceRoot)).trim() || import_node_path22.default.basename(workspaceRoot);
|
|
5350
5428
|
console.error("[runsec] security review: starting unified scan + STRIDE threat model");
|
|
5351
5429
|
const [audit, threatModel] = await Promise.all([
|
|
5352
5430
|
executeAudit("runsec_audit_general", {
|
|
@@ -5379,20 +5457,20 @@ async function executeSecurityReview(opts) {
|
|
|
5379
5457
|
}
|
|
5380
5458
|
|
|
5381
5459
|
// src/engine/remediation.ts
|
|
5382
|
-
var
|
|
5383
|
-
var
|
|
5460
|
+
var import_node_fs17 = __toESM(require("fs"));
|
|
5461
|
+
var import_node_path24 = __toESM(require("path"));
|
|
5384
5462
|
|
|
5385
5463
|
// src/skills/metricLookup.ts
|
|
5386
|
-
var
|
|
5387
|
-
var
|
|
5464
|
+
var import_node_fs16 = __toESM(require("fs"));
|
|
5465
|
+
var import_node_path23 = __toESM(require("path"));
|
|
5388
5466
|
function findMetricRow(metricId) {
|
|
5389
5467
|
const mid = metricId.trim().toUpperCase();
|
|
5390
5468
|
if (!mid) return null;
|
|
5391
5469
|
const manifests = loadSkillManifests();
|
|
5392
5470
|
for (const [, manifest] of Object.entries(manifests)) {
|
|
5393
|
-
const patternsPath =
|
|
5394
|
-
if (!
|
|
5395
|
-
const rows = parsePatternRows(
|
|
5471
|
+
const patternsPath = import_node_path23.default.join(skillDirectory(manifest), "patterns.md");
|
|
5472
|
+
if (!import_node_fs16.default.existsSync(patternsPath)) continue;
|
|
5473
|
+
const rows = parsePatternRows(import_node_fs16.default.readFileSync(patternsPath, "utf-8"));
|
|
5396
5474
|
const row = rows.find((r) => r.metric_id.toUpperCase() === mid);
|
|
5397
5475
|
if (row) return row;
|
|
5398
5476
|
}
|
|
@@ -5405,12 +5483,12 @@ function normalizeRelPath2(p) {
|
|
|
5405
5483
|
return p.replace(/\\/g, "/").replace(/^\.\/+/, "");
|
|
5406
5484
|
}
|
|
5407
5485
|
function resolveTargetFile(workspaceRoot, filePath) {
|
|
5408
|
-
const root =
|
|
5486
|
+
const root = import_node_path24.default.resolve(workspaceRoot);
|
|
5409
5487
|
const rel = normalizeRelPath2(filePath.trim());
|
|
5410
5488
|
if (!rel) return { error: "file_path is required" };
|
|
5411
|
-
const abs =
|
|
5412
|
-
const relFromRoot =
|
|
5413
|
-
if (relFromRoot.startsWith("..") ||
|
|
5489
|
+
const abs = import_node_path24.default.resolve(root, rel);
|
|
5490
|
+
const relFromRoot = import_node_path24.default.relative(root, abs).replace(/\\/g, "/");
|
|
5491
|
+
if (relFromRoot.startsWith("..") || import_node_path24.default.isAbsolute(relFromRoot)) {
|
|
5414
5492
|
return { error: `file_path must be inside workspace: ${root}` };
|
|
5415
5493
|
}
|
|
5416
5494
|
for (const seg of BLOCKED_PATH_SEGMENTS) {
|
|
@@ -5418,8 +5496,8 @@ function resolveTargetFile(workspaceRoot, filePath) {
|
|
|
5418
5496
|
return { error: `refusing to modify path under blocked segment: ${seg}` };
|
|
5419
5497
|
}
|
|
5420
5498
|
}
|
|
5421
|
-
if (!
|
|
5422
|
-
if (!
|
|
5499
|
+
if (!import_node_fs17.default.existsSync(abs)) return { error: `path does not exist: ${relFromRoot}` };
|
|
5500
|
+
if (!import_node_fs17.default.statSync(abs).isFile()) return { error: `path is not a file: ${relFromRoot}` };
|
|
5423
5501
|
return { abs, rel: relFromRoot };
|
|
5424
5502
|
}
|
|
5425
5503
|
function normalizeNewlines(text) {
|
|
@@ -5486,12 +5564,12 @@ function locateTargetInFile(content, target) {
|
|
|
5486
5564
|
}
|
|
5487
5565
|
function writeBackup(absPath) {
|
|
5488
5566
|
const backupPath = `${absPath}.bak`;
|
|
5489
|
-
|
|
5567
|
+
import_node_fs17.default.copyFileSync(absPath, backupPath);
|
|
5490
5568
|
return backupPath;
|
|
5491
5569
|
}
|
|
5492
5570
|
function applyDvs001Fix(absPath) {
|
|
5493
|
-
const rel =
|
|
5494
|
-
const lines =
|
|
5571
|
+
const rel = import_node_path24.default.basename(absPath);
|
|
5572
|
+
const lines = import_node_fs17.default.readFileSync(absPath, "utf-8").split(/\r?\n/);
|
|
5495
5573
|
const newLines = lines.filter((ln) => !/^\s*user\s+root\s*$/i.test(ln.trim()));
|
|
5496
5574
|
const hasNonRootUser = newLines.some(
|
|
5497
5575
|
(ln) => /^\s*user\s+\S+\s*$/i.test(ln.trim()) && !/^\s*user\s+root\s*$/i.test(ln.trim())
|
|
@@ -5517,7 +5595,7 @@ function applyDvs001Fix(absPath) {
|
|
|
5517
5595
|
};
|
|
5518
5596
|
}
|
|
5519
5597
|
const backupPath = writeBackup(absPath);
|
|
5520
|
-
|
|
5598
|
+
import_node_fs17.default.writeFileSync(absPath, `${newLines.join("\n")}
|
|
5521
5599
|
`, "utf-8");
|
|
5522
5600
|
return {
|
|
5523
5601
|
status: "fixed",
|
|
@@ -5594,7 +5672,7 @@ function applyRemediation(args) {
|
|
|
5594
5672
|
fix_template: row.fix_template
|
|
5595
5673
|
};
|
|
5596
5674
|
}
|
|
5597
|
-
const original =
|
|
5675
|
+
const original = import_node_fs17.default.readFileSync(resolved.abs, "utf-8");
|
|
5598
5676
|
const { index, count } = locateTargetInFile(original, target);
|
|
5599
5677
|
if (count === 0) {
|
|
5600
5678
|
return {
|
|
@@ -5621,7 +5699,7 @@ function applyRemediation(args) {
|
|
|
5621
5699
|
const backupPath = writeBackup(resolved.abs);
|
|
5622
5700
|
const ending = original.includes("\r\n") ? "\r\n" : "\n";
|
|
5623
5701
|
const outText = updated.split("\n").join(ending);
|
|
5624
|
-
|
|
5702
|
+
import_node_fs17.default.writeFileSync(resolved.abs, outText.endsWith(ending) || !original.endsWith(ending) ? outText : outText + ending, "utf-8");
|
|
5625
5703
|
return {
|
|
5626
5704
|
status: "fixed",
|
|
5627
5705
|
message: "Remediation applied after exact target verification; backup created",
|
|
@@ -5823,7 +5901,7 @@ function isRemediationTool(name) {
|
|
|
5823
5901
|
}
|
|
5824
5902
|
|
|
5825
5903
|
// src/hubUploadUrl.ts
|
|
5826
|
-
var
|
|
5904
|
+
var import_node_fs18 = __toESM(require("fs"));
|
|
5827
5905
|
var HUB_UPLOAD_REPORT_PATH = "/api/mcp/upload-report";
|
|
5828
5906
|
var DEFAULT_PRODUCTION_HUB_ORIGIN = "https://runsec.io";
|
|
5829
5907
|
var DEFAULT_DOCKER_INTERNAL_HUB_ORIGIN = "http://frontend:3000";
|
|
@@ -5860,7 +5938,7 @@ function colocatedHubOrigin() {
|
|
|
5860
5938
|
}
|
|
5861
5939
|
function isDockerRuntime() {
|
|
5862
5940
|
try {
|
|
5863
|
-
return
|
|
5941
|
+
return import_node_fs18.default.existsSync("/.dockerenv");
|
|
5864
5942
|
} catch {
|
|
5865
5943
|
return false;
|
|
5866
5944
|
}
|
|
@@ -5947,6 +6025,10 @@ function dockerInternalAlternateUploadUrl(url) {
|
|
|
5947
6025
|
}
|
|
5948
6026
|
}
|
|
5949
6027
|
|
|
6028
|
+
// src/telemetryClient.ts
|
|
6029
|
+
var import_node_http = __toESM(require("http"));
|
|
6030
|
+
var import_node_https = __toESM(require("https"));
|
|
6031
|
+
|
|
5950
6032
|
// src/complianceScores.ts
|
|
5951
6033
|
var SEVERITY_PENALTY = {
|
|
5952
6034
|
CRITICAL: 15,
|
|
@@ -6055,14 +6137,78 @@ function logHubSyncFailure(message, error, targetUrl) {
|
|
|
6055
6137
|
}
|
|
6056
6138
|
console.error(`[runsec] Hub telemetry upload error (scan saved locally): ${message}`);
|
|
6057
6139
|
}
|
|
6058
|
-
|
|
6059
|
-
return
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6140
|
+
function postHubUploadNodeHttp(url, apiKey, payload) {
|
|
6141
|
+
return new Promise((resolve, reject) => {
|
|
6142
|
+
const body = JSON.stringify(payload);
|
|
6143
|
+
let parsed;
|
|
6144
|
+
try {
|
|
6145
|
+
parsed = new URL(url);
|
|
6146
|
+
} catch (err) {
|
|
6147
|
+
reject(err);
|
|
6148
|
+
return;
|
|
6149
|
+
}
|
|
6150
|
+
const headers = {
|
|
6151
|
+
...hubAuthHeaders(apiKey),
|
|
6152
|
+
"Content-Length": String(Buffer.byteLength(body))
|
|
6153
|
+
};
|
|
6154
|
+
const lib = parsed.protocol === "https:" ? import_node_https.default : import_node_http.default;
|
|
6155
|
+
const port = parsed.port !== "" ? Number(parsed.port) : parsed.protocol === "https:" ? 443 : 80;
|
|
6156
|
+
const req = lib.request(
|
|
6157
|
+
{
|
|
6158
|
+
hostname: parsed.hostname,
|
|
6159
|
+
port,
|
|
6160
|
+
path: `${parsed.pathname}${parsed.search}`,
|
|
6161
|
+
method: "POST",
|
|
6162
|
+
headers,
|
|
6163
|
+
timeout: HUB_UPLOAD_TIMEOUT_MS
|
|
6164
|
+
},
|
|
6165
|
+
(res) => {
|
|
6166
|
+
const chunks = [];
|
|
6167
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
6168
|
+
res.on("end", () => {
|
|
6169
|
+
const text = Buffer.concat(chunks).toString("utf8");
|
|
6170
|
+
const status = res.statusCode ?? 0;
|
|
6171
|
+
resolve({
|
|
6172
|
+
ok: status >= 200 && status < 300,
|
|
6173
|
+
status,
|
|
6174
|
+
statusText: res.statusMessage ?? "",
|
|
6175
|
+
text: async () => text,
|
|
6176
|
+
json: async () => JSON.parse(text || "{}")
|
|
6177
|
+
});
|
|
6178
|
+
});
|
|
6179
|
+
}
|
|
6180
|
+
);
|
|
6181
|
+
req.on("error", reject);
|
|
6182
|
+
req.on("timeout", () => {
|
|
6183
|
+
req.destroy();
|
|
6184
|
+
reject(Object.assign(new Error("Hub upload request timeout"), { code: "ETIMEDOUT" }));
|
|
6185
|
+
});
|
|
6186
|
+
req.write(body);
|
|
6187
|
+
req.end();
|
|
6064
6188
|
});
|
|
6065
6189
|
}
|
|
6190
|
+
function preferNodeHttpUpload() {
|
|
6191
|
+
if (process.platform === "win32") return true;
|
|
6192
|
+
if (process.env.RUNSEC_HUB_USE_NODE_HTTP === "1") return true;
|
|
6193
|
+
return false;
|
|
6194
|
+
}
|
|
6195
|
+
async function postHubUpload(url, apiKey, payload) {
|
|
6196
|
+
if (preferNodeHttpUpload()) {
|
|
6197
|
+
return postHubUploadNodeHttp(url, apiKey, payload);
|
|
6198
|
+
}
|
|
6199
|
+
try {
|
|
6200
|
+
return await fetch(url, {
|
|
6201
|
+
method: "POST",
|
|
6202
|
+
headers: hubAuthHeaders(apiKey),
|
|
6203
|
+
body: JSON.stringify(payload),
|
|
6204
|
+
signal: AbortSignal.timeout(HUB_UPLOAD_TIMEOUT_MS)
|
|
6205
|
+
});
|
|
6206
|
+
} catch (fetchError) {
|
|
6207
|
+
console.error("[runsec] fetch() failed \u2014 retrying Hub upload via node:http(s)");
|
|
6208
|
+
dumpHubNetworkError(fetchError, url);
|
|
6209
|
+
return postHubUploadNodeHttp(url, apiKey, payload);
|
|
6210
|
+
}
|
|
6211
|
+
}
|
|
6066
6212
|
function countSeverityMetrics(findings) {
|
|
6067
6213
|
const metrics = { critical: 0, high: 0, medium: 0, low: 0, total: 0 };
|
|
6068
6214
|
for (const f of findings) {
|
|
@@ -6516,7 +6662,9 @@ CRITICAL INSTRUCTIONS FOR LLM:
|
|
|
6516
6662
|
async function main() {
|
|
6517
6663
|
const key = getApiKey();
|
|
6518
6664
|
process.env.RUNSEC_API_KEY = key;
|
|
6519
|
-
console.error(
|
|
6665
|
+
console.error(
|
|
6666
|
+
`[runsec] MCP @runsec/mcp v${RUNSEC_MCP_VERSION} starting \u2014 Hub upload target: ${resolveHubUploadUrl()}`
|
|
6667
|
+
);
|
|
6520
6668
|
await verifyApiKey(key);
|
|
6521
6669
|
const summary = validateRules();
|
|
6522
6670
|
console.error("Rules registry validated:", summary);
|