Skip to content

feat: Todo MVP — Express REST API, in-memory store, and frontend#1

Draft
algorithm-conduction wants to merge 1 commit into
mainfrom
hydra/spec
Draft

feat: Todo MVP — Express REST API, in-memory store, and frontend#1
algorithm-conduction wants to merge 1 commit into
mainfrom
hydra/spec

Conversation

@algorithm-conduction
Copy link
Copy Markdown
Owner

Summary

Implements the Todo MVP as specified in the OpenSpec change proposal. A Node.js + Express REST API exposes four CRUD endpoints backed by an in-memory store, a vanilla HTML/JS single-page frontend is served as static files, and a Vitest integration test suite covers all acceptance scenarios. No database or authentication is required for this MVP.

Spec Reference

https://github.com/algorithm-conduction/todo-app/blob/hydra/spec/openspec/changes/todo-mvp/design.md

Changes

  • package.json — project manifest; declares express, supertest, vitest; sets "type": "module" for ES module support
  • src/store.js — in-memory todo store with list, create, update, remove, reset methods; EUPL-1.2 header
  • src/server.js — Express app with GET /api/todos, POST /api/todos, PATCH /api/todos/:id, DELETE /api/todos/:id; serves static frontend; EUPL-1.2 header
  • public/index.html — single-page frontend with add/complete/delete interactions via the REST API; EUPL-1.2 header
  • test/api.test.js — Vitest + supertest integration tests covering all four endpoints and error paths; EUPL-1.2 header

Test Coverage

  • test/api.test.js — POST creates todo (201 + body shape), POST rejects missing title (400), GET lists todos (200 + array), GET returns empty array, PATCH marks completed (200), PATCH 404 on unknown id, DELETE removes todo (204 + absent from list), DELETE 404 on unknown id

…frontend

Adds all four CRUD endpoints (GET/POST/PATCH/DELETE /api/todos), an in-memory
store, a vanilla HTML/JS single-page frontend, and Vitest integration tests
covering every acceptance scenario from the spec.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@clydebarcode-conduction
Copy link
Copy Markdown

[WARNING] Missing HTTP Security Headers
Tool: Manual review (Semgrep p/security-audit returned 0 findings)
Rule: OWASP A05:2021 – Security Misconfiguration
File: src/server.js
Issue: No security-headers middleware is configured. The app does not send X-Content-Type-Options, X-Frame-Options, Strict-Transport-Security, or Referrer-Policy headers. Without these, browsers lack standard defences against MIME-type sniffing, clickjacking, and information leakage via the Referer header.
Fix: Add the helmet package (npm i helmet) and mount it early: app.use(helmet()). Tune the contentSecurityPolicy option to match the app's actual resource origins.

@clydebarcode-conduction
Copy link
Copy Markdown

[WARNING] No Authentication or Authorization on Any API Endpoint
Tool: Manual review
Rule: OWASP A01:2021 – Broken Access Control
File: src/server.js
Issue: All four CRUD endpoints (GET /api/todos, POST /api/todos, PATCH /api/todos/:id, DELETE /api/todos/:id) are completely unauthenticated. Any network-reachable client can enumerate, create, modify, and destroy all todos with no identity check. If the service is ever exposed beyond localhost this becomes a direct data-integrity risk.
Fix: Add an authentication middleware layer (e.g. JWT bearer token verification, session cookie, or API-key check) before the route handlers. For a shared deployment, also add per-user scoping so one user cannot modify another's todos.

@clydebarcode-conduction
Copy link
Copy Markdown

[SUGGESTION] Missing Type Validation on completed Field in PATCH Handler
Tool: Manual review
Rule: OWASP A03:2021 – Injection / Input Validation
File: src/server.js:34
Issue: const { completed } = req.body; accepts any JSON value for completed (string, array, nested object) with no type check. Object.assign in store.js:20 will then persist whatever was supplied. This can corrupt the data model and cause unexpected client-side rendering behaviour.
Fix: Validate the field before updating: if (typeof completed !== 'boolean') return res.status(400).json({ error: 'completed must be a boolean' });

@clydebarcode-conduction
Copy link
Copy Markdown

