@integrity-labs/agt-cli 0.28.1019 → 0.28.1021

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/bin/agt.js CHANGED
@@ -40,7 +40,7 @@ import {
40
40
  success,
41
41
  table,
42
42
  warn
43
- } from "../chunk-CVCXDCT6.js";
43
+ } from "../chunk-SGAW3XWQ.js";
44
44
  import {
45
45
  getProjectDir,
46
46
  isSessionResumeDisabled,
@@ -5474,7 +5474,7 @@ import { execFileSync, execSync } from "child_process";
5474
5474
  import { existsSync as existsSync11, realpathSync as realpathSync2 } from "fs";
5475
5475
  import chalk18 from "chalk";
5476
5476
  import ora16 from "ora";
5477
- var cliVersion = true ? "0.28.1019" : "dev";
5477
+ var cliVersion = true ? "0.28.1021" : "dev";
5478
5478
  async function fetchLatestVersion() {
5479
5479
  const host2 = getHost();
5480
5480
  if (!host2) return null;
@@ -6734,7 +6734,7 @@ function handleError(err) {
6734
6734
  }
6735
6735
 
6736
6736
  // src/bin/agt.ts
6737
- var cliVersion2 = true ? "0.28.1019" : "dev";
6737
+ var cliVersion2 = true ? "0.28.1021" : "dev";
6738
6738
  var program = new Command();
6739
6739
  program.name("agt").description("Augmented CLI \u2014 agent provisioning and management").version(cliVersion2).option("--json", "Emit machine-readable JSON output (suppress spinners and colors)").option("--skip-update-check", "Skip the automatic update check on startup");
6740
6740
  program.hook("preAction", async (thisCommand, actionCommand) => {
@@ -1134,12 +1134,53 @@ var DecryptContextRequiredError = class extends SecretDecryptError {
1134
1134
  scope = "caller";
1135
1135
  };
1136
1136
 
1137
+ // ../../packages/core/dist/crypto/kek-ref.js
1138
+ var APP_KID_RE = /^[0-9a-f]{8}$/;
1139
+ var KMS_KEY_ARN_RE = /^arn:(aws|aws-cn|aws-us-gov|aws-iso[a-z-]*):kms:([a-z0-9-]+):(\d{12}):key\/(mrk-[0-9a-f]{32}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/;
1140
+ var BARE_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1141
+ function reject(ref, why) {
1142
+ throw new MalformedEnvelopeError(`envelope: wrap entry ref ${JSON.stringify(ref)} ${why}`);
1143
+ }
1144
+ function parseKekRef(ref) {
1145
+ const sep2 = ref.indexOf(":");
1146
+ if (sep2 <= 0) {
1147
+ if (BARE_UUID_RE.test(ref)) {
1148
+ reject(ref, "is a bare epoch_id \u2014 that form was WITHDRAWN with A5 option (1) (ADR-0062, ENG-10908); a ref must be app:<kid> or kms:<arn>");
1149
+ }
1150
+ reject(ref, "has no namespace \u2014 expected app:<kid> or kms:<arn>");
1151
+ }
1152
+ const namespace = ref.slice(0, sep2);
1153
+ const rest = ref.slice(sep2 + 1);
1154
+ if (namespace === "app") {
1155
+ if (!APP_KID_RE.test(rest)) {
1156
+ reject(ref, "is not app:<kid> with kid = 8 lowercase hex chars");
1157
+ }
1158
+ return { namespace: "app", kid: rest };
1159
+ }
1160
+ if (namespace === "kms") {
1161
+ const m = KMS_KEY_ARN_RE.exec(rest);
1162
+ if (!m) {
1163
+ if (/^arn:[^:]+:kms:[^:]*:\d{12}:alias\//.test(rest)) {
1164
+ reject(ref, "is a KMS ALIAS arn \u2014 an alias is a mutable pointer, so it cannot bind custody (ADR-0062 A6); use the key arn (\u2026:key/<key-id>)");
1165
+ }
1166
+ reject(ref, "is not kms:<key arn> (arn:<partition>:kms:<region>:<account>:key/<key-id>)");
1167
+ }
1168
+ return {
1169
+ namespace: "kms",
1170
+ arn: rest,
1171
+ region: m[2],
1172
+ accountId: m[3],
1173
+ keyId: m[4]
1174
+ };
1175
+ }
1176
+ reject(ref, `has unknown namespace ${JSON.stringify(namespace)} \u2014 expected one of app, kms`);
1177
+ }
1178
+
1137
1179
  // ../../packages/core/dist/crypto/envelope.js
1138
1180
  var PREFIX = "enc:";
1139
1181
  var V2_MARKER = "2";
1140
1182
  var IV_LENGTH = 12;
1141
1183
  var AUTH_TAG_LENGTH = 16;
1142
- var KID_RE = /^[0-9a-f]{8}$/;
1143
1184
  function deriveKid(key) {
1144
1185
  return createHash("sha256").update(key).digest("hex").slice(0, 8);
1145
1186
  }
@@ -1161,6 +1202,7 @@ function decodeWrapList(w) {
1161
1202
  if (typeof entry.ref !== "string" || typeof entry.dek !== "string") {
1162
1203
  throw new MalformedEnvelopeError("envelope: wrap entry ref/dek must be strings");
1163
1204
  }
1205
+ parseKekRef(entry.ref);
1164
1206
  return { ref: entry.ref, dek: entry.dek };
1165
1207
  });
1166
1208
  }
@@ -1179,7 +1221,7 @@ function decodeHeader(bytes) {
1179
1221
  if (keys.length !== 2 || keys[0] !== "kid" || keys[1] !== "s") {
1180
1222
  throw new MalformedEnvelopeError("envelope: scheme a header must have exactly {s, kid}");
1181
1223
  }
1182
- if (typeof decoded.kid !== "string" || !KID_RE.test(decoded.kid)) {
1224
+ if (typeof decoded.kid !== "string" || !APP_KID_RE.test(decoded.kid)) {
1183
1225
  throw new MalformedEnvelopeError("envelope: scheme a kid must be 8 lowercase hex chars");
1184
1226
  }
1185
1227
  return { s: "a", kid: decoded.kid };
@@ -1250,6 +1292,36 @@ function buildAad(headerBytes, ctx) {
1250
1292
  // ../../packages/core/dist/crypto/secret.js
1251
1293
  import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
1252
1294
 
1295
+ // ../../packages/core/dist/crypto/dek-cache.js
1296
+ var DEFAULT_DEK_CACHE_TTL_MS = 5 * 6e4;
1297
+ var cache = /* @__PURE__ */ new Map();
1298
+ var failures = /* @__PURE__ */ new Map();
1299
+ function cacheKey(entry) {
1300
+ return `${entry.ref}\0${entry.dek}`;
1301
+ }
1302
+ function getDekFailure(entry) {
1303
+ const key = cacheKey(entry);
1304
+ const hit = failures.get(key);
1305
+ if (!hit)
1306
+ return null;
1307
+ if (hit.expiresAt <= Date.now()) {
1308
+ failures.delete(key);
1309
+ return null;
1310
+ }
1311
+ return hit.err;
1312
+ }
1313
+ function getCachedDek(entry) {
1314
+ const key = cacheKey(entry);
1315
+ const hit = cache.get(key);
1316
+ if (!hit)
1317
+ return null;
1318
+ if (hit.expiresAt <= Date.now()) {
1319
+ cache.delete(key);
1320
+ return null;
1321
+ }
1322
+ return hit.dek;
1323
+ }
1324
+
1253
1325
  // ../../packages/core/dist/crypto/keyring.js
1254
1326
  var ACTIVE_ENV = "AUTH_ENCRYPTION_KEY";
1255
1327
  var PREVIOUS_ENV = "AUTH_ENCRYPTION_KEY_PREVIOUS";
@@ -1321,13 +1393,42 @@ function decryptSecret(encoded, context) {
1321
1393
  throw new AeadAuthFailureError(`scheme-a decrypt failed authentication for kid ${parsed.header.kid} (AAD mismatch or tampering)`, { cause });
1322
1394
  }
1323
1395
  }
1324
- // Schemes k/c are grammar this build parses but cannot unwrap — the KEK/DEK
1325
- // path is ADR-0062. A blob under either cannot exist in a 0061-only fleet
1326
- // (the writer never emits them); these throw so dispatch is total and typed.
1396
+ // Schemes k/c: the value is under a DEK, and the DEK is wrapped inline in the
1397
+ // header's `w` list (ADR-0062 A5 option 2). The unwrap is ASYNC and this
1398
+ // function is not, so the DEK must already be in the process cache — phase
1399
+ // (2) of the two-phase read (`collectWrapEntries` + `await primeDeks`) puts
1400
+ // it there. See dek-cache.ts for why the seam is shaped this way.
1327
1401
  case "k":
1328
- throw new PlatformKekUnavailableError("scheme k (platform-CMK DEK) unwrap is not available in this build (ADR-0062)");
1329
- case "c":
1330
- throw new CustomerKekUnavailableError("scheme c (customer-CMK DEK) unwrap is not available in this build (ADR-0062)");
1402
+ case "c": {
1403
+ const scheme = parsed.header.s;
1404
+ if (!context) {
1405
+ throw new DecryptContextRequiredError("decryptSecret: a v2 envelope requires a DecryptContext to reconstruct its AAD");
1406
+ }
1407
+ if (scheme === "c" && parsed.header.w.length > 1) {
1408
+ throw new CustomerKekUnavailableError(`scheme c: this row dual-wraps ${parsed.header.w.length} DEK entries, and which custody may satisfy it is an ADR-0062 increment D decision that has not been made \u2014 refusing rather than letting a platform wrap override a revoked customer KEK`);
1409
+ }
1410
+ let dek = null;
1411
+ for (const entry of parsed.header.w) {
1412
+ dek = getCachedDek(entry);
1413
+ if (dek)
1414
+ break;
1415
+ }
1416
+ if (!dek) {
1417
+ for (const entry of parsed.header.w) {
1418
+ const failure = getDekFailure(entry);
1419
+ if (failure)
1420
+ throw failure;
1421
+ }
1422
+ const detail = `no unwrapped DEK cached for any of this row's ${parsed.header.w.length} wrap entries \u2014 either the KEK is unavailable, or the read path did not prime the cache (collectWrapEntries + await primeDeks) before decrypting`;
1423
+ throw scheme === "k" ? new PlatformKekUnavailableError(`scheme k: ${detail}`) : new CustomerKekUnavailableError(`scheme c: ${detail}`);
1424
+ }
1425
+ const aad = buildAad(parsed.headerBytes, context);
1426
+ try {
1427
+ return gcmDecrypt(dek, parsed.iv, parsed.data, aad);
1428
+ } catch (cause) {
1429
+ throw new AeadAuthFailureError(`scheme-${scheme} decrypt failed authentication (AAD mismatch or tampering)`, { cause });
1430
+ }
1431
+ }
1331
1432
  }
1332
1433
  }
1333
1434
  function decryptV1(iv, data) {
@@ -2454,13 +2555,26 @@ function provisionStopHook(codeName) {
2454
2555
  'rm -f "$MARKER"',
2455
2556
  'AGT_HOST="${AGT_HOST:-}"; AGT_API_KEY="${AGT_API_KEY:-}"',
2456
2557
  'if [ -z "$AGT_HOST" ] || [ -z "$AGT_API_KEY" ]; then exit 0; fi',
2457
- 'JWT=$(curl -sf -X POST "${AGT_HOST}/host/exchange" -H "Content-Type: application/json" -d "{\\"api_key\\": \\"${AGT_API_KEY}\\"}" | jq -r \'.token // empty\' 2>/dev/null || true)',
2558
+ // CS-1787: the key goes in on STDIN, never in argv. Every agent on a host
2559
+ // runs as root and /proc/<pid>/cmdline is world-readable (0444), so a shell
2560
+ // that expands a secret into a command-line argument publishes it to every
2561
+ // other tenant on the box for the lifetime of the call.
2562
+ //
2563
+ // `AGT_API_KEY=... jq` is a command PREFIX assignment, so the value travels
2564
+ // in the environment rather than argv, and jq reads it with `env.` — which
2565
+ // also makes the body JSON-safe, unlike the string interpolation this
2566
+ // replaces. `-d @-` tells curl to read the body from stdin.
2567
+ //
2568
+ // `printf ... | curl --config -` for the bearer token, which is the form
2569
+ // `STDIN_CREDENTIAL_EXAMPLE` in security/argv-credentials.ts prescribes.
2570
+ // printf is a shell BUILTIN, so it forks nothing and has no argv to read.
2571
+ `JWT=$(AGT_API_KEY="$AGT_API_KEY" jq -nc '{api_key: env.AGT_API_KEY}' | curl -sf -X POST "\${AGT_HOST}/host/exchange" -H "Content-Type: application/json" -d @- | jq -r '.token // empty' 2>/dev/null || true)`,
2458
2572
  'if [ -z "$JWT" ]; then exit 0; fi',
2459
2573
  'case "$TEMPLATE_ID" in',
2460
2574
  " daily-standup|standup|weekly-standup)",
2461
- ` curl -sf -X POST "\${AGT_HOST}/host/agent-status" -H "Content-Type: application/json" -H "Authorization: Bearer $JWT" -d "$(jq -n --arg a \\"$AGENT_ID\\" --arg s \\"$RESP\\" '{agent_id:$a,standup:$s,current_status:"idle"}')" >/dev/null 2>&1 & ;;`,
2575
+ ` printf 'header = "Authorization: Bearer %s"\\n' "$JWT" | curl -sf --config - -X POST "\${AGT_HOST}/host/agent-status" -H "Content-Type: application/json" -d "$(jq -n --arg a \\"$AGENT_ID\\" --arg s \\"$RESP\\" '{agent_id:$a,standup:$s,current_status:"idle"}')" >/dev/null 2>&1 & ;;`,
2462
2576
  " *)",
2463
- ` curl -sf -X POST "\${AGT_HOST}/host/agent-status" -H "Content-Type: application/json" -H "Authorization: Bearer $JWT" -d "$(jq -n --arg a \\"$AGENT_ID\\" --arg t \\"$RESP\\" '{agent_id:$a,current_tasks:$t}')" >/dev/null 2>&1 & ;;`,
2577
+ ` printf 'header = "Authorization: Bearer %s"\\n' "$JWT" | curl -sf --config - -X POST "\${AGT_HOST}/host/agent-status" -H "Content-Type: application/json" -d "$(jq -n --arg a \\"$AGENT_ID\\" --arg t \\"$RESP\\" '{agent_id:$a,current_tasks:$t}')" >/dev/null 2>&1 & ;;`,
2464
2578
  "esac",
2465
2579
  "exit 0"
2466
2580
  ].join("\n") + "\n";
@@ -3785,10 +3899,19 @@ HOST="\${AGT_HOST:-}"
3785
3899
  KEY="\${AGT_API_KEY:-}"
3786
3900
  [ -n "$AGENT_ID" ] && [ -n "$HOST" ] && [ -n "$KEY" ] || exit 0
3787
3901
 
3788
- JWT="$(curl -sf --connect-timeout 2 --max-time 5 -X POST "\${HOST}/host/exchange" -H 'Content-Type: application/json' -d "$(jq -n --arg k "$KEY" '{api_key:$k}')" 2>/dev/null | jq -r '.token // empty' 2>/dev/null || true)"
3902
+ # CS-1787: the key goes in on STDIN, never in argv (/proc/<pid>/cmdline is
3903
+ # world-readable 0444 and every agent here runs as root). The form this
3904
+ # replaces leaked TWICE: the command substitution ran first, so jq held the
3905
+ # key in its own argv before curl ever saw it. A command PREFIX assignment
3906
+ # passes it through the environment instead, and jq's env. lookup keeps the body
3907
+ # JSON-safe into the bargain.
3908
+ JWT="$(AGT_API_KEY="$KEY" jq -nc '{api_key: env.AGT_API_KEY}' | curl -sf --connect-timeout 2 --max-time 5 -X POST "\${HOST}/host/exchange" -H 'Content-Type: application/json' -d @- 2>/dev/null | jq -r '.token // empty' 2>/dev/null || true)"
3789
3909
  [ -n "$JWT" ] || exit 0
3790
3910
 
3791
- BOARD="$(curl -sf --connect-timeout 2 --max-time 5 -X POST "\${HOST}/host/my-kanban" -H 'Content-Type: application/json' -H "Authorization: Bearer $JWT" -d "$(jq -n --arg a "$AGENT_ID" '{agent_id:$a}')" 2>/dev/null || true)"
3911
+ # Bearer on stdin via a curl config, per STDIN_CREDENTIAL_EXAMPLE in
3912
+ # security/argv-credentials.ts. printf is a shell builtin, so it forks
3913
+ # nothing and there is no argv to read.
3914
+ BOARD="$(printf 'header = "Authorization: Bearer %s"\\n' "$JWT" | curl -sf --config - --connect-timeout 2 --max-time 5 -X POST "\${HOST}/host/my-kanban" -H 'Content-Type: application/json' -d "$(jq -n --arg a "$AGENT_ID" '{agent_id:$a}')" 2>/dev/null || true)"
3792
3915
  [ -n "$BOARD" ] || exit 0
3793
3916
 
3794
3917
  # The counts that matter. \`waiting\` is deliberately NOT counted as work: a card
@@ -3944,9 +4067,14 @@ if [ -f "$TOKEN_FILE" ]; then
3944
4067
  [ $(( T_EXP - NOW )) -gt 300 ] && TOKEN="$(jq -r '.token // empty' "$TOKEN_FILE" 2>/dev/null || true)"
3945
4068
  fi
3946
4069
  if [ -z "$TOKEN" ]; then
3947
- EX="$(curl -fsS --max-time 5 -X POST "$HOST/host/exchange" \\
4070
+ # CS-1787: key on STDIN, never in argv. /proc/<pid>/cmdline is world-readable
4071
+ # (0444) and every agent on this host runs as root, so an expanded secret is
4072
+ # readable by every other tenant for the life of the call. jq's env. lookup also
4073
+ # makes the body JSON-safe, which the interpolation it replaces was not.
4074
+ EX="$(AGT_API_KEY="$KEY" jq -nc '{host_key: env.AGT_API_KEY}' \\
4075
+ | curl -fsS --max-time 5 -X POST "$HOST/host/exchange" \\
3948
4076
  -H 'Content-Type: application/json' \\
3949
- -d "{\\"host_key\\":\\"$KEY\\"}" 2>/dev/null || true)"
4077
+ -d @- 2>/dev/null || true)"
3950
4078
  TOKEN="$(printf '%s' "$EX" | jq -r '.token // empty' 2>/dev/null || true)"
3951
4079
  [ -n "$TOKEN" ] || exit 0
3952
4080
  EXP_ISO="$(printf '%s' "$EX" | jq -r '.expires_at // empty' 2>/dev/null || true)"
@@ -3960,9 +4088,14 @@ fi
3960
4088
 
3961
4089
  # Fire-and-forget the push (never block the agent's tool flow).
3962
4090
  BODY="$(jq -nc --arg a "$AGENT_ID" --arg s "$STEP" '{agent_id:$a, step:$s}' 2>/dev/null || printf '{"agent_id":"%s","step":"%s"}' "$AGENT_ID" "$STEP")"
3963
- ( curl -fsS --max-time 4 -X POST "$HOST/host/kanban/auto-progress" \\
4091
+ # CS-1787: bearer on stdin via a curl config, per STDIN_CREDENTIAL_EXAMPLE in
4092
+ # security/argv-credentials.ts. This one the existing header predicate already
4093
+ # matched - the $TOKEN name is credential-shaped and sits inside a -H argument - so
4094
+ # wiring the guard into the hook-script tests forces it fixed alongside the
4095
+ # three -d bodies. printf is a shell builtin: nothing forks, no argv to read.
4096
+ ( printf 'header = "Authorization: Bearer %s"\\n' "$TOKEN" \\
4097
+ | curl -fsS --config - --max-time 4 -X POST "$HOST/host/kanban/auto-progress" \\
3964
4098
  -H 'Content-Type: application/json' \\
3965
- -H "Authorization: Bearer $TOKEN" \\
3966
4099
  -d "$BODY" >/dev/null 2>&1 || true ) &
3967
4100
 
3968
4101
  exit 0
@@ -7007,7 +7140,7 @@ function exchangeFailureKind(err) {
7007
7140
  }
7008
7141
 
7009
7142
  // src/lib/api-client.ts
7010
- var agtCliVersion = true ? "0.28.1019" : "dev";
7143
+ var agtCliVersion = true ? "0.28.1021" : "dev";
7011
7144
  var lastConfigHash = null;
7012
7145
  function setConfigHash(hash) {
7013
7146
  lastConfigHash = hash && hash.length > 0 ? hash : null;
@@ -7332,8 +7465,8 @@ function writeFlagsCache(path, file) {
7332
7465
  atomicWriteFileSync(path, `${JSON.stringify(file, null, 2)}
7333
7466
  `);
7334
7467
  }
7335
- function flagsCacheAgeSeconds(cache2, path, now = /* @__PURE__ */ new Date()) {
7336
- const fromRecorded = Date.parse(cache2.updated_at);
7468
+ function flagsCacheAgeSeconds(cache3, path, now = /* @__PURE__ */ new Date()) {
7469
+ const fromRecorded = Date.parse(cache3.updated_at);
7337
7470
  if (!Number.isNaN(fromRecorded)) {
7338
7471
  return Math.max(0, (now.getTime() - fromRecorded) / 1e3);
7339
7472
  }
@@ -10767,7 +10900,7 @@ import { spawn as spawn2 } from "child_process";
10767
10900
  import { readFileSync as readFileSync9 } from "fs";
10768
10901
  var DEFAULT_ARCHIVE_ADDRESS_PATH = "/var/lib/augmented/session-archive-address.json";
10769
10902
  var CACHE_TTL_MS = 5 * 60 * 1e3;
10770
- var cache = /* @__PURE__ */ new Map();
10903
+ var cache2 = /* @__PURE__ */ new Map();
10771
10904
  function str(value) {
10772
10905
  if (typeof value !== "string") return null;
10773
10906
  const trimmed = value.trim();
@@ -10776,7 +10909,7 @@ function str(value) {
10776
10909
  function readHostArchiveAddress(path) {
10777
10910
  const file = path ?? process.env["ARCHIVE_ADDRESS_FILE"]?.trim() ?? process.env["AGT_SESSION_ARCHIVE_ADDRESS_FILE"]?.trim() ?? DEFAULT_ARCHIVE_ADDRESS_PATH;
10778
10911
  const now = Date.now();
10779
- const hit = cache.get(file);
10912
+ const hit = cache2.get(file);
10780
10913
  if (hit && now - hit.at < CACHE_TTL_MS) return hit.value;
10781
10914
  let value = null;
10782
10915
  try {
@@ -10798,7 +10931,7 @@ function readHostArchiveAddress(path) {
10798
10931
  } catch {
10799
10932
  value = null;
10800
10933
  }
10801
- cache.set(file, { at: now, value });
10934
+ cache2.set(file, { at: now, value });
10802
10935
  return value;
10803
10936
  }
10804
10937
 
@@ -11961,4 +12094,4 @@ export {
11961
12094
  managerInstallSystemUnitCommand,
11962
12095
  managerUninstallSystemUnitCommand
11963
12096
  };
11964
- //# sourceMappingURL=chunk-CVCXDCT6.js.map
12097
+ //# sourceMappingURL=chunk-SGAW3XWQ.js.map