1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
// Copyright 2026 Thomas Santerre and Moderately AI Inc.
//
// SPDX-License-Identifier: MIT OR Apache-2.0
//! Centralised security checks for names and attributes.
//!
//! Previously the `DANGEROUS_NAMES` and `BLOCKED_ATTRIBUTES` constants lived in
//! `security::names` but every callsite ran its own `.contains(...)` check and
//! built its own error message. Consolidate the checks here so every guard
//! path returns a uniform `InterpreterError::Security` shape and the security
//! policy is auditable from one file.
use crate::;
/// Identifies *where* a dangerous-name check is being applied, so the error
/// surface can distinguish "you tried to read a dangerous name" from
/// "you tried to redefine one via `def` / assignment".
///
/// The message emitted by [`validate_name`] branches on this so callers
/// don't each hand-roll their own `format!(...)` with their own wording —
/// this keeps messages consistent when audit tools grep for them.
///
/// State-restore paths (`serialize::import_state`) do not use this enum:
/// they raise `InterpreterError::Security` directly (not wrapped in
/// `EvalError`), so they use [`is_name_allowed`] as the raw predicate and
/// build their own message.
/// Fail with a security error if `name` appears in [`DANGEROUS_NAMES`].
///
/// The `ctx` parameter controls the message wording so the caller surface
/// mirrors the interpreter's error taxonomy without each call site building
/// its own `format!(...)`.
/// Pure query: is `name` safe to expose (e.g. as a tool)?
///
/// Non-fatal inverse of [`validate_name`]; used by the tool-registration
/// assert which cannot construct an `EvalError`.
/// Fail with a security error if `attr_name` appears in
/// [`BLOCKED_ATTRIBUTES`].
///
/// Called from every `obj.attr` evaluation path so the policy is single-
/// sourced. Previously this also blocked any attribute beginning with a
/// single underscore (`_private`) as a defence-in-depth measure, but in
/// Python `_attr` is a NAMING CONVENTION (a hint that the attribute is
/// internal), not a security boundary — `obj._field` access is allowed
/// freely in CPython and is idiomatic for any class with a property
/// backed by `_field`. Forbidding it broke every customer class that
/// followed the convention. The genuinely dangerous attributes
/// (`__class__`, `__globals__`, `__code__`, `__bases__`, `__mro__`, …)
/// that can be used to walk to interpreter internals are enumerated
/// explicitly in `BLOCKED_ATTRIBUTES`.