easyapi-django 1.2.2__tar.gz → 1.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. {easyapi_django-1.2.2/easyapi_django.egg-info → easyapi_django-1.3.0}/PKG-INFO +3 -2
  2. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/README.md +2 -1
  3. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/base.py +22 -8
  4. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/mcp/protocol.py +9 -8
  5. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/sensitive.py +12 -0
  6. {easyapi_django-1.2.2 → easyapi_django-1.3.0/easyapi_django.egg-info}/PKG-INFO +3 -2
  7. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/pyproject.toml +1 -1
  8. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/LICENSE +0 -0
  9. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/MANIFEST.in +0 -0
  10. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/__init__.py +0 -0
  11. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/auth.py +0 -0
  12. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/bearer.py +0 -0
  13. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/blocking.py +0 -0
  14. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/calc.py +0 -0
  15. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/calc_resource.py +0 -0
  16. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/client_ip.py +0 -0
  17. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/constants.py +0 -0
  18. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/core/__init__.py +0 -0
  19. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/core/endpoint.py +0 -0
  20. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/core/resource.py +0 -0
  21. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/dates.py +0 -0
  22. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/exception.py +0 -0
  23. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/filters.py +0 -0
  24. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/gen/__init__.py +0 -0
  25. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/gen/cli.py +0 -0
  26. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/gen/config.py +0 -0
  27. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/gen/generator.py +0 -0
  28. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/gen/inspector/__init__.py +0 -0
  29. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/gen/inspector/base.py +0 -0
  30. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/gen/inspector/mysql.py +0 -0
  31. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/gen/inspector/postgres.py +0 -0
  32. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/gen/templates/README.md.j2 +0 -0
  33. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/gen/templates/asgi.py.j2 +0 -0
  34. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/gen/templates/env.example.j2 +0 -0
  35. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/gen/templates/gitignore.j2 +0 -0
  36. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/gen/templates/manage.py.j2 +0 -0
  37. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/gen/templates/module/models.py.j2 +0 -0
  38. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/gen/templates/module/resources.py.j2 +0 -0
  39. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/gen/templates/requirements.txt.j2 +0 -0
  40. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/gen/templates/router/endpoints.py.j2 +0 -0
  41. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/gen/templates/router/urls.py.j2 +0 -0
  42. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/gen/templates/run.sh.j2 +0 -0
  43. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/gen/templates/settings/env.py.j2 +0 -0
  44. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/gen/templates/settings/settings.py.j2 +0 -0
  45. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/helpers.py +0 -0
  46. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/management/__init__.py +0 -0
  47. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/management/commands/__init__.py +0 -0
  48. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/management/commands/mcp_serve.py +0 -0
  49. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/mcp/__init__.py +0 -0
  50. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/mcp/schema.py +0 -0
  51. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/mcp/server.py +0 -0
  52. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/mcp/testing.py +0 -0
  53. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/mcp/tool.py +0 -0
  54. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/mcp/tools.py +0 -0
  55. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/middleware.py +0 -0
  56. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/oauth/__init__.py +0 -0
  57. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/oauth/_common.py +0 -0
  58. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/oauth/apps.py +0 -0
  59. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/oauth/clients.py +0 -0
  60. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/oauth/codes.py +0 -0
  61. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/oauth/constants.py +0 -0
  62. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/oauth/devices.py +0 -0
  63. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/oauth/discovery.py +0 -0
  64. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/oauth/migrations/0001_initial.py +0 -0
  65. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/oauth/migrations/__init__.py +0 -0
  66. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/oauth/models.py +0 -0
  67. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/oauth/resolver.py +0 -0
  68. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/oauth/resources/__init__.py +0 -0
  69. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/oauth/resources/authorize.py +0 -0
  70. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/oauth/resources/device.py +0 -0
  71. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/oauth/resources/device_authorization.py +0 -0
  72. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/oauth/resources/register.py +0 -0
  73. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/oauth/resources/revoke.py +0 -0
  74. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/oauth/resources/token.py +0 -0
  75. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/oauth/routes.py +0 -0
  76. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/oauth/session.py +0 -0
  77. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/oauth/settings_helper.py +0 -0
  78. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/oauth/templates/zeromcp_oauth/consent.html +0 -0
  79. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/oauth/templates/zeromcp_oauth/device.html +0 -0
  80. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/oauth/tokens.py +0 -0
  81. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/openapi.py +0 -0
  82. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/orm/__init__.py +0 -0
  83. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/rate_limit.py +0 -0
  84. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/redis_config.py +0 -0
  85. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/routes.py +0 -0
  86. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/schemas.py +0 -0
  87. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/security.py +0 -0
  88. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/serializer.py +0 -0
  89. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/settings_helper.py +0 -0
  90. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/tenant/__init__.py +0 -0
  91. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/tenant/db_router.py +0 -0
  92. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/tenant/registry.py +0 -0
  93. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/tenant/tenant.py +0 -0
  94. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi/util.py +0 -0
  95. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi_django.egg-info/SOURCES.txt +0 -0
  96. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi_django.egg-info/dependency_links.txt +0 -0
  97. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi_django.egg-info/entry_points.txt +0 -0
  98. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi_django.egg-info/requires.txt +0 -0
  99. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/easyapi_django.egg-info/top_level.txt +0 -0
  100. {easyapi_django-1.2.2 → easyapi_django-1.3.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: easyapi-django
3
- Version: 1.2.2
3
+ Version: 1.3.0
4
4
  Summary: Zero-effort MCP server (and REST API) generator from Django models.
5
5
  Author-email: Stamatios Stamou Jr <bushier.outsets.0c@icloud.com>
6
6
  License-Expression: MIT
@@ -235,7 +235,8 @@ Defaults are demo-friendly. Production deployments should:
235
235
  - **Tune session TTLs.** `SESSION_TTL` (cookies, default 1800s) and `API_SESSION_TTL` (api-key cache, default 300s) both slide on use. Pick numbers that match your security/UX trade-off.
236
236
  - **Never ship `DEBUG = True`.** Under `DEBUG`, edge security (scanner blocking, 4xx flood detection), rate limiting, credential-path defense and abuse blocking are all bypassed for dev ergonomics. The framework logs a loud startup warning when the bypass is active. To decouple from `DEBUG` — e.g. a staging box mirroring production — set `MCP = {'SECURITY_BYPASS': False}` to force protections on (or `True` to bypass without `DEBUG`).
237
237
  - **Trust your proxies explicitly.** IP-based rate limits and abuse blocking key on the client IP. `X-Forwarded-For` / `X-Real-IP` are only honored when `REMOTE_ADDR` is in `TRUSTED_PROXIES = ['10.0.0.0/8', ...]`; otherwise `REMOTE_ADDR` wins. Leave it unset behind a proxy and every request looks like it comes from the proxy.
238
- - **Bound the metrics surface.** The `/metrics` endpoint queries any model in the registry by default. Set `METRICS_MODELS = ['app_Model', ...]` to allowlist which models are queryable. References to credential-named columns (in filters, groups, order or calc) are always rejected.
238
+ - **Bound the metrics surface.** The `/metrics` endpoint queries any model in the registry by default. Set `METRICS_MODELS = ['app_Model', ...]` to allowlist which models are queryable. References to credential-named columns (in filters, groups, order or calc) are always rejected — the per-resource escape hatch does not reach `/metrics`.
239
+ - **Keep the escape hatch narrow.** Columns whose names look like credentials (`password`, `api_key`, `token`, `otp`, `secret`, …) are masked as `'*********'` on every response, on top of whatever `sensitive_fields` lists. When the heuristic catches a false positive, opt it out per surface: `edit_allow_sensitive_fields = ['token_count']` returns it verbatim on `GET /resource/<id>/` while LIST still masks it. `list_allow_sensitive_fields` does the same for LIST, where every row carries the value — reach for the detail-only form first.
239
240
  - **OAuth clients are public by default.** MCP clients (Claude.ai, ChatGPT, ...) authenticate with PKCE — list them under `MCP['OAUTH_CLIENTS']` with no `client_secret`. A client becomes confidential only with an explicit secret; `/register` never hands out a confidential secret (provision those out of band).
240
241
 
241
242
  ---
@@ -191,7 +191,8 @@ Defaults are demo-friendly. Production deployments should:
191
191
  - **Tune session TTLs.** `SESSION_TTL` (cookies, default 1800s) and `API_SESSION_TTL` (api-key cache, default 300s) both slide on use. Pick numbers that match your security/UX trade-off.
192
192
  - **Never ship `DEBUG = True`.** Under `DEBUG`, edge security (scanner blocking, 4xx flood detection), rate limiting, credential-path defense and abuse blocking are all bypassed for dev ergonomics. The framework logs a loud startup warning when the bypass is active. To decouple from `DEBUG` — e.g. a staging box mirroring production — set `MCP = {'SECURITY_BYPASS': False}` to force protections on (or `True` to bypass without `DEBUG`).
193
193
  - **Trust your proxies explicitly.** IP-based rate limits and abuse blocking key on the client IP. `X-Forwarded-For` / `X-Real-IP` are only honored when `REMOTE_ADDR` is in `TRUSTED_PROXIES = ['10.0.0.0/8', ...]`; otherwise `REMOTE_ADDR` wins. Leave it unset behind a proxy and every request looks like it comes from the proxy.
194
- - **Bound the metrics surface.** The `/metrics` endpoint queries any model in the registry by default. Set `METRICS_MODELS = ['app_Model', ...]` to allowlist which models are queryable. References to credential-named columns (in filters, groups, order or calc) are always rejected.
194
+ - **Bound the metrics surface.** The `/metrics` endpoint queries any model in the registry by default. Set `METRICS_MODELS = ['app_Model', ...]` to allowlist which models are queryable. References to credential-named columns (in filters, groups, order or calc) are always rejected — the per-resource escape hatch does not reach `/metrics`.
195
+ - **Keep the escape hatch narrow.** Columns whose names look like credentials (`password`, `api_key`, `token`, `otp`, `secret`, …) are masked as `'*********'` on every response, on top of whatever `sensitive_fields` lists. When the heuristic catches a false positive, opt it out per surface: `edit_allow_sensitive_fields = ['token_count']` returns it verbatim on `GET /resource/<id>/` while LIST still masks it. `list_allow_sensitive_fields` does the same for LIST, where every row carries the value — reach for the detail-only form first.
195
196
  - **OAuth clients are public by default.** MCP clients (Claude.ai, ChatGPT, ...) authenticate with PKCE — list them under `MCP['OAUTH_CLIENTS']` with no `client_secret`. A client becomes confidential only with an explicit secret; `/register` never hands out a confidential secret (provision those out of band).
196
197
 
197
198
  ---
@@ -40,7 +40,7 @@ from .helpers import (
40
40
  search_regex,
41
41
  )
42
42
  from .schemas import is_pydantic_model, serialize_output, validate_input
43
- from .sensitive import looks_sensitive
43
+ from .sensitive import is_sensitive
44
44
  from .serializer import CustomJSONEncoder
45
45
  from .util import validate_session_key
46
46
  from .rate_limit import RateLimiter
@@ -186,13 +186,21 @@ class BaseResource(CoreResource):
186
186
  description = None
187
187
 
188
188
  # Field names that should never leave the server. Listed names are
189
- # appended to ``list_exclude_fields`` and ``edit_exclude_fields`` at
190
- # init time. The framework already filters a baseline of obviously
191
- # sensitive names (``password``, ``api_key``, ``token``, ``otp``,
192
- # ``secret``, …) — use this for project-specific columns (audit
193
- # fields, internal flags, server-assigned IDs).
189
+ # masked as ``*********`` on serialization. The framework already
190
+ # filters a baseline of obviously sensitive names (``password``,
191
+ # ``api_key``, ``token``, ``otp``, ``secret``, …) — use this for
192
+ # project-specific columns (audit fields, internal flags,
193
+ # server-assigned IDs).
194
194
  sensitive_fields = None
195
195
 
196
+ # Escape hatch: names here are serialized verbatim, overriding both
197
+ # ``sensitive_fields`` and the name heuristic. Per surface, because a
198
+ # column safe to return on a single-object read is rarely safe to
199
+ # return on every row of an unbounded list. ``edit_*`` governs the
200
+ # detail payload (``_get_obj``), not the write path.
201
+ list_allow_sensitive_fields = None
202
+ edit_allow_sensitive_fields = None
203
+
196
204
  account_db = 'default'
197
205
  cache = False
198
206
  # Default TTL: settings.CACHE_TTL when set, else 120s.
@@ -307,6 +315,12 @@ class BaseResource(CoreResource):
307
315
  # Normalise once so downstream serialisation can use a set
308
316
  # without recomputing per-row.
309
317
  self.sensitive_fields = set(self.sensitive_fields) if self.sensitive_fields else set()
318
+ self.list_allow_sensitive_fields = (
319
+ set(self.list_allow_sensitive_fields) if self.list_allow_sensitive_fields else set()
320
+ )
321
+ self.edit_allow_sensitive_fields = (
322
+ set(self.edit_allow_sensitive_fields) if self.edit_allow_sensitive_fields else set()
323
+ )
310
324
  self.update_fields = list(self.update_fields) if self.update_fields else []
311
325
  self.create_fields = list(self.create_fields) if self.create_fields else []
312
326
  self.routes = list(self.routes) if self.routes else []
@@ -1325,7 +1339,7 @@ class BaseResource(CoreResource):
1325
1339
  if field in self.list_exclude_fields:
1326
1340
  continue
1327
1341
 
1328
- if looks_sensitive(field) or field in self.sensitive_fields:
1342
+ if is_sensitive(field, self.sensitive_fields, self.list_allow_sensitive_fields):
1329
1343
  result[field] = '*********'
1330
1344
  else:
1331
1345
  result[field] = getattr(row, field, None)
@@ -1449,7 +1463,7 @@ class BaseResource(CoreResource):
1449
1463
  result[key].append(item)
1450
1464
 
1451
1465
  for field in self.edit_fields:
1452
- if looks_sensitive(field) or field in self.sensitive_fields:
1466
+ if is_sensitive(field, self.sensitive_fields, self.edit_allow_sensitive_fields):
1453
1467
  result[field] = '*********'
1454
1468
  else:
1455
1469
  result[field] = getattr(self.obj, field, None)
@@ -31,7 +31,7 @@ import logging
31
31
 
32
32
  from pydantic import BaseModel, ValidationError
33
33
 
34
- from ..sensitive import looks_sensitive
34
+ from ..sensitive import is_sensitive
35
35
  from .schema import validate_input
36
36
 
37
37
  logger = logging.getLogger(__name__)
@@ -75,7 +75,7 @@ def _public_tools(tools):
75
75
  for t in tools]
