Skip to content

feat: let raven read and change its own configuration - #833

Merged
0xKT merged 22 commits into
mainfrom
feat/raven_self_config
Oct 3, 2026
Merged

0xKT merged 22 commits into
mainfrom
feat/raven_self_config

Conversation

@arelchan

@arelchan arelchan commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Raven can now read and change its own configuration through one tool, raven_config, instead of telling the user to open Settings or editing config.json with file tools. The same path lets it connect preset sub-agents and diagnose the ones that do not answer, and asks the user only for what needs them (a key, a sign-in, a paid model).

The tool and its catalog

  • raven_config actions: describe (every setting with its value, or a search), get, set (one path, or several under one confirmation), unset, add (connect presets, several at once), test, restart (reload or restart once pending changes are gathered). session.model switches only the current conversation.
  • raven/config/self_surface.py is the catalog: 14 sections, about 93 settings, each with its value kind, its writer, and when a change takes effect (next turn, at once, reload, restart, memory server, or inert). Writes go through the RPC methods the settings page already uses (SELF_CONFIG_METHODS allowlist), so the page and the agent share one validation path. tests/test_config_self_surface.py holds every entry against the schema and every next-turn claim against a real reader.
  • A refused sub-agent comes back with one next step per refusal kind (9 Raven fixes itself, 5 the user does), with stop rules: no hunting for keys, no credential stores, no restarting another app's services, stop after three commands that explain nothing.
  • subagents.add stays preset-only: every execution field comes from the preset. Presets can be named by display name, an add can pin a model the agent lists, and a model pick on an unmeasured agent reads the handshake menu first.
  • The companion skill raven-self-config describes the whole flow; its description is injected every turn, the body is read on demand.

Approval

  • Reads run unasked. Every write asks by default, on a dedicated config card (what changes, before and after, when it takes effect, a sensitive note). A grant for the session never carries a change.
  • In a turn someone is at, full access, the user's allow rule, and smart mode's reviewer let a change through; the reviewer never approves a sensitive setting. Sensitivity is declared by the owner: catalog entries (approval mode and review model, workspace confinement, sandbox, deny patterns, disabled tools, MCP servers switched on, skill blocklist and auto-install, provider endpoints, web and media proxies, remote machines, workspace path, lent keys, sub-agent descriptions) and each channel adapter's spec (who may instruct Raven, whether a channel is on, where its credentials and traffic go, encryption). The gate and the tool read a call the same way: one value decoder, one path spelling, one channel field lookup, an object value judged field by field; a path the tool does not write is refused at the gate, and a call naming one setting twice is refused. A value is secret when the schema, the channel spec or the catalog says so, a credential-named env entry, any header, or a credential inside a URL. The reviewer's instruction counts the agent's own non-security settings as ordinary work. Unattended turns (cron and other scheduled runs, no one to answer) are refused; a channel user who can answer an approval is attended.

Secrets

  • A key never travels through a tool call. The agent names the secret with an empty value and a credential card of its own (credential.request / submit / skip / pending / closed) takes it; the host writes it through the handler that owns it and the model learns only saved or skipped. Channel secrets use the same card. A call that only asks for keys goes straight to the card. Enabled on page surfaces only.
  • Tool output is scrubbed of the values Raven holds (config/held_secrets.py, including JSON-escaped spellings and URL credentials) and dotfile reads under home are redacted, once, inside ToolRegistry.execute, so the trace span, the page's diffs, the sentinel and both agent loops get the scrubbed copy. What a sub-agent says is scrubbed where it is kept: transcripts, call labels, closing text, the output a later DAG node renders, error records, announces, the ACP frame journal, probe details. A key pasted into the chat is refused.
  • Lent keys: an ACP sub-agent row can name Raven providers (lendKeys) whose key it is started with. The row stores the provider name; each start (the spawn path and the capability probe) reads the key into the variable the preset reads (presets.LENDABLE_KEYS, Pi only for now), so it never reaches the model and follows a rotated key. Verified live: Pi refused without it, answered with Raven's OpenRouter key, and its row reads connected and tested with a 398-model menu.

