multicorn-shield 0.1.6 → 0.1.8
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/README.md +2 -0
- package/dist/openclaw-hook/handler.js +8 -1
- package/dist/openclaw-plugin/index.js +31 -48
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -411,6 +411,12 @@ async function ensureAgent(agentName, apiKey, baseUrl, failMode) {
|
|
|
411
411
|
}
|
|
412
412
|
async function ensureConsent(agentName, apiKey, baseUrl, scope) {
|
|
413
413
|
if (agentRecord === null) return;
|
|
414
|
+
const apiScopes = await fetchGrantedScopes(agentRecord.id, apiKey, baseUrl);
|
|
415
|
+
if (apiScopes.length > 0) {
|
|
416
|
+
grantedScopes = apiScopes;
|
|
417
|
+
lastScopeRefresh = Date.now();
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
414
420
|
if (scope !== void 0) {
|
|
415
421
|
const requestedScope = {
|
|
416
422
|
service: scope.service,
|
|
@@ -468,8 +474,9 @@ var handler = async (event) => {
|
|
|
468
474
|
const permitted = isPermitted(event);
|
|
469
475
|
if (!permitted) {
|
|
470
476
|
const capitalizedService = mapping.service.charAt(0).toUpperCase() + mapping.service.slice(1);
|
|
477
|
+
const dashboardUrl = deriveDashboardUrl(config.baseUrl);
|
|
471
478
|
event.messages.push(
|
|
472
|
-
`Permission denied: ${capitalizedService} ${mapping.permissionLevel} access is not allowed.
|
|
479
|
+
`Permission denied: ${capitalizedService} ${mapping.permissionLevel} access is not allowed. Check pending approvals at ${dashboardUrl}/approvals `
|
|
473
480
|
);
|
|
474
481
|
void logAction(
|
|
475
482
|
{
|
|
@@ -495,53 +495,20 @@ var pluginLogger = null;
|
|
|
495
495
|
var pluginConfig;
|
|
496
496
|
var connectionLogged = false;
|
|
497
497
|
var SCOPE_REFRESH_INTERVAL_MS = 6e4;
|
|
498
|
+
var cachedMulticornConfig = null;
|
|
499
|
+
function loadMulticornConfig() {
|
|
500
|
+
try {
|
|
501
|
+
const configPath = path.join(os.homedir(), ".multicorn", "config.json");
|
|
502
|
+
const raw = fs.readFileSync(configPath, "utf-8");
|
|
503
|
+
return JSON.parse(raw);
|
|
504
|
+
} catch {
|
|
505
|
+
return null;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
498
508
|
function readConfig() {
|
|
499
509
|
const pc = pluginConfig ?? {};
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
if (!resolvedApiKey) {
|
|
503
|
-
try {
|
|
504
|
-
const multicornConfigPath = path.join(os.homedir(), ".multicorn", "config.json");
|
|
505
|
-
const multicornConfigContent = fs.readFileSync(multicornConfigPath, "utf-8");
|
|
506
|
-
const multicornConfig = JSON.parse(multicornConfigContent);
|
|
507
|
-
if (multicornConfig && typeof multicornConfig.apiKey === "string" && multicornConfig.apiKey.length > 0) {
|
|
508
|
-
resolvedApiKey = multicornConfig.apiKey;
|
|
509
|
-
if (!resolvedBaseUrl) {
|
|
510
|
-
resolvedBaseUrl = multicornConfig.baseUrl ?? "https://api.multicorn.ai";
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
} catch {
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
if (!resolvedApiKey) {
|
|
517
|
-
try {
|
|
518
|
-
const configPath = path.join(os.homedir(), ".openclaw", "openclaw.json");
|
|
519
|
-
const configContent = fs.readFileSync(configPath, "utf-8");
|
|
520
|
-
const config = JSON.parse(configContent);
|
|
521
|
-
const hooks = config["hooks"];
|
|
522
|
-
const internal = hooks?.["internal"];
|
|
523
|
-
const entries = internal?.["entries"];
|
|
524
|
-
const shieldEntry = entries?.["multicorn-shield"];
|
|
525
|
-
const env = shieldEntry?.env;
|
|
526
|
-
if (env) {
|
|
527
|
-
const hookApiKey = asString(env["MULTICORN_API_KEY"]);
|
|
528
|
-
const hookBaseUrl = asString(env["MULTICORN_BASE_URL"]);
|
|
529
|
-
if (hookApiKey) {
|
|
530
|
-
resolvedApiKey = hookApiKey;
|
|
531
|
-
resolvedBaseUrl = resolvedBaseUrl || (hookBaseUrl ?? "https://api.multicorn.ai");
|
|
532
|
-
pluginLogger?.warn(
|
|
533
|
-
"Multicorn Shield: Reading config from hooks.internal.entries. For cleaner setup, set MULTICORN_API_KEY as a system environment variable."
|
|
534
|
-
);
|
|
535
|
-
} else if (hookBaseUrl) {
|
|
536
|
-
resolvedBaseUrl = resolvedBaseUrl || hookBaseUrl;
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
} catch {
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
if (!resolvedBaseUrl) {
|
|
543
|
-
resolvedBaseUrl = "https://api.multicorn.ai";
|
|
544
|
-
}
|
|
510
|
+
const resolvedApiKey = asString(process.env["MULTICORN_API_KEY"]) ?? asString(cachedMulticornConfig?.apiKey) ?? "";
|
|
511
|
+
const resolvedBaseUrl = asString(process.env["MULTICORN_BASE_URL"]) ?? asString(cachedMulticornConfig?.baseUrl) ?? "https://api.multicorn.ai";
|
|
545
512
|
const agentName = asString(pc["agentName"]) ?? process.env["MULTICORN_AGENT_NAME"] ?? null;
|
|
546
513
|
const failModeRaw = asString(pc["failMode"]) ?? process.env["MULTICORN_FAIL_MODE"] ?? "open";
|
|
547
514
|
const failMode = failModeRaw === "closed" ? "closed" : "open";
|
|
@@ -609,6 +576,17 @@ async function ensureAgent(agentName, apiKey, baseUrl, failMode) {
|
|
|
609
576
|
}
|
|
610
577
|
async function ensureConsent(agentName, apiKey, baseUrl, scope) {
|
|
611
578
|
if (agentRecord === null) return;
|
|
579
|
+
const apiScopes = await fetchGrantedScopes(
|
|
580
|
+
agentRecord.id,
|
|
581
|
+
apiKey,
|
|
582
|
+
baseUrl,
|
|
583
|
+
pluginLogger ?? void 0
|
|
584
|
+
);
|
|
585
|
+
if (apiScopes.length > 0) {
|
|
586
|
+
grantedScopes = apiScopes;
|
|
587
|
+
lastScopeRefresh = Date.now();
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
612
590
|
if (scope !== void 0) {
|
|
613
591
|
const requestedScope = {
|
|
614
592
|
service: scope.service,
|
|
@@ -707,7 +685,9 @@ function buildApprovalDescription(agentName, permissionLevel, service, toolName,
|
|
|
707
685
|
async function beforeToolCall(event, ctx) {
|
|
708
686
|
const config = readConfig();
|
|
709
687
|
if (config.apiKey.length === 0) {
|
|
710
|
-
pluginLogger?.warn(
|
|
688
|
+
pluginLogger?.warn(
|
|
689
|
+
"Multicorn Shield: No API key found. Run 'npx multicorn-proxy init' or set MULTICORN_API_KEY."
|
|
690
|
+
);
|
|
711
691
|
return void 0;
|
|
712
692
|
}
|
|
713
693
|
const agentName = resolveAgentName(ctx.sessionKey ?? "", config.agentName);
|
|
@@ -804,7 +784,8 @@ async function beforeToolCall(event, ctx) {
|
|
|
804
784
|
await ensureConsent(agentName, config.apiKey, config.baseUrl, mapping);
|
|
805
785
|
}
|
|
806
786
|
const capitalizedService = mapping.service.charAt(0).toUpperCase() + mapping.service.slice(1);
|
|
807
|
-
const
|
|
787
|
+
const dashboardUrl = deriveDashboardUrl(config.baseUrl);
|
|
788
|
+
const reason = `${capitalizedService} ${mapping.permissionLevel} access is not allowed. Check pending approvals at ${dashboardUrl}/approvals `;
|
|
808
789
|
return { block: true, blockReason: reason };
|
|
809
790
|
}
|
|
810
791
|
function afterToolCall(event, ctx) {
|
|
@@ -837,13 +818,14 @@ var plugin = {
|
|
|
837
818
|
register(api) {
|
|
838
819
|
pluginLogger = api.logger;
|
|
839
820
|
pluginConfig = api.pluginConfig;
|
|
821
|
+
cachedMulticornConfig = loadMulticornConfig();
|
|
840
822
|
api.on("before_tool_call", beforeToolCall, { priority: 10 });
|
|
841
823
|
api.on("after_tool_call", afterToolCall);
|
|
842
824
|
api.logger.info("Multicorn Shield plugin registered.");
|
|
843
825
|
const config = readConfig();
|
|
844
826
|
if (config.apiKey.length === 0) {
|
|
845
827
|
api.logger.error(
|
|
846
|
-
"Multicorn Shield: No API key found. Run
|
|
828
|
+
"Multicorn Shield: No API key found. Run 'npx multicorn-proxy init' or set MULTICORN_API_KEY."
|
|
847
829
|
);
|
|
848
830
|
} else {
|
|
849
831
|
api.logger.info(`Multicorn Shield connecting to ${config.baseUrl}`);
|
|
@@ -862,6 +844,7 @@ function resetState() {
|
|
|
862
844
|
lastScopeRefresh = 0;
|
|
863
845
|
pluginLogger = null;
|
|
864
846
|
pluginConfig = void 0;
|
|
847
|
+
cachedMulticornConfig = null;
|
|
865
848
|
connectionLogged = false;
|
|
866
849
|
}
|
|
867
850
|
|