| doc_id | reader-security |
|---|---|
| surface | security-contract |
| owner | reader-maintainers |
| last_verified | 2026-07-29 |
| summary | Trust boundaries, path safety, dependency posture, and safe defaults for Reader operations. |
reader is a local experimental workbench, not a multi-tenant service. Its security model is therefore mostly about trust boundaries, safe defaults, and preventing accidental damage or ambiguous execution rather than defending an internet-facing API.
This document describes the current security posture and the boundaries maintainers should preserve.
Assume these inputs may be malformed or mistaken:
config.yaml- resource paths
- raw input files
- CLI overrides
- generated manifests
Assume these components are trusted code and should be treated accordingly:
- built-in plugins
- external plugins discovered from Python entry points
- the canonical notebook scaffold
- repo-local Python code and dependencies
External plugins and notebooks are executable Python. They are an extension surface, not a sandbox boundary.
- YAML uses a SafeLoader-based parser that rejects duplicate mapping keys.
- Only
reader/v8is accepted. - Removed config keys are rejected explicitly.
- Protocol, experiment, paths, resources, and annotations are shape-checked before model validation.
- Plugin configs use Pydantic models with
extra = "forbid". - Unsupported public keys fail fast.
- CLI JSON modes reject unsupported combinations explicitly.
paths.<subdir>must remain relative topaths.outputs- path escapes via
..are rejected - absolute subdirectory paths are rejected
- generated runtime outputs are confined to each experiment's
outputs/ - generated notebook paths reject path and symlink escapes; notebook previews read only exact, digest-verified record revisions
- provenance catalogs, lock files, and active invocation ledgers reject symlinks, nonregular files, and multiply linked files before reading or mutation
- plugin ports are typed and validated
- dataframe contracts are checked at runtime
- built-in plugins are registered through an explicit manifest, not by implicit package scanning
- external plugins must come from the
reader_workbench.pluginsentry-point group
- schema-v6 records bind complete and effective producer-config digests plus Reader build identity
- data and file bundles are tracked in
outputs/manifests/records.json - direct and auto-discovered input files are confined to the experiment root; schema-v6 evidence records their relative path, byte size, SHA-256 digest, and selection policy
- dataframe and file-bundle artifacts carry byte sizes and SHA-256 digests that
reader verifychecks against current files - input evidence is captured before execution and rechecked before catalog commit so a mid-run source change refuses persistence
- each mutating operation holds one experiment writer lease through artifact, catalog, and terminal invocation publication
- invocation verification rejects missing, duplicate, orphaned, or malformed terminal lifecycle events
- record readers accept schema v6 only; older payloads make the catalog invalid and require regeneration from the owning experiment
reader does not currently provide:
- sandboxing for untrusted plugins
- sandboxing for notebook execution
- secret management
- network isolation for external code
- policy enforcement that only signed plugins may run
If you install or author a plugin, you are executing Python with the same trust level as the local environment.
- Treat plugin additions as code-execution changes, not just config additions.
- Keep plugin implementations thin and push domain logic into
domains/. - Prefer explicit manifests and typed ports over implicit runtime discovery.
- Do not add config shims or raw graph mutation surfaces.
- Keep generated outputs generated; do not hand-edit manifests to “fix” runtime state.
- Do not place secrets in
config.yaml. - Review external plugins before enabling them.
- Treat notebooks as executable code.
- Use
uv sync --lockedso dependency state matches the lockfile. - Prefer preflight commands before mutation:
reader validate,reader explain,reader run --dry-run.
The public repository ships code, documentation, tests, and one synthetic
experiment template. Real experiment configurations, sample maps, raw
instrument files, generated outputs, and study-specific identifiers belong in
the ignored local experiments/ workbench and must not be committed.
Treat configuration and metadata as potentially sensitive even when they do not contain measurements: construct names, treatment maps, dates, and plate layouts can disclose study intent. Prefer text fixtures for public examples. Before committing binary documents, remove author, organization, device, and path metadata as well as visible data.
The ignore rules are a guardrail, not an authorization boundary: git add -f
can bypass them. Repository tests therefore verify that tracked experiment
content remains confined to the synthetic template and that public package
metadata does not expose a personal email address.
Release review must confirm that the checked-out public tree and built distributions contain no tracked private experiment files, personal email address, or local home path. Keep GitHub secret scanning and push protection enabled, and resolve alerts before release. These checks do not prove the contents of every remote branch, tag, cached view, clone, or superseded artifact.
Historical GitHub pull-request refs still retain superseded experiment material, local paths, and a personal commit identity. Treat that material as exposed. Rewriting a branch or deleting files from the current tree does not remove pull-request refs, cached views, existing clones, or other copies. Completing server-side remediation requires GitHub Support to dereference the affected pull requests and garbage-collect the unreachable objects; any copies already obtained by third parties remain outside Reader's control.
Use this checklist for security-sensitive changes.
- Does the change widen the public config surface?
- Does it add a new path or file-binding surface?
- Does it introduce dynamic import or implicit plugin discovery?
- Does it weaken contract validation or port checks?
- Does it change where generated outputs or manifests are written?
- Does it add a new execution surface for notebooks or external code?
If the answer is yes to any of those, the change deserves an explicit security review.
Historical pull-request refs remain the concrete public-data debt. Until GitHub confirms dereferencing and garbage collection, the repository must not claim that all publicly reachable history has been purged.
The main architectural risk is semantic drift. Reader limits it by compiling protocol semantics and execution bindings into one inspectable program. Nodes that are not executed must remain explicitly domain-defined or unavailable; they must not acquire behavior through an unreported plugin branch.
The other standing boundary is external plugins. They remain a deliberate trust boundary rather than a sandboxed capability surface.