Skip to content

Security hardening + DA-11 subset federation, MO-19 dim-guard, doc-honesty & flower→orchestration rename - #12

Merged
anurag2796 merged 38 commits into
vinffrom
staging
Jul 15, 2026
Merged

Security hardening + DA-11 subset federation, MO-19 dim-guard, doc-honesty & flower→orchestration rename#12
anurag2796 merged 38 commits into
vinffrom
staging

Conversation

@anurag2796

Copy link
Copy Markdown
Collaborator

Summary

A hardening + honesty pass over the platform, parked on staging off vinf. Every code change is behind tests (RED→GREEN); full suites green throughout — backend 474, framework 396 (bar the pre-existing torch-version env check), backend-scripts 78. No wire/proto changes; the check_proto_mirror contract is untouched.

9 of the 11 Fable-5 security-review findings are closed here (the other two — SE-14 deploy-coordinated flag flip, SE-23 desktop code-signing — need resources outside the repo).

Security hardening (Fable-5 findings)

Finding Sev What
SE-15 HIGH Bind the FL connection token to one client_id (partition↔client_id bijection, trust-on-first-use) — one token can no longer Sybil the cohort.
SE-16 MED Participant-gate the artifact read endpoints (BOLA) — with a published-to-marketplace carve-out so FE-12 downloads still work.
SE-17 MED Rebuild the spawned FL-server child env from an allowlist instead of inherit-then-subtract — stops leaking the DB password / API keys / cloud creds.
SE-18 MED Cap streamed model uploads (bytes + chunk count, checked before buffering) and a server-side wall-clock deadline — closes the memory-exhaustion + slow-drip DoS.
SE-19 MED Stop auto-executing remote dataset code (trust_remote_code off by default; explicit opt-in + revision pin).
SE-20 LOW Fail boot closed when app.fl.token-secret resolves equal to the web JWT key on a deployed profile.
SE-21 LOW Production auth cookie defaults to Secure=true + SameSite=Strict (dev/ec2demo stay insecure for plain HTTP by design).
SE-22 LOW Raise the backend-scripts lockfile pins to the repo's own documented security floors (aiohttp/cryptography/pillow/requests).
SE-24 INFO Restrict actuator /health details to PLATFORM_ADMIN on deployed profiles (status stays public for probes).

Features & correctness

  • DA-11 — trainable-subset federation (phase 1) + content-addressed frozen-backbone distribution (phase 2a): deterministic serializer, fetch-once/verify-always cache, non-strict reconstruction with a fail-loud key guard.
  • MO-19 (dim-guard half) — fail loud on a DeComFL client/server trainable-dimension mismatch (the d=43 vs d=25 silent-divergence bug). The server advertises model_dim in the existing config map; the client rejects a mismatch and exits (fatal, never retry-loops). No proto change — works for every client type.
  • MO-9 — stop capturing jsi::Runtime& across the worker boundary in runOnWorker (mobile use-after-free).

Refactor & docs

  • DA-12 — rename the legacy flower package / FlowerServerManagerorchestration / FlServerManager (there was never a Flower dependency). Pure code rename, no config-key change.
  • DO-10 — document the content-addressed artifact registry.
  • DO-14 / DO-15 — doc-honesty: corrected the false "identity/multi-tenancy/audit subsystem is not present" banners across 7 wiki files (the subsystem IS committed — V4V7 migrations, all classes) and the stale libtorchExecuTorch runtime descriptors.

Not in this PR (tracked follow-ups)

  • SE-14 (ship token-aware clients, then flip require-client-auth in deployed profiles) — deployment-coordinated.
  • SE-23 / DE-5 / DE-6 — desktop code-signing (needs Apple Developer ID / Windows certs).
  • MO-18, MO-19 real-data staging — need the mobile NDK/ExecuTorch toolchain.
  • The generated wikis/html/ mirror needs regenerating from the corrected markdown (no in-repo generator).

Testing

Per-finding TDD with an independent adversarial review pass on each diff. Backend integration suite runs on Testcontainers Postgres; framework + scripts suites run without a GPU.

…setDimMismatch + drop dead branch (review)

FedAvgAggregator.aggregate() derives its output key-set from the FIRST client's
update and silently skips a key a LATER client is missing, so validating only
the aggregated output (the old vertical-slice test) could never catch a
non-first client's bad payload. Add guard_client_updates() to run the
fail-loud check on each client's raw payload before aggregation, and rewire
the vertical-slice test to call it pre-aggregation with distinct client ids.

