@trohde/earos 1.3.0 → 1.3.2

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.
@@ -0,0 +1,210 @@
1
+ ---
2
+ title: "EaROS CLI comprehensive code review: 28 findings across security, performance, architecture, type safety, agent parity"
3
+ category: security-issues
4
+ date: 2026-03-23
5
+ tags:
6
+ - code-review
7
+ - path-traversal
8
+ - typescript-strict-mode
9
+ - dead-code-removal
10
+ - performance-optimization
11
+ - cli-architecture
12
+ - express-server
13
+ - agent-parity
14
+ - type-safety
15
+ - earos-cli
16
+ severity: high
17
+ component:
18
+ - tools/editor/src/serve.ts
19
+ - tools/editor/bin.js
20
+ - tools/editor/src/init.ts
21
+ - tools/editor/src/export-docx.ts
22
+ - tools/editor/manifest-cli.mjs
23
+ - tools/editor/src/utils/validate.ts
24
+ - tools/editor/src/utils/export-markdown.ts
25
+ - tools/editor/src/components/AssessmentSummary.tsx
26
+ - tools/editor/package.json
27
+ - tools/editor/tsconfig.server.json
28
+ root_cause_type: accumulated-tech-debt
29
+ resolution_type: multi-phase-refactor
30
+ ---
31
+
32
+ # EaROS CLI Comprehensive Code Review — 28 Findings
33
+
34
+ ## Problem
35
+
36
+ A 6-agent code review of the EaROS CLI (`tools/editor/`, ~3300 lines TypeScript/JavaScript) identified 28 findings across security, performance, architecture, type safety, agent parity, and code simplification. The CLI had grown organically with no prior comprehensive review, accumulating technical debt across multiple concern areas.
37
+
38
+ **Key symptoms:**
39
+ - Path traversal bypass in `safeRepoPath()` — `startsWith` without trailing separator allowed access to sibling directories
40
+ - Express server bound to `0.0.0.0`, exposing file read/write API to the local network
41
+ - Dead TypeScript source files (`cli.ts`, `manifest-cli.ts`) that were never compiled, creating contributor confusion
42
+ - CLI export command hardcoded to artifact kind only — rubrics and evaluations rejected
43
+ - No structured JSON output for any CLI command, blocking agent/CI integration
44
+ - Sequential icon downloads in `earos init --icons` (3 cloud packages one-at-a-time)
45
+ - `strict: false` in TypeScript configs with 49+ `any` usages
46
+ - Silent validation bypass — schema compilation failure returned `valid: true`
47
+
48
+ ## Root Cause
49
+
50
+ Six independent root causes identified:
51
+
52
+ 1. **Security:** `safeRepoPath` used `abs.startsWith(repoRoot)` without appending a path separator. On a system where `repoRoot = /projects/EAROS`, the path `/projects/EAROS-evil/secret.yaml` would pass the check. The server's `app.listen(port)` call omitted the host parameter, defaulting to `0.0.0.0`. The `resolveLocalMermaidImage` function had no containment check for `..` segments after the `/icons/` prefix.
53
+
54
+ 2. **Dead code:** `src/cli.ts` (78 lines) was described as a "TypeScript source reference for bin.js" but was never in any `tsconfig.json` files array. It had diverged from `bin.js` (missing `init`, `export`, `manifest`, `dev` commands). Similarly, `manifest-cli.ts` existed alongside `manifest-cli.mjs` but was never compiled.
55
+
56
+ 3. **Agent parity:** The `earos export` command checked `if (artifactData?.kind !== 'artifact')` and rejected all other kinds. No `--json` flag existed on any command.
57
+
58
+ 4. **Performance:** The `for (const config of ICON_PACKAGES)` loop in `init.ts` awaited each download sequentially. The `createIconAliases` function used `.sort()[0]` (O(n log n)) instead of a linear max-scan (O(n)).
59
+
60
+ 5. **Type safety:** Both `tsconfig.json` and `tsconfig.server.json` had `strict: false`. The `getGateSeverity` function was duplicated in `AssessmentSummary.tsx` and `score-helpers.tsx`.
61
+
62
+ 6. **Silent fail-open:** In `validate.ts`, when `ajv.compile(schema)` threw an error, the catch block returned `null`, and the caller treated `null` as "valid" — meaning a broken schema silently validated everything.
63
+
64
+ ## Solution
65
+
66
+ Implemented across 5 phases, published as `@trohde/earos@1.3.0`.
67
+
68
+ ### Phase 1 — Security Hardening
69
+
70
+ **Path traversal fix** (the most critical finding):
71
+
72
+ ```typescript
73
+ // BEFORE (vulnerable)
74
+ function safeRepoPath(repoRoot: string, rawPath: string): string | null {
75
+ const decoded = decodeURIComponent(rawPath)
76
+ const abs = resolve(repoRoot, decoded)
77
+ if (!abs.startsWith(repoRoot)) return null // EAROS-evil passes!
78
+ return abs
79
+ }
80
+
81
+ // AFTER (fixed)
82
+ import { sep } from 'path'
83
+
84
+ function safeRepoPath(repoRoot: string, rawPath: string): string | null {
85
+ const decoded = decodeURIComponent(rawPath)
86
+ const abs = resolve(repoRoot, decoded)
87
+ if (abs !== repoRoot && !abs.startsWith(repoRoot + sep)) return null
88
+ return abs
89
+ }
90
+ ```
91
+
92
+ **Other security fixes:**
93
+ - Bound Express to `127.0.0.1` with `EAROS_HOST` env var override for Docker/WSL2
94
+ - Added `X-Content-Type-Options: nosniff` and `X-Frame-Options: DENY` headers
95
+ - Sanitized 500 error responses — no more filesystem path leaks
96
+ - Scoped body limits: 1MB default, 5MB for file writes, 25MB for exports
97
+ - Restricted `POST /api/file/*` to `.yaml`/`.yml` extensions only
98
+ - Added containment check to `resolveLocalMermaidImage` with `baseDir + sep`
99
+
100
+ ### Phase 2 — Dead Code Removal
101
+
102
+ - Deleted `src/cli.ts` (78 lines)
103
+ - Deleted `manifest-cli.ts` (diverged from `manifest-cli.mjs`)
104
+ - Updated `manifest-cli.mjs` header comment
105
+
106
+ ### Phase 3 — Agent Parity
107
+
108
+ - Extended `earos export` to handle `artifact`, `evaluation`, `core_rubric`, `profile`, and `overlay` kinds
109
+ - Added `--format md` for Markdown export (artifact and rubric)
110
+ - Added `--json` flag to `earos validate` and `earos manifest check`
111
+ - Added `earos manifest list [--json]` command
112
+ - Added `earos list evaluations [--json]` command
113
+
114
+ ### Phase 4 — Performance
115
+
116
+ ```typescript
117
+ // BEFORE: sequential icon downloads
118
+ for (const config of ICON_PACKAGES) {
119
+ const result = await downloadIconPackage(target, config)
120
+ }
121
+
122
+ // AFTER: parallel with Promise.allSettled
123
+ const settled = await Promise.allSettled(
124
+ ICON_PACKAGES.map(config => downloadIconPackage(target, config))
125
+ )
126
+ ```
127
+
128
+ - Replaced `sort()[0]` with linear max-scan in alias matching
129
+ - Lazy-loaded `ARTIFACT_SCHEMA` via `getArtifactSchema()` function
130
+
131
+ ### Phase 5 — Code Quality
132
+
133
+ - Enabled `strict: true` in `tsconfig.server.json` (compiled with zero errors)
134
+ - De-duplicated `getGateSeverity` — `AssessmentSummary.tsx` now imports from `score-helpers.tsx`
135
+ - Moved `mermaid` from `dependencies` to `devDependencies` (Vite bundles it into dist/)
136
+ - Fixed silent validation bypass to fail-closed:
137
+ ```typescript
138
+ // BEFORE: catch returns null → caller treats as valid
139
+ } catch { return null }
140
+
141
+ // AFTER: catch returns a validator that always fails
142
+ } catch (e) {
143
+ console.warn('[validate] Schema compilation failed:', e)
144
+ const failValidator = Object.assign(
145
+ () => false,
146
+ { errors: [{ instancePath: '', message: 'Schema compilation failed' }] }
147
+ ) as unknown as ReturnType<typeof ajv.compile>
148
+ cache.set(key, failValidator)
149
+ return failValidator
150
+ }
151
+ ```
152
+ - Added 5-second TTL cache for evaluation file endpoint scans
153
+
154
+ ## Verification Steps
155
+
156
+ 1. **Build:** `npm run build` completed with zero errors under `strict: true`
157
+ 2. **CLI commands:** Tested `earos validate` (human + `--json`), `earos export` (all 3 kinds + `--format md`), `earos manifest check --json`, `earos manifest list`, `earos list evaluations --json`
158
+ 3. **Browser tests:** Verified home screen, rubric editor (load EAROS-REFARCH-001), assessment wizard (4-step flow), evaluation viewer (19 criteria loaded), security headers present in HTTP responses, path traversal blocked (`../EAROS-evil/` returns 403), YAML-only write restriction enforced
159
+ 4. **Published** as `@trohde/earos@1.3.0` via CI pipeline (GitHub Actions)
160
+
161
+ ## Prevention Strategies
162
+
163
+ ### Path Traversal Prevention
164
+ - **Canonical pattern:** Always append `path.sep` before `startsWith` comparison. Wrap in a single `assertWithinDirectory(base, candidate)` utility.
165
+ - **CI check:** Grep for `.startsWith(` on path variables without adjacent `path.sep` concatenation.
166
+
167
+ ### Server Binding
168
+ - **Rule:** Every `app.listen()` must specify an explicit bind address. Single-argument `listen()` calls should fail linting.
169
+
170
+ ### Fail-Closed Validation
171
+ - **Rule:** Validation functions must never return "valid" on error. Every `catch` block in a validation path must either re-throw or return a failure result.
172
+ - **Test requirement:** Every validation function needs a test with a corrupt/missing schema asserting failure.
173
+
174
+ ### Agent Parity
175
+ - **Design principle:** Every CLI command must support `--json`. This is non-negotiable for agent-native tools.
176
+ - **Test:** Parameterized test asserting `--json` produces valid JSON for every registered command.
177
+
178
+ ### TypeScript Strict Mode
179
+ - **CI guardrail:** Verify `strict: true` stays enabled in `tsconfig.json`:
180
+ ```bash
181
+ node -e "const c = require('./tsconfig.json'); if (!c.compilerOptions.strict) process.exit(1)"
182
+ ```
183
+
184
+ ### Dead Code Detection
185
+ - **CI check:** Compare `.ts` files on disk against `tsc --listFiles` output. Any uncompiled `.ts` under `src/` fails the build.
186
+
187
+ ## Code Review Checklist (Express + CLI)
188
+
189
+ ### Express Server
190
+ - [ ] Every `app.listen()` specifies an explicit bind address
191
+ - [ ] File path validation uses `resolve()` + separator-appended `startsWith`
192
+ - [ ] Error handlers do not leak stack traces or internal paths
193
+ - [ ] Security headers set (`X-Content-Type-Options`, `X-Frame-Options`)
194
+ - [ ] Body parser limits scoped per route based on actual need
195
+
196
+ ### CLI Tool
197
+ - [ ] Every command supports `--json` for structured output
198
+ - [ ] Validation failures produce structured error output
199
+ - [ ] No `catch` block silently swallows errors in validation paths
200
+
201
+ ### TypeScript / Build
202
+ - [ ] `tsconfig.json` has `strict: true`
203
+ - [ ] Every `.ts` file under `src/` is included in compilation
204
+ - [ ] Shared logic lives in `utils/`, not duplicated across files
205
+
206
+ ## Related Documents
207
+
208
+ - **Prior art:** [Site review findings plan](../../docs/plans/2026-03-23-001-refactor-site-review-findings-plan.md) — same review-then-fix methodology applied to the `site/` frontend (26 findings, 8 agents)
209
+ - **Implementation plan:** [CLI review findings plan](../../docs/plans/2026-03-23-002-refactor-cli-review-findings-plan.md) — the detailed 5-phase plan for this work
210
+ - **Review agents used:** Security Sentinel, Performance Oracle, Architecture Strategist, Kieran TypeScript Reviewer, Agent-Native Reviewer, Code Simplicity Reviewer
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trohde/earos",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "description": "Schema-driven editor and CLI for EaROS architecture assessment rubrics and evaluations",
5
5
  "type": "module",
6
6
  "bin": {
package/serve.js CHANGED
@@ -55,7 +55,7 @@ export async function startServer(fileArg) {
55
55
  process.exit(1);
56
56
  }
57
57
  const app = express();
58
- app.use(express.json({ limit: '1mb' }));
58
+ app.use(express.json({ limit: '200mb' }));
59
59
  app.use((_req, res, next) => {
60
60
  res.setHeader('X-Content-Type-Options', 'nosniff');
61
61
  res.setHeader('X-Frame-Options', 'DENY');