@solongate/proxy 0.82.48 → 0.82.50

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 CHANGED
@@ -9222,6 +9222,8 @@ function PoliciesPanel({ focused }) {
9222
9222
  const [editing, setEditing] = useState4(false);
9223
9223
  const [editVal, setEditVal] = useState4("");
9224
9224
  const [status, setStatus] = useState4(null);
9225
+ const [pendingDel, setPendingDel] = useState4(null);
9226
+ const [permCursor, setPermCursor] = useState4(0);
9225
9227
  const selected = policies[Math.min(pi, Math.max(0, policies.length - 1))];
9226
9228
  const detail = useLoader(() => selected ? api.policies.get(selected.id) : Promise.resolve(null), [selected?.id]);
9227
9229
  useEffect4(() => {
@@ -9244,6 +9246,11 @@ function PoliciesPanel({ focused }) {
9244
9246
  };
9245
9247
  const save = async () => {
9246
9248
  if (!selected || !detail.data) return;
9249
+ const blanket = rules.findIndex(isBlanketRule);
9250
+ if (blanket !== -1) {
9251
+ setStatus(`\u2717 rule ${blanket + 1} matches EVERYTHING \u2014 pick a Constraint + Match value, or narrow Permissions`);
9252
+ return;
9253
+ }
9247
9254
  setStatus("Saving\u2026");
9248
9255
  try {
9249
9256
  const res = await api.policies.update(selected.id, {
@@ -9292,9 +9299,13 @@ function PoliciesPanel({ focused }) {
9292
9299
  return;
9293
9300
  }
9294
9301
  if (view === "list") {
9295
- if (key.upArrow) setPi((n) => Math.max(0, n - 1));
9296
- else if (key.downArrow) setPi((n) => Math.min(policies.length - 1, n + 1));
9297
- else if (key.return || key.rightArrow) {
9302
+ if (key.upArrow) {
9303
+ setPendingDel(null);
9304
+ setPi((n) => Math.max(0, n - 1));
9305
+ } else if (key.downArrow) {
9306
+ setPendingDel(null);
9307
+ setPi((n) => Math.min(policies.length - 1, n + 1));
9308
+ } else if (key.return || key.rightArrow) {
9298
9309
  setStatus(null);
9299
9310
  setView("rules");
9300
9311
  } else if (input === "a") {
@@ -9314,6 +9325,22 @@ function PoliciesPanel({ focused }) {
9314
9325
  setStatus("\u2713 active policy: NONE \u2014 no rules enforced, w/b grants are refused");
9315
9326
  activeQ.reload();
9316
9327
  }).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
9328
+ } else if (input === "d") {
9329
+ if (!selected) return;
9330
+ if (pendingDel !== selected.id) {
9331
+ setPendingDel(selected.id);
9332
+ setStatus(`\u26A0 d again to DELETE policy "${selected.name}" \u2014 cannot be undone`);
9333
+ return;
9334
+ }
9335
+ setPendingDel(null);
9336
+ setStatus("Deleting\u2026");
9337
+ const name = selected.name;
9338
+ void api.policies.remove(selected.id).then(() => {
9339
+ setStatus(`\u2713 deleted policy "${name}"`);
9340
+ setPi(0);
9341
+ list5.reload();
9342
+ activeQ.reload();
9343
+ }).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
9317
9344
  }
9318
9345
  return;
9319
9346
  }
@@ -9329,7 +9356,7 @@ function PoliciesPanel({ focused }) {
9329
9356
  } else if (input === " ") {
9330
9357
  if (rules[ri]) mutate(rules.map((r, i) => i === ri ? { ...r, enabled: !r.enabled } : r));
9331
9358
  } else if (input === "e") {
9332
- if (rules[ri]) mutate(rules.map((r, i) => i === ri ? { ...r, effect: r.effect === "ALLOW" ? "DENY" : "ALLOW" } : r));
9359
+ if (rules[ri]) mutate(rules.map((r, i) => i === ri ? migrateEffect(r, r.effect === "ALLOW" ? "DENY" : "ALLOW") : r));
9333
9360
  } else if (input === "d") {
9334
9361
  if (rules[ri]) {
9335
9362
  const next = rules.filter((_, i) => i !== ri);
@@ -9339,7 +9366,7 @@ function PoliciesPanel({ focused }) {
9339
9366
  } else if (input === "m") {
9340
9367
  mutate(rules, mode === "denylist" ? "whitelist" : "denylist");
9341
9368
  } else if (input === "n") {
9342
- const nr = { id: `rule-${Date.now()}`, description: "", effect: "DENY", priority: 100, toolPattern: "*", minimumTrustLevel: "UNTRUSTED", enabled: true };
9369
+ const nr = { id: `rule-${Date.now()}`, description: "", effect: "DENY", priority: 100, toolPattern: "*", minimumTrustLevel: "UNTRUSTED", enabled: true, commandConstraints: { denied: [] } };
9343
9370
  mutate([nr, ...rules]);
9344
9371
  setRi(0);
9345
9372
  setFi(0);
@@ -9360,13 +9387,28 @@ function PoliciesPanel({ focused }) {
9360
9387
  else if (key.upArrow) setFi((n) => Math.max(0, n - 1));
9361
9388
  else if (key.downArrow) setFi((n) => Math.min(FIELDS.length - 1, n + 1));
9362
9389
  else if (field.kind === "effect" && (input === " " || key.rightArrow)) {
9363
- mutate(rules.map((r, i) => i === ri ? { ...r, effect: r.effect === "ALLOW" ? "DENY" : "ALLOW" } : r));
9390
+ mutate(rules.map((r, i) => i === ri ? migrateEffect(r, r.effect === "ALLOW" ? "DENY" : "ALLOW") : r));
9364
9391
  } else if (field.kind === "enabled" && (input === " " || key.rightArrow)) {
9365
9392
  mutate(rules.map((r, i) => i === ri ? { ...r, enabled: !r.enabled } : r));
9393
+ } else if (field.kind === "ctype" && (input === " " || key.rightArrow || key.leftArrow)) {
9394
+ const dir = key.leftArrow ? -1 : 1;
9395
+ const nextT = CTYPES[(CTYPES.indexOf(currentCType(rule2)) + dir + CTYPES.length) % CTYPES.length];
9396
+ mutate(rules.map((r, i) => i === ri ? setCType(r, nextT) : r));
9397
+ } else if (field.kind === "perms" && key.leftArrow) {
9398
+ setPermCursor((n) => (n + PERMS.length - 1) % PERMS.length);
9399
+ } else if (field.kind === "perms" && key.rightArrow) {
9400
+ setPermCursor((n) => (n + 1) % PERMS.length);
9401
+ } else if (field.kind === "perms" && input === " ") {
9402
+ const p = PERMS[permCursor % PERMS.length];
9403
+ mutate(rules.map((rr, i) => i === ri ? togglePerm(rr, p) : rr));
9404
+ } else if (field.kind === "match" && input === "[") {
9405
+ mutate(rules.map((rr, i) => i === ri ? toggleStar(rr, "left") : rr));
9406
+ } else if (field.kind === "match" && input === "]") {
9407
+ mutate(rules.map((rr, i) => i === ri ? toggleStar(rr, "right") : rr));
9366
9408
  } else if (field.kind === "priority" && (key.rightArrow || key.leftArrow)) {
9367
9409
  const d = key.rightArrow ? 1 : -1;
9368
9410
  mutate(rules.map((r, i) => i === ri ? { ...r, priority: Math.max(0, r.priority + d) } : r));
9369
- } else if (field.kind === "text" && key.return) {
9411
+ } else if ((field.kind === "text" || field.kind === "match") && key.return) {
9370
9412
  setEditVal(field.get(rule2));
9371
9413
  setEditing(true);
9372
9414
  } else if (input === "s") void save();
@@ -9392,7 +9434,7 @@ function PoliciesPanel({ focused }) {
9392
9434
  /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: activeBy ? ` (${activeBy})` : "" })
9393
9435
  ] }) : /* @__PURE__ */ jsx4(Text4, { color: theme.bad, bold: true, children: "\u25CB NONE \u2014 nothing enforced, grants refused" })
9394
9436
  ] }),
9395
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, wrap: "truncate", children: focused ? `\u2191\u2193 select \xB7 enter open \xB7 n new policy \xB7 a activate \xB7 x deactivate \xB7 ^R refresh${lAbove ? ` \xB7 \u25B2${lAbove}` : ""}${lBelow ? ` \xB7 \u25BC${lBelow}` : ""}` : "press \u2192 to browse" }),
9437
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, wrap: "truncate", children: focused ? `\u2191\u2193 select \xB7 enter open \xB7 n new \xB7 a activate \xB7 x deactivate \xB7 d delete \xB7 ^R refresh${lAbove ? ` \xB7 \u25B2${lAbove}` : ""}${lBelow ? ` \xB7 \u25BC${lBelow}` : ""}` : "press \u2192 to browse" }),
9396
9438
  creating !== null ? /* @__PURE__ */ jsxs4(Box4, { children: [
9397
9439
  /* @__PURE__ */ jsx4(Text4, { color: theme.warn, children: "new policy name: " }),
9398
9440
  /* @__PURE__ */ jsx4(TextInput3, { value: creating, onChange: setCreating, onSubmit: commitNewPolicy })
@@ -9467,12 +9509,53 @@ function PoliciesPanel({ focused }) {
9467
9509
  /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: rule?.id ?? "" }),
9468
9510
  dirtyTag
9469
9511
  ] }),