Make validate_subset_update shape-aware: a same-key/wrong-shape update (e.g.
a misconfigured client's head) now raises the typed SubsetDimMismatch instead
of falling through to load_state_dict's raw RuntimeError. With the guard
covering both keys and shapes ahead of the call, the unreachable `unexpected`
branch in apply_trainable_subset is removed (YAGNI).

Also adds an order-sensitivity test (same key set, different order) and gives
the vertical-slice test's two clients distinct ids.
…undary in runOnWorker

runOnWorker spawned a worker std::thread that captured the executor's
jsi::Runtime& (rt2) by reference and forwarded it into the deferred
invokeAsync callback, where it was dereferenced via build(rt2, result).

By reference-collapse that capture binds to the real runtime object, not a
dangling stack slot, so there is no stack UAF on its own. The genuine hazard
is runtime lifetime: the jsi::Runtime is owned by the RN instance, and if that
instance is torn down (bridge reload / app shutdown) while a multi-minute round
is still running on the worker, the queued callback holds a reference to an
already-destroyed runtime — a use-after-free. Joining workers in the destructor
protects `this`, but cannot protect the runtime, since the runtime's teardown is
what triggers the module teardown. The old "rt2 outlives every worker" comment
was the false premise.

Fix: the worker now captures only value-copyable, runtime-independent state (no
jsi::Runtime, no `this`) and runs work() to a plain C++ result. Results are
marshalled back with the RN 0.80 CallFunc form of invokeAsync
(std::function<void(jsi::Runtime&)>), so the runtime used to build/resolve the
Promise is the one the CallInvoker hands the callback at execution time on the
JS thread — never a reference captured across the async boundary, and never
dereferenced against a torn-down runtime. Worker-tracking/join machinery is
unchanged.
…the .npz-overwrite gap

Add wikis/backend/07_artifact_registry.md — the full architecture doc for the
artifact_blobs / model_artifacts / artifact_lineage keystone (V12/V18), the
ArtifactBlobStore write-once content store, RegistryModelResolver's registry-first
read path for inference and FL-server warm-start, and the full artifact/lineage/
marketplace REST surface. Every structural claim carries a file:line anchor,
cross-checked against the current code. Wired into wikis/README.md and
wikis/backend/README.md's indices.

Corrects framework/src/fedlearn/bundle/BUNDLE_FORMAT.md's stale "Fixture-MVP
boundary" claim that fl_server.py "currently registers the legacy .npz bytes" --
that's no longer true for the LoRA path since _emit_and_register_lora_bundle
landed (DA-9 bullet 3): it registers real safetensors bytes now. The mobile
bundle-provisioning fixture gap (stage_model_bundle.py still hardcodes
TINYNET_GOLDEN) remains open and is left as-is.

Adds pointers from wikis/backend/01_architecture_overview.md and
03_project_management.md's .npz-based model-initialization steps to the new
page, since those describe the initial (pre-training) model file only and were
at risk of being read as the whole story.

docs/ is gitignored in this repo (wikis/ is the committed docs home), so
docs/plans/TODO.md does not exist in this worktree -- skipped per the fallback
instruction.
The content-addressed artifact registry read endpoints (GET /api/artifacts/{id},
/{id}/blob, ?projectId=, /latest, and /{id}/lineage) were gated on org scope alone.
In the single-org fallback every user collapses to one org, so org scope provided no
isolation and any authenticated user could download any project's trained model
weights (and read its base/license provenance) — a BOLA, since FL weights permit
membership-inference / model-inversion of the private training data.

Add AuthorizationService.isParticipant (the non-throwing sibling of requireParticipant)
and gate every artifact read: readable iff org-visible AND (no owning project — a
BASE_REF; OR explicitly published to the org marketplace, FE-12; OR the caller is a
participant of its project). A non-participant reads a private artifact as absent (404 /
empty list), so neither the weights nor the project's existence leak, while the
publish-to-share marketplace flow keeps working. Mirrors the gate the rest of the
project read surface already applies (results, logs, STOMP).

Tests: RED-proven — the new non-participant cases returned 200 before the gate; now 404.
Covers participant/non-participant/published/BASE_REF/cross-org for get/blob/list/latest
and lineage. Full backend suite green.
…anti-Sybil)

The connection token carries a server-assigned, unforgeable partitionId (one per
run+user enrollment), but the wire client_id on the FL RPCs is a self-chosen handle
the proto marks "NOT trusted for authz". The interceptor proved the token and bound it
to the run, but discarded the claims — so one valid token could be replayed under many
client_ids, letting a single enrolled participant impersonate the whole cohort and
dominate FedAvg/DeComFL aggregation (each fake client_id an averaged slot).

Bind the token's partition to a single client_id, server-side only, without changing
clients or rekeying aggregation:
- security/identity.py: partition_from_metadata re-verifies the x-connection-token and
  returns its partitionId; partition_extractor_from_env gates this on the same
  FEDLEARN_REQUIRE_CLIENT_AUTH switch as the interceptor (off => None => disabled in dev).
- FLCoordinator.bind_or_check_identity: a 1:1 partition<->client_id bijection (TOFU),
  atomic under the coordinator lock — first pair pins it; a token replayed under a second
  client_id, or a client_id claimed by a second partition, is rejected.
