Bind Agent observations, review, enrichment and telemetry to current Zone
and inventory authority. Preserve immutable MAC ownership and historical
snapshots with stale-result rejection and transactional security audits.
Retire unbound direct/range/scheduled discovery and one/bulk legacy summary
writes in both modes. Require reconciled exact inventory identity or
explicit Zone-scoped IP selection for CSV metadata imports.
Independent Blocker #1 application-security re-audit: PASS on tree
cf863aadc9. Evidence: 141 focused PostgreSQL/HTTP
tests passed; Chromium and PowerShell checks passed. Two documented baseline
source assertions remain unchanged. Production deployment is not approved.
220 lines
14 KiB
Plaintext
220 lines
14 KiB
Plaintext
Blocker #1 discovery isolation: candidate review and compatibility contract
|
|
=======================================================================
|
|
Review candidate only. Blocker #1 is NOT declared PASS. No production cutover is
|
|
authorized. Base: e898f283ab0fc21bac77b63db1c4a9ad35e21562 (Stage A / draft PR #6).
|
|
No schema migration, new constraints, backfill, or runtime DDL is introduced.
|
|
|
|
Authority and observations
|
|
-------------------------
|
|
All machine discovery/telemetry routes retain per-Agent HTTP authentication.
|
|
Authority comes from the persisted Agent -> Site/Zone/network assignment, never
|
|
client Site, room, Zone, target-Agent names, or a body-supplied Agent identity.
|
|
Conflicting body Agent IDs are rejected. Disabled/unassigned/revoked routing fails
|
|
closed. Check-in cannot establish or change an Agent's initial assignment: use
|
|
existing authenticated enrollment or the separately reviewed legacy transition.
|
|
Unassigned inventory is not implicitly claimed by check-in or snapshots.
|
|
|
|
Discovery reports are bounded (128 items, 1 MiB body; 1,024 observations/minute per
|
|
Agent). IP literals must be in the current Zone networks. MACs must be canonical
|
|
ASCII unicast addresses. Explicit IDs, IP aliases, MAC aliases and supplied serial
|
|
must agree with current inventory and immutable historical MAC ownership. Global
|
|
historical MAC/serial collision checks reveal no foreign inventory IDs. Duplicate
|
|
current IPs within a Zone are conflicts; distinct Zones may use the same IP.
|
|
|
|
Each observation has a stable server ID and version. Raw reports and original
|
|
provenance are retained, with an atomic discovery.observed audit recording the
|
|
current authority, raw hash, inventory fingerprint and observation version. Exact
|
|
retries deduplicate; discovery clients must send discovered_at for a fresh scan
|
|
(the supplied Windows scanner does). Approval requires a current, proven,
|
|
non-superseded observation less than 15 minutes old. Legacy/null/mislinked rows
|
|
without this evidence remain visible but cannot authorize approval or enrichment.
|
|
There is no automatic evidence backfill, deletion, identity merge or ownership
|
|
transfer. An authorized operator must investigate conflicts and obtain a fresh
|
|
observation. Discovery does not implement an ownership-transfer workflow.
|
|
|
|
Human policy remains global administration: agents.view for discovery display;
|
|
agents.manage for stable-ID review/enrichment; miners.control for scan/poll
|
|
requests. The UI displays authoritative Agent/Zone and escapes all untrusted text.
|
|
IP-only approval is unsupported. Submit discovery_id, version and one of
|
|
add_to_inventory / ignore / non_miner. All three revalidate current authority and
|
|
identity under locks; only that observation changes. Adding new inventory also
|
|
requires a valid MAC. Repeat the same accepted decision/version to receive its
|
|
recorded response without another audit or insertion. Stale/different decisions
|
|
conflict; refresh before retrying. Audit actor is the authenticated human.
|
|
|
|
Read commands and enrichment
|
|
----------------------------
|
|
Enrichment is selected by discovery_id/version, routed to its actual observing
|
|
Agent, and bound to the current authority and IP. No arbitrary-IP or hardcoded
|
|
Plexus fallback exists. Scan/poll requests require an explicit registered Agent ID;
|
|
optional UUID request_id provides explicit retry idempotency. New requests without
|
|
request_id receive distinct IDs. Queue claim rejects legacy/unscoped/stale reads
|
|
with atomic cancellation/audit and preserves unknown delivery outcome for any
|
|
previous claim evidence. Existing Stage A physical rejection remains in force.
|
|
|
|
Before enrichment miner I/O, the upgraded Windows child submits command_id,
|
|
discovery_id and version to authenticated POST /api/agent/discovery-target. Only
|
|
a current claimed enrichment command returns a canonical IP. Enrichment submission
|
|
revalidates the same binding and appends a NEW child observation for human review;
|
|
it never resets the parent's decision or reparents history. V1/V2/V3 share the
|
|
same boundary. Replaying old V2 identity merge inputs cannot merge inventory.
|
|
|
|
Known-miners returns a short-lived signed miner_version bound to Agent authority
|
|
and the current miner ID/IP/MAC/updated_at fingerprint. Every miner/pool/API3
|
|
snapshot or known-miner enrichment must carry this token and matching identifiers.
|
|
All Windows polling clients propagate it and batch at 128. A concurrent update,
|
|
revocation or reassignment may invalidate in-flight telemetry: discard the stale
|
|
response, refetch known-miners and poll again. Do not fall back to IP matching.
|
|
Descriptive enrichment does not change MAC/IP/serial/ownership. Raw diagnostics
|
|
are retained in append-only audits, not empty status rows that hide measurements.
|
|
API3 retains raw snapshots, safe descriptive metadata, actual summary/pool/board
|
|
measurements and compatibility counters. Failed/metadata-only API3 responses do
|
|
not replace the most recent measurements. Nested identity contradictions fail.
|
|
|
|
Transactions and database roles
|
|
------------------------------
|
|
All discovery writers/read-command claims take bounded locks in a shared order:
|
|
advisory transaction lock 743190031, SHARE on Agent/Zone/network tables, SHARE ROW
|
|
EXCLUSIVE on miners, then row locks. These table locks prevent phantom inserts
|
|
from legacy writers which do not use the advisory lock. Agent routing/check-in
|
|
uses guarded updates. No miner network I/O occurs while these transactions run.
|
|
Audit insertion, inventory mutation and the observation decision commit together;
|
|
any error rolls back all changes. Audit-denial and real UPDATE-trigger failure,
|
|
concurrent approvals, cross-Zone MAC races, Zone disable and claim-vs-review are
|
|
covered with real PostgreSQL connections and registered HTTP authorization routes.
|
|
Lock timeout is 5 seconds; statement timeout 15 seconds. Controlled contention
|
|
returns conflict/retry. Security favors correctness over fleet throughput here.
|
|
|
|
Required release review: validate privileges/lock behavior for the actual deployed
|
|
DB role in a disposable replica and measure expected fleet load. Tests use an
|
|
isolated database owner plus a limited role with audit INSERT denied. They do not
|
|
certify the production role. No new uniqueness constraint is proposed, so do not
|
|
remove the transaction/table locks without a separately reviewed replacement.
|
|
|
|
Reproduction
|
|
------------
|
|
Use a disposable checkout of this candidate with an already available backend
|
|
image containing backend/requirements.txt. Never mount production credentials or
|
|
.env files. From the candidate root:
|
|
|
|
MINEROPS_REVIEW_BACKEND_IMAGE=<existing-image> scripts/test-discovery-security.sh
|
|
|
|
The runner creates a unique internal Docker network, tmpfs PostgreSQL 16, no
|
|
published ports, applies the unchanged existing Alembic chain and runs the two
|
|
PostgreSQL discovery suites. A trap destroys only its own resources. Tests use
|
|
simulated Agents and FastAPI's registered routes, without app startup/schedulers.
|
|
|
|
PowerShell (isolated PowerShell 7 host/container with simulated/loopback I/O):
|
|
pwsh -NoProfile -File agents/windows/tests/Test-MinerOpsDiscovery.ps1
|
|
pwsh -NoProfile -File agents/windows/tests/Test-MinerOpsControl.ps1
|
|
pwsh -NoProfile -File agents/windows/tests/Test-MinerOpsDiagnosticTransport.ps1
|
|
|
|
Browser (Python 3, Node, Playwright and its Chromium/runtime installed externally):
|
|
python3 backend/tests/browser/export_discovery_page.py . /tmp/discovery-page.html
|
|
node backend/tests/browser/discovery-security.cjs /tmp/discovery-page.html
|
|
|
|
The browser test intercepts all requests, supplies harmless event-marker input,
|
|
and verifies inert rendering and exact same-IP discovery_id/version selection.
|
|
Its trailing baseline argument demonstrates the baseline stored-markup defect.
|
|
|
|
Other regression suites (explicit disposable DB guards apply):
|
|
tests.test_stage_a_containment_postgres
|
|
tests.test_stage_a_delivery_evidence_postgres
|
|
tests.test_stage_a_schedule_cancellation_postgres
|
|
tests.test_agent_zones_postgres
|
|
tests.test_agent_credential_transition_postgres
|
|
tests.test_agent_enrollment_postgres
|
|
|
|
Baseline source-test exceptions retained unchanged
|
|
-------------------------------------------------
|
|
backend/tests/test_agent_commands.py:
|
|
AgentEnrichmentIsolationSourceTests.test_legacy_config_remains_explicitly_legacy_only
|
|
requires the old minerops_require_agent_secret source string / fixed Plexus config.
|
|
The authenticated config route already changed before this candidate. Replace the
|
|
obsolete source assertion with real legacy/authenticated route tests in separate
|
|
work; do not weaken credential authentication to satisfy the string assertion.
|
|
|
|
AgentSourceContractTests.test_deployment_preserves_local_state_and_registers_established_tasks
|
|
blanket-bans the quoted agent-config.json filename in installer source, although
|
|
the existing credential-recovery installer legitimately preserves/recovers local
|
|
configuration. Follow up with behavioral installation/state-preservation tests on
|
|
Windows. Do not remove the recovery path to satisfy the blanket source assertion.
|
|
|
|
Both failures reproduce on unchanged 2d89e80 and unchanged e898f28; both method
|
|
bodies remain identical. They are not silent skips or weakened assertions here.
|
|
The external-fixture legacy-retirement PostgreSQL test class also requires its
|
|
own migration-case setup and is not certified by the discovery test runner.
|
|
|
|
Outstanding gates
|
|
-----------------
|
|
Independent security review of this exact isolated candidate is required before
|
|
any Blocker #1 PASS or commit approval. Native Windows PowerShell 5.1/SYSTEM and
|
|
fleet-scale/production-role validation remain outstanding. Unsupported older
|
|
Agent payloads fail closed and require coordinated upgrade; this is an intentional
|
|
protocol compatibility change, not transparent mixed-version support.
|
|
|
|
Stage A application containment remains a separate audited boundary. Physical
|
|
execution is disabled; device identity binding and Blocker #3 Stages B-E remain
|
|
outstanding. Preserve STAGE_A_OLD_AGENT_CUTOVER.txt: verified network fencing must
|
|
precede stopping/draining/upgrading old Agents. Cached commands cannot be revoked
|
|
by server cancellation alone. Offline/unreachable Agents stay blocked; restart,
|
|
reconnection and rollback must not restore an unsafe executor. This candidate
|
|
neither performs nor authorizes that cutover or any production deployment.
|
|
|
|
Independent-audit remediation addendum (October 16, 2026)
|
|
-------------------------------------------------------
|
|
The original candidate (99f515924dfc85fe1e1e98361688f1060329c090) failed independent
|
|
review for F1-F3 in retained legacy callers. This corrected candidate requires a
|
|
new independent audit. It does not claim Blocker #1 PASS.
|
|
|
|
Retired in Hub AND non-Hub modes, with HTTP 410 after existing authentication and
|
|
permission checks:
|
|
POST /api/miners/discover-one
|
|
POST /api/scan-ranges/{scan_range_id}/discover-now
|
|
POST /api/scanner/run-known-now
|
|
POST /api/tools/legacy-summary-refresh-one
|
|
POST /api/tools/legacy-summary-refresh-missing
|
|
All definitions/aliases of their identity matcher, discovery writer, scheduler
|
|
adapters, summary writer and global-IP helper reject before transport/database
|
|
access. Startup creates no local scanner thread; status reports retired/disabled.
|
|
Ordinary miners.update therefore cannot use discovery to replace a control board
|
|
or clear a MAC. The dedicated board workflow and immutable MAC ownership remain.
|
|
|
|
Functional implication: legacy local discovery, automatic local refresh, and
|
|
legacy one/bulk summary backfill are unavailable even outside Hub mode. Operators
|
|
must use authenticated Agent observation/review and inventory-bound Agent polling.
|
|
Legacy summary replies lack trustworthy identity; there is no retained unversioned
|
|
write or in-flight read to revalidate. No denied request mutates inventory, audit,
|
|
or telemetry. The new shared discovery architecture is preserved.
|
|
|
|
CSV remains an explicit global administrative workflow protected by miners.update.
|
|
Each row requires Miner ID, current MAC, serial, or IP plus Agent Zone ID. Zone may
|
|
come from the query/UI input or a row column; conflicting selections are rejected.
|
|
All supplied ID/MAC/serial/IP/Site/Zone evidence must agree with current inventory.
|
|
A failed MAC/serial/ID never falls back to global IP. Missing inventory is reported
|
|
unmatched; contradictory or ambiguous selection returns row-specific 409, malformed
|
|
or insufficient identification 422. A failure rolls back the entire import.
|
|
IP-only rows need a Zone even when only one global match currently exists. Exact
|
|
MAC/ID/serial selection remains usable by global administrators across Sites.
|
|
CSV updates only model/type/firmware, never ownership, IP, MAC or telemetry.
|
|
Duplicate columns are rejected rather than silently using the last value.
|
|
|
|
CSV acquires the existing discovery lock order, including the miners table lock,
|
|
before resolving any row. It holds the locks through metadata changes and one
|
|
atomic audit containing every changed inventory ID/namespace and old/new values.
|
|
Lock contention is bounded by existing discovery timeouts; large fleet throughput
|
|
and actual deployed-role privileges still require independent release validation.
|
|
Preview uses identical identification checks and rolls back. Repeated imports do
|
|
not create inventory or telemetry; each applied request retains its import audit.
|
|
No migration or containment/control policy changes are part of this remediation.
|
|
|
|
Regression entrypoint: scripts/test-discovery-security.sh now includes
|
|
tests.test_legacy_inventory_security_postgres
|
|
with registered HTTP requests, actual PostgreSQL locks/concurrent ownership moves,
|
|
actual audit INSERT denial rollback, retired helper/alias coverage, both modes,
|
|
permission boundaries, ambiguous/conflicting CSV selection, legitimate imports,
|
|
preview/replay, and retained historical snapshots. All devices are simulated or
|
|
transport-patched; the runner uses an internal network and disposable database.
|
|
Full execution evidence and integrity manifest are retained outside the candidate
|
|
in the remediation evidence directory referenced by the final report.
|