Page

  • Config card and credential card; the model and permission chips refresh after the agent changes them; the transcript names raven_config calls by what they did.
  • Agents page: the note on a refused connect, a failed test, or a check that found a problem offers "Hand it to Raven" on its title line; it opens a new conversation with a prompt naming the agent and the reason, not sent. A connected row whose check found a problem now shows one head line and an amber note in the body (shaped like a failure's) instead of the check's English sentence folded under the head.

Other

  • Memory LLM left unset follows the main model (EverOS restarts when the main model moves).
  • Reload and restart wait for page turns in flight too.
  • plugin points agents and chat apps at raven_config; cron names the missing message; a GNU timeout shim on hosts without one, accepting only the durations the permission gate parses (one shared definition); read-only package-manager subcommands allow.
  • Contracts: CONTRACTS_VERSION 35 (this branch's credential types plus main's FileWrite) with the surface repinned; paper-tier line ceiling 3,700 (measured 3,682) with its review note. CONTEXT.md defines the self-configuration surface and lent key.

Type

  • Fix
  • Feature
  • Docs
  • CI / tooling
  • Refactor
  • Other

Verification

  • uv run pytest -q -n auto: 27187 passed, 3 failed. The 3 fail on this machine regardless of this branch: test_rpc_files::test_a_host_without_libreoffice_says_so (LibreOffice is installed here), test_simulation_scenario::test_a_fresh_trial_refreshes_the_subagent_homes_before_it_runs and test_simulation_suite::test_a_stopped_run_is_marked_in_its_record_and_finished_with_its_evidence. schemas/subagent.schema.json is regenerated for lendKeys; test_import_cycle_budget holds (the capability probe takes the launch env from its caller rather than importing the agent layer).

  • make lint-ui (gen:check, eslint, tsc): pass; the 4 eslint warnings are in files this branch does not touch.

  • npx vitest run in ui-web: 3116 passed; check-css, check-class-namespace, check-page OK.

  • npm run lint:i18n, lint:rpc, type-check in ui-tui: pass.

  • uv run ruff check and ruff format --check on every changed Python file, and lint-imports (10 contracts kept): pass.

  • In a browser against a local gateway: credential card save, skip, replace and reload replay; the value appears in no session, trace or log. Agents page link opens a new task with the prompt filled and unsent; sent, the agent reads the self-config skill and calls raven_config first, and for Pi offers Raven's key before a sign-in.

  • Smart-mode reviewer, run against the real model: 7 ordinary config changes allowed; sed or write_file on Raven's config, reading an SSH key, and curl | sh escalated.

  • Relevant tests pass locally

  • Relevant lint / type checks pass locally

  • User-facing docs or screenshots are updated when needed

Risk

  • Behaviour changes: the agent can now change settings and connect sub-agents after the user confirms (or unasked under full access, and under smart mode for non-sensitive settings). Lending gives a sub-agent Raven's key for a provider after an explicit, sensitive confirmation; what it spends is billed to that key.

  • Memory: a memory LLM left unset now follows the main model, so long-term memory starts working after the upgrade for anyone who never pinned one, extracting on the main model and billed to the main provider's key, and EverOS restarts when the main model changes. To keep it off, set memory.backend to null; to keep it on a different model, pin one of its own.

  • Backward compatible: new config fields are optional, and both probe fingerprints include lendKeys only when set, so recorded verdicts and menus survive. A config.json written with lendKeys will not load on an older build (extra=forbid).

  • Rollback: revert this PR; nothing is migrated. To switch the feature off without a revert, add raven_config to tools.disabledTools.

  • Security impact considered

  • Backward compatibility considered

  • Rollback path is clear for risky changes

Related Issues

N/A

arelchan and others added 8 commits September 30, 2026 10:37
Add the raven_config tool over a catalog of Raven's settings
(raven/config/self_surface.py). Each setting records its type, when a
change takes effect (next turn, immediately, reload, restart) and which
writer applies it: the settings page RPCs lent by the rpc bootstrap, or
a validated atomic write of config.json. Secrets are reported as set or
not set and never written through the tool. Sub-agents expose what
their kind allows (toggle, description, model).

The permission gate reads raven_config describe/get without asking and
asks for every change, whatever the mode or allow rules say, with no
session grant. The gateway lends the tool an idle-waiting restarter so
a batched reload or restart runs after the user confirms it.

The always-on raven-self-config skill teaches when to suspect
configuration (a failing sub-agent, a missing tool, a silent channel,
a GitHub link with no plugin) and how each change applies. The plugin
tool now finds and lists without a prompt and tells the agent to offer
a connection it lacks. Sub-agent processes and product agents do not
get the tool.

benchmarks/self_config_eval runs 16 implicit, explicit, negative and
injection cases through the real RPC stack in isolated homes, with an
optional LLM judge.

Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
Found by running the self-configuration flow against a real gateway and
page.

The approval sheet drew raven_config calls as raw JSON and offered
"allow for this conversation", a grant the gate never records for them.
The tool now declares a config.change layout: the setting, its old and
new value (the default when unset), when the change applies and any
security note, with deny and allow once only. The prompt never carries a
credential, and a key like maxTokens is no longer taken for one.

A reload asked from a page turn swapped two seconds later, mid-answer:
the gateway's busy check read only its own lock and scheduler, and a
page turn holds neither. It now counts the page's turns too. The swap
also signed every open tab out, because the old generation removed
serve.json before the next one mounted the page; the page's token and
cookie now cross a swap in memory.

describe also answers a path prefix, and the restart prompt names reload
or restart as the tool will actually run it.

Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
…ation's model

From trying the self-configuration flow on the page.

Asked to switch the web search vendor and set its key, the agent made
two separate changes and sent the user to Settings for the key.
raven_config set now takes several settings at once (no path, value an
object), checks every value before writing any, and shows them on one
card. A secret is named with an empty value: the card shows a password
field for it and saves what the user types through the page's own
settings.set or model.save_key before answering, so the key never passes
through the model. The tool then reports whether it is set. A call that
carries a key's value (one pasted into the chat) is refused by the gate
before anyone is asked. Surfaces without the field fall back to Settings.

session.model switches only the current conversation, through the
existing session-scoped config.set; agents.defaults.model still moves
the default. The skill says which one a request means.

The trajectory now words raven_config and plugin rows by what the call
did (checked settings, changed settings, reloaded Raven, checked or
connected plugins) instead of the bare tool name, and no longer offers a
raven_config setting path as a file to open.

Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
…es settings

The chips read the conversation's model and permission mode only when a
conversation opens or the reader picks one, so a switch the agent made
through raven_config left the picker naming the old model until the
conversation was reopened. A successful raven_config set or unset now
reads both back, under the current view ticket so a reader who has
moved on keeps the page they are on.

The approval card also spells a model value as provider/model, the way
the old value beside it already reads, instead of the JSON it was sent
as.

Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
…e approval mode

- Secrets: a key the agent needs is typed on a credential card of its own
  (credential.request / submit / skip / pending / closed) and written through
  the settings handler that owns it; the model only learns saved or skipped.
  Channel secret fields go through the same card. Enabled on page surfaces
  only. Tool output is scrubbed of the values Raven holds (held_secrets), and
  reads of dotfile configs under home are redacted.
- Approval: raven_config writes now honour full access, the user's allow rule
  and smart mode's reviewer, in a turn someone is at. The reviewer never
  approves a setting the catalog marks sensitive, and a grant for the session
  never carries a change. A call that only asks for keys goes straight to the
  credential card. The reviewer's instruction counts the agent's own
  non-security settings as ordinary work.
- Sub-agents: subagents.add stays preset-only (every execution field comes
  from the preset). A preset can be named by its display name, an add can pin
  a model the agent lists, a model pick on an unmeasured agent takes the
  handshake menu first, and refusals carry the models the agent offers.
- Probe and remedies: the ping passes the pinned model, an empty turn reads as
  silent, and each refusal kind maps to one next step, with stop rules for
  what the agent may not do while diagnosing.
- Tools: plugin says agents and chat apps belong to raven_config; cron names
  the missing message; a GNU timeout shim on hosts without one; read-only
  package manager subcommands allow.
- Skill: raven-self-config describes itself as reading and changing Raven's
  own settings, mostly connecting agents and chat apps, with no per-agent
  hints.
- Contracts: CONTRACTS_VERSION 33 with the credential types in the ledger;
  kernel budget ceiling 3,660 with its review note.
- The self-config eval harness is no longer part of the repository.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…aven

- Lent keys: an acp sub-agent row can name Raven providers (lendKeys) whose
  key it is started with. The row holds the provider name, not the key: each
  start reads the key from Raven's config into the variable the preset reads
  it from (presets.LENDABLE_KEYS, Pi only for now), so it never passes through
  the model and follows a key Raven rotates. subagents.add takes lend_key and
  subagents.update takes lend_keys; both refuse a provider the preset does not
  read or Raven holds no key for. Both fingerprints fold the field in only
  when set, so recorded verdicts and menus survive the upgrade.
- raven_config: describe shows lends_keys and can_lend, set
  subagents.<name>.lendKeys routes through subagents.update, and a refusal
  that needs a sign-in or a key offers lending first when Raven holds one.
  The catalog marks lendKeys sensitive, so smart mode's reviewer never
  approves it. The stop rule now forbids reading or copying keys rather than
  lending them, which had led the agent to say an agent could not use
  Raven's key.
- Agents page: the failure note on a refused connect or a failed test offers
  "Hand it to Raven", which opens a new conversation with a prompt naming the
  agent and the reason, not sent. The composer verb is lent by the app so the
  domain does not import a sibling.
- CONTEXT.md defines lent key; schemas/subagent.schema.json regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Between the lead and the server's own sentence it split one explanation in
two. On the title line it is out of the reading line and the note grows by
nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…as a note

- The capability probe started an agent with the row's own env only, so an
  agent connected on a key lent by Raven answered its ping and then read
  "connected, but no session could be opened: Authentication required".
  verify_agent now takes the launch env, and probe.py passes the lent keys
  when a row borrows any (the transport stays free of the agent layer).
- A connected row whose last check found a problem drew the check's English
  sentence under the head line. The head is now one line, and the finding is
  an amber note at the top of the body, shaped like a failure's: a sign-in
  reads as one, the check's words fold under "what the check found", and the
  note offers the hand-off to Raven. The note names the press the bar shows.
- The hand-off prompts name the agent as an agent: "Pi" alone sent the model
  searching Raven's files before it read the self-config skill. The Chinese
  link reads "Hand it to Raven" without "look into it".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@arelchan
arelchan requested a review from LivXue as a code owner September 30, 2026 06:26
@arelchan
arelchan requested review from 0xKT and gloryfromca and removed request for LivXue September 30, 2026 06:27

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking: sensitive configuration changes can bypass confirmation, secrets still leak through result telemetry, and the new timeout test fails on Linux.

I reviewed the complete github/main...HEAD diff and the surrounding callers/history. I covered the repository rules and canonical terms in AGENTS.md, CLAUDE.md, CONTEXT-MAP.md, CONTEXT.md, ui-web/CONTEXT.md, and the shipped-agent contract; the configuration catalog/write path; permission and credential flows; sub-agent key lending/probes; RPC and reload lifecycle; Web UI consumers; backward compatibility; and whether tests were weakened. I tried to refute each item against its call path and kept only the three concrete failures marked inline.

Verification after uv sync --all-packages:

  • uv run pytest -q -n 0 over the 15 directly affected Python test modules: 1,493 passed, 96 skipped, 1 failed. The failure is the inline timeout -s KILL case and is also the current Ubuntu CI failure.
  • Selected affected Web UI tests: 392 passed across 6 files.
  • git diff --check github/main...HEAD: pass.

Comment thread raven/config/self_surface.py
Comment thread raven/agent/loop/turn_path.py Outdated
Comment thread tests/test_sandbox_compat_bin.py
…ding out of review

- A tool result was scrubbed only where the model's message is built, after
  its preview had been written to the log and sent to the page as
  tool.complete.result_preview. The dotfile redaction and the held-secret
  scrub now run where the result is first read, so the watch note, the log
  line, the UI event and the model message all get the same scrubbed text.
- touches_sensitive read only the top-level path, so an add carrying
  lend_key (alone or in a list) went to smart mode's reviewer and could hand
  Raven's key to a third-party agent without the sensitive confirmation. It
  now reads the add's value.
- The timeout shim answered 124 for a child it had to KILL; GNU answers 137.
  The shim now does, and its test calls it by path so a host's own timeout
  (earlier on Linux PATHs) is not what the test measures.
- A test literal is written as an escape: the repository refuses non-English
  source additions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blockers; this can merge as far as I am concerned.

Reviewed the 0abda68 delta against the previously reviewed full main diff and verified all three prior blockers: lending adds cannot reach the smart reviewer, secret-bearing tool output is scrubbed before every log/UI/model consumer, and the compatibility test now measures the generated timeout shim with GNU-compatible SIGKILL status. No tests were weakened.

Verification: uv run pytest -q -n 0 tests/test_raven_config_tool.py tests/test_agent_loop_run_emit.py tests/test_security_untrusted_context.py tests/test_sandbox_compat_bin.py -> 166 passed. Current CI has one red unit shard: all 6,673 tests in that shard passed, but the session failed its idle-time watchdog on an untouched DAG test; the other completed checks pass.

Coverage included AGENTS.md and the domain context, the revision delta, affected callers and execution ordering, relevant history, backward compatibility, test-strength changes, and the permission/security architecture.

Diff coverage was 89.31% against a 90% gate. The uncovered lines were the
branches nothing exercised: a credential card the page never received, a close
that failed too, a sink that throws (the value must not reach the log), the
three credential methods served through the dispatcher and refusing a
malformed answer, a missing or unreadable config for the held-secret scrub and
for key lending, a dotfile read whose arguments JSON cannot encode, a model
refusal whose menu cannot be read, and a model pick whose handshake fails.
Locally the gate now reads 90.79%; the ratchet and baseline checks pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blockers; this can merge as far as I am concerned.

The new revision only adds coverage for existing fallback paths. I checked the added cases against the credential broker, credential RPC registration, sub-agent capability/model fallback, held-secret cache, dotfile redaction, and lent-key readers; they preserve the intended behavior and do not weaken prior assertions.

Verification: uv run pytest -q -n 0 tests/test_rpc_credential_broker.py tests/test_rpc_subagents.py tests/test_security_untrusted_context.py -> 162 passed. Completed CI checks are green; three unit shards were still running when reviewed.

Coverage included AGENTS.md/CLAUDE.md and runtime context rules, the delta against the previously reviewed full diff, affected callers and history, backward compatibility, test strength, and architecture boundaries. No production behavior or architecture changed.

…ckend

The three tests that stub record_memories still let the record task build
and start the configured memory backend. On CI that start and stop landed
as seconds of idle on test_a_node_leaves_a_memory_record, failing the
strict idle ceiling, and on a developer machine it probed the live everos
server on 18791. Hand them the file's lifecycle fake instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blockers; this can merge as far as I am concerned.

This revision only replaces an irrelevant real EverOS backend dependency in three DAG memory-record tests with the existing lifecycle fake. The tests still exercise their intended cancellation, file-writing, and instance-propagation behavior, so this removes external latency without weakening the assertions or changing production behavior.

Verification: uv run pytest -q -n 0 tests/test_subagent_dag_runner.py -> 270 passed. Completed CI checks are green; three unit shards were still running when reviewed.

Coverage included AGENTS.md/CLAUDE.md and runtime context rules, the delta against the previously reviewed full diff, the memory backend caller and test history, backward compatibility, test strength, and architecture boundaries.

Resolves the approval sheet conflict: keep main's keyboard answers (Esc,
Cmd+Enter, Shift+Cmd+Enter) and keep a configuration change to one-time
approval, so the broader chord does nothing on that card.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blockers; this can merge as far as I am concerned.