76
76
 
77
77
 
78
- def _serialize_result(value, sensitive_fields=()):
78
+ def _serialize_result(value, sensitive_fields=(), allow_sensitive_fields=()):
79
79
  if isinstance(value, BaseModel):
80
80
  payload = value.model_dump()
81
81
  elif isinstance(value, list) and value and isinstance(value[0], BaseModel):
@@ -84,20 +84,20 @@ def _serialize_result(value, sensitive_fields=()):
84
84
  payload = value
85
85
  # Always scrub: the credential-name baseline applies even when the
86
86
  # toolset declares no ``sensitive_fields``.
87
- _scrub(payload, set(sensitive_fields))
87
+ _scrub(payload, set(sensitive_fields), set(allow_sensitive_fields))
88
88
  return payload
89
89
 
90
90
 
91
- def _scrub(obj, blocked):
91
+ def _scrub(obj, blocked, allowed):
92
92
  if isinstance(obj, dict):
93
93
  for key in list(obj.keys()):
94
- if key in blocked or looks_sensitive(key):
94
+ if is_sensitive(key, blocked, allowed):
95
95
  obj.pop(key)
96
96
  else:
97
- _scrub(obj[key], blocked)
97
+ _scrub(obj[key], blocked, allowed)
98
98
  elif isinstance(obj, list):
