@yemi33/minions 0.1.1127 → 0.1.1128

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/CHANGELOG.md CHANGED
@@ -1,12 +1,15 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.1127 (2026-04-18)
3
+ ## 0.1.1128 (2026-04-18)
4
4
 
5
5
  ### Features
6
+ - redact ADO tokens and JWTs from engine/log.json writes (#1297)
7
+ - SEC-02 — replace curl shell-out in ado.js with adoFetch (#1296)
6
8
  - validate project name and path on POST /api/projects/add (SEC-04, SEC-05) (#1298)
7
9
  - seed realActivityMap at spawn time, stamp pid in live-output (#1200)
8
10
 
9
11
  ### Fixes
12
+ - preserve buildErrorLog through transient states, persist poll time (#1273)
10
13
  - auto-fetch PR title on link-pr (closes #1283) (#1299)
11
14
  - scheduler double-fire within same cron minute (#1277)
12
15
  - gate auto-fix dispatch on throttle state to prevent stale-data spurious fixes
package/engine/ado.js CHANGED
@@ -104,11 +104,12 @@ async function adoFetch(url, token, opts = {}) {
104
104
  const _retryCount = typeof opts === 'number' ? opts : (opts._retryCount || 0); // backward compat
105
105
  const method = (typeof opts === 'object' && opts.method) || 'GET';
106
106
  const body = (typeof opts === 'object' && opts.body) || undefined;
107
+ const timeout = (typeof opts === 'object' && Number.isFinite(opts.timeout)) ? opts.timeout : 30000;
107
108
  const MAX_RETRIES = 1;
108
109
  const res = await fetch(url, {
109
110
  method,
110
111
  headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
111
- signal: AbortSignal.timeout(30000),
112
+ signal: AbortSignal.timeout(timeout),
112
113
  body,
113
114
  });
114
115
  // ── Throttle detection: intercept 429/503 BEFORE generic !res.ok ──
@@ -811,8 +812,11 @@ async function checkLiveReviewStatus(pr, project) {
811
812
  const prNum = shared.getPrNumber(pr);
812
813
  if (!prNum) return null;
813
814
  const url = `${orgBase}/${project.adoProject}/_apis/git/repositories/${project.repositoryId}/pullrequests/${prNum}?api-version=7.1`;
814
- const result = await execAsync(`curl -s --max-time 4 -H "Authorization: Bearer ${token}" "${url}"`, { encoding: 'utf-8', timeout: 5000, windowsHide: true });
815
- const prData = JSON.parse(result);
815
+ // SEC-02: use in-process adoFetch rather than a shell-out keeps the bearer
816
+ // token out of the process argv list where any local process could read it.
817
+ // 4s timeout preserves the original request-cancellation semantics via AbortSignal.
818
+ const prData = await adoFetch(url, token, { timeout: 4000 });
819
+ if (!prData) return null;
816
820
  const votes = (prData.reviewers || []).map(r => r.vote).filter(v => v !== undefined);
817
821
  if (votes.length === 0) return 'pending';
818
822
  return votesToReviewStatus(votes);
package/engine/shared.js CHANGED
@@ -20,6 +20,46 @@ function ts() { return new Date().toISOString(); }
20
20
  function logTs() { return new Date().toLocaleTimeString(); }
21
21
  function dateStamp() { return new Date().toISOString().slice(0, 10); }
22
22
 
23
+ // ── Secret Redaction (SEC-09) ──────────────────────────────────────────────
24
+ // Pure, side-effect-free redactor applied to every entry on the log write path
25
+ // so ADO tokens, JWTs, and azureauth stdout dumps never land in engine/log.json
26
+ // (2500-entry ring buffer readable by any local process).
27
+ //
28
+ // Replacements (order matters — azureauth first, then Bearer, then bare JWT):
29
+ // 1. `"token":"<20+ char base64-ish>"` → `"token":"[REDACTED_AZUREAUTH]"`
30
+ // Redacts the value only, not the whole line, so surrounding JSON
31
+ // context (e.g. expiresOn) remains debuggable.
32
+ // 2. `Bearer <20+ char base64-ish>` → `Bearer [REDACTED]`
33
+ // 3. `ey<b64url>.<b64url>[.<b64url>]` → `[REDACTED_JWT]`
34
+ // Catches bare JWTs in error messages or stack traces (anything left
35
+ // after Bearer replacement has consumed its tokens).
36
+ //
37
+ // `redactSecrets` also recurses into objects and arrays — used by `log()` to
38
+ // sanitize both the message and the meta payload before persistence.
39
+ const _BEARER_RE = /Bearer\s+[A-Za-z0-9+/=._\-]{20,}/g;
40
+ const _JWT_RE = /ey[A-Za-z0-9_\-]{10,}\.[A-Za-z0-9_\-]{10,}(?:\.[A-Za-z0-9_\-]{10,})?/g;
41
+ const _AZUREAUTH_RE = /"token"\s*:\s*"[A-Za-z0-9+/=._\-]{20,}"/g;
42
+
43
+ function _redactString(s) {
44
+ if (typeof s !== 'string' || s.length === 0) return s;
45
+ return s
46
+ .replace(_AZUREAUTH_RE, '"token":"[REDACTED_AZUREAUTH]"')
47
+ .replace(_BEARER_RE, 'Bearer [REDACTED]')
48
+ .replace(_JWT_RE, '[REDACTED_JWT]');
49
+ }
50
+
51
+ function redactSecrets(value) {
52
+ if (value == null) return value;
53
+ if (typeof value === 'string') return _redactString(value);
54
+ if (Array.isArray(value)) return value.map(redactSecrets);
55
+ if (typeof value === 'object') {
56
+ const out = {};
57
+ for (const k of Object.keys(value)) out[k] = redactSecrets(value[k]);
58
+ return out;
59
+ }
60
+ return value;
61
+ }
62
+
23
63
  // ── Log Buffering ──────────────────────────────────────────────────────────
24
64
  // Buffer log entries in memory and flush to disk periodically to reduce lock
25
65
  // contention (~139 calls/tick → 1 lock acquisition per flush).
@@ -27,9 +67,14 @@ const _logBuffer = [];
27
67
  let _logFlushTimer = null;
28
68
 
29
69
  function log(level, msg, meta = {}) {
30
- const entry = { timestamp: ts(), level, message: msg, ...meta };
31
- // Console output remains immediate
32
- console.log(`[${logTs()}] [${level}] ${msg}`);
70
+ // SEC-09: redact sensitive patterns (ADO tokens, JWTs, azureauth stdout)
71
+ // before both the in-memory buffer push and the console echo — ensures
72
+ // nothing sensitive is persisted to engine/log.json or engine stdout logs.
73
+ const safeMsg = typeof msg === 'string' ? _redactString(msg) : msg;
74
+ const safeMeta = redactSecrets(meta) || {};
75
+ const entry = { timestamp: ts(), level, message: safeMsg, ...safeMeta };
76
+ // Console output remains immediate (also redacted)
77
+ console.log(`[${logTs()}] [${level}] ${safeMsg}`);
33
78
 
34
79
  _logBuffer.push(entry);
35
80
 
@@ -50,7 +95,9 @@ function log(level, msg, meta = {}) {
50
95
 
51
96
  function _flushLogBuffer() {
52
97
  if (_logBuffer.length === 0) return;
53
- const entries = _logBuffer.splice(0);
98
+ // SEC-09 defense-in-depth: redact again at flush time so any direct
99
+ // `_logBuffer.push(entry)` callers (tests, future paths) can't leak secrets.
100
+ const entries = _logBuffer.splice(0).map(redactSecrets);
54
101
  try {
55
102
  mutateJsonFileLocked(LOG_PATH, (logData) => {
56
103
  if (!Array.isArray(logData)) logData = logData?.entries || [];
@@ -1719,6 +1766,7 @@ module.exports = {
1719
1766
  _WIN_RESERVED_NAMES, // exported for testing
1720
1767
  LOCK_STALE_MS,
1721
1768
  flushLogs,
1769
+ redactSecrets,
1722
1770
  slugify,
1723
1771
  safeSlugComponent,
1724
1772
  formatTranscriptEntry,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1127",
3
+ "version": "0.1.1128",
4
4
  "description": "Multi-agent AI dev team that runs from ~/.minions/ — five autonomous agents share a single engine, dashboard, and knowledge base",
5
5
  "bin": {
6
6
  "minions": "bin/minions.js"