The new revision merges current main. I reviewed the current github/main...HEAD diff against the previously reviewed feature and checked the overlapping approval-sheet files and merge history. The resolution preserves main's Escape/Cmd+Enter/Shift+Cmd+Enter behavior while keeping self-configuration approval one-time-only: its broader chord is consumed without granting anything, and its ordinary allow chord still answers. Existing tests pin both behaviors, with no weakened assertions.

Verification: npm test -- --run src/features/composer/approve.test.ts -> 49 passed; npm test -- --run scripts/gates/approval-card-css.test.mjs -> 4 passed; npm run type-check -> passed. A preliminary direct node invocation of the Vitest gate was invalid because it bypassed the Vitest runner; the same gate passed through the project test command. Completed CI checks are green; four unit shards were still running when reviewed.

Coverage included AGENTS.md/CLAUDE.md and the UI/runtime context rules, the current diff, overlapping callers and merge history, backward compatibility, test strength, and architecture boundaries.

Main and this branch each moved the contract tier from version 33: main
for FileWrite on the tool paper, this branch for the credential card on
the asking paper. Both land here as version 35 with the surface repinned,
and the papers' line ceiling goes 3,660 -> 3,700 (measured 3,682), with
main's budget note kept and this branch's written after it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blockers; this can merge as far as I am concerned.

