Assync Security Model¶
Product Boundary¶
Assync is an external development control plane. It must never become
a runtime, build, deployment, or operational dependency of any managed
project. The mandatory invariant: if Assync is removed completely,
every managed project must still build, test, deploy, and run without
modification. Verified structurally in this codebase:
tests/test_isolation.py::NoManagedProjectDependencyTests asserts that
src/assync/** imports nothing beyond its own package, its three
declared dependencies (typer, rich, pyyaml), and the standard
library — never a managed project's own code.
Architecture Layering¶
CLI Web API (future) GitHub Integration (future)
\ | /
v v v
Application Services
|
v
Domain + Storage + Adapters
Business logic lives in the service modules (missions/service.py,
runs/service.py, orchestrator/service.py, etc.), never directly in CLI
handlers (commands/*.py) — so a future Web UI or GitHub App can call the
same services the CLI does, without duplicating logic. No web server is
required or implemented in this MVP.
Capability Enforcement¶
See authorization-model.md for the full model. The security-relevant
invariant: effective capabilities are calculated centrally
(authorization/policy.py::effective_capabilities) as an intersection —
no adapter, role, or synthesis result can grant itself a capability.
Subprocess Safety (adapters/local_command.py)¶
- Never
shell=True— arguments are always an explicit list. - Executable allowlist, checked before every execution.
git push/merge/cleanhard-denied regardless of granted capabilities.- Explicit timeout; output capped at 100,000 characters; a
credential-pattern regex redacts
api_key=/secret_key=/password=/token=-shaped values from captured output before it is ever persisted or displayed. - Only a minimal environment allowlist (
PATH,HOME,LANG,LC_ALLplus explicit caller-approved keys) is passed to the child process — never the full process environment, so ambient secrets are not inherited by default.
Filesystem Safety (storage/filesystem.py, local_command.py)¶
Every record ID and every resolved working directory is checked to remain
under its declared root before use — traversal is rejected outright.
projects/service.py::register_project additionally rejects a new
project path that is nested inside (or contains) an already-registered
project.
Prompt-Injection Boundary¶
This MVP calls no remote LLM, so there is no live prompt-injection surface
today — but the request/response contract is designed so that repository
or agent-output content can never alter: authorization, capability grants,
role identity, allowed paths, the output schema, or human decision
authority. Those are all fixed by the orchestrator before adapter.execute()
is called, and a returned report is independently schema-validated
(runs/report_schema.py) rather than trusted verbatim — a successful
adapter exit with an invalid report is still recorded as a failed run
(runs/service.py::execute_agent_run).
No Hidden Chain-of-Thought¶
AgentRunResult and the structured report schema only ever carry
summary, recommendation, confidence, findings, evidence,
assumptions, limitations, and required_human_decisions — never a raw
reasoning transcript.
Two BLOCKED Concepts — Deliberately Distinct¶
RuntimeState.BLOCKED (an agent/mission execution state) and
EvidenceClassification.BLOCKED (a specific claim's verification could
not be completed) intentionally share the literal string "BLOCKED" —
this mirrors .assync/runtime-visibility.md's own documented
distinction. Because both are str, Enum subclasses, Python considers
RuntimeState.BLOCKED == EvidenceClassification.BLOCKED to be True by
value; this codebase never compares across the two enum types, and this
exact behavior is documented and asserted directly in
tests/test_core.py (see "Known MVP Limitations" below).
Known MVP Limitations¶
- No production credentials, autonomous production actions, or release execution exist anywhere in this codebase — by omission, not by a runtime guard that could be bypassed. There is nothing to bypass.
- The credential-redaction regex in
local_command.pyis a defense-in-depth measure, not a guarantee — callers must still avoid passing secrets into adapter input in the first place. - No sandboxing (containers, seccomp, etc.) is applied to the local command adapter beyond the allowlist/timeout/output-cap controls described above.