palaryn 0.4.4 → 0.4.7
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/src/admin/templates.d.ts.map +1 -1
- package/dist/src/admin/templates.js +2 -1
- package/dist/src/admin/templates.js.map +1 -1
- package/dist/src/saas/routes.d.ts.map +1 -1
- package/dist/src/saas/routes.js +360 -23
- package/dist/src/saas/routes.js.map +1 -1
- package/dist/tests/unit/admin.test.js +6 -4
- package/dist/tests/unit/admin.test.js.map +1 -1
- package/package.json +1 -1
- package/src/admin/templates.ts +3 -1
- package/src/saas/routes.ts +381 -21
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../../src/admin/templates.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,GAAG,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,cAAc,GAAG,iBAAiB,GAAG,mBAAmB,CAAC;IACvF,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3B;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO9C;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CA2DxF;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CA2D/D;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,GAAG,EAAE,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CA4D9E;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,GAAG,EAAE,gBAAgB,CAAC,EAAE,GAAG,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAgF1K;AAED,MAAM,WAAW,WAAW;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAClD;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,WAAW,CAAC,EAAE,WAAW,GAAG,MAAM,CAkExF;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAiBD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,UAAU,CAAC,EAAE,cAAc,GAAG,MAAM,CAuCpF;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,CAgDzE;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../../src/admin/templates.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,GAAG,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,cAAc,GAAG,iBAAiB,GAAG,mBAAmB,CAAC;IACvF,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3B;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO9C;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CA2DxF;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CA2D/D;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,GAAG,EAAE,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CA4D9E;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,GAAG,EAAE,gBAAgB,CAAC,EAAE,GAAG,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAgF1K;AAED,MAAM,WAAW,WAAW;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAClD;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,WAAW,CAAC,EAAE,WAAW,GAAG,MAAM,CAkExF;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAiBD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,UAAU,CAAC,EAAE,cAAc,GAAG,MAAM,CAuCpF;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,CAgDzE;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAoFnG"}
|
|
@@ -430,7 +430,8 @@ function apiKeysTemplate(keys, csrfToken, createdKey) {
|
|
|
430
430
|
<br><strong>Copy this key now — it won’t be shown again.</strong>
|
|
431
431
|
</div>`
|
|
432
432
|
: '';
|
|
433
|
-
const
|
|
433
|
+
const activeKeys = keys.filter(k => !k.revoked);
|
|
434
|
+
const rows = activeKeys.map(k => {
|
|
434
435
|
const maskedKey = k.key.length > 8
|
|
435
436
|
? k.key.substring(0, 4) + '...' + k.key.substring(k.key.length - 4)
|
|
436
437
|
: k.key;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"templates.js","sourceRoot":"","sources":["../../../src/admin/templates.ts"],"names":[],"mappings":";;AA2BA,gCAOC;AAED,wCA2DC;AAED,8CA2DC;AAED,8CA4DC;AAED,4CAgFC;AAQD,0CAkEC;AAwBD,8CAuCC;AAED,kDAgDC;AAED,
|
|
1
|
+
{"version":3,"file":"templates.js","sourceRoot":"","sources":["../../../src/admin/templates.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../src/saas/routes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAqB,MAAM,SAAS,CAAC;AAGpD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EACL,SAAS,EAAE,cAAc,EAAE,oBAAoB,EAC/C,eAAe,EAAE,YAAY,EAAE,WAAW,EAC1C,oBAAoB,EAAE,iBAAiB,EAExC,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../src/saas/routes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAqB,MAAM,SAAS,CAAC;AAGpD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EACL,SAAS,EAAE,cAAc,EAAE,oBAAoB,EAC/C,eAAe,EAAE,YAAY,EAAE,WAAW,EAC1C,oBAAoB,EAAE,iBAAiB,EAExC,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAc5C,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,aAAa,CAAC;IACtB,SAAS,EAAE,SAAS,CAAC;IACrB,cAAc,EAAE,cAAc,CAAC;IAC/B,oBAAoB,EAAE,oBAAoB,CAAC;IAC3C,eAAe,EAAE,eAAe,CAAC;IACjC,YAAY,EAAE,YAAY,CAAC;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAC5C,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;CACvC;AAmBD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAm8E5D"}
|
package/dist/src/saas/routes.js
CHANGED
|
@@ -42,6 +42,7 @@ const calculator_1 = require("../trust/calculator");
|
|
|
42
42
|
const engine_1 = require("../replay/engine");
|
|
43
43
|
const plan_enforcer_1 = require("../billing/plan-enforcer");
|
|
44
44
|
const model_pricing_1 = require("../budget/model-pricing");
|
|
45
|
+
const engine_2 = require("../policy/engine");
|
|
45
46
|
/** Strip HTML tags from user-provided display strings to prevent stored XSS. */
|
|
46
47
|
function stripHtmlTags(input) {
|
|
47
48
|
return input.replace(/<[^>]*>/g, '').trim();
|
|
@@ -919,6 +920,67 @@ function createSaaSRouter(deps) {
|
|
|
919
920
|
const validation = gateway.validatePolicy(body);
|
|
920
921
|
res.json(validation);
|
|
921
922
|
});
|
|
923
|
+
router.post('/workspaces/:id/policies/evaluate-test', (req, res) => {
|
|
924
|
+
if (!requireSession(req, res))
|
|
925
|
+
return;
|
|
926
|
+
const user = req.sessionUser;
|
|
927
|
+
const workspaceId = param(req, 'id');
|
|
928
|
+
const membership = workspaceMemberStore.getByWorkspaceAndUser(workspaceId, user.id);
|
|
929
|
+
if (!membership) {
|
|
930
|
+
res.status(403).json({ error: 'Not a member of this workspace' });
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
const { policy, tool_call } = req.body;
|
|
934
|
+
if (!policy || !tool_call) {
|
|
935
|
+
res.status(400).json({ error: 'policy and tool_call are required' });
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
// Validate the policy
|
|
939
|
+
const validation = engine_2.PolicyEngine.validate(policy);
|
|
940
|
+
if (!validation.valid) {
|
|
941
|
+
res.status(400).json({ error: 'Invalid policy', details: validation.errors });
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
// Extract domain from url if present
|
|
945
|
+
let domain;
|
|
946
|
+
if (tool_call.url) {
|
|
947
|
+
try {
|
|
948
|
+
domain = new URL(tool_call.url).hostname;
|
|
949
|
+
}
|
|
950
|
+
catch {
|
|
951
|
+
// not a valid URL, skip domain extraction
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
// Construct full ToolCall from simplified input
|
|
955
|
+
const fullToolCall = {
|
|
956
|
+
tool_call_id: `test-${Date.now()}`,
|
|
957
|
+
task_id: 'test',
|
|
958
|
+
workspace_id: workspaceId,
|
|
959
|
+
actor: {
|
|
960
|
+
type: tool_call.actor_type || 'agent',
|
|
961
|
+
id: tool_call.actor_id || 'test-actor',
|
|
962
|
+
},
|
|
963
|
+
source: {
|
|
964
|
+
platform: tool_call.platform || 'test',
|
|
965
|
+
},
|
|
966
|
+
tool: {
|
|
967
|
+
name: tool_call.tool_name || 'unknown',
|
|
968
|
+
capability: tool_call.capability || 'read',
|
|
969
|
+
},
|
|
970
|
+
args: {
|
|
971
|
+
method: tool_call.method,
|
|
972
|
+
url: tool_call.url,
|
|
973
|
+
...(domain ? { domain } : {}),
|
|
974
|
+
},
|
|
975
|
+
context: {
|
|
976
|
+
labels: tool_call.labels,
|
|
977
|
+
},
|
|
978
|
+
timestamp: new Date().toISOString(),
|
|
979
|
+
};
|
|
980
|
+
const engine = engine_2.PolicyEngine.fromPack(policy);
|
|
981
|
+
const result = engine.evaluate(fullToolCall);
|
|
982
|
+
res.json({ result });
|
|
983
|
+
});
|
|
922
984
|
// ---------------------------------------------------------------------------
|
|
923
985
|
// Policy Rule Generation (LLM-powered)
|
|
924
986
|
// ---------------------------------------------------------------------------
|
|
@@ -975,7 +1037,7 @@ function createSaaSRouter(deps) {
|
|
|
975
1037
|
}
|
|
976
1038
|
validTimestamps.push(now);
|
|
977
1039
|
llmRateLimitHits.set(user.id, validTimestamps);
|
|
978
|
-
const systemPrompt = `You are a policy rule generator for Palaryn, an AI agent gateway. Given a natural language description, generate
|
|
1040
|
+
const systemPrompt = `You are a policy rule generator for Palaryn, an AI agent gateway. Given a natural language description, generate one or more JSON PolicyRule objects.
|
|
979
1041
|
|
|
980
1042
|
The PolicyRule schema:
|
|
981
1043
|
{
|
|
@@ -993,6 +1055,7 @@ The PolicyRule schema:
|
|
|
993
1055
|
"domain_blocklist": string[],// Blocked domains
|
|
994
1056
|
"methods": string[], // HTTP methods: "GET", "POST", "PUT", "DELETE", "PATCH"
|
|
995
1057
|
"labels": string[], // Context labels
|
|
1058
|
+
"dlp_pattern_names": string[],// DLP pattern names: "secret-detected", "pii-detected"
|
|
996
1059
|
"platforms": string[], // Source platforms: "langgraph", "claude_code", "n8n", "custom"
|
|
997
1060
|
"workspace_ids": string[] // Specific workspace IDs
|
|
998
1061
|
},
|
|
@@ -1004,12 +1067,16 @@ The PolicyRule schema:
|
|
|
1004
1067
|
}
|
|
1005
1068
|
|
|
1006
1069
|
Rules:
|
|
1070
|
+
- If the description requires multiple distinct rules (different effects, different conditions), return a JSON array of rules.
|
|
1071
|
+
- If the description needs only one rule, return a single JSON object (not wrapped in an array).
|
|
1007
1072
|
- Only include condition fields that are relevant to the description. Omit empty arrays.
|
|
1008
1073
|
- Use "DENY" for blocking, "ALLOW" for permitting, "REQUIRE_APPROVAL" when human approval is needed.
|
|
1009
1074
|
- For domain wildcards use "*.example.com" format.
|
|
1010
1075
|
- IMPORTANT: Always pair capabilities with matching methods. read → ["GET","HEAD","OPTIONS"], write → ["POST","PUT","PATCH"], delete → ["DELETE"], admin → all methods.
|
|
1011
1076
|
- When the description says "read-only", set capabilities to ["read"] AND methods to ["GET","HEAD","OPTIONS"].
|
|
1012
|
-
-
|
|
1077
|
+
- For SSRF protection, use domain_blocklist with IP addresses and internal hostnames.
|
|
1078
|
+
- For DLP-based blocking, use dlp_pattern_names in conditions (e.g. "secret-detected").
|
|
1079
|
+
- Return ONLY the JSON (object or array), no markdown fences, no explanation.
|
|
1013
1080
|
|
|
1014
1081
|
Examples:
|
|
1015
1082
|
Input: "Allow GET requests to GitHub and Google APIs"
|
|
@@ -1022,7 +1089,10 @@ Input: "Allow read-only access"
|
|
|
1022
1089
|
Output: {"name":"allow-read-only","description":"Allow read-only access","effect":"ALLOW","priority":10,"conditions":{"capabilities":["read"],"methods":["GET","HEAD","OPTIONS"]}}
|
|
1023
1090
|
|
|
1024
1091
|
Input: "Require approval for write operations to Slack"
|
|
1025
|
-
Output: {"name":"approve-slack-writes","description":"Require approval for write operations to Slack","effect":"REQUIRE_APPROVAL","priority":15,"conditions":{"capabilities":["write"],"methods":["POST","PUT","PATCH"],"domains":["api.slack.com"]},"approval":{"scope":"team_lead","ttl_seconds":3600,"reason":"Write operations to Slack require approval"}}
|
|
1092
|
+
Output: {"name":"approve-slack-writes","description":"Require approval for write operations to Slack","effect":"REQUIRE_APPROVAL","priority":15,"conditions":{"capabilities":["write"],"methods":["POST","PUT","PATCH"],"domains":["api.slack.com"]},"approval":{"scope":"team_lead","ttl_seconds":3600,"reason":"Write operations to Slack require approval"}}
|
|
1093
|
+
|
|
1094
|
+
Input: "Allow GET to example.com, block DELETE everywhere"
|
|
1095
|
+
Output: [{"name":"allow-get-example","description":"Allow GET requests to example.com","effect":"ALLOW","priority":10,"conditions":{"methods":["GET"],"domains":["example.com"]}},{"name":"block-delete-all","description":"Block all DELETE operations","effect":"DENY","priority":5,"conditions":{"capabilities":["delete"],"methods":["DELETE"]}}]`;
|
|
1026
1096
|
try {
|
|
1027
1097
|
const controller = new AbortController();
|
|
1028
1098
|
const timeout = setTimeout(() => controller.abort(), 15000);
|
|
@@ -1044,7 +1114,7 @@ Output: {"name":"approve-slack-writes","description":"Require approval for write
|
|
|
1044
1114
|
],
|
|
1045
1115
|
})
|
|
1046
1116
|
: JSON.stringify({
|
|
1047
|
-
model: 'claude-sonnet-4-5-
|
|
1117
|
+
model: 'claude-sonnet-4-5-20250514',
|
|
1048
1118
|
max_tokens: 1024,
|
|
1049
1119
|
system: systemPrompt,
|
|
1050
1120
|
messages: [{ role: 'user', content: trimmed }],
|
|
@@ -1068,33 +1138,39 @@ Output: {"name":"approve-slack-writes","description":"Require approval for write
|
|
|
1068
1138
|
: (llmData.content?.[0]?.text || '');
|
|
1069
1139
|
// Strip markdown code fences if present
|
|
1070
1140
|
const cleaned = text.replace(/^```(?:json)?\s*/i, '').replace(/\s*```\s*$/i, '').trim();
|
|
1071
|
-
let
|
|
1141
|
+
let parsed;
|
|
1072
1142
|
try {
|
|
1073
|
-
|
|
1143
|
+
parsed = JSON.parse(cleaned);
|
|
1074
1144
|
}
|
|
1075
1145
|
catch {
|
|
1076
1146
|
console.error('[generate-rule] Failed to parse LLM output:', cleaned);
|
|
1077
1147
|
res.status(500).json({ error: 'Failed to parse generated rule. Please try rephrasing your description.' });
|
|
1078
1148
|
return;
|
|
1079
1149
|
}
|
|
1080
|
-
// Normalize
|
|
1081
|
-
|
|
1082
|
-
rule.effect = rule.effect.toUpperCase();
|
|
1083
|
-
}
|
|
1084
|
-
// Ensure required fields exist
|
|
1085
|
-
if (!rule.name)
|
|
1086
|
-
rule.name = 'generated-rule';
|
|
1087
|
-
if (!rule.conditions)
|
|
1088
|
-
rule.conditions = {};
|
|
1089
|
-
if (!rule.effect)
|
|
1090
|
-
rule.effect = 'DENY';
|
|
1091
|
-
// Strip unknown top-level fields
|
|
1150
|
+
// Normalize: accept both single object and array of rules
|
|
1151
|
+
const rawRules = Array.isArray(parsed) ? parsed : [parsed];
|
|
1092
1152
|
const validKeys = ['name', 'description', 'effect', 'priority', 'conditions', 'transformations', 'approval'];
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1153
|
+
const rules = rawRules.map((rule, i) => {
|
|
1154
|
+
// Normalize effect to uppercase
|
|
1155
|
+
if (rule.effect && typeof rule.effect === 'string') {
|
|
1156
|
+
rule.effect = rule.effect.toUpperCase();
|
|
1157
|
+
}
|
|
1158
|
+
// Ensure required fields exist
|
|
1159
|
+
if (!rule.name)
|
|
1160
|
+
rule.name = i === 0 ? 'generated-rule' : `generated-rule-${i + 1}`;
|
|
1161
|
+
if (!rule.conditions)
|
|
1162
|
+
rule.conditions = {};
|
|
1163
|
+
if (!rule.effect)
|
|
1164
|
+
rule.effect = 'DENY';
|
|
1165
|
+
// Strip unknown top-level fields
|
|
1166
|
+
for (const key of Object.keys(rule)) {
|
|
1167
|
+
if (!validKeys.includes(key))
|
|
1168
|
+
delete rule[key];
|
|
1169
|
+
}
|
|
1170
|
+
return rule;
|
|
1171
|
+
});
|
|
1172
|
+
// Return both formats for backward compatibility
|
|
1173
|
+
res.json({ rule: rules[0], rules });
|
|
1098
1174
|
}
|
|
1099
1175
|
catch (err) {
|
|
1100
1176
|
if (err.name === 'AbortError') {
|
|
@@ -1106,6 +1182,267 @@ Output: {"name":"approve-slack-writes","description":"Require approval for write
|
|
|
1106
1182
|
}
|
|
1107
1183
|
});
|
|
1108
1184
|
// ---------------------------------------------------------------------------
|
|
1185
|
+
// Policy Chat (LLM-powered, SSE streaming)
|
|
1186
|
+
// ---------------------------------------------------------------------------
|
|
1187
|
+
const LLM_CHAT_RATE_LIMIT_MAX = 30;
|
|
1188
|
+
const LLM_CHAT_RATE_LIMIT_WINDOW_MS = 60 * 60 * 1000; // 1 hour
|
|
1189
|
+
const llmChatRateLimitHits = new Map();
|
|
1190
|
+
// Periodic cleanup to prevent memory leaks
|
|
1191
|
+
setInterval(() => {
|
|
1192
|
+
const now = Date.now();
|
|
1193
|
+
for (const [userId, timestamps] of llmChatRateLimitHits) {
|
|
1194
|
+
const valid = timestamps.filter(t => now - t < LLM_CHAT_RATE_LIMIT_WINDOW_MS);
|
|
1195
|
+
if (valid.length === 0) {
|
|
1196
|
+
llmChatRateLimitHits.delete(userId);
|
|
1197
|
+
}
|
|
1198
|
+
else {
|
|
1199
|
+
llmChatRateLimitHits.set(userId, valid);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
}, LLM_CHAT_RATE_LIMIT_WINDOW_MS).unref();
|
|
1203
|
+
router.post('/workspaces/:id/policies/chat', async (req, res) => {
|
|
1204
|
+
if (!requireSession(req, res))
|
|
1205
|
+
return;
|
|
1206
|
+
const user = req.sessionUser;
|
|
1207
|
+
const workspaceId = param(req, 'id');
|
|
1208
|
+
const membership = workspaceMemberStore.getByWorkspaceAndUser(workspaceId, user.id);
|
|
1209
|
+
if (!membership) {
|
|
1210
|
+
res.status(403).json({ error: 'Not a member of this workspace' });
|
|
1211
|
+
return;
|
|
1212
|
+
}
|
|
1213
|
+
const apiKey = process.env.PALARYN_LLM_API_KEY;
|
|
1214
|
+
if (!apiKey) {
|
|
1215
|
+
res.status(503).json({ error: 'AI policy chat is not configured. Set PALARYN_LLM_API_KEY environment variable.' });
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
const { messages, current_policy } = req.body;
|
|
1219
|
+
if (!messages || !Array.isArray(messages) || messages.length === 0) {
|
|
1220
|
+
res.status(400).json({ error: 'messages array is required and must not be empty' });
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
|
+
// Per-user rate limit check
|
|
1224
|
+
const now = Date.now();
|
|
1225
|
+
const userTimestamps = llmChatRateLimitHits.get(user.id) || [];
|
|
1226
|
+
const windowStart = now - LLM_CHAT_RATE_LIMIT_WINDOW_MS;
|
|
1227
|
+
const validTimestamps = userTimestamps.filter(t => t > windowStart);
|
|
1228
|
+
if (validTimestamps.length >= LLM_CHAT_RATE_LIMIT_MAX) {
|
|
1229
|
+
const retryAfterMs = LLM_CHAT_RATE_LIMIT_WINDOW_MS - (now - validTimestamps[0]);
|
|
1230
|
+
res.status(429).json({
|
|
1231
|
+
error: `You've reached the limit of ${LLM_CHAT_RATE_LIMIT_MAX} AI chat messages per hour. Please try again later.`,
|
|
1232
|
+
retry_after_ms: retryAfterMs,
|
|
1233
|
+
});
|
|
1234
|
+
return;
|
|
1235
|
+
}
|
|
1236
|
+
validTimestamps.push(now);
|
|
1237
|
+
llmChatRateLimitHits.set(user.id, validTimestamps);
|
|
1238
|
+
const chatSystemPrompt = `You are a policy advisor for Palaryn, an AI agent gateway. You help users create, understand, and refine security policies that control what AI agents can do.
|
|
1239
|
+
|
|
1240
|
+
You respond conversationally, explaining your reasoning and the implications of the policy you propose.
|
|
1241
|
+
|
|
1242
|
+
Your response MUST include:
|
|
1243
|
+
|
|
1244
|
+
1. A brief explanation of what the policy does and why
|
|
1245
|
+
2. A "Policy Proposal" section with bullet points summarizing the key rules
|
|
1246
|
+
3. Example scenarios showing how the policy would behave, e.g.:
|
|
1247
|
+
- "If agent X tries to DELETE /api/users -> DENIED by rule block-delete-ops"
|
|
1248
|
+
- "If agent Y sends a GET to api.github.com -> ALLOWED by rule allow-github-read"
|
|
1249
|
+
4. The complete PolicyPack JSON between <policy> and </policy> tags at the end
|
|
1250
|
+
|
|
1251
|
+
The PolicyPack JSON schema:
|
|
1252
|
+
{
|
|
1253
|
+
"name": string, // Short kebab-case name
|
|
1254
|
+
"version": string, // Semver, e.g. "1.0.0"
|
|
1255
|
+
"description": string, // Human-readable description
|
|
1256
|
+
"default_effect": "ALLOW" | "DENY", // What happens when no rule matches
|
|
1257
|
+
"domain_allowlist": string[], // Optional: only these domains allowed
|
|
1258
|
+
"domain_blocklist": string[], // Optional: these domains blocked
|
|
1259
|
+
"rules": [
|
|
1260
|
+
{
|
|
1261
|
+
"name": string,
|
|
1262
|
+
"description": string,
|
|
1263
|
+
"effect": "ALLOW" | "DENY" | "REQUIRE_APPROVAL" | "TRANSFORM",
|
|
1264
|
+
"priority": number, // Lower = higher precedence (1-100)
|
|
1265
|
+
"conditions": {
|
|
1266
|
+
"tools": string[], // e.g. "http.request", "slack.post_message"
|
|
1267
|
+
"tool_match": string, // Regex pattern for tool name matching
|
|
1268
|
+
"capabilities": string[], // "read", "write", "delete", "admin"
|
|
1269
|
+
"actors": string[], // Actor/agent IDs
|
|
1270
|
+
"actor_types": string[], // "agent", "user", "system"
|
|
1271
|
+
"domains": string[], // "api.github.com", "*.googleapis.com"
|
|
1272
|
+
"domain_blocklist": string[],// Blocked domains
|
|
1273
|
+
"methods": string[], // "GET", "POST", "PUT", "DELETE", "PATCH"
|
|
1274
|
+
"labels": string[], // Context labels
|
|
1275
|
+
"dlp_pattern_names": string[],// "secret-detected", "pii-detected"
|
|
1276
|
+
"platforms": string[] // "langgraph", "claude_code", "n8n"
|
|
1277
|
+
},
|
|
1278
|
+
"approval": { // Only for REQUIRE_APPROVAL effect
|
|
1279
|
+
"scope": string,
|
|
1280
|
+
"ttl_seconds": number,
|
|
1281
|
+
"reason": string
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
]
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
Important guidelines:
|
|
1288
|
+
- Only include condition fields that are relevant. Omit empty arrays.
|
|
1289
|
+
- Use "DENY" for blocking, "ALLOW" for permitting, "REQUIRE_APPROVAL" for human-in-the-loop.
|
|
1290
|
+
- Always pair capabilities with matching methods: read -> ["GET","HEAD","OPTIONS"], write -> ["POST","PUT","PATCH"], delete -> ["DELETE"].
|
|
1291
|
+
- For domain wildcards use "*.example.com" format.
|
|
1292
|
+
- The JSON between <policy> tags must be valid JSON (no trailing commas, no comments).
|
|
1293
|
+
${current_policy ? `\nThe user has an existing policy. Refine it based on their latest message. Here is the current policy:\n${JSON.stringify(current_policy, null, 2)}` : ''}`;
|
|
1294
|
+
// Set up SSE response
|
|
1295
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
1296
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
1297
|
+
res.setHeader('Connection', 'keep-alive');
|
|
1298
|
+
res.flushHeaders();
|
|
1299
|
+
try {
|
|
1300
|
+
const isOpenAI = apiKey.startsWith('sk-proj-') || apiKey.startsWith('sk-');
|
|
1301
|
+
const llmUrl = isOpenAI
|
|
1302
|
+
? 'https://api.openai.com/v1/chat/completions'
|
|
1303
|
+
: 'https://api.anthropic.com/v1/messages';
|
|
1304
|
+
const llmHeaders = isOpenAI
|
|
1305
|
+
? { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }
|
|
1306
|
+
: { 'Content-Type': 'application/json', 'x-api-key': apiKey, 'anthropic-version': '2023-06-01' };
|
|
1307
|
+
// Build message array for multi-turn context
|
|
1308
|
+
const chatMessages = messages.map((m) => ({
|
|
1309
|
+
role: m.role,
|
|
1310
|
+
content: m.content.slice(0, 2000),
|
|
1311
|
+
}));
|
|
1312
|
+
const llmBody = isOpenAI
|
|
1313
|
+
? JSON.stringify({
|
|
1314
|
+
model: 'gpt-4.1-mini',
|
|
1315
|
+
max_tokens: 2048,
|
|
1316
|
+
stream: true,
|
|
1317
|
+
messages: [
|
|
1318
|
+
{ role: 'system', content: chatSystemPrompt },
|
|
1319
|
+
...chatMessages,
|
|
1320
|
+
],
|
|
1321
|
+
})
|
|
1322
|
+
: JSON.stringify({
|
|
1323
|
+
model: 'claude-sonnet-4-5-20250514',
|
|
1324
|
+
max_tokens: 2048,
|
|
1325
|
+
stream: true,
|
|
1326
|
+
system: chatSystemPrompt,
|
|
1327
|
+
messages: chatMessages,
|
|
1328
|
+
});
|
|
1329
|
+
const controller = new AbortController();
|
|
1330
|
+
const timeout = setTimeout(() => controller.abort(), 60000);
|
|
1331
|
+
// Handle client disconnect
|
|
1332
|
+
req.on('close', () => {
|
|
1333
|
+
controller.abort();
|
|
1334
|
+
clearTimeout(timeout);
|
|
1335
|
+
});
|
|
1336
|
+
const llmRes = await fetch(llmUrl, {
|
|
1337
|
+
method: 'POST',
|
|
1338
|
+
headers: llmHeaders,
|
|
1339
|
+
body: llmBody,
|
|
1340
|
+
signal: controller.signal,
|
|
1341
|
+
});
|
|
1342
|
+
clearTimeout(timeout);
|
|
1343
|
+
if (!llmRes.ok) {
|
|
1344
|
+
const errBody = await llmRes.text();
|
|
1345
|
+
console.error('[policy-chat] LLM API error:', llmRes.status, errBody);
|
|
1346
|
+
res.write(`event: error\ndata: ${JSON.stringify({ error: 'LLM API returned an error.' })}\n\n`);
|
|
1347
|
+
res.end();
|
|
1348
|
+
return;
|
|
1349
|
+
}
|
|
1350
|
+
if (!llmRes.body) {
|
|
1351
|
+
res.write(`event: error\ndata: ${JSON.stringify({ error: 'No response stream from LLM.' })}\n\n`);
|
|
1352
|
+
res.end();
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1355
|
+
let fullText = '';
|
|
1356
|
+
const reader = llmRes.body;
|
|
1357
|
+
const decoder = new TextDecoder();
|
|
1358
|
+
let buffer = '';
|
|
1359
|
+
for await (const chunk of reader) {
|
|
1360
|
+
buffer += decoder.decode(chunk, { stream: true });
|
|
1361
|
+
const lines = buffer.split('\n');
|
|
1362
|
+
buffer = lines.pop() || '';
|
|
1363
|
+
for (const line of lines) {
|
|
1364
|
+
if (!line.startsWith('data: '))
|
|
1365
|
+
continue;
|
|
1366
|
+
const data = line.slice(6).trim();
|
|
1367
|
+
if (data === '[DONE]')
|
|
1368
|
+
continue;
|
|
1369
|
+
try {
|
|
1370
|
+
const parsed = JSON.parse(data);
|
|
1371
|
+
let text = '';
|
|
1372
|
+
if (isOpenAI) {
|
|
1373
|
+
text = parsed.choices?.[0]?.delta?.content || '';
|
|
1374
|
+
}
|
|
1375
|
+
else {
|
|
1376
|
+
// Anthropic streaming events
|
|
1377
|
+
if (parsed.type === 'content_block_delta') {
|
|
1378
|
+
text = parsed.delta?.text || '';
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
if (text) {
|
|
1382
|
+
fullText += text;
|
|
1383
|
+
res.write(`event: text\ndata: ${JSON.stringify(text)}\n\n`);
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
catch {
|
|
1387
|
+
// Skip unparseable SSE data lines
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
// Process any remaining buffer
|
|
1392
|
+
if (buffer.trim()) {
|
|
1393
|
+
const remaining = buffer.trim();
|
|
1394
|
+
if (remaining.startsWith('data: ') && remaining.slice(6).trim() !== '[DONE]') {
|
|
1395
|
+
try {
|
|
1396
|
+
const parsed = JSON.parse(remaining.slice(6).trim());
|
|
1397
|
+
let text = '';
|
|
1398
|
+
if (isOpenAI) {
|
|
1399
|
+
text = parsed.choices?.[0]?.delta?.content || '';
|
|
1400
|
+
}
|
|
1401
|
+
else if (parsed.type === 'content_block_delta') {
|
|
1402
|
+
text = parsed.delta?.text || '';
|
|
1403
|
+
}
|
|
1404
|
+
if (text) {
|
|
1405
|
+
fullText += text;
|
|
1406
|
+
res.write(`event: text\ndata: ${JSON.stringify(text)}\n\n`);
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
catch {
|
|
1410
|
+
// Skip
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
// Extract policy JSON from <policy>...</policy> tags
|
|
1415
|
+
const policyMatch = fullText.match(/<policy>([\s\S]*?)<\/policy>/);
|
|
1416
|
+
if (policyMatch) {
|
|
1417
|
+
try {
|
|
1418
|
+
const policyJson = JSON.parse(policyMatch[1].trim());
|
|
1419
|
+
const validation = engine_2.PolicyEngine.validate(policyJson);
|
|
1420
|
+
if (validation.valid) {
|
|
1421
|
+
res.write(`event: policy\ndata: ${JSON.stringify(policyJson)}\n\n`);
|
|
1422
|
+
}
|
|
1423
|
+
else {
|
|
1424
|
+
res.write(`event: policy\ndata: ${JSON.stringify({ ...policyJson, _validation_errors: validation.errors })}\n\n`);
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
catch {
|
|
1428
|
+
console.error('[policy-chat] Failed to parse policy JSON from LLM output');
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
res.write(`event: done\ndata: {}\n\n`);
|
|
1432
|
+
res.end();
|
|
1433
|
+
}
|
|
1434
|
+
catch (err) {
|
|
1435
|
+
if (err.name === 'AbortError') {
|
|
1436
|
+
res.write(`event: error\ndata: ${JSON.stringify({ error: 'Request timed out or was cancelled.' })}\n\n`);
|
|
1437
|
+
}
|
|
1438
|
+
else {
|
|
1439
|
+
console.error('[policy-chat] Unexpected error:', err);
|
|
1440
|
+
res.write(`event: error\ndata: ${JSON.stringify({ error: 'An unexpected error occurred.' })}\n\n`);
|
|
1441
|
+
}
|
|
1442
|
+
res.end();
|
|
1443
|
+
}
|
|
1444
|
+
});
|
|
1445
|
+
// ---------------------------------------------------------------------------
|
|
1109
1446
|
// Security (DLP detections dashboard)
|
|
1110
1447
|
// ---------------------------------------------------------------------------
|
|
1111
1448
|
router.get('/workspaces/:id/security', (req, res) => {
|