Reviewed the new merge revision against both the previously reviewed head and current main. The three manual conflict resolutions correctly retain both branches' contract additions: contract version 35, both FileWrite and credential exports in the ledger, and the cumulative 3,700-line paper-tier ceiling (3,682 measured). I found no weakened tests or compatibility regression.

Verification: uv run pytest -q -n 0 tests/test_contracts_two_tier_ledger.py tests/test_kernel_budget.py tests/test_agent_loop_run_emit.py tests/test_shell_command_writes.py tests/test_raven_config_tool.py passed (182 tests), and git diff --check passed for both the revision delta and the full PR diff. CI has one red shard on the untouched ACP heartbeat timing test (1 failed, 6,920 passed); that case passed 10/10 locally, neither merged delta changes its ACP code or test, and the merged main commit passed all four unit shards, so I do not attribute that failure to this revision.

Coverage included AGENTS.md, CLAUDE.md and applicable CONTEXT guidance, the diff and conflict resolutions, callers and history, backward compatibility, test-strength review, and the contract-tier architecture constraints. All three threads I opened remain resolved.

Comment thread raven/config/self_surface.py
Comment thread raven/config/held_secrets.py
Comment thread raven/sandbox/compat_bin.py
Comment thread raven/permissions/gate.py
Comment thread raven/config/self_surface.py
@0xKT

