@astrasyncai/verification-gateway 5.9.0 → 5.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.
Files changed (79) hide show
  1. package/dist/adapters/express.js +19 -1
  2. package/dist/adapters/express.js.map +1 -1
  3. package/dist/adapters/express.mjs +19 -1
  4. package/dist/adapters/express.mjs.map +1 -1
  5. package/dist/adapters/mcp.js +19 -1
  6. package/dist/adapters/mcp.js.map +1 -1
  7. package/dist/adapters/mcp.mjs +19 -1
  8. package/dist/adapters/mcp.mjs.map +1 -1
  9. package/dist/adapters/nextjs.js +19 -1
  10. package/dist/adapters/nextjs.js.map +1 -1
  11. package/dist/adapters/nextjs.mjs +19 -1
  12. package/dist/adapters/nextjs.mjs.map +1 -1
  13. package/dist/adapters/sdk.js +19 -1
  14. package/dist/adapters/sdk.js.map +1 -1
  15. package/dist/adapters/sdk.mjs +19 -1
  16. package/dist/adapters/sdk.mjs.map +1 -1
  17. package/dist/agent/index.js +1 -1
  18. package/dist/agent/index.js.map +1 -1
  19. package/dist/agent/index.mjs +1 -1
  20. package/dist/agent/index.mjs.map +1 -1
  21. package/dist/bin/astrasync-claude-hook.js +540 -10
  22. package/dist/bin/astrasync-codex-hook.js +540 -10
  23. package/dist/bin/astrasync-guard.js +629 -65
  24. package/dist/bin/astrasync.js +673 -66
  25. package/dist/browser/background.js +19 -1
  26. package/dist/browser/background.js.map +1 -1
  27. package/dist/browser/background.mjs +19 -1
  28. package/dist/browser/background.mjs.map +1 -1
  29. package/dist/cli/index.d.mts +8 -8
  30. package/dist/cli/index.d.ts +8 -8
  31. package/dist/cli/index.js +22 -64
  32. package/dist/cli/index.js.map +1 -1
  33. package/dist/cli/index.mjs +22 -64
  34. package/dist/cli/index.mjs.map +1 -1
  35. package/dist/codex/index.d.mts +5 -0
  36. package/dist/codex/index.d.ts +5 -0
  37. package/dist/codex/index.js +556 -18
  38. package/dist/codex/index.js.map +1 -1
  39. package/dist/codex/index.mjs +558 -20
  40. package/dist/codex/index.mjs.map +1 -1
  41. package/dist/cursor/extension.d.mts +9 -2
  42. package/dist/cursor/extension.d.ts +9 -2
  43. package/dist/cursor/extension.js +522 -5
  44. package/dist/cursor/extension.js.map +1 -1
  45. package/dist/cursor/extension.mjs +524 -5
  46. package/dist/cursor/extension.mjs.map +1 -1
  47. package/dist/edge-config.js +1 -1
  48. package/dist/edge-config.js.map +1 -1
  49. package/dist/edge-config.mjs +1 -1
  50. package/dist/edge-config.mjs.map +1 -1
  51. package/dist/edge-core/index.js +20 -2
  52. package/dist/edge-core/index.js.map +1 -1
  53. package/dist/edge-core/index.mjs +20 -2
  54. package/dist/edge-core/index.mjs.map +1 -1
  55. package/dist/enrolment-store-8DyaIOLe.d.mts +26 -0
  56. package/dist/enrolment-store-8DyaIOLe.d.ts +26 -0
  57. package/dist/gateway/gateway.js +19 -1
  58. package/dist/gateway/gateway.js.map +1 -1
  59. package/dist/gateway/gateway.mjs +19 -1
  60. package/dist/gateway/gateway.mjs.map +1 -1
  61. package/dist/index.d.mts +100 -3
  62. package/dist/index.d.ts +100 -3
  63. package/dist/index.js +3289 -18
  64. package/dist/index.js.map +1 -1
  65. package/dist/index.mjs +3287 -18
  66. package/dist/index.mjs.map +1 -1
  67. package/dist/registration/index.js +1 -1
  68. package/dist/registration/index.js.map +1 -1
  69. package/dist/registration/index.mjs +1 -1
  70. package/dist/registration/index.mjs.map +1 -1
  71. package/dist/transport/index.js +1 -1
  72. package/dist/transport/index.js.map +1 -1
  73. package/dist/transport/index.mjs +1 -1
  74. package/dist/transport/index.mjs.map +1 -1
  75. package/dist/verify.js +19 -1
  76. package/dist/verify.js.map +1 -1
  77. package/dist/verify.mjs +19 -1
  78. package/dist/verify.mjs.map +1 -1
  79. package/package.json +1 -1
