Secure Web is a security boundary. Most of what makes a change acceptable is about not weakening that boundary, so read the invariants below before you open a pull request.
cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --locked --workspace
cargo run -- --helpAll four must pass. CI runs them, plus cargo audit, cargo deny check, a coverage gate (at least
85 % of lines) and release-build checks.
Tests are deterministic and offline. Never hit the live Internet in a test. Use the local
fixture server and, when injection-scanner is not on PATH, the stub scanner in tests/support/
(docs/testing.md). A test that needs the real scanner must not skip silently
when it is missing. The real-scanner suite runs with:
INJECTION_SCANNER_BIN=$(command -v injection-scanner) cargo test --test scanner_real -- --ignoredThese are enforced in code, tests and review. A pull request that breaks one will not be merged, however useful it is otherwise.
- All model-facing Internet content passes through
PageProcessor→ injection-scanner → policy. There is exactly one processing path. - SSRF validation happens after DNS resolution and on every redirect hop.
- Scanner errors (crash, timeout, invalid output, oversized input) fail closed.
- Blocked payload text is never returned over MCP, written to the agent corpus or logged at the default level.
- Crawl budgets are hard limits enforced in code.
- A blocked page contributes no child links to the frontier.
- No LLM, embeddings, vector DB, message broker or browser engine in the core.
- Every new model-facing field (title, snippet, filename, URL text) is untrusted input and must be scanned or tightly normalized.
- MCP tool names and schemas, the manifest schema, decision and reason enums, and CLI exit codes
are contracts with contract tests. To change one, update
docs/contracts/in the same pull request. - Security-affecting decisions need an ADR in
docs/adr/.
Trust state lives in the type system (UntrustedDocument → ScannedDocument →
AcceptedDocument / BlockedDocument). Only an AcceptedDocument may be stored or serialized
into an MCP result. See docs/architecture.md.
- Edition 2021,
#![forbid(unsafe_code)]in every crate. - No
unwrap()orexpect()outside tests. Library crates usethiserror; the binary usesanyhow. tracingfor logs. Never log request bodies or extracted text atinfoor above.- Bounded concurrency only (
Semaphoreand bounded queues). Never spawn one task per discovered URL. - Pin every new dependency to an exact version (
=x.y.z) and keepcargo auditandcargo deny checkclean.
- Keep each one small and focused on a single change.
- Fill in the checklist in the pull request template. It mirrors the invariants above.
- If you change behavior that a contract or the security policy describes, update that document in the same pull request.
- A new attack case needs a test that asserts its expected decision, not only that nothing
crashed. Fixture-server routes and corpus files are described in
docs/testing.md.
- Ordinary bugs: open an issue.
- Vulnerabilities, such as an SSRF bypass, a leak of blocked content, a budget bypass or a
scanner fail-open: do not open a public issue. Follow
SECURITY.md.
By contributing, you agree that your contributions are licensed under the MIT License.