0xKT commented Sep 30, 2026

Copy link
Copy Markdown
Member

Not a blocker, but it is the same seam as the sub-agent scrub thread and worth fixing in the same pass: inside add_tool_result itself, the scrub covers one of two branches.

context/builder.py:292-297:

result = scrub_held_secrets(result)
if blocks:
    content = wrap_untrusted_blocks(blocks, source=tool_name)
else:
    content = wrap_untrusted(result, source=tool_name)

When blocks is present the scrubbed result is discarded and the model message is built from blocks, which nothing scrubbed. wrap_untrusted_blocks fences text blocks but does not scrub them. The same asymmetry is upstream: turn_path.py:1394 scrubs model_text, while turn_path.py:1452 takes result_blocks raw and organ_glue.py:99 passes them through untouched when the model can carry an image in a tool role. redact_home_config_read rides on the same call, so it is bypassed there too.

Reproduced, with the no-blocks arm as the control:

ARM A (no blocks) secret present in model message: False
ARM B (blocks)    secret present in model message: True
CONTROL the scrubber works on this value: True
CONTROL arm B is the branch that drops `result`: True

I am filing this as not blocking because of reachability, and I would rather say so than overstate it. Blocks are set only for a multimodal result on a model that carries images in a tool role, and the producers are mcp/resources.py:309 (blocks=[text_block(text), *blocks], where the text block carries the whole resource text), mcp/client.py:101, browser.py:386 (a page summary beside a screenshot) and filesystem.py:219 (an image description). So it takes an image-bearing tool result whose text half contains one of Raven's own keys. That is real but it is not the jq config.json case the module was written for, which goes through exec, returns a plain string, and is scrubbed correctly.

Scrubbing the text blocks where they are fenced would cover both branches from one call.

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking: resolve the newly reported self-configuration security gaps before merging.

The head is unchanged, but the new review record invalidates my earlier clean stance. I traced all five reports and reproduced the relevant production predicates:

  • The three outbound-destination settings are not sensitive, so smart mode can approve changes that redirect credential-bearing traffic.
  • The raven-loop subagent path fences raw tool output but never applies the held-secret scrub before sending it to its model.
  • With curl *: deny and *: allow, direct curl and timeout 0.5 curl ... deny, while the installed shim accepts .5, 1e3, and +5 spellings that the gate classifies as allow.
  • A trailing space bypasses both sensitive and secret classification before the tool normalizes the path; this preserves the sensitive-setting write bypass and exposes supplied secret text in approval rendering.
  • Feishu encrypt_key is secret in the adapter schema, but channels.feishu.encryptKey is not secret to the gate and its plaintext is rendered in the approval text. The downstream channel writer does discard that supplied value and route to the credential flow, so persistence is prevented, but that happens after the exposure and does not defeat the reported control gap.

I did not reply in or resolve another reviewer's threads. Their blockers remain open for the author to address. Verification: uv run pytest -q -n 0 tests/test_config_self_surface.py tests/test_permissions_gate.py tests/test_raven_config_tool.py tests/test_security_untrusted_context.py tests/test_sandbox_compat_bin.py tests/test_shell_policy_reasons.py passed (500 tests); those passing tests do not exercise the reported bypass cases.

arelchan and others added 2 commits October 2, 2026 22:26
Resolves the i18n catalogue conflict: main's connections keys and this
branch's credential card keys land side by side, and the generated
ui-tui catalogue is regenerated from the merged file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Mark tools.web.proxy, tools.media.proxy and providers.*.apiBase
  sensitive: each sends Raven's keys and traffic to a host the call names,
  so smart mode's reviewer no longer settles them.
- Classify the path the tool writes: the gate and the tool share
  canonical_path, so a trailing space or dot no longer turns a secret into
  an ordinary setting or a sensitive setting into a plain one. Batch keys
  and a blank path take the same spelling.
- Ask the channel's own spec which fields are secret (Feishu encryptKey),
  and read an object set on a channel field by field, so its secrets are
  refused and never printed in the approval text.
- Scrub held keys and home dotfile reads in the sub-agent loop and in the
  text blocks of an image-bearing result, through one shared helper.
- The timeout shim accepts only the durations the gate's parser reads
  past, from one shared definition, and exits 125 on any other spelling.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@arelchan