9470
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "\u2191\u2193 field \xB7 enter edit text \xB7 space/\u2192 toggle \xB7 s save \xB7 \u2190 back" }),
9512
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "\u2191\u2193 field \xB7 enter edit \xB7 space toggle \xB7 \u2190\u2192 move/pick \xB7 [ ] wildcards \xB7 s save \xB7 \u2190 back" }),
9471
9513
  /* @__PURE__ */ jsx4(Box4, { marginTop: 1, flexDirection: "column", children: FIELDS.map((f, i) => {
9472
- const val = rule ? f.get(rule) : "";
9473
9514
  const active2 = i === fi;
9515
+ const label = /* @__PURE__ */ jsx4(Text4, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + f.label.padEnd(13) });
9516
+ if (f.kind === "perms" && rule) {
9517
+ return /* @__PURE__ */ jsxs4(Box4, { children: [
9518
+ label,
9519
+ PERMS.map((p, pi2) => {
9520
+ const on = permList(rule).includes(p);
9521
+ const cur = active2 && pi2 === permCursor % PERMS.length;
9522
+ return /* @__PURE__ */ jsxs4(Text4, { color: on ? theme.ok : theme.bad, inverse: cur, children: [
9523
+ p,
9524
+ pi2 < PERMS.length - 1 ? " " : ""
9525
+ ] }, p);
9526
+ })
9527
+ ] }, f.label);
9528
+ }
9529
+ if (f.kind === "match" && rule) {
9530
+ if (active2 && editing) {
9531
+ return /* @__PURE__ */ jsxs4(Box4, { children: [
9532
+ label,
9533
+ /* @__PURE__ */ jsx4(
9534
+ TextInput3,
9535
+ {
9536
+ value: editVal,
9537
+ onChange: setEditVal,
9538
+ onSubmit: (v2) => {
9539
+ if (f.set) mutate(rules.map((r, idx) => idx === ri ? f.set(r, v2) : r));
9540
+ setEditing(false);
9541
+ }
9542
+ }
9543
+ )
9544
+ ] }, f.label);
9545
+ }
9546
+ const v = f.get(rule);
9547
+ const lOn = starActive(rule, "left");
9548
+ const rOn = starActive(rule, "right");
9549
+ return /* @__PURE__ */ jsxs4(Box4, { children: [
9550
+ label,
9551
+ /* @__PURE__ */ jsx4(Text4, { color: lOn ? theme.ok : theme.dim, children: lOn ? "\u2731 " : "\xB7 " }),
9552
+ /* @__PURE__ */ jsx4(Text4, { dimColor: !v, children: v || "\u2014" }),
9553
+ /* @__PURE__ */ jsx4(Text4, { color: rOn ? theme.ok : theme.dim, children: rOn ? " \u2731" : " \xB7" })
9554
+ ] }, f.label);
9555
+ }
9556
+ const val = rule ? f.get(rule) : "";
9474
9557
  return /* @__PURE__ */ jsxs4(Box4, { children: [
9475
- /* @__PURE__ */ jsx4(Text4, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + f.label.padEnd(13) }),
9558
+ label,
9476
9559
  active2 && editing && f.kind === "text" ? /* @__PURE__ */ jsx4(
9477
9560
  TextInput3,
9478
9561
  {
@@ -9493,10 +9576,11 @@ function PoliciesPanel({ focused }) {
9493
9576
  )
9494
9577
  ] }, f.label);
9495
9578
  }) }),
