External security review pack
Scope, trust boundaries, the evidence the project produces with reproduction commands, the questions for an independent reviewer and the review log.
Everything an independent reviewer needs to assess the OpenSmartRoute control plane against the threat model in SECURITY.md: scope, trust boundaries, the evidence the project already produces, how to reproduce it, the questions we want answered and how a report is published. It exists so that the review named in the roadmap can be run by a third party without a briefing call.
1. Engagement#
| Subject | opensmartroute 1.0.0 (pip install opensmartroute), the osr serve image, the hosted platform at https://api.opensmartroute.ai / https://opensmartroute.ai |
| Type | White-box: full source, tests, CI and deployment code are public in this repository |
| Rules of engagement | Test against your own osr serve / python -m osr_platform.app instance or a free-plan workspace you created; no load testing of the hosted platform; no access to other workspaces' data |
| Contact | security@opensmartroute.org or GitHub private vulnerability reporting (../SECURITY.md); acknowledgement within 72 h |
| Publication | Findings fixed or accepted first; then the report is linked in section 7 and in the roadmap |
2. System under review#
caller ── HTTPS ──> platform/web (Next.js, proxies /api,/v1,/mcp) ──> platform/api (FastAPI, osr_platform)
│ API keys, sessions, plans, metering,
│ workspaces, audit, admin
└──> opensmartroute (SDK: signals,
policy, strategies, learners)
└──> targets (Azure OpenAI,
MCP tools, agents, humans)
self-hosted: caller ──> osr serve (src/opensmartroute/server.py) ──> opensmartroute ──> targets
library: your code ──> Router / RouterBuilder ──> targets
Trust boundaries, from the outside in:
- Internet -> web / API. Anonymous: landing, docs,
/api/v1/models,/rankings,/stats/public,/status,/estimate(rate limited),/signup(5 per IP per day). Everything else needs an API key (osr_live_, hashed at rest) or a browser session (osr_sess_, 30 days, SSO via PKCE with a signed state). Admin routes needX-Admin-Token. Cross-workspace reads are 404 by construction (db.usage_by_request(account, request_id)scopes every trace / event lookup). osr serve. Optional bearer /X-API-Keymiddleware (serve --token,--require-auth,OSR_SERVER_AUTH_TOKENS);OPEN_PATHSare the probes,/metrics,/whoamiand the OpenAPI UI. The Helm chart and the Docker image are documented as "front it with your gateway".- Router -> request text.
InputGuard(size, gadget and steering scrub, optional PII redaction) runs before signals;Policyruns before scoring and cannot be outweighed; the LLM judge sees a fenced, sanitised prompt and its output is parsed item by item. - Router -> catalogue. Imported MCP tool descriptions, A2A cards and
SKILL.mdfiles pass the injection lexicon and the learned gadget score; MCP manifests can be signed (HMAC-SHA256 / Ed25519). - Router -> execution.
OriginPolicy(parameter provenance),ResourceLimiter(steps, calls, depth, tokens, cost, wall-clock), breakers and budgets sit between a decision and a handler call. - Router -> state. JSON only, atomic writes, hashed keys, versioned schema, quarantine of corrupt
state, optional AES-256-GCM at rest, hash-chained audit with
verify().
3. Threat model#
SECURITY.md, "Threat model & controls" lists T1-T16 with the OWASP Top 10 / OWASP LLM Top 10 mapping and the implementing code; "Threat-model delta: 0.3 -> 0.4" lists the residual risk we already know about per threat. The reviewer is asked to confirm, refute or extend that table rather than start from a blank page.
4. Evidence the project produces#
| Evidence | Where | How to reproduce |
|---|---|---|
Static analysis on every push: ruff (incl. the S bandit rules), bandit, mypy | .github/workflows/ci.yml | python -m ruff check src tests scripts; python -m bandit -q -r src -c pyproject.toml; python -m mypy src |
CodeQL security-and-quality for Python and GitHub Actions, on push and weekly | .github/workflows/codeql.yml | Security tab of the repository |
| Unit tests for the controls (guard, gadget detector, sanitiser, provenance, limits, signed manifests, audit chain, encrypted store) | tests/test_security.py (49 tests), tests/test_enterprise_ops_stores.py | python -m pytest tests/test_security.py -q |
| Safety-routing red-team suite (paraphrase, gadget and steering attacks against safety routing) gates CI and every release | security.safety.run_safety_suite, ci.yml | osr -t examples/targets.yaml safety --learned-guard |
| Platform authorisation tests (API keys, sessions, workspaces, SSO state, device flow, admin token, trace scoping) | platform/api/tests (108 tests) | PYTHONPATH=src:platform/api python -m pytest platform/api/tests -q |
| Zero runtime dependencies in the SDK core; optional extras imported lazily | pyproject.toml | pip install opensmartroute && pip show opensmartroute (Requires: none) |
| Signed releases: SLSA build provenance and PEP 740 attestations, trusted publishing (OIDC, no long-lived PyPI token) | .github/workflows/release.yml | gh attestation verify dist/opensmartroute-*.whl --owner isathish |
| Container hardening: non-root image, read-only root filesystem, dropped capabilities, NetworkPolicy, PDB in the Helm chart | deploy/Dockerfile, deploy/helm/opensmartroute | helm template deploy/helm/opensmartroute |
| Content-free logs and traces (ids, digests and lengths only) | observability.text_digest, LoggingTelemetry | grep the test suite for text_digest; run osr serve with a FileSink and inspect |
| Hash-chained audit log with tamper detection | enterprise.FileAuditSink.verify | edit one line of an audit file and run verify() |
5. Questions for the reviewer#
Ordered by the impact a positive finding would have.
- Rerouting (T1, T2). Can a request author steer the decision to a chosen target through the text
alone - gadgets the learned detector misses, steering the scrub does not remove, or judge-prompt
escapes past the sanitiser? Success = a reproducible prompt that moves a target's propensity by more
than the ensemble's stated confidence margin on
examples/targets.yaml. - Policy bypass (T3). Does any strategy, plan slot, fallback, cascade or
exclude/candidatespath reach a target thatPolicy.checkrejected (PII to apii_allowed: falsetarget, region or tenant constraint)? The platform's/api/v1/policyoverlay is in scope. - Cross-workspace access (platform). Any read or write that crosses
account_id: traces, events, activity, savings, API keys, invites, SSO connections, marketplace listings, admin routes without the admin token, IDOR onrequest_id. - Authentication (platform). API-key hashing and comparison, session fixation / expiry, SSO PKCE
and state signature, the RFC 8628 device flow (
slow_down, code entropy, approval binding), signup rate limiting, the OpenAI-compatible/v1proxy's key handling. - Catalogue poisoning (T13, T14). An MCP tool description, A2A card or
SKILL.mdthat passes the import filter and changes routing or smuggles a parameter pastOriginPolicy. - Learner poisoning (T5). Feedback (
/feedback,Outcome) sequences that move a target's learned quality faster than the documented prior / confidence ramp allows, or state files that load and are trusted when they should be quarantined. - Resource amplification (T6, T15). Inputs or plans that escape
InputGuardlimits,ResourceLimitercaps or the knapsack meters; unbounded growth in any in-memory map or state store. - Data at rest and in logs (T4, T16). Any code path that writes request text to logs, traces, cache
keys, feedback, audit or metrics; weaknesses in
EncryptedStateStore(nonce reuse, key rotation). - Supply chain (T11). Release workflow, attestation coverage, Docker base image, the
install.sh/install.ps1bootstrap scripts served from the web app.
6. Known gaps (do not re-report)#
- The SDK does not authenticate callers;
osr serveis unauthenticated unless--require-author tokens are configured, and the Helm chart says so. - The gadget detector ships trained on synthetic gadgets; the injection lexicon is English-centric.
- A compromised LLM judge can still bias ranks among legitimate candidate ids.
- A valid-looking poisoned learner snapshot is indistinguishable from real learning; write access to the state store is the boundary.
- Audit verification is only meaningful if the log file is write-once at the OS level.
- Encryption at rest is opt-in; key management is the operator's (
load_secret).
7. Review log#
| Date | Reviewer | Scope | Report | Findings (open / fixed) |
|---|---|---|---|---|
| - | none yet | - | - | - |
A completed review adds a row here, a "Threat-model delta" row in SECURITY.md for anything
that changed, and a CHANGELOG entry for every fix. Until a row exists, the roadmap lists the independent
report as an open adoption item under v1.0, not as a shipped control: python scripts/release.py readiness
reads this table and reports the row as open while the only entry is the placeholder above.