arelchan commented Oct 2, 2026

Copy link
Copy Markdown
Contributor Author

On the blocks branch of add_tool_result (no thread for this one): fixed in bbbe84b. The text blocks of an image-bearing result are scrubbed at the source in turn_path (held keys plus redact_home_config_read, through the shared scrub_tool_blocks) and again in add_tool_result before they are fenced. Pinned by test_a_held_key_in_an_image_bearing_result_never_reaches_the_model and test_a_dotfile_read_with_pictures_is_redacted_in_its_text_blocks.

🤖 Addressed by Claude Code

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blockers; this can merge as far as I am concerned.

Verified the revision delta from the previously reviewed head and the full PR context. The five security blockers are addressed at their shared enforcement points: keyed-traffic destinations are sensitive, permission and execution share canonical paths, channel secrecy comes from adapter specs (including object writes), both agent loops and multimodal text blocks use the shared output scrubber, and the timeout shim accepts only the duration grammar the gate can unwrap. The earlier nonblocking image-block seam is covered by the same scrub fix. I found no weakened tests or compatibility regression.

Verification: the focused config, permission, shell-policy, agent-loop and secret-output suite passed (610 passed); i18n boundary, prompt and source-language tests passed (49 passed). The local TUI generator check could not start because this worktree lacks the prettier Node dependency, while CI's TUI check and all four unit shards passed; the aggregate coverage job was still running at review time. git diff --check passed for both the revision delta and full PR diff.

Coverage included AGENTS.md/CLAUDE.md and applicable CONTEXT guidance, the delta and full diff, the merge-only i18n conflicts, callers and history, backward compatibility, whether tests were weakened, and the permission/configuration architecture constraints.

Comment thread raven/config/self_surface.py
Comment thread raven/config/self_surface.py
Comment thread raven/config/self_surface.py
@0xKT

0xKT commented Oct 2, 2026

Copy link
Copy Markdown
Member

Not a blocker, and separate from the three threads -- one more catalog entry

Severity of this one finding, not a verdict on the pull request. The blocking findings from this pass are review threads on the changed files; GitHub renders those collapsed, as a file name with no text.
in the same family as the proxy/endpoint ones you just marked.

permissions.judgeModel names the model that decides whether a smart-mode call
is allowed, and it carries no sensitive=. Its sibling two entries up does:

permissions.mode        sensitive='full' runs every tool call without asking
permissions.judgeModel  (none)

So a raven_config set permissions.judgeModel ... is offered to the smart-mode
reviewer rather than to the user, which means the reviewer can approve its own
replacement.

Not blocking, for two reasons I checked rather than assumed: the value is a model
name, not an endpoint (providers.*.apiBase is the endpoint, and you have now
marked it), so the attacker must name a model an already-configured provider
serves; and a name that resolves to nothing makes the judge fail, which
gate.py answers by escalating rather than by allowing. It degrades the reviewer
instead of defeating it.

Mentioning it because the three threads are about the same question -- which
settings a reviewer may settle on the user's behalf -- and this is the cheapest
of the four to close.

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking: the three newly opened security-control gaps must be resolved before this revision merges.

The head is unchanged, but the new open threads invalidate my earlier clean stance. Static inspection confirms the cited source conditions: canonical_path() leaves a space when the input ends in space-plus-dot; provider secret classification does not consult ProviderConfig declarations such as extra_headers; and channel destination fields are not catalogued as sensitive even though the smart-mode gate uses that flag to keep them with the user. These are @0xKT's findings, so I am not re-grading, replying in, or resolving their threads. No tests were rerun because this answer evaluates new comments on unchanged code; the prior verification remains the latest test result for this head.

arelchan and others added 2 commits October 2, 2026 23:41
- canonical_path strips whitespace and dots together, so no order of them
  survives (`token .` read as `token `, a secret classed as ordinary);
  tested over every wrapping of up to two such characters at each end.
- is_secret_path asks the provider schema the way it asks a channel's
  spec: extraHeaders (declared secret) and Gemini's apiKeyList (on the
  provider writer's patch list) are credentials, values nested in them
  included, and held_secrets now collects string items of a list.
- A channel field that sends its traffic somewhere (a proxy or server
  address) is declared sensitive in its adapter's spec, and the gate,
  the card and describe read that declaration.
- permissions.judgeModel is sensitive: it picks the reviewer that settles
  smart-mode calls.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in the doctor warning for full mode or an off sandbox and the
uncached deliverable downloads; no conflicts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@arelchan

arelchan commented Oct 2, 2026

Copy link
Copy Markdown
Contributor Author

On permissions.judgeModel (no thread): fixed in e193368, it is now sensitive (it picks the reviewer that settles smart-mode calls), added to test_a_setting_that_redirects_keyed_traffic_stays_with_the_user.

🤖 Addressed by Claude Code

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking: the three open security findings need their author or a maintainer to settle them before this revision merges.

I independently verified that this head closes the paths they cite: canonical_path() now removes whitespace and dots as one character set; provider-level extraHeaders and Gemini apiKeyList follow the provider writer's secret classification into held-secret scrubbing; adapter-declared channel destinations and permissions.judgeModel stay out of smart-mode review. These are @0xKT's findings, so this verification does not downgrade or resolve their threads.

