@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 +4 -1
- package/engine/ado.js +7 -3
- package/engine/shared.js +52 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
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(
|
|
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
|
-
|
|
815
|
-
|
|
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
|
-
|
|
31
|
-
//
|
|
32
|
-
|
|
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
|
-
|
|
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.
|
|
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"
|