@@ -3713,7 +3713,7 @@ Policy saved to ${outputPath}`);
3713
3713
  };
3714
3714
 
3715
3715
  // src/version.ts
3716
- var SDK_VERSION = "5.9.0";
3716
+ var SDK_VERSION = "5.11.0";
3717
3717
 
3718
3718
  // src/http.ts
3719
3719
  var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
@@ -3793,27 +3793,23 @@ var UpgradeFlow = class {
3793
3793
  */
3794
3794
  async continueUpgrade(apiKey, kydId) {
3795
3795
  const { print, prompt } = this.callbacks;
3796
- print("\n[3/5] Migrate local policy to cloud");
3797
- print(` Policy: ${this.config.policyPath}`);
3798
- print(` Name: ${this.config.policy.name}`);
3799
- print(` Rules: ${this.config.policy.purposes.length} purpose rules`);
3800
- if (this.config.policy.scope) {
3801
- const scope = this.config.policy.scope;
3802
- const domains = [
3803
- ...(scope.allowedDomains || []).length ? [`${scope.allowedDomains.length} allowed`] : [],
3804
- ...(scope.blockedDomains || []).length ? [`${scope.blockedDomains.length} blocked`] : []
3805
- ];
3806
- if (domains.length) print(` Scope: ${domains.join(", ")} domains`);
3807
- }
3808
- print("");
3809
- print(" Uploading YAML -> Cloud PDLSS boundary...");
3810
- const uploadResult = await this.uploadPolicy(apiKey);
3811
- if (!uploadResult.success) {
3812
- print(` Upload failed: ${uploadResult.error}`);
3813
- return uploadResult;
3814
- }
3815
- print(" Policy uploaded. Same rules, now with trust scoring and audit.");
3816
- print(` Agent registered: ${uploadResult.agentId}`);
3796
+ print("\n[3/5] Link your agent");
3797
+ print(
3798
+ ` Local policy ${this.config.policyPath} stays on this machine (${this.config.policy.purposes.length} purpose rules).`
3799
+ );
3800
+ print(" Register the agent in the AstraSync dashboard (or with `astrasync register`) and");
3801
+ print(" paste its ASTRA- id so synced decisions are attributed to it.\n");
3802
+ const agentInput = (await prompt(" Agent ASTRA- id (Enter to skip): ")).trim();
3803
+ let agentId;
3804
+ if (agentInput) {
3805
+ if (!/^ASTRA-[A-Za-z0-9_-]+$/.test(agentInput)) {
3806
+ return { success: false, error: `Not an ASTRA- agent id: ${agentInput}` };
3807
+ }
3808
+ agentId = agentInput;
3809
+ print(` Agent: ${agentId}`);
3810
+ } else {
3811
+ print(" No agent linked \u2014 decisions sync under your API key only.");
3812
+ }
3817
3813
  if (kydId) {
3818
3814
  print(` Developer: ${kydId}`);
3819
3815
  }
@@ -3824,23 +3820,23 @@ var UpgradeFlow = class {
3824
3820
  const mode = modeInput.trim().toLowerCase() === "online" ? "online" : "hybrid";
3825
3821
  print(`
3826
3822
  [5/5] Activating ${mode} verification`);
3827
- await this.writeLocalConfig(apiKey, uploadResult.agentId, mode, kydId);
3823
+ await this.writeLocalConfig(apiKey, agentId, mode, kydId);
3828
3824
  print(` Config written to ${this.configDir}/config`);
3829
3825
  print(` Mode: ${mode}`);
3830
3826
  print(" Done!\n");
3831
- print(` Your agent (${uploadResult.agentId}) is verified on the AstraSync network.`);
3827
+ if (agentId) print(` Your agent (${agentId}) is verified on the AstraSync network.`);
3832
3828
  if (mode === "hybrid") {
3833
3829
  print(" Local evaluation active for speed. Cloud sync for audit and trust scoring.");
3834
3830
  } else {
3835
3831
  print(" Trust scoring, audit trails, and blockchain recording are active.");
3836
3832
  }
3837
3833
  print(`
3838
- Dashboard: ${this.platformUrl}/agents/${uploadResult.agentId}
3834
+ Dashboard: ${this.platformUrl}/agents${agentId ? `/${agentId}` : ""}
3839
3835
  `);
3840
3836
  return {
3841
3837
  success: true,
3842
3838
  apiKey,
3843
- agentId: uploadResult.agentId,
3839
+ agentId,
3844
3840
  kydId,
3845
3841
  mode
3846
3842
  };
@@ -3893,44 +3889,6 @@ var UpgradeFlow = class {
3893
3889
  return { valid: true, error: `Could not reach platform (proceeding anyway)` };
3894
3890
  }
3895
3891
  }
3896
- /**
3897
- * Upload local policy to the AstraSync cloud.
3898
- * YAML -> cloud PDLSS boundary, 1:1 mapping, no lossy transformation.
3899
- */
3900
- async uploadPolicy(apiKey) {
3901
- const policyYaml = toYaml(this.config.policy);
3902
- try {
3903
- const response = await sdkFetch(`${this.platformUrl}/api/v1/agents/policies/upload`, {
3904
- method: "POST",
3905
- headers: {
3906
- "Content-Type": "application/json",
3907
- Authorization: `Bearer ${apiKey}`
3908
- },
3909
- body: JSON.stringify({
3910
- policy: policyYaml,
3911
- policyObject: this.config.policy,
3912
- format: "yaml",
3913
- source: "local-guard-upgrade",
3914
- name: this.config.policy.name
3915
- })
3916
- });
3917
- if (!response.ok) {
3918
- const body = await response.text();
3919
- return { success: false, error: `Upload failed (${response.status}): ${body}` };
3920
- }
3921
- const data = await response.json();
3922
- return {
3923
- success: true,
3924
- agentId: data.agentId || "ASTRA-PENDING",
3925
- kydId: data.kydId
3926
- };
3927
- } catch (err) {
3928
- return {
3929
- success: false,
3930
- error: `Network error: ${err instanceof Error ? err.message : String(err)}`
3931
- };
3932
- }
3933
- }
3934
3892
  /**
3935
3893
  * Write the config file for the chosen mode.
3936
3894
  */
@@ -4978,6 +4936,24 @@ async function callVerifyAccessAPI(config, request2) {
4978
4936
  }
4979
4937
  };
4980
4938
  }
4939
+ if (response.status === 400 && data?.error === "INVALID_AGENT_ID") {
4940
+ const message = typeof data?.message === "string" ? data.message : "Agent identifier must be a canonical ASTRA-* id.";
4941
+ return {
4942
+ success: true,
4943
+ attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
4944
+ access: {
4945
+ allowed: false,
4946
+ reason: "invalid_agent_id",
4947
+ failures: [
4948
+ {
4949
+ dimension: "identity.invalid_agent_id",
4950
+ message,
4951
+ guidance: typeof data?.guidance === "string" ? data.guidance : "Present your canonical ASTRA-* id (discover it via GET /api/agents/request-registration/{requestId} after owner approval)."
4952
+ }
4953
+ ]
4954
+ }
4955
+ };
4956
+ }
4981
4957
  if (!response.ok) {
4982
4958
  if (response.status === 401) {
4983
4959
  return {
@@ -6007,7 +5983,15 @@ var PERMISSION_DENY_PATHS = [
6007
5983
  "~/.claude/settings.json",
6008
5984
  "~/.claude/settings.local.json",
6009
5985
  "**/.astrasync/**",
6010
- "~/.astrasync/**"
5986
+ "~/.astrasync/**",
5987
+ // 5.11.0 fleet mode: enrolment + signed-policy cache (user scope is inside
5988
+ // ~/.astrasync already; listed explicitly so the intent survives edits) and
5989
+ // the machine-scope enrolment dirs. `//` = absolute from filesystem root;
5990
+ // Windows paths are matched in POSIX form (C:\ → /c/).
5991
+ "~/.astrasync/fleet/**",
5992
+ "//etc/astrasync/**",
5993
+ "//Library/Application Support/AstraSync/**",
5994
+ "//c/ProgramData/AstraSync/**"
6011
5995
  ];
