Skip to content

Comments

init oidc module#1

Merged
kjdev merged 8 commits intomainfrom
develop
Feb 20, 2026
Merged

init oidc module#1
kjdev merged 8 commits intomainfrom
develop

Conversation

@kjdev
Copy link
Owner

@kjdev kjdev commented Feb 19, 2026

Summary by CodeRabbit

  • New Features

    • Added comprehensive OpenID Connect (OIDC) authentication module for Nginx
    • Support for multiple identity providers with configurable endpoints
    • Session management with memory and Redis backends
    • Token validation and claim extraction via Nginx variables
    • RP-initiated logout functionality
    • Status endpoint for monitoring cache and configuration
    • PKCE support for enhanced security
  • Documentation

    • Added comprehensive README with installation, configuration, and usage guides
    • Added example configurations for major providers (Google, Azure, Keycloak, Okta)
  • Tests

    • Added integration test suite covering authentication flows, logout, and multiple algorithms
  • Chores

    • Added Docker build configuration and GitHub Actions workflows
    • Added MIT license and code style configuration

@coderabbitai
Copy link

coderabbitai bot commented Feb 19, 2026

Important

Review skipped

This PR was authored by the user configured for CodeRabbit reviews. By default, CodeRabbit skips reviewing PRs authored by this user. It's recommended to use a dedicated user account to post CodeRabbit review feedback.

To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a full Nginx OpenID Connect module: discovery/JWKS caching, JWT verification (many algs), PKCE, session management (memory/Redis), HTTP subrequest helpers, URL/JSON/hash utilities, extensive tests, Docker + CI, and project formatting/config files.

Changes

Cohort / File(s) Summary
Project config & style
\.editorconfig, \.uncrustify.cfg, \.gitignore, LICENSE, config
Add EditorConfig, Uncrustify style, ignore /build/, MIT license, and nginx module build config.
CI & container
.github/workflows/test.yaml, .github/workflows/container.yaml, Dockerfile
Add GitHub Actions test and container workflows; multi-stage Dockerfile that builds nginx and bundles the dynamic OIDC module and runtime templates.
Module entry & config
src/ngx_http_oidc_module.c, src/ngx_http_oidc_module.h
New nginx module definition, directives, lifecycle hooks, access-phase handler registration, and shared-memory zones.
Auth handlers
src/ngx_oidc_handler_authenticate.*, src/ngx_oidc_handler_callback.*, src/ngx_oidc_handler_logout.*
Implement auth redirect (PKCE), callback state machine (code exchange, token verification, userinfo), and logout (including RP-initiated).
Status endpoint
src/ngx_oidc_handler_status.*
Status handler reporting metadata/JWKS and session-store stats for diagnostics.
Metadata & JWKS caching
src/ngx_oidc_metadata.*, src/ngx_oidc_jwks.*, src/ngx_oidc_jwt.*, src/ngx_oidc_hash.*
Discovery metadata cache with fetch/lock semantics; JWKS parsing and shared-memory key cache with OpenSSL key construction; JWT parsing/verification across RSA/PS/EC/OKP; hash utilities.
Session management & stores
src/ngx_oidc_session.*, src/ngx_oidc_session_store.*, src/ngx_oidc_session_store_memory.*, src/ngx_oidc_session_store_redis.*
Session CRUD, cookie handling (temporary/permanent), rotation, anti-replay marking; pluggable session-store abstraction and memory/Redis implementations and init helpers.
HTTP, JSON, URL, random
src/ngx_oidc_http.*, src/ngx_oidc_json.*, src/ngx_oidc_url.*, src/ngx_oidc_random.*
Subrequest helpers (GET/POST, bearer), response extraction, Jansson JSON parsing/validation, URL build/validation + CRLF sanitization, and cryptographic random/PKCE helpers.
Variables & provider resolution
src/ngx_oidc_variable.*, src/ngx_oidc_provider.*
Nginx variables exposing tokens/claims/userinfo and provider lookup helpers.
Tests & test config
t/*.t, t/conf/*
Integration test suite for auth flows, modes, logout, status, Redis store, algorithm matrix, and supporting nginx test configs.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Nginx as Nginx+OIDC
    participant IdP as Identity Provider
    participant SessionStore as Session Store
    participant Metadata as Metadata/JWKS Cache

    Client->>Nginx: GET /protected
    Nginx->>Nginx: access handler determines request type
    alt not authenticated
        Nginx->>Metadata: ensure discovery & JWKS
        Metadata-->>Nginx: metadata + jwks_uri
        Nginx->>SessionStore: store pre-auth (state, nonce, verifier)
        Nginx->>Client: 302 Redirect to IdP auth endpoint
        Client->>IdP: Authorization request
        IdP-->>Client: Redirect to callback (code,state)
        Client->>Nginx: GET /callback?code=...&state=...
        Nginx->>SessionStore: retrieve pre-auth data
        Nginx->>IdP: POST token exchange
        IdP-->>Nginx: id_token + access_token
        Nginx->>Metadata: fetch JWKS if needed
        Metadata-->>Nginx: JWKS keys
        Nginx->>Nginx: verify token signature & claims
        Nginx->>SessionStore: save tokens and rotate session
        Nginx->>Client: 302 Redirect to original URL with cookie
    else authenticated
        Nginx->>SessionStore: read session tokens
        SessionStore-->>Nginx: tokens present/valid
        Nginx->>Client: allow request (proxy to app)
    end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~150 minutes

Poem

🐰 I hopped through metadata, keys in paw,
I chased a nonce and danced with PKCE law,
I tucked tokens safe in cookie beds,
I cached JWKS and counted heads,
Nginx hums — the rabbit naps content and awed.


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

@kjdev kjdev force-pushed the develop branch 6 times, most recently from d6f5854 to 6ca17d3 Compare February 20, 2026 03:06
@kjdev
Copy link
Owner Author

kjdev commented Feb 20, 2026

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Feb 20, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 18

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (15)
.github/workflows/package.yaml-107-114 (1)

107-114: ⚠️ Potential issue | 🟡 Minor

github.actor may not be the package owner — use github.repository_owner.

github.actor is whoever triggered the run (e.g. a collaborator), not necessarily the org/user that owns the GHCR package. This will cause the cleanup to silently fail (masked by continue-on-error: true).

Proposed fix
       - name: Clean up
         uses: dataaxiom/ghcr-cleanup-action@cd0cdb900b5dbf3a6f2cc869f0dbb0b8211f50c4 `#v1.0.16`
         with:
-          owner: ${{ github.actor }}
+          owner: ${{ github.repository_owner }}
           package: ${{ github.event.repository.name }}/nginx
           token: ${{ github.token }}
           keep-n-untagged: 0
         continue-on-error: true
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/package.yaml around lines 107 - 114, The workflow step
"Clean up" uses dataaxiom/ghcr-cleanup-action and incorrectly sets the owner
input to `${{ github.actor }}`; change that to `${{ github.repository_owner }}`
so the package owner is the repository/org owner (update the owner field in the
"Clean up" step), leaving the rest of the inputs (package, token,
keep-n-untagged, continue-on-error) unchanged.
src/ngx_oidc_handler_logout.c-131-134 (1)

131-134: ⚠️ Potential issue | 🟡 Minor

Unreachable metadata == NULL guard at Line 134.

ctx->cached.metadata is already verified non-NULL at Line 122, then assigned to metadata at Line 129, and immediately passed (dereferenced) to ngx_oidc_metadata_get_end_session_endpoint(metadata) at Line 131. The metadata == NULL branch in the composite condition on Line 134 can never be true.

🔧 Suggested fix
-        if (metadata == NULL || end_session == NULL || end_session->len == 0) {
+        if (end_session == NULL || end_session->len == 0) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ngx_oidc_handler_logout.c` around lines 131 - 134, The check for metadata
== NULL is unreachable because ctx->cached.metadata was verified non-NULL
earlier and metadata is immediately passed to
ngx_oidc_metadata_get_end_session_endpoint; either remove metadata == NULL from
the composite condition or, alternatively, move the NULL check for metadata
before calling ngx_oidc_metadata_get_end_session_endpoint so you only call that
function when metadata is non-NULL. Update the condition around end_session (and
the surrounding logic that references end_session->len) to rely only on
end_session being NULL/empty or perform the metadata NULL guard prior to calling
ngx_oidc_metadata_get_end_session_endpoint(metadata).
src/ngx_oidc_hash.c-27-62 (1)

27-62: ⚠️ Potential issue | 🟡 Minor

hash_compute lacks NULL guards for r, input, output, and output_len.

If any caller passes NULL for these parameters (e.g., output not allocated large enough), the function will dereference them and crash. The public wrappers (ngx_oidc_hash_sha256 etc.) pass parameters through without validation.

🛡️ Suggested fix
 static ngx_int_t
 hash_compute(ngx_http_request_t *r, ngx_str_t *input,
     u_char *output, unsigned int *output_len, const EVP_MD *md,
     const char *algorithm_name)
 {
+    if (r == NULL || input == NULL || output == NULL || output_len == NULL) {
+        return NGX_ERROR;
+    }
+
     EVP_MD_CTX *ctx;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ngx_oidc_hash.c` around lines 27 - 62, hash_compute currently
dereferences r, input, output, and output_len without validation which can crash
if callers like ngx_oidc_hash_sha256 pass NULL or an undersized output buffer;
add upfront NULL checks in hash_compute for r, input, output, and output_len,
log a descriptive ngx_log_error (including algorithm_name) and return NGX_ERROR
if any are NULL, and additionally validate that *output_len is at least
EVP_MD_size(md) (log and return NGX_ERROR if too small) so callers (e.g.,
ngx_oidc_hash_sha256) cannot trigger a buffer overrun.
src/ngx_oidc_session.c-258-264 (1)

258-264: ⚠️ Potential issue | 🟡 Minor

Format specifier mismatch: %ui used for size_t value.

token_data.len is size_t, but %ui is for ngx_uint_t. Use %uz for size_t values in nginx logging.

Proposed fix
         } else {
             ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                            "oidc_session: moved userinfo to new session "
-                           "(%ui bytes)", token_data.len);
+                           "(%uz bytes)", token_data.len);
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ngx_oidc_session.c` around lines 258 - 264, The ngx_log_debug1 call in
the oidc session rotation path uses the wrong format specifier (%ui) for
token_data.len (a size_t) — update the format string in the ngx_log_debug1
invocation that logs "oidc_session: moved userinfo to new session" to use %uz
instead of %ui so the size_t value token_data.len is logged correctly; locate
the ngx_log_debug1 call in the session rotation/oidc_session code and change the
specifier only.
src/ngx_oidc_session.c-593-626 (1)

593-626: ⚠️ Potential issue | 🟡 Minor

Fragile buffer size: hardcoded "600" in sizeof will underflow if NGX_OIDC_PRE_AUTH_TIMEOUT changes.

The len calculation uses sizeof("; Path=/; HttpOnly; SameSite=Lax; Max-Age=600") which reserves exactly 3 characters for the number "600". If NGX_OIDC_PRE_AUTH_TIMEOUT is ever changed to a value with more digits (e.g., 3600), ngx_snprintf will silently truncate the cookie header, producing a malformed Set-Cookie. Use NGX_INT_T_LEN for the numeric portion instead.

Proposed fix
     /* Calculate Set-Cookie header length */
     len = cookie_name.len + sizeof("=") - 1 + cookie_value.len
