Policy and context
What a load may do beyond parsing, and what a format or source knows about the load it is part of. See Security for how the policy is applied.
anycfg.Policy(allow_commands=False, allow_templates=False, allow_includes=True, sandbox=True, strict=False, confine_to=None, max_includes=1000, max_depth=64, max_bytes=64 * 1024 * 1024, command_timeout=60.0)
dataclass
What a load may do beyond parsing the documents it was given.
The default is the untrusted posture: a document may include other
files, but cannot run commands, cannot render templates, and interpolation
runs sandboxed. Policy.trusted() turns commands and templates on and the
sandbox off, for configuration you wrote yourself.
A document can never change the policy: an include mapping cannot carry any of these fields, and every nested load (an include, a command's output) runs under the policy of the load that reached it.
Subclass it to apply your own trust rules. The hooks :meth:check_path,
:meth:check_command and :meth:check_template are asked after the fields
have allowed something, so a subclass can only narrow them; returning a
falsy value refuses. trusted(), untrusted() and replace() keep
the subclass::
class LocalOnly(Policy):
def check_path(self, path):
return isinstance(path, pathapi.LocalPath)
Loader(policy=LocalOnly())
Attributes:
| Name | Type | Description |
|---|---|---|
allow_commands |
bool
|
Run command sources ( |
allow_templates |
bool
|
Render template sources ( |
allow_includes |
bool
|
Honour |
sandbox |
bool
|
Render templates and interpolate values in a sandbox that blocks Python internals. A template engine that cannot be sandboxed is refused while this is on. |
strict |
bool
|
An undefined name in a template or interpolation raises. |
confine_to |
ConfineTo
|
Roots every local file read must fall inside: a sequence
of absolute paths, one |
max_includes |
int
|
Most includes one load may resolve. |
max_depth |
int
|
Deepest nesting of includes. |
max_bytes |
int
|
Most bytes one load may read, all sources together. |
command_timeout |
'_ty.Optional[float]'
|
Seconds a command may run before it and its child
processes are killed; |
allow_commands = False
class-attribute
instance-attribute
allow_includes = True
class-attribute
instance-attribute
allow_templates = False
class-attribute
instance-attribute
command_timeout = 60.0
class-attribute
instance-attribute
confine_to = None
class-attribute
instance-attribute
max_bytes = 64 * 1024 * 1024
class-attribute
instance-attribute
max_depth = 64
class-attribute
instance-attribute
max_includes = 1000
class-attribute
instance-attribute
sandbox = True
class-attribute
instance-attribute
strict = False
class-attribute
instance-attribute
check_command(command)
Whether the command source command may run, once allow_commands
is on. command is the text a source names (cmd:// and friends) or
a script file's path. A refusal is a CommandsDisabledError.
Source code in src/anycfg/_context.py
116 117 118 119 120 | |
check_path(path)
Whether path, a pathapi Path, may be read.
Asked about every path before it is read: a top-level source, an
include, a script file, a glob pattern before it is expanded and each
match. A str source, top-level or from a document, is always a
local path; any other kind of Path comes only from the caller. A
refusal is a ConfinementError. Allows everything by default.
Source code in src/anycfg/_context.py
105 106 107 108 109 110 111 112 113 114 | |
check_template(name, engine)
Whether the template file name may render with the tplx engine
class, once allow_templates and the sandbox allow it. A refusal is a
TemplatesDisabledError.
Source code in src/anycfg/_context.py
122 123 124 125 126 | |
replace(**changes)
A copy with changes applied.
Source code in src/anycfg/_context.py
87 88 89 | |
trusted(**changes)
classmethod
Commands and templates allowed, no sandbox, with changes applied.
Source code in src/anycfg/_context.py
78 79 80 81 82 83 84 85 | |
untrusted(**changes)
classmethod
The default posture, with changes applied.
Source code in src/anycfg/_context.py
73 74 75 76 | |
using(**settings)
classmethod
This class with settings as class attributes: a mixin carrying its
settings, as in AllowedSuffixes.using(suffixes={".yaml"}).
Raises:
| Type | Description |
|---|---|
TypeError
|
For a setting the class does not declare. |
Source code in src/anycfg/_context.py
91 92 93 94 95 96 97 98 99 100 101 | |
Policy mixins
anycfg.policies
Ready-made Policy mixins, combined by inheritance or with :func:compose.
Each mixin narrows one hook and then asks the next class in line
(super()), so any number of them stack: a path must pass every
check_path, a command every check_command. Their settings are class
attributes, set on the combined class::
from anycfg import Loader, policies
class Production(policies.LocalPathsOnly, policies.AllowedSuffixes,
policies.AllowedCommands):
suffixes = {".yaml", ".json"}
commands = {"vault read -field=password secret/db"}
loader = Loader(policy=Production(allow_commands=True))
or, without a class statement::
Production = policies.compose(
policies.LocalPathsOnly, policies.AllowedSuffixes, suffixes={".yaml"}
)
A mixin only ever narrows: the policy's fields (allow_commands,
allow_templates, sandbox, confine_to) still decide first.
__all__ = ['LocalPathsOnly', 'AllowedSuffixes', 'AllowedCommands', 'AllowedEngines', 'compose']
module-attribute
AllowedCommands(allow_commands=False, allow_templates=False, allow_includes=True, sandbox=True, strict=False, confine_to=None, max_includes=1000, max_depth=64, max_bytes=64 * 1024 * 1024, command_timeout=60.0)
dataclass
Bases: Policy
Run only the commands listed in :attr:commands, compared as written.
allow_commands must still be on; this narrows it to an allowlist. A
script file is named by its path.
commands = frozenset()
class-attribute
check_command(command)
Source code in src/anycfg/policies.py
88 89 | |
AllowedEngines(allow_commands=False, allow_templates=False, allow_includes=True, sandbox=True, strict=False, confine_to=None, max_includes=1000, max_depth=64, max_bytes=64 * 1024 * 1024, command_timeout=60.0)
dataclass
Bases: Policy
Render template files only with the tplx engines named in :attr:engines.
allow_templates must still be on, and the sandbox still refuses an
engine it cannot hold.
engines = frozenset({'jinja'})
class-attribute
check_template(name, engine)
Source code in src/anycfg/policies.py
102 103 104 105 | |
AllowedSuffixes(allow_commands=False, allow_templates=False, allow_includes=True, sandbox=True, strict=False, confine_to=None, max_includes=1000, max_depth=64, max_bytes=64 * 1024 * 1024, command_timeout=60.0)
dataclass
Bases: Policy
Read only files whose name ends with one of :attr:suffixes.
Compared case-insensitively; a glob pattern is judged by its own last
component (*.yaml) and each match by its name.
suffixes = frozenset()
class-attribute
check_path(path)
Source code in src/anycfg/policies.py
72 73 74 75 | |
LocalPathsOnly(allow_commands=False, allow_templates=False, allow_includes=True, sandbox=True, strict=False, confine_to=None, max_includes=1000, max_depth=64, max_bytes=64 * 1024 * 1024, command_timeout=60.0)
dataclass
Bases: Policy
Read only local and in-memory paths: a remote pathapi Path is refused.
A document can only ever name local paths, so this vets the paths the caller passes.
check_path(path)
Source code in src/anycfg/policies.py
56 57 58 59 | |
compose(*mixins, name='ComposedPolicy', **settings)
A Policy subclass combining mixins, with settings as its class attributes.
compose(LocalPathsOnly, AllowedSuffixes, suffixes={".yaml"}). Each
setting must be one a mixin declares.
Raises:
| Type | Description |
|---|---|
TypeError
|
For a mixin that is not a |
Source code in src/anycfg/policies.py
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | |
anycfg.LoadContext(policy=Policy(), encoding='utf-8', origin=None, name=None, chain=(), trail=(), keep_text=False, _loader=None, _state=None)
dataclass
What a format or source knows about the load it is part of.
Formats receive it as context=; a format with includes resolves them
through :meth:include, which applies the policy, confinement, the budget
and cycle detection.
Attributes:
| Name | Type | Description |
|---|---|---|
policy |
Policy
|
The load's :class: |
encoding |
str
|
The codec bytes are decoded with, and the default for every include that names none of its own. |
origin |
Any
|
The path the current document was read from, which relative
includes resolve against; |
name |
'_ty.Optional[str]'
|
The current document's name, for messages. |
chain |
'_ty.Tuple[str, ...]'
|
Identities of the documents being loaded, outermost first. |
trail |
'_ty.Tuple[str, ...]'
|
Their names, for messages. |
keep_text |
bool
|
The load builds a type ( |
chain = ()
class-attribute
instance-attribute
encoding = 'utf-8'
class-attribute
instance-attribute
keep_text = False
class-attribute
instance-attribute
name = None
class-attribute
instance-attribute
origin = None
class-attribute
instance-attribute
policy = dataclasses.field(default_factory=Policy)
class-attribute
instance-attribute
trail = ()
class-attribute
instance-attribute
derive(**changes)
A copy with changes applied, sharing the load's counters.
Source code in src/anycfg/_context.py
188 189 190 | |
include(source, *more, **options)
Load source (and more, merged in order) as part of this document.
options are the document-settable include options (encoding,
default, flatten, merge, merge_options, recursive,
format); the caller filters anything else out.
Raises:
| Type | Description |
|---|---|
ConfigValueError
|
Without a loader, as for a direct format call. |
Source code in src/anycfg/_context.py
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 | |