frappe-secure-endpoint
Generate secure, tenant-aware API endpoints with authentication and tenant isolation.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Generate secure, tenant-aware API endpoints with authentication and tenant isolation.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Use when configuring hooks.py — doc_events, scheduler_events, override_whitelisted_methods, override_doctype_class, jinja, boot_session, permission_query_conditions, has_permission, and fixtures. Prevents silent hook failures from wrong paths or missing migrate after scheduler changes. Covers full hooks.py reference beyond document lifecycle events. Keywords: hooks.py, doc_events, scheduler_events, override method, permission query conditions, boot session, fixtures, override_doctype_class, extend_doctype_class.
Use when building or consuming Frappe REST APIs — auto /api/resource CRUD, @frappe.whitelist endpoints, token vs session auth, file uploads, and response handling. Prevents unauthorized exposure from missing permission checks on whitelisted methods. Covers /api/resource, /api/method, token auth, OAuth, file upload, error mapping. Keywords: whitelist API, /api/resource, /api/method, Frappe REST, token auth, api_key api_secret, call Frappe from outside, expand, filters.
Use when reading or writing Frappe data safely — get_doc, get_all, get_list, frappe.db.get_value, parameterized frappe.db.sql, transactions, bulk ops, and performance. Prevents SQL injection and permission leaks from wrong API choice. Covers ORM reads/writes, raw SQL, transactions, bulk_update, N+1 avoidance. Keywords: frappe.db.sql, frappe.get_all, get_value, set_value, bulk update, N+1, db transaction, get_list, SQL injection, parameterized query.
Use when implementing Frappe's permission model — roles, perm levels, user permissions, share, permission_query_conditions, has_permission hooks, and in-code checks. Prevents unauthorized access from missing server-side checks or broken row-level filters. Covers role permissions, field-level perm_level, User Permissions, share, hooks, frappe.has_permission. Keywords: permissions, role, user permission, perm level, restrict rows, frappe.has_permission, field level security, permission query conditions, share, PermissionError.
Use when debugging Frappe errors, using bench console for live inspection, analyzing tracebacks, or reading Frappe log files. Prevents wasted debugging time from ignoring log context, misreading tracebacks, and not using bench console effectively. Covers bench console, frappe.logger, error log DocType, traceback analysis, common error patterns, log file locations, pdb/debugger integration, VS Code DAP, profiling, Frappe Recorder, mariadb diagnostics. Keywords: debug, bench console, traceback, error log, frappe.logger, pdb, debugging, log analysis, inspect, VS Code, DAP, profiling, recorder, mariadb, monitor, ERPNext error, how to debug, find the bug, what went wrong, stack trace, error message..
Use when receiving vague or unclear ERPNext/Frappe development requests that need interpretation. Transforms requirements like 'make invoice auto-calculate' or 'add approval workflow' into concrete technical specifications. Determines which Frappe mechanisms to use and maps to the full 61-skill catalog. Keywords: vague requirement, clarify scope, translate business need, technical spec, implementation plan, what does this mean, unclear requirement, translate to code, how to build this.
| name | frappe-secure-endpoint |
| description | Generate secure, tenant-aware API endpoints with authentication and tenant isolation. |
Create secure API endpoints with automatic authentication and tenant isolation.
@app.secure_route('/endpoint', methods=['GET'])
def handler(user):
tenant_id = get_current_tenant_id()
app.set_tenant_id(tenant_id)
data = app.tenant_db.get_all('DocType', filters={...})
return {"data": data}
@app.secure_route('/endpoint', methods=['POST'])
def create_handler(user):
data = request.json
if not data or not data.get('required_field'):
return {"error": "required_field is required"}, 400
tenant_id = get_current_tenant_id()
app.set_tenant_id(tenant_id)
try:
frappe.db.begin()
doc = app.tenant_db.insert_doc('DocType', data)
app.tenant_db.commit()
return {"success": True, "data": doc.as_dict()}, 201
except Exception as e:
app.tenant_db.rollback()
return {"error": str(e)}, 500
@app.secure_route('/endpoint', methods=['GET'])
def list_handler(user):
tenant_id = get_current_tenant_id()
app.set_tenant_id(tenant_id)
page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 20))
status = request.args.get('status')
filters = {'status': status} if status else {}
data = app.tenant_db.get_all('DocType', filters=filters, limit_page_length=limit, limit_start=(page-1)*limit)
return {"data": data, "page": page, "limit": limit}
@app.secure_route('/endpoint/<name>', methods=['PUT'])
def update_handler(user, name):
tenant_id = get_current_tenant_id()
app.set_tenant_id(tenant_id)
doc = app.tenant_db.get_doc('DocType', name)
doc.update(request.json)
doc.save()
app.tenant_db.commit()
return {"success": True, "data": doc.as_dict()}
@app.secure_route('/endpoint/<name>', methods=['DELETE'])
def delete_handler(user, name):
tenant_id = get_current_tenant_id()
app.set_tenant_id(tenant_id)
doc = app.tenant_db.get_doc('DocType', name)
doc.delete()
app.tenant_db.commit()
return {"success": True}
@app.secure_route (never @app.route)app.set_tenant_id()app.tenant_db (never frappe.db)@app.secure_routeRemember: This skill is model-invoked. Claude will use it autonomously when detecting secure endpoint development needs.
Condensed from frappe-core-permissions (Frappe_Claude_Skill_Package): Frappe-native access control aligns with secure, user-facing endpoints.
Need access control?
├── Who can CRUD/submit/etc. on DocType? → Role Permissions on DocType
├── Which concrete records visible? → User Permissions (record-level filters on Link targets)
├── Which fields readable/editable? → Perm Levels (permlevel ≥ 1; grant 0 before higher levels)
├── Field values masked [v16+]? → Field mask + grant `mask` on role rows
├── Custom deny-only logic? → has_permission hook
├── Narrow list/query results before return? → permission_query_conditions hook
└── One-off sharing? → frappe.share helpers
Checking in Python?
├── Decide allow/deny → frappe.has_permission(...) or doc.has_permission(ptype)
├── Enforce abort → frappe.has_permission(..., throw=True) or doc.check_permission(ptype)
├── System/batch bypass → ignore_permissions (comments required)
└── User-facing listings → frappe.get_list (NOT get_all)
Layers
| Layer | Purpose |
|---|---|
| Role permissions | Capability per DocType/action |
| User permissions | Allowed Link values per user |
| Perm levels | Hide/split sensitive fields |
| Hooks | Extend/deny programmatically |
| Data masking | Obfuscate sensitive fields |
permission_query_conditions vs get_list
| API | Applies user perms | Applies query hook |
|---|---|---|
frappe.get_list() | Yes | Yes |
frappe.get_all() | No | No |
Typical ptype checks include read, write, create, delete, submit, cancel, amend; select exists for Link access (v14+).
| ALWAYS | NEVER |
|---|---|
Prefer frappe.has_permission(doctype, ptype, ...) for authorization — not raw role string checks alone. | Return True from has_permission hooks to “grant”; use None to defer to core rules. |
Use frappe.get_list for responses shown to authenticated users unless you deliberately bypass hooks. | Return user-controlled SQL fragments without escaping (frappe.db.escape for hook WHERE parts). |
Return False/None appropriately from has_permission; document ignore_permissions. | Raise frappe.throw inside permission hooks when denying — return False instead. |
Grant permlevel 0 before granting higher permlevels. For hooks that build WHERE: prefix tab{Doctype} columns safely. | Assume data masking protects raw SQL or Query Reports — mask manually where needed [v16+] |
| Avoid | Prefer |
|---|---|
if "Role" in frappe.get_roles(): alone for gates | frappe.has_permission(doctype, permtype) |
frappe.get_all() for filtered user data | frappe.get_list() |
Concatenating unescaped %/f-string values into hook SQL snippets | 'owner = ' + frappe.db.escape(user) |
Administrator → Role permissions → User permissions → has_permission denials → Sharing/if_owner refinements — plan checks so cumulative rules stay predictable.