Named follow-up (nonblocking): endpoint-specific providers.*.endpoints[].extraHeaders remains outside is_secret_path() and held_secrets(). A supported endpoint header such as APP-Code therefore remains visible when a tool prints Raven's config; the generic home-config redactor does not catch that arbitrary header name. This requires endpoint-specific custom headers plus a tool read of the config, so at this late review round I am carrying it as a narrow follow-up rather than a blocker.

Verification: the focused self-configuration, permission, scrubbing, channel, and provider suite passed (765 passed). The full suite completed with 27052 passed, 133 skipped, 1 failed; the sole failure is the reproducible tests/test_cli_theme.py::test_bold_accent_renders_styled_not_bare on an untouched file, while every GitHub check including all four unit shards is green. git diff --check passes for both the fix delta and the full PR.

Covered this round: AGENTS.md/CLAUDE.md, CONTEXT-MAP.md and the relevant runtime terms; the full github/main...HEAD inventory and the complete fix delta; affected gate, writer, schema, scrubber, provider and channel callers; commit history and compatibility; test changes for weakening; and the self-configuration, Config-with-cargo, Permission Gate, and credential-boundary architecture.

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blockers; suggestions only, and they are marked inline.

The new commit is a conflict-free merge of current main; its doctor warning, identity guidance, and deliverable-cache changes do not alter the self-configuration fixes. All previously blocking review threads are now resolved. The already named endpoint-header follow-up is unchanged and remains nonblocking, so I have not repeated it.

Verification: the focused self-configuration, permission, secret-scrubbing, doctor, deliverables, and identity-rendering tests pass (629 passed); git diff --check passes for both the merge delta and the full PR, and GitHub reports the PR checks green.

Covered this round: the repository rules and relevant runtime context, github/main...HEAD, the complete merge delta, affected callers and merge history, backward compatibility, test changes for weakening, and the self-configuration, Permission Gate, Config-with-cargo, identity, and RPC transport boundaries.

arelchan and others added 2 commits October 3, 2026 00:02
… can be

ProviderEndpoint.extra_headers is declared secret like the provider-level
field, and the gate and the held-secret scrub read endpoint fields through
the same schema marker, so a per-endpoint APP-Code no longer reaches a model
when a tool prints the config.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oint

A systematic audit of the self-configuration surface (gate versus tool,
secret locations, sensitive settings, output paths) found the remaining
gaps were all one of four shapes; each is now closed at a single point.

- The gate and the tool read a call the same way: one value decoder,
  one channel field lookup (exact name, then snake_case, any case of the
  channel name), and the gate refuses every path the tool would refuse,
  so a value at such a path reaches neither the card nor the reviewer.
  A call naming one setting twice is refused. The prompt title is the
  tool's own account, so a bare restart reads as the restart it runs.
