Security¶
security
¶
Central security utilities for Linux AI NPU Assistant.
All security-sensitive operations are consolidated here so they can be reviewed, tested, and updated in one place.
Responsibilities¶
- URL validation: block external hosts when
network.allow_externalis off. - Response sanitisation: strip control characters / oversized AI output before it reaches the UI or tool dispatcher.
- Secure file I/O: atomic writes with owner-only (0o600) permissions.
- Path permission checks: warn when config/history files are world-readable.
- Rate limiting: token-bucket guard on AI backend calls.
- Tool argument validation: sanitise AI-supplied JSON args before dispatch.
- Secret masking: redact API keys in log output.
ExternalNetworkBlockedError
¶
Bases: RuntimeError
Raised when a request to an external host is attempted while
network.allow_external is False.
RateLimitExceededError
¶
Bases: RuntimeError
Raised when the backend call rate limit is exceeded.
RateLimiter
¶
Thread-safe token-bucket rate limiter for AI backend calls.
Parameters¶
calls_per_minute:
Maximum number of calls allowed per minute. 0 disables limiting.
Usage¶
::
limiter = RateLimiter(calls_per_minute=30)
limiter.check() # raises RateLimitExceededError if over limit
Source code in src/security.py
check
¶
Consume one token or raise :class:RateLimitExceededError.
Call this immediately before every AI backend request.
Source code in src/security.py
is_local_url
¶
Return True if url resolves to a loopback or RFC-1918 private address.
Only bare IP addresses and the hostname localhost/::1 are accepted
as local. Any hostname that is not a bare IP (e.g. my-server.lan) is
treated as potentially external and rejected when external traffic is off.
Source code in src/security.py
assert_local_url
¶
Raise :class:ExternalNetworkBlockedError if url is external and
external traffic is not permitted.
Parameters¶
url:
Full URL to validate.
allow_external:
If True the check is skipped entirely. Set this only when the
user has explicitly opted in via network.allow_external: true.
Source code in src/security.py
sanitize_ai_response
¶
Strip dangerous characters from text before it reaches the UI.
- Removes ANSI escape sequences.
- Removes C0/C1 control characters (keeps tab, newline, carriage-return).
- Truncates to max_chars to prevent memory exhaustion from a runaway model.
Parameters¶
text: Raw text received from the AI backend. max_chars: Maximum number of characters to return. Text beyond this is silently dropped (the UI will show the truncation naturally during streaming).
Returns¶
str Sanitised text, safe to display in the UI.
Source code in src/security.py
secure_write
¶
Write data to path atomically with restricted permissions.
The file is written to a sibling .tmp file first, then renamed so the
target is never partially written. After the rename the file's mode is set
to mode (default 0o600 — owner read/write only).
Parameters¶
path:
Destination file path.
data:
Text content to write (UTF-8 encoded).
mode:
POSIX file permission bits. Default 0o600 restricts the file to
the owning user, preventing other local users from reading sensitive
data such as conversation history or config files.
Source code in src/security.py
check_path_permissions
¶
Log a warning if path is readable by group or world.
Sensitive files such as conversation history and config files should be
readable only by the owning user (mode 0o600 or 0o400).
Parameters¶
path:
File to inspect.
label:
Human-readable label used in warning messages (e.g. "config file").
Source code in src/security.py
validate_tool_args
¶
Sanitise AI-supplied tool arguments before dispatch.
- Strips null bytes from all string values.
- Truncates oversized string values to :data:
_MAX_ARG_STRING_LEN. - Optionally validates args against a JSON-schema
propertiesmap to ensure required fields are present and types match.
Parameters¶
args:
Raw argument dict supplied by the AI (already JSON-decoded).
schema:
Optional JSON Schema dict with a "properties" key. Used only for
presence and basic type checks; full JSON Schema validation is not
performed.
Returns¶
dict Sanitised copy of args.
Raises¶
ValueError If a required field from the schema is missing. TypeError If a field's value is of the wrong primitive type.
Source code in src/security.py
mask_secret
¶
Return a masked version of value safe for logging.
Only the first two and last two characters are kept; everything in between
is replaced with ***. Values shorter than 8 characters are fully
masked.
Examples¶
mask_secret("sk-abc123xyz") 'skyz' mask_secret("short") ''
Source code in src/security.py
get_api_key_from_env
¶
Retrieve an API key from an environment variable.
The key is never read from the config file directly — it must always come from the process environment so it is not accidentally committed to version control.
Parameters¶
env_var: Name of the environment variable to read.
Returns¶
str The API key value, or an empty string if the variable is not set.