Skip to content

feat: gap fill — bundle guards + SQL linter + passkeys + OTel + post edit screen - #484

Merged
tayebmokni merged 5 commits into
mainfrom
feat/gap-bundle-sqllint-passkeys-otel-postedit
May 26, 2026
Merged

feat: gap fill — bundle guards + SQL linter + passkeys + OTel + post edit screen#484
tayebmokni merged 5 commits into
mainfrom
feat/gap-bundle-sqllint-passkeys-otel-postedit

Conversation

@tayebmokni

Copy link
Copy Markdown
Contributor

Closes #27, #53, #159, #186, #35.

Summary

Test plan

  • go test ./... for the four touched Go packages
  • pnpm test in apps/admin (566 tests pass)
  • Boot the API with GONEXT_OTLP_ENDPOINT set — confirm spans land on the collector
  • Click "Add passkey" in /settings/account and verify the WebAuthn ceremony round-trips

🤖 Generated with Claude Code

tib0o0o and others added 5 commits May 26, 2026 20:04
The .gnplugin loader at cli/gonext/internal/plugintest accepted
arbitrarily-large archives and never validated entry names. A
hostile bundle could:

  * exhaust process memory with a multi-gigabyte file or a zip-bomb
    entry (small compressed, huge declared size);
  * exhaust the file-descriptor pool with a million zero-byte
    entries;
  * extract files outside the bundle root via "../" or "/etc/..."
    paths (zip-slip, https://snyk.io/research/zip-slip).

This change adds three independent caps — 50 MiB total, 10 MiB
per entry, 10,000 entries — plus a path-safety check that rejects
any entry whose raw name starts with "/" or contains a ".."
segment in either / or \ form.

Each cap has a dedicated sentinel error (ErrBundleTooLarge,
ErrBundleEntryTooLarge, ErrBundleTooManyEntries,
ErrBundleUnsafePath) so the admin UI can branch on the failure
shape and produce a friendlier message.

Read paths (ReadManifest, ReadWASM) now also pass through a
LimitReader-backed cap so directory-form bundles (which have no
central directory to pre-validate) are equally protected.

Closes #27.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Tayeb Mokni <tayeb.mokni@gmail.com>
Plugin-supplied migrations run inside the same database as core
GoNext, so a careless or malicious plugin could drop core tables,
shadow core relations, or rewrite core indexes. Per
docs/02-plugin-system.md §3.3, every DDL statement in a plugin's
migrations must target an object whose name begins with
`plugin_<slug>_`.

This change adds a regex-based static linter
(packages/go/plugins/lifecycle/sqllint.go) that enforces the
prefix on CREATE/ALTER/DROP TABLE, CREATE/DROP INDEX, and rejects
the forbidden-verb kill-list (CREATE FUNCTION/VIEW/EXTENSION/
TRIGGER, GRANT, TRUNCATE, ALTER SCHEMA, ...). Schema qualifiers
and quoted identifiers are stripped before the check so
`CREATE TABLE public."plugin_seo_x"` passes while `CREATE TABLE
users` is rejected.

Wire-up into the Migrator runner lands in a follow-up — this PR
ships the linter as a standalone function so the bundle-parser
issue (#27) and the lifecycle storage (#44) can plug it in
without coupling.

Closes #53.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Tayeb Mokni <tayeb.mokni@gmail.com>
apps/api/cmd/server/main.go imported otelhttp transitively but never
initialised a tracer provider, so the otelhttp middleware spans
silently fell through to the global no-op tracer. This change adds a
packages/go/tracing package that:

  * reads `GONEXT_OTLP_ENDPOINT` (or Options.Endpoint),
  * stands up an OTLP HTTP exporter pointed at it,
  * wraps it in a batched SDK TracerProvider with the binary's
    `service.name` / `service.version` resource attributes,
  * installs the W3C TraceContext + Baggage composite propagator
    globally so incoming `traceparent` headers thread through every
    downstream client,
  * returns a Shutdown closer the binary registers with the
    shutdown orchestrator for graceful flush.

When the env var is unset the package returns a no-op Shutdown but
still installs the W3C propagator — partial rollouts where this
service has tracing disabled but its neighbours don't will still
propagate trace IDs end-to-end.

main.go wires the whole mux through otelhttp.NewHandler with a
custom SpanNameFormatter that prefers the matched ServeMux pattern
over the raw URL path, keeping span-name cardinality bounded across
the /api/v1/posts/{id} family.

Closes #186.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Tayeb Mokni <tayeb.mokni@gmail.com>
GoNext's auth surface shipped password / TOTP / OAuth but had no
phishing-resistant credential. This change adds end-to-end
WebAuthn / passkey support:

  * packages/go/auth/webauthn — Service wrapping the upstream
    go-webauthn library, MemoryStore for tests + the boot-time
    in-memory dev loop, Record/User adapter types.
  * apps/api/internal/auth/webauthn — four HTTP routes wiring
    the ceremony (register/begin, register/finish, login/begin,
    login/finish) plus the admin list+delete surface
    (GET/DELETE /api/v1/auth/webauthn/credentials[/{id}]). The
    handler enforces row-level ownership before delete and
    rejects discoverable-login probes.
  * migrations/000035_webauthn_credentials — new
    webauthn_credentials table with a unique index on
    credential_id (assertion lookup) and a per-user index for
    the admin list.
  * apps/admin /settings/account — page with the PasskeyList
    component, an "Add passkey" form that drives
    navigator.credentials.create + .get, and per-row Remove.
    Pasted into the settings overview as a fifth card.

The handler keeps ceremony state in a separate SessionStore
(Redis in prod, in-memory in tests) keyed by a random 16-byte
ceremony id; the user id is part of the key so a stolen
ceremony id from user A cannot be replayed against user B.

Closes #159.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Tayeb Mokni <tayeb.mokni@gmail.com>
The post-detail page shipped as a metadata-only stub — no block
editor, no autosave, no PATCH wiring. This change turns it into a
real edit screen:

  * BlockEditCanvas (from @gonext/blocks-editor) renders the
    content_blocks tree with the paragraph + heading core blocks
    registered via defaultCoreBlocks. The editor takes its own
    "Add your first block" affordance when the tree is empty.
  * useAutosave wires the canvas to /api/v1/posts/{id}/autosave
    with the package defaults (30s interval, 1.5s debounce). The
    page-head row carries a tiny AutosaveStatusPip the user can
    glance at to confirm their work is safe.
  * The "Save changes" button issues a PATCH to /api/v1/posts/{id}
    with the current title / slug / status / content_blocks; the
    handler returns the freshly-saved row and our state stays in
    sync with the server.
  * The settings overview adds a fifth "Account" card so the
    passkey-management surface from #159 has a navigable entry
    point.

apps/admin/package.json gains @gonext/blocks-editor +
@gonext/blocks-sdk as workspace deps so the import paths resolve
under tsc.

Closes #35.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Tayeb Mokni <tayeb.mokni@gmail.com>
@tayebmokni
tayebmokni enabled auto-merge (squash) May 26, 2026 18:23
@github-actions

Copy link
Copy Markdown

Heads up — this PR touches strings that often signal a security disclosure (vulnerab, CVE-, exploit, bypass, or auth bypass).

If this PR fixes or describes a real vulnerability that has not yet been publicly disclosed, please stop and use the private path:

  1. Open a private security advisory, or
  2. Email security@gonext.io with subject [SECURITY] GoNext - <summary>.

See /SECURITY.md for the full disclosure flow and /docs/16-bug-bounty.md for bounty terms.

If this is a false positive (test fixture, doc update, release notes, etc.) please ignore this comment — the check is advisory only and does not block the PR.

Matched files:

  • cli/gonext/internal/plugintest/bundle.go

@tayebmokni
tayebmokni merged commit d725574 into main May 26, 2026
16 of 25 checks passed
@tayebmokni
tayebmokni deleted the feat/gap-bundle-sqllint-passkeys-otel-postedit branch May 26, 2026 21:39
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.

Plugin bundle: .gnplugin format parser, size caps, hash, ZIP-slip-safe extraction

2 participants