Repository navigation
chore(vehicle): security consolidation train — #17113, #16444, #17088, #17095 in one CI run (#17048) - #17134
Conversation
…#16335) The toggle button ("Settings"/"Hide settings"), the settings section heading, and its aria-label were hardcoded English. Add transcriber.layout.{settingsButton,hideSettingsButton,settingsHeading} with real translations in all 11 locales. The other transcriber views (ProjectsView, ProjectDetailView, TranscriptView) carry no strings of this hardcoded-settings-label class, so they are untouched. The section's isAdmin && settingsOpen gate (this issue's second AC) is deferred: isAdmin is only introduced by #16259, which is still open, so adding it here now would duplicate that PR's plumbing and conflict on merge.
…entialStore (#16428) Owner decision via #13632: connector credentials come from one store, ConnectorCredentialStore. Full CRUD bridge on /api/secrets when a request names connector_id+auth_type+credentials, transparently alongside the existing legacy-file path -- create/get/rotate/revoke, no second copy ever written to the page's own store. Proves both auth shapes AC3 names: BearerAuth (single-field) and OAuthRefreshAuth (four-field bundle). See changelog for full detail.
The model_validator's `not self.credentials` treated credentials={}
(a caller-supplied dict that just doesn't satisfy auth_type's schema
yet) the same as credentials never being sent at all, raising the
wrong error before validate_config_against_schema ever got to name
the actually-missing field. Checks `is None` instead -- presence is
this validator's job; completeness is validate_config_against_schema's,
inside _create_connector_bridged_secret.
Caught by the pre-push hook actually running the new tests.
…dged secret (#16428) _get_connector_bridged_secret is shared by both GET's dual-read and PUT's rotate response, but the two endpoints disagree on whether "value" belongs in the shape at all: GET's own contract puts a value key on every result, real for a legacy secret or None for a bridged one; PUT's response has never carried a value key for any secret kind (SecretModel, the legacy shape, has no such field). The shared helper now returns neither key, and _get_secret_dual_read (GET's own caller) adds value=None itself for a bridged hit. Caught by the pre-push hook running the new tests -- the previous commit's fix for the schema-validator bug uncovered this one.
Auto-regenerated (py3.14) to match the backend and/or SLM backend schema so the required verify-generated-types gate(s) pass. Triggered by auto-fix-generated-types.yml.
…6428) Review on #16444: _get_connector_bridged_secret read straight through SecretsService.get_secret with no per-owner check, so another owner's metadata was readable via GET/PUT/DELETE on /api/secrets/{id} even though the same secret is already owner-scoped everywhere ConnectorCredentialStore is reached directly (load/rotate/revoke, via its _require_owner). Now calls that same check rather than a second copy of it, so the boundary can't drift between the two paths. The two existing rotate/revoke tests had bridged-secret mocks with no created_by at all; _require_owner treats a missing owner as a denial, not a skip, so both needed created_by added to keep asserting the same-owner success case. Added a dedicated test class proving the owner check directly: same owner succeeds, a different owner is refused. secrets.py's grandfathered line-count ceiling moves 1157 -> 1158 for this one added line; the file-size ratchet is per-file and this PR already carries a bump for the same feature. Not touched here, per review: #16579 (SecretsService.get_secret's underlying SELECT omits created_by entirely, so this same check currently denies even the real owner by default) is a pre-existing main bug, filed separately, out of this PR's scope.
…et (#16428) 37's delta review on eec2a5d: the owner-check fix's one net new line pushed autobot-backend/api/secrets.py from 1157 to 1158, raising both ratchet copies for it. repo_tests/python_file_size_ratchet_test.py:17 forbids that -- a coordinated raise of both copies would otherwise pass CI unnoticed (#16393), so it's enforced in review instead. Folded a 3-line comment in _get_connector_bridged_secret into 2 lines, no content lost. File is back at 1157; both ratchet entries hold their prior value rather than following it up.
…6709) _share_single_fact (knowledge/facts.py) hand-rolled a second sharing index, user:shared_facts:{user_id}, via a direct hset that set_owner and check_access never read, and it never set visibility=SHARED. Verified consequence (not a disclosure -- see below): a fact "shared" this way was refused to its own recipient by every check_access-gated read, since check_access grants a shared fact only when visibility is SHARED and the caller is in shared_with. Fixed by routing _share_single_fact/get_shared_facts through the already- correct KnowledgeOwnership.share_fact()/get_shared_facts() (knowledge/ ownership.py) -- the same canonical user:kb:shared:{user_id} index set_owner's own indexes are built from and check_access agrees with. Neither ownership_index.py's index_ownership nor facts.py's _project_fact_to_redis were touched (37's #16693/#16705 territory). Tested in both directions (facts_shared_facts_16709_test.py, real KnowledgeOwnership.check_access, not mocked): share_facts flips visibility to SHARED and check_access grants the recipient; get_shared_facts lists exactly what check_access would grant, nothing a stranger couldn't see. Migration: scripts/migrate_shared_facts_index.py carries over every existing user:shared_facts:* entry to user:kb:shared:*, promoting visibility the same way share_fact() would for a fresh share, then retires the legacy key. Idempotent (a second run finds nothing left to migrate); --dry-run reports without writing. Tested for no data loss and idempotency. Disclosure question (unverified in the issue): confirmed NOT a disclosure. facts.py's own get_shared_facts (the only reader of the buggy user:shared_facts:* index) has zero callers anywhere in the tree -- api/chat_sessions.py:1816 is the only caller of share_facts (the write side). The two live "shared facts" read routes (api/knowledge_ownership.py get_my_facts:313, get_shared_facts:369) both require Depends(check_admin_permission) and already call the correct KnowledgeOwnership.get_shared_facts, which the buggy write path never populated -- so a fact shared through the old bypass was neither discoverable through those routes nor grantable through any check_access gate. Grepped the whole tree for any other shared_with membership check outside knowledge/ownership.py and knowledge/facts.py: none found. File-size ceiling: facts.py net shrank with this fix (1799 -> 1795, one-line comprehension in get_shared_facts); lowered to match. Will recompute fresh against main's then-current value when rebasing onto #16705, per the ratchet's own "against merged main" invariant. NOT pushed yet: 37's #16705 (branch issue-16662-kb-helpers-followup) is mid-flight on the same file's _project_fact_to_redis / ownership_index.py's index_ownership; holding until it lands per coordinator direction, then rebasing before push.
…not a tainted parameter (#16444) CodeQL failed this PR with 3 high py/clear-text-logging-sensitive-data alerts. Two of them are pre-existing on main, unchanged by this branch: services/json_secrets_read.py:85 is identical there, and api/secrets.py :553 is main's :550 shifted by this PR's own additions. main currently carries 31 open alerts of that rule. Neither is this PR's to answer. The third is genuinely new and belongs to this branch even though it does not edit the file it lands in: adding `_mirror_llm_provider_key(request.name, request.value, _user)` created a new interprocedural path into provider_key_vault.py:239, where the failure branch logged `name`. main has no alert in that file at all. It is a false positive -- `name` reaches that log only by passing the VAULT_RESOLVED_CREDENTIAL_NAMES membership guard, so it is a label like OPENAI_API_KEY and never a credential -- but api/secrets.py unpacks `name` and `value` from one request model and the analysis taints every attribute of it alike. A comment saying "this is fine" neither breaks a taint path nor survives a refactor, so the log re-reads the label out of the frozenset and the property holds by construction. Tests pin the property rather than the mechanism: a failed mirror still logs the key name (an operator needs to know which key failed) and never the value, and an unrecognised name never reaches the log because the guard returns first. A registry-non-empty check goes first so none of them can pass vacuously. Refs #16444, #16428
Three of the 32 routers in `KNOWN_UNGATED` are reachable anonymously today. `app_factory.py` mounts config-registered routers without `dependencies=`, and `AuthenticationMiddleware` is never installed as middleware (#16368), so nothing else was standing behind them. Each now carries `APIRouter(dependencies=[Depends(get_current_user)])`, the convention api/skills.py established under #16368: the gate is router-level so it covers a route added later, rather than per-route where the next route is unprotected by default. The per-router judgement, since "a gate" is not one decision: captcha — authenticated user, NOT admin. Resolving or skipping a CAPTCHA acts on a workflow the caller is operating; the operator who answers one is not necessarily an administrator, and requiring admin would gate the routine case on the rare role. diagnostics — authenticated user. Both routes analyse a failure and change nothing; the POST is a POST because it carries an error payload, not because it mutates. metrics — authenticated user, plus check_admin_permission per route on `/system/monitoring/start` and `/system/monitoring/stop`. Those two switch a system-wide collector on and off for everyone, which is administrative rather than a read. The other nine routes are reads and take the router-level gate only. `KNOWN_UNGATED` drops from 31 to 28 in the same commit. It has to: the list only shrinks, and `test_the_known_ungated_list_has_not_gone_stale` fails on an entry that has since acquired a gate — so leaving them listed would be red either way. This is a slice of #16375, not its close. Twenty-eight routers remain, and each needs the same per-router reading rather than a blanket gate; several in the "mutating, not documented public" tier carry subprocess and LLM call sites that need an author's judgement about which permission is right, not mine. Refs #16375
…tegrity (#13034) No model weights downloaded by AutoBot were pinned to a revision or verified for integrity -- every from_pretrained(name) call resolved against the mutable upstream default branch, and 18 `# nosec B615` suppressions asserted "revision pinning managed operationally" with no registry, lockfile, or bump procedure actually implementing that. New autobot_shared/pinned_model_registry.py: repo_id -> (exact commit SHA, expected weight-file sha256). get_pinned_revision() raises KeyError for an unregistered model, so a new call site cannot silently load unpinned by omission. verify_cached_model() locates the downloaded file in the local HuggingFace cache after from_pretrained() and fails closed (ModelIntegrityError) on a digest mismatch, or if none of the registered files were found in the cache at all -- "nothing to verify" is a failure, not a silent pass. Every value in the registry was obtained against the live HuggingFace API (exact commit sha via the models API, weight-file sha256 via the raw git-lfs pointer -- a few hundred bytes, not the real multi-GB file), never guessed or reconstructed -- see the module docstring and docs/developer/MODEL_REVISION_PINNING.md for the exact procedure. A fabricated hash here would be worse than no pin: it would either never verify (permanently fail closed) or, if wrong in a way that still let something through, provide false assurance. Wired into the 5 fixed-model call sites this covers: ai_hardware_accelerator.py (CLIP, Wav2Vec2), code_embedding_generator.py (CodeBERT), multimodal_processor/processors/vision.py (CLIP, BLIP-2), multimodal_processor/processors/voice.py (Whisper, Wav2Vec2) -- 15 of the 18 nosec B615 suppressions removed, one per now-pinned from_pretrained call. Not closing on this PR: 3 suppressions remain in llm_shared/optimization/layer_inference.py and model_inspector.py, which load an arbitrary, caller-supplied model_name at runtime (traced to request.model_name in llm_shared/optimization/integration.py, and to test fixtures using models like mistralai/Mixtral-8x7B-Instruct-v0.1, meta-llama/Llama-3-8B). A static registry entry doesn't fit an open-ended, request-driven model set the way it fits the five fixed call sites this PR covers -- that needs its own design (e.g. trust-on-first-use pinning, or requiring the caller to supply a revision), documented as remaining scope rather than solved here. Tests: unit tests for the registry itself (real HF-cache-shaped directories, not mocked path resolution) plus per-call-site tests proving revision= is actually threaded through and verify_cached_model is actually called -- transformers/librosa aren't installed in this dev environment, so three of the four call-site test files inject a fake transformers module into sys.modules (the same technique llc/tests/test_replay.py already uses for llm_shared.credential_redaction) rather than skip coverage. 57 passed across the new and updated test files. ai_hardware_accelerator.py is ratchet-frozen; reflowed pre-existing comments and removed several genuinely redundant "explains what the next line does" comments to land the file 1 line under its previous 1042-line ceiling (lowered to 1041 in both registries, matching the ratchet's own rule: a file that lands below its ceiling must have the ceiling lowered to match, not left stale).
#16375) `autofix-types` failed with: [dump-openapi] FAILED to build app: name 'get_current_user' is not defined In `captcha.py` the import landed BELOW the `router = APIRouter(..., dependencies=[Depends(get_current_user)])` line, so the name was undefined when the module body executed. `metrics.py` and `diagnostics.py` were fine — their router construction sits further down the file. The edit script that introduced this asserted its anchor was unique and never asserted ORDER, which is exactly the gap: both lines were present, in the wrong sequence, and a uniqueness check cannot see that. The verification now compares the two line numbers rather than checking both strings exist. Worth noting what did NOT catch it: the module still imported cleanly in isolation (`ast.parse` passes, and the router registry logged "✅ Optional router loaded: captcha" is absent precisely because it failed), and `code-quality` stayed green. Only building the real app surfaced it — a module-level NameError behind a router that the loader treats as optional.
…eline (#13034) De-duplicated the docstring's example curl commands in favor of a pointer to docs/developer/MODEL_REVISION_PINNING.md's Bump procedure section (the SSOT scanner flags any literal https://huggingface.co/... URL outside docs/, and this text was a verbatim copy of the doc anyway). Reformatted the 3 new test files black flagged. Audited and labelled the 12 new Hex High Entropy String findings in pinned_model_registry.py/_test.py (real commit SHAs and weight sha256 digests, not secrets) into .secrets.baseline, scoped to only the two files touched per docs/developer/RATCHET_BASELINES.md's scan/audit/strip procedure.
…ot a raw id (#16428) CodeQL failed this PR with 2 high py/clear-text-logging-sensitive-data alerts, both confirmed pre-existing on main (open alerts #1049, #1046) and unrelated to this PR's own diff -- api/secrets.py's audit_log() and json_secrets_read.py's exception-path log both log secret_id, a DB row id, not a credential, but CodeQL's taint tracker flags it on the parameter name alone. main already carried 5 open alerts of this rule in these two files; a prior commit on this branch (767d29c) diagnosed this precisely for a third, PR-introduced alert but correctly left these two as "not this PR's to answer" since they predate it. They still block this PR's CI, so fixed here rather than leaving it stuck on a main-owned gap. audit_log()'s existing `secret_id[:8] + "..."` truncation still carried real id bytes -- not a sanitizer CodeQL recognizes. Both sites now reuse security.secrets_store_reader.secret_log_ref(), the canonical helper already used at 3 other call sites in the same file for exactly this (a stable, non-reversible SHA-256 correlator) -- no new hashing implementation, and net negative line growth on secrets.py rather than raising its ratchet ceiling (lowered 1157->1156 instead, matching the existing "ratchet only turns down" discipline already applied once to this same file in this branch's own history, 752af96). Tests pin the property (raw secret_id absent from the log, a stable correlator present so operators can still tell two log lines share one id), not the mechanism, matching the established pattern from 767d29c's provider_key_vault_test.py. Refs #16444, #16428
…3034) Secret Detection (whole tree) failed with 2 new findings unrelated to this PR's own diff: autobot-frontend/src/i18n/locales/en.json:9086 and ur.json:9086, both the "authApiKey": "API key" (and its Urdu translation) label string -- CodeQL^Wdetect-secrets' Secret Keyword plugin matches the key name "authApiKey" combined with a quoted value, same false-positive class as every other already-audited entry in these two locale files. Confirmed pre-existing and unrelated to this branch: both files are byte-identical to origin/main, and this PR touches neither. Reproduces only on a full-tree scan, not a single-file one -- a detect-secrets quirk, not investigated further since the finding itself is unambiguous by inspection. Refs #13034
…ry fails (#16375) KNOWN_UNGATED said "the list may only SHRINK" in a comment. The equality tests stop an ungated router that is not listed, but not one added to the list alongside it. Two assertions now back the comment: - every entry must be in _EVER_UNGATED, the 36 modules of #16370's first run, so a new module cannot be parked in the list; - len(KNOWN_UNGATED) must equal _MAX_KNOWN_UNGATED (27), so growth fails and each removal must lower the ceiling as a deliberate edit. That also catches an entry removed once and later put back, which the superset cannot.
#16950) BaseAgent._handle_communication_request built its AgentRequest from the payload and never read the header, so a peer's request was indistinguishable from the agent's own -- the point where authorization needs the originator is where it was dropped. And MessageHeader.sender is restamped on every send, correctly, so once one relay happened the originator was unrecoverable. - MessageHeader gains originator (set once, never overwritten by a relay) and chain (every hop, originator first). - protocols/message_origin.py: stamp() records each hop; acting_for() and a context variable make relays structural -- a message sent while handling a request continues that request's chain, even through helpers that never saw the inbound message. origin_of() reads an inbound header, treating a pre-field header's sender as its originator, never as nobody. - AgentRequest gains originator and chain; the handler fills them and runs process_request inside acting_for. Nothing authenticates these fields: until #16962, the bus's trust boundary is Redis write access (#16946, owner decision 4). The if-main demo moves to protocols/agent_communication_demo.py to pay for the lines in a file at its size ceiling (805 -> 750).
…so it cannot do what the parent is held from (#16950) _handle_delegate_tool passed the child only parent_agent_id (for a log line) and auth_role; the child was built from its own profile alone, so the parent's approval gates and work item were dropped. A parent held from write_file by its work item's "writing files" gate could delegate the write, and the child ran it unapproved. security/authority.py is the common form ruled on #16950 (F1): the four authority surfaces side by side, each with its own meet -- restrictions (approval gates, forbidden tools) by union, grants (RBAC permissions, A2A capabilities) by intersection, and a surface a hop does not use is top, never bottom. chat_workflow/run_authority.py reads a run's authority and what a child inherits from it. - internal engine: the child ctx carries the parent's work item, the union of both runs' gates, and the parent's authority, which the forbidden-work seam now unions into the child's boundary; - claude_code engine: it runs its own tool loop and cannot ask for approval, so the parent's gated tools and boundary are refused there (fail closed). Its only other reach, AutoBot's MCP server, exposes reads. Removes #16958's strict-xfail marker in the same commit as the fix.
…an, re-anchor THREAT_MODEL, collect the validation-handler tests (#17134) Three of #17134's red checks, none of which was a production defect. shard 6, the five 403s: #17052 gated approve-command behind require_interactive_human, and the shared backend test stub's principal is auth_method="stub", which is deliberately not an interactive human. The five legacy tests in api/security_api_test.py and security/security_integration_test.py are byte-identical to main and never supplied one. They now override get_current_user with a session-authenticated principal, the same way this PR's own test_agent_terminal_ownership_17052.py already does. The stub itself is NOT widened: that would satisfy the guard by default in every backend test and the guard would only ever be exercised by a test that opted out -- fail-open. shard 6, the two anchor mismatches: THREAT_MODEL.md is unchanged; its targets moved under it. _require_owner is at credential_store.py:626, not :604, and decode_token_async at services/auth.py:123, not :121. Both re-measured. shard 4, collection coverage: the three validation-handler tests added by #16444/#16428 sit in trees that are in no pytest testpaths entry and in no CI invocation, so they were counted as excused rather than run -- the runtime half of a security fix executing nowhere. Moved into repo_tests/ and pointed at their servers through repo_root(), the pattern infra_script_imports_resolve_test.py already documents for exactly this case. The recorded ceilings stay at 51/14; raising them is forbidden by the guard's own comment, and nothing here needed it. Refs #17134
…lking the import graph (#17134) defusedxml alone was not enough: the next run failed on jsonschema, reached via middleware/__init__ -> plugin_sdk/__init__ -> loader.py, an eager package chain entirely separate from the connectors one. Appending one package per red run is a 6-minute CI cycle per discovery with no bound on how many remain. So the graph was walked statically from api/secrets.py instead: module-level imports only (an import inside a function does not run on import), following relative imports, and importing every ancestor package __init__ the way Python does. The first two passes of that walk were wrong in instructive ways -- one followed imports nested in functions and reported 25 false gaps, the next skipped relative imports and missed jsonschema, the exact failure it was meant to find. With both fixed it reproduces jsonschema and finds prometheus_client (via auth_middleware -> security_layer) plus starlette, which pip already installs with fastapi. That is a bounded answer rather than another guess, but the honest scope is 'no further UNGUARDED module-level gap on this path', not 'this job is now correct in general'. #17138 remains the fix that makes the list unnecessary. Refs #17134, #17138
…t on api.secrets (#17134) With the import chain finally resolving, the #17099 test got far enough to fail on its own mock: AttributeError, api.secrets has no attribute 'get_coordinator'. It does not. _create_system_vault_secret imports it inside the function body (api/secrets.py:681), so the name never exists on api.secrets at module level; the deferred import resolves it from api.envelope_secrets at call time, where get_coordinator is defined (api/envelope_secrets.py:49). Patching that module is what the mock has to target. This test has never passed in CI -- each earlier red stopped at the import, so its body ran for the first time on the previous run. Verified locally only as far as the patch target existing: the test skips without the migration_gate marker's Postgres, so CI is the proof point for it passing. Refs #17134
mrveiss
left a comment
There was a problem hiding this comment.
Reviewed at 2afd939, --detach, challenging the judgement calls specifically (not just reading green CI). Verdict: APPROVE.
1. base_agent.py / agent_communication.py merge resolutions — correct, not just plausible.
execute_with_tracking(agent_request)(replacing the bareprocess_requestcall underacting_for(origin)): read_tracked_processdirectly — it callsself.process_request(request)internally (base_agent.py:263), so this is a strict superset (adds analytics + scope/work-claim gating around the same call), never a loss. And it's the safer default now that #16986 makes peer delivery real: an incoming peer request now gets the same scope/work-claim protection every other caller gets, rather than bypassing it.run_or_scheduleimport +__main__block restoration: confirmed exactly one import, one__main__block, one call site — all insideif __name__ == "__main__":, which never executes on import. Nothing on the train could have "depended on" its absence; it's dead weight either way for every real code path.
2. security_api_test.py / security_integration_test.py — direction confirmed correct.
Traced is_interactive_human() directly (autobot_shared/auth/interactive_principal.py): _INTERACTIVE_AUTH_METHODS = {"jwt", "jwt_websocket", "session"} does NOT include "stub", and _TOKEN_AUTH_METHODS = {"jwt", "jwt_websocket"} excludes "session" from the login_token requirement, so {"username": "root", "role": "admin", "auth_method": "session"} genuinely passes the real gate. Widening the shared stub would have been a fail-open shortcut for every other backend test using it; the per-test override is the right call.
3. test_secrets_coordinator.py patch-target fix — verified directly. api/secrets.py has zero module-level get_coordinator (only a function-local from api.envelope_secrets import get_coordinator inside _create_system_vault_secret, at line 681). patch("api.secrets.get_coordinator", ...) would raise AttributeError; patching api.envelope_secrets.get_coordinator is exactly right for a deferred import resolved at call time.
4. migration-gate.yml's 3 new packages — traced both full import chains end to end, independently. defusedxml: direct (nextcloud.py:32). jsonschema: api/secrets.py → middleware.proxy_utils → triggers middleware/manager.py → autobot_shared.plugin_sdk.registry → triggers plugin_sdk/__init__.py (package init always runs first) → .loader → import jsonschema. prometheus_client: api/secrets.py → auth_middleware → security_layer → autobot_shared.monitoring.metrics.audit → import prometheus_client. Both chains are real; the comment's wording is a bit loose (skips the autobot_shared vs local plugin_sdk distinction) but the substance checks out.
5. Ratchet re-pins — spot-checked the 3 named entries exactly: manager.py=4067, tool_handler.py=3721, agent_communication.py=711, all matching wc -l and both baseline mirrors precisely. Lighter touch on the 3 relocated tests themselves (didn't re-derive their own assertions) given the volume here, but nothing about the relocation looked incomplete.
Bonus, unprompted: independently verified repo_tests/preflight_runs_synced_source_17137_test.py's property is the right generalization — checked all 3 playbook call sites reference the script via {{ git_repo_root }} (freshly-synced tree) while the venv interpreter path and --repo-root argument are correctly excluded from the "executable line" check. Matches the live incident exactly.
Nothing blocking.
…idden line citations (#17147) Shards 6 and 7 failed on one root cause wearing two shapes: line-number references that the consolidated tree moved out from under. Shard 6 -- #17145's lazy-import refactor shifted the symbols THREAT_MODEL.md anchors at. import_module 541 -> 550, spec_from_file_location 610 -> 619, ConnectorCredentialStore 178 -> 195, _require_owner 626 -> 643. Each new line read from the guard's own report of where the symbol actually is, or measured directly; none estimated. _require_owner had already been re-pointed once tonight, 604 -> 626 on #17134, and has moved again. Shard 7 -- #17140's new comments cited AUTOBOT_REFERENCE.md:64, which repo_tests/comment_line_number_citations_test.py forbids outright: a line reference goes wrong an order of magnitude more often than a symbol one (#15877). Now cites the document and the claim, which is what a reader needs to find anyway. Neither failure was visible on the individual PRs. #17145 moved the code and #17140 wrote the citations, and only the merged tree contains both -- the same class of cross-PR interaction that tripped the reach floor on this vehicle. Refs #17147
#17116) 347 behind. Blob comparison against origin/main shows 13 of the 21 files this branch touched are already byte-identical on main -- #16249 and #16394 landed via #17149, and the CI action/workflow payload with them. Three conflicts, all resolved toward main so no later fix is reversed: - services/role_registry.py: branch only reworded a comment and re-wrapped a string to hold the 715-line ceiling; main is already 715. Took main. - repo_tests/secrets_baseline_reasons.py: main extracted the reason table into secrets_baseline_reason_entries.py (#17134); the branch still carried it inline. Taking the branch here would have reverted that refactor. Took main and PORTED the branch's one real change into the new module -- a reason that cited 'line 242' for _BASIC_AUTH_URL_RE, which now sits at line 262. - repo_tests/python_filter_covers_tested_shell_wrappers_test.py: add/add, and the two sides differ only in black's line-wrapping. Took main. Net residue actually carried by this PR: the dependabot uv bump (anyio 4.12.1->4.14.2, httpcore2/httpx2 2.10.0->2.12.0), pypdf 6.18.1->6.19.0 in two manifests, and the ported reason reword. All upgrades; no downgrade.
Thinking Path
This follows the owner's consolidation direction of 2026-09-19 ("fix all red, review and merge, check if any are consolidation candidates"). The approved security-class trains land together in one CI run, not one after another. The rest trains ride #17133. #17106 (session ownership, security) is appended to this train once #17133 has landed, because its resolution already contains #17133's content.
What Changed
The branch was cut from
mainafter #17048 and carries these members:d69a1d12418b7bd135c682233a4017172c5a7f45Resolution commits, made locally on this branch (the coordinator's own):
64d9bd0a1a,539ab7645danda0e7b408dcbring in feat(secrets): bridge connector credential templates to ConnectorCredentialStore (#16428) #16444, security(models): pin 3 unpinned HF loads, widen bandit to npu-worker (#17087) #17088 and chore(train): batch vehicle 2 — 4 approved PRs (#17093, #17094, #16896, #16899), one CI run #17095.wc -l.autobot-slm-backend/main.pyis 798 andai_hardware_accelerator.pyis 1040..secrets.baseline: JSON union, de-duplicated by (type, hashed_secret); 1413 entries, and theshould_exclude_filefilter is kept.autobot-slm-backend/main.py: auto-merged. It registers the shared 422 handler once, next to chore(vehicle): land A2A capability gating and originator authority (#16957, #16950) #17113'sforwarded_allow_ips.vision.py,voice.pyandai_hardware_accelerator.py. It loads and verifies first and only then assigns; this was checked across the chore(train): batch vehicle 2 — 4 approved PRs (#17093, #17094, #16896, #16899), one CI run #17095 merge.3df1ea20e0: deletesautobot-backend/api/a2a.py's size entry (563 lines, underMAX_LINES). security(models): pin 3 unpinned HF loads, widen bandit to npu-worker (#17087) #17088 had added it, andpython_file_size_ratchet_test.pyrequires compliant files to be removed.7cd4c30bad:# pragma: allowlist secreton 9 non-secret literals that the whole-tree scan flags:pinned_model_registry.py;hashed_secretkeys inrepo_tests/secrets_baseline_reasons.py;X-API-Keyheader name inmiddleware/api_key_allow_list.py.Closes #16957
Closes #16040
Closes #16294
Closes #16999
Closes #16428
Closes #17087
Refs #13034
Refs #16335
Refs #16375
Refs #16709
Verification
detect-secrets-hook --baseline .secrets.baselineover all 124 files changed againstmain: exit 0.secrets_baseline_reasons_guard_test,secrets_baseline_legacy_keys_excluded_17130_testandpython_file_size_ratchet_test: 40 passed.fastapi_validation_handler_guard_testandmain_validation_handler_test: 8 passed.Model Used
Claude Opus 5 (coordinator) and a Sonnet subagent for the conflict resolution
Closes #17052
Closes #17053
Closes #17057
Closes #17074
Closes #17078
Closes #17079
Carried in after #17133 landed
mainat1b9f5143— 0 behind. Four conflicts resolved:agent_communication.py(kept main's
run_or_scheduleimport, which main still calls, and main's__main__CLI block,which security(agents): an agent refused an action can get another agent to do it — no per-peer identity #16950 dropped incidentally; all of security(agents): an agent refused an action can get another agent to do it — no per-peer identity #16950's own changes survive),
base_agent.py(bothsides wanted —
acting_for(origin)now wrapsexecute_with_tracking), and the two sizebaselines, pinned to sizes measured in the merged tree, not to either side's number.
issue-17052-command-approvals), whose closing links are added above.