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
| File | Why |
|---|---|
REVIEW_FINDINGS.md | Stable finding IDs (F-01…), severity, evidence, fixes |
SECURITY_INCIDENT_2026-08-21.md | Closed Neo4j/MCP origin-shield incident; GROBID origin follow-up |
ARCHITECTURE.md | Credential tiers, topology |
STANDARDS.md | R1–R10 (provenance, gates, jobs) |
wrangler.toml comments | Worker secret names, KV bindings |
Dockerfile.api comments | Injected env names; no secrets in image |
public/.well-known/security.txt | Public contact |
CLAUDE.md | Session 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
| Name | Meaning |
|---|---|
NEO4J_URI / host | Aura instance |
NEO4J_USER / NEO4J_PASSWORD | reader syntology_api_reader |
NEO4J_WRITE_USER / NEO4J_WRITE_PASSWORD | writer syntology_loader |
NEO4J_ADMIN | password for syntology_admin (name looks like a username — D-1 / F-03; rotate if ever printed) |
NEO4J_WEB_PASSWORD | Worker / 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:
- Traffic anomaly investigation; one request to the real Aura hostname
returned
202. Root cause of that202stayed inconclusive; Aura security log showed no unauthorized login. Passwords forsyntology_web,syntology_api_reader,syntology_loaderwere rotated anyway. - App Runner origin for
/mcpwas publicly reachable, bypassing CF WAF/rate limit. Fixed withOriginShieldMiddlewareinmcp_server.py+ header injection inmcp-proxy.js, deployed both sides, verified. - 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)
| ID | Topic |
|---|---|
| F-01 | Corpus symlink / backup receipt (addressed later: PR volume retired) |
| F-05 | Ledger tests write production |
| F-08 | WriteLedger path + lock not shared across checkouts |
| F-09 | OpenReview token is a human dependency for weekly ingest |
| F-11 | Dirty trees / concurrent sessions |
| F-12 | Documentation drift |
| F-19 | Local GROBID SPOF |
| F-20 | Backup topology (versioning, ledgers) |
Corrections
User-submitted graph corrections (correction.js) land in KV for humans —
they are not auto-applied to Neo4j.