6012
5996
  var GUARD_PERMISSION_DENY_RULES = PERMISSION_DENY_PATHS.map(
6013
5997
  (p) => `Edit(${p})`
@@ -6138,6 +6122,509 @@ function uninstallCodexHooks(hooksPath) {
6138
6122
  return uninstallClaudeHooks(hooksPath, ASTRASYNC_CODEX_HOOK_MARKER);
6139
6123
  }
6140
6124
 
6125
+ // src/fleet/cli.ts
6126
+ var import_crypto2 = require("crypto");
6127
+ var import_fs7 = require("fs");
6128
+ var import_os2 = require("os");
6129
+ var import_path6 = require("path");
6130
+
6131
+ // src/fleet/client.ts
6132
+ var FleetApiError = class extends Error {
6133
+ constructor(message, status, code) {
6134
+ super(message);
6135
+ this.status = status;
6136
+ this.code = code;
6137
+ this.name = "FleetApiError";
6138
+ }
6139
+ };
6140
+ function base(url) {
6141
+ return url.replace(/\/+$/, "");
6142
+ }
6143
+ async function call(url, init, timeoutMs) {
6144
+ const controller = new AbortController();
6145
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
6146
+ try {
6147
+ const res = await sdkFetch(url, { ...init, signal: controller.signal });
6148
+ const text = await res.text();
6149
+ let body = text;
6150
+ try {
6151
+ body = text ? JSON.parse(text) : null;
6152
+ } catch {
6153
+ }
6154
+ return { status: res.status, body, headers: res.headers };
6155
+ } finally {
6156
+ clearTimeout(timer);
6157
+ }
6158
+ }
6159
+ function apiError(status, body, what) {
6160
+ const b = body ?? {};
6161
+ const code = b.code ?? (typeof b.error === "string" ? b.error : void 0);
6162
+ return new FleetApiError(`${what} failed (${status}${code ? ` ${code}` : ""})`, status, code);
6163
+ }
6164
+ async function enrolDevice(apiBaseUrl, request2, timeoutMs = 15e3) {
6165
+ const r = await call(
6166
+ `${base(apiBaseUrl)}/fleet/enrol`,
6167
+ {
6168
+ method: "POST",
6169
+ headers: { "Content-Type": "application/json" },
6170
+ body: JSON.stringify(request2)
6171
+ },
6172
+ timeoutMs
6173
+ );
6174
+ if (r.status !== 201 && r.status !== 200) throw apiError(r.status, r.body, "Enrolment");
6175
+ const data = r.body;
6176
+ if (!data?.deviceId || !data.credential || !data.jwks || !data.policyJws) {
6177
+ throw new FleetApiError("Enrolment response is missing required fields", r.status);
6178
+ }
6179
+ return data;
6180
+ }
6181
+
6182
+ // src/fleet/enrolment-store.ts
6183
+ var import_fs6 = require("fs");
6184
+ var import_os = require("os");
6185
+ var import_path5 = require("path");
6186
+ function machineScopeDir(platform2 = process.platform, env = process.env) {
6187
+ if (platform2 === "darwin") return "/Library/Application Support/AstraSync";
6188
+ if (platform2 === "win32") return (0, import_path5.join)(env.ProgramData || "C:\\ProgramData", "AstraSync");
6189
+ return "/etc/astrasync";
6190
+ }
6191
+ function fleetPaths(options = {}) {
6192
+ const home = options.home ?? (0, import_os.homedir)();
6193
+ const machineDir = options.machineDir ?? options.env?.ASTRASYNC_FLEET_MACHINE_DIR ?? machineScopeDir(options.platform, options.env);
6194
+ const userDir = (0, import_path5.join)(home, ".astrasync", "fleet");
6195
+ return {
6196
+ machineDevice: (0, import_path5.join)(machineDir, "fleet.json"),
6197
+ machinePolicy: (0, import_path5.join)(machineDir, "policy.jws"),
6198
+ userDir,
6199
+ userDevice: (0, import_path5.join)(userDir, "device.json"),
6200
+ userPolicy: (0, import_path5.join)(userDir, "policy.jws"),
6201
+ userKeyChain: (0, import_path5.join)(userDir, "keychain.json"),
6202
+ state: (0, import_path5.join)(userDir, "state.json")
6203
+ };
6204
+ }
6205
+ function isEnrolment(value) {
6206
+ const v = value;
6207
+ return !!v && typeof v.deviceId === "string" && typeof v.credential === "string" && typeof v.apiBaseUrl === "string" && !!v.org && typeof v.org.ownerId === "string" && !!v.jwks && Array.isArray(v.jwks.keys);
6208
+ }
6209
+ function loadEnrolment(paths) {
6210
+ const candidates = [
6211
+ [paths.machineDevice, "machine"],
6212
+ [paths.userDevice, "user"]
6213
+ ];
6214
+ for (const [path2, scope] of candidates) {
6215
+ if (!(0, import_fs6.existsSync)(path2)) continue;
6216
+ try {
6217
+ const parsed = JSON.parse((0, import_fs6.readFileSync)(path2, "utf-8"));
6218
+ if (isEnrolment(parsed)) return { enrolment: parsed, scope };
6219
+ } catch {
6220
+ }
6221
+ return { corrupt: true, path: path2 };
6222
+ }
6223
+ return null;
6224
+ }
6225
+ function writeFileMode(path2, content, mode) {
6226
+ (0, import_fs6.mkdirSync)((0, import_path5.dirname)(path2), { recursive: true, mode: 448 });
6227
+ (0, import_fs6.writeFileSync)(path2, content, { encoding: "utf-8", mode });
6228
+ try {
6229
+ (0, import_fs6.chmodSync)(path2, mode);
6230
+ } catch {
6231
+ }
6232
+ }
6233
+ function saveEnrolment(paths, enrolment, scope) {
6234
+ const path2 = scope === "machine" ? paths.machineDevice : paths.userDevice;
6235
+ writeFileMode(path2, JSON.stringify(enrolment, null, 2), scope === "machine" ? 420 : 384);
6236
+ return path2;
6237
+ }
6238
+ function readCachedPolicies(paths) {
6239
+ const out = [];
6240
+ for (const p of [paths.userPolicy, paths.machinePolicy]) {
6241
+ try {
6242
+ if ((0, import_fs6.existsSync)(p)) out.push((0, import_fs6.readFileSync)(p, "utf-8").trim());
6243
+ } catch {
6244
+ }
6245
+ }
6246
+ return out.filter(Boolean);
6247
+ }
6248
+ function writeCachedPolicy(paths, jws) {
6249
+ writeFileMode(paths.userPolicy, jws, 384);
6250
+ }
6251
+ function readKeyChain(paths) {
6252
+ try {
6253
+ const parsed = JSON.parse((0, import_fs6.readFileSync)(paths.userKeyChain, "utf-8"));
6254
+ return Array.isArray(parsed) ? parsed.filter((j) => typeof j === "string") : [];
6255
+ } catch {
6256
+ return [];
6257
+ }
6258
+ }
6259
+ function writeKeyChain(paths, chain) {
6260
+ writeFileMode(paths.userKeyChain, JSON.stringify(chain), 384);
6261
+ }
6262
+ function writeMachinePolicy(paths, jws) {
6263
+ writeFileMode(paths.machinePolicy, jws, 420);
6264
+ }
6265
+ function readState(paths) {
6266
+ try {
6267
+ const parsed = JSON.parse((0, import_fs6.readFileSync)(paths.state, "utf-8"));
6268
+ const c = parsed.counts;
6269
+ return {
6270
+ ...parsed,
6271
+ counts: { allow: c?.allow ?? 0, deny: c?.deny ?? 0, ask: c?.ask ?? 0 }
6272
+ };
6273
+ } catch {
6274
+ return { counts: { allow: 0, deny: 0, ask: 0 } };
6275
+ }
6276
+ }
6277
+ function writeState(paths, state) {
6278
+ try {
6279
+ writeFileMode(paths.state, JSON.stringify(state, null, 2), 384);
6280
+ } catch {
6281
+ }
6282
+ }
6283
+
6284
+ // src/fleet/policy-verify.ts
6285
+ var import_crypto = require("crypto");
6286
+ var import_jose2 = require("jose");
6287
+
6288
+ // src/fleet/types.ts
6289
+ var FLEET_POLICY_TYP = "fleet-policy+jwt";
6290
+ var FLEET_POLICY_AUD = "astrasync:fleet-policy";
6291
+ var FLEET_POLICY_ISS = "astrasync.ai";
6292
+ var FLEET_KEYS_TYP = "fleet-keys+jwt";
6293
+ var FLEET_KEYS_AUD = "astrasync:fleet-keys";
6294
+ var DEFAULT_HEARTBEAT_INTERVAL_SEC = 900;
6295
+
6296
+ // src/fleet/policy-verify.ts
6297
+ var FleetPolicyError = class extends Error {
6298
+ constructor(message) {
6299
+ super(message);
6300
+ this.name = "FleetPolicyError";
6301
+ }
6302
+ };
6303
+ function sha256Hex(text) {
6304
+ return (0, import_crypto.createHash)("sha256").update(text, "utf8").digest("hex");
6305
+ }
6306
+ function parseSettings(raw) {
6307
+ if (!raw || typeof raw !== "object") throw new FleetPolicyError("settings missing");
6308
+ const s = raw;
6309
+ if (s.posture !== "active" && s.posture !== "passive") {
6310
+ throw new FleetPolicyError("settings.posture must be active or passive");
6311
+ }
6312
+ if (s.failOpen !== void 0 && typeof s.failOpen !== "boolean") {
6313
+ throw new FleetPolicyError("settings.failOpen must be a boolean");
6314
+ }
6315
+ if (s.governMcp !== void 0 && typeof s.governMcp !== "boolean") {
6316
+ throw new FleetPolicyError("settings.governMcp must be a boolean");
6317
+ }
6318
+ if (s.skipTools !== void 0 && !(Array.isArray(s.skipTools) && s.skipTools.every((t) => typeof t === "string"))) {
6319
+ throw new FleetPolicyError("settings.skipTools must be a string array");
6320
+ }
6321
+ const num = (v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : void 0;
6322
+ return {
6323
+ posture: s.posture,
6324
+ failOpen: s.failOpen === true,
6325
+ ...typeof s.governMcp === "boolean" && { governMcp: s.governMcp },
6326
+ ...Array.isArray(s.skipTools) && { skipTools: s.skipTools },
6327
+ ...num(s.heartbeatIntervalSec) && { heartbeatIntervalSec: num(s.heartbeatIntervalSec) },
6328
+ ...num(s.staleAfterSec) && { staleAfterSec: num(s.staleAfterSec) }
6329
+ };
6330
+ }
6331
+ async function verifyFleetPolicy(jws, jwks, options) {
6332
+ let payloadBytes;
6333
+ let header;
6334
+ try {
6335
+ const keySet = (0, import_jose2.createLocalJWKSet)(jwks);
6336
+ const result = await (0, import_jose2.compactVerify)(jws, keySet, { algorithms: ["ES256"] });
6337
+ payloadBytes = result.payload;
6338
+ header = result.protectedHeader;
6339
+ } catch (err) {
6340
+ throw new FleetPolicyError(
6341
+ `signature verification failed (${err instanceof Error ? err.message : String(err)})`
6342
+ );
6343
+ }
6344
+ if (header.typ !== FLEET_POLICY_TYP) throw new FleetPolicyError(`unexpected typ ${header.typ}`);
6345
+ let claims;
6346
+ try {
6347
+ claims = JSON.parse(new TextDecoder().decode(payloadBytes));
6348
+ } catch {
6349
+ throw new FleetPolicyError("payload is not JSON");
6350
+ }
6351
+ if (claims.aud !== FLEET_POLICY_AUD) throw new FleetPolicyError(`unexpected aud ${claims.aud}`);
6352
+ if (claims.iss !== FLEET_POLICY_ISS) throw new FleetPolicyError(`unexpected iss ${claims.iss}`);
6353
+ if (!options.expectedSubs.includes(claims.sub)) {
6354
+ throw new FleetPolicyError("policy was issued for a different organisation or device");
6355
+ }
6356
+ if (!Number.isInteger(claims.ver) || claims.ver < 1) {
6357
+ throw new FleetPolicyError("ver must be a positive integer");
6358
+ }
6359
+ if (options.minVer !== void 0 && claims.ver < options.minVer) {
6360
+ throw new FleetPolicyError(
6361
+ `policy version ${claims.ver} is older than the accepted version ${options.minVer} (rollback)`
6362
+ );
6363
+ }
6364
+ if (typeof claims.yaml !== "string" || sha256Hex(claims.yaml) !== claims.sha256) {
6365
+ throw new FleetPolicyError("policy hash mismatch");
6366
+ }
6367
+ const settings = parseSettings(claims.settings);
6368
+ const parsed = parseYaml(claims.yaml);
6369
+ if (!parsed.policy) {
6370
+ const first = parsed.validation.errors[0];
6371
+ throw new FleetPolicyError(
6372
+ `signed policy is invalid${first ? ` (${first.path}: ${first.message})` : ""}`
6373
+ );
6374
+ }
6375
+ return { kid: header.kid, claims: { ...claims, settings }, policy: parsed.policy, settings, jws };
6376
+ }
6377
+ async function resolveFleetJwks(pinned, chain, ownerId) {
6378
+ let trusted = pinned;
6379
+ let lastSeq = 0;
6380
+ for (const link of chain) {
6381
+ try {
6382
+ const { payload, protectedHeader } = await (0, import_jose2.compactVerify)(
6383
+ link,
6384
+ (0, import_jose2.createLocalJWKSet)(trusted),
6385
+ { algorithms: ["ES256"] }
6386
+ );
6387
+ if (protectedHeader.typ !== FLEET_KEYS_TYP) continue;
6388
+ const c = JSON.parse(new TextDecoder().decode(payload));
6389
+ if (c.aud !== FLEET_KEYS_AUD || c.iss !== FLEET_POLICY_ISS || c.sub !== ownerId) continue;
6390
+ if (!Number.isInteger(c.seq) || c.seq <= lastSeq) continue;
6391
+ const keys = Array.isArray(c.keys) ? c.keys.filter((k) => k && k.kty === "EC" && k.crv === "P-256" && !("d" in k)) : [];
6392
+ if (keys.length === 0) continue;
6393
+ trusted = { keys };
6394
+ lastSeq = c.seq;
6395
+ } catch {
6396
+ }
6397
+ }
6398
+ return trusted;
6399
+ }
6400
+ async function pickNewestValid(candidates, jwks, options) {
6401
+ let best = null;
6402
+ let lastError = new FleetPolicyError("no cached fleet policy");
6403
+ for (const jws of candidates) {
6404
+ try {
6405
+ const v = await verifyFleetPolicy(jws, jwks, options);
6406
+ if (!best || v.claims.ver > best.claims.ver) best = v;
6407
+ } catch (err) {
6408
+ lastError = err instanceof Error ? err : new FleetPolicyError(String(err));
6409
+ }
6410
+ }
6411
+ if (!best) throw lastError;
6412
+ return best;
6413
+ }
6414
+
6415
+ // src/fleet/cli.ts
6416
+ var DEFAULT_FLEET_API_BASE_URL = "https://astrasync.ai/api";
6417
+ function machineFingerprint() {
6418
+ const candidates = ["/etc/machine-id", "/var/lib/dbus/machine-id"];
6419
+ let raw = "";
6420
+ for (const c of candidates) {
6421
+ try {
6422
+ if ((0, import_fs7.existsSync)(c)) {
6423
+ raw = (0, import_fs7.readFileSync)(c, "utf-8").trim();
6424
+ if (raw) break;
6425
+ }
6426
+ } catch {
6427
+ }
6428
+ }
6429
+ if (!raw) raw = `${(0, import_os2.hostname)()}|${(0, import_os2.platform)()}|${(0, import_os2.arch)()}`;
6430
+ return (0, import_crypto2.createHash)("sha256").update(`astrasync-fleet:${raw}`).digest("hex");
6431
+ }
6432
+ async function runEnroll(options) {
6433
+ const print = options.print ?? ((l) => console.log(l));
6434
+ const token = options.token.trim();
6435
+ if (!token)
6436
+ throw new Error("Usage: astrasync guard enroll <token> [--api-base-url <url>] [--system]");
6437
+ const apiBaseUrl = (options.apiBaseUrl ?? DEFAULT_FLEET_API_BASE_URL).replace(/\/+$/, "");
6438
+ const paths = options.paths ?? fleetPaths();
6439
+ const response = await enrolDevice(apiBaseUrl, {
6440
+ token,
6441
+ fingerprint: options.fingerprint ?? machineFingerprint(),
6442
+ hostname: (0, import_os2.hostname)(),
6443
+ os: { platform: (0, import_os2.platform)(), release: (0, import_os2.release)(), arch: (0, import_os2.arch)() },
6444
+ guardVersion: SDK_VERSION,
6445
+ adapters: options.adapters ?? [
6446
+ { kind: "claude-code", managed: false },
6447
+ { kind: "codex", managed: false }
6448
+ ]
6449
+ });
6450
+ const enrolment = {
6451
+ version: 1,
6452
+ deviceId: response.deviceId,
6453
+ credential: response.credential,
6454
+ org: response.org,
6455
+ apiBaseUrl: response.apiBaseUrl || apiBaseUrl,
6456
+ jwks: response.jwks,
6457
+ heartbeatIntervalSec: response.heartbeatIntervalSec || DEFAULT_HEARTBEAT_INTERVAL_SEC,
6458
+ enrolledAt: (/* @__PURE__ */ new Date()).toISOString()
6459
+ };
6460
+ const verified = await verifyFleetPolicy(response.policyJws, enrolment.jwks, {
6461
+ expectedSubs: [enrolment.org.ownerId, enrolment.deviceId]
6462
+ });
6463
+ const scope = options.system ? "machine" : "user";
6464
+ const savedTo = saveEnrolment(paths, enrolment, scope);
6465
+ if (options.system) writeMachinePolicy(paths, response.policyJws);
6466
+ writeCachedPolicy(paths, response.policyJws);
6467
+ writeKeyChain(paths, []);
6468
+ const state = readState(paths);
6469
+ writeState(paths, {
6470
+ ...state,
6471
+ appliedSha256: verified.claims.sha256,
6472
+ maxVer: Math.max(state.maxVer ?? 0, verified.claims.ver),
6473
+ revoked: false
6474
+ });
6475
+ print(
6476
+ `Enrolled in ${enrolment.org.name ?? enrolment.org.ownerId} (device ${enrolment.deviceId}).`
6477
+ );
6478
+ print(` Enrolment: ${savedTo} (${scope} scope)`);
6479
+ print(` Policy: v${verified.claims.ver}, posture ${verified.settings.posture}`);
6480
+ print(
6481
+ " Local policy.yaml / guard.json files are now ignored \u2014 the organisation policy governs."
6482
+ );
6483
+ return enrolment;
6484
+ }
6485
+ async function getFleetStatus(paths = fleetPaths()) {
6486
+ const loaded = loadEnrolment(paths);
6487
+ if (!loaded) return { enrolled: false };
6488
+ if ("corrupt" in loaded) return { enrolled: true, corrupt: loaded.path };
6489
+ const { enrolment, scope } = loaded;
6490
+ const state = readState(paths);
6491
+ let policy;
6492
+ try {
6493
+ const jwks = await resolveFleetJwks(enrolment.jwks, readKeyChain(paths), enrolment.org.ownerId);
6494
+ const v = await pickNewestValid(readCachedPolicies(paths), jwks, {
6495
+ expectedSubs: [enrolment.org.ownerId, enrolment.deviceId],
6496
+ minVer: state.maxVer
6497
+ });
6498
+ policy = {
6499
+ ver: v.claims.ver,
6500
+ sha256: v.claims.sha256,
6501
+ posture: v.settings.posture,
6502
+ failOpen: v.settings.failOpen
6503
+ };
6504
+ } catch (err) {
6505
+ policy = { error: err instanceof Error ? err.message : String(err) };
6506
+ }
6507
+ return {
6508
+ enrolled: true,
6509
+ scope,
6510
+ org: enrolment.org,
6511
+ deviceId: enrolment.deviceId,
6512
+ apiBaseUrl: enrolment.apiBaseUrl,
6513
+ policy,
6514
+ lastHeartbeatAt: state.lastHeartbeatAt,
6515
+ revoked: state.revoked === true,
6516
+ lastError: state.lastError,
6517
+ pendingCounts: state.counts
6518
+ };
6519
+ }
6520
+ function formatFleetStatus(status) {
6521
+ if (!status.enrolled) return ["Fleet: not enrolled (local policy mode)."];
6522
+ if (status.corrupt) {
6523
+ return [`Fleet: enrolment file ${status.corrupt} is unreadable \u2014 the guard fails closed.`];
6524
+ }
6525
+ const lines = [
6526
+ `Fleet: enrolled in ${status.org?.name ?? status.org?.ownerId} (${status.scope} scope)`,
6527
+ ` Device: ${status.deviceId}`,
6528
+ ` Platform: ${status.apiBaseUrl}`
6529
+ ];
6530
+ if (status.policy && "error" in status.policy) {
6531
+ lines.push(` Policy: NO VALID SIGNED POLICY \u2014 ${status.policy.error} (guard denies)`);
6532
+ } else if (status.policy) {
6533
+ lines.push(
6534
+ ` Policy: v${status.policy.ver} (${status.policy.sha256.slice(0, 12)}\u2026), posture ${status.policy.posture}${status.policy.failOpen ? ", fail-open" : ""}`
6535
+ );
6536
+ }
6537
+ lines.push(` Heartbeat: ${status.lastHeartbeatAt ?? "never"}`);
6538
+ if (status.revoked) {
6539
+ lines.push(" Status: REVOKED by the organisation \u2014 enforcing the last good policy");
6540
+ }
6541
+ if (status.lastError) lines.push(` Last sync error: ${status.lastError}`);
6542
+ return lines;
6543
+ }
6544
+ var MANAGED_SETTINGS_LOCATIONS = {
6545
+ // code.claude.com/docs/en/managed-settings
6546
+ claude: {
6547
+ darwin: "/Library/Application Support/ClaudeCode/managed-settings.json",
6548
+ linux: "/etc/claude-code/managed-settings.json",
6549
+ win32: "C:\\Program Files\\ClaudeCode\\managed-settings.json"
6550
+ },
6551
+ // learn.chatgpt.com/docs/enterprise/managed-configuration
6552
+ codex: {
6553
+ darwin: "/etc/codex/requirements.toml",
6554
+ linux: "/etc/codex/requirements.toml",
6555
+ win32: "%ProgramData%\\OpenAI\\Codex\\requirements.toml"
6556
+ }
6557
+ };
6558
+ function managedHookCommand(target, os2) {
6559
+ const bin = target === "claude" ? "astrasync-claude-hook" : "astrasync-codex-hook";
6560
+ if (os2 === "win32") return `"C:\\Program Files\\nodejs\\${bin}.cmd"`;
6561
+ return `/usr/local/bin/${bin}`;
6562
+ }
6563
+ function buildClaudeManagedSettings(os2) {
6564
+ return {
6565
+ allowManagedHooksOnly: true,
6566
+ env: { ASTRASYNC_FLEET_REQUIRED: "1" },
6567
+ hooks: {
6568
+ PreToolUse: [
6569
+ {
6570
+ matcher: HOOK_MATCHER,
6571
+ hooks: [{ type: "command", command: managedHookCommand("claude", os2), timeout: 10 }]
6572
+ }
6573
+ ]
6574
+ },
6575
+ permissions: { deny: [...GUARD_PERMISSION_DENY_RULES] }
6576
+ };
6577
+ }
6578
+ function tomlString(value) {
6579
+ return JSON.stringify(value);
6580
+ }
6581
+ function buildCodexRequirementsToml(os2) {
6582
+ const command = os2 === "win32" ? `set ASTRASYNC_FLEET_REQUIRED=1&& ${managedHookCommand("codex", os2)}` : `ASTRASYNC_FLEET_REQUIRED=1 ${managedHookCommand("codex", os2)}`;
6583
+ return [
6584
+ "# AstraSync Local Guard \u2014 managed hook (generated by `astrasync guard managed-settings --codex`)",
6585
+ `# Install at: ${MANAGED_SETTINGS_LOCATIONS.codex[os2]}`,
6586
+ "allow_managed_hooks_only = true",
6587
+ "",
6588
+ "[features]",
6589
+ "hooks = true",
6590
+ "",
6591
+ "[[hooks.PreToolUse]]",
6592
+ `matcher = ${tomlString(CODEX_HOOK_MATCHER)}`,
6593
+ "",
6594
+ "[[hooks.PreToolUse.hooks]]",
6595
+ 'type = "command"',
6596
+ `command = ${tomlString(command)}`,
6597
+ "timeout = 10",
6598
+ 'statusMessage = "AstraSync Local Guard"',
6599
+ ""
6600
+ ].join("\n");
6601
+ }
6602
+ function buildManagedSettingsFiles(targets) {
6603
+ const out = [];
6604
+ for (const target of targets) {
6605
+ for (const os2 of ["darwin", "linux", "win32"]) {
6606
+ const content = target === "claude" ? `${JSON.stringify(buildClaudeManagedSettings(os2), null, 2)}
6607
+ ` : buildCodexRequirementsToml(os2);
6608
+ out.push({
6609
+ target,
6610
+ os: os2,
6611
+ installPath: MANAGED_SETTINGS_LOCATIONS[target][os2],
6612
+ fileName: `${target}-${os2}-${target === "claude" ? "managed-settings.json" : "requirements.toml"}`,
6613
+ content
6614
+ });
6615
+ }
6616
+ }
6617
+ return out;
6618
+ }
6619
+ function writeManagedSettings(files, outDir) {
6620
+ (0, import_fs7.mkdirSync)(outDir, { recursive: true });
6621
+ return files.map((f) => {
6622
+ const p = (0, import_path6.join)(outDir, f.fileName);
6623
+ (0, import_fs7.writeFileSync)(p, f.content, "utf-8");
6624
+ return p;
6625
+ });
6626
+ }
6627
+
6141
6628
  // src/cli/guard.ts
6142
6629
  var DEFAULT_POLICY_PATH = "./local-policy.yaml";
6143
6630
  var DEFAULT_CONFIG_DIR = ".astrasync";
@@ -6277,6 +6764,9 @@ async function cmdStatus() {
6277
6764
  }
6278
6765
  });
