@kehto/runtime 0.8.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/index.d.ts +291 -88
- package/dist/index.js +232 -49
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { resolveCapabilitiesNub } from "@kehto/acl";
|
|
|
4
4
|
var CLASS_CAPABILITY_ALLOWLIST = Object.freeze({
|
|
5
5
|
"class-1": new Set(ALL_CAPABILITIES),
|
|
6
6
|
"class-2": new Set(ALL_CAPABILITIES.filter(
|
|
7
|
-
(c) => c !== "relay:write" && c !== "outbox:write"
|
|
7
|
+
(c) => c !== "relay:write" && c !== "outbox:write" && c !== "intent:write"
|
|
8
8
|
))
|
|
9
9
|
});
|
|
10
10
|
function createEnforceGate(config) {
|
|
@@ -158,6 +158,8 @@ var CAP_CVM_CALL = 1 << 17;
|
|
|
158
158
|
var CAP_OUTBOX_READ = 1 << 18;
|
|
159
159
|
var CAP_OUTBOX_WRITE = 1 << 19;
|
|
160
160
|
var CAP_UPLOAD_WRITE = 1 << 20;
|
|
161
|
+
var CAP_INTENT_READ = 1 << 21;
|
|
162
|
+
var CAP_INTENT_WRITE = 1 << 22;
|
|
161
163
|
var CAP_MAP = {
|
|
162
164
|
"relay:read": CAP_RELAY_READ,
|
|
163
165
|
"relay:write": CAP_RELAY_WRITE,
|
|
@@ -178,7 +180,9 @@ var CAP_MAP = {
|
|
|
178
180
|
"cvm:call": CAP_CVM_CALL,
|
|
179
181
|
"outbox:read": CAP_OUTBOX_READ,
|
|
180
182
|
"outbox:write": CAP_OUTBOX_WRITE,
|
|
181
|
-
"upload:write": CAP_UPLOAD_WRITE
|
|
183
|
+
"upload:write": CAP_UPLOAD_WRITE,
|
|
184
|
+
"intent:read": CAP_INTENT_READ,
|
|
185
|
+
"intent:write": CAP_INTENT_WRITE
|
|
182
186
|
};
|
|
183
187
|
var RUNTIME_CAP_ALL = Object.values(CAP_MAP).reduce((bits, bit) => bits | bit, 0);
|
|
184
188
|
function capToBit(cap) {
|
|
@@ -281,6 +285,68 @@ function createAclState(persistence, defaultPolicy = "permissive") {
|
|
|
281
285
|
};
|
|
282
286
|
}
|
|
283
287
|
|
|
288
|
+
// src/firewall-state.ts
|
|
289
|
+
import {
|
|
290
|
+
evaluate,
|
|
291
|
+
defaultConfig,
|
|
292
|
+
createState as createState2,
|
|
293
|
+
serialize as serialize2,
|
|
294
|
+
deserialize as deserialize2,
|
|
295
|
+
setPolicy,
|
|
296
|
+
setRateLimit,
|
|
297
|
+
setGlobalRate,
|
|
298
|
+
addMatcher
|
|
299
|
+
} from "@kehto/firewall";
|
|
300
|
+
function createFirewallState(persistence) {
|
|
301
|
+
let config = defaultConfig();
|
|
302
|
+
let counters = createState2();
|
|
303
|
+
return {
|
|
304
|
+
evaluate(observation) {
|
|
305
|
+
const result = evaluate(config, counters, observation);
|
|
306
|
+
counters = result.newState;
|
|
307
|
+
return result;
|
|
308
|
+
},
|
|
309
|
+
setPolicy(napplet, policy) {
|
|
310
|
+
config = setPolicy(config, napplet, policy);
|
|
311
|
+
},
|
|
312
|
+
setRateLimit(napplet, opClass, limit) {
|
|
313
|
+
config = setRateLimit(config, napplet, opClass, limit);
|
|
314
|
+
},
|
|
315
|
+
setGlobalRate(napplet, limit) {
|
|
316
|
+
config = setGlobalRate(config, napplet, limit);
|
|
317
|
+
},
|
|
318
|
+
addMatcher(matcher) {
|
|
319
|
+
config = addMatcher(config, matcher);
|
|
320
|
+
},
|
|
321
|
+
getConfig() {
|
|
322
|
+
return config;
|
|
323
|
+
},
|
|
324
|
+
persist() {
|
|
325
|
+
try {
|
|
326
|
+
persistence?.persist(serialize2(config));
|
|
327
|
+
} catch {
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
load() {
|
|
331
|
+
try {
|
|
332
|
+
const raw = persistence?.load() ?? null;
|
|
333
|
+
if (!raw) return;
|
|
334
|
+
config = deserialize2(raw);
|
|
335
|
+
} catch {
|
|
336
|
+
config = defaultConfig();
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
clear() {
|
|
340
|
+
config = defaultConfig();
|
|
341
|
+
counters = createState2();
|
|
342
|
+
try {
|
|
343
|
+
persistence?.persist("");
|
|
344
|
+
} catch {
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
284
350
|
// src/manifest-cache.ts
|
|
285
351
|
function createManifestCache(persistence) {
|
|
286
352
|
const cache = /* @__PURE__ */ new Map();
|
|
@@ -776,7 +842,8 @@ function createIfcRuntime(hooks, sessionRegistry) {
|
|
|
776
842
|
const state = {
|
|
777
843
|
subscriptions: /* @__PURE__ */ new Map(),
|
|
778
844
|
channels: /* @__PURE__ */ new Map(),
|
|
779
|
-
channelsByWindow: /* @__PURE__ */ new Map()
|
|
845
|
+
channelsByWindow: /* @__PURE__ */ new Map(),
|
|
846
|
+
domainByWindow: /* @__PURE__ */ new Map()
|
|
780
847
|
};
|
|
781
848
|
return {
|
|
782
849
|
handleMessage(windowId, msg) {
|
|
@@ -785,14 +852,24 @@ function createIfcRuntime(hooks, sessionRegistry) {
|
|
|
785
852
|
destroyWindow(windowId) {
|
|
786
853
|
removeWindowChannels(state, hooks, windowId);
|
|
787
854
|
removeWindowSubscriptions(state, windowId);
|
|
855
|
+
state.domainByWindow.delete(windowId);
|
|
788
856
|
},
|
|
789
857
|
clear() {
|
|
790
858
|
state.subscriptions.clear();
|
|
791
859
|
state.channels.clear();
|
|
792
860
|
state.channelsByWindow.clear();
|
|
861
|
+
state.domainByWindow.clear();
|
|
793
862
|
}
|
|
794
863
|
};
|
|
795
864
|
}
|
|
865
|
+
function domainOf(type) {
|
|
866
|
+
const dot = type.indexOf(".");
|
|
867
|
+
const d = dot === -1 ? type : type.slice(0, dot);
|
|
868
|
+
return d === "inc" ? "inc" : "ifc";
|
|
869
|
+
}
|
|
870
|
+
function prefixFor(state, windowId, fallback) {
|
|
871
|
+
return state.domainByWindow.get(windowId) ?? fallback;
|
|
872
|
+
}
|
|
796
873
|
function addChannel(state, channelId, peerA, peerB) {
|
|
797
874
|
state.channels.set(channelId, { channelId, peerA, peerB });
|
|
798
875
|
for (const windowId of [peerA, peerB]) {
|
|
@@ -832,51 +909,56 @@ function handleIfcMessage(state, hooks, sessionRegistry, windowId, msg) {
|
|
|
832
909
|
const m = msg;
|
|
833
910
|
const dotIdx = msg.type.indexOf(".");
|
|
834
911
|
const action = msg.type.slice(dotIdx + 1);
|
|
912
|
+
const incomingDomain = domainOf(msg.type);
|
|
913
|
+
if (!state.domainByWindow.has(windowId)) {
|
|
914
|
+
state.domainByWindow.set(windowId, incomingDomain);
|
|
915
|
+
}
|
|
835
916
|
switch (action) {
|
|
836
917
|
case "emit":
|
|
837
|
-
handleEmit(state, hooks, windowId, m);
|
|
918
|
+
handleEmit(state, hooks, windowId, m, incomingDomain);
|
|
838
919
|
return;
|
|
839
920
|
case "subscribe":
|
|
840
|
-
handleSubscribe(state, hooks, windowId, m);
|
|
921
|
+
handleSubscribe(state, hooks, windowId, m, incomingDomain);
|
|
841
922
|
return;
|
|
842
923
|
case "unsubscribe":
|
|
843
924
|
handleUnsubscribe(state, windowId, m);
|
|
844
925
|
return;
|
|
845
926
|
case "channel.open":
|
|
846
|
-
handleChannelOpen(state, hooks, sessionRegistry, windowId, m);
|
|
927
|
+
handleChannelOpen(state, hooks, sessionRegistry, windowId, m, incomingDomain);
|
|
847
928
|
return;
|
|
848
929
|
case "channel.emit":
|
|
849
|
-
handleChannelEmit(state, hooks, windowId, m);
|
|
930
|
+
handleChannelEmit(state, hooks, windowId, m, incomingDomain);
|
|
850
931
|
return;
|
|
851
932
|
case "channel.broadcast":
|
|
852
|
-
handleChannelBroadcast(state, hooks, windowId, m);
|
|
933
|
+
handleChannelBroadcast(state, hooks, windowId, m, incomingDomain);
|
|
853
934
|
return;
|
|
854
935
|
case "channel.list":
|
|
855
|
-
handleChannelList(state, hooks, windowId, m);
|
|
936
|
+
handleChannelList(state, hooks, windowId, m, incomingDomain);
|
|
856
937
|
return;
|
|
857
938
|
case "channel.close":
|
|
858
|
-
handleChannelClose(state, hooks, windowId, m);
|
|
939
|
+
handleChannelClose(state, hooks, windowId, m, incomingDomain);
|
|
859
940
|
return;
|
|
860
941
|
default:
|
|
861
942
|
return;
|
|
862
943
|
}
|
|
863
944
|
}
|
|
864
|
-
function handleEmit(state, hooks, windowId, m) {
|
|
945
|
+
function handleEmit(state, hooks, windowId, m, senderDomain) {
|
|
865
946
|
const topic = m.topic ?? "";
|
|
866
947
|
if (!topic) return;
|
|
867
948
|
const subscribers = state.subscriptions.get(topic);
|
|
868
949
|
if (!subscribers) return;
|
|
869
950
|
for (const subscriberWindowId of subscribers) {
|
|
870
951
|
if (subscriberWindowId !== windowId) {
|
|
871
|
-
|
|
952
|
+
const prefix = prefixFor(state, subscriberWindowId, senderDomain);
|
|
953
|
+
hooks.sendToNapplet(subscriberWindowId, { type: `${prefix}.event`, topic, payload: m.payload, sender: windowId });
|
|
872
954
|
}
|
|
873
955
|
}
|
|
874
956
|
}
|
|
875
|
-
function handleSubscribe(state, hooks, windowId, m) {
|
|
957
|
+
function handleSubscribe(state, hooks, windowId, m, incomingDomain) {
|
|
876
958
|
const id = m.id ?? "";
|
|
877
959
|
const topic = m.topic ?? "";
|
|
878
960
|
if (!topic) {
|
|
879
|
-
hooks.sendToNapplet(windowId, { type:
|
|
961
|
+
hooks.sendToNapplet(windowId, { type: `${incomingDomain}.subscribe.result`, id, error: "missing topic" });
|
|
880
962
|
return;
|
|
881
963
|
}
|
|
882
964
|
let subscriptions = state.subscriptions.get(topic);
|
|
@@ -885,7 +967,7 @@ function handleSubscribe(state, hooks, windowId, m) {
|
|
|
885
967
|
state.subscriptions.set(topic, subscriptions);
|
|
886
968
|
}
|
|
887
969
|
subscriptions.add(windowId);
|
|
888
|
-
hooks.sendToNapplet(windowId, { type:
|
|
970
|
+
hooks.sendToNapplet(windowId, { type: `${incomingDomain}.subscribe.result`, id });
|
|
889
971
|
}
|
|
890
972
|
function handleUnsubscribe(state, windowId, m) {
|
|
891
973
|
const topic = m.topic ?? "";
|
|
@@ -895,30 +977,36 @@ function handleUnsubscribe(state, windowId, m) {
|
|
|
895
977
|
subscriptions.delete(windowId);
|
|
896
978
|
if (subscriptions.size === 0) state.subscriptions.delete(topic);
|
|
897
979
|
}
|
|
898
|
-
function handleChannelOpen(state, hooks, sessionRegistry, windowId, m) {
|
|
980
|
+
function handleChannelOpen(state, hooks, sessionRegistry, windowId, m, incomingDomain) {
|
|
899
981
|
const id = m.id ?? "";
|
|
900
982
|
const peerWindow = resolveTarget(sessionRegistry, m.target ?? "");
|
|
901
983
|
if (!peerWindow) {
|
|
902
|
-
hooks.sendToNapplet(windowId, { type:
|
|
984
|
+
hooks.sendToNapplet(windowId, { type: `${incomingDomain}.channel.open.result`, id, error: "target not found" });
|
|
903
985
|
return;
|
|
904
986
|
}
|
|
905
987
|
const channelId = hooks.crypto.randomUUID().replace(/-/g, "").slice(0, 32);
|
|
906
988
|
addChannel(state, channelId, windowId, peerWindow);
|
|
907
|
-
hooks.sendToNapplet(windowId, { type:
|
|
989
|
+
hooks.sendToNapplet(windowId, { type: `${incomingDomain}.channel.open.result`, id, channelId, peer: peerWindow });
|
|
908
990
|
}
|
|
909
|
-
function handleChannelEmit(state, hooks, windowId, m) {
|
|
991
|
+
function handleChannelEmit(state, hooks, windowId, m, senderDomain) {
|
|
910
992
|
const peer = peerOf(state, m.channelId ?? "", windowId);
|
|
911
|
-
if (peer)
|
|
993
|
+
if (peer) {
|
|
994
|
+
const prefix = prefixFor(state, peer, senderDomain);
|
|
995
|
+
hooks.sendToNapplet(peer, { type: `${prefix}.channel.event`, channelId: m.channelId ?? "", sender: windowId, payload: m.payload });
|
|
996
|
+
}
|
|
912
997
|
}
|
|
913
|
-
function handleChannelBroadcast(state, hooks, windowId, m) {
|
|
998
|
+
function handleChannelBroadcast(state, hooks, windowId, m, senderDomain) {
|
|
914
999
|
const channels = state.channelsByWindow.get(windowId);
|
|
915
1000
|
if (!channels) return;
|
|
916
1001
|
for (const channelId of channels) {
|
|
917
1002
|
const peer = peerOf(state, channelId, windowId);
|
|
918
|
-
if (peer)
|
|
1003
|
+
if (peer) {
|
|
1004
|
+
const prefix = prefixFor(state, peer, senderDomain);
|
|
1005
|
+
hooks.sendToNapplet(peer, { type: `${prefix}.channel.event`, channelId, sender: windowId, payload: m.payload });
|
|
1006
|
+
}
|
|
919
1007
|
}
|
|
920
1008
|
}
|
|
921
|
-
function handleChannelList(state, hooks, windowId, m) {
|
|
1009
|
+
function handleChannelList(state, hooks, windowId, m, incomingDomain) {
|
|
922
1010
|
const channels = [];
|
|
923
1011
|
const set = state.channelsByWindow.get(windowId);
|
|
924
1012
|
if (set) {
|
|
@@ -927,14 +1015,15 @@ function handleChannelList(state, hooks, windowId, m) {
|
|
|
927
1015
|
if (peer) channels.push({ id: channelId, peer });
|
|
928
1016
|
}
|
|
929
1017
|
}
|
|
930
|
-
hooks.sendToNapplet(windowId, { type:
|
|
1018
|
+
hooks.sendToNapplet(windowId, { type: `${incomingDomain}.channel.list.result`, id: m.id ?? "", channels });
|
|
931
1019
|
}
|
|
932
|
-
function handleChannelClose(state, hooks, windowId, m) {
|
|
1020
|
+
function handleChannelClose(state, hooks, windowId, m, closerDomain) {
|
|
933
1021
|
const channelId = m.channelId ?? "";
|
|
934
1022
|
const peer = peerOf(state, channelId, windowId);
|
|
935
1023
|
if (!peer) return;
|
|
936
|
-
hooks.sendToNapplet(windowId, { type:
|
|
937
|
-
|
|
1024
|
+
hooks.sendToNapplet(windowId, { type: `${closerDomain}.channel.closed`, channelId });
|
|
1025
|
+
const peerPrefix = prefixFor(state, peer, closerDomain);
|
|
1026
|
+
hooks.sendToNapplet(peer, { type: `${peerPrefix}.channel.closed`, channelId });
|
|
938
1027
|
removeChannel(state, channelId);
|
|
939
1028
|
}
|
|
940
1029
|
function removeWindowSubscriptions(state, windowId) {
|
|
@@ -948,7 +1037,11 @@ function removeWindowChannels(state, hooks, windowId) {
|
|
|
948
1037
|
if (!channelIds) return;
|
|
949
1038
|
for (const channelId of Array.from(channelIds)) {
|
|
950
1039
|
const peer = peerOf(state, channelId, windowId);
|
|
951
|
-
if (peer)
|
|
1040
|
+
if (peer) {
|
|
1041
|
+
const destroyeeDomain = prefixFor(state, windowId, "ifc");
|
|
1042
|
+
const peerPrefix = prefixFor(state, peer, destroyeeDomain);
|
|
1043
|
+
hooks.sendToNapplet(peer, { type: `${peerPrefix}.channel.closed`, channelId });
|
|
1044
|
+
}
|
|
952
1045
|
removeChannel(state, channelId);
|
|
953
1046
|
}
|
|
954
1047
|
}
|
|
@@ -1041,7 +1134,7 @@ function handleStorageNub(windowId, msg, sendToNapplet, sessionRegistry, aclStat
|
|
|
1041
1134
|
break;
|
|
1042
1135
|
}
|
|
1043
1136
|
case "clear": {
|
|
1044
|
-
sendErrorNub("storage.clear is not in @napplet/
|
|
1137
|
+
sendErrorNub("storage.clear is not in @napplet/nap/storage; action not supported");
|
|
1045
1138
|
break;
|
|
1046
1139
|
}
|
|
1047
1140
|
case "keys": {
|
|
@@ -1080,7 +1173,8 @@ function createRuntimeDomainHandlers(context) {
|
|
|
1080
1173
|
resource: (windowId, msg) => handleServiceOnlyMessage(context, "resource", windowId, msg),
|
|
1081
1174
|
cvm: (windowId, msg) => handleServiceOnlyMessage(context, "cvm", windowId, msg),
|
|
1082
1175
|
outbox: (windowId, msg) => handleServiceOnlyMessage(context, "outbox", windowId, msg),
|
|
1083
|
-
upload: (windowId, msg) => handleServiceOnlyMessage(context, "upload", windowId, msg)
|
|
1176
|
+
upload: (windowId, msg) => handleServiceOnlyMessage(context, "upload", windowId, msg),
|
|
1177
|
+
intent: (windowId, msg) => handleServiceOnlyMessage(context, "intent", windowId, msg)
|
|
1084
1178
|
};
|
|
1085
1179
|
}
|
|
1086
1180
|
function handleStorageMessage(context, windowId, msg) {
|
|
@@ -1209,19 +1303,21 @@ function createNubEnvelopeDispatcher(handlers) {
|
|
|
1209
1303
|
const adapt = (handler) => (msg) => {
|
|
1210
1304
|
if (currentWindowId !== null) handler(currentWindowId, msg);
|
|
1211
1305
|
};
|
|
1212
|
-
nubDispatch.
|
|
1213
|
-
nubDispatch.
|
|
1214
|
-
nubDispatch.
|
|
1215
|
-
nubDispatch.
|
|
1216
|
-
nubDispatch.
|
|
1217
|
-
nubDispatch.
|
|
1218
|
-
nubDispatch.
|
|
1219
|
-
nubDispatch.
|
|
1220
|
-
nubDispatch.
|
|
1221
|
-
nubDispatch.
|
|
1222
|
-
nubDispatch.
|
|
1223
|
-
nubDispatch.
|
|
1224
|
-
nubDispatch.
|
|
1306
|
+
nubDispatch.registerNap("relay", adapt(handlers.relay));
|
|
1307
|
+
nubDispatch.registerNap("identity", adapt(handlers.identity));
|
|
1308
|
+
nubDispatch.registerNap("keys", adapt(handlers.keys));
|
|
1309
|
+
nubDispatch.registerNap("media", adapt(handlers.media));
|
|
1310
|
+
nubDispatch.registerNap("notify", adapt(handlers.notify));
|
|
1311
|
+
nubDispatch.registerNap("storage", adapt(handlers.storage));
|
|
1312
|
+
nubDispatch.registerNap("ifc", adapt(handlers.ifc));
|
|
1313
|
+
nubDispatch.registerNap("inc", adapt(handlers.ifc));
|
|
1314
|
+
nubDispatch.registerNap("theme", adapt(handlers.theme));
|
|
1315
|
+
nubDispatch.registerNap("config", adapt(handlers.config));
|
|
1316
|
+
nubDispatch.registerNap("resource", adapt(handlers.resource));
|
|
1317
|
+
nubDispatch.registerNap("cvm", adapt(handlers.cvm));
|
|
1318
|
+
nubDispatch.registerNap("outbox", adapt(handlers.outbox));
|
|
1319
|
+
nubDispatch.registerNap("upload", adapt(handlers.upload));
|
|
1320
|
+
nubDispatch.registerNap("intent", adapt(handlers.intent));
|
|
1225
1321
|
return (windowId, envelope) => {
|
|
1226
1322
|
currentWindowId = windowId;
|
|
1227
1323
|
try {
|
|
@@ -1231,7 +1327,67 @@ function createNubEnvelopeDispatcher(handlers) {
|
|
|
1231
1327
|
}
|
|
1232
1328
|
};
|
|
1233
1329
|
}
|
|
1234
|
-
function
|
|
1330
|
+
function utf8ByteLength(str) {
|
|
1331
|
+
let bytes = 0;
|
|
1332
|
+
for (let i = 0; i < str.length; i++) {
|
|
1333
|
+
const c = str.charCodeAt(i);
|
|
1334
|
+
if (c < 128) bytes += 1;
|
|
1335
|
+
else if (c < 2048) bytes += 2;
|
|
1336
|
+
else if (c < 55296 || c >= 57344) bytes += 3;
|
|
1337
|
+
else {
|
|
1338
|
+
i++;
|
|
1339
|
+
bytes += 4;
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
return bytes;
|
|
1343
|
+
}
|
|
1344
|
+
function extractKindSize(envelope) {
|
|
1345
|
+
const ev = envelope.event;
|
|
1346
|
+
if (typeof ev !== "object" || ev === null) return {};
|
|
1347
|
+
const kind = typeof ev.kind === "number" ? ev.kind : void 0;
|
|
1348
|
+
let size;
|
|
1349
|
+
try {
|
|
1350
|
+
size = utf8ByteLength(JSON.stringify(ev));
|
|
1351
|
+
} catch {
|
|
1352
|
+
}
|
|
1353
|
+
return { kind, size };
|
|
1354
|
+
}
|
|
1355
|
+
function buildObservation(envelope, windowId, senderCap, sessionRegistry, getFocusContext) {
|
|
1356
|
+
const now = Date.now();
|
|
1357
|
+
const entry = sessionRegistry.getEntryByWindowId(windowId);
|
|
1358
|
+
const napplet = entry?.dTag ?? "";
|
|
1359
|
+
const initElapsedMs = now - (entry?.registeredAt ?? now);
|
|
1360
|
+
const focus = getFocusContext?.(windowId) ?? { focused: true };
|
|
1361
|
+
const opClass = senderCap ?? envelope.type;
|
|
1362
|
+
const { kind, size } = extractKindSize(envelope);
|
|
1363
|
+
return { napplet, opClass, kind, size, initElapsedMs, focused: focus.focused, msSinceFocusGain: focus.msSinceFocusGain, now };
|
|
1364
|
+
}
|
|
1365
|
+
function createFirewallGate(config) {
|
|
1366
|
+
const { firewallState, sessionRegistry, hooks, fireConsent } = config;
|
|
1367
|
+
return function firewallGate(windowId, envelope, senderCap) {
|
|
1368
|
+
const obs = buildObservation(envelope, windowId, senderCap, sessionRegistry, hooks.getFocusContext);
|
|
1369
|
+
const result = firewallState.evaluate(obs);
|
|
1370
|
+
const { decision, action, ruleId, reason } = result;
|
|
1371
|
+
const napplet = obs.napplet;
|
|
1372
|
+
const opClass = obs.opClass;
|
|
1373
|
+
if (decision === "reject" || decision === "prompt") {
|
|
1374
|
+
const id = envelope.id ?? "";
|
|
1375
|
+
const isStorageEnvelope = envelope.type.startsWith("storage.");
|
|
1376
|
+
const type = isStorageEnvelope ? `${envelope.type}.result` : `${envelope.type}.error`;
|
|
1377
|
+
hooks.sendToNapplet(windowId, { type, id, error: `firewall: ${reason}` });
|
|
1378
|
+
hooks.onFirewallEvent?.({ windowId, napplet, opClass, decision, action, ruleId, reason, message: envelope });
|
|
1379
|
+
if (decision === "prompt") {
|
|
1380
|
+
fireConsent(windowId, napplet);
|
|
1381
|
+
}
|
|
1382
|
+
return "drop";
|
|
1383
|
+
}
|
|
1384
|
+
if (action === "flag") {
|
|
1385
|
+
hooks.onFirewallEvent?.({ windowId, napplet, opClass, decision, action, ruleId, reason, message: envelope });
|
|
1386
|
+
}
|
|
1387
|
+
return "dispatch";
|
|
1388
|
+
};
|
|
1389
|
+
}
|
|
1390
|
+
function createMessageHandler(hooks, enforceNub, dispatchNubEnvelope, firewallGate) {
|
|
1235
1391
|
return (windowId, msg) => {
|
|
1236
1392
|
if (typeof msg !== "object" || msg === null || !("type" in msg)) return;
|
|
1237
1393
|
const envelope = msg;
|
|
@@ -1249,6 +1405,8 @@ function createMessageHandler(hooks, enforceNub, dispatchNubEnvelope) {
|
|
|
1249
1405
|
return;
|
|
1250
1406
|
}
|
|
1251
1407
|
}
|
|
1408
|
+
const verdict = firewallGate(windowId, envelope, caps.senderCap);
|
|
1409
|
+
if (verdict === "drop") return;
|
|
1252
1410
|
dispatchNubEnvelope(windowId, envelope);
|
|
1253
1411
|
};
|
|
1254
1412
|
}
|
|
@@ -1267,6 +1425,7 @@ function createInjectedEvent(hooks, topic, payload) {
|
|
|
1267
1425
|
function createRuntimeInstance(context) {
|
|
1268
1426
|
const {
|
|
1269
1427
|
aclState,
|
|
1428
|
+
firewallState,
|
|
1270
1429
|
eventBuffer,
|
|
1271
1430
|
hooks,
|
|
1272
1431
|
ifcRuntime,
|
|
@@ -1275,10 +1434,10 @@ function createRuntimeInstance(context) {
|
|
|
1275
1434
|
replayDetector,
|
|
1276
1435
|
serviceRegistry,
|
|
1277
1436
|
sessionRegistry,
|
|
1278
|
-
subscriptions
|
|
1437
|
+
subscriptions,
|
|
1438
|
+
consentHandlerRef
|
|
1279
1439
|
} = context;
|
|
1280
1440
|
const undeclaredServiceConsents = /* @__PURE__ */ new Set();
|
|
1281
|
-
let consentHandler = null;
|
|
1282
1441
|
return {
|
|
1283
1442
|
handleMessage: context.handleMessage,
|
|
1284
1443
|
injectEvent(topic, payload) {
|
|
@@ -1287,6 +1446,7 @@ function createRuntimeInstance(context) {
|
|
|
1287
1446
|
destroy() {
|
|
1288
1447
|
manifestCache.persist();
|
|
1289
1448
|
aclState.persist();
|
|
1449
|
+
firewallState.persist();
|
|
1290
1450
|
replayDetector.clear();
|
|
1291
1451
|
subscriptions.clear();
|
|
1292
1452
|
ifcRuntime.clear();
|
|
@@ -1295,8 +1455,7 @@ function createRuntimeInstance(context) {
|
|
|
1295
1455
|
undeclaredServiceConsents.clear();
|
|
1296
1456
|
},
|
|
1297
1457
|
registerConsentHandler(handler) {
|
|
1298
|
-
|
|
1299
|
-
void consentHandler;
|
|
1458
|
+
consentHandlerRef.current = handler;
|
|
1300
1459
|
},
|
|
1301
1460
|
registerService(name, handler) {
|
|
1302
1461
|
serviceRegistry[name] = handler;
|
|
@@ -1326,6 +1485,9 @@ function createRuntimeInstance(context) {
|
|
|
1326
1485
|
get aclState() {
|
|
1327
1486
|
return aclState;
|
|
1328
1487
|
},
|
|
1488
|
+
get firewallState() {
|
|
1489
|
+
return firewallState;
|
|
1490
|
+
},
|
|
1329
1491
|
get manifestCache() {
|
|
1330
1492
|
return manifestCache;
|
|
1331
1493
|
}
|
|
@@ -1337,10 +1499,26 @@ function createRuntime(hooks) {
|
|
|
1337
1499
|
const registeredServices = createRegisteredServices(serviceRegistry);
|
|
1338
1500
|
const sessionRegistry = createSessionRegistry(hooks.onPendingUpdate);
|
|
1339
1501
|
const aclState = createAclState(hooks.aclPersistence);
|
|
1502
|
+
const firewallState = createFirewallState(hooks.firewallPersistence);
|
|
1340
1503
|
const manifestCache = createManifestCache(hooks.manifestPersistence);
|
|
1341
1504
|
const replayDetector = createReplayDetector(
|
|
1342
1505
|
hooks.getConfigOverrides ? () => hooks.getConfigOverrides().replayWindowSeconds : void 0
|
|
1343
1506
|
);
|
|
1507
|
+
const consentHandlerRef = { current: null };
|
|
1508
|
+
const fireConsent = (windowId, napplet) => {
|
|
1509
|
+
const handler = consentHandlerRef.current;
|
|
1510
|
+
if (!handler) return;
|
|
1511
|
+
handler({
|
|
1512
|
+
type: "firewall-policy",
|
|
1513
|
+
windowId,
|
|
1514
|
+
napplet,
|
|
1515
|
+
pubkey: "",
|
|
1516
|
+
resolve: (allowed) => {
|
|
1517
|
+
firewallState.setPolicy(napplet, allowed ? "allow" : "deny");
|
|
1518
|
+
firewallState.persist();
|
|
1519
|
+
}
|
|
1520
|
+
});
|
|
1521
|
+
};
|
|
1344
1522
|
const enforce = createEnforceGate({
|
|
1345
1523
|
checkAcl: (pubkey, dTag, aggregateHash, capability) => aclState.check(pubkey, dTag, aggregateHash, capability),
|
|
1346
1524
|
resolveIdentity: (pubkey) => {
|
|
@@ -1357,6 +1535,7 @@ function createRuntime(hooks) {
|
|
|
1357
1535
|
},
|
|
1358
1536
|
onAclCheck: hooks.onAclCheck
|
|
1359
1537
|
});
|
|
1538
|
+
const firewallGate = createFirewallGate({ firewallState, sessionRegistry, hooks, fireConsent });
|
|
1360
1539
|
const eventBuffer = createEventBuffer(
|
|
1361
1540
|
hooks.sendToNapplet,
|
|
1362
1541
|
sessionRegistry,
|
|
@@ -1365,6 +1544,7 @@ function createRuntime(hooks) {
|
|
|
1365
1544
|
hooks.getConfigOverrides ? () => hooks.getConfigOverrides().ringBufferSize ?? RING_BUFFER_SIZE : void 0
|
|
1366
1545
|
);
|
|
1367
1546
|
aclState.load();
|
|
1547
|
+
firewallState.load();
|
|
1368
1548
|
manifestCache.load();
|
|
1369
1549
|
const ifcRuntime = createIfcRuntime(hooks, sessionRegistry);
|
|
1370
1550
|
const domainHandlers = createRuntimeDomainHandlers({ hooks, serviceRegistry, sessionRegistry, aclState });
|
|
@@ -1374,7 +1554,7 @@ function createRuntime(hooks) {
|
|
|
1374
1554
|
ifc: ifcRuntime.handleMessage,
|
|
1375
1555
|
...domainHandlers
|
|
1376
1556
|
});
|
|
1377
|
-
const handleMessage = createMessageHandler(hooks, enforceNub, dispatchNubEnvelope);
|
|
1557
|
+
const handleMessage = createMessageHandler(hooks, enforceNub, dispatchNubEnvelope, firewallGate);
|
|
1378
1558
|
return createRuntimeInstance({
|
|
1379
1559
|
hooks,
|
|
1380
1560
|
serviceRegistry,
|
|
@@ -1385,7 +1565,9 @@ function createRuntime(hooks) {
|
|
|
1385
1565
|
ifcRuntime,
|
|
1386
1566
|
sessionRegistry,
|
|
1387
1567
|
aclState,
|
|
1568
|
+
firewallState,
|
|
1388
1569
|
manifestCache,
|
|
1570
|
+
consentHandlerRef,
|
|
1389
1571
|
handleMessage
|
|
1390
1572
|
});
|
|
1391
1573
|
}
|
|
@@ -1399,6 +1581,7 @@ export {
|
|
|
1399
1581
|
createAclState,
|
|
1400
1582
|
createEnforceGate,
|
|
1401
1583
|
createEventBuffer,
|
|
1584
|
+
createFirewallState,
|
|
1402
1585
|
createManifestCache,
|
|
1403
1586
|
createNappKeyRegistry,
|
|
1404
1587
|
createNubEnforceGate,
|