-          + sizeof("; Path=/; HttpOnly; SameSite=Lax; Max-Age=600") - 1;
+          + sizeof("; Path=/; HttpOnly; SameSite=Lax; Max-Age=") - 1
+          + NGX_INT_T_LEN;
     if (secure) {
         len += sizeof("; Secure") - 1;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ngx_oidc_session.c` around lines 593 - 626, The Set-Cookie buffer length
calculation is fragile because it hardcodes the digits for
NGX_OIDC_PRE_AUTH_TIMEOUT; update the len computation to reserve NGX_INT_T_LEN
bytes for the numeric Max-Age instead of using sizeof("...600"), so that when
building the header with ngx_snprintf (in the block that formats "%V=%V; Path=/;
HttpOnly; ... Max-Age=%d") there is always enough space; adjust the base string
length to exclude the "600" literal, add NGX_INT_T_LEN (and still add the Secure
suffix when secure is true), and keep set_cookie->value allocation and
ngx_snprintf usage unchanged.
src/ngx_oidc_handler_authenticate.h-9-22 (1)

9-22: ⚠️ Potential issue | 🟡 Minor

Header is not self-contained: include ngx_http_oidc_module.h for type definition.

The function signature uses ngx_http_oidc_provider_t *provider (line 22) but this type is defined in ngx_http_oidc_module.h, which is not included. Any translation unit that includes only this header will fail to compile. Add #include "ngx_http_oidc_module.h" after the other includes.

Suggested fix
 `#include` <ngx_config.h>
 `#include` <ngx_core.h>
 `#include` <ngx_http.h>
+#include "ngx_http_oidc_module.h"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ngx_oidc_handler_authenticate.h` around lines 9 - 22, The header is
missing the definition for ngx_http_oidc_provider_t used by
ngx_oidc_handler_authenticate; add `#include` "ngx_http_oidc_module.h" to this
header (after the existing ngx includes) so the type is available to any
translation unit that includes ngx_oidc_handler_authenticate's declaration and
avoid compile errors.
src/ngx_http_oidc_module.h-12-16 (1)

12-16: ⚠️ Potential issue | 🟡 Minor

Remove unnecessary <jansson.h> include.

This header includes <jansson.h> but doesn't use any Jansson types directly. Since ngx_oidc_json.h deliberately abstracts away Jansson (exporting only an opaque ngx_oidc_json_t pointer), the <jansson.h> include should be removed to maintain the abstraction boundary. The required ngx_oidc_json_t type (used at line 165) is already available transitively through ngx_oidc_jwt.h.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ngx_http_oidc_module.h` around lines 12 - 16, Remove the unnecessary
`#include` <jansson.h> from ngx_http_oidc_module.h: the file does not use any
Jansson types directly and relies on the opaque ngx_oidc_json_t which is
provided transitively via ngx_oidc_jwt.h, so delete the <jansson.h> include to
preserve the abstraction boundary while leaving the existing includes
(ngx_oidc_jwt.h, ngx_oidc_session_store.h, ngx_oidc_metadata.h) intact.
src/ngx_oidc_json.c-414-432 (1)

414-432: ⚠️ Potential issue | 🟡 Minor

ngx_oidc_json_integer truncates json_int_t (typically long long) to ngx_int_t (pointer-sized) on 32-bit platforms.

Jansson's json_integer_value returns json_int_t, which is defined as long long (or long)—typically 64-bit on modern platforms. Casting to ngx_int_t (which is intptr_t, i.e., 32-bit on 32-bit platforms) silently truncates large values. This affects OIDC timestamp fields (exp, iat, nbf) on 32-bit systems and creates a latent Y2038 overflow risk on 32-bit platforms. Additionally, in ngx_oidc_variable.c:357, the code allocates NGX_INT64_LEN (64-bit buffer) but stores a 32-bit value, then formats it with "%L" (64-bit specifier)—an inconsistency that masks the underlying truncation issue.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ngx_oidc_json.c` around lines 414 - 432, ngx_oidc_json_integer currently
casts Jansson's json_integer_value (json_int_t, typically 64-bit) down to
ngx_int_t which truncates on 32-bit platforms; change ngx_oidc_json_integer to
return a 64-bit type (e.g. ngx_int64_t or json_int_t) and store
json_integer_value into that 64-bit variable without narrowing cast, update the
function signature and all call sites (including ngx_oidc_variable.c) to accept
the 64-bit return, and in ngx_oidc_variable.c ensure the buffer allocation uses
NGX_INT64_LEN and the formatting uses the correct 64-bit specifier (matching
NGINX's "%L" / ngx_sprintf expectations) so timestamps (exp/iat/nbf) are
preserved on 32-bit platforms.
.github/workflows/test.yaml-86-100 (1)

86-100: ⚠️ Potential issue | 🟡 Minor

No readiness check for the IdP stub container before running tests.

The docker run --detach returns immediately. If the stub isn't ready when prove starts, tests will fail intermittently. Add a health/readiness poll.

Example readiness check
           ghcr.io/kjdev/oidc-provider-stub &>/dev/null
+          # Wait for IdP stub to be ready
+          for i in $(seq 1 30); do
+            curl -sf http://127.0.0.1:8888/.well-known/openid-configuration && break
+            sleep 1
+          done
           # Run Redis
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/test.yaml around lines 86 - 100, The workflow currently
starts the IdP stub container named oidc-stub with docker run --detach but
immediately proceeds to run tests, causing race conditions; add a readiness poll
after starting the container that waits until the stub is accepting requests
(for example, poll an HTTP health or metadata endpoint on localhost:8888 or
check docker logs for a "listening" message) with a timeout and short sleep
between attempts before running prove -r t; update the job to fail if the
container never becomes ready and only then continue to running Redis and the
prove command so tests run reliably against a healthy oidc-stub.
README.md-53-53 (1)

53-53: ⚠️ Potential issue | 🟡 Minor

Update README license statement to match the actual MIT license.

Line 53 claims "2-clause BSD License (same as nginx)", but the LICENSE file contains the MIT License. Update the README to reflect the correct license.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 53, Update the README.md license statement to match the
project's actual LICENSE file: replace the current "2-clause BSD License (same
as nginx)" text with the correct "MIT License" wording (e.g., "License: MIT
License" or "MIT License (see LICENSE)"), ensuring the README and LICENSE file
are consistent.
src/ngx_oidc_handler_logout.h-9-23 (1)

9-23: ⚠️ Potential issue | 🟡 Minor

Missing include for ngx_http_oidc_provider_t in handler headers — not self-contained.

Both ngx_oidc_handler_logout.h and ngx_oidc_handler_authenticate.h use ngx_http_oidc_provider_t * in their function declarations but do not include ngx_http_oidc_module.h, where the type is defined. These headers will fail to compile if included before ngx_http_oidc_module.h. Compare with src/ngx_oidc_provider.h, which correctly includes it.

Proposed fix

Add to both files after the standard includes:

 `#include` <ngx_config.h>
 `#include` <ngx_core.h>
 `#include` <ngx_http.h>
+#include "ngx_http_oidc_module.h"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ngx_oidc_handler_logout.h` around lines 9 - 23, The headers
ngx_oidc_handler_logout.h and ngx_oidc_handler_authenticate.h declare functions
using ngx_http_oidc_provider_t* but do not include the header that defines that
type; update both files to include ngx_http_oidc_module.h (the header that
defines ngx_http_oidc_provider_t) after the existing
ngx_config.h/ngx_core.h/ngx_http.h includes so the declarations (e.g., ngx_int_t
ngx_oidc_handler_logout(ngx_http_request_t *r, ngx_http_oidc_provider_t
*provider);) are self-contained and will compile when these handler headers are
included before ngx_http_oidc_module.h.
src/ngx_oidc_jwt.h-40-41 (1)

40-41: ⚠️ Potential issue | 🟡 Minor

Doc comment references token_type field that doesn't exist in the struct.

The Validation Options section mentions token_type: Expected typ header value (e.g., "JWT") but the struct has no such field. Remove the stale doc line to avoid confusion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ngx_oidc_jwt.h` around lines 40 - 41, Remove the stale "token_type:
Expected typ header value (e.g., \"JWT\")" line from the Validation Options doc
comment in src/ngx_oidc_jwt.h (the comment block that lists
clock_skew_tolerance) since there is no corresponding struct field; update the
comment to only list existing fields (e.g., clock_skew_tolerance) and do not add
a new struct member unless you intend to implement token_type validation.
src/ngx_oidc_provider.c-13-36 (1)

13-36: ⚠️ Potential issue | 🟡 Minor

Missing NULL check on provider_name parameter.

ngx_oidc_provider_by_name is a public API (declared in ngx_oidc_provider.h) but dereferences provider_name at line 27 without a NULL guard. While current callers validate before calling, a direct invocation with NULL would crash.

🛡️ Proposed fix
 ngx_http_oidc_provider_t *
 ngx_oidc_provider_by_name(ngx_http_request_t *r, ngx_str_t *provider_name)
 {
     ngx_http_oidc_main_conf_t *omcf;
     ngx_http_oidc_provider_t *provider;
     ngx_uint_t i;
 
+    if (r == NULL || provider_name == NULL) {
+        return NULL;
+    }
+
     omcf = ngx_http_get_module_main_conf(r, ngx_http_oidc_module);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ngx_oidc_provider.c` around lines 13 - 36, ngx_oidc_provider_by_name
currently dereferences the provider_name pointer (ngx_str_t *provider_name)
without a NULL check; add an early NULL guard at the start of
ngx_oidc_provider_by_name to return NULL if provider_name is NULL (before
calling ngx_http_get_module_main_conf or before comparing lengths), ensuring you
do not access provider_name->len or provider_name->data when provider_name is
NULL; keep the rest of the function unchanged so it still retrieves omcf via
ngx_http_get_module_main_conf and iterates omcf->providers to match
provider[i].name.
src/ngx_oidc_jwt.c-536-553 (1)

536-553: ⚠️ Potential issue | 🟡 Minor

nbf underflow guard incorrectly rejects tokens with nbf < clock_skew.

When claims->not_before < params->clock_skew (e.g., nbf=100, skew=300), the code rejects the token as expired. However, such a token has already passed its not-before time — it should be accepted. The guard prevents signed integer underflow (UB in C), which is correct, but the error path should accept the token (since now is certainly >= nbf - skew when nbf is that small), not reject it.

In practice this only affects contrived/test timestamps near the epoch, so the real-world impact is minimal.

🛡️ Proposed fix
     /* Check not_before */
     if (claims->not_before != 0) {
-        /* Check for underflow before subtraction */
-        if (claims->not_before < params->clock_skew) {
-            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
-                          "oidc_jwt: not_before time underflow "
-                          "(nbf=%T, skew=%T)",
-                          claims->not_before, params->clock_skew);
-            return JWT_ERR_TOKEN_EXPIRED;
-        }
-
-        if (now < claims->not_before - params->clock_skew) {
+        /* Rewrite to avoid underflow: now + skew < nbf */
+        if (now + params->clock_skew < claims->not_before) {
             ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                           "oidc_jwt: token not yet valid "
                           "(nbf=%T, now=%T, skew=%T)",
                           claims->not_before, now, params->clock_skew);
             return JWT_ERR_TOKEN_EXPIRED;
         }
     }

Note: this mirrors the overflow guard already present for exp at line 496, which you'd also want for now + clock_skew here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ngx_oidc_jwt.c` around lines 536 - 553, The current underflow guard for
claims->not_before incorrectly rejects tokens when claims->not_before <
params->clock_skew; instead handle the potential underflow by computing an
adjusted not-before boundary and only reject if now is before that boundary.
Change the logic in the not-before check to: if claims->not_before <
params->clock_skew set adjusted_nbf = 0 (or UINT64/0-equivalent) else
adjusted_nbf = claims->not_before - params->clock_skew, then if now <
adjusted_nbf log the "token not yet valid" message and return
JWT_ERR_TOKEN_EXPIRED; mirror the safe overflow-handling pattern used for exp
(the code near the existing exp overflow guard).
src/ngx_oidc_session_store_memory.h-74-84 (1)

74-84: ⚠️ Potential issue | 🟡 Minor

Doc for delete omits NGX_DECLINED return value.

The implementation returns NGX_DECLINED when the key is not found (consistent with get), but the doc on Line 81 only lists NGX_OK / NGX_ERROR.

📝 Proposed fix
- * `@return` NGX_OK on success, NGX_ERROR on failure
+ * `@return` NGX_OK on success, NGX_DECLINED if not found, NGX_ERROR on failure
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ngx_oidc_session_store_memory.h` around lines 74 - 84, Update the
documentation comment for ngx_oidc_session_store_memory_delete to include
NGX_DECLINED as a possible return value (used when the key is not found,
matching ngx_oidc_session_store_memory_get), so state that the function returns
NGX_OK on success, NGX_DECLINED when the key does not exist, and NGX_ERROR on
failure; ensure the description near the `@return` block mentions NGX_DECLINED
explicitly.
🧹 Nitpick comments (20)
src/ngx_oidc_json.h (1)

211-233: Inline type-check helpers return ngx_int_t (0 or 1), but callers may expect a proper boolean.

ngx_oidc_json_is_string, ngx_oidc_json_is_integer, etc. return the result of an enum comparison — which is 0 or 1. Using ngx_int_t as the return type (intptr_t under nginx) is technically fine but slightly misleading; the nginx convention for boolean-like predicates is ngx_uint_t or simply int. This is a minor readability nit.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ngx_oidc_json.h` around lines 211 - 233, The helpers
ngx_oidc_json_is_string, ngx_oidc_json_is_integer, ngx_oidc_json_is_boolean, and
ngx_oidc_json_is_array return the result of an enum comparison but are typed as
ngx_int_t; change their return type to a boolean-style type (prefer ngx_uint_t
or int per nginx conventions) to make intent clearer and match predicate
usage—update the function signatures of ngx_oidc_json_is_string,
ngx_oidc_json_is_integer, ngx_oidc_json_is_boolean, and ngx_oidc_json_is_array
accordingly while leaving their bodies (the enum comparisons) unchanged.
src/ngx_oidc_handler_logout.c (2)

40-47: Redundant u_char *encoded_client_id / encoded_post_logout_uri top-level declarations.

These raw pointer aliases (Lines 46–47) are only ever assigned from encoded_client_id_str.data and encoded_post_logout_uri_str.data respectively (Lines 234 and 244) and immediately used to build the URL. Keeping both the ngx_str_t structs and the u_char * aliases adds visual noise without benefit — just use encoded_client_id_str.data directly in ngx_cpymem.

♻️ Suggested cleanup
-    u_char *p, *encoded_client_id, *encoded_post_logout_uri;
+    u_char *p;
     u_char *encoded_id_token = NULL;
 
 // ...
-        encoded_client_id = encoded_client_id_str.data;
-        size_t encoded_client_id_len = encoded_client_id_str.len;
 
 // ...
-        encoded_post_logout_uri = encoded_post_logout_uri_str.data;
-        size_t encoded_post_logout_uri_len = encoded_post_logout_uri_str.len;
 
 // ...
-        p = ngx_cpymem(p, encoded_client_id, encoded_client_id_len);
+        p = ngx_cpymem(p, encoded_client_id_str.data, encoded_client_id_str.len);
 // ...
-        p = ngx_cpymem(p, encoded_post_logout_uri, encoded_post_logout_uri_len);
+        p = ngx_cpymem(p, encoded_post_logout_uri_str.data,
+                       encoded_post_logout_uri_str.len);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ngx_oidc_handler_logout.c` around lines 40 - 47, Remove the redundant
top-level u_char pointers encoded_client_id and encoded_post_logout_uri: they
are only assigned from encoded_client_id_str.data and
encoded_post_logout_uri_str.data and then passed into ngx_cpymem, so delete the
declarations for encoded_client_id and encoded_post_logout_uri and update the
ngx_cpymem calls to use encoded_client_id_str.data and
encoded_post_logout_uri_str.data directly; keep the ngx_str_t variables
encoded_client_id_str and encoded_post_logout_uri_str intact and ensure all
references to the removed pointers are replaced accordingly.

119-142: C99 mid-block variable declarations deviate from nginx coding style.

Nginx traditionally requires all variables to be declared at the beginning of a block, following C89/C90 conventions. Lines 120, 142, 217, 226, 235, 245, 254, and 255 all declare variables after executable statements. While modern C compilers and nginx 1.18+ handle this without issue, it diverges from the traditional nginx style convention.

Consider moving these declarations to the top of the enclosing block (lines 108–340) to maintain consistency with nginx coding practices, though this module does not currently enforce this style requirement.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ngx_oidc_handler_logout.c` around lines 119 - 142, Move all variable
declarations in the oidc_handler_logout function to the top of its enclosing
block so they precede any executable statements; specifically relocate the
mid-block declarations such as ngx_http_oidc_ctx_t *ctx, ngx_str_t metadata,
ngx_str_t *end_session, ngx_str_t full_logout_url and the other variables
declared later in the function to the start of the function/block (before the
first statement that uses ngx_http_get_module_ctx or any other logic). Update
any initialization expressions to be assignments after the declarations so
behavior is unchanged. Ensure you only change declaration positions (not types
or initialization logic) and keep references like ngx_http_get_module_ctx,
ngx_oidc_metadata_get_end_session_endpoint and subsequent uses intact.
src/ngx_oidc_hash.h (1)

61-71: const void * return type loses type safety for ngx_oidc_hash_get_md.

Callers must silently cast to const EVP_MD * before use, which bypasses compiler type checking. Since all callers are within the same module and already include <openssl/evp.h> (as seen in src/ngx_oidc_hash.c), returning the proper type directly would be safer and self-documenting.

♻️ Suggested fix

In src/ngx_oidc_hash.h, add the forward declaration and use the real type:

+#include <openssl/evp.h>
 
-const void * ngx_oidc_hash_get_md(const char *algorithm);
+const EVP_MD *ngx_oidc_hash_get_md(const char *algorithm);

And in src/ngx_oidc_hash.c:

-const void *
+const EVP_MD *
 ngx_oidc_hash_get_md(const char *algorithm)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ngx_oidc_hash.h` around lines 61 - 71, Change the prototype of
ngx_oidc_hash_get_md from returning const void * to returning const EVP_MD * and
add a forward declaration or include for EVP_MD in the header so the real
OpenSSL type is visible (e.g., forward-declare typedef struct evp_md_st EVP_MD;
or include <openssl/evp.h>), then update the implementation signature in
ngx_oidc_hash.c to match const EVP_MD * ngx_oidc_hash_get_md(const char
*algorithm) and remove any casts at call sites so callers use the proper const
EVP_MD * directly.
.uncrustify.cfg (1)

175-180: Duplicate align_var_def_star_style setting.

align_var_def_star_style = 2 is already set at Line 85. The second assignment here is redundant. Since the values match, there's no functional impact, but it can cause confusion if they ever drift.

🔧 Suggested cleanup
 # Variable definitions (no alignment - OIDC module has diverse type name lengths)
 align_var_def_span = 0
-align_var_def_star_style = 2      # Pointer alignment (star attached to name)
 align_var_def_amp_style = 2       # Reference alignment
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.uncrustify.cfg around lines 175 - 180, Remove the duplicate assignment of
align_var_def_star_style in the later variable-definition block: locate the
second "align_var_def_star_style = 2" (the one next to align_var_def_amp_style)
and delete it so the setting exists only once (the original at the earlier
declaration). Keep the surrounding comments and other settings (e.g.,
align_var_def_span, align_var_def_amp_style) unchanged.
config (1)

5-5: ngx_module_deps is empty — header changes won't trigger recompilation.

The ngx_module_deps variable should list the module's header files so nginx's build system can track dependencies for incremental builds. Without this, modifying any .h file (e.g., ngx_http_oidc_module.h) won't trigger recompilation of the affected .c files, leading to stale object files.

Proposed fix
-ngx_module_deps=
+ngx_module_deps="\
+  $ngx_addon_dir/src/ngx_http_oidc_module.h \
+  $ngx_addon_dir/src/ngx_oidc_handler_authenticate.h \
+  $ngx_addon_dir/src/ngx_oidc_handler_callback.h \
+  $ngx_addon_dir/src/ngx_oidc_handler_logout.h \
+  $ngx_addon_dir/src/ngx_oidc_handler_status.h \
+  $ngx_addon_dir/src/ngx_oidc_hash.h \
+  $ngx_addon_dir/src/ngx_oidc_http.h \
+  $ngx_addon_dir/src/ngx_oidc_json.h \
+  $ngx_addon_dir/src/ngx_oidc_jwks.h \
+  $ngx_addon_dir/src/ngx_oidc_jwt.h \
+  $ngx_addon_dir/src/ngx_oidc_metadata.h \
+  $ngx_addon_dir/src/ngx_oidc_provider.h \
+  $ngx_addon_dir/src/ngx_oidc_random.h \
+  $ngx_addon_dir/src/ngx_oidc_session.h \
+  $ngx_addon_dir/src/ngx_oidc_session_store.h \
+  $ngx_addon_dir/src/ngx_oidc_session_store_memory.h \
+  $ngx_addon_dir/src/ngx_oidc_session_store_redis.h \
+  $ngx_addon_dir/src/ngx_oidc_url.h \
+"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@config` at line 5, The ngx_module_deps variable is empty so header changes
won't trigger recompilation; update the ngx_module_deps definition to list the
module's public/private header files (e.g., ngx_http_oidc_module.h and any other
.h files used by the module) so the build system tracks dependencies and will
recompile affected .c files; edit the ngx_module_deps entry to include those
header filenames separated appropriately to match the existing config format.
src/ngx_oidc_session.c (1)

