Skip to content

feat(api): per-user resource selection endpoint — Part of #586 - #981

Merged
northdpole merged 3 commits into
OWASP:mainfrom
skypank-coder:feat/586-resource-selection-api
Aug 1, 2026
Merged

feat(api): per-user resource selection endpoint — Part of #586#981
northdpole merged 3 commits into
OWASP:mainfrom
skypank-coder:feat/586-resource-selection-api

Conversation

@skypank-coder

Copy link
Copy Markdown
Contributor

What & why

Second increment of #586. Builds on PR1's user persistence to expose the
per-user resource selection over the API, so the frontend (PR4) and server-side
filtering (PR3) have a read/write surface. No new tables or migrations — pure
HTTP + validation over PR1's Node_collection methods.

Endpoints

/rest/v1/user/resources (canonical /rest/v1 surface):

  • GET{"selected": [...]} — the current user's saved standard names, sorted.
  • PUT {"selected": ["ASVS","CWE"]} → dedup + replace, persists via PR1's
    set_user_resource_selection, returns the stored list.

Gating

Requires login_required + is_login_enabled() + is_myopencre_enabled()
(per the #966 decision):

  • Login flag off → safe default {"selected": []}, 200, no auth, no writes.
  • MyOpenCRE capability off → same safe default; the gate short-circuits before
    any DB read/write.
  • Flag on + anonymous → 401.
  • Invalid PUT body (missing selected, not a list, empty/non-string entries) → 400.

Implemented via a generalized feature_enabled_or_default helper composed over
login_required (a generalization of PR1's flag-gating; PR1's behaviour is
unchanged).

OpenAPI

Endpoint added to the spec properly — two PathSpec entries +
@openapi_documented decorators, docs/api/openapi.yaml regenerated (not
hand-edited). The OpenAPI guardrail passes all four checks (documented views,
freshness, validity, route coverage).

Testing

12 new tests, verified on real Postgres (prod parity): flag-off default,
myopencre-off default + no-write, anonymous 401, dedup, replace against the live
UNIQUE(user_id, standard_name), and body-validation 400s. The myopencre-off
tests are mutation-proven — removing the capability from the gate makes them fail
(GET leaks the saved selection, PUT persists), restoring it makes them pass — so
they genuinely detect a bypassed gate rather than passing incidentally.

black clean; no new mypy errors vs. baseline.

Scope boundaries

No server-side filtering (PR3), no frontend (PR4), no new DB surface. Reuses
PR1's methods and login_required.

Files (4)

  • application/web/web_main.py — routes + gate + feature_enabled_or_default.
  • application/web/openapi_registry.py — two PathSpec entries.
  • docs/api/openapi.yaml — regenerated.
  • application/tests/user_resources_api_test.py — new, 12 tests.

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: ca148e98-59c6-4a01-a94a-d14026e94bf7

📥 Commits

Reviewing files that changed from the base of the PR and between 5512979 and 6423878.

📒 Files selected for processing (1)
  • application/tests/user_resources_api_test.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • application/tests/user_resources_api_test.py

Summary by CodeRabbit

  • New Features

    • Added API operations to retrieve and replace a user’s selected resources.
    • Selections are trimmed, deduplicated, validated, and persisted.
    • Access is controlled by login and feature settings, with appropriate handling for anonymous requests.
    • Responses include authentication and validation errors and are marked to prevent caching.
  • Documentation

    • Added OpenAPI documentation covering request formats, responses, authentication behavior, and validation rules.

Walkthrough

Adds feature-gated GET and PUT endpoints for per-user resource selections. The change adds session-based user resolution, validation, normalization, persistence behavior, no-store headers, OpenAPI definitions, documentation, and tests.

Changes

User resource selection API

Layer / File(s) Summary
Resource-selection API contract
application/web/openapi_registry.py, docs/api/openapi.yaml
Adds OpenAPI request-body support and documents GET and PUT /rest/v1/user/resources operations, schemas, feature behavior, authentication errors, and validation responses.
Feature-gated endpoint runtime
application/web/web_main.py
Adds feature gating, session-first user resolution, no-store caching, authenticated GET handling, and validated PUT handling with trimming, deduplication, replacement, and persistence.
Endpoint behavior validation
application/tests/user_resources_api_test.py
Tests feature flags, authentication, reads, writes, replacement, normalization, invalid bodies, cache headers, session-user precedence, and database writes.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

  • OWASP/OpenCRE#968: Both changes update application/web/openapi_registry.py and docs/api/openapi.yaml for related API definitions.

Suggested reviewers: pa04rth, paoga87, robvanderveer

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.78% 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
Title check ✅ Passed The title clearly identifies the API feature and the per-user resource selection change.
Description check ✅ Passed The description directly explains the endpoint, gating, validation, persistence, OpenAPI updates, and tests.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@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: 3

🧹 Nitpick comments (2)
application/web/web_main.py (2)

1233-1246: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Consider disabling caching for per-user selection responses.

Neither GET nor PUT /rest/v1/user/resources sets Cache-Control, so a per-user list could be cached by an intermediary proxy or browser (e.g., shared-computer back/forward cache) and served to a different session.

Also applies to: 1249-1269

🤖 Prompt for 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.

In `@application/web/web_main.py` around lines 1233 - 1246, Disable caching for
both get_user_resources and the corresponding PUT user-resources handler by
adding an explicit no-cache/no-store Cache-Control response header to their JSON
responses. Ensure the header applies to authenticated per-user selections,
including the feature-disabled fallback where applicable.

846-882: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Tighten type hints for mypy compliance.

feature_enabled_or_default(is_enabled: Any, default_factory: Any) -> Any and _resolve_current_user(database): use Any/no annotations, unlike the fully-typed upsert_user/get_user_by_sub in db.py. As per coding guidelines, **/*.py should "Run make mypy for Python type checking" — loose Any typing here defeats that check for callable misuse (e.g. a non-callable default_factory).

def feature_enabled_or_default(
    is_enabled: Callable[[], bool], default_factory: Callable[[], Any]
) -> Callable[[Callable], Callable]:
    ...

def _resolve_current_user(database: "db.Node_collection") -> Optional["db.User"]:
    ...
🤖 Prompt for 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.

In `@application/web/web_main.py` around lines 846 - 882, Tighten the annotations
on feature_enabled_or_default by replacing Any with callable types for
is_enabled and default_factory and an appropriately typed decorator return,
importing the required typing symbols. Annotate _resolve_current_user’s database
parameter with db.Node_collection and its return value with Optional[db.User],
preserving the existing user lookup and creation behavior.

Source: Coding guidelines

🤖 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 `@application/database/db.py`:
- Around line 1101-1125: Update set_user_resource_selection to handle concurrent
writes using the same IntegrityError recovery pattern established by
upsert_user: catch the insert/commit failure, roll back the session, and retry
the replacement operation so concurrent PUTs do not surface an unhandled error.
Preserve deduplication and return the refreshed selection after a successful
commit.

In `@application/web/openapi_registry.py`:
- Around line 333-340: Update put_user_resources in
application/web/openapi_registry.py to configure PathSpec request-body
generation for a required JSON object containing a required selected array of
non-empty strings, then update docs/api/openapi.yaml at lines 516-533 with the
matching required application/json requestBody schema.

In `@application/web/web_main.py`:
- Around line 1256-1263: Normalize each string in selected by trimming leading
and trailing whitespace before passing it to set_user_resource_selection in
put_user_resources. Validate and persist the normalized values so equivalent
entries such as whitespace-padded and unpadded names deduplicate consistently.

---

Nitpick comments:
In `@application/web/web_main.py`:
- Around line 1233-1246: Disable caching for both get_user_resources and the
corresponding PUT user-resources handler by adding an explicit no-cache/no-store
Cache-Control response header to their JSON responses. Ensure the header applies
to authenticated per-user selections, including the feature-disabled fallback
where applicable.
- Around line 846-882: Tighten the annotations on feature_enabled_or_default by
replacing Any with callable types for is_enabled and default_factory and an
appropriately typed decorator return, importing the required typing symbols.
Annotate _resolve_current_user’s database parameter with db.Node_collection and
its return value with Optional[db.User], preserving the existing user lookup and
creation behavior.
🪄 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: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 688f202a-5958-4507-a9d8-43f0df37825c

📥 Commits

Reviewing files that changed from the base of the PR and between 6b6a5ac and 5156c51.

📒 Files selected for processing (9)
  • application/database/db.py
  • application/tests/user_model_test.py
  • application/tests/user_resources_api_test.py
  • application/tests/web_main_test.py
  • application/web/openapi_registry.py
  • application/web/web_main.py
  • docs/api/openapi.yaml
  • migrations/versions/a1b2c3d4e5f6_add_users_and_resource_selection.py
  • migrations/versions/f0e1d2c3b4a5_merge_heads_before_users.py

Comment thread application/database/db.py
Comment thread application/web/openapi_registry.py Outdated
Comment thread application/web/web_main.py

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
application/web/openapi_registry.py (1)

307-334: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Document the 401 response for both resource endpoints.

web_main.py returns 401 for anonymous requests when both feature flags are enabled, but these PathSpecs document only 200 and, for PUT, 400. Add a 401 response to both entries and keep docs/api/openapi.yaml synchronized so clients can discover the authentication failure.

Also applies to: 336-343

🤖 Prompt for 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.

In `@application/web/openapi_registry.py` around lines 307 - 334, Add a documented
401 response to both resource endpoint PathSpec entries, including the GET entry
identified by get_user_resources and the corresponding PUT entry, while
preserving their existing responses. Regenerate or update docs/api/openapi.yaml
so the OpenAPI specification remains synchronized and exposes the authentication
failure.
docs/api/openapi.yaml (2)

510-515: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Require selected in both response schemas.

The documented response is {"selected": [...]}, but omitting required: [selected] allows {} according to OpenAPI.

Proposed schema fix
                 type: object
+                required:
+                - selected
                 properties:
                   selected:

Apply this to both the GET and PUT response schemas.

Also applies to: 542-547

🤖 Prompt for 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.

In `@docs/api/openapi.yaml` around lines 510 - 515, Add `required: [selected]` to
both GET and PUT response object schemas containing the `selected` property in
the OpenAPI definition, ensuring each documented response requires that field
while preserving its existing array-of-string type.

501-505: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Document the conditional 401 behavior.

The endpoint contract says anonymous users receive 401 when both login and MyOpenCRE flags are enabled, but neither operation documents that response. Clarify the GET description and add a 401 response to both GET and PUT; when either flag is disabled, document the empty-selection fallback instead.

Also applies to: 536-549

🤖 Prompt for 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.

In `@docs/api/openapi.yaml` around lines 501 - 505, Update the selected-standards
GET and PUT OpenAPI definitions to document that authenticated access is
required when both login and MyOpenCRE are enabled, including a 401 response for
anonymous users. Clarify each GET/PUT description to state that the endpoint
returns an empty selection when either feature flag is disabled, and add the
corresponding 401 response entries to both operations.
🤖 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.

Outside diff comments:
In `@application/web/openapi_registry.py`:
- Around line 307-334: Add a documented 401 response to both resource endpoint
PathSpec entries, including the GET entry identified by get_user_resources and
the corresponding PUT entry, while preserving their existing responses.
Regenerate or update docs/api/openapi.yaml so the OpenAPI specification remains
synchronized and exposes the authentication failure.

In `@docs/api/openapi.yaml`:
- Around line 510-515: Add `required: [selected]` to both GET and PUT response
object schemas containing the `selected` property in the OpenAPI definition,
ensuring each documented response requires that field while preserving its
existing array-of-string type.
- Around line 501-505: Update the selected-standards GET and PUT OpenAPI
definitions to document that authenticated access is required when both login
and MyOpenCRE are enabled, including a 401 response for anonymous users. Clarify
each GET/PUT description to state that the endpoint returns an empty selection
when either feature flag is disabled, and add the corresponding 401 response
entries to both operations.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 55780973-4784-49cb-b6fd-8379a657de19

📥 Commits

Reviewing files that changed from the base of the PR and between 5156c51 and 95f914c.

📒 Files selected for processing (5)
  • application/database/db.py
  • application/tests/user_resources_api_test.py
  • application/web/openapi_registry.py
  • application/web/web_main.py
  • docs/api/openapi.yaml
🚧 Files skipped from review as they are similar to previous changes (3)
  • application/tests/user_resources_api_test.py
  • application/web/web_main.py
  • application/database/db.py

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
application/database/db.py (1)

2697-2706: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Fix inconsistent return type and race condition on concurrent inserts.

There are two issues in this segment:

  1. Inconsistent return type: The function returns emb (a single Embeddings object) when inserting a new record, but returns existing (a list of Embeddings objects, since get_embedding uses .all()) at line 2714. This inconsistency will likely cause AttributeError or TypeError in callers.
  2. Race condition: If two requests concurrently add an embedding for the same document, both will evaluate if not existing: as true and attempt to insert. If there is a unique constraint, this will raise an unhandled IntegrityError (unlike upsert_user which safely recovers). If there is no constraint, duplicate rows will be created.

Consider returning existing[0] in the update branch, and wrapping the self.session.commit() in a try/except IntegrityError block to safely handle concurrent inserts.

🤖 Prompt for 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.

In `@application/database/db.py` around lines 2697 - 2706, Update the embedding
upsert function so both insert and update paths return a single Embeddings
object, specifically existing[0] after updating rather than the existing list.
Protect the new-record self.session.commit() with IntegrityError handling for
concurrent inserts, roll back the session, retrieve the now-existing embedding,
and return it instead of propagating the conflict or creating duplicates.
🤖 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.

Outside diff comments:
In `@application/database/db.py`:
- Around line 2697-2706: Update the embedding upsert function so both insert and
update paths return a single Embeddings object, specifically existing[0] after
updating rather than the existing list. Protect the new-record
self.session.commit() with IntegrityError handling for concurrent inserts, roll
back the session, retrieve the now-existing embedding, and return it instead of
propagating the conflict or creating duplicates.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 8f68c470-27f2-48c4-9d20-0469da758dd2

📥 Commits

Reviewing files that changed from the base of the PR and between 95f914c and cd16ffa.

📒 Files selected for processing (5)
  • application/database/db.py
  • application/tests/web_main_test.py
  • application/web/openapi_registry.py
  • application/web/web_main.py
  • docs/api/openapi.yaml
🚧 Files skipped from review as they are similar to previous changes (4)
  • application/tests/web_main_test.py
  • application/web/openapi_registry.py
  • docs/api/openapi.yaml
  • application/web/web_main.py

@northdpole northdpole left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the clean API surface — feature_enabled_or_default + login/MyOpenCRE gate, validation/trim, OpenAPI coverage, and the mutation-proven flag-off tests look solid.

Blocking before approve: rebase onto current main (now includes #980). This branch still carries the pre-#980 DB layer:

  1. Obsolete Alembic merge — still has f0e1d2c3b4a5 and a1b2c3d4e5f6 revising it. On main, users tables land via a1b2c3d4e5f6c7d8e9f0a1b2 (no f0e1…). After rebase this PR should be API-only (routes, OpenAPI, user_resources_api_test.py) with no migration files / no re-introducing the DB models.
  2. Stale login paths vs #980 — this branch’s /rest/v1/login NO_LOGIN path and broad except Exception on callback persistence are behind what landed in #980. Rebase will pick those up; don’t re-apply the old versions.

Non-blocking (nice after rebase):

  • Global @app.after_request sets cache_control.max_age = 300; consider Cache-Control: no-store on the per-user GET/PUT responses.
  • Prefer session['user_id'] when present in _resolve_current_user before upsert-by-sub (avoids an extra lookup on the happy path after #980).

Once rebased green, ping for re-review — then #963 (auth route migration) can open cleanly on top of the persisted-user foundation.

@skypank-coder
skypank-coder force-pushed the feat/586-resource-selection-api branch from cd16ffa to 5512979 Compare August 1, 2026 14:57

@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

🧹 Nitpick comments (2)
application/tests/user_resources_api_test.py (2)

137-147: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert the required GET ordering directly.

The test submits ["CWE", "ASVS"] but sorts the GET response before comparison. An endpoint that returns insertion order would pass although the API contract requires sorted values.

Assert json.loads(get.data)["selected"] == ["ASVS", "CWE"] without sorting.

🤖 Prompt for 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.

In `@application/tests/user_resources_api_test.py` around lines 137 - 147, Update
the GET response assertion in the user resources test to compare
json.loads(get.data)["selected"] directly with ["ASVS", "CWE"] instead of
sorting it, while leaving the PUT assertion unchanged.

216-247: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Verify that invalid PUT requests do not replace saved selections.

These cases only assert 400 against a user with no selection. Seed a selection before each invalid request, then GET the resource and assert that the original values remain. This detects validation that occurs after replacement begins.

🤖 Prompt for 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.

In `@application/tests/user_resources_api_test.py` around lines 216 - 247, The
test_put_400_on_invalid_body test must verify that rejected PUT requests
preserve existing selections. Seed a known selection before each invalid
payload, send the PUT, assert a 400 response, then GET the resource and assert
the seeded values remain unchanged; repeat this setup for the payloads already
covered.
🤖 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 `@application/tests/user_resources_api_test.py`:
- Line 20: Preserve the pre-existing NO_LOAD_GRAPH_DB environment value across
the test lifecycle: update the test case’s setUp and tearDown methods to save
and restore it, or manage the override with a patch.dict patcher started and
stopped for the test. Ensure cleanup does not remove a value that was already
configured by the test runner.

---

Nitpick comments:
In `@application/tests/user_resources_api_test.py`:
- Around line 137-147: Update the GET response assertion in the user resources
test to compare json.loads(get.data)["selected"] directly with ["ASVS", "CWE"]
instead of sorting it, while leaving the PUT assertion unchanged.
- Around line 216-247: The test_put_400_on_invalid_body test must verify that
rejected PUT requests preserve existing selections. Seed a known selection
before each invalid payload, send the PUT, assert a 400 response, then GET the
resource and assert the seeded values remain unchanged; repeat this setup for
the payloads already covered.
🪄 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: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: d8966307-9cc7-4d5e-8a9f-dafdf21534be

📥 Commits

Reviewing files that changed from the base of the PR and between cd16ffa and 5512979.

📒 Files selected for processing (4)
  • application/tests/user_resources_api_test.py
  • application/web/openapi_registry.py
  • application/web/web_main.py
  • docs/api/openapi.yaml
🚧 Files skipped from review as they are similar to previous changes (3)
  • application/web/openapi_registry.py
  • docs/api/openapi.yaml
  • application/web/web_main.py

Comment thread application/tests/user_resources_api_test.py

@northdpole northdpole left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-reviewed after the rebase onto main/#980.

Blocking items from the earlier review are addressed: API-only diff (no migrations / no reintroduced DB layer), current login paths preserved, and the non-blocking no-store + session['user_id'] preferences landed. CI is green.

Optional follow-ups only (not blocking): assert GET ordering without re-sorting in the persist test, and seed a selection before invalid PUT 400s to prove no partial replace.

Approving and merging with rebase.

@northdpole
northdpole merged commit 44494dc into OWASP:main Aug 1, 2026
6 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.

2 participants