CtrlK
BlogDocsLog inGet started
Tessl Logo

guydemo/authguard

Authorization and access control security guidance based on Project CodeGuard — covers RBAC/ABAC/ReBAC, IDOR prevention, mass assignment, and transaction authorization

87

1.45x
Quality

82%

Does it follow best practices?

Impact

93%

1.45x

Average score across 6 eval scenarios

SecuritybySnyk

Passed

No findings from the security scan

Overview
Quality
Evals
Security
Files

task.mdevals/scenario-2/

Project Reporting API Access Control

Problem/Feature Description

A B2B analytics platform lets workspace members generate and download reports on their projects. The backend team has recently expanded from two roles (admin, member) to four (admin, manager, analyst, viewer), and permissions are now scattered inconsistently across route handlers. During a recent pen test, the testers found that several denied requests left no trace in the logs, making it impossible to detect whether someone was probing for unauthorized resources.

The team lead wants the codebase cleaned up before the next SOC 2 audit: all authorization checks should be enforced consistently, and every time a request is blocked the system must produce a structured record that the security team can use to investigate incidents. The record needs enough detail to reconstruct what happened, but must not expose sensitive personal data that would complicate GDPR compliance.

Output Specification

Produce reports_api.py containing the production-ready Flask application. You should also produce security_notes.md explaining the authorization design and logging approach.

Input Files

The following files are provided as inputs. Extract them before beginning.

=============== FILE: inputs/reports_api.py =============== from flask import Flask, jsonify, request, abort, g

app = Flask(name)

Simulated auth — reads role from header (in production this comes from JWT)

def get_current_user(): return { "user_id": request.headers.get("X-User-Id", "u-unknown"), "email": request.headers.get("X-User-Email", "user@example.com"), "role": request.headers.get("X-Role", "viewer"), "workspace_id": request.headers.get("X-Workspace-Id", "ws-1"), }

Simulated DB

_reports = { "rpt-001": {"id": "rpt-001", "workspace_id": "ws-1", "title": "Q1 Revenue", "owner_id": "u-001"}, "rpt-002": {"id": "rpt-002", "workspace_id": "ws-2", "title": "Churn Analysis", "owner_id": "u-002"}, "rpt-003": {"id": "rpt-003", "workspace_id": "ws-1", "title": "User Growth", "owner_id": "u-003"}, }

ROLE_PERMISSIONS = { "admin": ["reports:list", "reports:read", "reports:create", "reports:delete", "reports:download"], "manager": ["reports:list", "reports:read", "reports:create", "reports:download"], "analyst": ["reports:list", "reports:read", "reports:create"], "viewer": ["reports:list", "reports:read"], }

@app.route("/api/reports", methods=["GET"]) def list_reports(): user = get_current_user() if "reports:list" not in ROLE_PERMISSIONS.get(user["role"], []): abort(403) workspace_reports = [r for r in _reports.values() if r["workspace_id"] == user["workspace_id"]] return jsonify(workspace_reports)

@app.route("/api/reports/<report_id>", methods=["GET"]) def get_report(report_id): user = get_current_user() report = _reports.get(report_id) if report is None: abort(404) return jsonify(report)

@app.route("/api/reports", methods=["POST"]) def create_report(): user = get_current_user() if "reports:create" not in ROLE_PERMISSIONS.get(user["role"], []): abort(403) data = request.get_json() new_id = f"rpt-{len(_reports)+1:03d}" _reports[new_id] = {"id": new_id, "workspace_id": user["workspace_id"], "title": data.get("title"), "owner_id": user["user_id"]} return jsonify(_reports[new_id]), 201

@app.route("/api/reports/<report_id>/download", methods=["POST"]) def download_report(report_id): report = _reports.get(report_id) if report is None: abort(404) return jsonify({"url": f"https://cdn.example.com/reports/{report_id}.pdf"})

@app.route("/api/reports/<report_id>", methods=["DELETE"]) def delete_report(report_id): user = get_current_user() if user["role"] != "admin": abort(403) if report_id not in _reports: abort(404) del _reports[report_id] return "", 204

if name == "main": app.run(debug=True) =============== END FILE ===============

evals

SKILL.md

tile.json