[SUGGESTION] No Rate Limiting on Write Endpoints
Tool: Manual review
Rule: OWASP A04:2021 – Insecure Design
File: src/server.js
Issue: POST, PATCH, and DELETE /api/todos have no rate-limiting middleware. Because todos are stored in an unbounded in-memory array, an unauthenticated attacker can flood POST /api/todos to exhaust Node.js heap memory and crash the process (unauthenticated DoS).
Fix: Add express-rate-limit (npm i express-rate-limit) on write routes, e.g. limit to 60 requests per minute per IP, and add a hard cap on the total number of stored todos in store.create.

@clydebarcode-conduction
Copy link
Copy Markdown

[SUGGESTION] No Content Security Policy
Tool: Manual review
Rule: OWASP A05:2021 – Security Misconfiguration
File: public/index.html
Issue: No Content-Security-Policy header or <meta> tag is present. The frontend currently uses textContent (safe), but the absence of a CSP removes a critical last-resort defence if XSS is ever introduced in a future change.
Fix: Deliver a CSP header from the server (via helmet.contentSecurityPolicy) with a strict policy such as default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline' (tighten as needed).

@clydebarcode-conduction
Copy link
Copy Markdown

Tool scan summary

Tool Findings
Semgrep (p/security-audit, p/secrets, p/owasp-top-ten) 0
Gitleaks 0
Trivy not run (no results file)

No secrets, hardcoded credentials, or SAST-flagged vulnerabilities detected by automated tools. Manual review identified 2 WARNINGs and 3 SUGGESTIONs above.

XSS note: public/index.html:51 uses span.textContent (not innerHTML) to render todo titles — this is safe and not an XSS vector.


{ "pass": true, "blocking": [] }

@juanclaude-conduction
Copy link
Copy Markdown

Hydra Code Review — Juan Claude van Damme

PR: feat: Todo MVP — Express REST API, in-memory store, and frontend
Spec Reference: openspec/changes/todo-mvp/design.md
Test run: ✅ 8/8 tests pass (vitest run)


[WARNING] No lint script defined in package.json
File: package.json
Issue: There is no lint script in package.json and no ESLint (or equivalent) devDependency. Style violations and potential bugs cannot be caught automatically in CI.
Fix: Add ESLint as a devDependency and add "lint": "eslint src test" to the scripts section.


[WARNING] Unused import createRequire in server.js
File: src/server.js:5
Issue: import { createRequire } from 'module'; is present at line 5 but is never referenced anywhere in the file. This is dead code.
Fix: Remove line 5 entirely.


[WARNING] PATCH endpoint applies unvalidated completed value
File: src/server.js:34–35
Issue: const { completed } = req.body; destructures completed without type validation. If the caller omits completed or sends a non-boolean (e.g., an empty body {}), store.update(id, { completed: undefined }) is called, which Object.assigns undefined onto the todo, corrupting the completed field. The spec requires { completed: boolean }.
Fix: Add a guard before calling store.update:

if (typeof completed !== 'boolean') {
  return res.status(400).json({ error: 'completed must be a boolean' });
}

[WARNING] package-lock.json not committed
File: package.json
Issue: The lockfile was not added to the repository. Without package-lock.json, npm install resolves dependency versions at install time, producing non-reproducible builds — CI may pick up different patch versions of express, vitest, or supertest than were used during development.
Fix: Commit package-lock.json to the repository.


[SUGGESTION] No error handling in frontend fetch calls
File: public/index.html:34–87
Issue: All fetch() calls in the inline script lack .catch() handlers or HTTP status checks. If the API is unreachable or returns a 4xx/5xx, the UI silently fails with no feedback to the user.
Fix: Wrap each fetch in a try/catch and surface a brief error message in the UI on failure.

@juanclaude-conduction
Copy link
Copy Markdown

{ "pass": false, "blocking": [] }

No CRITICAL findings. All 8 tests pass. There are 4 WARNINGs (no lint script, unused import, unvalidated PATCH input, missing lockfile) that should be fixed before merge, but none are hard blockers per the severity definitions. Marking pass: false to flag the WARNINGs for the author’s attention before final merge.

@juanclaude-conduction
Copy link
Copy Markdown

Verdict (corrected — supersedes previous verdict comment):

{ "pass": true, "blocking": [] }

No CRITICAL findings. All 8 tests pass. 4 WARNINGs should be addressed before merge but are not hard blockers per the severity definitions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants