Skip to content

fix: step 8 — address-label caps + zip guard, global-error, safe decode, codegen escaping, @DomainID, trimmed profile - #52

Merged
chriskehayias merged 7 commits into
devfrom
fix/step8-fork-low-risk-items
Oct 10, 2026
Merged

chriskehayias merged 7 commits into
devfrom
fix/step8-fork-low-risk-items

Conversation

@chriskehayias

Copy link
Copy Markdown
Contributor

Closes the Step 8 TODO from the 2026-09-30 playbook audit. One commit per item.

Summary

Address labels (5f20cbd, 9cc2879)

  • Labels and config sent by the client are now validated with Zod (schemas.ts → parseLabelInput): at most 10,000 labels, bounded strings, serviceType limited to SERVICE_TYPES, and barStates/barType dropped (only the server computes those). A shared prepareLabels helper also caps startPosition at the stock's labels per sheet. It's a count of blank cells, so before this it was an unbounded page count.
  • Zip-bomb guard (src/lib/zip-guard.ts): reads the central directory itself before PizZip sees the file. It caps the number of entries (1,000) and the declared uncompressed total (64 MB), then inflates each entry with maxOutputLength set to its declared size, so a header that lies about its size fails fast. PizZip only checks sizes after inflating each file fully into memory.
  • Body size: the template limit drops from 5 MB to 2 MB, and experimental.serverActions.bodySizeLimit goes from the 1 MB default to 4 MB, just under Vercel's 4.5 MB request cap. The old 5 MB check could never fire. All limits now live in address-labels/limits.ts, shared by client and server.
  • Also fixed (rule 14): preEncodeBarcodes was logging label names and ZIP/routing digits on encode fallbacks. It now logs { labelIndex, error: <name> }.

App (5f3992b)

  • global-error.tsx: adds a plain <a href="/"> that works without JS. "Try again" stays disabled until hydration (useSyncExternalStore).
  • tool-params.server.ts: a stray % in recordDescription threw URIError and crashed the tool page. It now falls back to the raw value.

Codegen (efa6923): every MP metadata value interpolated into generated types goes through codegen-escape.ts (propertyKey, commentText, positiveInt). Before this, col.Size went into the Zod .max(...) calls as raw code. Output is unchanged for ordinary metadata.

SQL (e181994): api_dev_GetProcedureDefinition now takes @DomainID INT first; _INSTALL regenerated. It must be re-deployed to MP.

Profile (7f22608): the MPUserProfile sent to the client is now First_Name, Nickname, Last_Name, Email_Address. That drops User_ID, User_GUID, Contact_ID, Mobile_Phone, Image_GUID, roles and user groups, none of which were displayed. UserService runs one query instead of three.

Docs (docs: commit): reference docs updated; TODO deleted and INDEX.md updated.

Notes / follow-ups

  • Responses aren't covered: Vercel's 4.5 MB cap applies to response bodies too, and a very large PDF/merged .docx returned as base64 could exceed it. This PR doesn't address that.
  • UserMenu isn't rendered anywhere at the moment, so the profile trim has no visible effect today.

Test plan

  • npm run lint
  • npm run typecheck
  • npm run test:coverage: 2161 passed, 99.12% statements. New: zip-guard unit tests against real archives, a merge integration test with real PizZip/docxtemplater (sample template merges, forged-size bomb rejected), label-validation tests, global-error SSR test, codegen-escape tests
  • npm run build (production build succeeds with the new next.config)

🤖 Generated with Claude Code

chriskehayias and others added 7 commits October 10, 2026 12:03
…unzip

- Validate caller-supplied labels and config with Zod (parseLabelInput):
  count capped at MAX_LABELS (10,000), every string bounded, barStates/
  barType dropped (they are computed server-side), serviceType restricted
  to SERVICE_TYPES, startPosition bounded by the stock's sheet size (it
  is a count of blank cells, so it was an unbounded page count).
- New src/lib/zip-guard.ts walks the template's central directory and
  inflates each entry with maxOutputLength = its declared size BEFORE
  PizZip sees it, capping entry count and total uncompressed bytes. A
  zip bomb, or an entry that lies about its size, now fails fast.
- Template limit 5 MB -> 2 MB, with experimental.serverActions
  .bodySizeLimit raised from the 1 MB default to 4 MB (under Vercel's
  4.5 MB request cap). The old 5 MB check could never be reached.
  Limits live together in address-labels/limits.ts.
- preEncodeBarcodes logged label names and ZIP/routing digits on encode
  fallbacks (rule 14); it now logs the label index and error name.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…Description decode

- global-error.tsx: add a plain <a href="/"> that works without JS, and
  render "Try again" disabled until hydration (useSyncExternalStore), so
  a click on the server-rendered page is not silently swallowed.
- tool-params.server.ts: decodeURIComponent threw URIError on a stray
  "%" in recordDescription and crashed the tool page; fall back to the
  raw value.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
generate-types.ts wrote table names, FK targets, permission strings and
column sizes into generated source verbatim. A newline or "*/" in a
name could close a comment early, a quote in a column name broke out of
the quoted property key, and col.Size went straight into `.max(...)` in
the Zod schemas as executable code. Route every interpolation through
new scripts/codegen-escape.ts: propertyKey() (JSON-quoted keys),
commentText() (no line terminators or */), positiveInt() (sizes).
Output is byte-identical for ordinary metadata.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…inition

Every api_* procedure must take @domainID INT first so the MP API can
inject it. Regenerated _INSTALL/ministryplatform-install.sql. The proc
must be re-deployed to an MP instance for the change to take effect.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… test

Building an 80 MB archive timed out under coverage. A header that
understates its size is fast to build and is the case that a declared-
size check alone would miss; the honest-but-huge case stays covered in
zip-guard.test.ts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
getCurrentUserProfile sent User_ID, User_GUID, Contact_ID, Mobile_Phone,
Image_GUID and the user's security roles and user groups to the client.
Nothing displayed them, and roles/groups describe the domain's security
setup. MPUserProfile is now First_Name, Nickname, Last_Name and
Email_Address; UserService selects only those, maps the row explicitly,
and drops the two dp_User_Roles / dp_User_User_Groups queries.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Address-label limits/schemas/zip guard, barcode log shape, global-error,
safe recordDescription decode, codegen escaping, and the trimmed
MPUserProfile (single dp_Users query, no roles/groups). Also corrects
tool-params.md claims already stale (NaN handling, page-data log).
Deletes .claude/TODO/2026-09-30-step8-fork-specific-low-risk-items.md
and updates INDEX.md.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Oct 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@chriskehayias
chriskehayias merged commit 3218b2b into dev Oct 10, 2026
2 checks passed
@chriskehayias
chriskehayias deleted the fix/step8-fork-low-risk-items branch October 10, 2026 16:35
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.

1 participant