defense-mcp-server 0.9.2 → 0.9.3
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/build/core/auto-installer.js +31 -31
- package/build/core/command-allowlist.js +1 -1
- package/build/core/dependency-validator.js +9 -9
- package/build/core/distro-adapter.d.ts +0 -5
- package/build/core/distro-adapter.d.ts.map +1 -1
- package/build/core/distro-adapter.js +0 -7
- package/build/core/distro.d.ts +0 -11
- package/build/core/distro.d.ts.map +1 -1
- package/build/core/distro.js +0 -48
- package/build/core/encrypted-state.d.ts +0 -7
- package/build/core/encrypted-state.d.ts.map +1 -1
- package/build/core/encrypted-state.js +0 -7
- package/build/core/logger.js +1 -1
- package/build/core/pam-utils.js +1 -1
- package/build/core/parsers.js +1 -1
- package/build/core/preflight.d.ts +4 -4
- package/build/core/preflight.js +13 -13
- package/build/core/progress.js +20 -20
- package/build/core/run-command.d.ts +14 -0
- package/build/core/run-command.d.ts.map +1 -0
- package/build/core/run-command.js +46 -0
- package/build/core/spawn-safe.d.ts +6 -6
- package/build/core/spawn-safe.d.ts.map +1 -1
- package/build/core/sudo-guard.js +4 -4
- package/build/core/third-party-installer.js +4 -4
- package/build/core/tool-wrapper.js +3 -3
- package/build/tools/access-control.js +6 -6
- package/build/tools/api-security.d.ts.map +1 -1
- package/build/tools/api-security.js +5 -51
- package/build/tools/app-hardening.d.ts.map +1 -1
- package/build/tools/app-hardening.js +23 -25
- package/build/tools/cloud-security.d.ts.map +1 -1
- package/build/tools/cloud-security.js +5 -51
- package/build/tools/compliance.d.ts.map +1 -1
- package/build/tools/compliance.js +9 -13
- package/build/tools/container-security.d.ts.map +1 -1
- package/build/tools/container-security.js +51 -52
- package/build/tools/deception.d.ts.map +1 -1
- package/build/tools/deception.js +8 -54
- package/build/tools/dns-security.d.ts.map +1 -1
- package/build/tools/dns-security.js +2 -48
- package/build/tools/encryption.d.ts.map +1 -1
- package/build/tools/encryption.js +86 -87
- package/build/tools/firewall.d.ts.map +1 -1
- package/build/tools/firewall.js +324 -30
- package/build/tools/hardening.d.ts.map +1 -1
- package/build/tools/hardening.js +12 -13
- package/build/tools/incident-response.d.ts.map +1 -1
- package/build/tools/incident-response.js +3 -3
- package/build/tools/logging.d.ts.map +1 -1
- package/build/tools/logging.js +17 -59
- package/build/tools/malware.js +2 -2
- package/build/tools/meta.d.ts.map +1 -1
- package/build/tools/meta.js +86 -165
- package/build/tools/network-defense.d.ts.map +1 -1
- package/build/tools/network-defense.js +3 -3
- package/build/tools/patch-management.js +8 -8
- package/build/tools/process-security.d.ts.map +1 -1
- package/build/tools/process-security.js +38 -92
- package/build/tools/sudo-management.js +36 -36
- package/build/tools/threat-intel.d.ts.map +1 -1
- package/build/tools/threat-intel.js +2 -48
- package/build/tools/vulnerability-management.d.ts.map +1 -1
- package/build/tools/vulnerability-management.js +3 -49
- package/build/tools/waf.d.ts.map +1 -1
- package/build/tools/waf.js +47 -93
- package/build/tools/wireless-security.d.ts.map +1 -1
- package/build/tools/wireless-security.js +9 -55
- package/package.json +4 -2
|
@@ -454,8 +454,8 @@ export function registerAppHardeningTools(server) {
|
|
|
454
454
|
case "audit": {
|
|
455
455
|
try {
|
|
456
456
|
const sections = [];
|
|
457
|
-
sections.push("
|
|
458
|
-
sections.push("
|
|
457
|
+
sections.push("Application Security Audit");
|
|
458
|
+
sections.push("");
|
|
459
459
|
const apps = await detectRunningApps();
|
|
460
460
|
if (apps.length === 0) {
|
|
461
461
|
sections.push("\nNo known applications detected.");
|
|
@@ -467,9 +467,7 @@ export function registerAppHardeningTools(server) {
|
|
|
467
467
|
apps.sort((a, b) => riskOrder[a.profile.riskLevel] - riskOrder[b.profile.riskLevel]);
|
|
468
468
|
let totalRisks = 0;
|
|
469
469
|
for (const app of apps) {
|
|
470
|
-
|
|
471
|
-
app.profile.riskLevel === "high" ? "🔴" : app.profile.riskLevel === "medium" ? "🟡" : "🟢";
|
|
472
|
-
sections.push(`── ${riskIcon} ${app.profile.name} ──`);
|
|
470
|
+
sections.push(`── ${app.profile.name} ──`);
|
|
473
471
|
sections.push(` Category: ${app.profile.category}`);
|
|
474
472
|
sections.push(` Risk Level: ${app.profile.riskLevel.toUpperCase()}`);
|
|
475
473
|
sections.push(` Running as: ${app.user}`);
|
|
@@ -480,17 +478,17 @@ export function registerAppHardeningTools(server) {
|
|
|
480
478
|
sections.push(` Listening Ports:`);
|
|
481
479
|
for (const lp of app.listenPorts) {
|
|
482
480
|
const external = !lp.address.includes("127.0.0.1") && !lp.address.includes("::1");
|
|
483
|
-
sections.push(` ${lp.protocol}/${lp.port} on ${lp.address} [${external ? "
|
|
481
|
+
sections.push(` ${lp.protocol}/${lp.port} on ${lp.address} [${external ? "WARNING: EXTERNAL" : "localhost"}]`);
|
|
484
482
|
}
|
|
485
483
|
}
|
|
486
484
|
sections.push(` Security Concerns:`);
|
|
487
485
|
for (const concern of app.profile.securityConcerns) {
|
|
488
|
-
sections.push(`
|
|
486
|
+
sections.push(` WARNING: ${concern}`);
|
|
489
487
|
totalRisks++;
|
|
490
488
|
}
|
|
491
489
|
sections.push(` Top Recommendations:`);
|
|
492
490
|
for (const rec of app.profile.recommendations.slice(0, 3)) {
|
|
493
|
-
sections.push(`
|
|
491
|
+
sections.push(` ${rec}`);
|
|
494
492
|
}
|
|
495
493
|
if (app.profile.recommendations.length > 3) {
|
|
496
494
|
sections.push(` ... +${app.profile.recommendations.length - 3} more (use action=recommend)`);
|
|
@@ -530,24 +528,24 @@ export function registerAppHardeningTools(server) {
|
|
|
530
528
|
};
|
|
531
529
|
}
|
|
532
530
|
const sections = [];
|
|
533
|
-
sections.push(
|
|
534
|
-
sections.push("
|
|
531
|
+
sections.push(`Hardening Guide: ${profile.name}`);
|
|
532
|
+
sections.push("");
|
|
535
533
|
sections.push(`Category: ${profile.category} | Risk: ${profile.riskLevel.toUpperCase()}`);
|
|
536
534
|
sections.push("\n── Security Concerns ──");
|
|
537
535
|
for (const concern of profile.securityConcerns) {
|
|
538
|
-
sections.push(`
|
|
536
|
+
sections.push(` WARNING: ${concern}`);
|
|
539
537
|
}
|
|
540
538
|
sections.push("\n── Network Hardening ──");
|
|
541
539
|
if (profile.requiredPorts.length > 0) {
|
|
542
540
|
sections.push(" Ports that MUST remain open (core functionality):");
|
|
543
541
|
for (const p of profile.requiredPorts) {
|
|
544
|
-
sections.push(`
|
|
542
|
+
sections.push(` ${p.protocol}/${p.port} — ${p.purpose}`);
|
|
545
543
|
}
|
|
546
544
|
}
|
|
547
545
|
if (profile.localhostOnlyPorts.length > 0) {
|
|
548
546
|
sections.push(" Ports to restrict to localhost/LAN:");
|
|
549
547
|
for (const p of profile.localhostOnlyPorts) {
|
|
550
|
-
sections.push(`
|
|
548
|
+
sections.push(` ${p.protocol}/${p.port} — ${p.purpose}`);
|
|
551
549
|
}
|
|
552
550
|
}
|
|
553
551
|
sections.push(" Firewall strategy:");
|
|
@@ -571,11 +569,11 @@ export function registerAppHardeningTools(server) {
|
|
|
571
569
|
sections.push("\n── Filesystem Permissions ──");
|
|
572
570
|
sections.push(" Writable paths (required for operation):");
|
|
573
571
|
for (const p of profile.writablePaths) {
|
|
574
|
-
sections.push(`
|
|
572
|
+
sections.push(` ${p}`);
|
|
575
573
|
}
|
|
576
574
|
sections.push(" Read-only paths:");
|
|
577
575
|
for (const p of profile.readablePaths) {
|
|
578
|
-
sections.push(`
|
|
576
|
+
sections.push(` ${p}`);
|
|
579
577
|
}
|
|
580
578
|
return { content: [createTextContent(sections.join("\n"))] };
|
|
581
579
|
}
|
|
@@ -600,8 +598,8 @@ export function registerAppHardeningTools(server) {
|
|
|
600
598
|
}
|
|
601
599
|
const effectiveDryRun = dry_run ?? getConfig().dryRun;
|
|
602
600
|
const sections = [];
|
|
603
|
-
sections.push(
|
|
604
|
-
sections.push("
|
|
601
|
+
sections.push(`Firewall Rules for ${profile.name}`);
|
|
602
|
+
sections.push("");
|
|
605
603
|
sections.push(`LAN CIDR: ${lan_cidr}`);
|
|
606
604
|
sections.push(effectiveDryRun ? "\n[DRY RUN] Rules that would be applied:\n" : "\nApplying rules:\n");
|
|
607
605
|
const rules = [];
|
|
@@ -632,7 +630,7 @@ export function registerAppHardeningTools(server) {
|
|
|
632
630
|
else
|
|
633
631
|
failed++;
|
|
634
632
|
}
|
|
635
|
-
sections.push(`\
|
|
633
|
+
sections.push(`\nApplied ${applied} rules, ${failed} failed`);
|
|
636
634
|
}
|
|
637
635
|
sections.push("\n── Additional Recommendations ──");
|
|
638
636
|
sections.push(" • Consider using nftables for more granular control");
|
|
@@ -690,10 +688,10 @@ export function registerAppHardeningTools(server) {
|
|
|
690
688
|
}
|
|
691
689
|
}
|
|
692
690
|
const sections = [];
|
|
693
|
-
sections.push(
|
|
694
|
-
sections.push("
|
|
691
|
+
sections.push(`Systemd Hardening: ${profile.name}`);
|
|
692
|
+
sections.push("");
|
|
695
693
|
if (!svcName) {
|
|
696
|
-
sections.push(`\
|
|
694
|
+
sections.push(`\nWARNING: No running systemd service found for ${profile.name}.`);
|
|
697
695
|
sections.push("Provide service_name manually if the service uses a different name.");
|
|
698
696
|
sections.push("\nRecommended override content for when the service is configured:\n");
|
|
699
697
|
}
|
|
@@ -726,12 +724,12 @@ export function registerAppHardeningTools(server) {
|
|
|
726
724
|
});
|
|
727
725
|
if (writeResult.exitCode === 0) {
|
|
728
726
|
await executeCommand({ toolName: "app_hardening", command: "sudo", args: ["systemctl", "daemon-reload"], timeout: 10000 });
|
|
729
|
-
sections.push(`\
|
|
730
|
-
sections.push("
|
|
731
|
-
sections.push(`\
|
|
727
|
+
sections.push(`\nOverride written to ${overridePath}`);
|
|
728
|
+
sections.push("systemd daemon reloaded");
|
|
729
|
+
sections.push(`\nRestart the service to apply: sudo systemctl restart ${svcName}`);
|
|
732
730
|
}
|
|
733
731
|
else {
|
|
734
|
-
sections.push(`\
|
|
732
|
+
sections.push(`\nFailed to write override: ${writeResult.stderr}`);
|
|
735
733
|
}
|
|
736
734
|
}
|
|
737
735
|
sections.push("\n── What These Directives Do ──");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloud-security.d.ts","sourceRoot":"","sources":["../../src/tools/cloud-security.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;
|
|
1
|
+
{"version":3,"file":"cloud-security.d.ts","sourceRoot":"","sources":["../../src/tools/cloud-security.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAyCpE;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKpD;AA4fD,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA6RlE"}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* exposure checking, storage audit, and IMDS security assessment for AWS/GCP/Azure.
|
|
9
9
|
*/
|
|
10
10
|
import { z } from "zod";
|
|
11
|
-
import {
|
|
11
|
+
import { runCommand } from "../core/run-command.js";
|
|
12
12
|
import { createTextContent, createErrorContent, formatToolOutput, } from "../core/parsers.js";
|
|
13
13
|
import { existsSync } from "node:fs";
|
|
14
14
|
/** Sensitive environment variable names for cloud credentials */
|
|
@@ -30,53 +30,7 @@ const CREDENTIAL_FILE_PATHS = [
|
|
|
30
30
|
{ provider: "gcp", path: "~/.config/gcloud/application_default_credentials.json" },
|
|
31
31
|
{ provider: "azure", path: "~/.azure/accessTokens.json" },
|
|
32
32
|
];
|
|
33
|
-
|
|
34
|
-
* Run a command via spawnSafe and collect output as a promise.
|
|
35
|
-
* Handles errors gracefully — returns error info instead of throwing.
|
|
36
|
-
*/
|
|
37
|
-
async function runCommand(command, args, timeoutMs = 30_000) {
|
|
38
|
-
return new Promise((resolve) => {
|
|
39
|
-
let child;
|
|
40
|
-
try {
|
|
41
|
-
child = spawnSafe(command, args);
|
|
42
|
-
}
|
|
43
|
-
catch (err) {
|
|
44
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
45
|
-
resolve({ stdout: "", stderr: msg, exitCode: -1 });
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
let stdout = "";
|
|
49
|
-
let stderr = "";
|
|
50
|
-
let resolved = false;
|
|
51
|
-
const timer = setTimeout(() => {
|
|
52
|
-
if (!resolved) {
|
|
53
|
-
resolved = true;
|
|
54
|
-
child.kill("SIGTERM");
|
|
55
|
-
resolve({ stdout, stderr: stderr + "\n[TIMEOUT]", exitCode: -1 });
|
|
56
|
-
}
|
|
57
|
-
}, timeoutMs);
|
|
58
|
-
child.stdout?.on("data", (data) => {
|
|
59
|
-
stdout += data.toString();
|
|
60
|
-
});
|
|
61
|
-
child.stderr?.on("data", (data) => {
|
|
62
|
-
stderr += data.toString();
|
|
63
|
-
});
|
|
64
|
-
child.on("close", (code) => {
|
|
65
|
-
if (!resolved) {
|
|
66
|
-
resolved = true;
|
|
67
|
-
clearTimeout(timer);
|
|
68
|
-
resolve({ stdout, stderr, exitCode: code ?? -1 });
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
child.on("error", (err) => {
|
|
72
|
-
if (!resolved) {
|
|
73
|
-
resolved = true;
|
|
74
|
-
clearTimeout(timer);
|
|
75
|
-
resolve({ stdout, stderr: err.message, exitCode: -1 });
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
}
|
|
33
|
+
// ── Helpers ────────────────────────────────────────────────────────────────────
|
|
80
34
|
// ── Credential masking ─────────────────────────────────────────────────────────
|
|
81
35
|
/**
|
|
82
36
|
* Mask a credential value, showing first 4 chars + asterisks.
|
|
@@ -611,7 +565,7 @@ export function registerCloudSecurityTools(server) {
|
|
|
611
565
|
text += "Credential Files:\n";
|
|
612
566
|
for (const file of creds.credentialFiles) {
|
|
613
567
|
const status = file.exists
|
|
614
|
-
? `EXISTS (permissions: ${file.permissions || "unknown"})${file.permWarning ? `
|
|
568
|
+
? `EXISTS (permissions: ${file.permissions || "unknown"})${file.permWarning ? ` WARNING: ${file.permWarning}` : ""}`
|
|
615
569
|
: "not found";
|
|
616
570
|
text += ` • ${file.path}: ${status}\n`;
|
|
617
571
|
}
|
|
@@ -706,10 +660,10 @@ export function registerCloudSecurityTools(server) {
|
|
|
706
660
|
return { content: [formatToolOutput(output)] };
|
|
707
661
|
}
|
|
708
662
|
let text = "Cloud Security — IMDS Security Check\n\n";
|
|
709
|
-
text += `IMDSv1 (unauthenticated): ${imds.v1Accessible ? "ACCESSIBLE
|
|
663
|
+
text += `IMDSv1 (unauthenticated): ${imds.v1Accessible ? "ACCESSIBLE WARNING" : "not accessible OK"}\n`;
|
|
710
664
|
text += `IMDSv2 (token-based): ${imds.v2Accessible ? "accessible" : "not accessible"}\n`;
|
|
711
665
|
text += `IMDSv2 Token Endpoint: ${imds.v2TokenWorks ? "working" : "not working"}\n`;
|
|
712
|
-
text += `Iptables IMDS Rules: ${imds.iptablesBlocked ? "BLOCKED
|
|
666
|
+
text += `Iptables IMDS Rules: ${imds.iptablesBlocked ? "BLOCKED OK" : imds.iptablesRules.length > 0 ? "rules found" : "no rules"}\n`;
|
|
713
667
|
text += `Hop Limit: ${imds.hopLimit}\n`;
|
|
714
668
|
text += `\nSeverity: ${imds.severity}\n`;
|
|
715
669
|
text += `Security Score: ${imds.securityScore}/100\n`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compliance.d.ts","sourceRoot":"","sources":["../../src/tools/compliance.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAsdpE,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"compliance.d.ts","sourceRoot":"","sources":["../../src/tools/compliance.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAsdpE,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAsiC/D"}
|
|
@@ -14,7 +14,7 @@ import { generateDurationBanner, generateTimingSummary, startTiming, } from "../
|
|
|
14
14
|
import { logChange, createChangeEntry } from "../core/changelog.js";
|
|
15
15
|
import { getDistroAdapter } from "../core/distro-adapter.js";
|
|
16
16
|
import { sanitizeArgs } from "../core/sanitizer.js";
|
|
17
|
-
import { readFileSync } from "node:fs";
|
|
17
|
+
import { readFileSync, statSync } from "node:fs";
|
|
18
18
|
import { loadPolicy, evaluatePolicy, getBuiltinPolicies, } from "../core/policy-engine.js";
|
|
19
19
|
async function runCisCheck(command, args, id, title, level, expectPattern) {
|
|
20
20
|
try {
|
|
@@ -668,7 +668,7 @@ export function registerComplianceTools(server) {
|
|
|
668
668
|
}
|
|
669
669
|
function filePermCheckLocal(filePath, maxPerm) {
|
|
670
670
|
try {
|
|
671
|
-
|
|
671
|
+
// statSync imported at module level
|
|
672
672
|
const stat = statSync(filePath);
|
|
673
673
|
const mode = (stat.mode & 0o777).toString(8);
|
|
674
674
|
return { passed: parseInt(mode, 8) <= parseInt(maxPerm, 8), detail: `${filePath}: ${mode} (max: ${maxPerm})` };
|
|
@@ -943,21 +943,17 @@ export function registerComplianceTools(server) {
|
|
|
943
943
|
for (const section of report.sections) {
|
|
944
944
|
md += `## ${section.name}\n\n`;
|
|
945
945
|
md += `**Score:** ${section.score}/100\n\n`;
|
|
946
|
-
md += `\`\`\`json\n${JSON.stringify(section.details
|
|
946
|
+
md += `\`\`\`json\n${JSON.stringify(section.details)}\n\`\`\`\n\n`;
|
|
947
947
|
}
|
|
948
948
|
return { content: [createTextContent(md)] };
|
|
949
949
|
}
|
|
950
950
|
// Text format
|
|
951
|
-
let text =
|
|
952
|
-
text += `
|
|
953
|
-
text += `
|
|
954
|
-
text += ` Overall Score: ${report.overallScore}/100\n`;
|
|
955
|
-
text += `${"=".repeat(60)}\n\n`;
|
|
951
|
+
let text = `COMPLIANCE REPORT\n`;
|
|
952
|
+
text += `Generated: ${report.timestamp}\n`;
|
|
953
|
+
text += `Overall Score: ${report.overallScore}/100\n\n`;
|
|
956
954
|
for (const section of report.sections) {
|
|
957
|
-
text += `${
|
|
958
|
-
text +=
|
|
959
|
-
text += `${"─".repeat(50)}\n`;
|
|
960
|
-
text += `${JSON.stringify(section.details, null, 2)}\n\n`;
|
|
955
|
+
text += `${section.name} — Score: ${section.score}/100\n`;
|
|
956
|
+
text += `${JSON.stringify(section.details)}\n\n`;
|
|
961
957
|
}
|
|
962
958
|
return { content: [createTextContent(text)] };
|
|
963
959
|
}
|
|
@@ -981,7 +977,7 @@ export function registerComplianceTools(server) {
|
|
|
981
977
|
const currentUser = process.env.USER || process.env.LOGNAME;
|
|
982
978
|
if (currentUser && currentUser !== "root" && !allowed_users.includes(currentUser)) {
|
|
983
979
|
allowed_users = [...allowed_users, currentUser];
|
|
984
|
-
changes.push(
|
|
980
|
+
changes.push(`WARNING: Auto-included current user '${currentUser}' in allowed_users to prevent self-lockout`);
|
|
985
981
|
}
|
|
986
982
|
// Validate usernames
|
|
987
983
|
for (const user of allowed_users) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"container-security.d.ts","sourceRoot":"","sources":["../../src/tools/container-security.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAwBpE,eAAO,MAAM,yBAAyB,aAyBpC,CAAC;AAIH,wBAAgB,8BAA8B,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"container-security.d.ts","sourceRoot":"","sources":["../../src/tools/container-security.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAwBpE,eAAO,MAAM,yBAAyB,aAyBpC,CAAC;AAIH,wBAAgB,8BAA8B,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA4vBtE"}
|