@agenshield/daemon 0.6.1 → 0.6.2
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/acl.d.ts +15 -12
- package/acl.d.ts.map +1 -1
- package/index.js +18 -18
- package/main.js +18 -18
- package/package.json +4 -4
package/acl.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* macOS ACL utilities for filesystem policies.
|
|
3
3
|
*
|
|
4
|
-
* Uses `chmod +a / -a` to grant/revoke
|
|
4
|
+
* Uses `chmod +a / -a` to grant/revoke user-level ACLs on paths
|
|
5
5
|
* derived from policy patterns. Failures are logged but never thrown.
|
|
6
6
|
*/
|
|
7
7
|
import type { PolicyConfig } from '@agenshield/ipc';
|
|
@@ -21,24 +21,27 @@ export declare function stripGlobToBasePath(pattern: string): string;
|
|
|
21
21
|
*/
|
|
22
22
|
export declare function operationsToAclPerms(operations: string[]): string;
|
|
23
23
|
/**
|
|
24
|
-
* Add a
|
|
24
|
+
* Add a user ACL entry to a path.
|
|
25
25
|
*/
|
|
26
|
-
export declare function
|
|
26
|
+
export declare function addUserAcl(targetPath: string, userName: string, permissions: string, log?: Logger): void;
|
|
27
27
|
/**
|
|
28
|
-
* Remove all ACL entries for a
|
|
28
|
+
* Remove all ACL entries for a user from a path.
|
|
29
29
|
*
|
|
30
|
-
* Reads current ACL entries via `ls -le`, finds entries matching the
|
|
31
|
-
* and removes them by index (highest-first so indices
|
|
30
|
+
* Reads current ACL entries via `ls -le`, finds entries matching the user
|
|
31
|
+
* (both allow and deny), and removes them by index (highest-first so indices
|
|
32
|
+
* stay valid). This ensures a clean slate before reapplying permissions.
|
|
32
33
|
*/
|
|
33
|
-
export declare function
|
|
34
|
+
export declare function removeUserAcl(targetPath: string, userName: string, log?: Logger): void;
|
|
34
35
|
/**
|
|
35
36
|
* Synchronise filesystem policy ACLs after a config change.
|
|
36
37
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
38
|
+
* For every path in the union of old and new ACL maps:
|
|
39
|
+
* 1. Remove all existing user ACLs (clean slate)
|
|
40
|
+
* 2. Reapply permissions if the path is in the new map
|
|
41
|
+
*
|
|
42
|
+
* This "wipe then reapply" strategy avoids stale permission accumulation
|
|
43
|
+
* and the deny+allow conflict where layering ACLs produces wrong results.
|
|
41
44
|
*/
|
|
42
|
-
export declare function syncFilesystemPolicyAcls(oldPolicies: PolicyConfig[], newPolicies: PolicyConfig[],
|
|
45
|
+
export declare function syncFilesystemPolicyAcls(oldPolicies: PolicyConfig[], newPolicies: PolicyConfig[], userName: string, logger?: Logger): void;
|
|
43
46
|
export {};
|
|
44
47
|
//# sourceMappingURL=acl.d.ts.map
|
package/acl.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"acl.d.ts","sourceRoot":"","sources":["../src/acl.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD,UAAU,MAAM;IACd,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;CAC7C;AAoBD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAe3D;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,CASjE;AAED;;GAEG;AACH,wBAAgB,
|
|
1
|
+
{"version":3,"file":"acl.d.ts","sourceRoot":"","sources":["../src/acl.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD,UAAU,MAAM;IACd,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;CAC7C;AAoBD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAe3D;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,CASjE;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,GAAE,MAAa,GAAG,IAAI,CAa9G;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,GAAE,MAAa,GAAG,IAAI,CAqC5F;AAmED;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,YAAY,EAAE,EAC3B,WAAW,EAAE,YAAY,EAAE,EAC3B,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,MAAM,GACd,IAAI,CAsBN"}
|
package/index.js
CHANGED
|
@@ -975,21 +975,21 @@ function operationsToAclPerms(operations) {
|
|
|
975
975
|
}
|
|
976
976
|
return perms.join(",");
|
|
977
977
|
}
|
|
978
|
-
function
|
|
978
|
+
function addUserAcl(targetPath, userName, permissions, log = noop) {
|
|
979
979
|
try {
|
|
980
980
|
if (!fs4.existsSync(targetPath)) {
|
|
981
981
|
log.warn(`[acl] skipping non-existent path: ${targetPath}`);
|
|
982
982
|
return;
|
|
983
983
|
}
|
|
984
984
|
execSync2(
|
|
985
|
-
`sudo chmod +a "
|
|
985
|
+
`sudo chmod +a "user:${userName} allow ${permissions}" "${targetPath}"`,
|
|
986
986
|
{ stdio: "pipe" }
|
|
987
987
|
);
|
|
988
988
|
} catch (err) {
|
|
989
989
|
log.warn(`[acl] failed to add ACL on ${targetPath}: ${err.message}`);
|
|
990
990
|
}
|
|
991
991
|
}
|
|
992
|
-
function
|
|
992
|
+
function removeUserAcl(targetPath, userName, log = noop) {
|
|
993
993
|
try {
|
|
994
994
|
if (!fs4.existsSync(targetPath)) {
|
|
995
995
|
log.warn(`[acl] skipping non-existent path: ${targetPath}`);
|
|
@@ -1001,8 +1001,8 @@ function removeGroupAcl(targetPath, groupName, log = noop) {
|
|
|
1001
1001
|
});
|
|
1002
1002
|
const indices = [];
|
|
1003
1003
|
for (const line of output.split("\n")) {
|
|
1004
|
-
const match = line.match(/^\s*(\d+):\s+
|
|
1005
|
-
if (match && match[2] ===
|
|
1004
|
+
const match = line.match(/^\s*(\d+):\s+user:(\S+)\s+/);
|
|
1005
|
+
if (match && match[2] === userName) {
|
|
1006
1006
|
indices.push(Number(match[1]));
|
|
1007
1007
|
}
|
|
1008
1008
|
}
|
|
@@ -1065,20 +1065,20 @@ function computeAclMap(policies) {
|
|
|
1065
1065
|
}
|
|
1066
1066
|
return aclMap;
|
|
1067
1067
|
}
|
|
1068
|
-
function syncFilesystemPolicyAcls(oldPolicies, newPolicies,
|
|
1068
|
+
function syncFilesystemPolicyAcls(oldPolicies, newPolicies, userName, logger) {
|
|
1069
1069
|
const log = logger ?? noop;
|
|
1070
1070
|
const oldFs = oldPolicies.filter((p) => p.target === "filesystem");
|
|
1071
1071
|
const newFs = newPolicies.filter((p) => p.target === "filesystem");
|
|
1072
1072
|
const oldAclMap = computeAclMap(oldFs);
|
|
1073
1073
|
const newAclMap = computeAclMap(newFs);
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1074
|
+
const allPaths = /* @__PURE__ */ new Set([...oldAclMap.keys(), ...newAclMap.keys()]);
|
|
1075
|
+
for (const targetPath of allPaths) {
|
|
1076
|
+
removeUserAcl(targetPath, userName, log);
|
|
1077
|
+
const newPerms = newAclMap.get(targetPath);
|
|
1078
|
+
if (newPerms) {
|
|
1079
|
+
addUserAcl(targetPath, userName, newPerms, log);
|
|
1077
1080
|
}
|
|
1078
1081
|
}
|
|
1079
|
-
for (const [targetPath, perms] of newAclMap) {
|
|
1080
|
-
addGroupAcl(targetPath, groupName, perms, log);
|
|
1081
|
-
}
|
|
1082
1082
|
}
|
|
1083
1083
|
|
|
1084
1084
|
// libs/shield-daemon/src/routes/config.ts
|
|
@@ -1100,9 +1100,9 @@ async function configRoutes(app) {
|
|
|
1100
1100
|
const updated = updateConfig(request.body);
|
|
1101
1101
|
if (request.body.policies) {
|
|
1102
1102
|
const state = loadState();
|
|
1103
|
-
const
|
|
1104
|
-
if (
|
|
1105
|
-
syncFilesystemPolicyAcls(oldPolicies, updated.policies,
|
|
1103
|
+
const agentUser = state.users.find((u) => u.type === "agent");
|
|
1104
|
+
if (agentUser) {
|
|
1105
|
+
syncFilesystemPolicyAcls(oldPolicies, updated.policies, agentUser.username, app.log);
|
|
1106
1106
|
}
|
|
1107
1107
|
syncCommandPoliciesAndWrappers(updated.policies, state, app.log);
|
|
1108
1108
|
}
|
|
@@ -1125,9 +1125,9 @@ async function configRoutes(app) {
|
|
|
1125
1125
|
try {
|
|
1126
1126
|
const oldConfig = loadConfig();
|
|
1127
1127
|
const state = loadState();
|
|
1128
|
-
const
|
|
1129
|
-
if (
|
|
1130
|
-
syncFilesystemPolicyAcls(oldConfig.policies, [],
|
|
1128
|
+
const agentUser = state.users.find((u) => u.type === "agent");
|
|
1129
|
+
if (agentUser) {
|
|
1130
|
+
syncFilesystemPolicyAcls(oldConfig.policies, [], agentUser.username, app.log);
|
|
1131
1131
|
}
|
|
1132
1132
|
syncCommandPoliciesAndWrappers([], state, app.log);
|
|
1133
1133
|
saveConfig(getDefaultConfig());
|
package/main.js
CHANGED
|
@@ -966,21 +966,21 @@ function operationsToAclPerms(operations) {
|
|
|
966
966
|
}
|
|
967
967
|
return perms.join(",");
|
|
968
968
|
}
|
|
969
|
-
function
|
|
969
|
+
function addUserAcl(targetPath, userName, permissions, log = noop) {
|
|
970
970
|
try {
|
|
971
971
|
if (!fs4.existsSync(targetPath)) {
|
|
972
972
|
log.warn(`[acl] skipping non-existent path: ${targetPath}`);
|
|
973
973
|
return;
|
|
974
974
|
}
|
|
975
975
|
execSync2(
|
|
976
|
-
`sudo chmod +a "
|
|
976
|
+
`sudo chmod +a "user:${userName} allow ${permissions}" "${targetPath}"`,
|
|
977
977
|
{ stdio: "pipe" }
|
|
978
978
|
);
|
|
979
979
|
} catch (err) {
|
|
980
980
|
log.warn(`[acl] failed to add ACL on ${targetPath}: ${err.message}`);
|
|
981
981
|
}
|
|
982
982
|
}
|
|
983
|
-
function
|
|
983
|
+
function removeUserAcl(targetPath, userName, log = noop) {
|
|
984
984
|
try {
|
|
985
985
|
if (!fs4.existsSync(targetPath)) {
|
|
986
986
|
log.warn(`[acl] skipping non-existent path: ${targetPath}`);
|
|
@@ -992,8 +992,8 @@ function removeGroupAcl(targetPath, groupName, log = noop) {
|
|
|
992
992
|
});
|
|
993
993
|
const indices = [];
|
|
994
994
|
for (const line of output.split("\n")) {
|
|
995
|
-
const match = line.match(/^\s*(\d+):\s+
|
|
996
|
-
if (match && match[2] ===
|
|
995
|
+
const match = line.match(/^\s*(\d+):\s+user:(\S+)\s+/);
|
|
996
|
+
if (match && match[2] === userName) {
|
|
997
997
|
indices.push(Number(match[1]));
|
|
998
998
|
}
|
|
999
999
|
}
|
|
@@ -1056,20 +1056,20 @@ function computeAclMap(policies) {
|
|
|
1056
1056
|
}
|
|
1057
1057
|
return aclMap;
|
|
1058
1058
|
}
|
|
1059
|
-
function syncFilesystemPolicyAcls(oldPolicies, newPolicies,
|
|
1059
|
+
function syncFilesystemPolicyAcls(oldPolicies, newPolicies, userName, logger) {
|
|
1060
1060
|
const log = logger ?? noop;
|
|
1061
1061
|
const oldFs = oldPolicies.filter((p) => p.target === "filesystem");
|
|
1062
1062
|
const newFs = newPolicies.filter((p) => p.target === "filesystem");
|
|
1063
1063
|
const oldAclMap = computeAclMap(oldFs);
|
|
1064
1064
|
const newAclMap = computeAclMap(newFs);
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1065
|
+
const allPaths = /* @__PURE__ */ new Set([...oldAclMap.keys(), ...newAclMap.keys()]);
|
|
1066
|
+
for (const targetPath of allPaths) {
|
|
1067
|
+
removeUserAcl(targetPath, userName, log);
|
|
1068
|
+
const newPerms = newAclMap.get(targetPath);
|
|
1069
|
+
if (newPerms) {
|
|
1070
|
+
addUserAcl(targetPath, userName, newPerms, log);
|
|
1068
1071
|
}
|
|
1069
1072
|
}
|
|
1070
|
-
for (const [targetPath, perms] of newAclMap) {
|
|
1071
|
-
addGroupAcl(targetPath, groupName, perms, log);
|
|
1072
|
-
}
|
|
1073
1073
|
}
|
|
1074
1074
|
|
|
1075
1075
|
// libs/shield-daemon/src/routes/config.ts
|
|
@@ -1091,9 +1091,9 @@ async function configRoutes(app) {
|
|
|
1091
1091
|
const updated = updateConfig(request.body);
|
|
1092
1092
|
if (request.body.policies) {
|
|
1093
1093
|
const state = loadState();
|
|
1094
|
-
const
|
|
1095
|
-
if (
|
|
1096
|
-
syncFilesystemPolicyAcls(oldPolicies, updated.policies,
|
|
1094
|
+
const agentUser = state.users.find((u) => u.type === "agent");
|
|
1095
|
+
if (agentUser) {
|
|
1096
|
+
syncFilesystemPolicyAcls(oldPolicies, updated.policies, agentUser.username, app.log);
|
|
1097
1097
|
}
|
|
1098
1098
|
syncCommandPoliciesAndWrappers(updated.policies, state, app.log);
|
|
1099
1099
|
}
|
|
@@ -1116,9 +1116,9 @@ async function configRoutes(app) {
|
|
|
1116
1116
|
try {
|
|
1117
1117
|
const oldConfig = loadConfig();
|
|
1118
1118
|
const state = loadState();
|
|
1119
|
-
const
|
|
1120
|
-
if (
|
|
1121
|
-
syncFilesystemPolicyAcls(oldConfig.policies, [],
|
|
1119
|
+
const agentUser = state.users.find((u) => u.type === "agent");
|
|
1120
|
+
if (agentUser) {
|
|
1121
|
+
syncFilesystemPolicyAcls(oldConfig.policies, [], agentUser.username, app.log);
|
|
1122
1122
|
}
|
|
1123
1123
|
syncCommandPoliciesAndWrappers([], state, app.log);
|
|
1124
1124
|
saveConfig(getDefaultConfig());
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agenshield/daemon",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AgenShield HTTP daemon server with embedded UI",
|
|
6
6
|
"main": "./index.js",
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
],
|
|
25
25
|
"license": "MIT",
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@agenshield/ipc": "0.6.
|
|
28
|
-
"@agenshield/broker": "0.6.
|
|
29
|
-
"@agenshield/sandbox": "0.6.
|
|
27
|
+
"@agenshield/ipc": "0.6.2",
|
|
28
|
+
"@agenshield/broker": "0.6.2",
|
|
29
|
+
"@agenshield/sandbox": "0.6.2",
|
|
30
30
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
31
31
|
"fastify": "^5.7.0",
|
|
32
32
|
"zod": "^4.3.6",
|