Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 4 additions & 11 deletions .github/workflows/build-publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,36 +11,29 @@ on:
jobs:
checks:
runs-on: ubuntu-latest
env:
PNPM_CONFIG_MINIMUM_RELEASE_AGE: 0

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Enable corepack
run: corepack enable

- name: Set up Node.js
uses: actions/setup-node@v5
with:
node-version: 24
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
cache: npm

- name: Set up Helm
uses: azure/setup-helm@v4
with:
version: v3.16.3

- name: Install dependencies
run: pnpm install --frozen-lockfile
run: npm ci

- name: Lint
run: pnpm lint
run: npm run lint

- name: Test
run: pnpm test
run: npm test

- name: Helm lint
run: make helm-lint
Expand Down
19 changes: 10 additions & 9 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@

## Commands

- Install: `pnpm install --frozen-lockfile`
- Start: `pnpm start`
- Lint: `pnpm lint`
- Test: `pnpm test`
- Install: `npm ci`
- Start: `npm start`
- Lint: `npm run lint`
- Test: `npm test`
- Helm lint: `make helm-lint` or `helm lint charts/readability-js-server`
- Helm template: `make helm-template` or `helm template readability-js-server charts/readability-js-server`
- Memory soak: `make soak` or `node scripts/memory-soak.js --requests 100 --concurrency 2 --sample-every 10`
Expand All @@ -26,20 +26,21 @@ The Makefile mirrors those workflows with `make install`, `make start`, `make li

## Testing expectations

- Run `pnpm lint`, `pnpm test`, and the Helm verification targets for any docs, API, config, chart, or dependency change.
- Run `npm run lint`, `npm test`, and the Helm verification targets for any docs, API, config, chart, or dependency change.
- Add or update tests when a change touches response shape, error normalization, URL validation, sanitization, redirect handling, concurrency gating, or config parsing.
- Use the memory soak script when a change could affect allocation behavior or long-run stability.

## Memory and security constraints

- Keep the default SSRF protections: private-network blocking on, absolute `http:`/`https:` URLs only, redirect limits, HTML-only fetches, body-size limits, and timeouts.
- Keep DOMPurify sanitization in place.
- Keep jsdom parsing free of external resource loading and script execution.
- Keep sanitize-html sanitization in place with the iframe/video allowlist.
- Keep linkedom parsing free of external resource loading and script execution.
- Preserve the current `iframe` and `video` allowlist only if the tests still cover the intended surface.
- Treat any memory growth that appears only under load as a regression candidate until a longer soak shows it is expected allocator behavior.

## Dependency and DOM policy

- `@mozilla/readability@0.6.0` was already the latest npm release at the time of the uplift. Do not upgrade it casually.
- Only widen DOMPurify or Readability/jsdom behavior with a clear reason and tests.
- Keep the container on Node 24 and the repo on pnpm 11.7.0 unless the branch explicitly changes runtime policy.
- Only widen sanitize-html or Readability/linkedom behavior with a clear reason and tests.
- Keep the container on Node 24.
- linkedom 0.18.x and sanitize-html 2.17.x are the supported parser and sanitizer pins; sanitize-html upstream is archived, do not expect backports.
11 changes: 2 additions & 9 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,9 @@ FROM node:24-alpine AS deps

WORKDIR /application

ENV PNPM_HOME="/pnpm"
ENV PATH="${PNPM_HOME}:${PATH}"
ENV PNPM_CONFIG_MINIMUM_RELEASE_AGE=0
COPY package.json package-lock.json ./

RUN corepack enable \
&& corepack prepare pnpm@11.7.0 --activate

COPY package.json pnpm-lock.yaml ./

RUN pnpm install --frozen-lockfile --prod
RUN npm ci --omit=dev

FROM node:24-alpine

Expand Down
15 changes: 9 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
.PHONY: test

check: lint test helm-verify

install:
pnpm install --frozen-lockfile
npm ci

test:
pnpm test
npm test

start:
pnpm start
npm start

check: lint test helm-verify
lint:
pnpm lint
npm run lint

lint-fix:
pnpm lint:fix
npm run lint:fix

HELM_CHART ?= charts/readability-js-server
HELM_RELEASE_NAME ?= readability-js-server
Expand Down
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ At the time of this uplift, `@mozilla/readability@0.6.0` was already the latest
## Overview

- Runtime: Node.js 24
- Package manager: pnpm 11.7.0
- Package manager: npm
- Web framework: Express 5
- HTML parsing: jsdom 29
- Sanitization: DOMPurify 3
- HTML parsing: linkedom 0.18
- Sanitization: sanitize-html 2.17
- Deployment image: `node:24-alpine`

The container runs as a non-root user and the service exposes `POST /` plus a lightweight `GET /healthz` probe endpoint. The Compose example uses that same `/healthz` path for its container healthcheck.
Expand Down Expand Up @@ -103,21 +103,21 @@ All configuration is driven by environment variables.
Example:

```bash
PORT=3000 MAX_CONCURRENT_REQUESTS=20 pnpm start
PORT=3000 MAX_CONCURRENT_REQUESTS=20 npm start
```

## Local development

Prerequisites:

- Node.js 24
- pnpm 11.7.0
- npm (bundled with Node.js)

Install and start:

```bash
pnpm install --frozen-lockfile
pnpm start
npm ci
npm start
```

The server starts on `http://localhost:3000/` by default.
Expand Down Expand Up @@ -161,11 +161,11 @@ For the chart itself:

## Testing

Run the lint and test suites with pnpm:
Run the lint and test suites with npm:

```bash
pnpm lint
pnpm test
npm run lint
npm test
```

Run the Helm chart checks with the Makefile:
Expand Down Expand Up @@ -221,9 +221,9 @@ For Docker Compose setup, see [`examples/compose.yaml`](examples/compose.yaml).
- Redirects are followed manually and capped.
- Upstream responses must be HTML.
- Upstream bodies are capped by byte size and timeout.
- Article HTML is sanitized with DOMPurify.
- Article HTML is sanitized with sanitize-html. No external resources are loaded; no script execution is triggered.
- `iframe` and `video` tags are intentionally allowed, along with a narrow attribute allowlist.
- jsdom is used with its default disabled external-loading and script-execution behavior during parsing.
- linkedom is used for parsing with no external resource loading or script execution.

This service is still an untrusted content fetcher. Do not relax the defaults without tests that cover the new risk.

Expand Down
Loading
Loading