Newsletter Service Security Review
This service handles member PII (recipient emails and names in transit)
and exposes deliberately unauthenticated endpoints: a leak is a privacy
incident, and parts of the surface are reachable by anyone on the internet.
Those facts set the stakes for every security judgment here.
Methodology
Run a focused, diff-aware review, not a whole-repo audit:
- Only new risk. Assess what this PR introduces or weakens. Do not
relitigate pre-existing issues the diff does not touch (at most a
nit).
- Assume hostile input, report only what is real. Flag only
high-confidence, concretely exploitable findings: if you cannot trace a
path from an attacker-controlled input to a sensitive sink, it is not
critical/high.
- Three passes.
- Context: discover, from the code and the repo docs at review time, the
guards this service relies on around the diff (format checks,
validators, body caps, parameterized queries, signature verification).
Never assume a guard exists; find it.
- Comparative: does the change deviate from the guard patterns the
surrounding code establishes?
- Assessment: trace each input to its sink and confirm a guard sits on
the path the data actually takes, not three functions away.
- Confidence-gate every finding (1-10, report only >= 7). A few real
findings beat a speculative list.
- Evidence, not vibes. Each finding names the file and function, what
the attacker controls, the boundary crossed, the concrete impact, and the
fix.
Durable threat anchors
These are the kinds of boundaries that make a diff security-relevant in this
service. They describe the service's shape, not its current line-level
guards; verify the concrete mechanism in the code each time.
- The unauthenticated surface. Two routes are reachable by anyone with no
session: the open-tracking pixel (guarded by an opaque recipient hash) and one-click
unsubscribe (guarded by an HMAC-signed token, and it writes a project-scoped
opt-out). Weakening either token check, returning detail to an anonymous caller,
broadening what an anonymous write can do, or adding any new unauthenticated route
is the top of the severity scale.
- Authentication. JWT verification in the middleware, its toggle, and the
deliberate decision not to forward the bearer to downstream NATS calls.
Watch for weakened validation (audience, expiry, algorithms), library
errors echoed to unauthenticated callers, and any path that starts
forwarding or logging a bearer.
- Authorization lives at the gateway. The service runs no access checks
of its own; the chart's Heimdall RuleSet is the entire authorization model.
A handler or route change must keep the route shape the RuleSet gates on;
a chart change to the rules is an authorization change outright.
- Recipient PII. Raw emails move through the service in process and over
NATS during dispatch. Open tracking persists only an opaque recipient hash, but
the unsubscribe table persists the lowercased email (project-scoped) — so a
durable email store already exists, and its exposure, how it is matched, and the
path that reaches it are in scope. Any new path that logs, returns, or stores an
email or name beyond that, or weakens a hash, is a leak. The newsletter HTML body
travels to real inboxes: if a PR changes how it is rendered or what gets
interpolated into it, injection into the email becomes the question.
- SQL and schema. Queries go through a parameterized builder; any
hand-built SQL string carrying input is injection. The schema's CHECK
constraints and uniqueness rules are security guards as well as data
rules; a migration that drops one deserves the same scrutiny as code.
- Input bounds. The service bounds request bodies, list sizes, field
lengths, and fan-out. A new input path that bypasses the established
decode/validation helpers, or a loosened cap with no reason, is a finding.
- Secrets and config. Configuration is read in one documented place;
secrets (DB credentials) must never appear
in logs, responses, or plaintext chart values. Chart changes that weaken
the network policy or expose a route that should sit behind the gateway
count here too.
What not to flag
Signal discipline keeps the reviewer trusted. Do not raise:
- Denial of service, resource exhaustion, or "add rate limiting" on their
own. (Unbounded unauthenticated writes are flagged as data integrity,
not load.)
- Mere lack of hardening or defense-in-depth with no concrete vulnerability.
- Outdated third-party dependencies (managed separately); a new
dependency's risk belongs to the architecture lens.
- Theoretical race or timing issues with no practical exploit.
- Test-only files, Markdown, and docs.
- Log spoofing, regex-DoS, and missing audit logs.
- SSRF that only controls a path; it counts when the attacker controls host
or protocol.
Precedents: UUIDs are unguessable and need no validation (an authorization
finding rests on a missing check, not on guessing an id); environment
variables and config are trusted inputs; logging URLs and non-PII is fine.
Reporting
For each finding give the file and function, what the attacker controls, the
boundary crossed, the concrete impact on this service, and the fix. If the
diff does not touch an anchor above, do not invent a finding for it.
1---2name: newsletter-security-review3description: Security review for lfx-v2-newsletter-service pull requests. Use when a PR touches a handler, auth, persistence, the dispatch path, recipient data, config, or the chart. Applies a diff-aware, high-confidence, low-false-positive methodology (adapted from Anthropic's claude-code-security-review) to this service's durable threat anchors: recipient PII, the deliberately unauthenticated endpoints, JWT verification, gateway-delegated authorization, SQL construction, and secrets. Discovers the concrete guards from the code at review time; this skill carries the method, not an inventory.4---56<!-- Copyright The Linux Foundation and each contributor to LFX. -->7<!-- SPDX-License-Identifier: MIT -->89# Newsletter Service Security Review1011This service handles **member PII** (recipient emails and names in transit)12and exposes **deliberately unauthenticated endpoints**: a leak is a privacy13incident, and parts of the surface are reachable by anyone on the internet.14Those facts set the stakes for every security judgment here.1516## Methodology1718Run a focused, **diff-aware** review, not a whole-repo audit:19201. **Only new risk.** Assess what this PR introduces or weakens. Do not21 relitigate pre-existing issues the diff does not touch (at most a `nit`).222. **Assume hostile input, report only what is real.** Flag only23 high-confidence, concretely exploitable findings: if you cannot trace a24 path from an attacker-controlled input to a sensitive sink, it is not25 `critical`/`high`.263. **Three passes.**27 - *Context*: discover, from the code and the repo docs at review time, the28 guards this service relies on around the diff (format checks,29 validators, body caps, parameterized queries, signature verification).30 Never assume a guard exists; find it.31 - *Comparative*: does the change deviate from the guard patterns the32 surrounding code establishes?33 - *Assessment*: trace each input to its sink and confirm a guard sits on34 the path the data actually takes, not three functions away.354. **Confidence-gate every finding** (1-10, report only >= 7). A few real36 findings beat a speculative list.375. **Evidence, not vibes.** Each finding names the file and function, what38 the attacker controls, the boundary crossed, the concrete impact, and the39 fix.4041## Durable threat anchors4243These are the kinds of boundaries that make a diff security-relevant in this44service. They describe the service's shape, not its current line-level45guards; verify the concrete mechanism in the code each time.4647- **The unauthenticated surface.** Two routes are reachable by anyone with no48 session: the open-tracking pixel (guarded by an opaque recipient hash) and one-click49 unsubscribe (guarded by an HMAC-signed token, and it writes a project-scoped50 opt-out). Weakening either token check, returning detail to an anonymous caller,51 broadening what an anonymous write can do, or adding any new unauthenticated route52 is the top of the severity scale.53- **Authentication.** JWT verification in the middleware, its toggle, and the54 deliberate decision not to forward the bearer to downstream NATS calls.55 Watch for weakened validation (audience, expiry, algorithms), library56 errors echoed to unauthenticated callers, and any path that starts57 forwarding or logging a bearer.58- **Authorization lives at the gateway.** The service runs no access checks59 of its own; the chart's Heimdall RuleSet is the entire authorization model.60 A handler or route change must keep the route shape the RuleSet gates on;61 a chart change to the rules is an authorization change outright.62- **Recipient PII.** Raw emails move through the service in process and over63 NATS during dispatch. Open tracking persists only an opaque recipient hash, but64 the unsubscribe table persists the lowercased email (project-scoped) — so a65 durable email store already exists, and its exposure, how it is matched, and the66 path that reaches it are in scope. Any new path that logs, returns, or stores an67 email or name beyond that, or weakens a hash, is a leak. The newsletter HTML body68 travels to real inboxes: if a PR changes how it is rendered or what gets69 interpolated into it, injection into the email becomes the question.70- **SQL and schema.** Queries go through a parameterized builder; any71 hand-built SQL string carrying input is injection. The schema's CHECK72 constraints and uniqueness rules are security guards as well as data73 rules; a migration that drops one deserves the same scrutiny as code.74- **Input bounds.** The service bounds request bodies, list sizes, field75 lengths, and fan-out. A new input path that bypasses the established76 decode/validation helpers, or a loosened cap with no reason, is a finding.77- **Secrets and config.** Configuration is read in one documented place;78 secrets (DB credentials) must never appear79 in logs, responses, or plaintext chart values. Chart changes that weaken80 the network policy or expose a route that should sit behind the gateway81 count here too.8283## What not to flag8485Signal discipline keeps the reviewer trusted. Do not raise:8687- Denial of service, resource exhaustion, or "add rate limiting" on their88 own. (Unbounded *unauthenticated* writes are flagged as data integrity,89 not load.)90- Mere lack of hardening or defense-in-depth with no concrete vulnerability.91- Outdated third-party dependencies (managed separately); a *new*92 dependency's risk belongs to the architecture lens.93- Theoretical race or timing issues with no practical exploit.94- Test-only files, Markdown, and docs.95- Log spoofing, regex-DoS, and missing audit logs.96- SSRF that only controls a path; it counts when the attacker controls host97 or protocol.9899Precedents: UUIDs are unguessable and need no validation (an authorization100finding rests on a missing check, not on guessing an id); environment101variables and config are trusted inputs; logging URLs and non-PII is fine.102103## Reporting104105For each finding give the file and function, what the attacker controls, the106boundary crossed, the concrete impact on this service, and the fix. If the107diff does not touch an anchor above, do not invent a finding for it.