Syntology Docs

Security & fragility

Key names only — never values. For incidents and defect lists, read the repo files rather than duplicating secrets or long transcripts here.

Must-read repo documents

FileWhy
REVIEW_FINDINGS.mdStable finding IDs (F-01…), severity, evidence, fixes
SECURITY_INCIDENT_2026-08-21.mdClosed Neo4j/MCP origin-shield incident; GROBID origin follow-up
ARCHITECTURE.mdCredential tiers, topology
STANDARDS.mdR1–R10 (provenance, gates, jobs)
wrangler.toml commentsWorker secret names, KV bindings
Dockerfile.api commentsInjected env names; no secrets in image
public/.well-known/security.txtPublic contact
CLAUDE.mdSession entry: shared modules, never bulk-write without ledger

Those files may exist on local main before they land on origin/main.

Internal key fail-open (F-06)

main.py _verify_internal_key: if SYNTOLOGY_INTERNAL_KEY is unset, the check returns immediately (deliberately fail-open). Intended so a misconfigured Worker secret does not lock a private lab — in production it means a forgotten env var opens paid/internal App Runner routes to anyone who finds the origin URL. Candidate /healthz reports auth mode. Worker proxies inject X-Syntology-Internal-Key.

MCP budget / KV (F-17)

mcp_server.py documents CLOUDFLARE_KV_READ_TOKEN as KV read. _decrement_query_budget_sync / _increment_agents_served_sync nevertheless PUT with that token; RequestException is swallowed. If the token cannot write, free-trial MCP caps and paying-tier debits may never apply. Verify scope with a scratch-key PUT; fail closed for paying tier; count failures for free tier.

Related env names: CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_TRIAL_TOKENS_NAMESPACE_ID, CLOUDFLARE_BILLING_NAMESPACE_ID, MCP_RESOURCE_URL.

Request-path ensure_indexes (F-06)

linker.ensure_indexes() may CREATE FULLTEXT INDEX … IF NOT EXISTS on the API process’s first request using the reader credential. Aura reader cannot execute procedures; index-create permission is undocumented. A permissions change can 500 every first request. Kernel candidate: assert_indexes() reports, never creates; migrations belong on the writer tier with a ledger intent.

Secrets layout (names)

Local .env (gitignored) — historically ~25 variables including AWS, Cloudflare tokens, GitHub, Lambda, OpenReview email/password/TOTP, S2 key, Neo4j three tiers. Loaded by many load_dotenv() sites and several shell grep | cut parsers (F-03 / F-14). run_dashboard.sh may source .env.

Neo4j

NameMeaning
NEO4J_URI / hostAura instance
NEO4J_USER / NEO4J_PASSWORDreader syntology_api_reader
NEO4J_WRITE_USER / NEO4J_WRITE_PASSWORDwriter syntology_loader
NEO4J_ADMINpassword for syntology_admin (name looks like a username — D-1 / F-03; rotate if ever printed)
NEO4J_WEB_PASSWORDWorker / syntology_web (Cloudflare secret)

Cloudflare Worker secrets (wrangler secret put): STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, SESSION_SIGNING_KEY, plus web Neo4j password and graph API / internal-key names used by proxies.

App Runner injected (not baked into image): Neo4j reader URI/user/password, region, MCP KV token, SYNTOLOGY_INTERNAL_KEY. Bedrock should use task role, not static AWS_ACCESS_KEY_* in the container.

Do not commit: .env, *.pem, scp accident files, token JSON, corpus.

Origin shield history (2026-08-21/22)

From SECURITY_INCIDENT_2026-08-21.md:

  1. Traffic anomaly investigation; one request to the real Aura hostname returned 202. Root cause of that 202 stayed inconclusive; Aura security log showed no unauthorized login. Passwords for syntology_web, syntology_api_reader, syntology_loader were rotated anyway.
  2. App Runner origin for /mcp was publicly reachable, bypassing CF WAF/rate limit. Fixed with OriginShieldMiddleware in mcp_server.py + header injection in mcp-proxy.js, deployed both sides, verified.
  3. GROBID App Runner origin similarly reachable with no auth — deferred overnight, then fixed/verified 2026-08-22.

Treat raw App Runner / GROBID hostnames as sensitive. Worker is the public door; origin shield is load-bearing.

Other fragility (pointers)

IDTopic
F-01Corpus symlink / backup receipt (addressed later: PR volume retired)
F-05Ledger tests write production
F-08WriteLedger path + lock not shared across checkouts
F-09OpenReview token is a human dependency for weekly ingest
F-11Dirty trees / concurrent sessions
F-12Documentation drift
F-19Local GROBID SPOF
F-20Backup topology (versioning, ledgers)

Corrections

User-submitted graph corrections (correction.js) land in KV for humans — they are not auto-applied to Neo4j.