- Sensitivity is declared by the owner: channel adapter specs and the
  shared socket fields carry a reason (who may instruct Raven, where a
  channel's credentials go, encryption, the weixin login store), and the
  catalog marks disabledTools, MCP enabled, channel enabled, the skill
  blocklist and auto-install, and sub-agent descriptions. An object value
  is judged field by field, and the card carries the field's note.
- Secrecy is read from the schema (a walk through lists, maps, unions and
  Annotated), MCP headers are secret, credential-named env entries are,
  and a URL's userinfo or query credential is one to the gate, the card
  and the scrub alike.
- ToolRegistry.execute scrubs every text a result carries, inside the
  traced call, so the trace span, the page diff and the sentinel get the
  scrubbed copy. Sub-agent transcripts, call labels, closing text, the
  persisted output a later DAG node renders, error records, announces,
  the ACP frame journal, probe details and CLI transcript artifacts are
  scrubbed where they are kept. held_secrets also collects short declared
  values, JSON-escaped spellings and URL credentials.

Each fix has a test that fails when the fix is reverted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking: reject nonexistent channel and sub-agent configuration targets before smart review.

I reviewed the 40161c3..7982d68 delta and the full main diff, including the repository rules, the gate/registry/tool call order, affected writers and persistence callers, commit history, backward-compatibility implications, and whether the tests were weakened. The focused runs passed: 246 config/security tests and 705 gate, registry, journal, and sub-agent tests; both full-PR and revision git diff --check passed. One concrete pre-review validation gap remains, marked inline.

Comment thread raven/config/self_surface.py Outdated
…re review

unwritable_target accepted every three-segment channel path and every
member of a channel object, so a misspelt field such as
channels.telegram.tokne carried its plaintext value to the smart-mode
reviewer and the approval card before the tool refused it. Channel names
and fields, including each key of a channel object, are now checked
against the adapter declarations the writer uses; a sub-agent's settings
were already the catalog's four wildcard entries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blockers; this can merge as far as I am concerned.

Reviewed the complete 7982d68..83ad36a delta and the supporting gate, writer, channel-declaration, and catalog paths. The fix validates scalar, object, and batch channel targets with the writer's own declarations and preserves valid aliases and sub-agent settings; the added tests strengthen rather than weaken coverage. uv run pytest -q tests/test_raven_config_tool.py tests/test_config_self_surface.py passed (231 tests), and both revision and full-PR git diff --check passed.

@0xKT 0xKT left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not blockers -- four items from a full-diff pass at 83ad36a. The blocking findings from this pass are the three review threads (rules.py, held_secrets.py, redact.py).

N1 permissions.mode writes the default, not the conversation's mode (self_surface.py:699, raven_config.py _value_view)
The tool reads and writes the file default only. A web conversation that set its own mode in the picker keeps it: the user sets this chat to full, then asks Raven to go back to ask; the tool replies Set permissions.mode: ... -> "ask". It takes effect from the next turn, and the gate (gate.py:114, session_mode(...) or cfg.mode) still reads the session's full next turn. The reply is wrong in the unsafe direction. The other way round, "make this chat full" widens the default for every conversation without its own mode, channels included. Fix: give it a session scope like session.model (config.set with scope: "session"), or at least say in the reply that this conversation has its own mode X and only the default changed. Fine as a follow-up if you prefer.

N2 a batch set can leave a partial write and report only the error (raven_config.py:855-901)
The docstring says every value is checked before anything is written, but subagents.* entries are appended unchecked (873-875) and channel / sub-agent entries are not checked for a missing RPC caller. Example: {"agents.defaults.temperature": 0.5, "subagents.codex.enabled": "yes"} writes the temperature, then returns only Error: subagents.codex.enabled takes true or false. Fix: pre-check sub-agent fields and the caller in the first pass, or refuse subagents.* / channels.* in a batch to match SKILL.md ("Channels and sub-agents are changed one call each"); include the lines already applied when a later write fails.

N3 Risk section: memory now follows the main model (plugins-dist/everos-memory/raven_everos/config.py)
Summary mentions it, Risk does not. A user who never pinned the memory LLM gets long-term memory working after the upgrade, extracting on the main model and billed to the main provider's key, and EverOS restarts when the main model changes. Worth one line in Risk and the release notes (and how to keep it off).

N4 comment in gate.py:152-153 says a channel message is unattended
raven/gateway/spine.py:120 binds a responder for channel user turns on purpose ("A channel user can answer a question, so they can answer an approval"), so channel turns are attended and the code behaves as designed. Only the comment is wrong: drop "or a channel message".

Smaller items seen, no action needed in this PR: the deferred reload / restart gives up silently after 600s busy, lent_key_env reads only apiKey (not apiKeyList), opening an approval card sweeps away a docked credential card in the same conversation, the catalog's tools.exec.timeout has no upper bound (writer allows 5..3600).

Comment thread raven/permissions/rules.py
Comment thread raven/config/held_secrets.py Outdated
Comment thread raven/security/redact.py
arelchan and others added 2 commits October 3, 2026 09:51
- Package managers and docker: the read-only subcommand must come first,
  after flags known to take no value, so a valued global option
  (`npm --prefix ls install x`, `docker -H ps run x`) no longer makes an
  install read as a query.
- held_secrets keeps a header value only when the header names a
  credential; every header still counts as secret to the gate and the
  card, but `Content-Type: application/json` is no longer replaced in
  every tool result.
- The home dot-directory redaction leaves Raven's own home out: the
  default workspace and channel scratch directories live there, and
  rewriting their source broke edits. Raven's keys are still removed by
  the exact-match pass.
- Changing permissions.mode says when this conversation keeps its own
  mode, since only the default changes.
- A batch checks sub-agent values and the services it needs before
  writing anything, and a write refused partway names what already took.
- The gate's comment no longer calls a channel message unattended.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in the cleared rich style cache between theme tests; no conflicts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@arelchan

arelchan commented Oct 3, 2026

Copy link
Copy Markdown
Contributor Author

On the non-blocking items in the review summary: N1 fixed in 342735e (the reply now says when this conversation keeps its own approval mode; a session scope for permissions.mode is left as a follow-up); N2 fixed in 342735e (a batch checks sub-agent values and the services it needs before writing anything, and a write refused partway names what already took); N3 added to the Risk section of the description; N4 comment corrected in 342735e. The smaller items you listed are left as you suggested.

🤖 Addressed by Claude Code

@gloryfromca gloryfromca left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blockers; this can merge as far as I am concerned.

Reviewed the full 83ad36a..1ac64e2 delta: all five changed production files, relevant callers and history, backward compatibility, test changes, the permission/config architecture, and repository rules. I independently verified that valued global options can no longer disguise mutating package-manager commands as reads, ordinary header values are preserved while credential headers are scrubbed, and reads under Raven home no longer corrupt source while other home-dotfile configuration remains redacted. I found no new issue.

Verification: uv run pytest tests/test_permissions_gate.py tests/test_raven_config_tool.py tests/test_security_untrusted_context.py -q passed (463 tests), and both diff checks passed. The combined run with tests/test_cli_theme.py was 516 passed / 1 failed; the same theme test fails in isolation, but GitHub reports the current base as cad436c, which is the commit that introduced that identical test-only change, so it is not caused by this PR delta.

@0xKT

0xKT commented Oct 3, 2026

Copy link
Copy Markdown
Member

Not a blocker -- R1-R3 re-checked against 1ac64e2: the package-manager bypass shapes (plus npm -g install x, npm --global --prefix ls install x) now ask, ordinary header values survive while Authorization / X-Api-Key are replaced, and ~/.raven/workspace / ~/.raven/tmp reads are left alone while ~/.qwen/settings.json is still redacted.

One gap from the R3 fix, tracked as a follow-up in #842 rather than held against this PR: skipping all of Raven's home also skips ~/.raven/oauth/<provider>/..., and those sign-in tokens are not in config.json, so neither scrub pass covers them. Repro and suggested fix are in the issue. Merging this as is.

@0xKT
0xKT merged commit 9c1cdae into main Oct 3, 2026
34 of 35 checks passed
@0xKT
0xKT deleted the feat/raven_self_config branch October 3, 2026 04:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants