@runsec/mcp 1.0.80 → 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 +182 -119
- package/package.json +2 -1
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({
|
|
@@ -3287,8 +3318,29 @@ async function executeAudit(toolName, args) {
|
|
|
3287
3318
|
}
|
|
3288
3319
|
|
|
3289
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
|
|
3290
3325
|
var import_node_fs9 = __toESM(require("fs"));
|
|
3291
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
|
|
3292
3344
|
var REPORT_SECTION_TITLES = {
|
|
3293
3345
|
code: "Code Vulnerabilities",
|
|
3294
3346
|
secrets: "Exposed Secrets",
|
|
@@ -3381,7 +3433,7 @@ function shortRuleLabel(ruleId) {
|
|
|
3381
3433
|
return parts[parts.length - 1] || ruleId;
|
|
3382
3434
|
}
|
|
3383
3435
|
function snippetLanguage(filePath) {
|
|
3384
|
-
const ext =
|
|
3436
|
+
const ext = import_node_path16.default.extname(filePath).replace(/^\./, "").toLowerCase();
|
|
3385
3437
|
const map = {
|
|
3386
3438
|
yml: "yaml",
|
|
3387
3439
|
yaml: "yaml",
|
|
@@ -3483,9 +3535,9 @@ var TECH_STACK_BY_EXT = {
|
|
|
3483
3535
|
};
|
|
3484
3536
|
function techStackFromFilePath(filePath) {
|
|
3485
3537
|
const normalized = String(filePath ?? "").replace(/\\/g, "/");
|
|
3486
|
-
const base =
|
|
3538
|
+
const base = import_node_path16.default.basename(normalized).toLowerCase();
|
|
3487
3539
|
if (base === "dockerfile" || base.startsWith("dockerfile.")) return "Docker";
|
|
3488
|
-
const ext =
|
|
3540
|
+
const ext = import_node_path16.default.extname(normalized).toLowerCase();
|
|
3489
3541
|
return TECH_STACK_BY_EXT[ext] ?? "Other";
|
|
3490
3542
|
}
|
|
3491
3543
|
function countFindingsByTechStack(findings) {
|
|
@@ -3679,6 +3731,7 @@ function buildServerSideReportMarkdown(standard, findings, metrics) {
|
|
|
3679
3731
|
out.push(`# RunSec Unified Security Report`);
|
|
3680
3732
|
out.push("");
|
|
3681
3733
|
out.push(`**Generated at:** ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
3734
|
+
out.push(`**MCP package:** \`@runsec/mcp@${safeText(RUNSEC_MCP_VERSION)}\``);
|
|
3682
3735
|
out.push(`**Standard:** ${safeText(standard)}`);
|
|
3683
3736
|
out.push(`**X-RunSec-Verdict:** \`${safeText(verdictLabel)}\`${metrics.verdict?.is_safe === false && metrics.verdict.fail_reason ? ` \u2014 ${safeText(metrics.verdict.fail_reason)}` : ""}`);
|
|
3684
3737
|
out.push(
|
|
@@ -3748,15 +3801,15 @@ function buildServerSideReportMarkdown(standard, findings, metrics) {
|
|
|
3748
3801
|
return finalizeReportMarkdown(out.join("\n"));
|
|
3749
3802
|
}
|
|
3750
3803
|
function resolveReportPath(workspacePath) {
|
|
3751
|
-
const base = workspacePath?.trim() ?
|
|
3752
|
-
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");
|
|
3753
3806
|
}
|
|
3754
3807
|
function generateMarkdownReport(standard, findings, metrics, workspacePath) {
|
|
3755
3808
|
const m = metrics || {};
|
|
3756
3809
|
const rows = Array.isArray(findings) ? findings : [];
|
|
3757
3810
|
const reportContent = finalizeReportMarkdown(buildServerSideReportMarkdown(standard, rows, m));
|
|
3758
3811
|
const reportPath = resolveReportPath(workspacePath);
|
|
3759
|
-
|
|
3812
|
+
import_node_fs10.default.writeFileSync(reportPath, reportContent, "utf-8");
|
|
3760
3813
|
console.error(`[runsec] wrote unified report to: ${reportPath}`);
|
|
3761
3814
|
console.error(`[runsec] X-RunSec-Verdict: ${m.verdict?.http_headers?.["X-RunSec-Verdict"] ?? m.verdict?.status ?? "PASS"}`);
|
|
3762
3815
|
return `
|
|
@@ -3771,17 +3824,17 @@ Simply confirm that the scan is complete and the report is saved at the path abo
|
|
|
3771
3824
|
}
|
|
3772
3825
|
|
|
3773
3826
|
// src/engine/reviewFormatter.ts
|
|
3774
|
-
var
|
|
3775
|
-
var
|
|
3827
|
+
var import_node_fs15 = __toESM(require("fs"));
|
|
3828
|
+
var import_node_path22 = __toESM(require("path"));
|
|
3776
3829
|
|
|
3777
3830
|
// src/engine/threatModelEngine.ts
|
|
3778
3831
|
var import_node_crypto4 = require("crypto");
|
|
3779
|
-
var
|
|
3780
|
-
var
|
|
3832
|
+
var import_node_fs14 = __toESM(require("fs"));
|
|
3833
|
+
var import_node_path21 = __toESM(require("path"));
|
|
3781
3834
|
|
|
3782
3835
|
// src/skills/skillsApi.ts
|
|
3783
|
-
var
|
|
3784
|
-
var
|
|
3836
|
+
var import_node_fs13 = __toESM(require("fs"));
|
|
3837
|
+
var import_node_path20 = __toESM(require("path"));
|
|
3785
3838
|
|
|
3786
3839
|
// src/skills/patternParser.ts
|
|
3787
3840
|
var METRIC_ID_RE = /^[A-Z0-9]{2,4}-[0-9A-Za-z.\-]+$/;
|
|
@@ -3854,43 +3907,43 @@ function parsePatternRows(patternsText) {
|
|
|
3854
3907
|
}
|
|
3855
3908
|
|
|
3856
3909
|
// src/skills/paths.ts
|
|
3857
|
-
var
|
|
3910
|
+
var import_node_path17 = __toESM(require("path"));
|
|
3858
3911
|
var RUNSEC_RELEASE_VERSION = "v1.0";
|
|
3859
3912
|
var RAG_CACHE_SCHEMA_VERSION = 3;
|
|
3860
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.";
|
|
3861
3914
|
function getSkillsDirectory() {
|
|
3862
|
-
return
|
|
3915
|
+
return import_node_path17.default.join(getDataDirectory(), "skills");
|
|
3863
3916
|
}
|
|
3864
3917
|
function getRagCachePath() {
|
|
3865
|
-
return
|
|
3918
|
+
return import_node_path17.default.join(getDataDirectory(), ".rag-cache.json");
|
|
3866
3919
|
}
|
|
3867
3920
|
|
|
3868
3921
|
// src/skills/ragIndex.ts
|
|
3869
3922
|
var import_node_crypto3 = require("crypto");
|
|
3870
|
-
var
|
|
3871
|
-
var
|
|
3923
|
+
var import_node_fs12 = __toESM(require("fs"));
|
|
3924
|
+
var import_node_path19 = __toESM(require("path"));
|
|
3872
3925
|
|
|
3873
3926
|
// src/skills/skillLoader.ts
|
|
3874
|
-
var
|
|
3875
|
-
var
|
|
3927
|
+
var import_node_fs11 = __toESM(require("fs"));
|
|
3928
|
+
var import_node_path18 = __toESM(require("path"));
|
|
3876
3929
|
function loadSkillManifests() {
|
|
3877
3930
|
const skillsDir = getSkillsDirectory();
|
|
3878
3931
|
const manifests = {};
|
|
3879
|
-
if (!
|
|
3932
|
+
if (!import_node_fs11.default.existsSync(skillsDir)) {
|
|
3880
3933
|
return manifests;
|
|
3881
3934
|
}
|
|
3882
|
-
for (const entry of
|
|
3935
|
+
for (const entry of import_node_fs11.default.readdirSync(skillsDir, { withFileTypes: true })) {
|
|
3883
3936
|
if (!entry.isDirectory()) continue;
|
|
3884
|
-
const skillJson =
|
|
3885
|
-
if (!
|
|
3886
|
-
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"));
|
|
3887
3940
|
const sid = String(data.skill_id ?? entry.name);
|
|
3888
3941
|
manifests[sid] = { ...data, skill_id: sid, __dir_name: entry.name };
|
|
3889
3942
|
}
|
|
3890
3943
|
return manifests;
|
|
3891
3944
|
}
|
|
3892
3945
|
function skillDirectory(manifest) {
|
|
3893
|
-
return
|
|
3946
|
+
return import_node_path18.default.join(getSkillsDirectory(), manifest.__dir_name);
|
|
3894
3947
|
}
|
|
3895
3948
|
|
|
3896
3949
|
// src/skills/ragIndex.ts
|
|
@@ -3936,28 +3989,28 @@ function cosine(a, b) {
|
|
|
3936
3989
|
}
|
|
3937
3990
|
function hashFile(filePath) {
|
|
3938
3991
|
const h = (0, import_node_crypto3.createHash)("sha256");
|
|
3939
|
-
h.update(
|
|
3992
|
+
h.update(import_node_fs12.default.readFileSync(filePath));
|
|
3940
3993
|
return h.digest("hex");
|
|
3941
3994
|
}
|
|
3942
3995
|
function listSkillSourceFiles() {
|
|
3943
3996
|
const skillsDir = getSkillsDirectory();
|
|
3944
3997
|
const files = [];
|
|
3945
3998
|
const walk = (dir) => {
|
|
3946
|
-
for (const entry of
|
|
3947
|
-
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);
|
|
3948
4001
|
if (entry.isDirectory()) walk(full);
|
|
3949
4002
|
else if (entry.isFile()) files.push(full);
|
|
3950
4003
|
}
|
|
3951
4004
|
};
|
|
3952
|
-
if (
|
|
4005
|
+
if (import_node_fs12.default.existsSync(skillsDir)) walk(skillsDir);
|
|
3953
4006
|
return files.sort();
|
|
3954
4007
|
}
|
|
3955
4008
|
function computeFilesChecksum() {
|
|
3956
4009
|
const files = listSkillSourceFiles();
|
|
3957
4010
|
const entries = [];
|
|
3958
4011
|
for (const full of files) {
|
|
3959
|
-
const st =
|
|
3960
|
-
const rel =
|
|
4012
|
+
const st = import_node_fs12.default.statSync(full);
|
|
4013
|
+
const rel = import_node_path19.default.relative(getSkillsDirectory(), full).replace(/\\/g, "/");
|
|
3961
4014
|
const mtimeNs = typeof st.mtimeNs === "bigint" ? Number(st.mtimeNs) : Math.round(st.mtimeMs * 1e6);
|
|
3962
4015
|
entries.push({
|
|
3963
4016
|
path: rel,
|
|
@@ -3988,11 +4041,11 @@ function buildRagIndex() {
|
|
|
3988
4041
|
const manifests = loadSkillManifests();
|
|
3989
4042
|
for (const [skill_id, manifest] of Object.entries(manifests)) {
|
|
3990
4043
|
const dir = skillDirectory(manifest);
|
|
3991
|
-
const indexPath =
|
|
3992
|
-
const patternsPath =
|
|
3993
|
-
if (!
|
|
3994
|
-
const indexText =
|
|
3995
|
-
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");
|
|
3996
4049
|
const example_path = String(manifest.few_shot_examples ?? "");
|
|
3997
4050
|
for (const paragraph of indexText.split(/\n\n+/).map((p) => p.trim()).filter(Boolean)) {
|
|
3998
4051
|
chunks.push({
|
|
@@ -4033,9 +4086,9 @@ function buildRagIndex() {
|
|
|
4033
4086
|
}
|
|
4034
4087
|
function loadRagCache(current) {
|
|
4035
4088
|
const cachePath = getRagCachePath();
|
|
4036
|
-
if (!
|
|
4089
|
+
if (!import_node_fs12.default.existsSync(cachePath)) return null;
|
|
4037
4090
|
try {
|
|
4038
|
-
const payload = JSON.parse(
|
|
4091
|
+
const payload = JSON.parse(import_node_fs12.default.readFileSync(cachePath, "utf-8"));
|
|
4039
4092
|
if (payload.schema_version !== RAG_CACHE_SCHEMA_VERSION) return null;
|
|
4040
4093
|
if (JSON.stringify(payload.files_checksum) !== JSON.stringify(current)) return null;
|
|
4041
4094
|
return deserializeChunks(payload.chunks ?? []);
|
|
@@ -4050,7 +4103,7 @@ function saveRagCache(chunks, filesChecksum) {
|
|
|
4050
4103
|
files_checksum: filesChecksum,
|
|
4051
4104
|
chunks: serializeChunks(chunks)
|
|
4052
4105
|
};
|
|
4053
|
-
|
|
4106
|
+
import_node_fs12.default.writeFileSync(getRagCachePath(), JSON.stringify(payload), "utf-8");
|
|
4054
4107
|
} catch {
|
|
4055
4108
|
console.error("[runsec] RAG cache write failed; continuing in-memory only");
|
|
4056
4109
|
}
|
|
@@ -4214,7 +4267,7 @@ function selectSkillsForContext(opts) {
|
|
|
4214
4267
|
const query = [opts.question ?? "", opts.file_path ?? "", opts.file_content ?? ""].filter(Boolean).join("\n");
|
|
4215
4268
|
const semScores = query ? semanticSkillScores(query) : {};
|
|
4216
4269
|
const keywordBoosts = query ? skillBoosts(query) : {};
|
|
4217
|
-
const suffix =
|
|
4270
|
+
const suffix = import_node_path19.default.extname(opts.file_path ?? "").toLowerCase();
|
|
4218
4271
|
const hay = query.toLowerCase();
|
|
4219
4272
|
const ranked = [];
|
|
4220
4273
|
for (const [sid, manifest] of Object.entries(manifests)) {
|
|
@@ -4251,20 +4304,20 @@ function findPatternChunkByMetric(metricId) {
|
|
|
4251
4304
|
|
|
4252
4305
|
// src/skills/skillsApi.ts
|
|
4253
4306
|
function loadComplianceSnapshot() {
|
|
4254
|
-
const p =
|
|
4255
|
-
if (!
|
|
4307
|
+
const p = import_node_path20.default.join(getDataDirectory(), "rule-compliance-map.json");
|
|
4308
|
+
if (!import_node_fs13.default.existsSync(p)) return {};
|
|
4256
4309
|
try {
|
|
4257
|
-
return JSON.parse(
|
|
4310
|
+
return JSON.parse(import_node_fs13.default.readFileSync(p, "utf-8"));
|
|
4258
4311
|
} catch {
|
|
4259
4312
|
return {};
|
|
4260
4313
|
}
|
|
4261
4314
|
}
|
|
4262
4315
|
function extractTestbedExample(metricId, examplePath, skillsRoot) {
|
|
4263
4316
|
if (!examplePath) return "";
|
|
4264
|
-
const p =
|
|
4265
|
-
const resolved =
|
|
4266
|
-
if (!
|
|
4267
|
-
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/);
|
|
4268
4321
|
const needle = `Vulnerable: ${metricId}`;
|
|
4269
4322
|
for (let idx = 0; idx < lines.length; idx += 1) {
|
|
4270
4323
|
if (lines[idx].includes(needle)) {
|
|
@@ -4316,16 +4369,16 @@ function getSkillContext(args) {
|
|
|
4316
4369
|
}
|
|
4317
4370
|
const manifest = manifests[skill_id];
|
|
4318
4371
|
const dir = skillDirectory(manifest);
|
|
4319
|
-
const indexPath =
|
|
4320
|
-
const patternsPath =
|
|
4321
|
-
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)) {
|
|
4322
4375
|
return {
|
|
4323
4376
|
error: `incomplete skill data for ${skill_id}`,
|
|
4324
|
-
index_exists:
|
|
4325
|
-
patterns_exists:
|
|
4377
|
+
index_exists: import_node_fs13.default.existsSync(indexPath),
|
|
4378
|
+
patterns_exists: import_node_fs13.default.existsSync(patternsPath)
|
|
4326
4379
|
};
|
|
4327
4380
|
}
|
|
4328
|
-
const parsedRows = parsePatternRows(
|
|
4381
|
+
const parsedRows = parsePatternRows(import_node_fs13.default.readFileSync(patternsPath, "utf-8"));
|
|
4329
4382
|
const grouped = {};
|
|
4330
4383
|
for (const row of parsedRows) {
|
|
4331
4384
|
const stack = row.stack.trim() || "Generic";
|
|
@@ -4338,13 +4391,13 @@ function getSkillContext(args) {
|
|
|
4338
4391
|
source: row.source
|
|
4339
4392
|
});
|
|
4340
4393
|
}
|
|
4341
|
-
const skillsRoot =
|
|
4394
|
+
const skillsRoot = import_node_path20.default.dirname(dir);
|
|
4342
4395
|
const response = {
|
|
4343
4396
|
skill_id,
|
|
4344
|
-
index_path:
|
|
4345
|
-
patterns_path:
|
|
4346
|
-
index_md:
|
|
4347
|
-
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"),
|
|
4348
4401
|
patterns_by_stack: Object.fromEntries(Object.keys(grouped).sort().map((k) => [k, grouped[k]])),
|
|
4349
4402
|
agent_system_insert: ANTI_HALLUCINATION_PROMPT,
|
|
4350
4403
|
compliance_snapshot: loadComplianceSnapshot()
|
|
@@ -4384,7 +4437,7 @@ function askGuidance(question) {
|
|
|
4384
4437
|
if (!q) return { error: "question is required" };
|
|
4385
4438
|
const best = semanticSearch(q, 50, "pattern", true);
|
|
4386
4439
|
const manifests = loadSkillManifests();
|
|
4387
|
-
const skillsRoot =
|
|
4440
|
+
const skillsRoot = import_node_path20.default.join(getDataDirectory(), "skills");
|
|
4388
4441
|
const required = requiredMetricIds(q);
|
|
4389
4442
|
const out = [];
|
|
4390
4443
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -4447,7 +4500,7 @@ var STRIDE_LABELS = {
|
|
|
4447
4500
|
};
|
|
4448
4501
|
function readTextSafe(filePath, limit = 4e5) {
|
|
4449
4502
|
try {
|
|
4450
|
-
const data =
|
|
4503
|
+
const data = import_node_fs14.default.readFileSync(filePath, "utf-8");
|
|
4451
4504
|
return data.length > limit ? data.slice(0, limit) : data;
|
|
4452
4505
|
} catch {
|
|
4453
4506
|
return "";
|
|
@@ -4487,13 +4540,13 @@ function walkFiles2(root, opts) {
|
|
|
4487
4540
|
let dir;
|
|
4488
4541
|
try {
|
|
4489
4542
|
dir = stack.pop();
|
|
4490
|
-
const entries =
|
|
4543
|
+
const entries = import_node_fs14.default.readdirSync(dir, { withFileTypes: true });
|
|
4491
4544
|
for (const ent of entries) {
|
|
4492
4545
|
if (out.length >= max) break;
|
|
4493
|
-
const full =
|
|
4546
|
+
const full = import_node_path21.default.join(dir, ent.name);
|
|
4494
4547
|
if (ent.isDirectory()) {
|
|
4495
4548
|
if (!skip.has(ent.name)) stack.push(full);
|
|
4496
|
-
} else if (ent.isFile() && exts.has(
|
|
4549
|
+
} else if (ent.isFile() && exts.has(import_node_path21.default.extname(ent.name).toLowerCase())) {
|
|
4497
4550
|
out.push(full);
|
|
4498
4551
|
}
|
|
4499
4552
|
}
|
|
@@ -4534,7 +4587,7 @@ function syftPackages(payload) {
|
|
|
4534
4587
|
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
4535
4588
|
}
|
|
4536
4589
|
function collectRepoThreatSignals(scanRoot, syftPayload) {
|
|
4537
|
-
const root =
|
|
4590
|
+
const root = import_node_path21.default.resolve(scanRoot);
|
|
4538
4591
|
const scanRel = ".";
|
|
4539
4592
|
const signals = {
|
|
4540
4593
|
scan_root: scanRel,
|
|
@@ -4556,7 +4609,7 @@ function collectRepoThreatSignals(scanRoot, syftPayload) {
|
|
|
4556
4609
|
}
|
|
4557
4610
|
};
|
|
4558
4611
|
try {
|
|
4559
|
-
for (const ent of
|
|
4612
|
+
for (const ent of import_node_fs14.default.readdirSync(root, { withFileTypes: true })) {
|
|
4560
4613
|
if (ent.isDirectory() && !ent.name.startsWith(".")) {
|
|
4561
4614
|
signals.top_level_dirs.push(ent.name);
|
|
4562
4615
|
if (signals.top_level_dirs.length >= 40) break;
|
|
@@ -4575,16 +4628,16 @@ function collectRepoThreatSignals(scanRoot, syftPayload) {
|
|
|
4575
4628
|
const dir = stack.pop();
|
|
4576
4629
|
let entries;
|
|
4577
4630
|
try {
|
|
4578
|
-
entries =
|
|
4631
|
+
entries = import_node_fs14.default.readdirSync(dir, { withFileTypes: true });
|
|
4579
4632
|
} catch {
|
|
4580
4633
|
continue;
|
|
4581
4634
|
}
|
|
4582
4635
|
for (const ent of entries) {
|
|
4583
|
-
const full =
|
|
4636
|
+
const full = import_node_path21.default.join(dir, ent.name);
|
|
4584
4637
|
if (ent.isDirectory()) {
|
|
4585
4638
|
if (!skip.has(ent.name) && !ent.name.startsWith(".")) stack.push(full);
|
|
4586
4639
|
} else if (ent.isFile() && patternNames.includes(ent.name)) {
|
|
4587
|
-
const rel =
|
|
4640
|
+
const rel = import_node_path21.default.relative(root, full).replace(/\\/g, "/");
|
|
4588
4641
|
if (!rel.startsWith("..")) handler(full, rel);
|
|
4589
4642
|
}
|
|
4590
4643
|
}
|
|
@@ -4596,30 +4649,30 @@ function collectRepoThreatSignals(scanRoot, syftPayload) {
|
|
|
4596
4649
|
});
|
|
4597
4650
|
globWalk(["requirements.txt", "pyproject.toml", "go.mod"], (full, rel) => {
|
|
4598
4651
|
addKey(rel);
|
|
4599
|
-
if (
|
|
4652
|
+
if (import_node_path21.default.basename(full) === "requirements.txt") {
|
|
4600
4653
|
for (const d of parseRequirementsDeps(full)) deps.add(d);
|
|
4601
|
-
} else if (
|
|
4654
|
+
} else if (import_node_path21.default.basename(full) === "pyproject.toml") {
|
|
4602
4655
|
const txt = readTextSafe(full, 8e4).toLowerCase();
|
|
4603
4656
|
for (const m of txt.matchAll(/['"]([a-zA-Z0-9_.\-]+)['"]/g)) deps.add(m[1].toLowerCase());
|
|
4604
4657
|
}
|
|
4605
4658
|
});
|
|
4606
4659
|
for (const name of ["Dockerfile", "docker-compose.yml", "docker-compose.yaml"]) {
|
|
4607
|
-
const fp =
|
|
4608
|
-
if (
|
|
4660
|
+
const fp = import_node_path21.default.join(root, name);
|
|
4661
|
+
if (import_node_fs14.default.existsSync(fp)) {
|
|
4609
4662
|
addKey(name);
|
|
4610
4663
|
if (name === "Dockerfile") signals.flags.has_dockerfile = true;
|
|
4611
4664
|
else signals.flags.has_compose = true;
|
|
4612
4665
|
}
|
|
4613
4666
|
}
|
|
4614
4667
|
for (const full of walkFiles2(root, { maxFiles: 80, extensions: /* @__PURE__ */ new Set([".yaml", ".yml"]) })) {
|
|
4615
|
-
const base =
|
|
4668
|
+
const base = import_node_path21.default.basename(full).toLowerCase();
|
|
4616
4669
|
if (base.includes("deploy") || base.includes("helm") || base.includes("k8s") || base.includes("values")) {
|
|
4617
4670
|
signals.flags.has_k8s_yaml = true;
|
|
4618
|
-
addKey(
|
|
4671
|
+
addKey(import_node_path21.default.relative(root, full).replace(/\\/g, "/"));
|
|
4619
4672
|
break;
|
|
4620
4673
|
}
|
|
4621
4674
|
}
|
|
4622
|
-
if (
|
|
4675
|
+
if (import_node_fs14.default.existsSync(import_node_path21.default.join(root, ".github", "workflows"))) {
|
|
4623
4676
|
signals.flags.has_github_workflows = true;
|
|
4624
4677
|
}
|
|
4625
4678
|
for (const pkg of signals.syft_packages) {
|
|
@@ -4651,12 +4704,12 @@ ${context}
|
|
|
4651
4704
|
${repoFp}`).digest("hex");
|
|
4652
4705
|
}
|
|
4653
4706
|
function loadThreatModelCache(workspaceRoot) {
|
|
4654
|
-
const p =
|
|
4655
|
-
if (!
|
|
4707
|
+
const p = import_node_path21.default.join(workspaceRoot, THREAT_MODEL_CACHE_FILE);
|
|
4708
|
+
if (!import_node_fs14.default.existsSync(p)) {
|
|
4656
4709
|
return { schema_version: THREAT_MODEL_CACHE_SCHEMA_VERSION, entries: {} };
|
|
4657
4710
|
}
|
|
4658
4711
|
try {
|
|
4659
|
-
const data = JSON.parse(
|
|
4712
|
+
const data = JSON.parse(import_node_fs14.default.readFileSync(p, "utf-8"));
|
|
4660
4713
|
if (data.schema_version !== THREAT_MODEL_CACHE_SCHEMA_VERSION) {
|
|
4661
4714
|
return { schema_version: THREAT_MODEL_CACHE_SCHEMA_VERSION, entries: {} };
|
|
4662
4715
|
}
|
|
@@ -4667,7 +4720,7 @@ function loadThreatModelCache(workspaceRoot) {
|
|
|
4667
4720
|
}
|
|
4668
4721
|
}
|
|
4669
4722
|
function saveThreatModelCache(workspaceRoot, cacheKey, markdown, baseline) {
|
|
4670
|
-
const p =
|
|
4723
|
+
const p = import_node_path21.default.join(workspaceRoot, THREAT_MODEL_CACHE_FILE);
|
|
4671
4724
|
const payload = loadThreatModelCache(workspaceRoot);
|
|
4672
4725
|
payload.entries[cacheKey] = {
|
|
4673
4726
|
markdown,
|
|
@@ -4676,7 +4729,7 @@ function saveThreatModelCache(workspaceRoot, cacheKey, markdown, baseline) {
|
|
|
4676
4729
|
repo_fingerprint: String(baseline.repo_fingerprint ?? "")
|
|
4677
4730
|
};
|
|
4678
4731
|
payload.baseline = baseline;
|
|
4679
|
-
|
|
4732
|
+
import_node_fs14.default.writeFileSync(p, JSON.stringify(payload, null, 2), "utf-8");
|
|
4680
4733
|
return p;
|
|
4681
4734
|
}
|
|
4682
4735
|
function strideClassifyClause(clause) {
|
|
@@ -4880,7 +4933,7 @@ function loadRepoCrosscheckHaystack(scanRoot, maxChars = 35e4) {
|
|
|
4880
4933
|
let total = 0;
|
|
4881
4934
|
const files = walkFiles2(scanRoot, { maxFiles: 220 });
|
|
4882
4935
|
for (const full of files) {
|
|
4883
|
-
const rel =
|
|
4936
|
+
const rel = import_node_path21.default.relative(scanRoot, full).replace(/\\/g, "/");
|
|
4884
4937
|
const snippet = readTextSafe(full, 14e3);
|
|
4885
4938
|
const block = `
|
|
4886
4939
|
--- ${rel} ---
|
|
@@ -5090,8 +5143,8 @@ function generateThreatModelMarkdown(profile, baseline, signals, ragKeywords, ca
|
|
|
5090
5143
|
`;
|
|
5091
5144
|
}
|
|
5092
5145
|
async function runThreatModelEngine(opts) {
|
|
5093
|
-
const workspaceRoot =
|
|
5094
|
-
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";
|
|
5095
5148
|
const userContext = (opts.context ?? "").trim();
|
|
5096
5149
|
const profile = detectSecurityProfile(projectName, userContext);
|
|
5097
5150
|
const baseline = ARCHITECTURE_BASELINES[profile];
|
|
@@ -5106,13 +5159,13 @@ Project: ${projectName}`;
|
|
|
5106
5159
|
const cache = loadThreatModelCache(workspaceRoot);
|
|
5107
5160
|
const cached = cache.entries[cacheKey];
|
|
5108
5161
|
if (cached?.markdown) {
|
|
5109
|
-
const reportPath2 =
|
|
5110
|
-
|
|
5162
|
+
const reportPath2 = import_node_path21.default.join(workspaceRoot, THREAT_MODEL_REPORT_FILE);
|
|
5163
|
+
import_node_fs14.default.writeFileSync(reportPath2, cached.markdown, "utf-8");
|
|
5111
5164
|
return {
|
|
5112
5165
|
profile,
|
|
5113
5166
|
markdown: cached.markdown,
|
|
5114
5167
|
report_path: reportPath2,
|
|
5115
|
-
cache_path:
|
|
5168
|
+
cache_path: import_node_path21.default.join(workspaceRoot, THREAT_MODEL_CACHE_FILE),
|
|
5116
5169
|
cache_hit: true,
|
|
5117
5170
|
cache_key: cacheKey,
|
|
5118
5171
|
repo_fingerprint: repoFp,
|
|
@@ -5144,8 +5197,8 @@ Project: ${projectName}`;
|
|
|
5144
5197
|
rag_keywords: [...ragKeywords].sort().slice(0, 40)
|
|
5145
5198
|
};
|
|
5146
5199
|
const cachePath = saveThreatModelCache(workspaceRoot, cacheKey, markdown, baselinePayload);
|
|
5147
|
-
const reportPath =
|
|
5148
|
-
|
|
5200
|
+
const reportPath = import_node_path21.default.join(workspaceRoot, THREAT_MODEL_REPORT_FILE);
|
|
5201
|
+
import_node_fs14.default.writeFileSync(reportPath, markdown, "utf-8");
|
|
5149
5202
|
return {
|
|
5150
5203
|
profile,
|
|
5151
5204
|
markdown,
|
|
@@ -5360,18 +5413,18 @@ function buildSecurityReviewMarkdown(opts) {
|
|
|
5360
5413
|
return out.join("\n");
|
|
5361
5414
|
}
|
|
5362
5415
|
function resolveSecurityReviewPath(workspacePath) {
|
|
5363
|
-
const base = workspacePath?.trim() ?
|
|
5364
|
-
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);
|
|
5365
5418
|
}
|
|
5366
5419
|
function writeSecurityReviewReport(markdown, workspacePath) {
|
|
5367
5420
|
const reportPath = resolveSecurityReviewPath(workspacePath);
|
|
5368
|
-
|
|
5421
|
+
import_node_fs15.default.writeFileSync(reportPath, markdown, "utf-8");
|
|
5369
5422
|
console.error(`[runsec] wrote security review to: ${reportPath}`);
|
|
5370
5423
|
return reportPath;
|
|
5371
5424
|
}
|
|
5372
5425
|
async function executeSecurityReview(opts) {
|
|
5373
|
-
const workspaceRoot =
|
|
5374
|
-
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);
|
|
5375
5428
|
console.error("[runsec] security review: starting unified scan + STRIDE threat model");
|
|
5376
5429
|
const [audit, threatModel] = await Promise.all([
|
|
5377
5430
|
executeAudit("runsec_audit_general", {
|
|
@@ -5404,20 +5457,20 @@ async function executeSecurityReview(opts) {
|
|
|
5404
5457
|
}
|
|
5405
5458
|
|
|
5406
5459
|
// src/engine/remediation.ts
|
|
5407
|
-
var
|
|
5408
|
-
var
|
|
5460
|
+
var import_node_fs17 = __toESM(require("fs"));
|
|
5461
|
+
var import_node_path24 = __toESM(require("path"));
|
|
5409
5462
|
|
|
5410
5463
|
// src/skills/metricLookup.ts
|
|
5411
|
-
var
|
|
5412
|
-
var
|
|
5464
|
+
var import_node_fs16 = __toESM(require("fs"));
|
|
5465
|
+
var import_node_path23 = __toESM(require("path"));
|
|
5413
5466
|
function findMetricRow(metricId) {
|
|
5414
5467
|
const mid = metricId.trim().toUpperCase();
|
|
5415
5468
|
if (!mid) return null;
|
|
5416
5469
|
const manifests = loadSkillManifests();
|
|
5417
5470
|
for (const [, manifest] of Object.entries(manifests)) {
|
|
5418
|
-
const patternsPath =
|
|
5419
|
-
if (!
|
|
5420
|
-
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"));
|
|
5421
5474
|
const row = rows.find((r) => r.metric_id.toUpperCase() === mid);
|
|
5422
5475
|
if (row) return row;
|
|
5423
5476
|
}
|
|
@@ -5430,12 +5483,12 @@ function normalizeRelPath2(p) {
|
|
|
5430
5483
|
return p.replace(/\\/g, "/").replace(/^\.\/+/, "");
|
|
5431
5484
|
}
|
|
5432
5485
|
function resolveTargetFile(workspaceRoot, filePath) {
|
|
5433
|
-
const root =
|
|
5486
|
+
const root = import_node_path24.default.resolve(workspaceRoot);
|
|
5434
5487
|
const rel = normalizeRelPath2(filePath.trim());
|
|
5435
5488
|
if (!rel) return { error: "file_path is required" };
|
|
5436
|
-
const abs =
|
|
5437
|
-
const relFromRoot =
|
|
5438
|
-
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)) {
|
|
5439
5492
|
return { error: `file_path must be inside workspace: ${root}` };
|
|
5440
5493
|
}
|
|
5441
5494
|
for (const seg of BLOCKED_PATH_SEGMENTS) {
|
|
@@ -5443,8 +5496,8 @@ function resolveTargetFile(workspaceRoot, filePath) {
|
|
|
5443
5496
|
return { error: `refusing to modify path under blocked segment: ${seg}` };
|
|
5444
5497
|
}
|
|
5445
5498
|
}
|
|
5446
|
-
if (!
|
|
5447
|
-
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}` };
|
|
5448
5501
|
return { abs, rel: relFromRoot };
|
|
5449
5502
|
}
|
|
5450
5503
|
function normalizeNewlines(text) {
|
|
@@ -5511,12 +5564,12 @@ function locateTargetInFile(content, target) {
|
|
|
5511
5564
|
}
|
|
5512
5565
|
function writeBackup(absPath) {
|
|
5513
5566
|
const backupPath = `${absPath}.bak`;
|
|
5514
|
-
|
|
5567
|
+
import_node_fs17.default.copyFileSync(absPath, backupPath);
|
|
5515
5568
|
return backupPath;
|
|
5516
5569
|
}
|
|
5517
5570
|
function applyDvs001Fix(absPath) {
|
|
5518
|
-
const rel =
|
|
5519
|
-
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/);
|
|
5520
5573
|
const newLines = lines.filter((ln) => !/^\s*user\s+root\s*$/i.test(ln.trim()));
|
|
5521
5574
|
const hasNonRootUser = newLines.some(
|
|
5522
5575
|
(ln) => /^\s*user\s+\S+\s*$/i.test(ln.trim()) && !/^\s*user\s+root\s*$/i.test(ln.trim())
|
|
@@ -5542,7 +5595,7 @@ function applyDvs001Fix(absPath) {
|
|
|
5542
5595
|
};
|
|
5543
5596
|
}
|
|
5544
5597
|
const backupPath = writeBackup(absPath);
|
|
5545
|
-
|
|
5598
|
+
import_node_fs17.default.writeFileSync(absPath, `${newLines.join("\n")}
|
|
5546
5599
|
`, "utf-8");
|
|
5547
5600
|
return {
|
|
5548
5601
|
status: "fixed",
|
|
@@ -5619,7 +5672,7 @@ function applyRemediation(args) {
|
|
|
5619
5672
|
fix_template: row.fix_template
|
|
5620
5673
|
};
|
|
5621
5674
|
}
|
|
5622
|
-
const original =
|
|
5675
|
+
const original = import_node_fs17.default.readFileSync(resolved.abs, "utf-8");
|
|
5623
5676
|
const { index, count } = locateTargetInFile(original, target);
|
|
5624
5677
|
if (count === 0) {
|
|
5625
5678
|
return {
|
|
@@ -5646,7 +5699,7 @@ function applyRemediation(args) {
|
|
|
5646
5699
|
const backupPath = writeBackup(resolved.abs);
|
|
5647
5700
|
const ending = original.includes("\r\n") ? "\r\n" : "\n";
|
|
5648
5701
|
const outText = updated.split("\n").join(ending);
|
|
5649
|
-
|
|
5702
|
+
import_node_fs17.default.writeFileSync(resolved.abs, outText.endsWith(ending) || !original.endsWith(ending) ? outText : outText + ending, "utf-8");
|
|
5650
5703
|
return {
|
|
5651
5704
|
status: "fixed",
|
|
5652
5705
|
message: "Remediation applied after exact target verification; backup created",
|
|
@@ -5848,7 +5901,7 @@ function isRemediationTool(name) {
|
|
|
5848
5901
|
}
|
|
5849
5902
|
|
|
5850
5903
|
// src/hubUploadUrl.ts
|
|
5851
|
-
var
|
|
5904
|
+
var import_node_fs18 = __toESM(require("fs"));
|
|
5852
5905
|
var HUB_UPLOAD_REPORT_PATH = "/api/mcp/upload-report";
|
|
5853
5906
|
var DEFAULT_PRODUCTION_HUB_ORIGIN = "https://runsec.io";
|
|
5854
5907
|
var DEFAULT_DOCKER_INTERNAL_HUB_ORIGIN = "http://frontend:3000";
|
|
@@ -5885,7 +5938,7 @@ function colocatedHubOrigin() {
|
|
|
5885
5938
|
}
|
|
5886
5939
|
function isDockerRuntime() {
|
|
5887
5940
|
try {
|
|
5888
|
-
return
|
|
5941
|
+
return import_node_fs18.default.existsSync("/.dockerenv");
|
|
5889
5942
|
} catch {
|
|
5890
5943
|
return false;
|
|
5891
5944
|
}
|
|
@@ -6134,7 +6187,15 @@ function postHubUploadNodeHttp(url, apiKey, payload) {
|
|
|
6134
6187
|
req.end();
|
|
6135
6188
|
});
|
|
6136
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
|
+
}
|
|
6137
6195
|
async function postHubUpload(url, apiKey, payload) {
|
|
6196
|
+
if (preferNodeHttpUpload()) {
|
|
6197
|
+
return postHubUploadNodeHttp(url, apiKey, payload);
|
|
6198
|
+
}
|
|
6138
6199
|
try {
|
|
6139
6200
|
return await fetch(url, {
|
|
6140
6201
|
method: "POST",
|
|
@@ -6601,7 +6662,9 @@ CRITICAL INSTRUCTIONS FOR LLM:
|
|
|
6601
6662
|
async function main() {
|
|
6602
6663
|
const key = getApiKey();
|
|
6603
6664
|
process.env.RUNSEC_API_KEY = key;
|
|
6604
|
-
console.error(
|
|
6665
|
+
console.error(
|
|
6666
|
+
`[runsec] MCP @runsec/mcp v${RUNSEC_MCP_VERSION} starting \u2014 Hub upload target: ${resolveHubUploadUrl()}`
|
|
6667
|
+
);
|
|
6605
6668
|
await verifyApiKey(key);
|
|
6606
6669
|
const summary = validateRules();
|
|
6607
6670
|
console.error("Rules registry validated:", summary);
|