Skip to content

fix: exempt /metrics /healthz /readyz from auth; numeric UID in Dockerfile#148

Merged
michaelmcnees merged 11 commits into
mainfrom
fix/chores-infra
May 31, 2026
Merged

fix: exempt /metrics /healthz /readyz from auth; numeric UID in Dockerfile#148
michaelmcnees merged 11 commits into
mainfrom
fix/chores-infra

Conversation

@michaelmcnees

@michaelmcnees michaelmcnees commented May 31, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • /metrics, /healthz, and /readyz are now registered on a top-level mux that sits outside the auth middleware chain, so Kubernetes probes and Prometheus scrapers work without an API key
  • Dockerfile creates the mantle user with explicit -u 10001 -g 10001 so kubelet can verify runAsNonRoot: true against a numeric UID (matching the runAsUser: 10001 already in values.yaml)

What was already fixed

Test plan

  • go build ./... passes (no changes to business logic)
  • go test ./internal/server/... passes
  • go test ./internal/api/health/... passes
  • Kubernetes: curl http://pod:8080/metrics returns 200 without an Authorization header
  • Kubernetes: curl http://pod:8080/healthz and /readyz return 200 without auth
  • curl http://pod:8080/api/v1/workflows still returns 401 without auth

Closes #139
Closes #137

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Fixed Mailchimp integration to use the correct API endpoint format for member operations.
    • Improved Okta user creation to consistently handle activation parameter in all scenarios.
  • Chores

    • Updated Docker runtime configuration for consistent user/group ID assignment.
    • Reorganized API routing to better separate authenticated and unauthenticated endpoints.

Michael McNees and others added 4 commits May 30, 2026 20:22
…ks, and RabbitMQ connectors

Adds 17 new connector actions across 7 services:
- stripe/create_charge, stripe/create_customer, stripe/create_refund
- okta/list_users, okta/create_user
- shopify/list_orders, shopify/list_products, shopify/create_order
- mailchimp/list_members, mailchimp/add_member
- onedrive/upload, sharepoint/list_items
- quickbooks/create_invoice, quickbooks/list_invoices
- rabbitmq/publish, rabbitmq/consume

Also adds parseJSONBody shared helper to tools.go and
github.com/rabbitmq/amqp091-go v1.11.0 as the AMQP protocol client.

Closes #94, #108, #109, #111, #112, #114, #115

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- OneDrive: remove url.PathEscape on filePath to preserve literal "/"
  separators in Graph API path templates (escaping produced %2F which
  resolved to a file literally named "documents%2Fhello.txt")
- RabbitMQ: ack each message inside the consume loop when auto_ack=false
  so messages are not requeued when the channel closes on return

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- rabbitmq: validate params before dialing (exchange/routing_key/body for
  publish, queue for consume) so invalid requests fail without a connection
- rabbitmq: use amqp.DialConfig with net.Dialer.DialContext so the dial
  respects context cancellation
- rabbitmq: default auto_ack=false (at-least-once); true opts into at-most-once
- onedrive: add escapePath helper to encode individual path segments without
  escaping "/" separators, so nested folders work with Graph path addressing
- onedrive_test: assert request URL path to catch future slash-escaping regressions
- quickbooks: reject where/order_by values that contain structural keywords
  (maxresults, startposition, orderby) to prevent clause injection
- shopify: extract shopifyAPIURL package-level helper, removing three identical
  apiURL methods
- go.mod: go mod tidy promotes amqp091-go from indirect to direct

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…kerfile

- server: register /metrics, /healthz, and /readyz on a top-level mux so
  Kubernetes probes and Prometheus scrapers bypass API key auth entirely.
  All other routes fall through to the auth/rate-limit-wrapped apiMux.
- Dockerfile: create the mantle user with explicit -u 10001 -g 10001 and
  switch to USER 10001:10001 so kubelet can verify runAsNonRoot: true
  without a numeric runAsUser override in the Helm chart.

Closes #139
Closes #137

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@michaelmcnees, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 48 minutes and 25 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0b48aa06-4324-49e6-9035-2481baf87034

📥 Commits

Reviewing files that changed from the base of the PR and between 643a93b and 258e276.

📒 Files selected for processing (3)
  • .github/codeql/codeql-config.yml
  • packages/engine/internal/connector/mailchimp.go
  • packages/engine/internal/server/server.go
📝 Walkthrough

Walkthrough

This PR updates container security hardening, refactors two identity provider integrations (Mailchimp and Okta) to follow their respective API specifications, and restructures server request routing to separate authentication requirements for different endpoint categories.

Changes

Connector Updates and Server Routing

Layer / File(s) Summary
Container UID/GID hardening
packages/engine/Dockerfile
Dockerfile explicitly creates the mantle group and user with fixed UID/GID 10001 and runs the container as numeric USER 10001:10001 instead of the username form.
Mailchimp subscriber hash integration
packages/engine/internal/connector/mailchimp.go, packages/engine/internal/connector/mailchimp_test.go
Mailchimp add-member request switches from POST /lists/{listID}/members to PUT /lists/{listID}/members/{subscriberHash} where subscriberHash is the MD5 hash of the lowercase email, with test updated to expect the new hashed endpoint and request method.
Okta activate parameter enforcement
packages/engine/internal/connector/okta.go, packages/engine/internal/connector/okta_test.go
Okta user creation endpoint now unconditionally includes the activate query parameter via fmt.Sprintf formatting instead of conditionally appending it; existing test assertions updated and new TestOktaCreateUserConnector_ActivateFalse added to verify both activate=true and activate=false behaviors.
Server routing and authentication separation
packages/engine/internal/server/server.go
Server refactors Server.Start to build a dedicated apiMux containing only authenticated/rate-limited routes (Git webhook, API, OpenAPI), wraps it with metrics/auth/rate-limit middleware to form apiHandler, and routes all other requests through a top-level mux that serves /metrics, /healthz, and /readyz directly without auth.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • dvflw/mantle#141: Both PRs modify packages/engine/internal/server/server.go routing and auth logic around health endpoint registration and Server.Start structure.

