@ebowwa/mcp-nm 2.0.2 → 2.0.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/dist/index.js +612 -1
- package/package.json +6 -3
- package/src/index.ts +758 -1
package/dist/index.js
CHANGED
|
@@ -13643,7 +13643,7 @@ import { promisify } from "util";
|
|
|
13643
13643
|
var execAsync = promisify(exec);
|
|
13644
13644
|
var server = new Server({
|
|
13645
13645
|
name: "@ebowwa/mcp-nm",
|
|
13646
|
-
version: "2.0.
|
|
13646
|
+
version: "2.0.3"
|
|
13647
13647
|
}, {
|
|
13648
13648
|
capabilities: {
|
|
13649
13649
|
tools: {}
|
|
@@ -14977,6 +14977,517 @@ async function handleHexEditor(args) {
|
|
|
14977
14977
|
throw new Error(`Hex editor failed: ${error2 instanceof Error ? error2.message : error2}`);
|
|
14978
14978
|
}
|
|
14979
14979
|
}
|
|
14980
|
+
function isMacOS() {
|
|
14981
|
+
return process.platform === "darwin";
|
|
14982
|
+
}
|
|
14983
|
+
async function handleCodesignInfo(args) {
|
|
14984
|
+
if (!isMacOS()) {
|
|
14985
|
+
return {
|
|
14986
|
+
content: [{
|
|
14987
|
+
type: "text",
|
|
14988
|
+
text: "Error: Code signing tools are only available on macOS. This tool requires the 'codesign' utility."
|
|
14989
|
+
}],
|
|
14990
|
+
isError: true
|
|
14991
|
+
};
|
|
14992
|
+
}
|
|
14993
|
+
try {
|
|
14994
|
+
const results = [];
|
|
14995
|
+
results.push(`Code Signature Info: ${args.filePath}`);
|
|
14996
|
+
results.push("");
|
|
14997
|
+
const { stdout: fileInfo } = await execAsync(`file "${args.filePath}"`);
|
|
14998
|
+
if (!fileInfo.toLowerCase().includes("mach-o")) {
|
|
14999
|
+
results.push("Warning: File does not appear to be a Mach-O binary");
|
|
15000
|
+
results.push(`File type: ${fileInfo.trim()}`);
|
|
15001
|
+
}
|
|
15002
|
+
try {
|
|
15003
|
+
const { stdout: signInfo } = await execAsync(`codesign -dv "${args.filePath}" 2>&1`);
|
|
15004
|
+
results.push("Signature Details:");
|
|
15005
|
+
results.push(signInfo.split(`
|
|
15006
|
+
`).map((line) => ` ${line}`).join(`
|
|
15007
|
+
`));
|
|
15008
|
+
} catch (e) {
|
|
15009
|
+
const output = e instanceof Error ? e.message : String(e);
|
|
15010
|
+
if (output.includes("no signature") || output.includes("code object is not signed")) {
|
|
15011
|
+
results.push("Status: NOT SIGNED");
|
|
15012
|
+
results.push(" The binary has no code signature");
|
|
15013
|
+
} else {
|
|
15014
|
+
results.push(`Signature check: ${output}`);
|
|
15015
|
+
}
|
|
15016
|
+
}
|
|
15017
|
+
try {
|
|
15018
|
+
await execAsync(`codesign -v "${args.filePath}" 2>&1`);
|
|
15019
|
+
results.push("");
|
|
15020
|
+
results.push("Validity: VALID - Signature is intact");
|
|
15021
|
+
} catch (e) {
|
|
15022
|
+
const output = e instanceof Error ? e.message : String(e);
|
|
15023
|
+
if (output.includes("not signed")) {
|
|
15024
|
+
results.push("");
|
|
15025
|
+
results.push("Validity: NOT SIGNED");
|
|
15026
|
+
} else {
|
|
15027
|
+
results.push("");
|
|
15028
|
+
results.push(`Validity: INVALID - ${output}`);
|
|
15029
|
+
}
|
|
15030
|
+
}
|
|
15031
|
+
try {
|
|
15032
|
+
const { stdout: reqs } = await execAsync(`codesign -dr - "${args.filePath}" 2>&1`);
|
|
15033
|
+
if (reqs.trim() && !reqs.includes("no signature")) {
|
|
15034
|
+
results.push("");
|
|
15035
|
+
results.push("Designated Requirements:");
|
|
15036
|
+
results.push(reqs.split(`
|
|
15037
|
+
`).map((line) => ` ${line}`).join(`
|
|
15038
|
+
`));
|
|
15039
|
+
}
|
|
15040
|
+
} catch {}
|
|
15041
|
+
return { content: [{ type: "text", text: results.join(`
|
|
15042
|
+
`) }] };
|
|
15043
|
+
} catch (error2) {
|
|
15044
|
+
return {
|
|
15045
|
+
content: [{ type: "text", text: `Error: ${error2 instanceof Error ? error2.message : String(error2)}` }],
|
|
15046
|
+
isError: true
|
|
15047
|
+
};
|
|
15048
|
+
}
|
|
15049
|
+
}
|
|
15050
|
+
async function handleCodesignRemove(args) {
|
|
15051
|
+
if (!isMacOS()) {
|
|
15052
|
+
return {
|
|
15053
|
+
content: [{
|
|
15054
|
+
type: "text",
|
|
15055
|
+
text: "Error: Code signing tools are only available on macOS."
|
|
15056
|
+
}],
|
|
15057
|
+
isError: true
|
|
15058
|
+
};
|
|
15059
|
+
}
|
|
15060
|
+
const createBackup = args.createBackup !== false;
|
|
15061
|
+
try {
|
|
15062
|
+
const results = [];
|
|
15063
|
+
results.push(`Remove Code Signature: ${args.filePath}`);
|
|
15064
|
+
results.push("");
|
|
15065
|
+
if (createBackup) {
|
|
15066
|
+
const backupPath = `${args.filePath}.bak`;
|
|
15067
|
+
await execAsync(`cp "${args.filePath}" "${backupPath}"`);
|
|
15068
|
+
results.push(`Backup created: ${backupPath}`);
|
|
15069
|
+
}
|
|
15070
|
+
try {
|
|
15071
|
+
const { stdout: signInfo } = await execAsync(`codesign -dv "${args.filePath}" 2>&1`);
|
|
15072
|
+
results.push(`Previous signature: ${signInfo.split(`
|
|
15073
|
+
`)[0] || "signed"}`);
|
|
15074
|
+
} catch {
|
|
15075
|
+
results.push("Previous signature: not signed or invalid");
|
|
15076
|
+
}
|
|
15077
|
+
try {
|
|
15078
|
+
await execAsync(`codesign --remove-signature "${args.filePath}"`);
|
|
15079
|
+
results.push("Status: SUCCESS - Code signature removed");
|
|
15080
|
+
} catch (e) {
|
|
15081
|
+
const output = e instanceof Error ? e.message : String(e);
|
|
15082
|
+
if (output.includes("no signature")) {
|
|
15083
|
+
results.push("Status: NO ACTION - Binary was not signed");
|
|
15084
|
+
} else {
|
|
15085
|
+
throw new Error(`Failed to remove signature: ${output}`);
|
|
15086
|
+
}
|
|
15087
|
+
}
|
|
15088
|
+
try {
|
|
15089
|
+
await execAsync(`codesign -v "${args.filePath}" 2>&1`);
|
|
15090
|
+
results.push("Warning: Signature still present after removal");
|
|
15091
|
+
} catch (e) {
|
|
15092
|
+
if (e instanceof Error && e.message.includes("not signed")) {
|
|
15093
|
+
results.push("Verification: Confirmed - Binary is now unsigned");
|
|
15094
|
+
}
|
|
15095
|
+
}
|
|
15096
|
+
return { content: [{ type: "text", text: results.join(`
|
|
15097
|
+
`) }] };
|
|
15098
|
+
} catch (error2) {
|
|
15099
|
+
return {
|
|
15100
|
+
content: [{ type: "text", text: `Error: ${error2 instanceof Error ? error2.message : String(error2)}` }],
|
|
15101
|
+
isError: true
|
|
15102
|
+
};
|
|
15103
|
+
}
|
|
15104
|
+
}
|
|
15105
|
+
async function handleCodesignSign(args) {
|
|
15106
|
+
if (!isMacOS()) {
|
|
15107
|
+
return {
|
|
15108
|
+
content: [{
|
|
15109
|
+
type: "text",
|
|
15110
|
+
text: "Error: Code signing tools are only available on macOS."
|
|
15111
|
+
}],
|
|
15112
|
+
isError: true
|
|
15113
|
+
};
|
|
15114
|
+
}
|
|
15115
|
+
const force = args.force !== false;
|
|
15116
|
+
const createBackup = args.createBackup === true;
|
|
15117
|
+
const deep = args.deep === true;
|
|
15118
|
+
const certificate = args.certificate || "-";
|
|
15119
|
+
try {
|
|
15120
|
+
const results = [];
|
|
15121
|
+
results.push(`Sign Binary: ${args.filePath}`);
|
|
15122
|
+
results.push("");
|
|
15123
|
+
if (createBackup) {
|
|
15124
|
+
const backupPath = `${args.filePath}.bak`;
|
|
15125
|
+
await execAsync(`cp "${args.filePath}" "${backupPath}"`);
|
|
15126
|
+
results.push(`Backup created: ${backupPath}`);
|
|
15127
|
+
}
|
|
15128
|
+
const forceFlag = force ? "--force" : "";
|
|
15129
|
+
const deepFlag = deep ? "--deep" : "";
|
|
15130
|
+
const optionsFlag = args.options ? `--options ${args.options}` : "";
|
|
15131
|
+
const cmd = `codesign -s ${certificate} ${forceFlag} ${deepFlag} ${optionsFlag} "${args.filePath}"`.replace(/\s+/g, " ").trim();
|
|
15132
|
+
results.push(`Command: ${cmd}`);
|
|
15133
|
+
results.push(`Signing with: ${certificate === "-" ? "ad-hoc signature" : certificate}`);
|
|
15134
|
+
await execAsync(cmd);
|
|
15135
|
+
results.push("Status: SUCCESS - Binary signed");
|
|
15136
|
+
try {
|
|
15137
|
+
await execAsync(`codesign -v "${args.filePath}" 2>&1`);
|
|
15138
|
+
results.push("Verification: VALID - Signature is intact");
|
|
15139
|
+
} catch (e) {
|
|
15140
|
+
results.push(`Verification: WARNING - ${e instanceof Error ? e.message : String(e)}`);
|
|
15141
|
+
}
|
|
15142
|
+
try {
|
|
15143
|
+
const { stdout: signInfo } = await execAsync(`codesign -dv "${args.filePath}" 2>&1`);
|
|
15144
|
+
const infoLine = signInfo.split(`
|
|
15145
|
+
`).find((l) => l.includes("Identifier") || l.includes("Authority"));
|
|
15146
|
+
if (infoLine) {
|
|
15147
|
+
results.push(`New signature: ${infoLine.trim()}`);
|
|
15148
|
+
}
|
|
15149
|
+
} catch {}
|
|
15150
|
+
return { content: [{ type: "text", text: results.join(`
|
|
15151
|
+
`) }] };
|
|
15152
|
+
} catch (error2) {
|
|
15153
|
+
return {
|
|
15154
|
+
content: [{ type: "text", text: `Error: ${error2 instanceof Error ? error2.message : String(error2)}` }],
|
|
15155
|
+
isError: true
|
|
15156
|
+
};
|
|
15157
|
+
}
|
|
15158
|
+
}
|
|
15159
|
+
async function handleQuarantineCheck(args) {
|
|
15160
|
+
if (!isMacOS()) {
|
|
15161
|
+
return {
|
|
15162
|
+
content: [{
|
|
15163
|
+
type: "text",
|
|
15164
|
+
text: "Error: Quarantine tools are only available on macOS."
|
|
15165
|
+
}],
|
|
15166
|
+
isError: true
|
|
15167
|
+
};
|
|
15168
|
+
}
|
|
15169
|
+
try {
|
|
15170
|
+
const results = [];
|
|
15171
|
+
results.push(`Quarantine Check: ${args.filePath}`);
|
|
15172
|
+
results.push("");
|
|
15173
|
+
try {
|
|
15174
|
+
const { stdout: quarantine } = await execAsync(`xattr -p com.apple.quarantine "${args.filePath}" 2>&1`);
|
|
15175
|
+
if (quarantine.trim()) {
|
|
15176
|
+
results.push("Status: QUARANTINED");
|
|
15177
|
+
results.push("");
|
|
15178
|
+
results.push("Quarantine data:");
|
|
15179
|
+
results.push(` ${quarantine.trim()}`);
|
|
15180
|
+
try {
|
|
15181
|
+
const { stdout: details } = await execAsync(`xattr -l "${args.filePath}" 2>&1`);
|
|
15182
|
+
const quarantineSection = details.split(`
|
|
15183
|
+
`).filter((l) => l.includes("quarantine") || l.includes("provenance"));
|
|
15184
|
+
if (quarantineSection.length > 0) {
|
|
15185
|
+
results.push("");
|
|
15186
|
+
results.push("Extended attributes:");
|
|
15187
|
+
quarantineSection.forEach((l) => results.push(` ${l}`));
|
|
15188
|
+
}
|
|
15189
|
+
} catch {}
|
|
15190
|
+
results.push("");
|
|
15191
|
+
results.push("Note: Quarantine prevents execution of downloaded files.");
|
|
15192
|
+
results.push("Use bin_quarantine_remove to allow execution.");
|
|
15193
|
+
} else {
|
|
15194
|
+
results.push("Status: NOT QUARANTINED");
|
|
15195
|
+
results.push("The file has no quarantine attribute.");
|
|
15196
|
+
}
|
|
15197
|
+
} catch (e) {
|
|
15198
|
+
const output = e instanceof Error ? e.message : String(e);
|
|
15199
|
+
if (output.includes("No such xattr") || output.includes("not found")) {
|
|
15200
|
+
results.push("Status: NOT QUARANTINED");
|
|
15201
|
+
results.push("The file has no quarantine attribute.");
|
|
15202
|
+
} else {
|
|
15203
|
+
throw new Error(`Failed to check quarantine: ${output}`);
|
|
15204
|
+
}
|
|
15205
|
+
}
|
|
15206
|
+
try {
|
|
15207
|
+
const { stdout: provenance } = await execAsync(`xattr -p com.apple.provenance "${args.filePath}" 2>&1`);
|
|
15208
|
+
if (provenance.trim()) {
|
|
15209
|
+
results.push("");
|
|
15210
|
+
results.push("Provenance: PRESENT");
|
|
15211
|
+
results.push(" File has provenance metadata (tracks download source)");
|
|
15212
|
+
}
|
|
15213
|
+
} catch {}
|
|
15214
|
+
return { content: [{ type: "text", text: results.join(`
|
|
15215
|
+
`) }] };
|
|
15216
|
+
} catch (error2) {
|
|
15217
|
+
return {
|
|
15218
|
+
content: [{ type: "text", text: `Error: ${error2 instanceof Error ? error2.message : String(error2)}` }],
|
|
15219
|
+
isError: true
|
|
15220
|
+
};
|
|
15221
|
+
}
|
|
15222
|
+
}
|
|
15223
|
+
async function handleQuarantineRemove(args) {
|
|
15224
|
+
if (!isMacOS()) {
|
|
15225
|
+
return {
|
|
15226
|
+
content: [{
|
|
15227
|
+
type: "text",
|
|
15228
|
+
text: "Error: Quarantine tools are only available on macOS."
|
|
15229
|
+
}],
|
|
15230
|
+
isError: true
|
|
15231
|
+
};
|
|
15232
|
+
}
|
|
15233
|
+
try {
|
|
15234
|
+
const results = [];
|
|
15235
|
+
results.push(`Remove Quarantine: ${args.filePath}`);
|
|
15236
|
+
results.push("");
|
|
15237
|
+
let removedAny = false;
|
|
15238
|
+
try {
|
|
15239
|
+
await execAsync(`xattr -d com.apple.quarantine "${args.filePath}" 2>&1`);
|
|
15240
|
+
results.push("Removed: com.apple.quarantine");
|
|
15241
|
+
removedAny = true;
|
|
15242
|
+
} catch (e) {
|
|
15243
|
+
const output = e instanceof Error ? e.message : String(e);
|
|
15244
|
+
if (output.includes("No such xattr") || output.includes("not found")) {
|
|
15245
|
+
results.push("Skipped: com.apple.quarantine (not present)");
|
|
15246
|
+
} else {
|
|
15247
|
+
results.push(`Warning: Could not remove quarantine - ${output}`);
|
|
15248
|
+
}
|
|
15249
|
+
}
|
|
15250
|
+
try {
|
|
15251
|
+
await execAsync(`xattr -d com.apple.provenance "${args.filePath}" 2>&1`);
|
|
15252
|
+
results.push("Removed: com.apple.provenance");
|
|
15253
|
+
removedAny = true;
|
|
15254
|
+
} catch {}
|
|
15255
|
+
try {
|
|
15256
|
+
await execAsync(`xattr -d com.apple.metadata:kMDItemWhereFroms "${args.filePath}" 2>&1`);
|
|
15257
|
+
results.push("Removed: com.apple.metadata:kMDItemWhereFroms");
|
|
15258
|
+
removedAny = true;
|
|
15259
|
+
} catch {}
|
|
15260
|
+
if (removedAny) {
|
|
15261
|
+
results.push("");
|
|
15262
|
+
results.push("Status: SUCCESS - Quarantine attributes removed");
|
|
15263
|
+
results.push("The file should now be able to execute without quarantine warnings.");
|
|
15264
|
+
} else {
|
|
15265
|
+
results.push("");
|
|
15266
|
+
results.push("Status: NO ACTION - No quarantine attributes found");
|
|
15267
|
+
}
|
|
15268
|
+
return { content: [{ type: "text", text: results.join(`
|
|
15269
|
+
`) }] };
|
|
15270
|
+
} catch (error2) {
|
|
15271
|
+
return {
|
|
15272
|
+
content: [{ type: "text", text: `Error: ${error2 instanceof Error ? error2.message : String(error2)}` }],
|
|
15273
|
+
isError: true
|
|
15274
|
+
};
|
|
15275
|
+
}
|
|
15276
|
+
}
|
|
15277
|
+
async function handleSafePatch(args) {
|
|
15278
|
+
if (!isMacOS()) {
|
|
15279
|
+
return {
|
|
15280
|
+
content: [{
|
|
15281
|
+
type: "text",
|
|
15282
|
+
text: "Error: Safe patching tools are only available on macOS."
|
|
15283
|
+
}],
|
|
15284
|
+
isError: true
|
|
15285
|
+
};
|
|
15286
|
+
}
|
|
15287
|
+
const createBackup = args.createBackup !== false;
|
|
15288
|
+
try {
|
|
15289
|
+
const results = [];
|
|
15290
|
+
results.push(`Safe Patch Workflow: ${args.filePath}`);
|
|
15291
|
+
results.push(` Offset: 0x${args.offset.toString(16)} (${args.offset})`);
|
|
15292
|
+
results.push(` Data: ${args.hexData}`);
|
|
15293
|
+
results.push("");
|
|
15294
|
+
if (createBackup) {
|
|
15295
|
+
const backupPath = `${args.filePath}.bak`;
|
|
15296
|
+
await execAsync(`cp "${args.filePath}" "${backupPath}"`);
|
|
15297
|
+
results.push(`[1/4] Backup created: ${backupPath}`);
|
|
15298
|
+
} else {
|
|
15299
|
+
results.push("[1/4] Backup: SKIPPED (createBackup=false)");
|
|
15300
|
+
}
|
|
15301
|
+
results.push("[2/4] Applying binary patch...");
|
|
15302
|
+
const hexBytes = args.hexData.replace(/\s+/g, "");
|
|
15303
|
+
const byteCount = hexBytes.length / 2;
|
|
15304
|
+
const patchCmd = `printf '${hexBytes}' | dd of="${args.filePath}" bs=1 seek=${args.offset} count=${byteCount} conv=notrunc 2>/dev/null`;
|
|
15305
|
+
await execAsync(patchCmd);
|
|
15306
|
+
results.push(` Patched ${byteCount} bytes at offset 0x${args.offset.toString(16)}`);
|
|
15307
|
+
results.push("[3/4] Removing old code signature...");
|
|
15308
|
+
try {
|
|
15309
|
+
await execAsync(`codesign --remove-signature "${args.filePath}" 2>&1`);
|
|
15310
|
+
results.push(" Old signature removed");
|
|
15311
|
+
} catch (e) {
|
|
15312
|
+
if (e instanceof Error && e.message.includes("no signature")) {
|
|
15313
|
+
results.push(" No previous signature to remove");
|
|
15314
|
+
} else {
|
|
15315
|
+
results.push(` Warning: ${e instanceof Error ? e.message : String(e)}`);
|
|
15316
|
+
}
|
|
15317
|
+
}
|
|
15318
|
+
results.push("[4/4] Applying ad-hoc signature...");
|
|
15319
|
+
await execAsync(`codesign --force --sign - "${args.filePath}"`);
|
|
15320
|
+
results.push(" Ad-hoc signature applied");
|
|
15321
|
+
results.push("[5/5] Removing quarantine attributes...");
|
|
15322
|
+
try {
|
|
15323
|
+
await execAsync(`xattr -d com.apple.quarantine "${args.filePath}" 2>&1`);
|
|
15324
|
+
results.push(" Quarantine attribute removed");
|
|
15325
|
+
} catch {
|
|
15326
|
+
results.push(" No quarantine attribute present");
|
|
15327
|
+
}
|
|
15328
|
+
try {
|
|
15329
|
+
await execAsync(`xattr -d com.apple.provenance "${args.filePath}" 2>&1`);
|
|
15330
|
+
results.push(" Provenance attribute removed");
|
|
15331
|
+
} catch {}
|
|
15332
|
+
results.push("");
|
|
15333
|
+
results.push("Verification:");
|
|
15334
|
+
try {
|
|
15335
|
+
await execAsync(`codesign -v "${args.filePath}" 2>&1`);
|
|
15336
|
+
results.push(" Code signature: VALID");
|
|
15337
|
+
} catch (e) {
|
|
15338
|
+
results.push(` Code signature: WARNING - ${e instanceof Error ? e.message : String(e)}`);
|
|
15339
|
+
}
|
|
15340
|
+
try {
|
|
15341
|
+
const { stdout: fileInfo } = await execAsync(`file "${args.filePath}"`);
|
|
15342
|
+
if (fileInfo.includes("executable")) {
|
|
15343
|
+
results.push(" File type: Executable");
|
|
15344
|
+
}
|
|
15345
|
+
} catch {}
|
|
15346
|
+
results.push("");
|
|
15347
|
+
results.push("Status: SUCCESS - Binary patched and ready to run");
|
|
15348
|
+
results.push("Note: The binary is now signed with an ad-hoc signature.");
|
|
15349
|
+
results.push(" Some apps may require proper code signing for full functionality.");
|
|
15350
|
+
return { content: [{ type: "text", text: results.join(`
|
|
15351
|
+
`) }] };
|
|
15352
|
+
} catch (error2) {
|
|
15353
|
+
return {
|
|
15354
|
+
content: [{ type: "text", text: `Error: ${error2 instanceof Error ? error2.message : String(error2)}` }],
|
|
15355
|
+
isError: true
|
|
15356
|
+
};
|
|
15357
|
+
}
|
|
15358
|
+
}
|
|
15359
|
+
async function handleBinVerify(args) {
|
|
15360
|
+
if (!isMacOS()) {
|
|
15361
|
+
return {
|
|
15362
|
+
content: [{
|
|
15363
|
+
type: "text",
|
|
15364
|
+
text: "Error: Binary verification tools are only available on macOS."
|
|
15365
|
+
}],
|
|
15366
|
+
isError: true
|
|
15367
|
+
};
|
|
15368
|
+
}
|
|
15369
|
+
try {
|
|
15370
|
+
const results = [];
|
|
15371
|
+
const issues = [];
|
|
15372
|
+
results.push(`Binary Verification: ${args.filePath}`);
|
|
15373
|
+
results.push("");
|
|
15374
|
+
let isMachO = false;
|
|
15375
|
+
try {
|
|
15376
|
+
const { stdout: fileInfo } = await execAsync(`file "${args.filePath}"`);
|
|
15377
|
+
results.push(`File type: ${fileInfo.trim()}`);
|
|
15378
|
+
isMachO = fileInfo.toLowerCase().includes("mach-o");
|
|
15379
|
+
} catch (e) {
|
|
15380
|
+
return {
|
|
15381
|
+
content: [{ type: "text", text: `Error: Cannot read file - ${e instanceof Error ? e.message : String(e)}` }],
|
|
15382
|
+
isError: true
|
|
15383
|
+
};
|
|
15384
|
+
}
|
|
15385
|
+
try {
|
|
15386
|
+
const { stdout: perms } = await execAsync(`ls -la "${args.filePath}"`);
|
|
15387
|
+
const permLine = perms.split(`
|
|
15388
|
+
`).find((l) => l.includes(args.filePath.split("/").pop() || ""));
|
|
15389
|
+
if (permLine) {
|
|
15390
|
+
results.push(`Permissions: ${permLine.split(/\s+/)[0]}`);
|
|
15391
|
+
if (!permLine.includes("x")) {
|
|
15392
|
+
issues.push("File is not executable (no execute permission)");
|
|
15393
|
+
}
|
|
15394
|
+
}
|
|
15395
|
+
} catch {
|
|
15396
|
+
results.push("Permissions: Unable to check");
|
|
15397
|
+
}
|
|
15398
|
+
results.push("");
|
|
15399
|
+
if (isMachO) {
|
|
15400
|
+
results.push("Code Signature:");
|
|
15401
|
+
try {
|
|
15402
|
+
const { stdout: signInfo } = await execAsync(`codesign -dv "${args.filePath}" 2>&1`);
|
|
15403
|
+
const authority = signInfo.split(`
|
|
15404
|
+
`).find((l) => l.includes("Authority"));
|
|
15405
|
+
if (authority) {
|
|
15406
|
+
results.push(` Status: SIGNED (${authority.split(":")[1]?.trim() || "unknown authority"})`);
|
|
15407
|
+
} else {
|
|
15408
|
+
results.push(" Status: SIGNED (ad-hoc or unknown)");
|
|
15409
|
+
}
|
|
15410
|
+
} catch (e) {
|
|
15411
|
+
const output = e instanceof Error ? e.message : String(e);
|
|
15412
|
+
if (output.includes("not signed")) {
|
|
15413
|
+
results.push(" Status: UNSIGNED");
|
|
15414
|
+
issues.push("Binary is not code signed");
|
|
15415
|
+
} else {
|
|
15416
|
+
results.push(` Status: INVALID - ${output}`);
|
|
15417
|
+
issues.push("Code signature is invalid");
|
|
15418
|
+
}
|
|
15419
|
+
}
|
|
15420
|
+
try {
|
|
15421
|
+
await execAsync(`codesign -v "${args.filePath}" 2>&1`);
|
|
15422
|
+
results.push(" Validity: VALID");
|
|
15423
|
+
} catch (e) {
|
|
15424
|
+
const output = e instanceof Error ? e.message : String(e);
|
|
15425
|
+
if (!output.includes("not signed")) {
|
|
15426
|
+
results.push(` Validity: INVALID - ${output}`);
|
|
15427
|
+
issues.push("Code signature verification failed");
|
|
15428
|
+
}
|
|
15429
|
+
}
|
|
15430
|
+
}
|
|
15431
|
+
results.push("");
|
|
15432
|
+
results.push("Quarantine:");
|
|
15433
|
+
try {
|
|
15434
|
+
const { stdout: quar } = await execAsync(`xattr -p com.apple.quarantine "${args.filePath}" 2>&1`);
|
|
15435
|
+
if (quar.trim()) {
|
|
15436
|
+
results.push(" Status: QUARANTINED");
|
|
15437
|
+
issues.push("File is quarantined and may be blocked from running");
|
|
15438
|
+
} else {
|
|
15439
|
+
results.push(" Status: NOT QUARANTINED");
|
|
15440
|
+
}
|
|
15441
|
+
} catch {
|
|
15442
|
+
results.push(" Status: NOT QUARANTINED");
|
|
15443
|
+
}
|
|
15444
|
+
if (isMachO) {
|
|
15445
|
+
results.push("");
|
|
15446
|
+
results.push("Architecture:");
|
|
15447
|
+
try {
|
|
15448
|
+
const { stdout: arch } = await execAsync(`lipo -info "${args.filePath}" 2>&1`);
|
|
15449
|
+
results.push(` ${arch.trim()}`);
|
|
15450
|
+
try {
|
|
15451
|
+
const { stdout: currentArch } = await execAsync(`uname -m`);
|
|
15452
|
+
if (!arch.includes(currentArch.trim()) && !arch.includes("universal")) {
|
|
15453
|
+
issues.push(`Binary may not be compatible with current architecture (${currentArch.trim()})`);
|
|
15454
|
+
}
|
|
15455
|
+
} catch {}
|
|
15456
|
+
} catch (e) {
|
|
15457
|
+
results.push(` Unable to determine: ${e instanceof Error ? e.message : String(e)}`);
|
|
15458
|
+
}
|
|
15459
|
+
}
|
|
15460
|
+
results.push("");
|
|
15461
|
+
results.push("=== SUMMARY ===");
|
|
15462
|
+
if (issues.length === 0) {
|
|
15463
|
+
results.push("Status: READY TO RUN");
|
|
15464
|
+
results.push("The binary should execute without issues.");
|
|
15465
|
+
} else {
|
|
15466
|
+
results.push("Status: ISSUES DETECTED");
|
|
15467
|
+
results.push("");
|
|
15468
|
+
results.push("Issues:");
|
|
15469
|
+
issues.forEach((issue2, i) => results.push(` ${i + 1}. ${issue2}`));
|
|
15470
|
+
results.push("");
|
|
15471
|
+
results.push("Suggested fixes:");
|
|
15472
|
+
if (issues.some((i) => i.includes("quarantined"))) {
|
|
15473
|
+
results.push(" - Run: xattr -d com.apple.quarantine <file>");
|
|
15474
|
+
}
|
|
15475
|
+
if (issues.some((i) => i.includes("not code signed") || i.includes("signature"))) {
|
|
15476
|
+
results.push(" - Run: codesign --force --sign - <file>");
|
|
15477
|
+
}
|
|
15478
|
+
if (issues.some((i) => i.includes("not executable"))) {
|
|
15479
|
+
results.push(" - Run: chmod +x <file>");
|
|
15480
|
+
}
|
|
15481
|
+
}
|
|
15482
|
+
return { content: [{ type: "text", text: results.join(`
|
|
15483
|
+
`) }] };
|
|
15484
|
+
} catch (error2) {
|
|
15485
|
+
return {
|
|
15486
|
+
content: [{ type: "text", text: `Error: ${error2 instanceof Error ? error2.message : String(error2)}` }],
|
|
15487
|
+
isError: true
|
|
15488
|
+
};
|
|
15489
|
+
}
|
|
15490
|
+
}
|
|
14980
15491
|
var TOOLS = [
|
|
14981
15492
|
{
|
|
14982
15493
|
name: "nm_list_symbols",
|
|
@@ -15439,6 +15950,92 @@ var TOOLS = [
|
|
|
15439
15950
|
},
|
|
15440
15951
|
required: ["filePath", "offset", "length"]
|
|
15441
15952
|
}
|
|
15953
|
+
},
|
|
15954
|
+
{
|
|
15955
|
+
name: "bin_codesign_info",
|
|
15956
|
+
description: "Show code signature details for a macOS binary (macOS only)",
|
|
15957
|
+
inputSchema: {
|
|
15958
|
+
type: "object",
|
|
15959
|
+
properties: {
|
|
15960
|
+
filePath: { type: "string", description: "Path to the binary file" }
|
|
15961
|
+
},
|
|
15962
|
+
required: ["filePath"]
|
|
15963
|
+
}
|
|
15964
|
+
},
|
|
15965
|
+
{
|
|
15966
|
+
name: "bin_codesign_remove",
|
|
15967
|
+
description: "Remove code signature from a macOS binary (DESTRUCTIVE - macOS only)",
|
|
15968
|
+
inputSchema: {
|
|
15969
|
+
type: "object",
|
|
15970
|
+
properties: {
|
|
15971
|
+
filePath: { type: "string", description: "Path to the binary file" },
|
|
15972
|
+
createBackup: { type: "boolean", description: "Create .bak backup (default: true)" }
|
|
15973
|
+
},
|
|
15974
|
+
required: ["filePath"]
|
|
15975
|
+
}
|
|
15976
|
+
},
|
|
15977
|
+
{
|
|
15978
|
+
name: "bin_codesign_sign",
|
|
15979
|
+
description: "Apply ad-hoc or certificate signature to a macOS binary (macOS only)",
|
|
15980
|
+
inputSchema: {
|
|
15981
|
+
type: "object",
|
|
15982
|
+
properties: {
|
|
15983
|
+
filePath: { type: "string", description: "Path to the binary file" },
|
|
15984
|
+
certificate: { type: "string", description: "Certificate identity (default: '-' for ad-hoc signature)" },
|
|
15985
|
+
force: { type: "boolean", description: "Force replace existing signature (default: true)" },
|
|
15986
|
+
createBackup: { type: "boolean", description: "Create .bak backup before signing" },
|
|
15987
|
+
deep: { type: "boolean", description: "Sign all nested code" },
|
|
15988
|
+
options: { type: "string", description: "Additional signing options (e.g., 'runtime' for hardened runtime)" }
|
|
15989
|
+
},
|
|
15990
|
+
required: ["filePath"]
|
|
15991
|
+
}
|
|
15992
|
+
},
|
|
15993
|
+
{
|
|
15994
|
+
name: "bin_quarantine_check",
|
|
15995
|
+
description: "Check quarantine status of a file on macOS (macOS only)",
|
|
15996
|
+
inputSchema: {
|
|
15997
|
+
type: "object",
|
|
15998
|
+
properties: {
|
|
15999
|
+
filePath: { type: "string", description: "Path to the file" }
|
|
16000
|
+
},
|
|
16001
|
+
required: ["filePath"]
|
|
16002
|
+
}
|
|
16003
|
+
},
|
|
16004
|
+
{
|
|
16005
|
+
name: "bin_quarantine_remove",
|
|
16006
|
+
description: "Remove quarantine attributes from a file on macOS (DESTRUCTIVE - macOS only)",
|
|
16007
|
+
inputSchema: {
|
|
16008
|
+
type: "object",
|
|
16009
|
+
properties: {
|
|
16010
|
+
filePath: { type: "string", description: "Path to the file" }
|
|
16011
|
+
},
|
|
16012
|
+
required: ["filePath"]
|
|
16013
|
+
}
|
|
16014
|
+
},
|
|
16015
|
+
{
|
|
16016
|
+
name: "bin_safe_patch",
|
|
16017
|
+
description: "Safe binary patching workflow for macOS: patch, remove signature, re-sign, remove quarantine (DESTRUCTIVE - macOS only)",
|
|
16018
|
+
inputSchema: {
|
|
16019
|
+
type: "object",
|
|
16020
|
+
properties: {
|
|
16021
|
+
filePath: { type: "string", description: "Path to the binary file" },
|
|
16022
|
+
offset: { type: "number", description: "Byte offset to patch" },
|
|
16023
|
+
hexData: { type: "string", description: "Hex bytes to write (e.g., '90 90 90')" },
|
|
16024
|
+
createBackup: { type: "boolean", description: "Create .bak backup (default: true)" }
|
|
16025
|
+
},
|
|
16026
|
+
required: ["filePath", "offset", "hexData"]
|
|
16027
|
+
}
|
|
16028
|
+
},
|
|
16029
|
+
{
|
|
16030
|
+
name: "bin_verify",
|
|
16031
|
+
description: "Verify if a macOS binary will run: check signature, quarantine, architecture (macOS only)",
|
|
16032
|
+
inputSchema: {
|
|
16033
|
+
type: "object",
|
|
16034
|
+
properties: {
|
|
16035
|
+
filePath: { type: "string", description: "Path to the binary file" }
|
|
16036
|
+
},
|
|
16037
|
+
required: ["filePath"]
|
|
16038
|
+
}
|
|
15442
16039
|
}
|
|
15443
16040
|
];
|
|
15444
16041
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
@@ -15516,6 +16113,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
15516
16113
|
return await handleHexEditor(args);
|
|
15517
16114
|
case "bin_convert":
|
|
15518
16115
|
return await handleConvertNumber(args);
|
|
16116
|
+
case "bin_codesign_info":
|
|
16117
|
+
return await handleCodesignInfo(args);
|
|
16118
|
+
case "bin_codesign_remove":
|
|
16119
|
+
return await handleCodesignRemove(args);
|
|
16120
|
+
case "bin_codesign_sign":
|
|
16121
|
+
return await handleCodesignSign(args);
|
|
16122
|
+
case "bin_quarantine_check":
|
|
16123
|
+
return await handleQuarantineCheck(args);
|
|
16124
|
+
case "bin_quarantine_remove":
|
|
16125
|
+
return await handleQuarantineRemove(args);
|
|
16126
|
+
case "bin_safe_patch":
|
|
16127
|
+
return await handleSafePatch(args);
|
|
16128
|
+
case "bin_verify":
|
|
16129
|
+
return await handleBinVerify(args);
|
|
15519
16130
|
default:
|
|
15520
16131
|
throw new Error(`Unknown tool: ${name}`);
|
|
15521
16132
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ebowwa/mcp-nm",
|
|
3
|
-
"version": "2.0.
|
|
4
|
-
"description": "Comprehensive binary analysis MCP server - symbols (nm), hex dumps (xxd), strings, disassembly, security audit, entropy analysis, ELF/Mach-O inspection, and
|
|
3
|
+
"version": "2.0.3",
|
|
4
|
+
"description": "Comprehensive binary analysis MCP server - symbols (nm), hex dumps (xxd), strings, disassembly, security audit, entropy analysis, ELF/Mach-O inspection, binary patching, and macOS code signing tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -33,7 +33,10 @@
|
|
|
33
33
|
"binary-patching",
|
|
34
34
|
"objdump",
|
|
35
35
|
"readelf",
|
|
36
|
-
"otool"
|
|
36
|
+
"otool",
|
|
37
|
+
"codesign",
|
|
38
|
+
"quarantine",
|
|
39
|
+
"macos"
|
|
37
40
|
],
|
|
38
41
|
"author": "ebowwa",
|
|
39
42
|
"license": "MIT",
|