Skip to content

feat: native REST + DataLoader + comment hooks + strict input + custom fields + audit/jobs CLIs#492

Merged
tayebmokni merged 6 commits into
mainfrom
feat/api-hardening-clis
May 26, 2026
Merged

feat: native REST + DataLoader + comment hooks + strict input + custom fields + audit/jobs CLIs#492
tayebmokni merged 6 commits into
mainfrom
feat/api-hardening-clis

Conversation

@tayebmokni
Copy link
Copy Markdown
Contributor

Closes #78, #115, #152, #161, #162, #223, #273.

@tayebmokni tayebmokni enabled auto-merge (squash) May 26, 2026 22:10
@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:

  • apps/api/internal/rest/comments/hooks.go
  • packages/go/middleware/strictinput/middleware.go
  • packages/go/middleware/strictinput/middleware_test.go

tib0o0o and others added 6 commits May 27, 2026 00:14
Adds public read-only REST surfaces mirroring the posts contract:

  GET  /api/v1/users[/{id|handle}]
  GET  /api/v1/media[/{id}]                ?mime_class=image|video|document
  GET  /api/v1/terms[/{id}]                ?taxonomy=&parent_id=&search=
  GET  /api/v1/taxonomies[/{slug}]
  GET  /api/v1/comments[/{id}]             ?post_id=

The users surface omits PII (email, password material, capabilities);
media omits uploader id + SHA256; terms surfaces ltree path + depth
for breadcrumb rendering without follow-up queries. Comments adds a
global read endpoint alongside the existing per-post submit/list.

All four packages share the cursor-based pagination shape from
router.Page[T] and the RFC-7807 error envelope from router.WriteError.
In-memory Store implementations back the unit tests; Pg-backed stores
will swap in via the same interfaces.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Tayeb Mokni <tayeb.mokni@gmail.com>
Extends apps/api/internal/graphql/dataloader with per-request batchers
for User, Term, Media, and TermsByPostID — each one a candidate N+1
dimension. Loaders embed atomic counters so a benchmark can read back
the per-resolver batch-round-trip count for budget enforcement.

Adds tools/graphql-budgets.yml as the single source of truth for
per-operation batch ceilings, plus a CI workflow that runs the
BenchmarkGraphQLBudgets harness on PRs that touch the GraphQL surface
or the budget file. The bench drives each operation with a
representative fan-out and fails the run if dataloader.Snapshot()
exceeds the configured budget.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Tayeb Mokni <tayeb.mokni@gmail.com>
)

Three layered moderation primitives wired into the public submit path:

  - pre_submit filter hook ("rest.comments.pre_submit") that plugins
    register through hooks.Bus. Handlers may (a) mutate the input,
    (b) hard-reject with ErrCommentRejected (→ 422), or (c) stamp a
    CommentVerdict that overrides the default classifier.
  - Duplicate-content gate: SHA-256 of normalised content + IP within
    a 5-minute window drops the second submission at 422. Anonymous
    only — logged-in users opt out.
  - IP redaction cron: RedactIPsBefore zeroes the last octet (IPv4)
    or last 80 bits (IPv6) of every comment row older than 30 days,
    preserving the /24 or /48 prefix for abuse-pattern triage.

The HookBus dependency is an interface, not a hard import, so the
package stays decoupled from packages/go/hooks. DupChecker is
similarly optional. MemoryStore implements both for tests + the no-DB
fall-through.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Tayeb Mokni <tayeb.mokni@gmail.com>
Adds packages/go/middleware/strictinput — opt-in middleware engaged by
GONEXT_STRICT_INPUT=1 that enforces request-shape budgets in front of
the REST + GraphQL surfaces.

REST: JSON bodies under /api/v1/* must parse as a single JSON value
with no trailing data. The unknown-fields rejection stays at the
per-handler level (where the route's payload struct is the authority).

GraphQL: bodies must conform to the {query,variables,operationName,
extensions} envelope; any other top-level key is a 400. Variables and
extensions are bounded by depth (default 8) and recursive key count
(default 100) so a hostile client cannot bury query-cost under a
deeply nested variable tree.

Disabled by default so the gate can land incrementally; flipping the
env var on once a deployment has audited its surface is a one-line
change.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Tayeb Mokni <tayeb.mokni@gmail.com>
New packages/go/customfields owns the runtime + storage primitives for
custom fields (GoNext's equivalent of ACF):

  - FieldGroup carries a draft 2020-12 JSON Schema describing what a
    group's meta blob may contain.
  - Validate applies the schema to a candidate blob, returning every
    violation in one multi-error so the admin can fix N issues at
    once.
  - MetaStore interface persists (post_id, group_id) -> validated
    blob. MemoryStore backs tests; the new field_groups +
    post_meta_values tables back production (migration 000035).

apps/api/internal/rest/customfields wires the CRUD surface:

  /api/v1/custom-fields/groups        (list, create, get, patch, delete)
  /api/v1/posts/{id}/meta[/{group}]   (list, get, put — schema-validated)

PUT meta paths validate against the resolved group's schema before
persistence; schema violations return 422 with the multi-error.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Tayeb Mokni <tayeb.mokni@gmail.com>
gonext audit tail (#223):
  Pulls recent events from the audit store; --follow polls every 1s
  using the last observed event's timestamp as the cursor. --json
  emits one event per line for jq pipelines; default is tab-delimited.
  Default limit is 50 events; --limit caps at 1000. Filters cover
  --type, --actor, --plugin, --severity, --since.

gonext jobs (#273):
  - queue:  list configured queues + size/active/pending/scheduled/
            retry/archived counts via asynq.Inspector.
  - failed: list archived (DLQ) tasks per queue with last error.
  - drain:  delete every archived task (with confirmation; --yes
            skips the prompt).
  - cron:   print the registered cron schedules from the boot
            snapshot.
  - plugin: aggregate per-plugin task counts by scanning archived
            task types' "{plugin}." prefix.

Inspector is an interface so each subcommand can be unit-tested
without a live Redis. Audit's tail likewise takes an injected store
factory for the same reason.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Tayeb Mokni <tayeb.mokni@gmail.com>
@tayebmokni tayebmokni force-pushed the feat/api-hardening-clis branch from 2d9a3d8 to 9ebce7f Compare May 26, 2026 22:15
@tayebmokni tayebmokni merged commit d034a12 into main May 26, 2026
14 of 19 checks passed
@tayebmokni tayebmokni deleted the feat/api-hardening-clis branch May 26, 2026 22:15
tayebmokni pushed a commit that referenced this pull request May 28, 2026
cli/gonext/cmd/audit/audit.go and verify.go both declared the same
package-level symbols — ExitOK, ExitFail, ExitUsage, usage, Run,
RunOS — because the verify.go landed in PR #492 carrying its own
copy of the entry-point boilerplate that was already in audit.go.
\`go vet\` rejects this with \"X redeclared in this block\" and CI
lint-go has been red on main since the merge.

Make audit.go the single owner of the package surface:
- Add \`case \"verify\": return runVerify(args[1:], stdout, stderr)\`
  to the dispatch switch.
- Extend the usage banner with the verify subcommand and its env
  requirement (GONEXT_AUDIT_HMAC_KEY).

Strip verify.go down to just \`runVerify\` and its imports —
everything else (exit codes, Run, RunOS, usage) is owned by
audit.go.

go vet ./... clean on cli/gonext after this change. cli/gonext/cmd/
audit unit tests still pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Tayeb Mokni <tayeb.mokni@gmail.com>
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.

REST API: comments, media, users, terms

2 participants