Files
Open_Ultra_SAST/src/openultrasast/ruleset/obligations/python.toml
T
Marius CiepluchandClaude Fable 5.1 17e07b36c4 feat(prover): delivery B: handler-named operations, Symfony, Livewire, Remix and Tornado entries, enforcement witnesses, memoised path walk
Codex delivery under spec access-control-prover plus one fix of mine. Operation rows may now name a handler by
name pattern and framework (Jenkins form validation and list filling, Tornado request methods, Remix actions);
the mapper recognises Symfony, Livewire and Laravel controllers, Remix exports and Tornado handlers;
`obligations/enforcement.py` turns Laravel route middleware, Symfony attributes and security.yaml rules and
Tornado's authenticated decorator into router and decorator witnesses. The path walk looks each node's callers
up once per change and does not walk a tree with no recognised entry: one seatunnel change fell from 670 s to
35 s. One name Codex could not source (Tornado get_current_user) is left out and said so in the facts header.

Smoke on the 23 fix changes: 15 pushes caught (65 percent, Wilson 45 to 81), 55 of 93 locations, precision
15 of 15 flagged, no instrument failure, no change over 120 s. Delivery A caught 7.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-10-14 17:31:07 +02:00

98 lines
4.7 KiB
TOML

# Checked mage-ai__mage-ai Git HEAD and websocket fix 66440e4d3: no authenticated
# decorator or get_current_user definition found. authenticated is already an existing fact.
# Handler names follow the Tornado RequestHandler/WebSocketHandler contract in the approved spec.
# Obligated operations and dischargers for Python web code (authorization-obligations, Req 1). Closed data.
version = "1"
language = "python"
[[operation]]
id = "orm-read"
kind = "protected_read"
calls = ["filter_by", "query.filter", "objects.filter", "query.get", "objects.get", "get_or_404", "first_or_404", "find_one", "objects.all", "query.all"]
requires = ["identity_constraint", "ownership_check"]
sensitivity = "high"
[[operation]]
id = "orm-write"
kind = "protected_write"
calls = ["session.add", "session.delete", "session.merge", "query.update", "query.delete", "objects.update", "objects.create", "objects.filter", "insert_one", "update_one", "delete_one", "bulk_update"]
requires = ["identity_constraint", "ownership_check"]
sensitivity = "high"
[[operation]]
id = "sql-access"
kind = "protected_read"
calls = ["execute", "executemany", "fetchone", "fetchall"]
requires = ["identity_constraint"]
sensitivity = "high"
[[operation]]
id = "privileged-action"
kind = "privileged_action"
calls = ["set_password", "promote", "grant", "revoke", "set_role", "transfer", "payout", "remove", "rmtree", "send_file", "make_admin"]
requires = ["path_guard", "ownership_check"]
sensitivity = "high"
[[operation]]
id = "security-setting"
kind = "security_setting"
# Flask/FastAPI (CORS, set_cookie, app.run), aiohttp (session_setup, the cookie storages) and Django
# (the SECURE_* / DEBUG settings). The table was Flask-shaped while the corpus contains aiohttp and Django
# apps, so three config pairs had no modelled setting in them at all.
calls = ["CORS", "cross_origin", "set_cookie", "app.run", "config.update", "TrustedHostMiddleware", "add_middleware", "set_debug",
"session_setup", "EncryptedCookieStorage", "SimpleCookieStorage", "cookie_storage", "setup_session",
"Application", "RedisStorage", "aiohttp_jinja2.setup", "Environment",
"SECURE_SSL_REDIRECT", "SESSION_COOKIE_SECURE", "CSRF_COOKIE_SECURE", "SESSION_COOKIE_HTTPONLY", "ALLOWED_HOSTS", "DEBUG",
"set_secure_cookie", "make_secure_cookie"]
resource_arg = 0
requires = ["non_permissive_value"]
sensitivity = "medium"
[[discharger]]
id = "route-guard"
kind = "path_guard"
decorators = ["login_required", "jwt_required", "auth_required", "roles_required", "permission_required", "admin_required", "requires_auth", "authenticated", "token_required"]
calls = ["token_validator", "verify_jwt_in_request", "get_jwt_identity", "check_auth", "require_login", "authenticate", "login_required", "current_user_required"]
[[discharger]]
id = "identity-from-context"
kind = "identity_constraint"
identity_sources = ["request.user", "current_user", "g.user", "g.current_user", "get_jwt_identity(", "['sub']", "[\"sub\"]", "session['user_id']", "session[\"user_id\"]", "request.state.user", "token.user", "identity"]
constraint_params = ["user", "user_id", "owner", "owner_id", "author", "author_id", "account", "account_id", "tenant", "tenant_id", "created_by", "username"]
request_sources = ["request.get_json(", "request.json", "request.args", "request.form", "request.values", "request.data", "request.headers", "request.cookies", "request.view_args"]
[[discharger]]
id = "ownership-check"
kind = "ownership_check"
calls = ["check_owner", "is_owner", "assert_owner", "ensure_owner", "owns", "can_access", "has_permission", "authorize"]
[[discharger]]
id = "safe-setting"
kind = "non_permissive_value"
calls = ["CORS", "cross_origin", "set_cookie", "app.run", "config.update", "add_middleware",
"session_setup", "SimpleCookieStorage", "setup_session", "set_secure_cookie",
"Application", "RedisStorage"]
permissive_values = ["'*'", "\"*\"", "True", "0.0.0.0", "None", "False", "allow_all", "disable"]
[[discharger]]
# Jinja2's `Environment(autoescape=...)`, moved out of the general list because `True` is in it and `True` is
# the SAFE value here. Sharing one permissive set made the correct configuration indistinguishable from the
# dangerous one, which is the same defect the JavaScript template engines would have introduced.
id = "safe-template-escaping"
kind = "non_permissive_value"
calls = ["Environment", "select_autoescape"]
permissive_values = ["False"]
[[discharger]]
id = "validated-input"
kind = "validated_input"
calls = ["validate", "schema.load", "parse_obj", "model_validate", "clean", "is_valid", "secure_filename", "safe_join"]
[[operation]]
id = "tornado-handler"
kind = "privileged_action"
handlers = ["^(?:get|post|put|delete|patch|head|options|open)$"]
framework = "tornado"
requires = ["path_guard"]
sensitivity = "high"