@runsec/mcp 1.0.80 → 1.0.83
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/bin/runsec-mcp.cjs +1 -1
- package/dist/index.js +187 -129
- package/package.json +2 -1
package/bin/runsec-mcp.cjs
CHANGED
package/dist/index.js
CHANGED
|
@@ -435,6 +435,25 @@ 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
|
+
}
|
|
438
457
|
var DEV_GENERIC_CREDENTIAL_RE = /(?:postgres:postgres|root:root|root:password|admin:admin|user:password|test:test|guest:guest|changeme:changeme)/i;
|
|
439
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;
|
|
440
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;
|
|
@@ -481,9 +500,17 @@ function applyNuclearHardDrop(finding, relPath) {
|
|
|
481
500
|
if (findingHasDevDatabaseSecret(finding)) {
|
|
482
501
|
return { cap: NUCLEAR_HARD_DROP_CONFIDENCE, reason: "dev_database_placeholder" };
|
|
483
502
|
}
|
|
484
|
-
|
|
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))) {
|
|
485
508
|
return { cap: NUCLEAR_HARD_DROP_CONFIDENCE, reason: "lockfile_or_layout_artifact" };
|
|
486
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
|
+
}
|
|
487
514
|
if (isCustomRegexFinding(finding) && !findingIsVerified(finding)) {
|
|
488
515
|
return { cap: NUCLEAR_HARD_DROP_CONFIDENCE, reason: "customregex_unverified" };
|
|
489
516
|
}
|
|
@@ -527,7 +554,10 @@ function looksLikeHighEntropyRawToken(finding) {
|
|
|
527
554
|
return true;
|
|
528
555
|
}
|
|
529
556
|
const compact = blob.replace(/\s+/g, "");
|
|
530
|
-
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
|
+
}
|
|
531
561
|
return false;
|
|
532
562
|
}
|
|
533
563
|
function customRegexNeedsBaseClamp(finding) {
|
|
@@ -1842,11 +1872,12 @@ function mapTrufflehogFindings(rows, workspaceRoot) {
|
|
|
1842
1872
|
const display = redacted || rawSecret || "[secret redacted]";
|
|
1843
1873
|
const description = `TruffleHog: exposed ${detector}${verified ? " (verified)" : ""}`;
|
|
1844
1874
|
if (!isTrufflehogVerified(verified, description)) {
|
|
1845
|
-
if (isLockfileOrModulesPath(rel)) continue;
|
|
1846
1875
|
const blob = `${display} ${rawSecret} ${description}`;
|
|
1876
|
+
if (isLockfileOrModulesPath(rel) || isStaticLayoutDumpPath(rel)) continue;
|
|
1847
1877
|
if (hasEnvironmentInterpolation(blob)) continue;
|
|
1848
1878
|
if (blobHasDevDatabaseSecret(blob)) continue;
|
|
1849
|
-
if (
|
|
1879
|
+
if (isHexChecksumBlob(display) || isHexChecksumBlob(rawSecret)) continue;
|
|
1880
|
+
if (isUnverifiedTrufflehogNoiseDetector(detector)) continue;
|
|
1850
1881
|
}
|
|
1851
1882
|
const severity = severityForSecret(detector, verified);
|
|
1852
1883
|
findings.push({
|
|
@@ -2545,16 +2576,11 @@ var import_promises = __toESM(require("fs/promises"));
|
|
|
2545
2576
|
var import_node_os = __toESM(require("os"));
|
|
2546
2577
|
var import_node_path11 = __toESM(require("path"));
|
|
2547
2578
|
var TRUFFLEHOG_EXCLUDE_PATTERNS = [
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
"**/Cargo.lock",
|
|
2554
|
-
"**/composer.lock",
|
|
2555
|
-
"**/Gemfile.lock",
|
|
2556
|
-
"**/*-lock.json",
|
|
2557
|
-
"**/node_modules/**"
|
|
2579
|
+
String.raw`\.lock$`,
|
|
2580
|
+
String.raw`package-lock\.json$`,
|
|
2581
|
+
String.raw`pnpm-lock\.yaml$`,
|
|
2582
|
+
String.raw`-lock\.json$`,
|
|
2583
|
+
String.raw`(^|[\\/])node_modules([\\/]|$)`
|
|
2558
2584
|
];
|
|
2559
2585
|
async function createTrufflehogExcludeFile() {
|
|
2560
2586
|
const tmpDir = await import_promises.default.mkdtemp(import_node_path11.default.join(import_node_os.default.tmpdir(), "runsec-th-exclude-"));
|
|
@@ -3287,8 +3313,29 @@ async function executeAudit(toolName, args) {
|
|
|
3287
3313
|
}
|
|
3288
3314
|
|
|
3289
3315
|
// src/engine/reportFormatter.ts
|
|
3316
|
+
var import_node_fs10 = __toESM(require("fs"));
|
|
3317
|
+
var import_node_path16 = __toESM(require("path"));
|
|
3318
|
+
|
|
3319
|
+
// src/version.ts
|
|
3290
3320
|
var import_node_fs9 = __toESM(require("fs"));
|
|
3291
3321
|
var import_node_path15 = __toESM(require("path"));
|
|
3322
|
+
function loadVersion() {
|
|
3323
|
+
const candidates = [
|
|
3324
|
+
import_node_path15.default.join(__dirname, "..", "package.json"),
|
|
3325
|
+
import_node_path15.default.join(__dirname, "package.json")
|
|
3326
|
+
];
|
|
3327
|
+
for (const candidate of candidates) {
|
|
3328
|
+
try {
|
|
3329
|
+
const raw = JSON.parse(import_node_fs9.default.readFileSync(candidate, "utf8"));
|
|
3330
|
+
if (raw.version?.trim()) return raw.version.trim();
|
|
3331
|
+
} catch {
|
|
3332
|
+
}
|
|
3333
|
+
}
|
|
3334
|
+
return "unknown";
|
|
3335
|
+
}
|
|
3336
|
+
var RUNSEC_MCP_VERSION = loadVersion();
|
|
3337
|
+
|
|
3338
|
+
// src/engine/reportFormatter.ts
|
|
3292
3339
|
var REPORT_SECTION_TITLES = {
|
|
3293
3340
|
code: "Code Vulnerabilities",
|
|
3294
3341
|
secrets: "Exposed Secrets",
|
|
@@ -3381,7 +3428,7 @@ function shortRuleLabel(ruleId) {
|
|
|
3381
3428
|
return parts[parts.length - 1] || ruleId;
|
|
3382
3429
|
}
|
|
3383
3430
|
function snippetLanguage(filePath) {
|
|
3384
|
-
const ext =
|
|
3431
|
+
const ext = import_node_path16.default.extname(filePath).replace(/^\./, "").toLowerCase();
|
|
3385
3432
|
const map = {
|
|
3386
3433
|
yml: "yaml",
|
|
3387
3434
|
yaml: "yaml",
|
|
@@ -3483,9 +3530,9 @@ var TECH_STACK_BY_EXT = {
|
|
|
3483
3530
|
};
|
|
3484
3531
|
function techStackFromFilePath(filePath) {
|
|
3485
3532
|
const normalized = String(filePath ?? "").replace(/\\/g, "/");
|
|
3486
|
-
const base =
|
|
3533
|
+
const base = import_node_path16.default.basename(normalized).toLowerCase();
|
|
3487
3534
|
if (base === "dockerfile" || base.startsWith("dockerfile.")) return "Docker";
|
|
3488
|
-
const ext =
|
|
3535
|
+
const ext = import_node_path16.default.extname(normalized).toLowerCase();
|
|
3489
3536
|
return TECH_STACK_BY_EXT[ext] ?? "Other";
|
|
3490
3537
|
}
|
|
3491
3538
|
function countFindingsByTechStack(findings) {
|
|
@@ -3679,6 +3726,7 @@ function buildServerSideReportMarkdown(standard, findings, metrics) {
|
|
|
3679
3726
|
out.push(`# RunSec Unified Security Report`);
|
|
3680
3727
|
out.push("");
|
|
3681
3728
|
out.push(`**Generated at:** ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
3729
|
+
out.push(`**MCP package:** \`@runsec/mcp@${safeText(RUNSEC_MCP_VERSION)}\``);
|
|
3682
3730
|
out.push(`**Standard:** ${safeText(standard)}`);
|
|
3683
3731
|
out.push(`**X-RunSec-Verdict:** \`${safeText(verdictLabel)}\`${metrics.verdict?.is_safe === false && metrics.verdict.fail_reason ? ` \u2014 ${safeText(metrics.verdict.fail_reason)}` : ""}`);
|
|
3684
3732
|
out.push(
|
|
@@ -3748,15 +3796,15 @@ function buildServerSideReportMarkdown(standard, findings, metrics) {
|
|
|
3748
3796
|
return finalizeReportMarkdown(out.join("\n"));
|
|
3749
3797
|
}
|
|
3750
3798
|
function resolveReportPath(workspacePath) {
|
|
3751
|
-
const base = workspacePath?.trim() ?
|
|
3752
|
-
return
|
|
3799
|
+
const base = workspacePath?.trim() ? import_node_path16.default.resolve(workspacePath) : process.cwd();
|
|
3800
|
+
return import_node_path16.default.join(base, "runsec-report.md");
|
|
3753
3801
|
}
|
|
3754
3802
|
function generateMarkdownReport(standard, findings, metrics, workspacePath) {
|
|
3755
3803
|
const m = metrics || {};
|
|
3756
3804
|
const rows = Array.isArray(findings) ? findings : [];
|
|
3757
3805
|
const reportContent = finalizeReportMarkdown(buildServerSideReportMarkdown(standard, rows, m));
|
|
3758
3806
|
const reportPath = resolveReportPath(workspacePath);
|
|
3759
|
-
|
|
3807
|
+
import_node_fs10.default.writeFileSync(reportPath, reportContent, "utf-8");
|
|
3760
3808
|
console.error(`[runsec] wrote unified report to: ${reportPath}`);
|
|
3761
3809
|
console.error(`[runsec] X-RunSec-Verdict: ${m.verdict?.http_headers?.["X-RunSec-Verdict"] ?? m.verdict?.status ?? "PASS"}`);
|
|
3762
3810
|
return `
|
|
@@ -3771,17 +3819,17 @@ Simply confirm that the scan is complete and the report is saved at the path abo
|
|
|
3771
3819
|
}
|
|
3772
3820
|
|
|
3773
3821
|
// src/engine/reviewFormatter.ts
|
|
3774
|
-
var
|
|
3775
|
-
var
|
|
3822
|
+
var import_node_fs15 = __toESM(require("fs"));
|
|
3823
|
+
var import_node_path22 = __toESM(require("path"));
|
|
3776
3824
|
|
|
3777
3825
|
// src/engine/threatModelEngine.ts
|
|
3778
3826
|
var import_node_crypto4 = require("crypto");
|
|
3779
|
-
var
|
|
3780
|
-
var
|
|
3827
|
+
var import_node_fs14 = __toESM(require("fs"));
|
|
3828
|
+
var import_node_path21 = __toESM(require("path"));
|
|
3781
3829
|
|
|
3782
3830
|
// src/skills/skillsApi.ts
|
|
3783
|
-
var
|
|
3784
|
-
var
|
|
3831
|
+
var import_node_fs13 = __toESM(require("fs"));
|
|
3832
|
+
var import_node_path20 = __toESM(require("path"));
|
|
3785
3833
|
|
|
3786
3834
|
// src/skills/patternParser.ts
|
|
3787
3835
|
var METRIC_ID_RE = /^[A-Z0-9]{2,4}-[0-9A-Za-z.\-]+$/;
|
|
@@ -3854,43 +3902,43 @@ function parsePatternRows(patternsText) {
|
|
|
3854
3902
|
}
|
|
3855
3903
|
|
|
3856
3904
|
// src/skills/paths.ts
|
|
3857
|
-
var
|
|
3905
|
+
var import_node_path17 = __toESM(require("path"));
|
|
3858
3906
|
var RUNSEC_RELEASE_VERSION = "v1.0";
|
|
3859
3907
|
var RAG_CACHE_SCHEMA_VERSION = 3;
|
|
3860
3908
|
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.";
|
|
3861
3909
|
function getSkillsDirectory() {
|
|
3862
|
-
return
|
|
3910
|
+
return import_node_path17.default.join(getDataDirectory(), "skills");
|
|
3863
3911
|
}
|
|
3864
3912
|
function getRagCachePath() {
|
|
3865
|
-
return
|
|
3913
|
+
return import_node_path17.default.join(getDataDirectory(), ".rag-cache.json");
|
|
3866
3914
|
}
|
|
3867
3915
|
|
|
3868
3916
|
// src/skills/ragIndex.ts
|
|
3869
3917
|
var import_node_crypto3 = require("crypto");
|
|
3870
|
-
var
|
|
3871
|
-
var
|
|
3918
|
+
var import_node_fs12 = __toESM(require("fs"));
|
|
3919
|
+
var import_node_path19 = __toESM(require("path"));
|
|
3872
3920
|
|
|
3873
3921
|
// src/skills/skillLoader.ts
|
|
3874
|
-
var
|
|
3875
|
-
var
|
|
3922
|
+
var import_node_fs11 = __toESM(require("fs"));
|
|
3923
|
+
var import_node_path18 = __toESM(require("path"));
|
|
3876
3924
|
function loadSkillManifests() {
|
|
3877
3925
|
const skillsDir = getSkillsDirectory();
|
|
3878
3926
|
const manifests = {};
|
|
3879
|
-
if (!
|
|
3927
|
+
if (!import_node_fs11.default.existsSync(skillsDir)) {
|
|
3880
3928
|
return manifests;
|
|
3881
3929
|
}
|
|
3882
|
-
for (const entry of
|
|
3930
|
+
for (const entry of import_node_fs11.default.readdirSync(skillsDir, { withFileTypes: true })) {
|
|
3883
3931
|
if (!entry.isDirectory()) continue;
|
|
3884
|
-
const skillJson =
|
|
3885
|
-
if (!
|
|
3886
|
-
const data = JSON.parse(
|
|
3932
|
+
const skillJson = import_node_path18.default.join(skillsDir, entry.name, "skill.json");
|
|
3933
|
+
if (!import_node_fs11.default.existsSync(skillJson)) continue;
|
|
3934
|
+
const data = JSON.parse(import_node_fs11.default.readFileSync(skillJson, "utf-8"));
|
|
3887
3935
|
const sid = String(data.skill_id ?? entry.name);
|
|
3888
3936
|
manifests[sid] = { ...data, skill_id: sid, __dir_name: entry.name };
|
|
3889
3937
|
}
|
|
3890
3938
|
return manifests;
|
|
3891
3939
|
}
|
|
3892
3940
|
function skillDirectory(manifest) {
|
|
3893
|
-
return
|
|
3941
|
+
return import_node_path18.default.join(getSkillsDirectory(), manifest.__dir_name);
|
|
3894
3942
|
}
|
|
3895
3943
|
|
|
3896
3944
|
// src/skills/ragIndex.ts
|
|
@@ -3936,28 +3984,28 @@ function cosine(a, b) {
|
|
|
3936
3984
|
}
|
|
3937
3985
|
function hashFile(filePath) {
|
|
3938
3986
|
const h = (0, import_node_crypto3.createHash)("sha256");
|
|
3939
|
-
h.update(
|
|
3987
|
+
h.update(import_node_fs12.default.readFileSync(filePath));
|
|
3940
3988
|
return h.digest("hex");
|
|
3941
3989
|
}
|
|
3942
3990
|
function listSkillSourceFiles() {
|
|
3943
3991
|
const skillsDir = getSkillsDirectory();
|
|
3944
3992
|
const files = [];
|
|
3945
3993
|
const walk = (dir) => {
|
|
3946
|
-
for (const entry of
|
|
3947
|
-
const full =
|
|
3994
|
+
for (const entry of import_node_fs12.default.readdirSync(dir, { withFileTypes: true })) {
|
|
3995
|
+
const full = import_node_path19.default.join(dir, entry.name);
|
|
3948
3996
|
if (entry.isDirectory()) walk(full);
|
|
3949
3997
|
else if (entry.isFile()) files.push(full);
|
|
3950
3998
|
}
|
|
3951
3999
|
};
|
|
3952
|
-
if (
|
|
4000
|
+
if (import_node_fs12.default.existsSync(skillsDir)) walk(skillsDir);
|
|
3953
4001
|
return files.sort();
|
|
3954
4002
|
}
|
|
3955
4003
|
function computeFilesChecksum() {
|
|
3956
4004
|
const files = listSkillSourceFiles();
|
|
3957
4005
|
const entries = [];
|
|
3958
4006
|
for (const full of files) {
|
|
3959
|
-
const st =
|
|
3960
|
-
const rel =
|
|
4007
|
+
const st = import_node_fs12.default.statSync(full);
|
|
4008
|
+
const rel = import_node_path19.default.relative(getSkillsDirectory(), full).replace(/\\/g, "/");
|
|
3961
4009
|
const mtimeNs = typeof st.mtimeNs === "bigint" ? Number(st.mtimeNs) : Math.round(st.mtimeMs * 1e6);
|
|
3962
4010
|
entries.push({
|
|
3963
4011
|
path: rel,
|
|
@@ -3988,11 +4036,11 @@ function buildRagIndex() {
|
|
|
3988
4036
|
const manifests = loadSkillManifests();
|
|
3989
4037
|
for (const [skill_id, manifest] of Object.entries(manifests)) {
|
|
3990
4038
|
const dir = skillDirectory(manifest);
|
|
3991
|
-
const indexPath =
|
|
3992
|
-
const patternsPath =
|
|
3993
|
-
if (!
|
|
3994
|
-
const indexText =
|
|
3995
|
-
const patternsText =
|
|
4039
|
+
const indexPath = import_node_path19.default.join(dir, "index.md");
|
|
4040
|
+
const patternsPath = import_node_path19.default.join(dir, "patterns.md");
|
|
4041
|
+
if (!import_node_fs12.default.existsSync(indexPath) || !import_node_fs12.default.existsSync(patternsPath)) continue;
|
|
4042
|
+
const indexText = import_node_fs12.default.readFileSync(indexPath, "utf-8");
|
|
4043
|
+
const patternsText = import_node_fs12.default.readFileSync(patternsPath, "utf-8");
|
|
3996
4044
|
const example_path = String(manifest.few_shot_examples ?? "");
|
|
3997
4045
|
for (const paragraph of indexText.split(/\n\n+/).map((p) => p.trim()).filter(Boolean)) {
|
|
3998
4046
|
chunks.push({
|
|
@@ -4033,9 +4081,9 @@ function buildRagIndex() {
|
|
|
4033
4081
|
}
|
|
4034
4082
|
function loadRagCache(current) {
|
|
4035
4083
|
const cachePath = getRagCachePath();
|
|
4036
|
-
if (!
|
|
4084
|
+
if (!import_node_fs12.default.existsSync(cachePath)) return null;
|
|
4037
4085
|
try {
|
|
4038
|
-
const payload = JSON.parse(
|
|
4086
|
+
const payload = JSON.parse(import_node_fs12.default.readFileSync(cachePath, "utf-8"));
|
|
4039
4087
|
if (payload.schema_version !== RAG_CACHE_SCHEMA_VERSION) return null;
|
|
4040
4088
|
if (JSON.stringify(payload.files_checksum) !== JSON.stringify(current)) return null;
|
|
4041
4089
|
return deserializeChunks(payload.chunks ?? []);
|
|
@@ -4050,7 +4098,7 @@ function saveRagCache(chunks, filesChecksum) {
|
|
|
4050
4098
|
files_checksum: filesChecksum,
|
|
4051
4099
|
chunks: serializeChunks(chunks)
|
|
4052
4100
|
};
|
|
4053
|
-
|
|
4101
|
+
import_node_fs12.default.writeFileSync(getRagCachePath(), JSON.stringify(payload), "utf-8");
|
|
4054
4102
|
} catch {
|
|
4055
4103
|
console.error("[runsec] RAG cache write failed; continuing in-memory only");
|
|
4056
4104
|
}
|
|
@@ -4214,7 +4262,7 @@ function selectSkillsForContext(opts) {
|
|
|
4214
4262
|
const query = [opts.question ?? "", opts.file_path ?? "", opts.file_content ?? ""].filter(Boolean).join("\n");
|
|
4215
4263
|
const semScores = query ? semanticSkillScores(query) : {};
|
|
4216
4264
|
const keywordBoosts = query ? skillBoosts(query) : {};
|
|
4217
|
-
const suffix =
|
|
4265
|
+
const suffix = import_node_path19.default.extname(opts.file_path ?? "").toLowerCase();
|
|
4218
4266
|
const hay = query.toLowerCase();
|
|
4219
4267
|
const ranked = [];
|
|
4220
4268
|
for (const [sid, manifest] of Object.entries(manifests)) {
|
|
@@ -4251,20 +4299,20 @@ function findPatternChunkByMetric(metricId) {
|
|
|
4251
4299
|
|
|
4252
4300
|
// src/skills/skillsApi.ts
|
|
4253
4301
|
function loadComplianceSnapshot() {
|
|
4254
|
-
const p =
|
|
4255
|
-
if (!
|
|
4302
|
+
const p = import_node_path20.default.join(getDataDirectory(), "rule-compliance-map.json");
|
|
4303
|
+
if (!import_node_fs13.default.existsSync(p)) return {};
|
|
4256
4304
|
try {
|
|
4257
|
-
return JSON.parse(
|
|
4305
|
+
return JSON.parse(import_node_fs13.default.readFileSync(p, "utf-8"));
|
|
4258
4306
|
} catch {
|
|
4259
4307
|
return {};
|
|
4260
4308
|
}
|
|
4261
4309
|
}
|
|
4262
4310
|
function extractTestbedExample(metricId, examplePath, skillsRoot) {
|
|
4263
4311
|
if (!examplePath) return "";
|
|
4264
|
-
const p =
|
|
4265
|
-
const resolved =
|
|
4266
|
-
if (!
|
|
4267
|
-
const lines =
|
|
4312
|
+
const p = import_node_path20.default.isAbsolute(examplePath) ? examplePath : import_node_path20.default.join(import_node_path20.default.dirname(skillsRoot), "..", "..", examplePath);
|
|
4313
|
+
const resolved = import_node_fs13.default.existsSync(p) ? p : import_node_path20.default.join(getDataDirectory(), "..", "..", examplePath);
|
|
4314
|
+
if (!import_node_fs13.default.existsSync(resolved)) return "";
|
|
4315
|
+
const lines = import_node_fs13.default.readFileSync(resolved, "utf-8").split(/\r?\n/);
|
|
4268
4316
|
const needle = `Vulnerable: ${metricId}`;
|
|
4269
4317
|
for (let idx = 0; idx < lines.length; idx += 1) {
|
|
4270
4318
|
if (lines[idx].includes(needle)) {
|
|
@@ -4316,16 +4364,16 @@ function getSkillContext(args) {
|
|
|
4316
4364
|
}
|
|
4317
4365
|
const manifest = manifests[skill_id];
|
|
4318
4366
|
const dir = skillDirectory(manifest);
|
|
4319
|
-
const indexPath =
|
|
4320
|
-
const patternsPath =
|
|
4321
|
-
if (!
|
|
4367
|
+
const indexPath = import_node_path20.default.join(dir, "index.md");
|
|
4368
|
+
const patternsPath = import_node_path20.default.join(dir, "patterns.md");
|
|
4369
|
+
if (!import_node_fs13.default.existsSync(indexPath) || !import_node_fs13.default.existsSync(patternsPath)) {
|
|
4322
4370
|
return {
|
|
4323
4371
|
error: `incomplete skill data for ${skill_id}`,
|
|
4324
|
-
index_exists:
|
|
4325
|
-
patterns_exists:
|
|
4372
|
+
index_exists: import_node_fs13.default.existsSync(indexPath),
|
|
4373
|
+
patterns_exists: import_node_fs13.default.existsSync(patternsPath)
|
|
4326
4374
|
};
|
|
4327
4375
|
}
|
|
4328
|
-
const parsedRows = parsePatternRows(
|
|
4376
|
+
const parsedRows = parsePatternRows(import_node_fs13.default.readFileSync(patternsPath, "utf-8"));
|
|
4329
4377
|
const grouped = {};
|
|
4330
4378
|
for (const row of parsedRows) {
|
|
4331
4379
|
const stack = row.stack.trim() || "Generic";
|
|
@@ -4338,13 +4386,13 @@ function getSkillContext(args) {
|
|
|
4338
4386
|
source: row.source
|
|
4339
4387
|
});
|
|
4340
4388
|
}
|
|
4341
|
-
const skillsRoot =
|
|
4389
|
+
const skillsRoot = import_node_path20.default.dirname(dir);
|
|
4342
4390
|
const response = {
|
|
4343
4391
|
skill_id,
|
|
4344
|
-
index_path:
|
|
4345
|
-
patterns_path:
|
|
4346
|
-
index_md:
|
|
4347
|
-
patterns_md:
|
|
4392
|
+
index_path: import_node_path20.default.relative(getDataDirectory(), indexPath).replace(/\\/g, "/"),
|
|
4393
|
+
patterns_path: import_node_path20.default.relative(getDataDirectory(), patternsPath).replace(/\\/g, "/"),
|
|
4394
|
+
index_md: import_node_fs13.default.readFileSync(indexPath, "utf-8"),
|
|
4395
|
+
patterns_md: import_node_fs13.default.readFileSync(patternsPath, "utf-8"),
|
|
4348
4396
|
patterns_by_stack: Object.fromEntries(Object.keys(grouped).sort().map((k) => [k, grouped[k]])),
|
|
4349
4397
|
agent_system_insert: ANTI_HALLUCINATION_PROMPT,
|
|
4350
4398
|
compliance_snapshot: loadComplianceSnapshot()
|
|
@@ -4384,7 +4432,7 @@ function askGuidance(question) {
|
|
|
4384
4432
|
if (!q) return { error: "question is required" };
|
|
4385
4433
|
const best = semanticSearch(q, 50, "pattern", true);
|
|
4386
4434
|
const manifests = loadSkillManifests();
|
|
4387
|
-
const skillsRoot =
|
|
4435
|
+
const skillsRoot = import_node_path20.default.join(getDataDirectory(), "skills");
|
|
4388
4436
|
const required = requiredMetricIds(q);
|
|
4389
4437
|
const out = [];
|
|
4390
4438
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -4447,7 +4495,7 @@ var STRIDE_LABELS = {
|
|
|
4447
4495
|
};
|
|
4448
4496
|
function readTextSafe(filePath, limit = 4e5) {
|
|
4449
4497
|
try {
|
|
4450
|
-
const data =
|
|
4498
|
+
const data = import_node_fs14.default.readFileSync(filePath, "utf-8");
|
|
4451
4499
|
return data.length > limit ? data.slice(0, limit) : data;
|
|
4452
4500
|
} catch {
|
|
4453
4501
|
return "";
|
|
@@ -4487,13 +4535,13 @@ function walkFiles2(root, opts) {
|
|
|
4487
4535
|
let dir;
|
|
4488
4536
|
try {
|
|
4489
4537
|
dir = stack.pop();
|
|
4490
|
-
const entries =
|
|
4538
|
+
const entries = import_node_fs14.default.readdirSync(dir, { withFileTypes: true });
|
|
4491
4539
|
for (const ent of entries) {
|
|
4492
4540
|
if (out.length >= max) break;
|
|
4493
|
-
const full =
|
|
4541
|
+
const full = import_node_path21.default.join(dir, ent.name);
|
|
4494
4542
|
if (ent.isDirectory()) {
|
|
4495
4543
|
if (!skip.has(ent.name)) stack.push(full);
|
|
4496
|
-
} else if (ent.isFile() && exts.has(
|
|
4544
|
+
} else if (ent.isFile() && exts.has(import_node_path21.default.extname(ent.name).toLowerCase())) {
|
|
4497
4545
|
out.push(full);
|
|
4498
4546
|
}
|
|
4499
4547
|
}
|
|
@@ -4534,7 +4582,7 @@ function syftPackages(payload) {
|
|
|
4534
4582
|
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
4535
4583
|
}
|
|
4536
4584
|
function collectRepoThreatSignals(scanRoot, syftPayload) {
|
|
4537
|
-
const root =
|
|
4585
|
+
const root = import_node_path21.default.resolve(scanRoot);
|
|
4538
4586
|
const scanRel = ".";
|
|
4539
4587
|
const signals = {
|
|
4540
4588
|
scan_root: scanRel,
|
|
@@ -4556,7 +4604,7 @@ function collectRepoThreatSignals(scanRoot, syftPayload) {
|
|
|
4556
4604
|
}
|
|
4557
4605
|
};
|
|
4558
4606
|
try {
|
|
4559
|
-
for (const ent of
|
|
4607
|
+
for (const ent of import_node_fs14.default.readdirSync(root, { withFileTypes: true })) {
|
|
4560
4608
|
if (ent.isDirectory() && !ent.name.startsWith(".")) {
|
|
4561
4609
|
signals.top_level_dirs.push(ent.name);
|
|
4562
4610
|
if (signals.top_level_dirs.length >= 40) break;
|
|
@@ -4575,16 +4623,16 @@ function collectRepoThreatSignals(scanRoot, syftPayload) {
|
|
|
4575
4623
|
const dir = stack.pop();
|
|
4576
4624
|
let entries;
|
|
4577
4625
|
try {
|
|
4578
|
-
entries =
|
|
4626
|
+
entries = import_node_fs14.default.readdirSync(dir, { withFileTypes: true });
|
|
4579
4627
|
} catch {
|
|
4580
4628
|
continue;
|
|
4581
4629
|
}
|
|
4582
4630
|
for (const ent of entries) {
|
|
4583
|
-
const full =
|
|
4631
|
+
const full = import_node_path21.default.join(dir, ent.name);
|
|
4584
4632
|
if (ent.isDirectory()) {
|
|
4585
4633
|
if (!skip.has(ent.name) && !ent.name.startsWith(".")) stack.push(full);
|
|
4586
4634
|
} else if (ent.isFile() && patternNames.includes(ent.name)) {
|
|
4587
|
-
const rel =
|
|
4635
|
+
const rel = import_node_path21.default.relative(root, full).replace(/\\/g, "/");
|
|
4588
4636
|
if (!rel.startsWith("..")) handler(full, rel);
|
|
4589
4637
|
}
|
|
4590
4638
|
}
|
|
@@ -4596,30 +4644,30 @@ function collectRepoThreatSignals(scanRoot, syftPayload) {
|
|
|
4596
4644
|
});
|
|
4597
4645
|
globWalk(["requirements.txt", "pyproject.toml", "go.mod"], (full, rel) => {
|
|
4598
4646
|
addKey(rel);
|
|
4599
|
-
if (
|
|
4647
|
+
if (import_node_path21.default.basename(full) === "requirements.txt") {
|
|
4600
4648
|
for (const d of parseRequirementsDeps(full)) deps.add(d);
|
|
4601
|
-
} else if (
|
|
4649
|
+
} else if (import_node_path21.default.basename(full) === "pyproject.toml") {
|
|
4602
4650
|
const txt = readTextSafe(full, 8e4).toLowerCase();
|
|
4603
4651
|
for (const m of txt.matchAll(/['"]([a-zA-Z0-9_.\-]+)['"]/g)) deps.add(m[1].toLowerCase());
|
|
4604
4652
|
}
|
|
4605
4653
|
});
|
|
4606
4654
|
for (const name of ["Dockerfile", "docker-compose.yml", "docker-compose.yaml"]) {
|
|
4607
|
-
const fp =
|
|
4608
|
-
if (
|
|
4655
|
+
const fp = import_node_path21.default.join(root, name);
|
|
4656
|
+
if (import_node_fs14.default.existsSync(fp)) {
|
|
4609
4657
|
addKey(name);
|
|
4610
4658
|
if (name === "Dockerfile") signals.flags.has_dockerfile = true;
|
|
4611
4659
|
else signals.flags.has_compose = true;
|
|
4612
4660
|
}
|
|
4613
4661
|
}
|
|
4614
4662
|
for (const full of walkFiles2(root, { maxFiles: 80, extensions: /* @__PURE__ */ new Set([".yaml", ".yml"]) })) {
|
|
4615
|
-
const base =
|
|
4663
|
+
const base = import_node_path21.default.basename(full).toLowerCase();
|
|
4616
4664
|
if (base.includes("deploy") || base.includes("helm") || base.includes("k8s") || base.includes("values")) {
|
|
4617
4665
|
signals.flags.has_k8s_yaml = true;
|
|
4618
|
-
addKey(
|
|
4666
|
+
addKey(import_node_path21.default.relative(root, full).replace(/\\/g, "/"));
|
|
4619
4667
|
break;
|
|
4620
4668
|
}
|
|
4621
4669
|
}
|
|
4622
|
-
if (
|
|
4670
|
+
if (import_node_fs14.default.existsSync(import_node_path21.default.join(root, ".github", "workflows"))) {
|
|
4623
4671
|
signals.flags.has_github_workflows = true;
|
|
4624
4672
|
}
|
|
4625
4673
|
for (const pkg of signals.syft_packages) {
|
|
@@ -4651,12 +4699,12 @@ ${context}
|
|
|
4651
4699
|
${repoFp}`).digest("hex");
|
|
4652
4700
|
}
|
|
4653
4701
|
function loadThreatModelCache(workspaceRoot) {
|
|
4654
|
-
const p =
|
|
4655
|
-
if (!
|
|
4702
|
+
const p = import_node_path21.default.join(workspaceRoot, THREAT_MODEL_CACHE_FILE);
|
|
4703
|
+
if (!import_node_fs14.default.existsSync(p)) {
|
|
4656
4704
|
return { schema_version: THREAT_MODEL_CACHE_SCHEMA_VERSION, entries: {} };
|
|
4657
4705
|
}
|
|
4658
4706
|
try {
|
|
4659
|
-
const data = JSON.parse(
|
|
4707
|
+
const data = JSON.parse(import_node_fs14.default.readFileSync(p, "utf-8"));
|
|
4660
4708
|
if (data.schema_version !== THREAT_MODEL_CACHE_SCHEMA_VERSION) {
|
|
4661
4709
|
return { schema_version: THREAT_MODEL_CACHE_SCHEMA_VERSION, entries: {} };
|
|
4662
4710
|
}
|
|
@@ -4667,7 +4715,7 @@ function loadThreatModelCache(workspaceRoot) {
|
|
|
4667
4715
|
}
|
|
4668
4716
|
}
|
|
4669
4717
|
function saveThreatModelCache(workspaceRoot, cacheKey, markdown, baseline) {
|
|
4670
|
-
const p =
|
|
4718
|
+
const p = import_node_path21.default.join(workspaceRoot, THREAT_MODEL_CACHE_FILE);
|
|
4671
4719
|
const payload = loadThreatModelCache(workspaceRoot);
|
|
4672
4720
|
payload.entries[cacheKey] = {
|
|
4673
4721
|
markdown,
|
|
@@ -4676,7 +4724,7 @@ function saveThreatModelCache(workspaceRoot, cacheKey, markdown, baseline) {
|
|
|
4676
4724
|
repo_fingerprint: String(baseline.repo_fingerprint ?? "")
|
|
4677
4725
|
};
|
|
4678
4726
|
payload.baseline = baseline;
|
|
4679
|
-
|
|
4727
|
+
import_node_fs14.default.writeFileSync(p, JSON.stringify(payload, null, 2), "utf-8");
|
|
4680
4728
|
return p;
|
|
4681
4729
|
}
|
|
4682
4730
|
function strideClassifyClause(clause) {
|
|
@@ -4880,7 +4928,7 @@ function loadRepoCrosscheckHaystack(scanRoot, maxChars = 35e4) {
|
|
|
4880
4928
|
let total = 0;
|
|
4881
4929
|
const files = walkFiles2(scanRoot, { maxFiles: 220 });
|
|
4882
4930
|
for (const full of files) {
|
|
4883
|
-
const rel =
|
|
4931
|
+
const rel = import_node_path21.default.relative(scanRoot, full).replace(/\\/g, "/");
|
|
4884
4932
|
const snippet = readTextSafe(full, 14e3);
|
|
4885
4933
|
const block = `
|
|
4886
4934
|
--- ${rel} ---
|
|
@@ -5090,8 +5138,8 @@ function generateThreatModelMarkdown(profile, baseline, signals, ragKeywords, ca
|
|
|
5090
5138
|
`;
|
|
5091
5139
|
}
|
|
5092
5140
|
async function runThreatModelEngine(opts) {
|
|
5093
|
-
const workspaceRoot =
|
|
5094
|
-
const projectName = (opts.project_name ??
|
|
5141
|
+
const workspaceRoot = import_node_path21.default.resolve(opts.workspace_path);
|
|
5142
|
+
const projectName = (opts.project_name ?? import_node_path21.default.basename(workspaceRoot)).trim() || "project";
|
|
5095
5143
|
const userContext = (opts.context ?? "").trim();
|
|
5096
5144
|
const profile = detectSecurityProfile(projectName, userContext);
|
|
5097
5145
|
const baseline = ARCHITECTURE_BASELINES[profile];
|
|
@@ -5106,13 +5154,13 @@ Project: ${projectName}`;
|
|
|
5106
5154
|
const cache = loadThreatModelCache(workspaceRoot);
|
|
5107
5155
|
const cached = cache.entries[cacheKey];
|
|
5108
5156
|
if (cached?.markdown) {
|
|
5109
|
-
const reportPath2 =
|
|
5110
|
-
|
|
5157
|
+
const reportPath2 = import_node_path21.default.join(workspaceRoot, THREAT_MODEL_REPORT_FILE);
|
|
5158
|
+
import_node_fs14.default.writeFileSync(reportPath2, cached.markdown, "utf-8");
|
|
5111
5159
|
return {
|
|
5112
5160
|
profile,
|
|
5113
5161
|
markdown: cached.markdown,
|
|
5114
5162
|
report_path: reportPath2,
|
|
5115
|
-
cache_path:
|
|
5163
|
+
cache_path: import_node_path21.default.join(workspaceRoot, THREAT_MODEL_CACHE_FILE),
|
|
5116
5164
|
cache_hit: true,
|
|
5117
5165
|
cache_key: cacheKey,
|
|
5118
5166
|
repo_fingerprint: repoFp,
|
|
@@ -5144,8 +5192,8 @@ Project: ${projectName}`;
|
|
|
5144
5192
|
rag_keywords: [...ragKeywords].sort().slice(0, 40)
|
|
5145
5193
|
};
|
|
5146
5194
|
const cachePath = saveThreatModelCache(workspaceRoot, cacheKey, markdown, baselinePayload);
|
|
5147
|
-
const reportPath =
|
|
5148
|
-
|
|
5195
|
+
const reportPath = import_node_path21.default.join(workspaceRoot, THREAT_MODEL_REPORT_FILE);
|
|
5196
|
+
import_node_fs14.default.writeFileSync(reportPath, markdown, "utf-8");
|
|
5149
5197
|
return {
|
|
5150
5198
|
profile,
|
|
5151
5199
|
markdown,
|
|
@@ -5360,18 +5408,18 @@ function buildSecurityReviewMarkdown(opts) {
|
|
|
5360
5408
|
return out.join("\n");
|
|
5361
5409
|
}
|
|
5362
5410
|
function resolveSecurityReviewPath(workspacePath) {
|
|
5363
|
-
const base = workspacePath?.trim() ?
|
|
5364
|
-
return
|
|
5411
|
+
const base = workspacePath?.trim() ? import_node_path22.default.resolve(workspacePath) : process.cwd();
|
|
5412
|
+
return import_node_path22.default.join(base, SECURITY_REVIEW_REPORT_FILE);
|
|
5365
5413
|
}
|
|
5366
5414
|
function writeSecurityReviewReport(markdown, workspacePath) {
|
|
5367
5415
|
const reportPath = resolveSecurityReviewPath(workspacePath);
|
|
5368
|
-
|
|
5416
|
+
import_node_fs15.default.writeFileSync(reportPath, markdown, "utf-8");
|
|
5369
5417
|
console.error(`[runsec] wrote security review to: ${reportPath}`);
|
|
5370
5418
|
return reportPath;
|
|
5371
5419
|
}
|
|
5372
5420
|
async function executeSecurityReview(opts) {
|
|
5373
|
-
const workspaceRoot =
|
|
5374
|
-
const projectName = (opts.project_name ??
|
|
5421
|
+
const workspaceRoot = import_node_path22.default.resolve(opts.workspace_path);
|
|
5422
|
+
const projectName = (opts.project_name ?? import_node_path22.default.basename(workspaceRoot)).trim() || import_node_path22.default.basename(workspaceRoot);
|
|
5375
5423
|
console.error("[runsec] security review: starting unified scan + STRIDE threat model");
|
|
5376
5424
|
const [audit, threatModel] = await Promise.all([
|
|
5377
5425
|
executeAudit("runsec_audit_general", {
|
|
@@ -5404,20 +5452,20 @@ async function executeSecurityReview(opts) {
|
|
|
5404
5452
|
}
|
|
5405
5453
|
|
|
5406
5454
|
// src/engine/remediation.ts
|
|
5407
|
-
var
|
|
5408
|
-
var
|
|
5455
|
+
var import_node_fs17 = __toESM(require("fs"));
|
|
5456
|
+
var import_node_path24 = __toESM(require("path"));
|
|
5409
5457
|
|
|
5410
5458
|
// src/skills/metricLookup.ts
|
|
5411
|
-
var
|
|
5412
|
-
var
|
|
5459
|
+
var import_node_fs16 = __toESM(require("fs"));
|
|
5460
|
+
var import_node_path23 = __toESM(require("path"));
|
|
5413
5461
|
function findMetricRow(metricId) {
|
|
5414
5462
|
const mid = metricId.trim().toUpperCase();
|
|
5415
5463
|
if (!mid) return null;
|
|
5416
5464
|
const manifests = loadSkillManifests();
|
|
5417
5465
|
for (const [, manifest] of Object.entries(manifests)) {
|
|
5418
|
-
const patternsPath =
|
|
5419
|
-
if (!
|
|
5420
|
-
const rows = parsePatternRows(
|
|
5466
|
+
const patternsPath = import_node_path23.default.join(skillDirectory(manifest), "patterns.md");
|
|
5467
|
+
if (!import_node_fs16.default.existsSync(patternsPath)) continue;
|
|
5468
|
+
const rows = parsePatternRows(import_node_fs16.default.readFileSync(patternsPath, "utf-8"));
|
|
5421
5469
|
const row = rows.find((r) => r.metric_id.toUpperCase() === mid);
|
|
5422
5470
|
if (row) return row;
|
|
5423
5471
|
}
|
|
@@ -5430,12 +5478,12 @@ function normalizeRelPath2(p) {
|
|
|
5430
5478
|
return p.replace(/\\/g, "/").replace(/^\.\/+/, "");
|
|
5431
5479
|
}
|
|
5432
5480
|
function resolveTargetFile(workspaceRoot, filePath) {
|
|
5433
|
-
const root =
|
|
5481
|
+
const root = import_node_path24.default.resolve(workspaceRoot);
|
|
5434
5482
|
const rel = normalizeRelPath2(filePath.trim());
|
|
5435
5483
|
if (!rel) return { error: "file_path is required" };
|
|
5436
|
-
const abs =
|
|
5437
|
-
const relFromRoot =
|
|
5438
|
-
if (relFromRoot.startsWith("..") ||
|
|
5484
|
+
const abs = import_node_path24.default.resolve(root, rel);
|
|
5485
|
+
const relFromRoot = import_node_path24.default.relative(root, abs).replace(/\\/g, "/");
|
|
5486
|
+
if (relFromRoot.startsWith("..") || import_node_path24.default.isAbsolute(relFromRoot)) {
|
|
5439
5487
|
return { error: `file_path must be inside workspace: ${root}` };
|
|
5440
5488
|
}
|
|
5441
5489
|
for (const seg of BLOCKED_PATH_SEGMENTS) {
|
|
@@ -5443,8 +5491,8 @@ function resolveTargetFile(workspaceRoot, filePath) {
|
|
|
5443
5491
|
return { error: `refusing to modify path under blocked segment: ${seg}` };
|
|
5444
5492
|
}
|
|
5445
5493
|
}
|
|
5446
|
-
if (!
|
|
5447
|
-
if (!
|
|
5494
|
+
if (!import_node_fs17.default.existsSync(abs)) return { error: `path does not exist: ${relFromRoot}` };
|
|
5495
|
+
if (!import_node_fs17.default.statSync(abs).isFile()) return { error: `path is not a file: ${relFromRoot}` };
|
|
5448
5496
|
return { abs, rel: relFromRoot };
|
|
5449
5497
|
}
|
|
5450
5498
|
function normalizeNewlines(text) {
|
|
@@ -5511,12 +5559,12 @@ function locateTargetInFile(content, target) {
|
|
|
5511
5559
|
}
|
|
5512
5560
|
function writeBackup(absPath) {
|
|
5513
5561
|
const backupPath = `${absPath}.bak`;
|
|
5514
|
-
|
|
5562
|
+
import_node_fs17.default.copyFileSync(absPath, backupPath);
|
|
5515
5563
|
return backupPath;
|
|
5516
5564
|
}
|
|
5517
5565
|
function applyDvs001Fix(absPath) {
|
|
5518
|
-
const rel =
|
|
5519
|
-
const lines =
|
|
5566
|
+
const rel = import_node_path24.default.basename(absPath);
|
|
5567
|
+
const lines = import_node_fs17.default.readFileSync(absPath, "utf-8").split(/\r?\n/);
|
|
5520
5568
|
const newLines = lines.filter((ln) => !/^\s*user\s+root\s*$/i.test(ln.trim()));
|
|
5521
5569
|
const hasNonRootUser = newLines.some(
|
|
5522
5570
|
(ln) => /^\s*user\s+\S+\s*$/i.test(ln.trim()) && !/^\s*user\s+root\s*$/i.test(ln.trim())
|
|
@@ -5542,7 +5590,7 @@ function applyDvs001Fix(absPath) {
|
|
|
5542
5590
|
};
|
|
5543
5591
|
}
|
|
5544
5592
|
const backupPath = writeBackup(absPath);
|
|
5545
|
-
|
|
5593
|
+
import_node_fs17.default.writeFileSync(absPath, `${newLines.join("\n")}
|
|
5546
5594
|
`, "utf-8");
|
|
5547
5595
|
return {
|
|
5548
5596
|
status: "fixed",
|
|
@@ -5619,7 +5667,7 @@ function applyRemediation(args) {
|
|
|
5619
5667
|
fix_template: row.fix_template
|
|
5620
5668
|
};
|
|
5621
5669
|
}
|
|
5622
|
-
const original =
|
|
5670
|
+
const original = import_node_fs17.default.readFileSync(resolved.abs, "utf-8");
|
|
5623
5671
|
const { index, count } = locateTargetInFile(original, target);
|
|
5624
5672
|
if (count === 0) {
|
|
5625
5673
|
return {
|
|
@@ -5646,7 +5694,7 @@ function applyRemediation(args) {
|
|
|
5646
5694
|
const backupPath = writeBackup(resolved.abs);
|
|
5647
5695
|
const ending = original.includes("\r\n") ? "\r\n" : "\n";
|
|
5648
5696
|
const outText = updated.split("\n").join(ending);
|
|
5649
|
-
|
|
5697
|
+
import_node_fs17.default.writeFileSync(resolved.abs, outText.endsWith(ending) || !original.endsWith(ending) ? outText : outText + ending, "utf-8");
|
|
5650
5698
|
return {
|
|
5651
5699
|
status: "fixed",
|
|
5652
5700
|
message: "Remediation applied after exact target verification; backup created",
|
|
@@ -5848,7 +5896,7 @@ function isRemediationTool(name) {
|
|
|
5848
5896
|
}
|
|
5849
5897
|
|
|
5850
5898
|
// src/hubUploadUrl.ts
|
|
5851
|
-
var
|
|
5899
|
+
var import_node_fs18 = __toESM(require("fs"));
|
|
5852
5900
|
var HUB_UPLOAD_REPORT_PATH = "/api/mcp/upload-report";
|
|
5853
5901
|
var DEFAULT_PRODUCTION_HUB_ORIGIN = "https://runsec.io";
|
|
5854
5902
|
var DEFAULT_DOCKER_INTERNAL_HUB_ORIGIN = "http://frontend:3000";
|
|
@@ -5885,7 +5933,7 @@ function colocatedHubOrigin() {
|
|
|
5885
5933
|
}
|
|
5886
5934
|
function isDockerRuntime() {
|
|
5887
5935
|
try {
|
|
5888
|
-
return
|
|
5936
|
+
return import_node_fs18.default.existsSync("/.dockerenv");
|
|
5889
5937
|
} catch {
|
|
5890
5938
|
return false;
|
|
5891
5939
|
}
|
|
@@ -6134,7 +6182,15 @@ function postHubUploadNodeHttp(url, apiKey, payload) {
|
|
|
6134
6182
|
req.end();
|
|
6135
6183
|
});
|
|
6136
6184
|
}
|
|
6185
|
+
function preferNodeHttpUpload() {
|
|
6186
|
+
if (process.platform === "win32") return true;
|
|
6187
|
+
if (process.env.RUNSEC_HUB_USE_NODE_HTTP === "1") return true;
|
|
6188
|
+
return false;
|
|
6189
|
+
}
|
|
6137
6190
|
async function postHubUpload(url, apiKey, payload) {
|
|
6191
|
+
if (preferNodeHttpUpload()) {
|
|
6192
|
+
return postHubUploadNodeHttp(url, apiKey, payload);
|
|
6193
|
+
}
|
|
6138
6194
|
try {
|
|
6139
6195
|
return await fetch(url, {
|
|
6140
6196
|
method: "POST",
|
|
@@ -6601,7 +6657,9 @@ CRITICAL INSTRUCTIONS FOR LLM:
|
|
|
6601
6657
|
async function main() {
|
|
6602
6658
|
const key = getApiKey();
|
|
6603
6659
|
process.env.RUNSEC_API_KEY = key;
|
|
6604
|
-
console.error(
|
|
6660
|
+
console.error(
|
|
6661
|
+
`[runsec] MCP @runsec/mcp v${RUNSEC_MCP_VERSION} starting \u2014 Hub upload target: ${resolveHubUploadUrl()}`
|
|
6662
|
+
);
|
|
6605
6663
|
await verifyApiKey(key);
|
|
6606
6664
|
const summary = validateRules();
|
|
6607
6665
|
console.error("Rules registry validated:", summary);
|