@zgeoff/atc 2.26.5 → 2.26.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zgeoff/atc",
3
- "version": "2.26.5",
3
+ "version": "2.26.7",
4
4
  "description": "Terminal control tower for coding-agent sessions",
5
5
  "homepage": "https://github.com/zgeoff/atc#readme",
6
6
  "bugs": "https://github.com/zgeoff/atc/issues",
@@ -1,5 +1,7 @@
1
1
  import { DaemonError } from '../protocol/daemon-error';
2
2
  import { isRecord } from '../shared/report';
3
+ import { sanitizeDetail } from '../shared/sanitize-detail';
4
+ import { truncateSummary } from '../shared/truncate-summary';
3
5
  import type {
4
6
  HarnessAttachment,
5
7
  HarnessExit,
@@ -925,10 +927,29 @@ function collectColdBoots(value: unknown): { readonly bootId: unknown; readonly
925
927
  }));
926
928
  }
927
929
 
930
+ // A refusal's message as detail a session list row can hold: credentials
931
+ // redacted while line breaks still bound a header's value, then control
932
+ // characters taken out so the text cannot redraw the row, and whitespace
933
+ // flattened to one line.
934
+ function buildSafeDetail(message: string): string {
935
+ return sanitizeDetail(message)
936
+ .replaceAll(/[\p{Cc}\p{Cf}]/gu, ' ')
937
+ .replaceAll(/\s+/gu, ' ')
938
+ .trim();
939
+ }
940
+
928
941
  // An end other than an exit, as a session's last message.
929
942
  function formatOutcome(outcome: Exclude<ImpSessionOutcome, { kind: 'exit' }>): string {
930
943
  if (outcome.kind === 'failed') {
931
- return `imp refused the session (${outcome.code ?? 'error'})`;
944
+ const code = outcome.code ?? 'error';
945
+ const detail = buildSafeDetail(outcome.message);
946
+
947
+ const line =
948
+ detail === ''
949
+ ? `imp refused the session (${code})`
950
+ : `imp refused the session (${code}): ${detail}`;
951
+
952
+ return truncateSummary(line);
932
953
  }
933
954
 
934
955
  if (outcome.kind === 'detached') {
@@ -649,10 +649,11 @@ interface ImpHost {
649
649
  }
650
650
 
651
651
  function buildImpSessionName(sessionID: string): string {
652
+ // The prefix plus 28 chars stays inside the 32-char session limit.
652
653
  return `atc-${sessionID
653
654
  .toLowerCase()
654
655
  .replaceAll(/[^a-z0-9]/g, '')
655
- .slice(0, 32)}`;
656
+ .slice(0, 28)}`;
656
657
  }
657
658
 
658
659
  // Short enough that the socket's path stays inside a unix socket's limit.
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Replaces credential-shaped text with `[redacted]`, whatever its length:
3
+ * URL userinfo, whole authorization header values, values of secret-named
4
+ * parameters and headers — quoted values included, whole — and long
5
+ * token-like runs. Ordinary words pass through.
6
+ */
7
+ export function sanitizeDetail(text: string): string {
8
+ return text
9
+ .replaceAll(/(?<scheme>[a-z][a-z0-9+.-]*:\/\/)[^/@\s]+@/giu, '$<scheme>[redacted]@')
10
+ .replaceAll(
11
+ /(?<name>["']?authorization["']?\s*:\s*)(?:"(?:[^"\\]|\\.)*"|[^\n]*)/giu,
12
+ '$<name>[redacted]',
13
+ )
14
+ .replaceAll(
15
+ /(?<name>["']?[a-z0-9_.-]*(?:key|token|secret|password|passwd|credential|signature)[a-z0-9_.-]*["']?\s*[:=]\s*)(?:"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|[^\s;,;&"']+)/giu,
16
+ '$<name>[redacted]',
17
+ )
18
+ .replaceAll(/\b(?<scheme>bearer|basic)\s+[a-z0-9+/=._-]+/giu, '$<scheme> [redacted]')
19
+ .replaceAll(/[a-z0-9+/_=-]{32,}/giu, '[redacted]');
20
+ }