6279
6766
  console.log("\nAstraSync Local Guard Status\n");
6767
+ for (const line of formatFleetStatus(await getFleetStatus(fleetPaths()))) {
6768
+ console.log(` ${line}`);
6769
+ }
6280
6770
  if (upgraded) {
6281
6771
  const config = JSON.parse(fs.readFileSync(`${DEFAULT_CONFIG_DIR}/config`, "utf-8"));
6282
6772
  console.log(` Mode: ${config.mode}`);
@@ -6632,6 +7122,66 @@ function printClaudeCodeStatus() {
6632
7122
  }
6633
7123
  }
6634
7124
  }
7125
+ function flagValue(flag) {
7126
+ const i = process.argv.indexOf(flag);
7127
+ return i !== -1 ? process.argv[i + 1] : void 0;
7128
+ }
7129
+ function positionalAfter(command) {
7130
+ const argv = process.argv;
7131
+ const start = argv.indexOf(command) + 1;
7132
+ for (let i = start; i < argv.length; i++) {
7133
+ if (argv[i].startsWith("--")) {
7134
+ if (argv[i] === "--api-base-url" || argv[i] === "--out") i++;
7135
+ continue;
7136
+ }
7137
+ return argv[i];
7138
+ }
7139
+ return void 0;
7140
+ }
7141
+ async function cmdEnroll() {
7142
+ const token = positionalAfter("enroll") ?? "";
7143
+ try {
7144
+ await runEnroll({
7145
+ token,
7146
+ apiBaseUrl: flagValue("--api-base-url"),
7147
+ system: process.argv.includes("--system")
7148
+ });
7149
+ } catch (err) {
7150
+ console.error(`Enrolment failed: ${err instanceof Error ? err.message : String(err)}`);
7151
+ if (process.argv.includes("--system")) {
7152
+ console.error(
7153
+ " (--system writes the machine-scope enrolment and needs administrator rights)"
7154
+ );
7155
+ }
7156
+ process.exit(1);
7157
+ }
7158
+ }
7159
+ async function cmdFleetStatus() {
7160
+ const status = await getFleetStatus(fleetPaths());
7161
+ for (const line of formatFleetStatus(status)) console.log(line);
7162
+ }
7163
+ async function cmdManagedSettings() {
7164
+ const targets = [];
7165
+ if (process.argv.includes("--claude")) targets.push("claude");
7166
+ if (process.argv.includes("--codex")) targets.push("codex");
7167
+ if (targets.length === 0) targets.push("claude", "codex");
7168
+ const files = buildManagedSettingsFiles(targets);
7169
+ const outDir = flagValue("--out");
7170
+ if (outDir) {
7171
+ const written = writeManagedSettings(files, outDir);
7172
+ console.log("Managed-settings templates written:");
7173
+ files.forEach((f, i) => console.log(` ${written[i]} \u2192 deploy to ${f.installPath}`));
7174
+ } else {
7175
+ for (const f of files) {
7176
+ console.log(`===== ${f.target} \xB7 ${f.os} \u2192 ${f.installPath}`);
7177
+ console.log(f.content);
7178
+ }
7179
+ }
7180
+ console.log(
7181
+ "Hook commands assume npm\u2019s default global prefix; adjust if your MDM installs elsewhere."
7182
+ );
7183
+ console.log("Machines must also be enrolled: astrasync guard enroll <token> --system");
7184
+ }
6635
7185
  async function main() {
6636
7186
  const command = process.argv[2] === "guard" ? process.argv[3] : process.argv[2];
6637
7187
  switch (command) {
@@ -6668,6 +7218,16 @@ async function main() {
6668
7218
  case "uninstall-codex":
6669
7219
  await cmdUninstallCodex();
6670
7220
  break;
7221
+ case "enroll":
7222
+ case "enrol":
7223
+ await cmdEnroll();
7224
+ break;
7225
+ case "fleet-status":
7226
+ await cmdFleetStatus();
7227
+ break;
7228
+ case "managed-settings":
7229
+ await cmdManagedSettings();
7230
+ break;
6671
7231
  default:
6672
7232
  console.log("AstraSync Local Guard\n");
6673
7233
  console.log("Usage: astrasync guard <command>\n");
@@ -6688,6 +7248,10 @@ async function main() {
6688
7248
  console.log(" install-codex Install the Local Guard hook for OpenAI Codex CLI");
6689
7249
  console.log(" (same flags; policy shared with Claude Code)");
6690
7250
  console.log(" uninstall-codex Remove the Codex hook (--project for project scope)");
7251
+ console.log(" enroll <token> Enrol this machine in your organisation fleet");
7252
+ console.log(" (--api-base-url <url>, --system for machine scope)");
7253
+ console.log(" fleet-status Show fleet enrolment, signed policy and heartbeat");
7254
+ console.log(" managed-settings Print MDM templates (--claude, --codex, --out <dir>)");
6691
7255
  console.log("");
6692
7256
  process.exit(command ? 1 : 0);
6693
7257
  }