🐰 A docker user with digits true,
Mailchimp hashes now come into view,
Okta's activate, clear as can be,
Routes separated—unauthenticated and free!
The server now knows which paths bypass the gate,
Security and clarity now calibrate. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly relates to and summarizes two significant changes in the changeset: exempting health/metrics endpoints from auth and using numeric UIDs in the Dockerfile.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/chores-infra

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b37d9fefbd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +125 to +126
if activate {
endpoint += "?activate=true"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor activate=false for Okta user creation

When a workflow passes activate: false, this code omits the query parameter entirely, but Okta's create-user API defaults activate to true when the parameter is absent. In that scenario the connector will still activate the user and potentially send activation/provisioning side effects, so the endpoint needs to include ?activate=false when the caller explicitly disables activation.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: changed endpoint construction to always pass ?activate=${activate} so activate=false is explicitly sent to Okta rather than relying on its default (which activates the user). Added a regression test for the false case.

Comment on lines +125 to +126
req, err := http.NewRequestWithContext(ctx, "POST",
c.apiURL(dc, fmt.Sprintf("/lists/%s/members", listID)),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use the upsert endpoint for Mailchimp members

For an email that is already in the audience, POST /lists/{list_id}/members returns a duplicate-member error rather than updating the existing contact, even though this connector is documented as adding or updating a subscriber. This makes common idempotent sync workflows fail on reruns; the add/update path should target the member resource (for example with the subscriber hash) using the Mailchimp upsert semantics.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: switched from POST /lists/{list_id}/members to PUT /lists/{list_id}/members/{subscriber_hash} (MD5 of lowercased email). The PUT endpoint is idempotent — it upserts rather than erroring on duplicate email addresses. Updated test to assert PUT method and correct path.

Michael McNees and others added 2 commits May 30, 2026 21:04
Okta: always pass ?activate=<bool> — omitting it when false causes Okta
to activate the user by default, triggering unintended provisioning side effects.

Mailchimp: switch add_member from POST /members to PUT /members/{hash}
(MD5 of lowercased email). The POST endpoint returns a duplicate-member
error on reruns; the PUT upsert endpoint is idempotent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Keep patched versions of okta.go and mailchimp.go that include the
Codex fixes (activate param and upsert endpoint) over the originals
that merged from PR #147.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread packages/engine/internal/connector/mailchimp.go Fixed
Michael McNees and others added 2 commits May 30, 2026 21:12
The Mailchimp subscriber hash is MD5(lowercase(email)) per Mailchimp's
own API specification — it is a lookup identifier, not a security hash.
Add inline suppression annotations for both gosec G401 and CodeQL
go/weak-sensitive-data-hashing to clarify the non-security intent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
G501 fires on the import line and G401 on the call site — both need
their own same-line #nosec annotations. The combined //nolint+#nosec
format wasn't parsed correctly by gosec.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread packages/engine/internal/connector/mailchimp.go Fixed
Add CodeQL[go/weak-sensitive-data-hashing] inline suppression alongside
the gosec #nosec annotation. The earlier attempt used lowercase 'codeql'
which GitHub Code Scanning does not recognize — the exact token is
'CodeQL[rule-id]' (capital C, Q, L).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread packages/engine/internal/connector/mailchimp.go Fixed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/engine/internal/server/server.go`:
- Around line 253-257: The /metrics endpoint is currently mounted directly on
the top-level mux with promhttp.Handler(), bypassing auth and leaking raw
r.URL.Path labels via metricsMiddleware/normalizePath; remove
mux.Handle("/metrics", promhttp.Handler()) and instead register the metrics
handler behind the same auth/metricsMiddleware path that wraps apiHandler so
requests to /metrics go through the auth middleware (see metricsMiddleware,
normalizePath, apiHandler and the auth middleware in
packages/engine/internal/auth/middleware.go) ensuring path labels are scrubbed
before being exposed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8eb69572-1684-4940-b687-954133dd1005

📥 Commits

Reviewing files that changed from the base of the PR and between e214fe4 and 643a93b.

📒 Files selected for processing (6)
  • packages/engine/Dockerfile
  • packages/engine/internal/connector/mailchimp.go
  • packages/engine/internal/connector/mailchimp_test.go
  • packages/engine/internal/connector/okta.go
  • packages/engine/internal/connector/okta_test.go
  • packages/engine/internal/server/server.go

Comment thread packages/engine/internal/server/server.go Outdated
…ent)

Use standalone // CodeQL[go/weak-sensitive-data-hashing] on the
preceding line as GitHub docs specify — no other text on that line.
Also add .github/codeql/codeql-config.yml as a fallback that excludes
the go/weak-sensitive-data-hashing query for the connector package.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread packages/engine/internal/connector/mailchimp.go Dismissed
Prometheus path labels include workflow names (via metricsMiddleware →
normalizePath). Serving /metrics on the unauthenticated top-level mux
let anyone enumerate workflow identifiers without credentials. Moved
/metrics to apiMux so it goes through the same auth middleware as all
other API routes. Health probes (/healthz, /readyz) remain on the
unauthenticated mux for Kubernetes probe access.

Prometheus scrapers must now supply an API key.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@michaelmcnees michaelmcnees merged commit 52d3860 into main May 31, 2026
21 checks passed
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.

Authenticated /metrics needs a Prometheus-friendly configuration path Helm chart runAsNonRoot fails with named Docker USER

2 participants