99
99
  for item in obj:
100
- _scrub(item, blocked)
100
+ _scrub(item, blocked, allowed)
101
101
 
102
102
 
103
103
  def _caller_scopes(request):
@@ -327,7 +327,8 @@ async def _dispatch_tool_call(params, tools, request):
327
327
  logger.exception('mcp.after_call.error tool=%s', tool_name)
328
328
 
329
329
  sensitive = getattr(toolset_cls, 'sensitive_fields', None) or ()
330
- payload = _serialize_result(ret, sensitive_fields=sensitive)
330
+ allowed = getattr(toolset_cls, 'allow_sensitive_fields', None) or ()
331
+ payload = _serialize_result(ret, sensitive_fields=sensitive, allow_sensitive_fields=allowed)
331
332
  return _tool_response(
332
333
  {'tool': tool_name, 'code': 'OK', 'data': payload},
333
334
  is_error=False,
@@ -35,3 +35,15 @@ def looks_sensitive(column_name):
35
35
  if any(name.endswith(s) for s in _SENSITIVE_SUFFIXES):
36
36
  return True
37
37
  return False
38
+
39
+
40
+ def is_sensitive(field, sensitive_fields=(), allow_sensitive_fields=()):
41
+ """Whether ``field`` must be withheld on the surface the caller serves.
42
+
43
+ ``allow_sensitive_fields`` is an escape hatch and wins over both the
44
+ per-resource deny list and the name heuristic — an explicit opt-in is
45
+ the only way a credential-named column ever leaves the server.
46
+ """
47
+ if field in allow_sensitive_fields:
48
+ return False
49
+ return looks_sensitive(field) or field in sensitive_fields
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: easyapi-django
3
- Version: 1.2.2
3
+ Version: 1.3.0
4
4
  Summary: Zero-effort MCP server (and REST API) generator from Django models.
5
5
  Author-email: Stamatios Stamou Jr <bushier.outsets.0c@icloud.com>
6
6
  License-Expression: MIT
@@ -235,7 +235,8 @@ Defaults are demo-friendly. Production deployments should:
235
235
  - **Tune session TTLs.** `SESSION_TTL` (cookies, default 1800s) and `API_SESSION_TTL` (api-key cache, default 300s) both slide on use. Pick numbers that match your security/UX trade-off.
236
236
  - **Never ship `DEBUG = True`.** Under `DEBUG`, edge security (scanner blocking, 4xx flood detection), rate limiting, credential-path defense and abuse blocking are all bypassed for dev ergonomics. The framework logs a loud startup warning when the bypass is active. To decouple from `DEBUG` — e.g. a staging box mirroring production — set `MCP = {'SECURITY_BYPASS': False}` to force protections on (or `True` to bypass without `DEBUG`).
237
237
  - **Trust your proxies explicitly.** IP-based rate limits and abuse blocking key on the client IP. `X-Forwarded-For` / `X-Real-IP` are only honored when `REMOTE_ADDR` is in `TRUSTED_PROXIES = ['10.0.0.0/8', ...]`; otherwise `REMOTE_ADDR` wins. Leave it unset behind a proxy and every request looks like it comes from the proxy.
238
- - **Bound the metrics surface.** The `/metrics` endpoint queries any model in the registry by default. Set `METRICS_MODELS = ['app_Model', ...]` to allowlist which models are queryable. References to credential-named columns (in filters, groups, order or calc) are always rejected.
238
+ - **Bound the metrics surface.** The `/metrics` endpoint queries any model in the registry by default. Set `METRICS_MODELS = ['app_Model', ...]` to allowlist which models are queryable. References to credential-named columns (in filters, groups, order or calc) are always rejected — the per-resource escape hatch does not reach `/metrics`.
239
+ - **Keep the escape hatch narrow.** Columns whose names look like credentials (`password`, `api_key`, `token`, `otp`, `secret`, …) are masked as `'*********'` on every response, on top of whatever `sensitive_fields` lists. When the heuristic catches a false positive, opt it out per surface: `edit_allow_sensitive_fields = ['token_count']` returns it verbatim on `GET /resource/<id>/` while LIST still masks it. `list_allow_sensitive_fields` does the same for LIST, where every row carries the value — reach for the detail-only form first.
239
240
  - **OAuth clients are public by default.** MCP clients (Claude.ai, ChatGPT, ...) authenticate with PKCE — list them under `MCP['OAUTH_CLIENTS']` with no `client_secret`. A client becomes confidential only with an explicit secret; `/register` never hands out a confidential secret (provision those out of band).
240
241
 
241
242
  ---
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "easyapi-django"
7
- version = "1.2.2"
7
+ version = "1.3.0"
8
8
  authors = [
9
9
  { name="Stamatios Stamou Jr", email="bushier.outsets.0c@icloud.com" },
10
10
  ]
File without changes
File without changes