- The servicer enforces it on every write/identity RPC (RegisterClient, SubmitModelUpdate,
  SubmitModelUpdateStream, SubmitGradientScalars, Heartbeat), aborting PERMISSION_DENIED on
  conflict; wired in at start_server. Enforcement is placed before each broad try/except
  (context.abort must reach gRPC, not be swallowed); the stream resolves+enforces the first
  chunk's client_id, then chains it back into the loop unchanged. Read/telemetry RPCs are
  intentionally not bound (reading as any client_id can't affect aggregation).

Composes with SE-14: binding activates only when client-auth is enforced. Two honest
clients colliding on a self-chosen client_id fail closed by design — the durable fix is
clients deriving client_id from their partition (follow-up).

Tests: coordinator bijection (Sybil + collision rejected), the identity extractor, and the
servicer gate (first client binds, a second on the same token is PERMISSION_DENIED).
Full framework suite green modulo the pre-existing torch-version env check.
The PNEUMONIA_CNN HuggingFace fallback called
load_dataset(repo, ..., trust_remote_code=True) on an unpinned repo, so HF
downloaded and RAN the repo's loader script on the backend host the moment such
a run started — a supply-chain RCE if the repo is compromised.

Route the load through a pure _hf_load_kwargs seam that:
  * omits trust_remote_code by default (no remote code executes),
  * pins the dataset to a commit when FEDLEARN_PNEUMONIA_REVISION is set, and
  * enables trust_remote_code only on an explicit FEDLEARN_PNEUMONIA_TRUST_REMOTE_CODE=1
    operator opt-in (a deliberate, auditable choice).

Tests: default-safe / revision-pin / opt-in-only kwargs, plus a bytecode wiring
guard that _full_dataset routes through the seam (RED against the old inline
literal, robust to docstrings). Full scripts suite green (77 passed).
The backend spawns the Python FL server as a child process and built its
environment by inheriting the backend's ENTIRE environment and removing a single
key (APP_JWT_SECRET). That leaked the DB password, the internal API key, cloud
credentials, and the CORS/JWT secrets into a network-facing process that also
loads datasets — an unnecessary blast radius on any FL-server compromise.

Rebuild the child env from an allowlist instead of inherit-then-subtract: keep
OS/runtime essentials (POSIX + Windows), the FEDLEARN_*/LC_/PYTHON/CUDA/NVIDIA_
namespaces (incl. the SE-2 TLS cert paths the child inherits), and the three
non-FEDLEARN_ vars the server actually reads (MAX_CLIENTS, SERVER_HOST,
AWS_HOST); drop everything else. The explicit per-run vars (the child's own
internal-API key + token-verify secret + enforcement/TLS toggles) are set after.

Verified: unit test pins secrets-dropped / allowlisted-vars-kept; a live run
under the allowlist-filtered env imports recipes(+torch) and fedlearn.server
cleanly (over-restriction ruled out). Full backend suite green (474 passed).
…y floors

backend/fl-platform-api/requirements.txt is installed by the backend-scripts
pytest CI job (.github/workflows/ci.yml), so it is a live lockfile, not dead.
It pinned aiohttp/cryptography/pillow/requests BELOW the patched floors that
framework/requirements.txt already documents (incl. an aiohttp RCE):
  aiohttp 3.12.15 -> >=3.14.0 (CVE-2026-34993/47265 RCE + DoS/smuggling)
  cryptography 44.0.3 -> >=46.0.6
  pillow 11.0.0 -> >=12.2.0 (PYSEC-2026-165 + CVE-2026-40192/42309/42310/42311)
  requests 2.32.4 -> >=2.33.0 (CVE-2026-25645)

A guard test reads the floors from framework/requirements.txt (single source of
truth) and fails if the backend lockfile allows an install below any of them, so
the two can't silently drift apart again.
…or posture

SE-21: the production profile set no cookie config, so it inherited the base
app.auth.cookie.secure=false / SameSite=Lax (those defaults exist so local dev
and the plain-HTTP ec2demo keep working). Production terminates TLS, so it now
defaults the auth cookie to Secure=true + SameSite=Strict. dev/ec2demo stay
Secure=false by design (asserted, so they aren't tightened by accident).

SE-24: actuator /health used show-details=when-authorized, exposing reconciler
counts + exception class names to any authenticated USER. production + ec2demo
now set management.endpoint.health.roles=PLATFORM_ADMIN, so /health STATUS stays
public (probes/uptime unaffected) but its DETAILS are admin-only.

A DeployedProfileHardeningTest pins both invariants against the profile files.
Full backend suite green (474).
…web JWT key

app.fl.token-secret defaults to app.jwt.secret (${APP_FL_TOKEN_SECRET:${app.jwt.secret}})
for local backward-compat. But the network-facing FL server holds the FL secret,
so if it equals the web-auth key a compromise of that server can forge web/admin
sessions — defeating the SE-7/SE-17 trust-domain isolation that keeps APP_JWT_SECRET
out of the FL child. A new FlSecretDistinctnessValidator fails the boot closed
(IllegalStateException) when a deployed profile (ec2demo/production) is active AND
the two secrets resolve equal; dev/test/base still allow the fallback.

This is the "boot check they differ" half of finding #7; the web-JWT audience/type
binding (defense-in-depth for a shared dev secret) touches the auth hot path and is
tracked as remaining. Unit-tested (deployed+equal -> throws; deployed+distinct and
non-deployed -> ok); full backend suite green.
…austion DoS

SubmitModelUpdateStream reassembled a client's ModelUpdateChunk stream into an
in-memory BytesIO with no bound. A client that never sends is_final_chunk (or
streams huge/endless chunks) grew that buffer without limit -> OOM the FL server.

Bound the reassembly buffer with env-configurable caps read in __init__
(FEDLEARN_MAX_UPLOAD_BYTES default 2 GiB, FEDLEARN_MAX_UPLOAD_CHUNKS default
100000). In the receive loop, BEFORE buffer.write: reject an honestly-declared
oversize (first chunk total_bytes > cap), and reject when cumulative bytes or
chunk count would exceed the cap — so the buffer never grows past the limit even
if the client lies about total_bytes/total_chunks or omits is_final. Enforcement
raises a dedicated _StreamLimitExceeded, caught by a clause placed BEFORE the
broad except ValueError/except Exception (which would otherwise remap the abort
to INVALID_ARGUMENT/INTERNAL, as with SE-15) and mapped to RESOURCE_EXHAUSTED.

This closes the memory-exhaustion vuln named in finding #5; a server-side per-RPC
wall-clock deadline and streaming-to-a-temp-sink are complementary follow-ups.
Identity enforcement (SE-15) is unchanged. 4 new tests (byte cap, chunk cap,
declared-oversize early reject, within-limits passes); framework suite green.
…h wiki claims

DO-15 — the wiki repeatedly claimed the identity / multi-tenancy / audit subsystem
was "designed on a separate identity-foundations branch and is not present here"
(page 06 even cited a stale V3 migration head and a feat/ember-rebrand branch).
That is false: the subsystem IS committed — V4-V7 migrations
(V5__identity_foundations, V6__identity_hardening, V7__owner_role_and_approval_workflows),
migrations run to V19, and every class exists (PlatformRole, OrgRole, OrgScopeFilter,
AuthorizationService, AuditEvent, the membership repos, the audit/ bootstrap/ email/
packages). The frontend also ships the role-gated UI (RoleRoute PLATFORM_ADMIN/
PROJECT_OWNER, AdminDashboard/OwnerDashboard, approval flows). Corrected the false
banners in wikis/README.md, wikis/backend/{README,01,02,03,06}.md and
wikis/frontend/Routing_and_Auth.md to state the subsystem is present.

DO-14 — fixed the stale on-device-runtime descriptor "native C++ (libtorch)" ->
"(ExecuTorch)" in wikis/README.md (x2), README.md, and wikis/framework/06_decomfl.md.
The build-instruction libtorch mentions in mobile_client/README.md are intentionally
left: build_libtorch_arm64.sh remains for the host-parity gate and the iOS xcframework
migration is still pending (both legitimately libtorch).

Note: the generated wikis/html/ mirror (per wikis/README.md, "generated") still carries
the old banners; it has no in-repo regenerator and is a pre-baked static export, so it
needs regenerating from this corrected markdown by the external doc tool.
…ed uploads

Follow-up to the SE-18 byte/chunk caps: a client can stay under those caps and
still tie up a server worker by streaming slowly. Add an env-configurable max
active-streaming duration (FEDLEARN_MAX_UPLOAD_SECONDS, default 600s; <=0
disables) measured with time.monotonic() and checked on each chunk arrival. On
expiry the same _StreamLimitExceeded is raised carrying grpc DEADLINE_EXCEEDED
(distinct from the size caps' RESOURCE_EXHAUSTED); the single handler now aborts
with the carried code, still ahead of the broad except so it isn't remapped.

Documented limitation: the check runs on chunk arrival, so a client that
connects then goes fully silent blocks on the next read and is bounded by gRPC's
max_connection_age_ms / keepalive (set in server.py), not this deadline. 2 new
tests (deadline exceeded -> DEADLINE_EXCEEDED; <=0 disables); framework suite
green (393 passed).
…orchestration/FlServerManager

The Spring Boot package com.federated.fl_platform_api.flower and the class
FlowerServerManager were historical names — there has never been a Flower/flwr
dependency (the framework is entirely custom). They forced a perpetual "this
isn't Flower" disclaimer. Renamed:
  package  ...fl_platform_api.flower  ->  ...fl_platform_api.orchestration
  class    FlowerServerManager        ->  FlServerManager
(joining the existing FlServerProcessRunner / LocalProcessFlServerRunner /
SpawnedFlProcess naming family). Pure code rename — no property keys named
"flower", so no config-contract change; only Java + comment references moved.
Full backend suite green (474), behavior unchanged.

Also swept the tracked docs to match (wikis/**, README.md): FlServerManager +
the orchestration/ directory, and — found while sweeping 01_architecture_overview
and 06 — three DO-15 leftovers the earlier pass missed (the model/ and core-entity
notes and 06's RBAC-UI banner still claimed the identity subsystem / admin console
were on a "designed branch"; corrected to present, matching the verified code).

Note: the generated wikis/html/ mirror still shows the old names + banners; no
in-repo regenerator, so it needs regenerating from this corrected markdown.
…-dim mismatch

DeComFL has every party apply a shared seed-generated perturbation z whose length
is the server's model_dim. If a client's trainable parameter count differs from
model_dim, z misaligns and the model diverges SILENTLY — the d_server=43 (a full
state_dict incl. frozen fc2) vs d_client=25 (trainable only) bug surfaced live in
the MO-15 bring-up. The server's validate_participant_dim guard existed but was
never wired to the handshake.

Close it with no wire/proto change by using the existing config string-map:
  * server GetDeComFLConfig now advertises config["model_dim"] = strategy.model_dim;
  * DeComFLClient.assert_dim_matches(server_model_dim) raises loud if its own
    get_num_params (== requires_grad-filtered num_trainable) differs;
  * the run loop (decomfl_start) checks it right after fetching the config and, on
    mismatch, exits ERROR immediately — a fatal setup error, never a retry loop.

Backward compatible: a config without model_dim skips the check (older server /
other client type), and mobile clients read the same map and ignore the new key
until they opt to self-check. 3 tests (client guard incl. the 43-vs-25 case;
server advertises model_dim; run-loop fatal-exit-without-retry). Framework suite
green (396); backend-scripts suite green (78).
Copilot AI review requested due to automatic review settings July 15, 2026 00:12

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens the platform against multiple security findings (auth/authorization, secret handling, DoS limits), adds a DA-11 “subset federation” vertical slice (trainable-subset + frozen-backbone distribution) and the MO-19 DeComFL dimension guard, and performs the “flower → orchestration” rename along with broad documentation “branch reality” corrections.

Changes:

  • Security hardening across backend + framework (client identity binding, participant-gated artifact reads, allowlisted child env, streamed upload caps, HF trust_remote_code opt-in, deployed-profile defaults/validators, dependency floor guards).
  • DA-11 subset federation + backbone distribution helpers/tests; MO-19 DeComFL client/server dimension advertisement + fail-loud checks.
  • Documentation updates and orchestration rename (FlowerServerManagerFlServerManager, flower/orchestration/), plus new artifact-registry documentation.

Reviewed changes

Copilot reviewed 69 out of 72 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
wikis/README.md Updates master wiki component descriptions and “branch reality” notes; adds artifact registry index entry.
wikis/frontend/Routing_and_Auth.md Updates frontend RBAC docs to reflect layered roles present on this branch.
wikis/framework/06_decomfl.md Updates DeComFL doc references (ExecuTorch naming).
wikis/framework/01_architecture_overview.md Updates framework overview wording for orchestration rename.
wikis/desktop/07-hardware-profiles.md Removes “Flower” terminology from desktop hardware profile docs.
wikis/backend/README.md Updates backend wiki index and “branch reality” to reflect identity subsystem presence + artifact registry page.
wikis/backend/07_artifact_registry.md New documentation page describing the content-addressed artifact registry subsystem.
wikis/backend/06_identity_multitenancy_and_audit.md Updates identity docs “branch reality” banner to reflect subsystem is committed.
wikis/backend/04_federated_orchestration.md Updates orchestration docs for FlServerManager rename.
wikis/backend/03_project_management.md Updates project management docs for identity presence + registry pointer + FlServerManager rename.
wikis/backend/02_security_and_auth.md Updates security/auth docs for identity presence + FlServerManager rename.
wikis/backend/01_architecture_overview.md Updates backend architecture overview for orchestration rename and identity presence + registry pointer.
README.md Updates repo-root README structure notes for orchestration rename and ExecuTorch wording.
mobile_client/bridge/common/FedLearnCoreModule.cpp Fixes JSI runtime lifetime hazard by avoiding capturing jsi::Runtime& across worker boundary (MO-9).
framework/tests/test_subset_federation.py New tests for DA-11 trainable-subset federation guards and reconstruction.
framework/tests/test_stream_upload_limits.py New tests for streamed upload size/chunk/time caps (SE-18).
framework/tests/test_decomfl_layout_contract.py Adds tests for MO-19 client-side dim guard + server config advertising model_dim.
framework/tests/test_decomfl_client_lifecycle.py Adds lifecycle test ensuring dim mismatch is fatal and does not retry-loop.
framework/tests/test_client_identity_binding.py New tests for SE-15 token-partition ↔ client_id identity binding.
framework/tests/test_backbone_distribution.py New tests for frozen backbone serialization/caching/reconstruction + subset-only federation.
framework/tests/fixtures/tiny_frozen_model.py New tiny frozen-backbone model fixture for DA-11 tests.
framework/tests/fixtures/init.py Marks fixtures package.
framework/tests/init.py Marks tests package.
framework/src/fedlearn/server/subset_federation.py New module implementing per-client subset guards + reconstruction for DA-11.
framework/src/fedlearn/server/server.py Wires SE-15 partition extractor into servicer creation.
framework/src/fedlearn/server/grpc_servicer.py Implements SE-15 enforcement, SE-18 streamed upload caps, and advertises model_dim in DeComFL config.
framework/src/fedlearn/server/coordinator.py Adds coordinator-side partition/client identity binding (SE-15).
framework/src/fedlearn/security/identity.py New module to extract verified partitionId from token metadata (SE-15).
framework/src/fedlearn/estimators/params.py Adds frozen_state() helper for frozen-backbone distribution.
framework/src/fedlearn/client/decomfl_start.py Adds MO-19 client-side dim mismatch fail-fast using server-advertised model_dim.
framework/src/fedlearn/client/decomfl_client.py Adds assert_dim_matches() implementation (MO-19).
framework/src/fedlearn/bundle/BUNDLE_FORMAT.md Updates doc “fixture boundary” status (what’s done vs still open).
framework/src/fedlearn/backbone/distribution.py New frozen-backbone serialization + cache + reconstruction utilities (DA-11).
framework/src/fedlearn/backbone/init.py Marks backbone package.
backend/fl-platform-api/src/test/java/com/federated/fl_platform_api/service/ModelBundleAutostageIntegrationTest.java Updates orchestration class rename in integration test wiring.
backend/fl-platform-api/src/test/java/com/federated/fl_platform_api/registry/ArtifactLineageControllerTest.java Extends tests for participant-gated lineage reads + published carve-out (SE-16).
backend/fl-platform-api/src/test/java/com/federated/fl_platform_api/registry/ArtifactControllerTest.java Extends tests for participant-gated artifact reads + marketplace carve-outs (SE-16).
backend/fl-platform-api/src/test/java/com/federated/fl_platform_api/ProjectServiceTest.java Updates orchestration manager rename in unit tests.
backend/fl-platform-api/src/test/java/com/federated/fl_platform_api/ProjectServiceExtendedTest.java Updates orchestration manager rename in unit tests.
backend/fl-platform-api/src/test/java/com/federated/fl_platform_api/orchestration/FlowerServerManagerRunnerSeamTest.java Updates orchestration package/class naming for runner seam test.
backend/fl-platform-api/src/test/java/com/federated/fl_platform_api/orchestration/FlowerServerManagerProcessIdentityTest.java Updates orchestration package/class naming for process identity test.
backend/fl-platform-api/src/test/java/com/federated/fl_platform_api/orchestration/FlowerServerManagerIntegrationTest.java Updates orchestration package/class naming for integration test.
backend/fl-platform-api/src/test/java/com/federated/fl_platform_api/orchestration/FlowerServerManagerDpPolicyTest.java Updates orchestration package/class naming for DP policy test.
backend/fl-platform-api/src/test/java/com/federated/fl_platform_api/orchestration/FlowerServerManagerCommandTest.java Updates command/env tests for FlServerManager and adds allowlist env test (SE-17).
backend/fl-platform-api/src/test/java/com/federated/fl_platform_api/orchestration/FlowerServerManagerCatalogGateTest.java Updates orchestration package/class naming for catalog gate test.
backend/fl-platform-api/src/test/java/com/federated/fl_platform_api/FlServerTlsProfileDefaultTest.java Updates naming references in TLS default test docs.
backend/fl-platform-api/src/test/java/com/federated/fl_platform_api/DeployedProfileHardeningTest.java New tests pinning deployed-profile defaults for cookie + actuator /health details (SE-21/SE-24).
backend/fl-platform-api/src/test/java/com/federated/fl_platform_api/config/FlSecretDistinctnessValidatorTest.java New tests for boot-fail when FL secret equals web JWT secret on deployed profiles (SE-20).
backend/fl-platform-api/src/test/java/com/federated/fl_platform_api/bootstrap/StartupReconcilerTest.java Updates orchestration manager rename in reconciler tests.
backend/fl-platform-api/src/test/java/com/federated/fl_platform_api/bootstrap/StartupReconcilerIntegrationTest.java Updates orchestration manager rename in reconciler integration test.
backend/fl-platform-api/src/main/resources/scripts/tests/test_requirements_security_floors.py New test preventing backend-scripts requirements from dropping below security floors (SE-22).
backend/fl-platform-api/src/main/resources/scripts/tests/test_recipe_catalog_matches_runnable_choices.py Updates doc reference for catalog gate class rename.
backend/fl-platform-api/src/main/resources/scripts/tests/test_pneumonia_hf_load_kwargs.py New tests enforcing HF trust_remote_code opt-in and revision pin seam (SE-19).
backend/fl-platform-api/src/main/resources/scripts/recipes.py Adds _hf_load_kwargs seam and disables remote code execution by default (SE-19).
backend/fl-platform-api/src/main/resources/scripts/fl_server.py Updates inline documentation to FlServerManager naming.
backend/fl-platform-api/src/main/resources/application.properties Updates orchestration naming note.
backend/fl-platform-api/src/main/resources/application-production.properties Pins production cookie defaults + gates /health details (SE-21/SE-24).
backend/fl-platform-api/src/main/resources/application-ec2demo.properties Gates /health details in ec2demo profile (SE-24).
backend/fl-platform-api/src/main/java/com/federated/fl_platform_api/service/RegistryModelResolver.java Updates orchestration naming in JavaDoc.
backend/fl-platform-api/src/main/java/com/federated/fl_platform_api/service/ProjectService.java Renames injected manager to FlServerManager and updates call sites.
backend/fl-platform-api/src/main/java/com/federated/fl_platform_api/service/InferenceService.java Updates comment to reflect FlServerManager naming.
backend/fl-platform-api/src/main/java/com/federated/fl_platform_api/service/AuthorizationService.java Adds isParticipant() helper and refactors requireParticipant() to reuse it (SE-16 support).
backend/fl-platform-api/src/main/java/com/federated/fl_platform_api/orchestration/SpawnedFlProcess.java Moves package to orchestration and updates JavaDoc naming.
backend/fl-platform-api/src/main/java/com/federated/fl_platform_api/orchestration/LocalProcessFlServerRunner.java Moves package to orchestration and updates JavaDoc naming.
backend/fl-platform-api/src/main/java/com/federated/fl_platform_api/orchestration/FlServerProcessRunner.java Moves package to orchestration and updates JavaDoc naming.
backend/fl-platform-api/src/main/java/com/federated/fl_platform_api/orchestration/FlServerManager.java Renames manager, adds child-env allowlist rebuild (SE-17), and updates related comments.
backend/fl-platform-api/src/main/java/com/federated/fl_platform_api/model/Project.java Updates comment to reflect FlServerManager naming.
backend/fl-platform-api/src/main/java/com/federated/fl_platform_api/controller/ArtifactLineageController.java Adds participant/published gating for lineage reads (SE-16).
backend/fl-platform-api/src/main/java/com/federated/fl_platform_api/controller/ArtifactController.java Adds participant/published gating for artifact list/get/blob/latest (SE-16).
backend/fl-platform-api/src/main/java/com/federated/fl_platform_api/config/FlSecretDistinctnessValidator.java New boot-time validator enforcing distinct secrets on deployed profiles (SE-20).
backend/fl-platform-api/src/main/java/com/federated/fl_platform_api/bootstrap/StartupReconciler.java Renames injected orchestration manager type references.
backend/fl-platform-api/requirements.txt Raises pins to meet documented security floors (SE-22).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread framework/tests/test_subset_federation.py
Comment thread wikis/README.md Outdated
Comment thread wikis/README.md Outdated
The security/secrets job (gitleaks 8.30.1, whole-tree scan) flagged the
pre-existing framework/tests/fixtures/golden_connection_token.json — a JWT golden
signed with the dummy secret base64("fedlearn-golden-token-secret-32b") that the
cross-language connection-token parity tests use. It sits outside the existing
src/test allowlist, so the scan failed on a test-only public fixture. Extend the
allowlist to .*/tests/fixtures/.* (test-only by policy, never a real credential),
matching the existing src/test carve-out. Verified with gitleaks 8.30.1: zero
tracked findings after the change.
…p resolution)

The first SE-22 pass bumped cryptography to >=46.0.6 to match the framework floor,
but backend/fl-platform-api/requirements.txt uses flwr-datasets (FederatedDataset
in fl_server.py/client.py) -> flwr 1.20.0, which pins cryptography<45.0.0. That
made `pip install -r requirements.txt` unresolvable and failed the backend-scripts
CI job with ResolutionImpossible. The framework dropped flwr (zero imports) so its
46.x floor never had to satisfy that constraint; the backend still uses it.

Revert cryptography to the newest flwr-compatible pin (44.0.3) with an in-file note,
and keep the aiohttp/pillow/requests bumps (those resolve — verified with a
fresh-venv dry-run: aiohttp-3.14.1, pillow-12.3.0, requests-2.34.2, cryptography-44.0.3,
no conflicts). Exclude cryptography from the floor-guard test and add a dedicated
test pinning the flwr-compatible <45 range so a future bump can't silently re-break
the install. Scripts suite 79 passed.
…olchain

The framework CI job installs torch==2.12.0 (the version the DeComFL golden
fixtures + executorch 1.3.1 were built against), then `pip install -r
framework/requirements.txt`, where torch/torchvision/torchaudio were all
UNPINNED. With torch 2.13.0 now released, the unpinned torchvision resolves to
its latest wheel (0.28.0), which requires torch 2.13.x and silently upgrades
torch past 2.12.0 — breaking two tests:
  * test_torch_version_matches_manifest (2.13.0 != frozen 2.12.0), and
  * test_pte_forward_matches_eager (executorch 1.3.1's native extension, built
    for torch 2.12.0, fails to load against 2.13.0 -> ImportError, not a skip).

Pin torch==2.12.0 in requirements.txt. The framework does not import
torchvision/torchaudio (only the backend scripts do, via their own lockfile), so
those simply backtrack to the torch-2.12.0-compatible line (torchvision 0.27.0).
Verified with a fresh-venv dry-run of the exact CI install: torch stays 2.12.0,
executorch 1.3.1 resolves, no conflicts. Pre-existing env-drift fix, unblocks the
ci-gate-required `framework` check.
… subset run

The python-parity job runs `pytest tests/test_perturbation.py` inside framework/,
whose pytest.ini addopts include `--cov=fedlearn` (TE-11). The job installed only
`numpy pytest`, so pytest died with `unrecognized arguments: --cov` before running
any parity test. Install pytest-cov so the flag resolves, and pass `--no-cov` for
this single-file subset (running one file would otherwise trip --cov-fail-under=73
by design — the pytest.ini already documents `--no-cov` for subset runs).
…d PyJWT)

Two CI env-drift blockers surfaced once the earlier fixes let the jobs run:

framework — pinning torch==2.12.0 (for the golden fixtures + executorch) made an
unpinned torchvision resolve to a PyPI wheel that is ABI-incompatible with the
pytorch-index torch build (`RuntimeError: operator torchvision::nms does not
exist`), breaking transformers-importing tests (test_peft_preflight). The
framework doesn't import torchvision/torchaudio (setup.py already filters
torch*/torchvision* out of install_requires), and transformers works without
torchvision for the text model the test uses — so drop them from
framework/requirements.txt. torch stays pinned; the manifest + executorch tests
keep passing.

backend-scripts — the FL-server import chain (security/token_verify.py ->
`import jwt`, via the connection-token interceptor) needs PyJWT. It's declared in
framework/requirements.txt, but this job installs the backend lockfile and uses
the framework via sys.path, so `import jwt` failed at collection. Add PyJWT to
the job's install (mirrors framework/requirements.txt), like peft/scikit-learn.
…onftest)