183-292: Partial rotation failure leaves inconsistent state.

If rotation succeeds in copying some tokens to the new session but fails midway (e.g., access_token set fails at Line 221), the function returns NGX_ERROR leaving the old session intact and partial data in the new session. The old session data is only deleted at Lines 267–285 after all copies succeed. However, the partially-written new session keys are never cleaned up on error.

The acknowledged comment about non-atomicity (Lines 171–181) is appreciated, but consider at least cleaning up the new session keys on partial failure to avoid dangling session data.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ngx_oidc_session.c` around lines 183 - 292, The ngx_oidc_session_rotate
function can leave partial data in the new session if a later copy fails; update
ngx_oidc_session_rotate to track which keys were successfully set and, on any
intermediate NGX_ERROR return, delete those keys from new_session_id using
ngx_oidc_session_delete to roll back; specifically record successful sets for
NGX_OIDC_SESSION_KEY_ID_TOKEN, NGX_OIDC_SESSION_KEY_ACCESS_TOKEN,
NGX_OIDC_SESSION_KEY_REFRESH_TOKEN, and NGX_OIDC_SESSION_KEY_USERINFO (the keys
passed to ngx_oidc_session_set), and call ngx_oidc_session_delete(r, store,
new_session_id, <key>) for each recorded key before returning NGX_ERROR from
ngx_oidc_session_rotate.
src/ngx_oidc_handler_authenticate.c (2)

91-103: redirect_uri is validated before the empty check — could produce a misleading error.

If redirect_uri evaluates to an empty string, ngx_oidc_url_validate is called first. Depending on that function's behavior with empty input, you might get a "validation failed" error when the actual issue is "no redirect_uri available". Swap the order so the empty check comes first.

Proposed fix
-    /* Validate redirect_uri parameter format */
-    if (ngx_oidc_url_validate(r, &redirect_uri) != NGX_OK) {
-        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
-                      "oidc_handler_authenticate: redirect_uri parameter "
-                      "validation failed");
-        return NULL;
-    }
     if (redirect_uri.len == 0) {
         ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                       "oidc_handler_authenticate: no redirect_uri available "
                       "from provider");
         return NULL;
     }
+
+    /* Validate redirect_uri parameter format */
+    if (ngx_oidc_url_validate(r, &redirect_uri) != NGX_OK) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "oidc_handler_authenticate: redirect_uri parameter "
+                      "validation failed");
+        return NULL;
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ngx_oidc_handler_authenticate.c` around lines 91 - 103, The current code
calls ngx_oidc_url_validate(r, &redirect_uri) before checking for an empty
redirect_uri, which can produce a misleading "validation failed" error when the
real problem is a missing redirect URI; modify the logic in
ngx_oidc_handler_authenticate so you first check if redirect_uri.len == 0 and
log the "no redirect_uri available from provider" error (using ngx_log_error)
and return NULL, and only then call ngx_oidc_url_validate(r, &redirect_uri) and
handle its failure with the "redirect_uri parameter validation failed" log path.

371-375: Redundant double-initialization of code_verifier.

code_verifier is zeroed via ngx_memzero at line 375, then null'd again via ngx_str_null at line 450. The second initialization is unnecessary since the only path between them either returns early or doesn't modify code_verifier.

Also applies to: 449-450

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ngx_oidc_handler_authenticate.c` around lines 371 - 375, The variable
code_verifier is being initialized twice (once with ngx_memzero(&code_verifier,
...) and again with ngx_str_null(&code_verifier)); remove the redundant early
zeroing so only one clear initialization remains. Specifically, delete the
ngx_memzero(&code_verifier, sizeof(ngx_str_t)) call and keep the later
ngx_str_null(&code_verifier) usage in the authenticate handler to ensure a
single clear initialization of code_verifier.
src/ngx_oidc_handler_status.h (1)

13-20: Docstring @return is incomplete — implementation also returns NGX_HTTP_* status codes.

Per the implementation in src/ngx_oidc_handler_status.c, this handler can return NGX_HTTP_NOT_ALLOWED, NGX_HTTP_INTERNAL_SERVER_ERROR, etc., in addition to NGX_OK and NGX_ERROR. Consider updating the @return to match, e.g., @return NGX_OK, NGX_ERROR, or NGX_HTTP_* status code.

Proposed fix
- * `@return` NGX_OK or NGX_ERROR
+ * `@return` NGX_OK, NGX_ERROR, or NGX_HTTP_* status code
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ngx_oidc_handler_status.h` around lines 13 - 20, Update the docstring for
the function ngx_oidc_handler_status to accurately reflect all possible return
values: change the `@return` line in the header comment block to something like
"@return NGX_OK, NGX_ERROR, or an NGX_HTTP_* status code (e.g.,
NGX_HTTP_NOT_ALLOWED, NGX_HTTP_INTERNAL_SERVER_ERROR)" so it matches the
implementation in ngx_oidc_handler_status.c and documents that the handler may
return NGX_HTTP_* codes in addition to NGX_OK/NGX_ERROR.
.github/workflows/test.yaml (1)

27-29: apt-get upgrade in CI makes builds non-deterministic and slow.

Full system upgrades can introduce unexpected package changes and significantly slow down CI runs. Consider removing it unless there's a specific CVE being mitigated.

Proposed fix
           sudo apt-get update
-          sudo apt-get upgrade -y --no-install-recommends
           sudo apt-get install -y --no-install-recommends \
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/test.yaml around lines 27 - 29, The CI workflow run step
currently performs a full system upgrade using "apt-get upgrade -y
--no-install-recommends" which makes builds non-deterministic and slow; remove
that upgrade command from the run block and keep only "sudo apt-get update" (or
replace with a targeted package install if a specific CVE or package version is
required), ensuring the run section no longer executes a full system upgrade
during the test job.
src/ngx_oidc_handler_status.c (2)

524-533: Mixed declaration and code — not C89-compatible.

Line 527 declares status_metadata_ctx_t ctx after the ngx_cpymem statement on line 525, which is valid in C99 but not in C89. nginx core code traditionally targets C89 for maximum portability. Wrap in a block or move the declaration to the top of the if body.

