@samanhappy/mcphub 1.1.6 → 1.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/dist/clients/openapi.js +2 -1
- package/dist/clients/openapi.js.map +1 -1
- package/dist/controllers/authController.js +27 -1
- package/dist/controllers/authController.js.map +1 -1
- package/dist/controllers/configController.js +4 -1
- package/dist/controllers/configController.js.map +1 -1
- package/dist/controllers/groupController.js +21 -6
- package/dist/controllers/groupController.js.map +1 -1
- package/dist/controllers/metricsController.js +33 -0
- package/dist/controllers/metricsController.js.map +1 -0
- package/dist/controllers/oauthServerController.js +12 -14
- package/dist/controllers/oauthServerController.js.map +1 -1
- package/dist/controllers/serverController.js +117 -11
- package/dist/controllers/serverController.js.map +1 -1
- package/dist/dao/GroupDaoDbImpl.js +9 -0
- package/dist/dao/GroupDaoDbImpl.js.map +1 -1
- package/dist/dao/OAuthTokenDaoDbImpl.js +2 -0
- package/dist/dao/OAuthTokenDaoDbImpl.js.map +1 -1
- package/dist/dao/ServerDao.js.map +1 -1
- package/dist/dao/ServerDaoDbImpl.js.map +1 -1
- package/dist/db/entities/Group.js +8 -0
- package/dist/db/entities/Group.js.map +1 -1
- package/dist/db/entities/OAuthToken.js +4 -0
- package/dist/db/entities/OAuthToken.js.map +1 -1
- package/dist/db/entities/Server.js +4 -0
- package/dist/db/entities/Server.js.map +1 -1
- package/dist/middlewares/auth.js +7 -25
- package/dist/middlewares/auth.js.map +1 -1
- package/dist/middlewares/toolCallGuard.js +499 -0
- package/dist/middlewares/toolCallGuard.js.map +1 -0
- package/dist/middlewares/userContext.js +6 -34
- package/dist/middlewares/userContext.js.map +1 -1
- package/dist/models/OAuth.js +14 -93
- package/dist/models/OAuth.js.map +1 -1
- package/dist/routes/index.js +4 -1
- package/dist/routes/index.js.map +1 -1
- package/dist/services/betterAuthConfig.js +1 -0
- package/dist/services/betterAuthConfig.js.map +1 -1
- package/dist/services/contextCostService.js +3 -1
- package/dist/services/contextCostService.js.map +1 -1
- package/dist/services/groupService.js +1 -0
- package/dist/services/groupService.js.map +1 -1
- package/dist/services/mcpOAuthProvider.js +0 -2
- package/dist/services/mcpOAuthProvider.js.map +1 -1
- package/dist/services/mcpService.js +120 -45
- package/dist/services/mcpService.js.map +1 -1
- package/dist/services/metricsService.js +54 -0
- package/dist/services/metricsService.js.map +1 -0
- package/dist/services/oauthClientRegistration.js +1 -1
- package/dist/services/oauthClientRegistration.js.map +1 -1
- package/dist/services/oauthServerService.js +15 -1
- package/dist/services/oauthServerService.js.map +1 -1
- package/dist/services/openApiGeneratorService.js +3 -1
- package/dist/services/openApiGeneratorService.js.map +1 -1
- package/dist/services/personalOAuthService.js +70 -8
- package/dist/services/personalOAuthService.js.map +1 -1
- package/dist/services/registrationService.js +28 -0
- package/dist/services/registrationService.js.map +1 -0
- package/dist/services/smartRoutingService.js +7 -6
- package/dist/services/smartRoutingService.js.map +1 -1
- package/dist/services/templateService.js +3 -1
- package/dist/services/templateService.js.map +1 -1
- package/dist/services/toolResultCompressionService.js +26 -1
- package/dist/services/toolResultCompressionService.js.map +1 -1
- package/dist/services/userService.js +22 -4
- package/dist/services/userService.js.map +1 -1
- package/dist/utils/migration.js +4 -2
- package/dist/utils/migration.js.map +1 -1
- package/dist/utils/oauthBearer.js +4 -2
- package/dist/utils/oauthBearer.js.map +1 -1
- package/dist/utils/oauthRedirectUri.js +3 -11
- package/dist/utils/oauthRedirectUri.js.map +1 -1
- package/dist/utils/parameterConversion.js +9 -4
- package/dist/utils/parameterConversion.js.map +1 -1
- package/dist/utils/passthroughHeaders.js +17 -0
- package/dist/utils/passthroughHeaders.js.map +1 -0
- package/dist/utils/rateLimit.js +1 -1
- package/dist/utils/rateLimit.js.map +1 -1
- package/dist/utils/serialization.js +2 -5
- package/dist/utils/serialization.js.map +1 -1
- package/dist/utils/serverConfigValidation.js +2 -1
- package/dist/utils/serverConfigValidation.js.map +1 -1
- package/dist/utils/settingsSync.js +58 -5
- package/dist/utils/settingsSync.js.map +1 -1
- package/dist/utils/tokenTruncation.js +1 -1
- package/dist/utils/tokenTruncation.js.map +1 -1
- package/dist/utils/toolConfig.js +93 -0
- package/dist/utils/toolConfig.js.map +1 -0
- package/dist/utils/userSession.js +29 -0
- package/dist/utils/userSession.js.map +1 -0
- package/frontend/dist/assets/{ActivityPage-e2s2j32V.js → ActivityPage-CJdAfOUF.js} +2 -2
- package/frontend/dist/assets/{ActivityPage-e2s2j32V.js.map → ActivityPage-CJdAfOUF.js.map} +1 -1
- package/frontend/dist/assets/{CopyClientConfigDialog-B71Cl89b.js → CopyClientConfigDialog-DKktm8jj.js} +2 -2
- package/frontend/dist/assets/{CopyClientConfigDialog-B71Cl89b.js.map → CopyClientConfigDialog-DKktm8jj.js.map} +1 -1
- package/frontend/dist/assets/{CredentialsPage-BnA17pRJ.js → CredentialsPage-CPyZrp4Z.js} +2 -2
- package/frontend/dist/assets/{CredentialsPage-BnA17pRJ.js.map → CredentialsPage-CPyZrp4Z.js.map} +1 -1
- package/frontend/dist/assets/{Dashboard-gC_Z286J.js → Dashboard-DM_IiBIT.js} +2 -2
- package/frontend/dist/assets/{Dashboard-gC_Z286J.js.map → Dashboard-DM_IiBIT.js.map} +1 -1
- package/frontend/dist/assets/{EndpointCopy-BjD8ehcn.js → EndpointCopy-DHQjJesJ.js} +2 -2
- package/frontend/dist/assets/{EndpointCopy-BjD8ehcn.js.map → EndpointCopy-DHQjJesJ.js.map} +1 -1
- package/frontend/dist/assets/{GroupsPage-DiT_skCf.js → GroupsPage-VKHjil-L.js} +4 -4
- package/frontend/dist/assets/GroupsPage-VKHjil-L.js.map +1 -0
- package/frontend/dist/assets/LoginPage-CKrI0By4.js +2 -0
- package/frontend/dist/assets/LoginPage-CKrI0By4.js.map +1 -0
- package/frontend/dist/assets/{LogsPage-BzI3IAHO.js → LogsPage-Dh8gRpEH.js} +2 -2
- package/frontend/dist/assets/{LogsPage-BzI3IAHO.js.map → LogsPage-Dh8gRpEH.js.map} +1 -1
- package/frontend/dist/assets/{MarketPage-DVo32QTs.js → MarketPage-BN4qL8L9.js} +2 -2
- package/frontend/dist/assets/{MarketPage-DVo32QTs.js.map → MarketPage-BN4qL8L9.js.map} +1 -1
- package/frontend/dist/assets/{OAuthConsentPage-DUapnxQ9.js → OAuthConsentPage-Cyj-rrA4.js} +2 -2
- package/frontend/dist/assets/{OAuthConsentPage-DUapnxQ9.js.map → OAuthConsentPage-Cyj-rrA4.js.map} +1 -1
- package/frontend/dist/assets/{PromptsPage-BAqJay5m.js → PromptsPage-YWD04XeI.js} +2 -2
- package/frontend/dist/assets/{PromptsPage-BAqJay5m.js.map → PromptsPage-YWD04XeI.js.map} +1 -1
- package/frontend/dist/assets/{ResourcesPage-kIOm5X1A.js → ResourcesPage-CVRzHCf4.js} +2 -2
- package/frontend/dist/assets/{ResourcesPage-kIOm5X1A.js.map → ResourcesPage-CVRzHCf4.js.map} +1 -1
- package/frontend/dist/assets/ServersPage-DJBurfOe.js +39 -0
- package/frontend/dist/assets/ServersPage-DJBurfOe.js.map +1 -0
- package/frontend/dist/assets/{SettingsPage-DkMmUgCh.js → SettingsPage-3sK6NJRn.js} +6 -6
- package/frontend/dist/assets/SettingsPage-3sK6NJRn.js.map +1 -0
- package/frontend/dist/assets/{StatusDot-Bm_C7v3r.js → StatusDot-CLZidCyq.js} +2 -2
- package/frontend/dist/assets/{StatusDot-Bm_C7v3r.js.map → StatusDot-CLZidCyq.js.map} +1 -1
- package/frontend/dist/assets/{ToggleGroup-DcOtP3Gf.js → ToggleGroup-Dfh4jKpP.js} +2 -2
- package/frontend/dist/assets/{ToggleGroup-DcOtP3Gf.js.map → ToggleGroup-Dfh4jKpP.js.map} +1 -1
- package/frontend/dist/assets/{UsersPage-DpcbXD3K.js → UsersPage-DfFqSRVb.js} +2 -2
- package/frontend/dist/assets/{UsersPage-DpcbXD3K.js.map → UsersPage-DfFqSRVb.js.map} +1 -1
- package/frontend/dist/assets/{clipboard-CvokTzlX.js → clipboard-wUZFI68F.js} +2 -2
- package/frontend/dist/assets/{clipboard-CvokTzlX.js.map → clipboard-wUZFI68F.js.map} +1 -1
- package/frontend/dist/assets/framework-vendor-CQFm9vKd.js.map +1 -1
- package/frontend/dist/assets/index-BbogfcdA.js +3 -0
- package/frontend/dist/assets/index-BbogfcdA.js.map +1 -0
- package/frontend/dist/assets/{resourceService-CbZwkCgS.js → resourceService-D7yitP7i.js} +2 -2
- package/frontend/dist/assets/{resourceService-CbZwkCgS.js.map → resourceService-D7yitP7i.js.map} +1 -1
- package/frontend/dist/assets/{useCostData-DZ5NdhVp.js → useCostData-33G6s4EW.js} +2 -2
- package/frontend/dist/assets/{useCostData-DZ5NdhVp.js.map → useCostData-33G6s4EW.js.map} +1 -1
- package/frontend/dist/assets/{variableDetection-SMtrW-P5.js → variableDetection-BVSe56ia.js} +2 -2
- package/frontend/dist/assets/{variableDetection-SMtrW-P5.js.map → variableDetection-BVSe56ia.js.map} +1 -1
- package/frontend/dist/index.html +1 -1
- package/package.json +3 -2
- package/frontend/dist/assets/GroupsPage-DiT_skCf.js.map +0 -1
- package/frontend/dist/assets/LoginPage-DK3WmTV9.js +0 -2
- package/frontend/dist/assets/LoginPage-DK3WmTV9.js.map +0 -1
- package/frontend/dist/assets/ServersPage-CkUdGq0R.js +0 -39
- package/frontend/dist/assets/ServersPage-CkUdGq0R.js.map +0 -1
- package/frontend/dist/assets/SettingsPage-DkMmUgCh.js.map +0 -1
- package/frontend/dist/assets/index-DkQidC_d.js +0 -3
- package/frontend/dist/assets/index-DkQidC_d.js.map +0 -1
|
@@ -25,6 +25,10 @@ __decorate([
|
|
|
25
25
|
Column({ type: 'text', nullable: true }),
|
|
26
26
|
__metadata("design:type", String)
|
|
27
27
|
], Group.prototype, "description", void 0);
|
|
28
|
+
__decorate([
|
|
29
|
+
Column({ type: 'text', nullable: true }),
|
|
30
|
+
__metadata("design:type", String)
|
|
31
|
+
], Group.prototype, "instructions", void 0);
|
|
28
32
|
__decorate([
|
|
29
33
|
Column({ type: 'simple-json' }),
|
|
30
34
|
__metadata("design:type", Array)
|
|
@@ -41,6 +45,10 @@ __decorate([
|
|
|
41
45
|
Column({ type: 'simple-json', nullable: true }),
|
|
42
46
|
__metadata("design:type", Array)
|
|
43
47
|
], Group.prototype, "sharedWithUsers", void 0);
|
|
48
|
+
__decorate([
|
|
49
|
+
Column({ type: 'boolean', default: false }),
|
|
50
|
+
__metadata("design:type", Boolean)
|
|
51
|
+
], Group.prototype, "settingsSyncManaged", void 0);
|
|
44
52
|
__decorate([
|
|
45
53
|
CreateDateColumn({ name: 'created_at', type: 'timestamp' }),
|
|
46
54
|
__metadata("design:type", Date)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Group.js","sourceRoot":"","sources":["../../../src/db/entities/Group.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EACL,MAAM,EACN,MAAM,EACN,sBAAsB,EACtB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,SAAS,CAAC;AAEjB;;GAEG;AAEI,IAAM,KAAK,GAAX,MAAM,KAAK;
|
|
1
|
+
{"version":3,"file":"Group.js","sourceRoot":"","sources":["../../../src/db/entities/Group.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EACL,MAAM,EACN,MAAM,EACN,sBAAsB,EACtB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,SAAS,CAAC;AAEjB;;GAEG;AAEI,IAAM,KAAK,GAAX,MAAM,KAAK;CA4CjB,CAAA;AA1CC;IADC,sBAAsB,CAAC,MAAM,CAAC;;iCACpB;AAGX;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;;mCAC1C;AAGb;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;0CACpB;AAGrB;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;2CACnB;AAGtB;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;8BACvB,KAAK;sCAUZ;AAGF;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;oCAC1C;AAGf;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;yCACZ;AAG5C;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;8CACrB;AAI3B;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;;kDACd;AAG9B;IADC,gBAAgB,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;8BACjD,IAAI;wCAAC;AAGhB;IADC,gBAAgB,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;8BACjD,IAAI;wCAAC;AA3CL,KAAK;IADjB,MAAM,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;GACd,KAAK,CA4CjB;;AAED,eAAe,KAAK,CAAC"}
|
|
@@ -50,6 +50,10 @@ __decorate([
|
|
|
50
50
|
Column({ type: 'varchar', length: 255 }),
|
|
51
51
|
__metadata("design:type", String)
|
|
52
52
|
], OAuthToken.prototype, "username", void 0);
|
|
53
|
+
__decorate([
|
|
54
|
+
Column({ name: 'session_version', type: 'varchar', length: 64, nullable: true }),
|
|
55
|
+
__metadata("design:type", String)
|
|
56
|
+
], OAuthToken.prototype, "sessionVersion", void 0);
|
|
53
57
|
__decorate([
|
|
54
58
|
CreateDateColumn({ name: 'created_at', type: 'timestamp' }),
|
|
55
59
|
__metadata("design:type", Date)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OAuthToken.js","sourceRoot":"","sources":["../../../src/db/entities/OAuthToken.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EACL,MAAM,EACN,MAAM,EACN,sBAAsB,EACtB,gBAAgB,EAChB,gBAAgB,EAChB,KAAK,GACN,MAAM,SAAS,CAAC;AAEjB;;;GAGG;AAEI,IAAM,UAAU,GAAhB,MAAM,UAAU;
|
|
1
|
+
{"version":3,"file":"OAuthToken.js","sourceRoot":"","sources":["../../../src/db/entities/OAuthToken.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EACL,MAAM,EACN,MAAM,EACN,sBAAsB,EACtB,gBAAgB,EAChB,gBAAgB,EAChB,KAAK,GACN,MAAM,SAAS,CAAC;AAEjB;;;GAGG;AAEI,IAAM,UAAU,GAAhB,MAAM,UAAU;CAqCtB,CAAA;AAnCC;IADC,sBAAsB,CAAC,MAAM,CAAC;;sCACpB;AAIX;IAFC,KAAK,EAAE;IACP,MAAM,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;;+CACzD;AAGpB;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,yBAAyB,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;8BACzC,IAAI;wDAAC;AAI3B;IAFC,KAAK,EAAE;IACP,MAAM,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;;gDACxE;AAGtB;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,0BAA0B,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;8BACxD,IAAI;yDAAC;AAG7B;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;yCAC1C;AAIf;IAFC,KAAK,EAAE;IACP,MAAM,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;;4CAC3C;AAIjB;IAFC,KAAK,EAAE;IACP,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;;4CACxB;AAGjB;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;kDACzD;AAGxB;IADC,gBAAgB,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;8BACjD,IAAI;6CAAC;AAGhB;IADC,gBAAgB,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;8BACjD,IAAI;6CAAC;AApCL,UAAU;IADtB,MAAM,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC;GACpB,UAAU,CAqCtB;;AAED,eAAe,UAAU,CAAC"}
|
|
@@ -117,6 +117,10 @@ __decorate([
|
|
|
117
117
|
Column({ type: 'varchar', length: 128, nullable: true }),
|
|
118
118
|
__metadata("design:type", String)
|
|
119
119
|
], Server.prototype, "settingsSyncHash", void 0);
|
|
120
|
+
__decorate([
|
|
121
|
+
Column({ type: 'boolean', default: false }),
|
|
122
|
+
__metadata("design:type", Boolean)
|
|
123
|
+
], Server.prototype, "settingsSyncManaged", void 0);
|
|
120
124
|
__decorate([
|
|
121
125
|
CreateDateColumn({ name: 'created_at', type: 'timestamp' }),
|
|
122
126
|
__metadata("design:type", Date)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Server.js","sourceRoot":"","sources":["../../../src/db/entities/Server.ts"],"names":[],"mappings":";;;;;;;;;AACA,OAAO,EACL,MAAM,EACN,MAAM,EACN,sBAAsB,EACtB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,SAAS,CAAC;AAEjB;;GAEG;AAEI,IAAM,MAAM,GAAZ,MAAM,MAAM;
|
|
1
|
+
{"version":3,"file":"Server.js","sourceRoot":"","sources":["../../../src/db/entities/Server.ts"],"names":[],"mappings":";;;;;;;;;AACA,OAAO,EACL,MAAM,EACN,MAAM,EACN,sBAAsB,EACtB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,SAAS,CAAC;AAEjB;;GAEG;AAEI,IAAM,MAAM,GAAZ,MAAM,MAAM;CA8FlB,CAAA;AA5FC;IADC,sBAAsB,CAAC,MAAM,CAAC;;kCACpB;AAGX;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;;oCAC1C;AAGb;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;oCAC1C;AAGd;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;2CACpB;AAGrB;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;mCAC5B;AAGb;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;uCACxC;AAGjB;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;oCAChC;AAGhB;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;mCACnB;AAG7B;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;uCACf;AAGjC;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;;uCAC1B;AAGjB;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;qCAC1C;AAQf;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;;0CACjB;AAG3C;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;+CAC3B;AAG3B;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;;+CAClB;AAG1B;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;iDACb;AAG3B;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;qCAC4C;AAG5F;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;uCACqB;AAGrE;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;yCACuB;AAGvE;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;uCAClB;AAG9B;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;qCACpB;AAG5B;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;qCACpB;AAG5B;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;uCAClB;AAG9B;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;kDAClB;AAG9B;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;gDACjB;AAG3B;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;kDACV;AAItC;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;gDAC/B;AAI1B;IADC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;;mDACd;AAG9B;IADC,gBAAgB,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;8BACjD,IAAI;yCAAC;AAGhB;IADC,gBAAgB,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;8BACjD,IAAI;yCAAC;AA7FL,MAAM;IADlB,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;GACf,MAAM,CA8FlB;;AAED,eAAe,MAAM,CAAC"}
|
package/dist/middlewares/auth.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { resolveJwtUser } from '../utils/userSession.js';
|
|
2
2
|
import defaultConfig from '../config/index.js';
|
|
3
|
-
import { JWT_SECRET } from '../config/jwt.js';
|
|
4
|
-
import { getToken } from '../models/OAuth.js';
|
|
5
|
-
import { isOAuthServerEnabled } from '../services/oauthServerService.js';
|
|
6
3
|
import { getBearerKeyDao, getSystemConfigDao } from '../dao/index.js';
|
|
7
4
|
import { getBetterAuthRuntimeConfig } from '../services/betterAuthConfig.js';
|
|
8
5
|
import { authenticateForwardAuthToken } from '../services/forwardAuthService.js';
|
|
@@ -122,24 +119,6 @@ export const auth = async (req, res, next) => {
|
|
|
122
119
|
next();
|
|
123
120
|
return;
|
|
124
121
|
}
|
|
125
|
-
// Check for OAuth access token in the configured bearer auth header
|
|
126
|
-
const accessToken = getBearerTokenFromHeaders(req.headers, systemConfig);
|
|
127
|
-
if (accessToken && isOAuthServerEnabled()) {
|
|
128
|
-
const oauthToken = await getToken(accessToken);
|
|
129
|
-
if (oauthToken && oauthToken.accessToken === accessToken) {
|
|
130
|
-
// Valid OAuth token - look up user to get admin status
|
|
131
|
-
const { findUserByUsername } = await import('../models/User.js');
|
|
132
|
-
const user = await findUserByUsername(oauthToken.username);
|
|
133
|
-
// Set user context with proper admin status
|
|
134
|
-
req.user = {
|
|
135
|
-
username: oauthToken.username,
|
|
136
|
-
isAdmin: user?.isAdmin || false,
|
|
137
|
-
};
|
|
138
|
-
req.oauthToken = oauthToken;
|
|
139
|
-
next();
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
122
|
const betterAuthConfig = await getBetterAuthRuntimeConfig(systemConfig);
|
|
144
123
|
if (betterAuthConfig.enabled) {
|
|
145
124
|
const betterAuthUser = await resolveBetterAuthUserSafe(req);
|
|
@@ -168,9 +147,12 @@ export const auth = async (req, res, next) => {
|
|
|
168
147
|
}
|
|
169
148
|
// Verify JWT token
|
|
170
149
|
try {
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
150
|
+
const user = await resolveJwtUser(token);
|
|
151
|
+
if (!user) {
|
|
152
|
+
res.status(401).json({ success: false, message: 'Token is not valid' });
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
req.user = { username: user.username, isAdmin: user.isAdmin || false };
|
|
174
156
|
next();
|
|
175
157
|
}
|
|
176
158
|
catch (error) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/middlewares/auth.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/middlewares/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opt-in pre-call guard for `tools/call` requests.
|
|
3
|
+
*
|
|
4
|
+
* When enabled, tool call arguments are inspected for a small set of
|
|
5
|
+
* high-confidence dangerous payloads — shell command injection/RCE and
|
|
6
|
+
* cloud instance-metadata (SSRF / credential-hijack) targets — and a
|
|
7
|
+
* matching call is blocked before it reaches the upstream MCP server
|
|
8
|
+
* (fail-closed).
|
|
9
|
+
*
|
|
10
|
+
* Rules are split into two tiers:
|
|
11
|
+
* - **Always-on** rules detect payloads that are dangerous in any field,
|
|
12
|
+
* not only an executable one: cloud instance-metadata endpoints and
|
|
13
|
+
* credential paths (SSRF / credential hijack). These are checked on
|
|
14
|
+
* every string value.
|
|
15
|
+
* - **Execution** rules detect shell/RCE content (chaining, command
|
|
16
|
+
* substitution, download-and-execute, sensitive-file reads, destructive
|
|
17
|
+
* deletion, reverse shells, PowerShell cradles). Because shell-like text
|
|
18
|
+
* and punctuation routinely appear inside ordinary prose, documentation
|
|
19
|
+
* and messaging arguments, these rules are applied only to values held
|
|
20
|
+
* in fields explicitly named to carry executable content
|
|
21
|
+
* (`command`, `cmd`, `script`, `run`, … — see {@link EXECUTION_KEYS}).
|
|
22
|
+
*
|
|
23
|
+
* Every pattern is a high-confidence indicator of an attack and every
|
|
24
|
+
* inspection is linear in the size of its input (no catastrophic
|
|
25
|
+
* backtracking). No network access and no third-party dependencies are
|
|
26
|
+
* required.
|
|
27
|
+
*
|
|
28
|
+
* Enable with `TOOL_CALL_GUARD_ENABLED=true`. The guard is off by default
|
|
29
|
+
* and performs no work unless explicitly enabled.
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* Thrown when the guard blocks a tool call. The message is safe to return to
|
|
33
|
+
* the caller; it deliberately does not echo the matched payload.
|
|
34
|
+
*/
|
|
35
|
+
export class ToolCallBlockedError extends Error {
|
|
36
|
+
constructor(message,
|
|
37
|
+
/** Stable machine-readable identifier of the rule that fired. */
|
|
38
|
+
ruleId) {
|
|
39
|
+
super(message);
|
|
40
|
+
this.ruleId = ruleId;
|
|
41
|
+
this.name = 'ToolCallBlockedError';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** Returns true when the pre-call guard is explicitly enabled via environment. */
|
|
45
|
+
export const isToolCallGuardEnabled = (env = process.env) => env.TOOL_CALL_GUARD_ENABLED === 'true';
|
|
46
|
+
/**
|
|
47
|
+
* Normalised names of object keys whose values hold executable content.
|
|
48
|
+
* A key matches when its lower-cased name, after removing `_`, `-` and
|
|
49
|
+
* spaces, is one of these tokens, or ends with one of the suffixes in
|
|
50
|
+
* {@link EXECUTION_KEY_SUFFIXES}.
|
|
51
|
+
*/
|
|
52
|
+
const EXECUTION_KEYS = new Set([
|
|
53
|
+
'command',
|
|
54
|
+
'cmd',
|
|
55
|
+
'cmds',
|
|
56
|
+
'script',
|
|
57
|
+
'exec',
|
|
58
|
+
'execute',
|
|
59
|
+
'shell',
|
|
60
|
+
'run',
|
|
61
|
+
'code',
|
|
62
|
+
'statement',
|
|
63
|
+
'expression',
|
|
64
|
+
]);
|
|
65
|
+
/** Normalised key suffixes that indicate executable content. */
|
|
66
|
+
const EXECUTION_KEY_SUFFIXES = ['command', 'commands', 'script'];
|
|
67
|
+
/** Returns true when an object key is explicitly named to carry a command. */
|
|
68
|
+
const isExecutionKey = (key) => {
|
|
69
|
+
const normalised = key.toLowerCase().replace(/[_\-\s]/g, '');
|
|
70
|
+
if (EXECUTION_KEYS.has(normalised))
|
|
71
|
+
return true;
|
|
72
|
+
return EXECUTION_KEY_SUFFIXES.some((suffix) => normalised.endsWith(suffix));
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Command substitution / execution expansions. These forms execute the
|
|
76
|
+
* embedded command whenever a shell interprets the value, so they are a
|
|
77
|
+
* direct RCE indicator.
|
|
78
|
+
*
|
|
79
|
+
* `$( ... )` is matched directly. Backtick pairs are inspected with a
|
|
80
|
+
* linear scan rather than a single regex, because a single opening
|
|
81
|
+
* backtick followed by a long run of non-backtick characters made the old
|
|
82
|
+
* backtick expression retry across the whole string.
|
|
83
|
+
*/
|
|
84
|
+
const COMMAND_EXPANSION = {
|
|
85
|
+
id: 'command-expansion',
|
|
86
|
+
scope: 'execution',
|
|
87
|
+
test: (v) => /\$\([^)]/.test(v) || backtickSpanContains(v, SHELL_SYNTAX_CHARS),
|
|
88
|
+
};
|
|
89
|
+
/** Matches characters that indicate the content of a backtick span is shell syntax. */
|
|
90
|
+
const SHELL_SYNTAX_CHARS = /[;&|$/]/;
|
|
91
|
+
/**
|
|
92
|
+
* Linear scan for backtick-delimited spans that contain shell syntax or a
|
|
93
|
+
* known command. Each character is visited once; there is no backtracking.
|
|
94
|
+
*/
|
|
95
|
+
const backtickSpanContains = (value, syntax) => {
|
|
96
|
+
let open = -1;
|
|
97
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
98
|
+
if (value[i] !== '`')
|
|
99
|
+
continue;
|
|
100
|
+
if (open === -1) {
|
|
101
|
+
open = i;
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
const span = value.slice(open + 1, i);
|
|
105
|
+
if (syntax.test(span) || COMMAND_IN_BACKTICK.test(span))
|
|
106
|
+
return true;
|
|
107
|
+
open = -1;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return false;
|
|
111
|
+
};
|
|
112
|
+
/** A backtick span that runs a known command with an argument. */
|
|
113
|
+
const COMMAND_IN_BACKTICK = /^\s*(?:sudo\s+)?(?:cat|ls|id|whoami|uname|wget|curl|bash|sh|nc|python|perl|ruby|chmod|chown|rm|cp|mv)\s+\S/;
|
|
114
|
+
/**
|
|
115
|
+
* Classic shell metacharacter chaining (`;`, `&&`, `||`) immediately
|
|
116
|
+
* followed by a command name. Requires a following command token so prose
|
|
117
|
+
* and ordinary punctuation do not match.
|
|
118
|
+
*/
|
|
119
|
+
const SHELL_CHAINING = {
|
|
120
|
+
id: 'shell-chaining',
|
|
121
|
+
scope: 'execution',
|
|
122
|
+
test: (v) => /(?:;|&&|\|\|)\s*(?:sudo\s+)?(?:cat|ls|id|whoami|uname|wget|curl|bash|sh|nc|python|perl|ruby|chmod|chown|rm|cp|mv)\b/.test(v),
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* Download-and-execute pattern, e.g. `curl http://... | sh` or
|
|
126
|
+
* `wget -O- ... | bash`, including the `sh -c "$(curl ...)"` form.
|
|
127
|
+
*
|
|
128
|
+
* The piped form is detected with a linear scan: the line is split at each
|
|
129
|
+
* pipe and a single pass records whether a downloader appeared before the
|
|
130
|
+
* pipe and whether a shell follows it. The previous expression used
|
|
131
|
+
* `curl/wget` followed by `[^|\n]*` running to the terminator, so a string
|
|
132
|
+
* of repeated command words (`'curl '.repeat(40000)`) made the engine retry
|
|
133
|
+
* from each word and produced quadratic time. The shell is not required to
|
|
134
|
+
* sit at the end of the line: real invocations pass arguments (`sh -s`) or
|
|
135
|
+
* append comments (`sh # install`), and this rule is execution-scoped so
|
|
136
|
+
* downloaders merely quoted in prose are not matched.
|
|
137
|
+
*/
|
|
138
|
+
const DOWNLOADER_PIPE = {
|
|
139
|
+
id: 'downloader-pipe',
|
|
140
|
+
scope: 'execution',
|
|
141
|
+
test: (v) => {
|
|
142
|
+
if (SH_C_CURL.test(v))
|
|
143
|
+
return true;
|
|
144
|
+
for (const line of v.split('\n')) {
|
|
145
|
+
if (hasDownloaderPipedToShell(line))
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
return false;
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
/** The `sh -c "$(curl ...)"` download-and-execute form. */
|
|
152
|
+
const SH_C_CURL = /(?:ba|z|fi|k|t?c)?sh\s+-c\s+["']?\$\((?:curl|wget)\b/;
|
|
153
|
+
/** A shell command name (bash/sh/zsh/fish/ksh/tcsh). */
|
|
154
|
+
const SHELL_AFTER_PIPE = /^(?:sudo\s+)?(?:ba|z|fi|k|t?c)?sh\b/;
|
|
155
|
+
/** A downloader command name. */
|
|
156
|
+
const DOWNLOADER_IN_SEGMENT = /\b(?:curl|wget)\b/;
|
|
157
|
+
/**
|
|
158
|
+
* Linear detection of `downloader ... | shell` within one line. The line is
|
|
159
|
+
* split at pipes and visited once; every segment is inspected with a
|
|
160
|
+
* bounded regex that does not run to an end-of-line terminator.
|
|
161
|
+
*/
|
|
162
|
+
const hasDownloaderPipedToShell = (line) => {
|
|
163
|
+
let downloaderSeen = false;
|
|
164
|
+
for (const segment of line.split('|')) {
|
|
165
|
+
if (SHELL_AFTER_PIPE.test(segment.trimStart()) && downloaderSeen)
|
|
166
|
+
return true;
|
|
167
|
+
if (DOWNLOADER_IN_SEGMENT.test(segment))
|
|
168
|
+
downloaderSeen = true;
|
|
169
|
+
}
|
|
170
|
+
return false;
|
|
171
|
+
};
|
|
172
|
+
/**
|
|
173
|
+
* Reading sensitive account files through a shell command, including their
|
|
174
|
+
* use inside command substitution. `passwd` alone is harmless (it contains
|
|
175
|
+
* only public account names), so a reader command must precede it; `shadow`
|
|
176
|
+
* is sensitive regardless.
|
|
177
|
+
*
|
|
178
|
+
* Detected with a linear token scan: the line is split on whitespace and
|
|
179
|
+
* visited once, recording whether a reader command appeared before the
|
|
180
|
+
* `/etc/passwd` token. The previous expression used a reader word followed
|
|
181
|
+
* by `[^\n]*` running to the path, so repeated command words
|
|
182
|
+
* (`'cat '.repeat(50000)`) caused quadratic retries. Quoted path tokens
|
|
183
|
+
* (`cat "/etc/passwd"`) and absolute reader paths (`/bin/cat`) are handled
|
|
184
|
+
* by normalising each token before comparison.
|
|
185
|
+
*/
|
|
186
|
+
const SENSITIVE_FILE_READ = {
|
|
187
|
+
id: 'sensitive-file-read',
|
|
188
|
+
scope: 'execution',
|
|
189
|
+
test: (v) => {
|
|
190
|
+
for (const line of v.split('\n')) {
|
|
191
|
+
if (SHADOW_PATH.test(line))
|
|
192
|
+
return true;
|
|
193
|
+
if (hasReaderThenPasswd(line))
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
return false;
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
/** A reference to `/etc/shadow`; sensitive even without an explicit reader. */
|
|
200
|
+
const SHADOW_PATH = /\/etc\/shadow(?![\w.-])/;
|
|
201
|
+
/**
|
|
202
|
+
* Removes a single matching pair of surrounding quotes from a whitespace
|
|
203
|
+
* token, so a path written as `"/etc/passwd"` is recognised the same as the
|
|
204
|
+
* unquoted form.
|
|
205
|
+
*/
|
|
206
|
+
const stripQuotes = (token) => {
|
|
207
|
+
if (token.length < 2)
|
|
208
|
+
return token;
|
|
209
|
+
const first = token[0];
|
|
210
|
+
const last = token[token.length - 1];
|
|
211
|
+
if ((first === '"' || first === "'") && last === first) {
|
|
212
|
+
return token.slice(1, -1);
|
|
213
|
+
}
|
|
214
|
+
return token;
|
|
215
|
+
};
|
|
216
|
+
/** True when a (possibly quote-wrapped) token ends in `/etc/passwd`. */
|
|
217
|
+
const isPasswdToken = (token) => /\/etc\/passwd(?![\w.-])$/.test(stripQuotes(token));
|
|
218
|
+
/** Basenames of commands that read a file. */
|
|
219
|
+
const FILE_READER_WORDS = new Set([
|
|
220
|
+
'cat',
|
|
221
|
+
'head',
|
|
222
|
+
'tail',
|
|
223
|
+
'less',
|
|
224
|
+
'more',
|
|
225
|
+
'tac',
|
|
226
|
+
'nl',
|
|
227
|
+
'od',
|
|
228
|
+
'xxd',
|
|
229
|
+
'cp',
|
|
230
|
+
'mv',
|
|
231
|
+
'grep',
|
|
232
|
+
'open',
|
|
233
|
+
]);
|
|
234
|
+
/**
|
|
235
|
+
* True when a whitespace token is a file-reader command, whether given as a
|
|
236
|
+
* bare name (`cat`) or an absolute executable path (`/bin/cat`,
|
|
237
|
+
* `/usr/bin/cat`).
|
|
238
|
+
*/
|
|
239
|
+
const isFileReaderToken = (token) => {
|
|
240
|
+
const slash = token.lastIndexOf('/');
|
|
241
|
+
const name = slash === -1 ? token : token.slice(slash + 1);
|
|
242
|
+
return FILE_READER_WORDS.has(name);
|
|
243
|
+
};
|
|
244
|
+
/**
|
|
245
|
+
* Linear detection of a reader command followed later by `/etc/passwd`
|
|
246
|
+
* within one line. Tokens are visited once; a reader seen earlier is
|
|
247
|
+
* remembered when the path token is reached. Quoted paths and absolute
|
|
248
|
+
* reader-command paths are handled by {@link isPasswdToken} and
|
|
249
|
+
* {@link isFileReaderToken}.
|
|
250
|
+
*/
|
|
251
|
+
const hasReaderThenPasswd = (line) => {
|
|
252
|
+
let readerSeen = false;
|
|
253
|
+
for (const token of line.split(/\s+/)) {
|
|
254
|
+
if (isPasswdToken(token) && readerSeen)
|
|
255
|
+
return true;
|
|
256
|
+
if (isFileReaderToken(token))
|
|
257
|
+
readerSeen = true;
|
|
258
|
+
}
|
|
259
|
+
return false;
|
|
260
|
+
};
|
|
261
|
+
/**
|
|
262
|
+
* Forced recursive deletion of a system/home root.
|
|
263
|
+
*
|
|
264
|
+
* Implemented as a linear per-line scan instead of a single regex. The
|
|
265
|
+
* original combined three greedy `[^\n]*` segments in one expression, and
|
|
266
|
+
* even the intermediate `/-[^\s]*[rf]/` flag check retried from each dash,
|
|
267
|
+
* so long runs of dashes (e.g. `rm ` + `'-'.repeat(100000)) produced
|
|
268
|
+
* catastrophic backtracking. The checks below run independently on each
|
|
269
|
+
* line, inspecting only whitespace-delimited flag tokens, so the total work
|
|
270
|
+
* is O(n) with no backtracking across segments.
|
|
271
|
+
*/
|
|
272
|
+
const FORCE_DELETE_ROOT = {
|
|
273
|
+
id: 'force-delete-root',
|
|
274
|
+
scope: 'execution',
|
|
275
|
+
test: (v) => {
|
|
276
|
+
for (const line of v.split('\n')) {
|
|
277
|
+
if (!/\brm\b/.test(line))
|
|
278
|
+
continue;
|
|
279
|
+
if (!hasRecursiveForceFlag(line))
|
|
280
|
+
continue;
|
|
281
|
+
if (/(?:--no-preserve-root\b)?\s\/(?:\s|$|\*)/.test(line))
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
return false;
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
/**
|
|
288
|
+
* Returns true when the line carries a recursive flag (a single-dash flag
|
|
289
|
+
* token containing `r`), or `--no-preserve-root`. Recursive deletion of the
|
|
290
|
+
* root target is dangerous with `-r` alone, so `-f` is not required. Only
|
|
291
|
+
* whitespace-delimited tokens are inspected, so a run of dashes that never
|
|
292
|
+
* forms such a token is visited once and rejected.
|
|
293
|
+
*/
|
|
294
|
+
const hasRecursiveForceFlag = (line) => {
|
|
295
|
+
if (line.includes('--no-preserve-root'))
|
|
296
|
+
return true;
|
|
297
|
+
for (const token of line.split(/\s+/)) {
|
|
298
|
+
if (token.length < 2 || token[0] !== '-' || token[1] === '-')
|
|
299
|
+
continue;
|
|
300
|
+
if (token.slice(1).includes('r'))
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
return false;
|
|
304
|
+
};
|
|
305
|
+
/**
|
|
306
|
+
* Reverse-shell starters.
|
|
307
|
+
*
|
|
308
|
+
* The `/dev/tcp/host/port` and `nc -e /bin/sh` forms are detected with
|
|
309
|
+
* linear scans rather than run-on regexes. The previous expressions were
|
|
310
|
+
* `/dev/tcp/\S+/\d+` and `\bnc\b[^\n]*-e\s+/…/sh`; on a string of repeated
|
|
311
|
+
* prefixes (`'/dev/tcp/'.repeat(20000)` or `'nc '.repeat(60000)`) each
|
|
312
|
+
* match advanced while scanning to the end of the line for a suffix that
|
|
313
|
+
* was absent, producing quadratic retries.
|
|
314
|
+
*/
|
|
315
|
+
const REVERSE_SHELL = {
|
|
316
|
+
id: 'reverse-shell',
|
|
317
|
+
scope: 'execution',
|
|
318
|
+
test: (v) => {
|
|
319
|
+
if (hasDevTcpRedirect(v))
|
|
320
|
+
return true;
|
|
321
|
+
if (/bash\s+-i\b/.test(v))
|
|
322
|
+
return true;
|
|
323
|
+
for (const line of v.split('\n')) {
|
|
324
|
+
if (hasNetcatExecShell(line))
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
return false;
|
|
328
|
+
},
|
|
329
|
+
};
|
|
330
|
+
const DEV_TCP_PREFIX = '/dev/tcp/';
|
|
331
|
+
/** Characters that terminate a shell word even without whitespace. */
|
|
332
|
+
const isShellDelimiter = (ch) => /[;|&'"`><)]/.test(ch);
|
|
333
|
+
/**
|
|
334
|
+
* Linear detection of a bash `/dev/tcp/host/port` redirect. Each prefix
|
|
335
|
+
* occurrence advances a single cursor (there is no rescanning of earlier
|
|
336
|
+
* text). The host/port token ends at whitespace or a shell delimiter such as
|
|
337
|
+
* `;` or a closing quote, so a port immediately followed by `;` is still
|
|
338
|
+
* recognised; both the host and the port are validated.
|
|
339
|
+
*/
|
|
340
|
+
const hasDevTcpRedirect = (value) => {
|
|
341
|
+
let from = 0;
|
|
342
|
+
for (;;) {
|
|
343
|
+
const start = value.indexOf(DEV_TCP_PREFIX, from);
|
|
344
|
+
if (start === -1)
|
|
345
|
+
return false;
|
|
346
|
+
const restStart = start + DEV_TCP_PREFIX.length;
|
|
347
|
+
let end = restStart;
|
|
348
|
+
while (end < value.length) {
|
|
349
|
+
const ch = value[end];
|
|
350
|
+
if (/\s/.test(ch) || isShellDelimiter(ch))
|
|
351
|
+
break;
|
|
352
|
+
end += 1;
|
|
353
|
+
}
|
|
354
|
+
const segment = value.slice(restStart, end);
|
|
355
|
+
const slash = segment.indexOf('/');
|
|
356
|
+
if (slash > 0 &&
|
|
357
|
+
/^[A-Za-z0-9_.[\]:-]+$/.test(segment.slice(0, slash)) &&
|
|
358
|
+
/^\d+$/.test(segment.slice(slash + 1))) {
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
from = end;
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
/**
|
|
365
|
+
* Linear detection of a netcat/ncat reverse shell within one line. Both the
|
|
366
|
+
* starter and the `-e /…/sh` execute form are matched with bounded patterns;
|
|
367
|
+
* neither runs to the end of the line, so repeated `nc` words do not retry.
|
|
368
|
+
*/
|
|
369
|
+
const hasNetcatExecShell = (line) => {
|
|
370
|
+
if (!/\bnc(?:at)?\b/.test(line))
|
|
371
|
+
return false;
|
|
372
|
+
return /(?:^|\s)-e\s+\/(?:bin|usr\/bin)\/sh(?![\w.-])/.test(line);
|
|
373
|
+
};
|
|
374
|
+
/** PowerShell download cradles (download + execute APIs used together). */
|
|
375
|
+
const POWERSHELL_CRADLE = {
|
|
376
|
+
id: 'powershell-cradle',
|
|
377
|
+
scope: 'execution',
|
|
378
|
+
test: (v) => {
|
|
379
|
+
const lower = v.toLowerCase();
|
|
380
|
+
const downloads = /(?:downloadstring|downloadfile|invoke-webrequest\s*\(|invoke-restmethod\s*\(|\biwr\b|\birm\b)/.test(lower);
|
|
381
|
+
const executes = /(?:iex|invoke-expression)\s*[.(]/.test(lower);
|
|
382
|
+
return downloads && executes;
|
|
383
|
+
},
|
|
384
|
+
};
|
|
385
|
+
/**
|
|
386
|
+
* Cloud instance metadata service targets (AWS / GCP / Azure / Alibaba /
|
|
387
|
+
* Oracle / DigitalOcean / OpenStack), in dotted, decimal or hex form for the
|
|
388
|
+
* AWS IMDS address. The IMDS hostnames are not routable on any ordinary
|
|
389
|
+
* network, so a match is always an SSRF attempt regardless of the field it
|
|
390
|
+
* appears in.
|
|
391
|
+
*/
|
|
392
|
+
const CLOUD_METADATA_URL = {
|
|
393
|
+
id: 'cloud-metadata-url',
|
|
394
|
+
scope: 'always',
|
|
395
|
+
test: (v) => /https?:\/\/(?:169\.254\.169\.254|2852039166|0xa9fea9fe)(?::\d+)?\//.test(v) ||
|
|
396
|
+
/https?:\/\/(?:metadata\.google\.internal|169\.254\.169\.254\.metadata\.azure\.com|100\.100\.100\.200)\//.test(v),
|
|
397
|
+
};
|
|
398
|
+
/** Well-known cloud credential / IMDS token paths or hostnames. */
|
|
399
|
+
const CLOUD_CREDENTIAL_PATH = {
|
|
400
|
+
id: 'cloud-credential-path',
|
|
401
|
+
scope: 'always',
|
|
402
|
+
test: (v) => /\/latest\/(?:meta-data|api\/token)/.test(v) ||
|
|
403
|
+
/\/computeMetadata\/v1\//.test(v) ||
|
|
404
|
+
/\/metadata\/instance\//.test(v) ||
|
|
405
|
+
/(?:metadata\.aliyuninc\.com|169\.254\.170\.2)\//.test(v),
|
|
406
|
+
};
|
|
407
|
+
const RULES = [
|
|
408
|
+
COMMAND_EXPANSION,
|
|
409
|
+
SHELL_CHAINING,
|
|
410
|
+
DOWNLOADER_PIPE,
|
|
411
|
+
SENSITIVE_FILE_READ,
|
|
412
|
+
FORCE_DELETE_ROOT,
|
|
413
|
+
REVERSE_SHELL,
|
|
414
|
+
POWERSHELL_CRADLE,
|
|
415
|
+
CLOUD_METADATA_URL,
|
|
416
|
+
CLOUD_CREDENTIAL_PATH,
|
|
417
|
+
];
|
|
418
|
+
/**
|
|
419
|
+
* Cap on total argument text inspected per call. When a single call contains
|
|
420
|
+
* more text than this, it cannot be fully inspected and the guard blocks the
|
|
421
|
+
* call (fail-closed) rather than treating the unexamined portion as safe.
|
|
422
|
+
* This keeps a single call from doing unbounded work while preventing an
|
|
423
|
+
* attacker from padding a payload past the budget to smuggle a command.
|
|
424
|
+
*/
|
|
425
|
+
const MAX_SCAN_BYTES = 1000000;
|
|
426
|
+
/**
|
|
427
|
+
* Rule id reported when a call contains more text than the scan budget and
|
|
428
|
+
* therefore cannot be fully inspected.
|
|
429
|
+
*/
|
|
430
|
+
const SCAN_LIMIT_RULE_ID = 'scan-limit-exceeded';
|
|
431
|
+
/**
|
|
432
|
+
* Inspect tool call arguments for high-confidence dangerous payloads.
|
|
433
|
+
*
|
|
434
|
+
* Every string value reachable from the arguments object (including arrays
|
|
435
|
+
* and nested objects) is checked. Strings inside a field whose key is named
|
|
436
|
+
* for executable content (and everything nested beneath it) are also
|
|
437
|
+
* checked against the execution-tier rules; other strings are checked only
|
|
438
|
+
* against the always-on (SSRF/credential) rules. Non-string values are
|
|
439
|
+
* ignored.
|
|
440
|
+
*
|
|
441
|
+
* @returns `{ blocked: false }` when nothing matched, otherwise the id of
|
|
442
|
+
* the first matching rule.
|
|
443
|
+
*/
|
|
444
|
+
export const inspectToolArguments = (args) => {
|
|
445
|
+
let budget = MAX_SCAN_BYTES;
|
|
446
|
+
const scan = (node, execution) => {
|
|
447
|
+
if (typeof node === 'string') {
|
|
448
|
+
// Fail closed: if this string cannot be fully inspected within the
|
|
449
|
+
// remaining budget, block the call instead of silently passing the
|
|
450
|
+
// unexamined content through.
|
|
451
|
+
if (node.length > budget)
|
|
452
|
+
return SCAN_LIMIT_RULE_ID;
|
|
453
|
+
budget -= node.length;
|
|
454
|
+
for (const rule of RULES) {
|
|
455
|
+
if (rule.scope === 'execution' && !execution)
|
|
456
|
+
continue;
|
|
457
|
+
if (rule.test(node))
|
|
458
|
+
return rule.id;
|
|
459
|
+
}
|
|
460
|
+
return undefined;
|
|
461
|
+
}
|
|
462
|
+
if (Array.isArray(node)) {
|
|
463
|
+
for (const item of node) {
|
|
464
|
+
const hit = scan(item, execution);
|
|
465
|
+
if (hit)
|
|
466
|
+
return hit;
|
|
467
|
+
}
|
|
468
|
+
return undefined;
|
|
469
|
+
}
|
|
470
|
+
if (node !== null && typeof node === 'object') {
|
|
471
|
+
for (const [key, value] of Object.entries(node)) {
|
|
472
|
+
// A command-named field puts its whole subtree into execution scope;
|
|
473
|
+
// other fields keep the scope inherited from their parent.
|
|
474
|
+
const hit = scan(value, execution || isExecutionKey(key));
|
|
475
|
+
if (hit)
|
|
476
|
+
return hit;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return undefined;
|
|
480
|
+
};
|
|
481
|
+
// A top-level bare string is not a named execution field, so only the
|
|
482
|
+
// always-on rules apply to it. (In MCP, tool call arguments are an object;
|
|
483
|
+
// bare-string inputs exist mainly for testing.)
|
|
484
|
+
const ruleId = scan(args, false);
|
|
485
|
+
return ruleId ? { blocked: true, ruleId } : { blocked: false };
|
|
486
|
+
};
|
|
487
|
+
/**
|
|
488
|
+
* Assert that tool call arguments are safe. Throws {@link ToolCallBlockedError}
|
|
489
|
+
* (fail-closed) when a high-confidence dangerous pattern is present.
|
|
490
|
+
*/
|
|
491
|
+
export const assertToolCallArgumentsSafe = (args, env = process.env) => {
|
|
492
|
+
if (!isToolCallGuardEnabled(env))
|
|
493
|
+
return;
|
|
494
|
+
const result = inspectToolArguments(args);
|
|
495
|
+
if (result.blocked && result.ruleId) {
|
|
496
|
+
throw new ToolCallBlockedError(`Blocked by tool call guard: tool call arguments matched rule '${result.ruleId}'`, result.ruleId);
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
//# sourceMappingURL=toolCallGuard.js.map
|