The scripts-tests conftest put framework/src on sys.path so `import fedlearn`
resolves without an editable install. But sys.path does not cross a process
boundary: tests that spawn a fresh interpreter (test_init_model_tinynet_golden
runs `python init_model.py ...`) hit `ModuleNotFoundError: No module named
'fedlearn'` in the child. Also set os.environ['PYTHONPATH'] so spawned
subprocesses inherit the framework source. Verified: a fresh subprocess with only
PYTHONPATH (no sys.path hack) imports fedlearn; scripts suite 79 passed.
…edlearn

test_perturbation.py imports `fedlearn`, whose package __init__ eagerly imports
the gRPC server (grpc/protobuf) and the token-verify path (PyJWT). The parity job
installed only numpy/pytest, so collection died on `No module named 'grpc'` once
the earlier --cov fix let it get that far. Install framework/requirements.txt
(pins the matching torch==2.12.0, no torchvision) so the fedlearn import chain
resolves.
…issed doc spots

- framework/tests/test_subset_federation.py: hoist the mid-file
  `from collections import OrderedDict` to the top. (It was not actually a bug —
  every use is inside a test-function body, so the module-level import at line 67
  runs before any test executes; the suite passed. But the placement was a smell.)
- wikis/README.md: two spots the DO-14/DO-15 sweep missed — the mobile prose still
  said the C++ core "runs on libtorch" (-> ExecuTorch), and the doc-tree still
  labelled 06_identity as "designed; not on this branch" (-> present, V4–V7).
@anurag2796 anurag2796 changed the title Security hardening (9 Fable-5 findings) + DA-11 subset federation, MO-19 dim-guard, doc-honesty & flower→orchestration rename Security hardening + DA-11 subset federation, MO-19 dim-guard, doc-honesty & flower→orchestration rename Jul 15, 2026
@anurag2796
anurag2796 merged commit 3dad86d into vinf Jul 15, 2026
14 of 18 checks passed
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.

2 participants