♻️ Proposed fix
     /* Format Metadata details */
     if (metadata_entries > 0) {
+        status_metadata_ctx_t ctx;
+
         b->last = ngx_cpymem(b->last, "Metadata:\n", sizeof("Metadata:\n") - 1);
 
-        status_metadata_ctx_t ctx;
         ctx.buf = b;
         ctx.size = NULL;
         ctx.count = NULL;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ngx_oidc_handler_status.c` around lines 524 - 533, The declaration of
status_metadata_ctx_t ctx appears after code (ngx_cpymem) which violates C89;
move the declaration to the start of the if block (before the ngx_cpymem call)
or wrap the existing code and declaration inside its own inner block so ctx is
declared before any statements. Specifically adjust the if (metadata_entries >
0) block that uses ngx_cpymem, status_metadata_ctx_t ctx, and
ngx_oidc_metadata_iterate with status_metadata_format_callback so the ctx
variable is declared prior to any executable statements.

128-143: Silent data truncation in status_write_indented when buffer is full.

When the bounds check at line 130 fails, the line is silently dropped. For a diagnostic endpoint this may cause confusing output. Consider at minimum logging a debug message, or pre-computing a tighter buffer size.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ngx_oidc_handler_status.c` around lines 128 - 143, status_write_indented
silently drops lines when the check "b->last + indent + (p - line_start) + 1 <=
b->end" fails; update status_write_indented to handle a full buffer explicitly
by either logging a debug/error via ngx_log_debug*/ngx_log_error with context
(function name and line length) or by writing a truncated version (e.g., write
as much as fits then append "...\n") instead of silently skipping; reference the
buffer pointers (b->last, b->end), the indent variable, and the line_start/p
pointers to compute the writable length and perform the safe partial copy or
emit the log before advancing line_start.
src/ngx_oidc_session_store_redis.h (1)

12-12: Unnecessary hiredis/hiredis.h include in public header.

No hiredis types appear in any of the function declarations — they all use nginx types (ngx_int_t, ngx_str_t, ngx_http_request_t, etc.) and the forward-declared ngx_oidc_session_store_t. Moving this include to ngx_oidc_session_store_redis.c reduces coupling for consumers.

♻️ Proposed fix
 `#include` <ngx_config.h>
 `#include` <ngx_core.h>
 `#include` <ngx_http.h>
-#include <hiredis/hiredis.h>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ngx_oidc_session_store_redis.h` at line 12, The public header
ngx_oidc_session_store_redis.h should not include <hiredis/hiredis.h> since no
hiredis types appear in the function declarations; remove the `#include`
<hiredis/hiredis.h> from ngx_oidc_session_store_redis.h and instead add that
include to ngx_oidc_session_store_redis.c where hiredis types are actually used
(e.g., in functions that implement redis interactions for
ngx_oidc_session_store_t, and any helper functions referencing hiredis types).
Ensure the header still forward-declares ngx_oidc_session_store_t and exposes
functions using only nginx types (ngx_int_t, ngx_str_t, ngx_http_request_t) so
consumers are not coupled to hiredis.
src/ngx_oidc_jwt.h (1)

12-19: Unnecessary OpenSSL headers in public API header.

None of the types from these OpenSSL headers (EVP_PKEY, RSA, EC_KEY, BIGNUM, etc.) appear in the public API surface of this header. Moving them to ngx_oidc_jwt.c would reduce coupling and compile-time dependencies for consumers.

♻️ Proposed fix
 `#include` <ngx_config.h>
 `#include` <ngx_core.h>
 `#include` <ngx_http.h>
-#include <openssl/evp.h>
-#include <openssl/rsa.h>
-#include <openssl/ec.h>
-#include <openssl/bn.h>
-#include <openssl/param_build.h>
-#include <openssl/core_names.h>
-#include <openssl/err.h>
-#include <openssl/obj_mac.h>
 `#include` "ngx_oidc_json.h"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ngx_oidc_jwt.h` around lines 12 - 19, The public header ngx_oidc_jwt.h
currently includes unnecessary OpenSSL headers (EVP, RSA, EC, BIGNUM,
param_build, core_names, err, obj_mac) that are not part of the public API;
remove these `#include` lines from ngx_oidc_jwt.h and move them into
ngx_oidc_jwt.c so only the implementation sees EVP_PKEY/RSA/EC_KEY/BIGNUM types
and OpenSSL symbols, keeping the header limited to forward declarations and its
public declarations (leave any public typedefs/functions as-is but ensure
implementations in functions like those that manipulate JWT/OpenID keys
reference the OpenSSL headers inside ngx_oidc_jwt.c).
src/ngx_oidc_jwt.c (1)

567-610: Use-after-free risk: algorithm pointer checked after json_decref.

At line 604, algorithm (a pointer into header_json's memory) is checked for NULL after ngx_oidc_json_free(header_json) at line 599. The NULL check itself is safe, but the pointer is dangling — any future code that reads through it would be UaF. Consider setting a boolean flag instead.

Additionally, re-parsing the JWT header here is redundant since jwt_verify_signature already parsed it. Passing the algorithm through the jwt_claims_t struct (it's already available as claims->at_hash's sibling in the payload) or through the verification context would avoid the extra work.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ngx_oidc_jwt.c` around lines 567 - 610, The code re-parses the JWT header
to set a pointer variable algorithm into header_json's memory then frees
header_json with ngx_oidc_json_free(header_json) and later checks/uses
algorithm, creating a use-after-free risk; fix by not retaining a pointer into
freed JSON — either extract/copy the alg value into a local ngx_str_t or boolean
flag before freeing (e.g. set has_alg = true and copy algorithm bytes into a
stack/pooled ngx_str_t) and use that copy for ngx_oidc_jwt_validate_at_hash, or
better yet avoid re-parsing entirely by plumbing the parsed alg out of the
initial jwt parsing/verification (e.g. add algorithm to claims or verification
context produced by jwt_verify_signature and use claims->alg) and call
ngx_oidc_jwt_validate_at_hash with that safe value instead of accessing
header_json after ngx_oidc_json_free; update references:
ngx_oidc_jwt_decode_header, ngx_oidc_json_parse, ngx_oidc_json_object_get,
ngx_oidc_json_string, ngx_oidc_json_free, ngx_oidc_jwt_validate_at_hash, and the
claims struct (claims->at_hash / add claims->alg) accordingly.
src/ngx_oidc_jwks.c (2)

979-1034: jwks_shm_lookup returns with lock held on success — asymmetric lock contract.

The function holds the mutex on return when a node is found (Line 1022) but releases it when not found (Line 1032). This is the correct nginx pattern used in the codebase, and callers (ngx_oidc_jwks_get) handle both paths properly. However, a brief doc comment on the function noting this contract would help maintainability.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ngx_oidc_jwks.c` around lines 979 - 1034, The jwks_shm_lookup function
currently returns with the shared-memory mutex still held when a node is found
but unlocks the mutex when not found; add a concise doc comment atop
jwks_shm_lookup describing this lock contract (i.e., on success the caller
receives a jwks_shm_node_t* with the shm->shpool->mutex still locked and is
responsible for unlocking, while on failure the function has already unlocked
the mutex and returns NULL) and mention that callers such as ngx_oidc_jwks_get
must handle both cases accordingly.

906-916: Hard-coded 1-hour TTL in multiple locations.

The TTL 3600 appears at Lines 906, 916, and 1265. Consider extracting this to a named constant (e.g., #define NGX_OIDC_JWKS_DEFAULT_TTL 3600) so it can be found and changed in one place. Ideally, this would be configurable via the nginx conf, but a constant is a good first step.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ngx_oidc_jwks.c` around lines 906 - 916, Replace the repeated hard-coded
TTL literal (3600) with a named constant: add a macro like
NGX_OIDC_JWKS_DEFAULT_TTL (value 3600) and use it wherever TTL is set (e.g.,
assignments to key->expires_at, the call to
ngx_oidc_jwks_cache_node_set_expires_at, and any other occurrences such as the
place at line ~1265); update the code paths that set fetched/expires times
(references: key->expires_at, ngx_oidc_jwks_cache_node_set_fetched_at,
ngx_oidc_jwks_cache_node_set_expires_at) to use NGX_OIDC_JWKS_DEFAULT_TTL so the
TTL value is centralized (optionally later expose it via nginx config).
src/ngx_oidc_session_store_memory.c (2)

522-524: Unnecessary queue remove/reinsert in get — expiration is unchanged.

get does not modify ocn->expires, so removing and reinserting the node in the expire queue places it back at the same position. This is wasted work under the shared-memory mutex on every read.

If this was intended for future LRU-style touch semantics, consider adding a comment. Otherwise, remove it.

♻️ Proposed fix (remove no-op reinsert)
     ngx_memcpy(value->data, ocn->data.data, value->len);
 
-    /* Reinsert into expire queue at correct sorted position */
-    ngx_queue_remove(&ocn->queue);
-    mem_queue_insert_expire(&octx->expire_queue, ocn);
-
     ngx_shmtx_unlock(&octx->shpool->mutex);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ngx_oidc_session_store_memory.c` around lines 522 - 524, The get path
currently calls ngx_queue_remove(&ocn->queue) and
mem_queue_insert_expire(&octx->expire_queue, ocn) even though ocn->expires is
not changed, causing an unnecessary remove/reinsert under the shared-memory
mutex; remove those two calls from the get implementation (the calls around ocn
and octx in the get function) so reads do not touch the expire queue, or if you
intended future LRU touch behavior add a clear comment in the get function
referencing ocn, octx, expire_queue, ngx_queue_remove and
mem_queue_insert_expire to justify keeping them.

598-641: shm_zone_init discards previous zone data on reload.

The data parameter (old zone data) is explicitly ignored, so all in-flight sessions are lost on every nginx configuration reload. This is an acceptable trade-off for simplicity, but worth noting for operators. If session persistence across reloads matters, the old rbtree data could be reused (similar to the JWKS zone init pattern in ngx_oidc_jwks_init_zone).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ngx_oidc_session_store_memory.c` around lines 598 - 641, The shm zone
init currently ignores the incoming data parameter and always allocates a fresh
shm context, causing prior zone state to be discarded on reload; update
ngx_oidc_session_store_memory_shm_zone_init to check the data parameter (old
zone data) and, when non-NULL, reuse the existing shm context instead of
clobbering it: cast data to the existing shm context type (the same struct
allocated as octx), preserve/reuse its rbtree and expire_queue (do not
reinitialize them), update shpool and max_size from the new store if needed, and
set shm_zone->data to the existing context; follow the same approach used by
ngx_oidc_jwks_init_zone and ensure ngx_oidc_session_store_memory_setup_zones
still initializes shm_zone->data to the store pointer when creating new zones.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 12

🧹 Nitpick comments (6)
src/ngx_oidc_handler_status.h (1)

13-19: Documentation return value is incomplete.

The docstring states @return NGX_OK or NGX_ERROR, but the implementation in src/ngx_oidc_handler_status.c returns various NGX_HTTP_* status codes (NGX_HTTP_OK, NGX_HTTP_NOT_ALLOWED, NGX_HTTP_INTERNAL_SERVER_ERROR).

📝 Suggested documentation fix
 /**
  * Handles status endpoint requests for JWKS and Metadata cache statistics
  *
  * `@param`[in] r  HTTP request
  *
- * `@return` NGX_OK or NGX_ERROR
+ * `@return` NGX_HTTP_* status code
  */
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ngx_oidc_handler_status.h` around lines 13 - 19, Update the `@return` doc
in the ngx_oidc_handler_status.h comment to match the actual return values used
by the implementation (see ngx_oidc_handler_status.c); replace the generic
"NGX_OK or NGX_ERROR" with the specific HTTP status codes returned by the
handler (e.g., NGX_HTTP_OK, NGX_HTTP_NOT_ALLOWED,
NGX_HTTP_INTERNAL_SERVER_ERROR) and a short note that the function returns an
ngx_int_t HTTP status code per ngx_http handler conventions.
src/ngx_oidc_random.h (1)

13-81: Document output buffer ownership/lifetime in this public API.

Right now the docs describe what gets generated, but not where the output is allocated / who owns it (e.g., request pool vs caller-owned). Since these are public helpers, callers need that contract spelled out.

Proposed doc tweak
 `#include` <ngx_core.h>
 `#include` <ngx_http.h>
 
+/*
+ * Memory ownership:
+ * - Output buffers are allocated by these helpers (typically from r->pool).
+ * - Callers should treat returned ngx_str_t as binary-safe via .len, and must
+ *   not attempt to free the backing storage directly.
+ */
+
 /**
  * Generate random state parameter for OAuth 2.0 CSRF protection
  *
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ngx_oidc_random.h` around lines 13 - 81, Update the function docs to
specify buffer ownership/lifetime: for ngx_oidc_random_state,
ngx_oidc_random_nonce, ngx_oidc_random_code_verifier,
ngx_oidc_random_code_challenge, and ngx_oidc_random_session_id state that on
success (NGX_OK) the returned ngx_str_t->data is allocated from the request pool
(r->pool), is NUL-terminated if applicable, must not be freed by the caller, and
remains valid until the request pool is destroyed; also state that on failure
the output is undefined/unchanged. Add this ownership contract to each function
comment in ngx_oidc_random.h.
Dockerfile (1)

53-63: Consider running the final image as non-root.

Trivy flags the image running as root. If feasible, create a non-root user and run on an unprivileged port (or add only the minimal capabilities required).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Dockerfile` around lines 53 - 63, The image is currently running as root;
update the Dockerfile (the stage starting with "FROM nginx AS module") to create
and switch to a non-root user and ensure nginx runs on an unprivileged port or
with minimal capabilities: add a non-root user/group, chown the nginx config and
any runtime directories modified by the RUN block (including /etc/nginx and any
cache or log dirs touched during the RUN --mount block), update nginx.conf (the
file referenced by the sed line that inserts load_module
"/usr/lib/nginx/modules/ngx_http_oidc_module.so") to listen on an unprivileged
port (e.g., 8080) or adjust the container runtime to drop privileges, and add a
USER <nonroot> directive in the final image stage so the container does not run
as root. Ensure file permissions are set so the non-root user can read the
module and write to logs/temp.
src/ngx_oidc_jwt.c (1)

1338-1345: Consider using explicit algorithm checks for hash_half_len determination.

Similar to the concern in ngx_oidc_hash_get_md, the ngx_strstr calls here could match unintended algorithm strings. While the impact is limited (only affects hash truncation length), using explicit algorithm comparisons would be more robust and consistent with secure coding practices.

♻️ Suggested improvement
-    if (ngx_strstr(algorithm, "256")) {
-        hash_half_len = 16;  /* SHA-256 produces 32 bytes, take left 16 */
-    } else if (ngx_strstr(algorithm, "384")) {
-        hash_half_len = 24;  /* SHA-384 produces 48 bytes, take left 24 */
-    } else {
-        /* 512 or EdDSA */
-        hash_half_len = 32;  /* SHA-512 produces 64 bytes, take left 32 */
-    }
+    if (ngx_strcmp(algorithm, "RS256") == 0
+        || ngx_strcmp(algorithm, "ES256") == 0
+        || ngx_strcmp(algorithm, "PS256") == 0
+        || ngx_strcmp(algorithm, "HS256") == 0)
+    {
+        hash_half_len = 16;  /* SHA-256 produces 32 bytes, take left 16 */
+    } else if (ngx_strcmp(algorithm, "RS384") == 0
+               || ngx_strcmp(algorithm, "ES384") == 0
+               || ngx_strcmp(algorithm, "PS384") == 0
+               || ngx_strcmp(algorithm, "HS384") == 0)
+    {
+        hash_half_len = 24;  /* SHA-384 produces 48 bytes, take left 24 */
+    } else {
+        /* RS512, ES512, PS512, HS512, EdDSA */
+        hash_half_len = 32;  /* SHA-512 produces 64 bytes, take left 32 */
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ngx_oidc_jwt.c` around lines 1338 - 1345, The code uses
ngx_strstr(algorithm, "...") which can match unintended strings; update the
logic in ngx_oidc_jwt.c where hash_half_len is set (variable algorithm) to use
explicit comparisons against known JWT alg values (e.g.
"HS256","RS256","ES256","PS256" -> hash_half_len=16;
"HS384","RS384","ES384","PS384" -> 24; and
"HS512","RS512","ES512","PS512","EdDSA" -> 32) instead of substring searches;
implement these checks with exact string comparisons (ngx_strcmp/ngx_strncmp or
equivalent) to ensure only the intended algorithms set each truncation length.
src/ngx_oidc_session_store_memory.c (1)

522-524: Remove unnecessary queue reordering in get operation.

The code removes the node from the expiration queue and reinserts it, but since the expiration time (ocn->expires) hasn't changed, the node will be reinserted at the same position. This is unnecessary work that holds the lock longer than needed.

If the intent is LRU behavior (moving accessed items to prevent eviction), the expiration time should be updated. Otherwise, these lines should be removed.

🧹 Proposed fix (remove unnecessary reordering)
     ngx_memcpy(value->data, ocn->data.data, value->len);
 
-    /* Reinsert into expire queue at correct sorted position */
-    ngx_queue_remove(&ocn->queue);
-    mem_queue_insert_expire(&octx->expire_queue, ocn);
-
     ngx_shmtx_unlock(&octx->shpool->mutex);
     return NGX_OK;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ngx_oidc_session_store_memory.c` around lines 522 - 524, The get path is
doing unnecessary queue reordering: remove the calls to
ngx_queue_remove(&ocn->queue) and mem_queue_insert_expire(&octx->expire_queue,
ocn) in the get operation since ocn->expires is not modified and reinsertion
will place the node in the same spot, holding the lock longer; if you intended
LRU semantics instead, update ocn->expires before reinserting and then call
ngx_queue_remove + mem_queue_insert_expire, otherwise delete those two lines and
any comments referencing reordering to avoid needless work.
src/ngx_oidc_session.c (1)

171-292: Partial rotation failure leaves duplicate data in new session.

When rotation fails mid-way (e.g., access_token move fails after id_token succeeds), the function returns NGX_ERROR without cleaning up the partially-moved data from the new session. This leaves id_token in both sessions.

While the comment acknowledges non-atomicity, consider cleaning up the new session on failure to avoid data duplication:

♻️ Proposed rollback on failure

Add cleanup of the new session when rotation fails:

     /* Move Access Token */
     rc = ngx_oidc_session_get(r, store, old_session_id,
                               NGX_OIDC_SESSION_KEY_ACCESS_TOKEN,
                               &token_data);
     if (rc == NGX_OK) {
         rc = ngx_oidc_session_set(r, store, new_session_id,
                                   NGX_OIDC_SESSION_KEY_ACCESS_TOKEN,
                                   &token_data, expires);
         if (rc != NGX_OK) {
             ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                           "oidc_session: failed to move access_token "
                           "to new session, aborting rotation");
+            /* Clean up partially moved data */
+            ngx_oidc_session_delete(r, store, new_session_id,
+                                    NGX_OIDC_SESSION_KEY_ID_TOKEN);
             return NGX_ERROR;
         }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ngx_oidc_session.c` around lines 171 - 292, ngx_oidc_session_rotate can
leave partially-moved data in the new session when a later ngx_oidc_session_set
fails; modify ngx_oidc_session_rotate so that on any failure after a successful
ngx_oidc_session_set it calls ngx_oidc_session_delete for the same keys on
new_session_id to roll back partial moves (delete NGX_OIDC_SESSION_KEY_ID_TOKEN,
NGX_OIDC_SESSION_KEY_ACCESS_TOKEN, NGX_OIDC_SESSION_KEY_REFRESH_TOKEN,
NGX_OIDC_SESSION_KEY_USERINFO as applicable), keep error logging via
r->connection->log, and return NGX_ERROR; implement the cleanup in each failure
branch inside ngx_oidc_session_rotate (use the same ngx_oidc_session_delete
helper used for old_session_id).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/test.yaml:
- Around line 92-95: Replace the hex OIDC_CLIENT_SECRET value with a non-secret
test string across all test configs: update the environment variable in the CI
workflow (the OIDC_CLIENT_SECRET entry in the .github/workflows/test.yaml diff)
and every test provider config that contains b028e7a42bbb072acf09ed342e760627
(files referenced: t/conf/test-provider.conf, t/conf/test-provider-redis.conf,
t/conf/test-provider-logout.conf, and all variants of
t/conf/test-provider-alg.conf) to a clearly non-secret value like
test-secret-value so Gitleaks won’t flag it and all tests remain consistent.
- Around line 14-24: The GHCR login fails because the job token lacks package
scope; update the job-level permissions for the test job (the permissions map
under job name "test") to include packages: read (e.g., add packages: read
alongside contents: read) so that the docker/login-action that authenticates to
ghcr.io can use the token; alternatively set packages: read in the top-level
permissions block if you want it globally.

In `@Dockerfile`:
- Around line 175-180: The Dockerfile currently defines
NGINX_GOOGLE_CLIENT_SECRET (and other Google auth ENV vars) directly via the ENV
instruction, which bakes secrets into the image; remove the secret from the
Dockerfile and switch to runtime secret injection instead (e.g., use
Docker/Kubernetes secrets, docker-compose env_file or runtime -e, or inject via
entrypoint reading from a mounted secret file) so NGINX_GOOGLE_CLIENT_SECRET is
not present in the image history; update any references to
NGINX_GOOGLE_CLIENT_SECRET and documentation to show the secret must be provided
at deploy/run time rather than in the Dockerfile.

In `@docs/EXAMPLES.md`:
- Around line 1-618: The file docs/EXAMPLES.md is using ATX-style level-1/2
headings (e.g., "## Table of Contents", "## 1. Quick Start", "### 1.1 Minimal
Configuration") which triggers markdownlint MD003 because the repo expects
setext-style for top-level headings; fix by converting the affected ATX headings
to setext style for level-1/2 (replace "## Title" with "Title" followed by a
line of === or --- as appropriate, and similarly convert level-2 where required)
for sections such as "Table of Contents", "1. Quick Start", "1.1 Minimal
Configuration", "2. Configuration Examples", etc., or alternatively update the
markdownlint config to disable/override MD003 if ATX headings are preferred—pick
one approach and apply it consistently across EXAMPLES.md.

In `@README.md`:
- Line 1438: Change the compound modifier "session save" to the hyphenated form
"session-save" where it appears as a modifier in the README (replace occurrences
of the phrase "session save" used as an adjective with "session-save"); ensure
surrounding sentences retain correct spacing and punctuation and run a quick
spell/grammar check to confirm no other instances remain.
- Around line 818-821: The blockquote contains a blank line that triggers
markdownlint MD028; remove the empty line between the two quote lines or prefix
that blank line with ">" so the blockquote remains continuous—update the lines
containing "> **Warning**: For security reasons, restrict access to the status
endpoint appropriately (e.g., with IP address restrictions)." and ">
**Security**: In production environments, it is recommended to configure access
restrictions using `allow`/`deny` directives." to eliminate the blank quoted
line.
- Around line 4-12: The README uses ATX headings (e.g., "## 1. Overview", "###
1.1 About This Module") which triggers markdownlint MD003; either convert these
ATX headings to setext style for top-level headings (e.g., replace "## 1.
Overview" with "1. Overview" followed by an underline of '=' or '-' as
appropriate and similarly for subsection setext underlines) across the file, or
update the markdownlint config to disable/relax MD003 for this repo (adjust
.markdownlint.json or equivalent) so ATX headings are allowed; pick one approach
and apply it consistently to all headings in README.md.

In `@src/ngx_http_oidc_module.c`:
- Around line 321-399: The callback detection fails when an absolute
redirect_uri contains query or fragment components because uri_to_match
currently includes everything from the third '/' to end-of-string; modify the
extraction logic (where redirect_uri, path_start, uri_to_match are handled) to
stop uri_to_match at the first '?' or '#' after path_start (i.e., set end =
pointer to '?' or '#' if present, otherwise original end) before computing
uri_to_match.len, validate pointer ordering as before, and then compare
uri_to_match against r->uri.

In `@src/ngx_oidc_handler_authenticate.c`:
- Around line 84-103: The code is validating provider->redirect_uri directly
which rejects relative paths; modify the flow in ngx_oidc_handler_authenticate
after ngx_http_complex_value fills redirect_uri to first call
ngx_oidc_url_build_absolute(r, &redirect_uri) to convert relative redirect_uri
values to an absolute URL, check its return value for NGX_OK, then pass the
resulting absolute redirect_uri to ngx_oidc_url_validate; keep the existing
error logging and return NULL on failures (refer to ngx_http_complex_value,
redirect_uri, ngx_oidc_url_build_absolute, and ngx_oidc_url_validate).

In `@src/ngx_oidc_handler_authenticate.h`:
- Around line 9-22: The header declares
ngx_oidc_handler_authenticate(ngx_http_request_t *r, ngx_http_oidc_provider_t
*provider) but does not define or forward-declare ngx_http_oidc_provider_t; add
a declaration to avoid compilation errors by either including the module header
that defines the type (e.g., add `#include` "ngx_http_oidc_module.h") or add a
minimal forward declaration (typedef struct ngx_http_oidc_provider_s
ngx_http_oidc_provider_t;) at the top of this header so the symbol
ngx_http_oidc_provider_t referenced by ngx_oidc_handler_authenticate is known to
callers.

In `@src/ngx_oidc_handler_callback.c`:
- Around line 1406-1518: The ORIGINAL_URI (NGX_OIDC_SESSION_KEY_ORIGINAL_URI) is
deleted when ngx_oidc_session_rotate() removes the old session but it is not
migrated to the new session, so callback_redirect() cannot find the original
URI; update ngx_oidc_session_rotate() to include
NGX_OIDC_SESSION_KEY_ORIGINAL_URI in the set of keys copied from old_session_id
to new_session_id (perform the copy before deleting the old session) so that
callback_redirect() can retrieve the original URI using either session ID
(ensure any helper functions used for copying are invoked for the ORIGINAL_URI
key).

In `@src/ngx_oidc_handler_logout.h`:
- Around line 9-24: The header ngx_oidc_handler_logout.h declares ngx_int_t
ngx_oidc_handler_logout(ngx_http_request_t *r, ngx_http_oidc_provider_t
*provider) but does not include the definition of ngx_http_oidc_provider_t;
update the header to be self-contained by adding `#include`
"ngx_http_oidc_module.h" immediately after the existing NGINX includes (after
`#include` <ngx_http.h>) so the type ngx_http_oidc_provider_t is defined for
callers of ngx_oidc_handler_logout.

---

Duplicate comments:
In @.github/workflows/package.yaml:
- Around line 54-63: The GitHub Action step "Build and push for Google example"
currently tags images as `${{ env.container }}:google`, which pushes to Docker
Hub and causes both architectures to overwrite the same tag; change the `tags`
value in that step (the docker/build-push-action@...) to include the GHCR prefix
and an architecture-specific suffix, e.g. `ghcr.io/${{ env.container }}:${{
matrix.arch }}-google` (or `ghcr.io/${{ env.container }}:${{ matrix.arch
}}.google`) so images are pushed to GHCR and are unique per `${{ matrix.arch
}}`.
- Around line 97-106: The step "Build and push for Google example" produces
invalid image names because the image references use "${CONTAINER}${TAG}-amd64"
and "${CONTAINER}${TAG}-arm64" without a ":" separator; update the docker buildx
imagetools create arguments to use "${CONTAINER}:${TAG}-amd64" and
"${CONTAINER}:${TAG}-arm64" (and keep the target tag "-t
\"${CONTAINER}:${TAG}\"") so CONTAINER and TAG are separated correctly.

In @.github/workflows/test.yaml:
- Around line 64-67: Replace the insecure HTTP nginx download with HTTPS and
make curl fail loudly: update the curl invocation that uses the nginx_version
variable (see nginx_version="$(nginx -v ...)" and the curl -sL -o
"nginx-${nginx_version}.tar.gz" ... line) to use
"https://nginx.org/download/nginx-${nginx_version}.tar.gz" and add the -f flag
to curl (so failures exit non‑zero) while keeping any needed -s/-S/-L options.
- Around line 87-100: Remove the output suppression so CI retains startup and
test stderr: stop using "&>/dev/null" on the docker run that launches the
"oidc-stub" container (or redirect its stdout/stderr to a log file and run
"docker logs oidc-stub" afterwards) and stop redirecting test stderr away in the
"prove -r t 2> /dev/null" command (either let stderr flow to the job log or
redirect to a retained file/artifact and fail the job if non-empty); update the
lines that run the oidc-stub container and the "prove -r t" invocation
accordingly.

In `@Dockerfile`:
- Around line 28-30: The Dockerfile currently downloads nginx using an insecure
HTTP URL constructed with nginx_version (nginx_version=$(nginx -v ...)) via curl
-sL -o nginx-${nginx_version}.tar.gz
http://nginx.org/download/nginx-${nginx_version}.tar.gz; change the download URL
to HTTPS (use https://nginx.org/download/nginx-${nginx_version}.tar.gz) so curl
fetches over TLS, and keep the rest of the pipeline (curl and tar -xf
nginx-${nginx_version}.tar.gz) unchanged.

In `@src/ngx_oidc_handler_logout.c`:
- Around line 175-201: Extract the duplicated send-header/output-filter/finalize
sequence used in ngx_oidc_handler_logout into a single static helper (e.g.,
ngx_oidc_finalize_after_redirect) that accepts ngx_http_request_t *r and the
current ngx_int_t rc or computes rc internally; this helper should call
ngx_http_send_header, check rc for NGX_ERROR or rc > NGX_OK and return rc, call
ngx_http_output_filter and return on NGX_ERROR, check r != r->connection->data
and log the debug message before returning NGX_DONE, otherwise call
ngx_http_finalize_request(r, rc) and return NGX_DONE; replace the duplicated
blocks in ngx_oidc_handler_logout (and the similar block found later) with calls
to this helper to centralize behavior.
- Around line 247-279: The code blindly appends "?client_id=..." to
end_session->data causing malformed URLs when end_session already has a query
string; update the logic that computes url_len and that builds the string
(around symbols end_session, url_len, encoded_client_id_len, ngx_cpymem, p,
logout_url) to detect whether end_session->data contains a '?' (and whether it
already ends with '?' or '&') and choose the proper separator ("?" vs "&" or
empty if already present), adjust the added separator length used in url_len
accordingly, and use that chosen separator when copying into logout_url so the
final URL is syntactically correct.

In `@src/ngx_oidc_handler_status.c`:
- Around line 460-463: The format specifier for the value returned by
ngx_oidc_session_store_memory_stats_get_max_entries (which is size_t) is wrong:
update the ngx_snprintf call that writes into b->last (using b->end - b->last)
to use "%uz" instead of "%ui" so the size_t is printed safely (locate the
ngx_snprintf invocation referencing b->last, b->end and
ngx_oidc_session_store_memory_stats_get_max_entries and change the format
specifier accordingly).

In `@src/ngx_oidc_hash.c`:
- Around line 112-140: ngx_oidc_hash_get_md currently uses substring checks
(ngx_strstr) which can incorrectly match crafted alg names; replace those checks
with explicit comparisons against the approved RFC7518 algorithm identifiers
(e.g. "RS256","ES256","PS256","HS256","ES256K" => EVP_sha256(),
"RS384","ES384","PS384","HS384" => EVP_sha384(), "RS512","ES512","PS512","HS512"
=> EVP_sha512(), and explicitly "EdDSA" => EVP_sha512()). Update the function to
test the algorithm string with exact comparisons (ngx_strcmp or exact-length
ngx_strncmp) for each listed identifier and return NULL for anything not exactly
matched. Ensure comparisons remain case-sensitive and preserve the existing NULL
input guard.

In `@src/ngx_oidc_http.c`:
- Around line 112-145: The code only reads the first ngx_buf_t (sr->out->buf and
single cl->buf) which truncates multi-buffer responses; update the logic that
collects the response body (around the http_response_body_copy calls) to iterate
and concatenate across the full ngx_chain_t/ngx_buf_t chains instead of passing
a single buffer: for sr->out walk its chain (follow sr->out->next or use the
chain head if present), and for upstream use sr->upstream->buffer plus iterate
sr->upstream->out_bufs each cl->buf, feeding each buf segment into the
response-accumulation routine (or extend http_response_body_copy to accept a
chain) until all data (or last_buf) is consumed, preserving order and handling
partial/temporary buffers via ngx_buf_size/pos/last. Ensure checks still handle
NULL sr->upstream and return the same rc semantics.

In `@src/ngx_oidc_json.c`:
- Around line 37-150: The validation uses hardcoded limits inside
ngx_oidc_json_validate (max_string_length=4096, max_array_size=100,
max_object_keys=256) that can reject valid JWKS/metadata (e.g. x5c); replace
these static consts with configurable values from the module config (add nginx
directives and fields in the module config struct, e.g.,
oidc_json_max_string_length, oidc_json_max_array_size,
oidc_json_max_object_keys) and read those config values in
ngx_oidc_json_validate instead of the constants (fall back to raised safe
defaults, e.g. string 16384, array 1000, object_keys 4096), wiring directive
parsing to populate the config and using those config fields wherever
ngx_oidc_json_validate is called (preserve existing error logs but include the
configured limits in messages).

In `@src/ngx_oidc_json.h`:
- Around line 13-20: Change the opaque JSON handle from a raw void pointer to an
incomplete struct type so the public API using ngx_oidc_json_t * remains a
pointer to an opaque object: replace "typedef void *ngx_oidc_json_t;" with
"typedef struct ngx_oidc_json_s ngx_oidc_json_t;" and keep function signatures
such as ngx_oidc_json_parse(...) returning ngx_oidc_json_t *; update all
internal casts/usages in src/ngx_oidc_json.c (and any callers) that assumed
void** semantics—search for ngx_oidc_json_t usages and NGX_OIDC_JSON_CAST and
adjust them to cast to/from struct ngx_oidc_json_s* (or remove extra
indirection) so code no longer treats the handle as void**; run the provided
grep script to verify there are no non-pointer ngx_oidc_json_t usages remaining.

In `@src/ngx_oidc_jwks.c`:
- Around line 1596-1599: The traversal in ngx_oidc_jwks_clear_fetch_flag
incorrectly branches solely on the hash (using (hash < shm_node->key_hash) ?
node->left : node->right), causing hash-collision errors; update the comparison
so when hash == shm_node->key_hash you perform a byte-wise URI comparison using
ngx_memn2cmp against shm_node->key (and key length) to decide left/right,
otherwise keep the existing hash < shm_node->key_hash branch—modify the
traversal logic in ngx_oidc_jwks_clear_fetch_flag to use ngx_memn2cmp for
equal-hash disambiguation.
- Around line 1119-1124: In jwks_shm_save the rbtree traversal uses "hash <
shm_node->key_hash" even when hash == shm_node->key_hash, which forces
node->right and breaks collision ordering used by jwks_rbtree_insert_value;
change the branch to perform the same URI-based comparison used in
jwks_shm_lookup: when hash == shm_node->key_hash call ngx_memn2cmp against the
stored key (use shm_node->key_len and the incoming uri/len) and choose node =
node->left or node->right based on that string comparison so colliding keys are
ordered consistently with jwks_rbtree_insert_value.
- Around line 1489-1541: Replace the hash-only comparisons and the unconditional
fetch lock set to avoid collisions and TOCTOU races: when walking the rbtree
(both where it currently does `hash < shm_node->key_hash`) use
ngx_memn2cmp(shm_node->jwks_uri.data, jwks_uri->data, shm_node->jwks_uri.len,
jwks_uri->len) to decide left/right/equal so nodes with equal hash but different
URIs are distinguished; after re-locking and locating the candidate node
(jwks_shm_node_t *shm_node) check that the URI matches and then check
shm_node->fetching before setting it — if fetching == 0 set it to 1 and return
NGX_OK, otherwise ngx_shmtx_unlock(&shm->shpool->mutex) and return a suitable
status (e.g., NGX_BUSY) so you do not overwrite another worker's claim; keep
jwks_shm_save and the unlock/lock sequence but add these extra
comparisons/conditional before assigning shm_node->fetching.

In `@src/ngx_oidc_jwt.c`:
- Around line 293-335: The code only reads the first element of an "aud" array;
instead iterate all elements returned by ngx_oidc_json_array_size and use
ngx_oidc_json_array_get to check each element with ngx_oidc_json_is_string,
comparing the string value to the expected client id (the RP's client_id/config)
instead of only taking index 0; when a match is found allocate claims->audience,
copy that matching string (using ngx_pcalloc and ngx_memcpy as done) and stop;
if no element matches, log an error and ngx_oidc_json_free(root) then return
NGX_ERROR. Ensure you reuse the existing functions ngx_oidc_json_object_get,
ngx_oidc_json_is_array, ngx_oidc_json_array_get and handle NULL/memory failures
exactly like the current branches.

In `@src/ngx_oidc_metadata.c`:
- Around line 404-427: The rollback logic in the allocation loop for
ngx_slab_alloc_locked (new_data array in the code handling
src_fields/dest_fields) assumes contiguous allocations via allocated_count and
can leak when some new_data[i] remain NULL; change the failure cleanup to
iterate over the full new_data[] array (all 6 slots) and call
ngx_slab_free_locked(shm->shpool, new_data[j]) for each j where new_data[j] !=
NULL, and remove or update reliance on allocated_count for freeing so every
successful allocation is explicitly freed on error; ensure the error path still
logs using ngx_log_error and leaves new_data entries consistent for the caller.
- Around line 888-917: After re-locking the shared pool and locating the
matching cache_node (after ngx_memn2cmp), re-check cache_node->fetching before
unconditionally setting it: if cache_node->fetching is already non-zero,
ngx_shmtx_unlock(&shm->shpool->mutex) and return a non-fetching result (e.g.,
NGX_DECLINED or other existing sentinel used by this code path) to avoid
duplicate fetches; only set cache_node->fetching = 1 and proceed to log/return
NGX_OK when it was previously zero. Ensure you use the existing symbols
cache_node, fetching, ngx_shmtx_lock/ngx_shmtx_unlock and preserve the current
log/return behavior when acquiring the fetch lock.

In `@src/ngx_oidc_session.c`:
- Around line 331-434: The function ngx_oidc_session_get_temporary_id currently
parses r->headers_in.cookie manually and only inspects the first Cookie header;
replace that manual parsing with the existing helper ngx_oidc_http_cookie_get to
retrieve the cookie value for the fixed name NGX_OIDC_SESSION_CALLBACK, then
perform the same "provider:session_id" split on the returned ngx_str_t value
(use ngx_strlchr to find ':'), allocate a new ngx_str_t and copy only the
session_id portion into r->pool (handle the no-colon case by copying the whole
value), and preserve all current NULL checks and memory allocation error returns
used in ngx_oidc_session_get_temporary_id.
- Around line 436-530: ngx_oidc_session_get_permanent_id currently reimplements
manual cookie parsing and only reads the first Cookie header; replace the manual
loop with the same helper used in get_temporary_id (either call
ngx_oidc_http_cookie_get or use ngx_http_parse_multi_header_lines to collect
cookies) so all Cookie headers are considered, preserve the existing behavior of
resolving provider->cookie_name via ngx_http_complex_value and returning an
allocated ngx_str_t* with the session id, and ensure memory allocation and
null-termination remain correct (refer to ngx_oidc_session_get_permanent_id and
ngx_oidc_http_cookie_get/get_temporary_id to mirror the refactor).

---

Nitpick comments:
In `@Dockerfile`:
- Around line 53-63: The image is currently running as root; update the
Dockerfile (the stage starting with "FROM nginx AS module") to create and switch
to a non-root user and ensure nginx runs on an unprivileged port or with minimal
capabilities: add a non-root user/group, chown the nginx config and any runtime
directories modified by the RUN block (including /etc/nginx and any cache or log
dirs touched during the RUN --mount block), update nginx.conf (the file
referenced by the sed line that inserts load_module
"/usr/lib/nginx/modules/ngx_http_oidc_module.so") to listen on an unprivileged
port (e.g., 8080) or adjust the container runtime to drop privileges, and add a
USER <nonroot> directive in the final image stage so the container does not run
as root. Ensure file permissions are set so the non-root user can read the
module and write to logs/temp.

In `@src/ngx_oidc_handler_status.h`:
- Around line 13-19: Update the `@return` doc in the ngx_oidc_handler_status.h
comment to match the actual return values used by the implementation (see
ngx_oidc_handler_status.c); replace the generic "NGX_OK or NGX_ERROR" with the
specific HTTP status codes returned by the handler (e.g., NGX_HTTP_OK,
NGX_HTTP_NOT_ALLOWED, NGX_HTTP_INTERNAL_SERVER_ERROR) and a short note that the
function returns an ngx_int_t HTTP status code per ngx_http handler conventions.

In `@src/ngx_oidc_jwt.c`:
- Around line 1338-1345: The code uses ngx_strstr(algorithm, "...") which can
match unintended strings; update the logic in ngx_oidc_jwt.c where hash_half_len
is set (variable algorithm) to use explicit comparisons against known JWT alg
values (e.g. "HS256","RS256","ES256","PS256" -> hash_half_len=16;
"HS384","RS384","ES384","PS384" -> 24; and
"HS512","RS512","ES512","PS512","EdDSA" -> 32) instead of substring searches;
implement these checks with exact string comparisons (ngx_strcmp/ngx_strncmp or
equivalent) to ensure only the intended algorithms set each truncation length.

In `@src/ngx_oidc_random.h`:
- Around line 13-81: Update the function docs to specify buffer
ownership/lifetime: for ngx_oidc_random_state, ngx_oidc_random_nonce,
ngx_oidc_random_code_verifier, ngx_oidc_random_code_challenge, and
ngx_oidc_random_session_id state that on success (NGX_OK) the returned
ngx_str_t->data is allocated from the request pool (r->pool), is NUL-terminated
if applicable, must not be freed by the caller, and remains valid until the
request pool is destroyed; also state that on failure the output is
undefined/unchanged. Add this ownership contract to each function comment in
ngx_oidc_random.h.

In `@src/ngx_oidc_session_store_memory.c`:
- Around line 522-524: The get path is doing unnecessary queue reordering:
remove the calls to ngx_queue_remove(&ocn->queue) and
mem_queue_insert_expire(&octx->expire_queue, ocn) in the get operation since
ocn->expires is not modified and reinsertion will place the node in the same
spot, holding the lock longer; if you intended LRU semantics instead, update
ocn->expires before reinserting and then call ngx_queue_remove +
mem_queue_insert_expire, otherwise delete those two lines and any comments
referencing reordering to avoid needless work.

In `@src/ngx_oidc_session.c`:
- Around line 171-292: ngx_oidc_session_rotate can leave partially-moved data in
the new session when a later ngx_oidc_session_set fails; modify
ngx_oidc_session_rotate so that on any failure after a successful
ngx_oidc_session_set it calls ngx_oidc_session_delete for the same keys on
new_session_id to roll back partial moves (delete NGX_OIDC_SESSION_KEY_ID_TOKEN,
NGX_OIDC_SESSION_KEY_ACCESS_TOKEN, NGX_OIDC_SESSION_KEY_REFRESH_TOKEN,
NGX_OIDC_SESSION_KEY_USERINFO as applicable), keep error logging via
r->connection->log, and return NGX_ERROR; implement the cleanup in each failure
branch inside ngx_oidc_session_rotate (use the same ngx_oidc_session_delete
helper used for old_session_id).

Repository owner deleted a comment from coderabbitai bot Feb 20, 2026
Repository owner deleted a comment from coderabbitai bot Feb 20, 2026
Repository owner deleted a comment from coderabbitai bot Feb 20, 2026
Repository owner deleted a comment from coderabbitai bot Feb 20, 2026
Repository owner deleted a comment from coderabbitai bot Feb 20, 2026
Repository owner deleted a comment from coderabbitai bot Feb 20, 2026
Repository owner deleted a comment from coderabbitai bot Feb 20, 2026
Repository owner deleted a comment from coderabbitai bot Feb 20, 2026
Repository owner deleted a comment from coderabbitai bot Feb 20, 2026
Repository owner deleted a comment from coderabbitai bot Feb 20, 2026
Repository owner deleted a comment from coderabbitai bot Feb 20, 2026
Repository owner deleted a comment from coderabbitai bot Feb 20, 2026
Repository owner deleted a comment from coderabbitai bot Feb 20, 2026
Repository owner deleted a comment from coderabbitai bot Feb 20, 2026
Repository owner deleted a comment from coderabbitai bot Feb 20, 2026
Repository owner deleted a comment from coderabbitai bot Feb 20, 2026
Repository owner deleted a comment from coderabbitai bot Feb 20, 2026
Repository owner deleted a comment from coderabbitai bot Feb 20, 2026
Repository owner deleted a comment from coderabbitai bot Feb 20, 2026
Repository owner deleted a comment from coderabbitai bot Feb 20, 2026
@kjdev kjdev merged commit 1af6878 into main Feb 20, 2026
1 check passed
@kjdev kjdev deleted the develop branch February 20, 2026 05:14
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