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 CHANGED
@@ -1,3 +1,5 @@
1
+ ![Multicorn Shield](readme-header.svg)
2
+
1
3
  # Multicorn Shield
2
4
 
3
5
  The permissions and control layer for AI agents. Open source.
@@ -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. Visit the Multicorn Shield dashboard to manage permissions.`
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
- let resolvedApiKey = asString(pc["apiKey"]) ?? process.env["MULTICORN_API_KEY"] ?? "";
501
- let resolvedBaseUrl = asString(pc["baseUrl"]) ?? process.env["MULTICORN_BASE_URL"] ?? "";
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("MULTICORN_API_KEY is not set. Skipping permission checks.");
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 reason = `${capitalizedService} ${mapping.permissionLevel} access is not allowed. Visit the Multicorn Shield dashboard to manage permissions.`;
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 `npx multicorn-proxy init` to set up your API key, or set MULTICORN_API_KEY in your OpenClaw config (~/.openclaw/openclaw.json \u2192 plugins.entries.multicorn-shield.env.MULTICORN_API_KEY). Get a key from your Multicorn dashboard (Settings \u2192 API Keys)."
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "multicorn-shield",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "The control layer for AI agents: permissions, consent, spending limits, and audit logging.",
5
5
  "license": "MIT",
6
6
  "type": "module",