agentsys 5.10.0 → 5.12.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.
- package/.claude-plugin/marketplace.json +34 -29
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +22 -0
- package/lib/binary/index.js +203 -3
- package/lib/enhance/fixer.js +64 -1
- package/package.json +1 -1
- package/site/content.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentsys",
|
|
3
3
|
"description": "20 specialized plugins for AI workflow automation - task orchestration, PR workflow, slop detection, code review, drift detection, enhancement analysis, documentation sync, unified static analysis, perf investigations, topic research, agent config linting, cross-tool AI consultation, structured AI debate, workflow pattern learning, codebase onboarding, contributor guidance, and Zig language support",
|
|
4
|
-
"version": "5.
|
|
4
|
+
"version": "5.12.0",
|
|
5
5
|
"owner": {
|
|
6
6
|
"name": "Avi Fenesh",
|
|
7
7
|
"url": "https://github.com/avifenesh"
|
|
@@ -27,11 +27,11 @@
|
|
|
27
27
|
"source": {
|
|
28
28
|
"source": "url",
|
|
29
29
|
"url": "https://github.com/agent-sh/next-task.git",
|
|
30
|
-
"ref": "v1.1.
|
|
31
|
-
"commit": "
|
|
30
|
+
"ref": "v1.1.2",
|
|
31
|
+
"commit": "8feba0141a895d651a850cbe724a0e333c24a3a0"
|
|
32
32
|
},
|
|
33
33
|
"description": "Master workflow orchestrator: autonomous workflow with model optimization (opus/sonnet/haiku), two-file state management, workflow enforcement gates, 8 specialist agents",
|
|
34
|
-
"version": "1.1.
|
|
34
|
+
"version": "1.1.2",
|
|
35
35
|
"category": "productivity",
|
|
36
36
|
"homepage": "https://github.com/agent-sh/next-task"
|
|
37
37
|
},
|
|
@@ -40,10 +40,11 @@
|
|
|
40
40
|
"source": {
|
|
41
41
|
"source": "url",
|
|
42
42
|
"url": "https://github.com/agent-sh/prepare-delivery.git",
|
|
43
|
-
"commit": "
|
|
43
|
+
"commit": "0e9685fe0e93e058af5ca9a0374f4f97e1db878c",
|
|
44
|
+
"ref": "v0.1.2"
|
|
44
45
|
},
|
|
45
46
|
"description": "Pre-ship quality gates: deslop, simplify, agnix, enhance, review loop, delivery validation, docs sync",
|
|
46
|
-
"version": "0.1.
|
|
47
|
+
"version": "0.1.2",
|
|
47
48
|
"category": "productivity",
|
|
48
49
|
"homepage": "https://github.com/agent-sh/prepare-delivery"
|
|
49
50
|
},
|
|
@@ -64,11 +65,11 @@
|
|
|
64
65
|
"source": {
|
|
65
66
|
"source": "url",
|
|
66
67
|
"url": "https://github.com/agent-sh/ship.git",
|
|
67
|
-
"commit": "
|
|
68
|
-
"ref": "v1.1.
|
|
68
|
+
"commit": "189da6af2abdf67ab661098af2fc18453fe9e734",
|
|
69
|
+
"ref": "v1.1.2"
|
|
69
70
|
},
|
|
70
71
|
"description": "Complete PR workflow: commit to production, skips review when called from next-task, removes task from registry on cleanup, automatic rollback",
|
|
71
|
-
"version": "1.1.
|
|
72
|
+
"version": "1.1.2",
|
|
72
73
|
"category": "deployment",
|
|
73
74
|
"homepage": "https://github.com/agent-sh/ship"
|
|
74
75
|
},
|
|
@@ -77,7 +78,7 @@
|
|
|
77
78
|
"source": {
|
|
78
79
|
"source": "url",
|
|
79
80
|
"url": "https://github.com/agent-sh/deslop.git",
|
|
80
|
-
"commit": "
|
|
81
|
+
"commit": "00301b9ce81d12caa38063e4a65b535ba5b011b2"
|
|
81
82
|
},
|
|
82
83
|
"description": "3-phase AI slop detection: regex patterns (HIGH), multi-pass analyzers (MEDIUM), CLI tools (LOW)",
|
|
83
84
|
"version": "1.0.0",
|
|
@@ -89,10 +90,11 @@
|
|
|
89
90
|
"source": {
|
|
90
91
|
"source": "url",
|
|
91
92
|
"url": "https://github.com/agent-sh/audit-project.git",
|
|
92
|
-
"commit": "
|
|
93
|
+
"commit": "f703facec38765b6fd8cb5a2076c98bf14e5998e",
|
|
94
|
+
"ref": "v1.0.2"
|
|
93
95
|
},
|
|
94
96
|
"description": "Multi-agent iterative code review until zero issues remain",
|
|
95
|
-
"version": "1.0.
|
|
97
|
+
"version": "1.0.2",
|
|
96
98
|
"category": "development",
|
|
97
99
|
"homepage": "https://github.com/agent-sh/audit-project"
|
|
98
100
|
},
|
|
@@ -101,7 +103,7 @@
|
|
|
101
103
|
"source": {
|
|
102
104
|
"source": "url",
|
|
103
105
|
"url": "https://github.com/agent-sh/drift-detect.git",
|
|
104
|
-
"commit": "
|
|
106
|
+
"commit": "576aca37402068aef175c8a6002584e19cb6ea74"
|
|
105
107
|
},
|
|
106
108
|
"description": "Deep repository analysis to realign project plans with code reality - detects drift, gaps, and creates prioritized reconstruction plans",
|
|
107
109
|
"version": "1.0.0",
|
|
@@ -113,7 +115,7 @@
|
|
|
113
115
|
"source": {
|
|
114
116
|
"source": "url",
|
|
115
117
|
"url": "https://github.com/agent-sh/enhance.git",
|
|
116
|
-
"commit": "
|
|
118
|
+
"commit": "93f299e494a3a9ea74e82bd2d15bc1c517ce8f0c"
|
|
117
119
|
},
|
|
118
120
|
"description": "Master enhancement orchestrator: parallel analyzer execution for plugins, agents, docs, CLAUDE.md, and prompts with unified reporting",
|
|
119
121
|
"version": "1.0.0",
|
|
@@ -125,7 +127,7 @@
|
|
|
125
127
|
"source": {
|
|
126
128
|
"source": "url",
|
|
127
129
|
"url": "https://github.com/agent-sh/sync-docs.git",
|
|
128
|
-
"commit": "
|
|
130
|
+
"commit": "410e06739da101583b7238669f4acad7f5aea7ab"
|
|
129
131
|
},
|
|
130
132
|
"description": "Standalone documentation sync: find outdated refs, update CHANGELOG, flag stale examples based on code changes",
|
|
131
133
|
"version": "1.0.0",
|
|
@@ -150,10 +152,11 @@
|
|
|
150
152
|
"source": {
|
|
151
153
|
"source": "url",
|
|
152
154
|
"url": "https://github.com/agent-sh/perf.git",
|
|
153
|
-
"commit": "
|
|
155
|
+
"commit": "189eb15e22bb6678da4d773f1c52b57d8880abff",
|
|
156
|
+
"ref": "v1.0.1"
|
|
154
157
|
},
|
|
155
158
|
"description": "Rigorous performance investigation workflow with baselines, profiling, hypotheses, and evidence-backed decisions",
|
|
156
|
-
"version": "1.0.
|
|
159
|
+
"version": "1.0.1",
|
|
157
160
|
"category": "development",
|
|
158
161
|
"homepage": "https://github.com/agent-sh/perf"
|
|
159
162
|
},
|
|
@@ -162,7 +165,7 @@
|
|
|
162
165
|
"source": {
|
|
163
166
|
"source": "url",
|
|
164
167
|
"url": "https://github.com/agent-sh/learn.git",
|
|
165
|
-
"commit": "
|
|
168
|
+
"commit": "b3025d376a83841078f3ab5cf53b62c6960e46c3"
|
|
166
169
|
},
|
|
167
170
|
"description": "Research topics online and create comprehensive learning guides with RAG-optimized indexes",
|
|
168
171
|
"version": "1.0.0",
|
|
@@ -187,7 +190,7 @@
|
|
|
187
190
|
"source": {
|
|
188
191
|
"source": "url",
|
|
189
192
|
"url": "https://github.com/agent-sh/consult.git",
|
|
190
|
-
"commit": "
|
|
193
|
+
"commit": "8ce96f86c0ae67f732383a8b45c9994a7cd64d2a"
|
|
191
194
|
},
|
|
192
195
|
"description": "Cross-tool AI consultation: get second opinions from Gemini CLI, Codex CLI, Claude Code, OpenCode, or Copilot CLI with model and thinking effort control",
|
|
193
196
|
"version": "1.0.0",
|
|
@@ -199,10 +202,11 @@
|
|
|
199
202
|
"source": {
|
|
200
203
|
"source": "url",
|
|
201
204
|
"url": "https://github.com/agent-sh/debate.git",
|
|
202
|
-
"commit": "
|
|
205
|
+
"commit": "aba659706bd25f7e394096acb457446e44966711",
|
|
206
|
+
"ref": "v1.0.1"
|
|
203
207
|
},
|
|
204
208
|
"description": "Structured multi-round debate between AI tools with proposer/challenger roles and verdict",
|
|
205
|
-
"version": "1.0.
|
|
209
|
+
"version": "1.0.1",
|
|
206
210
|
"category": "productivity",
|
|
207
211
|
"homepage": "https://github.com/agent-sh/debate"
|
|
208
212
|
},
|
|
@@ -224,10 +228,11 @@
|
|
|
224
228
|
"source": {
|
|
225
229
|
"source": "url",
|
|
226
230
|
"url": "https://github.com/agent-sh/skillers.git",
|
|
227
|
-
"commit": "
|
|
231
|
+
"commit": "e1c1a9b752c0d20a0a1f83747c26e5dea195b5ae",
|
|
232
|
+
"ref": "v0.2.1"
|
|
228
233
|
},
|
|
229
234
|
"description": "Learn from workflow patterns across sessions and suggest skills, hooks, and agents to automate repetitive work",
|
|
230
|
-
"version": "
|
|
235
|
+
"version": "0.2.1",
|
|
231
236
|
"category": "productivity",
|
|
232
237
|
"homepage": "https://github.com/agent-sh/skillers"
|
|
233
238
|
},
|
|
@@ -236,11 +241,11 @@
|
|
|
236
241
|
"source": {
|
|
237
242
|
"source": "url",
|
|
238
243
|
"url": "https://github.com/agent-sh/onboard.git",
|
|
239
|
-
"ref": "v0.1.
|
|
240
|
-
"commit": "
|
|
244
|
+
"ref": "v0.1.1",
|
|
245
|
+
"commit": "6c2e47e567aac6249a0df6d15491cbcd42ce7717"
|
|
241
246
|
},
|
|
242
247
|
"description": "Codebase onboarding - automated data collection and interactive project orientation",
|
|
243
|
-
"version": "0.1.
|
|
248
|
+
"version": "0.1.1",
|
|
244
249
|
"category": "productivity",
|
|
245
250
|
"homepage": "https://github.com/agent-sh/onboard"
|
|
246
251
|
},
|
|
@@ -249,11 +254,11 @@
|
|
|
249
254
|
"source": {
|
|
250
255
|
"source": "url",
|
|
251
256
|
"url": "https://github.com/agent-sh/can-i-help.git",
|
|
252
|
-
"ref": "v0.1.
|
|
253
|
-
"commit": "
|
|
257
|
+
"ref": "v0.1.1",
|
|
258
|
+
"commit": "f1364158deb359b581d7113a54e8a6aa7a6d8679"
|
|
254
259
|
},
|
|
255
260
|
"description": "Find where to contribute to any project - matches developer skills to test gaps, stale docs, bugspots, and open issues",
|
|
256
|
-
"version": "0.1.
|
|
261
|
+
"version": "0.1.1",
|
|
257
262
|
"category": "productivity",
|
|
258
263
|
"homepage": "https://github.com/agent-sh/can-i-help"
|
|
259
264
|
},
|
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## [5.12.0] - 2026-04-26
|
|
13
|
+
|
|
14
|
+
### Propagated upstream releases
|
|
15
|
+
- agent-core v0.4.4 (fixer.js symlink + TOCTOU) -> v0.4.5 (client-side SLSA verification + sync allowlist) synced into all 13 consumers.
|
|
16
|
+
- agent-analyzer v0.8.0 -> v0.8.1 (cargo-deny CI).
|
|
17
|
+
- prepare-delivery v0.1.2, audit-project v1.0.2 (reviewer-contract markers + orchestrator blocked handling).
|
|
18
|
+
|
|
19
|
+
## [5.11.0] - 2026-04-26
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- **Upgraded marketplace sub-plugin pins from SHA-only to tag+SHA** after each downstream plugin cut security releases. Post-run totals: 12 pinned to tags, 8 fell back to default-branch SHA (up from 7/13 in v5.10.0). New tag pins in this wave: `prepare-delivery` v0.1.1, `audit-project` v1.0.1, `next-task` v1.1.2, `ship` v1.1.2, `skillers` v0.2.1, `onboard` v0.1.1, `can-i-help` v0.1.1, `perf` v1.0.1, `debate` v1.0.1. Consumers now install from verifiable release tags for these plugins.
|
|
23
|
+
|
|
24
|
+
### Propagated upstream security fixes
|
|
25
|
+
- agent-core v0.4.4 synced into all 13 consumers via `lib/`: fixer.js symlink + TOCTOU guards (#14 agent-core), earlier v0.4.3 code-point-safe truncate + sync-workflow test-file exclusion, v0.4.2 additive sync + upstreamed workflow-state/queries, v0.4.1 binary SHA-256 + zip-slip defenses.
|
|
26
|
+
- prepare-delivery + audit-project: falsePositive review-bypass cap (50% ratio + required reason).
|
|
27
|
+
- next-task: worktree-manager TASK_ID/BASE_BRANCH validation.
|
|
28
|
+
- ship: platform-API health checks instead of log-grep rollback DoS.
|
|
29
|
+
- skillers: transcript redaction pipeline (ported from consult).
|
|
30
|
+
- onboard + can-i-help: explicit argv arrays in collector git invocations.
|
|
31
|
+
- perf: command-parser error message accuracy.
|
|
32
|
+
- debate: SKILL.md routes AI CLI invocations through consult's hardened ACP transport.
|
|
33
|
+
|
|
12
34
|
## [5.10.0] - 2026-04-26
|
|
13
35
|
|
|
14
36
|
### Security
|
package/lib/binary/index.js
CHANGED
|
@@ -19,6 +19,22 @@
|
|
|
19
19
|
* script validates every zip entry before extracting it and rejects
|
|
20
20
|
* absolute, UNC, and parent-traversal entries.
|
|
21
21
|
*
|
|
22
|
+
* Verification chain (in order, each gate must pass to proceed):
|
|
23
|
+
* 1. TLS - https.get() pins the GitHub CA chain at the OS level.
|
|
24
|
+
* 2. SHA-256 sidecar - `<asset>.sha256` fetched from the same release and
|
|
25
|
+
* verified against the downloaded bytes. Closes basic tampering.
|
|
26
|
+
* 3. SLSA build provenance (optional / required) - `gh attestation verify`
|
|
27
|
+
* checks the Sigstore-signed attestation that agent-analyzer's release
|
|
28
|
+
* workflow publishes via `actions/attest-build-provenance`. This closes
|
|
29
|
+
* the "stolen release token uploads attacker binary + attacker sha256"
|
|
30
|
+
* hole that steps 1 and 2 cannot see.
|
|
31
|
+
*
|
|
32
|
+
* SLSA verification is SOFT by default: if `gh` is not on PATH we log
|
|
33
|
+
* a warning and proceed with just SHA-256. Set env var
|
|
34
|
+
* `AGENT_ANALYZER_REQUIRE_ATTESTATION=1` to make a missing `gh` a hard
|
|
35
|
+
* failure (recommended for CI). A present `gh` that reports a failed
|
|
36
|
+
* verification is ALWAYS a hard failure regardless of the env var.
|
|
37
|
+
*
|
|
22
38
|
* @module lib/binary
|
|
23
39
|
*/
|
|
24
40
|
|
|
@@ -572,6 +588,117 @@ function findBinaryInScratch(scratch, binaryBaseName) {
|
|
|
572
588
|
return null;
|
|
573
589
|
}
|
|
574
590
|
|
|
591
|
+
// ---------------------------------------------------------------------------
|
|
592
|
+
// SLSA build provenance verification
|
|
593
|
+
// ---------------------------------------------------------------------------
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Result of an attempted SLSA attestation verification.
|
|
597
|
+
* @typedef {Object} SlsaResult
|
|
598
|
+
* @property {'verified'|'skipped'|'failed'} status
|
|
599
|
+
* @property {string} [reason] human-readable detail (for skipped/failed)
|
|
600
|
+
* @property {string} [stderr] captured stderr from `gh` (failed only)
|
|
601
|
+
*/
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Default runner: spawn `gh attestation verify` and return the captured
|
|
605
|
+
* exit code, stdout, and stderr. Injectable for tests.
|
|
606
|
+
* @param {string} filePath
|
|
607
|
+
* @param {string} repo e.g. `agent-sh/agent-analyzer`
|
|
608
|
+
* @returns {{ status: number|null, stdout: string, stderr: string }}
|
|
609
|
+
*/
|
|
610
|
+
function defaultGhRunner(filePath, repo) {
|
|
611
|
+
try {
|
|
612
|
+
const stdout = cp.execFileSync(
|
|
613
|
+
'gh',
|
|
614
|
+
['attestation', 'verify', filePath, '--repo', repo, '--format', 'json'],
|
|
615
|
+
{
|
|
616
|
+
encoding: 'utf8',
|
|
617
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
618
|
+
timeout: 60000,
|
|
619
|
+
windowsHide: true
|
|
620
|
+
}
|
|
621
|
+
);
|
|
622
|
+
return { status: 0, stdout: stdout || '', stderr: '' };
|
|
623
|
+
} catch (err) {
|
|
624
|
+
return {
|
|
625
|
+
status: typeof err.status === 'number' ? err.status : null,
|
|
626
|
+
stdout: err.stdout ? String(err.stdout) : '',
|
|
627
|
+
stderr: err.stderr ? String(err.stderr) : (err.message || '')
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Returns true if the `gh` CLI is on PATH. Uses a short, non-privileged probe.
|
|
634
|
+
* @param {function} [runner] optional probe; defaults to real `gh --version`
|
|
635
|
+
* @returns {boolean}
|
|
636
|
+
*/
|
|
637
|
+
function isGhAvailable(runner) {
|
|
638
|
+
if (typeof runner === 'function') {
|
|
639
|
+
try { return !!runner(); } catch (e) { return false; }
|
|
640
|
+
}
|
|
641
|
+
try {
|
|
642
|
+
cp.execFileSync('gh', ['--version'], {
|
|
643
|
+
stdio: 'ignore',
|
|
644
|
+
timeout: 5000,
|
|
645
|
+
windowsHide: true
|
|
646
|
+
});
|
|
647
|
+
return true;
|
|
648
|
+
} catch (e) {
|
|
649
|
+
return false;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Verify a downloaded asset's SLSA build provenance attestation via the
|
|
655
|
+
* GitHub CLI. The check is SOFT by default: if `gh` is not installed the
|
|
656
|
+
* function returns { status: 'skipped' } and the caller logs a warning. Set
|
|
657
|
+
* `requireAttestation` (or the env var) to make a missing `gh` a failure.
|
|
658
|
+
*
|
|
659
|
+
* A present `gh` that reports verification failure ALWAYS returns
|
|
660
|
+
* { status: 'failed' } regardless of `requireAttestation`; the caller is
|
|
661
|
+
* expected to abort in that case.
|
|
662
|
+
*
|
|
663
|
+
* @param {string} filePath absolute path to the downloaded archive
|
|
664
|
+
* @param {Object} [options]
|
|
665
|
+
* @param {string} [options.repo] e.g. `agent-sh/agent-analyzer`
|
|
666
|
+
* @param {boolean} [options.requireAttestation] defaults to env
|
|
667
|
+
* `AGENT_ANALYZER_REQUIRE_ATTESTATION === '1'`
|
|
668
|
+
* @param {function} [options.ghRunner] injectable runner for tests. Receives
|
|
669
|
+
* (filePath, repo), returns { status, stdout, stderr }.
|
|
670
|
+
* @param {function} [options.ghProbe] injectable gh-on-PATH probe for tests.
|
|
671
|
+
* @returns {SlsaResult}
|
|
672
|
+
*/
|
|
673
|
+
function verifySlsaAttestation(filePath, options) {
|
|
674
|
+
const opts = options || {};
|
|
675
|
+
const repo = opts.repo || GITHUB_REPO;
|
|
676
|
+
const runner = typeof opts.ghRunner === 'function' ? opts.ghRunner : defaultGhRunner;
|
|
677
|
+
const require_ = typeof opts.requireAttestation === 'boolean'
|
|
678
|
+
? opts.requireAttestation
|
|
679
|
+
: process.env.AGENT_ANALYZER_REQUIRE_ATTESTATION === '1';
|
|
680
|
+
|
|
681
|
+
const ghPresent = isGhAvailable(opts.ghProbe);
|
|
682
|
+
if (!ghPresent) {
|
|
683
|
+
const reason = '`gh` CLI not found on PATH';
|
|
684
|
+
if (require_) {
|
|
685
|
+
return { status: 'failed', reason: reason + ' (AGENT_ANALYZER_REQUIRE_ATTESTATION=1)' };
|
|
686
|
+
}
|
|
687
|
+
return { status: 'skipped', reason: reason };
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
const result = runner(filePath, repo);
|
|
691
|
+
if (result && result.status === 0) {
|
|
692
|
+
return { status: 'verified' };
|
|
693
|
+
}
|
|
694
|
+
return {
|
|
695
|
+
status: 'failed',
|
|
696
|
+
reason: 'gh attestation verify exited with status ' +
|
|
697
|
+
(result && result.status !== null ? result.status : 'unknown'),
|
|
698
|
+
stderr: (result && result.stderr) || ''
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
|
|
575
702
|
// ---------------------------------------------------------------------------
|
|
576
703
|
// Download + install
|
|
577
704
|
// ---------------------------------------------------------------------------
|
|
@@ -582,11 +709,19 @@ function findBinaryInScratch(scratch, binaryBaseName) {
|
|
|
582
709
|
* @param {Object} [options]
|
|
583
710
|
* @param {boolean} [options.skipChecksum=false] LOCAL DEV ONLY. Skips the
|
|
584
711
|
* `.sha256` sidecar fetch and verification. NEVER set this in production.
|
|
712
|
+
* @param {boolean} [options.skipAttestation=false] LOCAL DEV ONLY. Skips the
|
|
713
|
+
* SLSA attestation check entirely.
|
|
714
|
+
* @param {boolean} [options.requireAttestation] when true, a missing `gh`
|
|
715
|
+
* CLI becomes a hard failure. Defaults to
|
|
716
|
+
* `process.env.AGENT_ANALYZER_REQUIRE_ATTESTATION === '1'`.
|
|
717
|
+
* @param {function} [options.ghRunner] injectable runner for tests.
|
|
718
|
+
* @param {function} [options.ghProbe] injectable gh-on-PATH probe for tests.
|
|
585
719
|
* @returns {Promise<string>} path to the installed binary
|
|
586
720
|
*/
|
|
587
721
|
async function downloadBinary(ver, options) {
|
|
588
722
|
const opts = options || {};
|
|
589
723
|
const skipChecksum = opts.skipChecksum === true;
|
|
724
|
+
const skipAttestation = opts.skipAttestation === true;
|
|
590
725
|
|
|
591
726
|
const platformKey = getPlatformKey();
|
|
592
727
|
if (!platformKey) {
|
|
@@ -643,6 +778,47 @@ async function downloadBinary(ver, options) {
|
|
|
643
778
|
verifySha256(buf, expected, filename);
|
|
644
779
|
}
|
|
645
780
|
|
|
781
|
+
// --- 2b. Verify SLSA build provenance (optional / required) ------------
|
|
782
|
+
if (skipAttestation) {
|
|
783
|
+
process.stderr.write(
|
|
784
|
+
'[WARN] skipAttestation=true - SLSA verification disabled. ' +
|
|
785
|
+
'This is LOCAL DEV ONLY and MUST NOT be used in production.\n'
|
|
786
|
+
);
|
|
787
|
+
} else {
|
|
788
|
+
// `gh attestation verify` needs a real file. Persist buf to a tmp path,
|
|
789
|
+
// verify, then drop it. Extraction continues from the in-memory buf so
|
|
790
|
+
// we don't need the tmp file beyond the verify call.
|
|
791
|
+
const attestDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-analyzer-slsa-'));
|
|
792
|
+
const attestFile = path.join(attestDir, filename);
|
|
793
|
+
try {
|
|
794
|
+
fs.writeFileSync(attestFile, buf);
|
|
795
|
+
const result = verifySlsaAttestation(attestFile, {
|
|
796
|
+
repo: GITHUB_REPO,
|
|
797
|
+
requireAttestation: opts.requireAttestation,
|
|
798
|
+
ghRunner: opts.ghRunner,
|
|
799
|
+
ghProbe: opts.ghProbe
|
|
800
|
+
});
|
|
801
|
+
if (result.status === 'verified') {
|
|
802
|
+
process.stderr.write('[OK] SLSA attestation verified for ' + filename + '\n');
|
|
803
|
+
} else if (result.status === 'skipped') {
|
|
804
|
+
process.stderr.write(
|
|
805
|
+
'[WARN] SLSA attestation check skipped: ' + result.reason + '. ' +
|
|
806
|
+
'Install the GitHub CLI (`gh`) to enable provenance verification. ' +
|
|
807
|
+
'Set AGENT_ANALYZER_REQUIRE_ATTESTATION=1 to require it.\n'
|
|
808
|
+
);
|
|
809
|
+
} else {
|
|
810
|
+
// 'failed'
|
|
811
|
+
throw new Error(
|
|
812
|
+
'SLSA attestation verification failed for ' + filename + ': ' +
|
|
813
|
+
result.reason + '. Refusing to execute binary.' +
|
|
814
|
+
(result.stderr ? '\n--- gh stderr ---\n' + result.stderr : '')
|
|
815
|
+
);
|
|
816
|
+
}
|
|
817
|
+
} finally {
|
|
818
|
+
rmrf(attestDir);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
646
822
|
// --- 3. Extract to isolated scratch dir + validate entries -------------
|
|
647
823
|
const binaryBaseName = path.basename(binPath);
|
|
648
824
|
let scratch;
|
|
@@ -707,7 +883,13 @@ async function ensureBinary(options) {
|
|
|
707
883
|
}
|
|
708
884
|
}
|
|
709
885
|
|
|
710
|
-
return downloadBinary(targetVer, {
|
|
886
|
+
return downloadBinary(targetVer, {
|
|
887
|
+
skipChecksum: opts.skipChecksum === true,
|
|
888
|
+
skipAttestation: opts.skipAttestation === true,
|
|
889
|
+
requireAttestation: opts.requireAttestation,
|
|
890
|
+
ghRunner: opts.ghRunner,
|
|
891
|
+
ghProbe: opts.ghProbe
|
|
892
|
+
});
|
|
711
893
|
}
|
|
712
894
|
|
|
713
895
|
/**
|
|
@@ -730,11 +912,27 @@ function ensureBinarySync(options) {
|
|
|
730
912
|
|
|
731
913
|
const targetVer = (options && options.version) || ANALYZER_MIN_VERSION;
|
|
732
914
|
const skipChecksum = !!(options && options.skipChecksum);
|
|
915
|
+
const skipAttestation = !!(options && options.skipAttestation);
|
|
916
|
+
// Forward requireAttestation when explicitly set (tri-state: undefined
|
|
917
|
+
// lets the child fall back to the AGENT_ANALYZER_REQUIRE_ATTESTATION
|
|
918
|
+
// env var, matching ensureBinary()). Without this forwarding, a sync
|
|
919
|
+
// caller with requireAttestation:true would silently lose the hard-fail
|
|
920
|
+
// intent when gh is missing.
|
|
921
|
+
const requireAttestation = options && typeof options.requireAttestation === 'boolean'
|
|
922
|
+
? options.requireAttestation
|
|
923
|
+
: undefined;
|
|
733
924
|
const selfPath = __filename;
|
|
925
|
+
const ensureOpts = {
|
|
926
|
+
version: targetVer,
|
|
927
|
+
skipChecksum: skipChecksum,
|
|
928
|
+
skipAttestation: skipAttestation
|
|
929
|
+
};
|
|
930
|
+
if (requireAttestation !== undefined) {
|
|
931
|
+
ensureOpts.requireAttestation = requireAttestation;
|
|
932
|
+
}
|
|
734
933
|
const helperLines = [
|
|
735
934
|
'var b = require(' + JSON.stringify(selfPath) + ');',
|
|
736
|
-
'b.ensureBinary(
|
|
737
|
-
', skipChecksum: ' + JSON.stringify(skipChecksum) + ' })',
|
|
935
|
+
'b.ensureBinary(' + JSON.stringify(ensureOpts) + ')',
|
|
738
936
|
' .then(function(p) { process.stdout.write(p); })',
|
|
739
937
|
' .catch(function(e) { process.stderr.write(e.message); process.exit(1); });'
|
|
740
938
|
];
|
|
@@ -798,6 +996,8 @@ module.exports = {
|
|
|
798
996
|
assertSafeArchiveEntry,
|
|
799
997
|
assertInsideRoot,
|
|
800
998
|
downloadBinary,
|
|
999
|
+
verifySlsaAttestation,
|
|
1000
|
+
isGhAvailable,
|
|
801
1001
|
// Exported for tests only
|
|
802
1002
|
extractTarGzToScratch,
|
|
803
1003
|
extractZipToScratch,
|
package/lib/enhance/fixer.js
CHANGED
|
@@ -7,6 +7,37 @@
|
|
|
7
7
|
const fs = require('fs');
|
|
8
8
|
const path = require('path');
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Reject symlinks before read/write operations.
|
|
12
|
+
*
|
|
13
|
+
* Security: A hostile repo could symlink a fixable file (e.g. `agent.md`) to a
|
|
14
|
+
* sensitive target (e.g. `~/.ssh/authorized_keys`). A HIGH-certainty auto-fix
|
|
15
|
+
* would then silently overwrite that target. We refuse to follow symlinks on
|
|
16
|
+
* any path we intend to read from or write to, including `.backup` siblings.
|
|
17
|
+
*
|
|
18
|
+
* This is called both before opening and immediately before writing, which
|
|
19
|
+
* narrows - though does not fully close - the TOCTOU window between calls.
|
|
20
|
+
* Node's fs module does not expose a portable `O_NOFOLLOW` open flag, so
|
|
21
|
+
* repeated lstat is the cleanest available mitigation for text-file edits.
|
|
22
|
+
*
|
|
23
|
+
* @param {string} targetPath - Path to check.
|
|
24
|
+
* @throws {Error} If the path exists and is a symlink.
|
|
25
|
+
*/
|
|
26
|
+
function assertNotSymlink(targetPath) {
|
|
27
|
+
let stat;
|
|
28
|
+
try {
|
|
29
|
+
stat = fs.lstatSync(targetPath);
|
|
30
|
+
} catch (err) {
|
|
31
|
+
if (err.code === 'ENOENT') return; // Path does not yet exist - fine.
|
|
32
|
+
throw err;
|
|
33
|
+
}
|
|
34
|
+
if (stat.isSymbolicLink()) {
|
|
35
|
+
const err = new Error('target is a symlink; refusing to follow');
|
|
36
|
+
err.code = 'ESYMLINK_REFUSED';
|
|
37
|
+
throw err;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
10
41
|
function applyFixes(issues, options = {}) {
|
|
11
42
|
const { dryRun = false, backup = true } = options;
|
|
12
43
|
|
|
@@ -59,6 +90,23 @@ function applyFixes(issues, options = {}) {
|
|
|
59
90
|
continue;
|
|
60
91
|
}
|
|
61
92
|
|
|
93
|
+
// Security: refuse symlinks before we read, so a hostile repo can't
|
|
94
|
+
// redirect a HIGH-certainty fix at ~/.ssh/authorized_keys or similar.
|
|
95
|
+
try {
|
|
96
|
+
assertNotSymlink(filePath);
|
|
97
|
+
} catch (err) {
|
|
98
|
+
if (err.code === 'ESYMLINK_REFUSED') {
|
|
99
|
+
results.errors.push({
|
|
100
|
+
filePath,
|
|
101
|
+
error: err.message,
|
|
102
|
+
success: false,
|
|
103
|
+
reason: 'target is a symlink; refusing to follow'
|
|
104
|
+
});
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
throw err;
|
|
108
|
+
}
|
|
109
|
+
|
|
62
110
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
63
111
|
let data;
|
|
64
112
|
|
|
@@ -135,6 +183,8 @@ function applyFixes(issues, options = {}) {
|
|
|
135
183
|
// Create backup
|
|
136
184
|
if (backup) {
|
|
137
185
|
const backupPath = `${filePath}.backup`;
|
|
186
|
+
// Refuse if the backup slot itself is a pre-existing symlink.
|
|
187
|
+
assertNotSymlink(backupPath);
|
|
138
188
|
fs.writeFileSync(backupPath, content, 'utf8');
|
|
139
189
|
}
|
|
140
190
|
|
|
@@ -145,6 +195,11 @@ function applyFixes(issues, options = {}) {
|
|
|
145
195
|
} else {
|
|
146
196
|
newContent = JSON.stringify(modified, null, 2);
|
|
147
197
|
}
|
|
198
|
+
// Re-check immediately before write. Narrows the TOCTOU window
|
|
199
|
+
// between the initial lstat and this writeFileSync (an attacker
|
|
200
|
+
// who swaps the regular file for a symlink between calls will
|
|
201
|
+
// be caught here).
|
|
202
|
+
assertNotSymlink(filePath);
|
|
148
203
|
fs.writeFileSync(filePath, newContent, 'utf8');
|
|
149
204
|
}
|
|
150
205
|
|
|
@@ -280,7 +335,14 @@ function restoreFromBackup(filePath) {
|
|
|
280
335
|
return false;
|
|
281
336
|
}
|
|
282
337
|
|
|
338
|
+
// Security: refuse if either the backup or the restore target is a
|
|
339
|
+
// symlink. Same threat model as applyFixes - a malicious post-hoc swap
|
|
340
|
+
// could redirect the restore at a sensitive file.
|
|
341
|
+
assertNotSymlink(backupPath);
|
|
342
|
+
assertNotSymlink(filePath);
|
|
343
|
+
|
|
283
344
|
const backupContent = fs.readFileSync(backupPath, 'utf8');
|
|
345
|
+
assertNotSymlink(filePath);
|
|
284
346
|
fs.writeFileSync(filePath, backupContent, 'utf8');
|
|
285
347
|
fs.unlinkSync(backupPath);
|
|
286
348
|
|
|
@@ -717,5 +779,6 @@ module.exports = {
|
|
|
717
779
|
fixAggressiveEmphasis,
|
|
718
780
|
previewFixes,
|
|
719
781
|
restoreFromBackup,
|
|
720
|
-
cleanupBackups
|
|
782
|
+
cleanupBackups,
|
|
783
|
+
assertNotSymlink
|
|
721
784
|
};
|
package/package.json
CHANGED
package/site/content.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"url": "https://agent-sh.github.io/agentsys",
|
|
6
6
|
"repo": "https://github.com/agent-sh/agentsys",
|
|
7
7
|
"npm": "https://www.npmjs.com/package/agentsys",
|
|
8
|
-
"version": "5.
|
|
8
|
+
"version": "5.12.0",
|
|
9
9
|
"author": "Avi Fenesh",
|
|
10
10
|
"author_url": "https://github.com/avifenesh"
|
|
11
11
|
},
|