9579
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: " * = wildcard (matches any run of chars). On Match: [ toggles a leading *, ] a trailing * \xB7 e.g. *ping* matches any command containing ping" }),
9496
9580
  status ? /* @__PURE__ */ jsx4(Text4, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
9497
9581
  ] });
9498
9582
  }
9499
- var constr, setConstr, FIELDS;
9583
+ var CTYPES, CTYPE_GROUP, currentCType, clearConstraints, setCType, cValue, setCValue, matchItems, starActive, toggleStar, migrateEffect, PERMS, permList, permIsAny, togglePerm, permDisplay, ruleHasValues, isBlanketRule, FIELDS;
9500
9584
  var init_Policies = __esm({
9501
9585
  "src/tui/panels/Policies.tsx"() {
9502
9586
  "use strict";
@@ -9506,25 +9590,99 @@ var init_Policies = __esm({
9506
9590
  init_components();
9507
9591
  init_hooks();
9508
9592
  init_theme();
9509
- constr = (r, g, side) => (r[g]?.[side] ?? []).join(", ");
9510
- setConstr = (r, g, side, val) => {
9593
+ CTYPES = ["none", "command", "path", "filename", "url"];
9594
+ CTYPE_GROUP = {
9595
+ command: "commandConstraints",
9596
+ path: "pathConstraints",
9597
+ filename: "filenameConstraints",
9598
+ url: "urlConstraints"
9599
+ };
9600
+ currentCType = (r) => {
9601
+ if (r.filenameConstraints) return "filename";
9602
+ if (r.urlConstraints) return "url";
9603
+ if (r.commandConstraints) return "command";
9604
+ if (r.pathConstraints) return "path";
9605
+ return "none";
9606
+ };
9607
+ clearConstraints = (r) => ({
9608
+ ...r,
9609
+ commandConstraints: void 0,
9610
+ pathConstraints: void 0,
9611
+ filenameConstraints: void 0,
9612
+ urlConstraints: void 0
9613
+ });
9614
+ setCType = (r, t) => {
9615
+ const base = clearConstraints(r);
9616
+ if (t === "none") return base;
9617
+ const listKey = r.effect === "ALLOW" ? "allowed" : "denied";
9618
+ return { ...base, [CTYPE_GROUP[t]]: { [listKey]: [] } };
9619
+ };
9620
+ cValue = (r) => {
9621
+ const t = currentCType(r);
9622
+ if (t === "none") return "";
9623
+ const c2 = r[CTYPE_GROUP[t]];
9624
+ return [...c2?.allowed ?? [], ...c2?.denied ?? []].join(", ");
9625
+ };
9626
+ setCValue = (r, val) => {
9627
+ const t = currentCType(r);
9628
+ if (t === "none") return r;
9511
9629
  const arr = val.split(",").map((s) => s.trim()).filter(Boolean);
9512
- const prev = r[g] ?? {};
9513
- return { ...r, [g]: { ...prev, [side]: arr } };
9630
+ const listKey = r.effect === "ALLOW" ? "allowed" : "denied";
9631
+ return { ...r, [CTYPE_GROUP[t]]: { [listKey]: arr } };
9632
+ };
9633
+ matchItems = (r) => {
9634
+ const t = currentCType(r);
9635
+ if (t === "none") return [];
9636
+ const c2 = r[CTYPE_GROUP[t]];
9637
+ return [...c2?.allowed ?? [], ...c2?.denied ?? []];
9638
+ };
9639
+ starActive = (r, side) => {
9640
+ const items = matchItems(r);
9641
+ return items.length > 0 && items.every((v) => side === "left" ? v.startsWith("*") : v.endsWith("*"));
9642
+ };
9643
+ toggleStar = (r, side) => {
9644
+ const t = currentCType(r);
9645
+ if (t === "none") return r;
9646
+ const on = starActive(r, side);
9647
+ const listKey = r.effect === "ALLOW" ? "allowed" : "denied";
9648
+ const items = matchItems(r).map(
9649
+ (v) => side === "left" ? on ? v.replace(/^\*+/, "") : "*" + v : on ? v.replace(/\*+$/, "") : v + "*"
9650
+ );
9651
+ return { ...r, [CTYPE_GROUP[t]]: { [listKey]: items } };
9652
+ };
9653
+ migrateEffect = (r, nextEffect) => {
9654
+ const flipped = { ...r, effect: nextEffect };
9655
+ const t = currentCType(r);
9656
+ if (t === "none") return flipped;
9657
+ const c2 = r[CTYPE_GROUP[t]];
9658
+ const items = [...c2?.allowed ?? [], ...c2?.denied ?? []];
9659
+ const listKey = nextEffect === "ALLOW" ? "allowed" : "denied";
9660
+ return { ...flipped, [CTYPE_GROUP[t]]: { [listKey]: items } };
9661
+ };
9662
+ PERMS = ["READ", "WRITE", "EXECUTE", "NETWORK"];
9663
+ permList = (r) => !r.permission ? [...PERMS] : Array.isArray(r.permission) ? r.permission : [r.permission];
9664
+ permIsAny = (r) => PERMS.every((p) => permList(r).includes(p));
9665
+ togglePerm = (r, p) => {
9666
+ const cur = permList(r);
9667
+ let next = cur.includes(p) ? cur.filter((x) => x !== p) : [...cur, p];
9668
+ if (next.length === 0) next = [p];
9669
+ return { ...r, permission: PERMS.every((x) => next.includes(x)) ? void 0 : next };
9670
+ };
9671
+ permDisplay = (r) => permIsAny(r) ? "any (READ WRITE EXECUTE NETWORK)" : permList(r).join(" ");
9672
+ ruleHasValues = (r) => {
9673
+ const t = currentCType(r);
9674
+ if (t === "none") return false;
9675
+ const c2 = r[CTYPE_GROUP[t]];
9676
+ return (c2?.allowed?.length ?? 0) + (c2?.denied?.length ?? 0) > 0;
9514
9677
  };
9678
+ isBlanketRule = (r) => !ruleHasValues(r) && permIsAny(r);
9515
9679
  FIELDS = [
9516
9680
  { label: "Effect", kind: "effect", get: (r) => r.effect },
9517
- { label: "Enabled", kind: "enabled", get: (r) => r.enabled ? "yes" : "no" },
9681
+ { label: "Constraint", kind: "ctype", get: (r) => currentCType(r) },
9682
+ { label: "Permissions", kind: "perms", get: permDisplay },
9518
9683
  { label: "Priority", kind: "priority", get: (r) => String(r.priority) },
9519
9684
  { label: "Description", kind: "text", get: (r) => r.description ?? "", set: (r, v) => ({ ...r, description: v }) },
9520
- { label: "Cmd allow", kind: "text", get: (r) => constr(r, "commandConstraints", "allowed"), set: (r, v) => setConstr(r, "commandConstraints", "allowed", v) },
9521
- { label: "Cmd deny", kind: "text", get: (r) => constr(r, "commandConstraints", "denied"), set: (r, v) => setConstr(r, "commandConstraints", "denied", v) },
9522
- { label: "Path allow", kind: "text", get: (r) => constr(r, "pathConstraints", "allowed"), set: (r, v) => setConstr(r, "pathConstraints", "allowed", v) },
9523
- { label: "Path deny", kind: "text", get: (r) => constr(r, "pathConstraints", "denied"), set: (r, v) => setConstr(r, "pathConstraints", "denied", v) },
9524
- { label: "File allow", kind: "text", get: (r) => constr(r, "filenameConstraints", "allowed"), set: (r, v) => setConstr(r, "filenameConstraints", "allowed", v) },
9525
- { label: "File deny", kind: "text", get: (r) => constr(r, "filenameConstraints", "denied"), set: (r, v) => setConstr(r, "filenameConstraints", "denied", v) },
9526
- { label: "URL allow", kind: "text", get: (r) => constr(r, "urlConstraints", "allowed"), set: (r, v) => setConstr(r, "urlConstraints", "allowed", v) },
9527
- { label: "URL deny", kind: "text", get: (r) => constr(r, "urlConstraints", "denied"), set: (r, v) => setConstr(r, "urlConstraints", "denied", v) }
9685
+ { label: "Match", kind: "match", get: cValue, set: setCValue }
9528
9686
  ];
9529
9687
  }
9530
9688
  });
package/dist/tui/index.js CHANGED
@@ -2420,25 +2420,99 @@ function onSectionRequest(fn) {
2420
2420
 
2421
2421
  // src/tui/panels/Policies.tsx
2422
2422
  import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
2423
- var constr = (r, g, side) => (r[g]?.[side] ?? []).join(", ");
2424
- var setConstr = (r, g, side, val) => {
2423
+ var CTYPES = ["none", "command", "path", "filename", "url"];
2424
+ var CTYPE_GROUP = {
2425
+ command: "commandConstraints",
2426
+ path: "pathConstraints",
2427
+ filename: "filenameConstraints",
2428
+ url: "urlConstraints"
2429
+ };
2430
+ var currentCType = (r) => {
2431
+ if (r.filenameConstraints) return "filename";
2432
+ if (r.urlConstraints) return "url";
2433
+ if (r.commandConstraints) return "command";
2434
+ if (r.pathConstraints) return "path";
2435
+ return "none";
2436
+ };
2437
+ var clearConstraints = (r) => ({
2438
+ ...r,
2439
+ commandConstraints: void 0,
2440
+ pathConstraints: void 0,
2441
+ filenameConstraints: void 0,
2442
+ urlConstraints: void 0
2443
+ });
2444
+ var setCType = (r, t) => {
2445
+ const base = clearConstraints(r);
2446
+ if (t === "none") return base;
2447
+ const listKey = r.effect === "ALLOW" ? "allowed" : "denied";
2448
+ return { ...base, [CTYPE_GROUP[t]]: { [listKey]: [] } };
2449
+ };
2450
+ var cValue = (r) => {
2451
+ const t = currentCType(r);
2452
+ if (t === "none") return "";
2453
+ const c2 = r[CTYPE_GROUP[t]];
2454
+ return [...c2?.allowed ?? [], ...c2?.denied ?? []].join(", ");
2455
+ };
2456
+ var setCValue = (r, val) => {
2457
+ const t = currentCType(r);
2458
+ if (t === "none") return r;
2425
2459
  const arr = val.split(",").map((s) => s.trim()).filter(Boolean);
2426
- const prev = r[g] ?? {};
2427
- return { ...r, [g]: { ...prev, [side]: arr } };
2460
+ const listKey = r.effect === "ALLOW" ? "allowed" : "denied";
2461
+ return { ...r, [CTYPE_GROUP[t]]: { [listKey]: arr } };
2462
+ };
2463
+ var matchItems = (r) => {
2464
+ const t = currentCType(r);
2465
+ if (t === "none") return [];
2466
+ const c2 = r[CTYPE_GROUP[t]];
2467
+ return [...c2?.allowed ?? [], ...c2?.denied ?? []];
2468
+ };
2469
+ var starActive = (r, side) => {
2470
+ const items = matchItems(r);
2471
+ return items.length > 0 && items.every((v) => side === "left" ? v.startsWith("*") : v.endsWith("*"));
2428
2472
  };
2473
+ var toggleStar = (r, side) => {
2474
+ const t = currentCType(r);
2475
+ if (t === "none") return r;
2476
+ const on = starActive(r, side);
2477
+ const listKey = r.effect === "ALLOW" ? "allowed" : "denied";
2478
+ const items = matchItems(r).map(
2479
+ (v) => side === "left" ? on ? v.replace(/^\*+/, "") : "*" + v : on ? v.replace(/\*+$/, "") : v + "*"
2480
+ );
2481
+ return { ...r, [CTYPE_GROUP[t]]: { [listKey]: items } };
2482
+ };
2483
+ var migrateEffect = (r, nextEffect) => {
2484
+ const flipped = { ...r, effect: nextEffect };
2485
+ const t = currentCType(r);
2486
+ if (t === "none") return flipped;
2487
+ const c2 = r[CTYPE_GROUP[t]];
2488
+ const items = [...c2?.allowed ?? [], ...c2?.denied ?? []];
2489
+ const listKey = nextEffect === "ALLOW" ? "allowed" : "denied";
2490
+ return { ...flipped, [CTYPE_GROUP[t]]: { [listKey]: items } };
2491
+ };
2492
+ var PERMS = ["READ", "WRITE", "EXECUTE", "NETWORK"];
2493
+ var permList = (r) => !r.permission ? [...PERMS] : Array.isArray(r.permission) ? r.permission : [r.permission];
2494
+ var permIsAny = (r) => PERMS.every((p) => permList(r).includes(p));
2495
+ var togglePerm = (r, p) => {
2496
+ const cur = permList(r);
2497
+ let next = cur.includes(p) ? cur.filter((x) => x !== p) : [...cur, p];
2498
+ if (next.length === 0) next = [p];
2499
+ return { ...r, permission: PERMS.every((x) => next.includes(x)) ? void 0 : next };
2500
+ };
2501
+ var permDisplay = (r) => permIsAny(r) ? "any (READ WRITE EXECUTE NETWORK)" : permList(r).join(" ");
2502
+ var ruleHasValues = (r) => {
2503
+ const t = currentCType(r);
2504
+ if (t === "none") return false;
2505
+ const c2 = r[CTYPE_GROUP[t]];
2506
+ return (c2?.allowed?.length ?? 0) + (c2?.denied?.length ?? 0) > 0;
2507
+ };
2508
+ var isBlanketRule = (r) => !ruleHasValues(r) && permIsAny(r);
2429
2509
  var FIELDS = [
2430
2510
  { label: "Effect", kind: "effect", get: (r) => r.effect },
2431
- { label: "Enabled", kind: "enabled", get: (r) => r.enabled ? "yes" : "no" },
2511
+ { label: "Constraint", kind: "ctype", get: (r) => currentCType(r) },
2512
+ { label: "Permissions", kind: "perms", get: permDisplay },
2432
2513
  { label: "Priority", kind: "priority", get: (r) => String(r.priority) },
2433
2514
  { label: "Description", kind: "text", get: (r) => r.description ?? "", set: (r, v) => ({ ...r, description: v }) },
2434
- { label: "Cmd allow", kind: "text", get: (r) => constr(r, "commandConstraints", "allowed"), set: (r, v) => setConstr(r, "commandConstraints", "allowed", v) },
2435
- { label: "Cmd deny", kind: "text", get: (r) => constr(r, "commandConstraints", "denied"), set: (r, v) => setConstr(r, "commandConstraints", "denied", v) },
2436
- { label: "Path allow", kind: "text", get: (r) => constr(r, "pathConstraints", "allowed"), set: (r, v) => setConstr(r, "pathConstraints", "allowed", v) },
2437
- { label: "Path deny", kind: "text", get: (r) => constr(r, "pathConstraints", "denied"), set: (r, v) => setConstr(r, "pathConstraints", "denied", v) },
2438
- { label: "File allow", kind: "text", get: (r) => constr(r, "filenameConstraints", "allowed"), set: (r, v) => setConstr(r, "filenameConstraints", "allowed", v) },
2439
- { label: "File deny", kind: "text", get: (r) => constr(r, "filenameConstraints", "denied"), set: (r, v) => setConstr(r, "filenameConstraints", "denied", v) },
2440
- { label: "URL allow", kind: "text", get: (r) => constr(r, "urlConstraints", "allowed"), set: (r, v) => setConstr(r, "urlConstraints", "allowed", v) },
2441
- { label: "URL deny", kind: "text", get: (r) => constr(r, "urlConstraints", "denied"), set: (r, v) => setConstr(r, "urlConstraints", "denied", v) }
2515
+ { label: "Match", kind: "match", get: cValue, set: setCValue }
2442
2516
  ];
2443
2517
  function ruleSummary(r) {
2444
2518
  const bits = [];
@@ -2466,6 +2540,8 @@ function PoliciesPanel({ focused }) {
2466
2540
  const [editing, setEditing] = useState4(false);
2467
2541
  const [editVal, setEditVal] = useState4("");
2468
2542
  const [status, setStatus] = useState4(null);
2543
+ const [pendingDel, setPendingDel] = useState4(null);
2544
+ const [permCursor, setPermCursor] = useState4(0);
2469
2545
  const selected = policies[Math.min(pi, Math.max(0, policies.length - 1))];
2470
2546
  const detail = useLoader(() => selected ? api.policies.get(selected.id) : Promise.resolve(null), [selected?.id]);
2471
2547
  useEffect4(() => {
@@ -2488,6 +2564,11 @@ function PoliciesPanel({ focused }) {
2488
2564
  };
2489
2565
  const save = async () => {
2490
2566
  if (!selected || !detail.data) return;
2567
+ const blanket = rules.findIndex(isBlanketRule);
2568
+ if (blanket !== -1) {
2569
+ setStatus(`\u2717 rule ${blanket + 1} matches EVERYTHING \u2014 pick a Constraint + Match value, or narrow Permissions`);
2570
+ return;
2571
+ }
2491
2572
  setStatus("Saving\u2026");
2492
2573
  try {
2493
2574
  const res = await api.policies.update(selected.id, {
@@ -2536,9 +2617,13 @@ function PoliciesPanel({ focused }) {
2536
2617
  return;
2537
2618
  }
2538
2619
  if (view === "list") {
2539
- if (key.upArrow) setPi((n) => Math.max(0, n - 1));
2540
- else if (key.downArrow) setPi((n) => Math.min(policies.length - 1, n + 1));
2541
- else if (key.return || key.rightArrow) {
2620
+ if (key.upArrow) {
2621
+ setPendingDel(null);
2622
+ setPi((n) => Math.max(0, n - 1));
2623
+ } else if (key.downArrow) {
2624
+ setPendingDel(null);
2625
+ setPi((n) => Math.min(policies.length - 1, n + 1));
2626
+ } else if (key.return || key.rightArrow) {
2542
2627
  setStatus(null);
2543
2628
  setView("rules");
2544
2629
  } else if (input === "a") {
@@ -2558,6 +2643,22 @@ function PoliciesPanel({ focused }) {
2558
2643
  setStatus("\u2713 active policy: NONE \u2014 no rules enforced, w/b grants are refused");
2559
2644
  activeQ.reload();
2560
2645
  }).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
2646
+ } else if (input === "d") {
2647
+ if (!selected) return;
2648
+ if (pendingDel !== selected.id) {
2649
+ setPendingDel(selected.id);
2650
+ setStatus(`\u26A0 d again to DELETE policy "${selected.name}" \u2014 cannot be undone`);
2651
+ return;
2652
+ }
2653
+ setPendingDel(null);
2654
+ setStatus("Deleting\u2026");
2655
+ const name = selected.name;
2656
+ void api.policies.remove(selected.id).then(() => {
2657
+ setStatus(`\u2713 deleted policy "${name}"`);
2658
+ setPi(0);
2659
+ list5.reload();
2660
+ activeQ.reload();
2661
+ }).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
2561
2662
  }
2562
2663
  return;
2563
2664
  }
@@ -2573,7 +2674,7 @@ function PoliciesPanel({ focused }) {
2573
2674
  } else if (input === " ") {
2574
2675
  if (rules[ri]) mutate(rules.map((r, i) => i === ri ? { ...r, enabled: !r.enabled } : r));
2575
2676
  } else if (input === "e") {
2576
- if (rules[ri]) mutate(rules.map((r, i) => i === ri ? { ...r, effect: r.effect === "ALLOW" ? "DENY" : "ALLOW" } : r));
2677
+ if (rules[ri]) mutate(rules.map((r, i) => i === ri ? migrateEffect(r, r.effect === "ALLOW" ? "DENY" : "ALLOW") : r));
2577
2678
  } else if (input === "d") {
2578
2679
  if (rules[ri]) {
2579
2680
  const next = rules.filter((_, i) => i !== ri);
@@ -2583,7 +2684,7 @@ function PoliciesPanel({ focused }) {
2583
2684
  } else if (input === "m") {
2584
2685
  mutate(rules, mode === "denylist" ? "whitelist" : "denylist");
2585
2686
  } else if (input === "n") {
2586
- const nr = { id: `rule-${Date.now()}`, description: "", effect: "DENY", priority: 100, toolPattern: "*", minimumTrustLevel: "UNTRUSTED", enabled: true };
2687
+ const nr = { id: `rule-${Date.now()}`, description: "", effect: "DENY", priority: 100, toolPattern: "*", minimumTrustLevel: "UNTRUSTED", enabled: true, commandConstraints: { denied: [] } };
2587
2688
  mutate([nr, ...rules]);
2588
2689
  setRi(0);
2589
2690
  setFi(0);
@@ -2604,13 +2705,28 @@ function PoliciesPanel({ focused }) {
2604
2705
  else if (key.upArrow) setFi((n) => Math.max(0, n - 1));
2605
2706
  else if (key.downArrow) setFi((n) => Math.min(FIELDS.length - 1, n + 1));
2606
2707
  else if (field.kind === "effect" && (input === " " || key.rightArrow)) {
2607
- mutate(rules.map((r, i) => i === ri ? { ...r, effect: r.effect === "ALLOW" ? "DENY" : "ALLOW" } : r));
2708
+ mutate(rules.map((r, i) => i === ri ? migrateEffect(r, r.effect === "ALLOW" ? "DENY" : "ALLOW") : r));
2608
2709
  } else if (field.kind === "enabled" && (input === " " || key.rightArrow)) {
2609
2710
  mutate(rules.map((r, i) => i === ri ? { ...r, enabled: !r.enabled } : r));
2711
+ } else if (field.kind === "ctype" && (input === " " || key.rightArrow || key.leftArrow)) {
2712
+ const dir = key.leftArrow ? -1 : 1;
2713
+ const nextT = CTYPES[(CTYPES.indexOf(currentCType(rule2)) + dir + CTYPES.length) % CTYPES.length];
2714
+ mutate(rules.map((r, i) => i === ri ? setCType(r, nextT) : r));
2715
+ } else if (field.kind === "perms" && key.leftArrow) {
2716
+ setPermCursor((n) => (n + PERMS.length - 1) % PERMS.length);
2717
+ } else if (field.kind === "perms" && key.rightArrow) {
2718
+ setPermCursor((n) => (n + 1) % PERMS.length);
2719
+ } else if (field.kind === "perms" && input === " ") {
2720
+ const p = PERMS[permCursor % PERMS.length];
2721
+ mutate(rules.map((rr, i) => i === ri ? togglePerm(rr, p) : rr));
2722
+ } else if (field.kind === "match" && input === "[") {
2723
+ mutate(rules.map((rr, i) => i === ri ? toggleStar(rr, "left") : rr));
2724
+ } else if (field.kind === "match" && input === "]") {
2725
+ mutate(rules.map((rr, i) => i === ri ? toggleStar(rr, "right") : rr));
2610
2726
  } else if (field.kind === "priority" && (key.rightArrow || key.leftArrow)) {
2611
2727
  const d = key.rightArrow ? 1 : -1;
2612
2728
  mutate(rules.map((r, i) => i === ri ? { ...r, priority: Math.max(0, r.priority + d) } : r));
2613
- } else if (field.kind === "text" && key.return) {
2729
+ } else if ((field.kind === "text" || field.kind === "match") && key.return) {
2614
2730
  setEditVal(field.get(rule2));
2615
2731
  setEditing(true);
2616
2732
  } else if (input === "s") void save();
@@ -2636,7 +2752,7 @@ function PoliciesPanel({ focused }) {
2636
2752
  /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: activeBy ? ` (${activeBy})` : "" })
2637
2753
  ] }) : /* @__PURE__ */ jsx4(Text4, { color: theme.bad, bold: true, children: "\u25CB NONE \u2014 nothing enforced, grants refused" })
2638
2754
  ] }),
2639
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, wrap: "truncate", children: focused ? `\u2191\u2193 select \xB7 enter open \xB7 n new policy \xB7 a activate \xB7 x deactivate \xB7 ^R refresh${lAbove ? ` \xB7 \u25B2${lAbove}` : ""}${lBelow ? ` \xB7 \u25BC${lBelow}` : ""}` : "press \u2192 to browse" }),
2755
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, wrap: "truncate", children: focused ? `\u2191\u2193 select \xB7 enter open \xB7 n new \xB7 a activate \xB7 x deactivate \xB7 d delete \xB7 ^R refresh${lAbove ? ` \xB7 \u25B2${lAbove}` : ""}${lBelow ? ` \xB7 \u25BC${lBelow}` : ""}` : "press \u2192 to browse" }),
2640
2756
  creating !== null ? /* @__PURE__ */ jsxs4(Box4, { children: [
2641
2757
  /* @__PURE__ */ jsx4(Text4, { color: theme.warn, children: "new policy name: " }),
2642
2758
  /* @__PURE__ */ jsx4(TextInput3, { value: creating, onChange: setCreating, onSubmit: commitNewPolicy })
@@ -2711,12 +2827,53 @@ function PoliciesPanel({ focused }) {
2711
2827
  /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: rule?.id ?? "" }),
2712
2828
  dirtyTag
2713
2829
  ] }),
2714
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "\u2191\u2193 field \xB7 enter edit text \xB7 space/\u2192 toggle \xB7 s save \xB7 \u2190 back" }),
2830
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "\u2191\u2193 field \xB7 enter edit \xB7 space toggle \xB7 \u2190\u2192 move/pick \xB7 [ ] wildcards \xB7 s save \xB7 \u2190 back" }),
2715
2831
  /* @__PURE__ */ jsx4(Box4, { marginTop: 1, flexDirection: "column", children: FIELDS.map((f, i) => {
2716
- const val = rule ? f.get(rule) : "";
2717
2832
  const active2 = i === fi;
2833
+ const label = /* @__PURE__ */ jsx4(Text4, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + f.label.padEnd(13) });
2834
+ if (f.kind === "perms" && rule) {
2835
+ return /* @__PURE__ */ jsxs4(Box4, { children: [
2836
+ label,
2837
+ PERMS.map((p, pi2) => {
2838
+ const on = permList(rule).includes(p);
2839
+ const cur = active2 && pi2 === permCursor % PERMS.length;
2840
+ return /* @__PURE__ */ jsxs4(Text4, { color: on ? theme.ok : theme.bad, inverse: cur, children: [
2841
+ p,
2842
+ pi2 < PERMS.length - 1 ? " " : ""
2843
+ ] }, p);
2844
+ })
2845
+ ] }, f.label);
2846
+ }
2847
+ if (f.kind === "match" && rule) {
2848
+ if (active2 && editing) {
2849
+ return /* @__PURE__ */ jsxs4(Box4, { children: [
2850
+ label,
2851
+ /* @__PURE__ */ jsx4(
2852
+ TextInput3,
2853
+ {
2854
+ value: editVal,
2855
+ onChange: setEditVal,
2856
+ onSubmit: (v2) => {
2857
+ if (f.set) mutate(rules.map((r, idx) => idx === ri ? f.set(r, v2) : r));
2858
+ setEditing(false);
2859
+ }
2860
+ }
2861
+ )
2862
+ ] }, f.label);
2863
+ }
2864
+ const v = f.get(rule);
2865
+ const lOn = starActive(rule, "left");
2866
+ const rOn = starActive(rule, "right");
2867
+ return /* @__PURE__ */ jsxs4(Box4, { children: [
2868
+ label,
2869
+ /* @__PURE__ */ jsx4(Text4, { color: lOn ? theme.ok : theme.dim, children: lOn ? "\u2731 " : "\xB7 " }),
2870
+ /* @__PURE__ */ jsx4(Text4, { dimColor: !v, children: v || "\u2014" }),
2871
+ /* @__PURE__ */ jsx4(Text4, { color: rOn ? theme.ok : theme.dim, children: rOn ? " \u2731" : " \xB7" })
2872
+ ] }, f.label);
2873
+ }
2874
+ const val = rule ? f.get(rule) : "";
2718
2875
  return /* @__PURE__ */ jsxs4(Box4, { children: [
2719
- /* @__PURE__ */ jsx4(Text4, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + f.label.padEnd(13) }),
2876
+ label,
2720
2877
  active2 && editing && f.kind === "text" ? /* @__PURE__ */ jsx4(
2721
2878
  TextInput3,
2722
2879
  {
@@ -2737,6 +2894,7 @@ function PoliciesPanel({ focused }) {
2737
2894
  )
2738
2895
  ] }, f.label);
2739
2896
  }) }),
2897
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: " * = wildcard (matches any run of chars). On Match: [ toggles a leading *, ] a trailing * \xB7 e.g. *ping* matches any command containing ping" }),
2740
2898
  status ? /* @__PURE__ */ jsx4(Text4, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
2741
2899
  ] });
2742
2900
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.82.48",
3
+ "version": "0.82.50",
4
4
  "description": "AI tool security proxy: protect any AI tool server with customizable policies, path/command constraints, rate limiting, and audit logging. No code changes required.",
5
5
  "type": "module",
6
6
  "bin": {