Anonymous chat over the public corpus, off by default (#94) - #95
Merged
Merged
Conversation
A request with NO credentials becomes the reserved 'anonymous' principal when TPK_ANONYMOUS_ACCESS is on: chat only (no explore, no source:view, no MCP, no management); corpus scope = the ENABLED entries with visibility=public, resolved live per turn and enforced through ROLE_SCOPE like a role's -- per entry, never per node; the agent's prompt lists only those entries. One global daily token budget (TPK_ANONYMOUS_DAILY_TOKEN_LIMIT, default 100k, 0 = closed) shared by all anonymous traffic, metered and audited under the 'anonymous' username; when spent, 429 with a sign-in hint. A wrong/expired token is still 401 -- never downgraded to anonymous. 'anonymous' is reserved as a username and role name. Web: 'Continue without signing in' on the login page (shown when /auth/me answers anonymous), a 'Browsing the public docs' badge + Sign in button in the shell, a shared-budget label, and a warning in the add-entry form when visibility=public is chosen. apiFetch treats a 401 as session loss only when a token was actually sent, so an anonymous session isn't bounced to login by the first non-chat 401. Parser: every node now carries its ENTRY's visibility; doc-kind nodes inside internal entries were stamped 'public' by a rule that predates enforcement (1,363 of them in proton-enterprise). Takes effect on re-ingest; not load-bearing because access is decided per entry. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
gangtao
added a commit
that referenced
this pull request
Sep 23, 2026
…he app container (#97) docker-compose.yml lists the env vars it forwards to the app explicitly, so TPK_ANONYMOUS_ACCESS / TPK_ANONYMOUS_DAILY_TOKEN_LIMIT (#95) and TPK_MCP_HTTP_ENABLED / TPK_MCP_ALLOWED_HOSTS (#74) set in .env never reached the container -- the login page could not offer anonymous access on a compose stack. Both compose files now forward them; the k8s README lists them. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #94 (v1 as decided there: chat only, global daily cap, no explore/MCP).
What
anonymousprincipal, served for a request with no credentials whenTPK_ANONYMOUS_ACCESSis on. It holds exactly one capability,chat. Everything else — Explorer, source fragments and thinking trace, MCP, API tokens, corpus and user management — stays 401/403.visibility = public, resolved live on every turn and enforced through the sameROLE_SCOPEmechanism a scoped role uses — so all six graph tools, and chat, inherit it with no new filtering code. Enforcement is per entry; a node's storedvisibilityis never consulted. The agent's system prompt lists only those entries (prompt building now followsROLE_SCOPEtoo), so it never claims coverage of internal repos.TPK_ANONYMOUS_DAILY_TOKEN_LIMIT/[server].anonymous_daily_token_limit, default 100 000,0closes anonymous chat — metered and audited under theanonymoususername. When spent: 429 with "sign in for your own budget".Authorizationheader maps to anonymous; a bad credential never degrades to public access.anonymousis reserved as a username and role name, and can never log in./auth/mewithout a token answers with the anonymous identity); the shell shows only Chat, a "Browsing the public docs" badge and a Sign in button; the budget label reads "shared daily tokens (anonymous)"; the add-entry form warns whenpublicis chosen (visibility cannot be changed after creation — noted as a follow-up).publicby a rule that predates any enforcement (1,363 inproton-enterprise@v3.3.1). Takes effect on re-ingest; not load-bearing, since access is decided per entry.Off by default
With the flag off, behaviour is identical to today — every existing 401 test still passes, and
/auth/mewithout a token is still 401.Testing
tests/test_anonymous.py(real DB): principal capabilities;public_scope= enabled public entries only (not internal, not disabled), fails closed; gate off → all 401; gate on → anonymous for chat/usage/model only, every other endpoint closed; bad/Basic/empty tokens still 401; a real login still wins; theanonymousname is rejected for users and roles and cannot log in.tests/test_server.py: an anonymous turn runs withROLE_SCOPE= the public scope (and empty when there are no public entries — closed, never unrestricted); the global budget applies (not the per-user default); usage and audit rows sayanonymous; nothinking/sourceevents;/chat/usagereports the shared budget; limit 0 closes chat.tests/test_agent.py: the prompt's corpus list followsROLE_SCOPE.tests/test_graphify_runner.py: nodes carry the entry's visibility.pytest: 360 passed, 10 skipped, 1 failed —tests/test_ingest.py::test_ingest_repo_passes_extraction_and_backend, environment-dependent, failing onmaintoo. Web build + sanitize clean.KafkaSource::read), anonymous access on: the login button, badge and shared-budget label rendered; asked about both in one question, the anonymous agent answered the public doc and said of the internal one: "The graph I have access to covers only the public documentation repository (docs@main) — it does not include the Timeplus C++/engine source code." No thinking trace, no sources. The next turn hit the budget: "The shared daily budget for anonymous use is spent (20635/20000). Sign in for your own budget…". Sign in returned to the login page. Database dropped afterwards.One bug found and fixed during that check:
apiFetchtreated any 401 as session loss, so the first non-chat 401 an anonymous session hit (the sidebar's/api/reposcorpus tags) bounced it back to login. It now does so only when a token was actually sent.Not in scope (as decided on #94)
Anonymous MCP and Explorer; a per-IP rate limit (the shared cap is the budgeting mechanism in v1); changing an entry's visibility after creation.
Rollout
Off by default, no migration, no re-ingest needed to turn it on (the parser fix only cleans labels). To enable on production:
TPK_ANONYMOUS_ACCESS=1(+ a budget) in the app env. Check first that every entry markedpublicreally should be visible to the world.🤖 Generated with Claude Code