fix: harden identity, live access, drafts and setup (review only)

Source-reviewed hardening changes and regression tests. UNBUILT / UNTESTED: not a deployable release. Go formatting, compilation, automated regression tests, full frontend validation and staging acceptance remain required. See docs/hardening/VALIDATION.md. Main remains the original clean snapshot; no upstream Git history imported.
This commit is contained in:
2026-10-16 12:15:20 +00:00
parent e57610a27c
commit 63e0457886
86 changed files with 5174 additions and 1929 deletions

View File

@@ -0,0 +1,109 @@
# Customer identity and live-access upgrade
These are operator instructions for a reviewed staging rollout, not commands
executed by this change. Back up data and verify the exact schema/engine before
any production upgrade. Existing customer and conversation records are retained.
## Additive schema and issuer boundaries
`CustomerIdentity` adds:
- `identity_scope`: indexed varchar(80), default empty for legacy rows.
- `scoped_key`: nullable varchar(64) with a unique index. Legacy rows remain NULL.
The normal GORM startup migration adds these definitions. No destructive index
replacement, automatic legacy merge, or data deletion is supplied. Test this
additive change on a backup with the deployment's actual SQLite/MySQL version.
A public identity scope is `v1:<jwt|hmac|guest>:SHA256(trimmed channel code)`.
The server derives it; clients cannot select an issuer or inject this scope.
The unique claim key is SHA-256 of the JSON array containing scope, external
source and trimmed external ID. Use the implementation's
`ScopedCustomerIdentityKey` helper rather than a hand-built delimiter format.
New public registrations insert the claim in the same customer transaction.
A competing claim rolls back rather than creating two authorized customers.
WebSocket routing uses canonical customer ID and channel record ID. Incoming
subscriptions and outgoing recipients are checked against current ownership.
Raw external IDs are not cross-channel or cross-trust routing keys.
## Existing sessions and history
Old customer-session JWTs without the new scope are rejected. The updated client
must exchange a fresh token. Until legacy mappings are explicitly reconciled,
a newly authenticated scoped identity can receive a separate customer record,
not the old account's history. The original records remain available to
appropriately authorized operators.
Before enabling traffic:
1. Back up data; stop exchanges/writes for the affected scope during reconciliation.
2. Run the read-only `scripts/audit-customer-identity-scopes.sql` reports against an
approved staging copy after the additive columns exist. Keep report output
private: it contains customer identifiers.
3. Establish channel and issuer ownership using trustworthy host-side evidence.
Legacy `user` rows alone cannot distinguish JWT from HMAC provenance.
4. For a proven unique mapping, review a transaction setting both its scope and
computed claim key while retaining its customer ID and conversation history.
Check all associated conversation channels before making a change.
5. Stop on duplicate, inactive, multi-channel or mixed-trust mappings. Never take
the first row or merge customers solely because external IDs match. If a new
scoped record already exists, reconcile both histories explicitly.
6. Reissue customer tokens and verify authorized history, isolation, and rollback
behavior on the staging copy before resuming traffic.
A channel reassigned to a different identity issuer needs a new channel code.
Ordinary signing-key rotation intentionally retains the same issuer scope.
Use a distinct host JWT signing secret for each independently trusted channel.
The existing host JWT contract validates the channel's secret and expiry, but
does not require a channel audience claim. Reusing a signing key across unrelated
channels lets the holder assert an identity to either channel; scoped storage
is not a defense against a shared issuer key. HMAC proofs bind the channel code
inside the signed payload. No mandatory JWT audience rollout is introduced here.
Legacy duplicate repair remains an operator-reviewed migration, not an automatic
startup side effect. The nullable unique claim allows dirty legacy rows to
remain isolated without blocking new correctly scoped registrations.
## Anonymous and authenticated-only channels
Unsigned exchange issues a new random server-owned anonymous identity. Supplying
an old guest external ID is not proof of ownership. Normal resume and refresh
use the signed customer-session token. Near-expiry, still-valid credentials are
renewed through the rate-limited `POST /api/customer/session_refresh` endpoint,
which authenticates the existing session and never creates an anonymous identity.
A refresh failure is not silently replaced with a fresh guest. Losing or expiring
that credential does not authorize reclaiming history by an external ID.
An optional boolean `requireSignedIdentity` in a web channel's `configJson`
rejects unsigned exchanges and existing guest sessions server-side. Default
false retains anonymous channels. The SDK's `requireUserToken` is not a substitute
for this policy.
Use the existing authenticated channel GET and
`POST /api/dashboard/channel/update` flow with `channel.update` permission,
preserving all unrelated configuration and required fields. No dedicated new UI
toggle is provided. Older editors which omit this property preserve a configured
true value; an explicit JSON false is needed to disable it. Null and nonboolean
values are rejected. This source change does not configure a live channel.
## Staff and customer live-session revocation
Staff sockets bind their original login-session ID and absolute expiry, without
serializing that internal metadata into API responses. Fresh session, account,
role and permission state is checked for incoming operations, event fanout,
actual queued writes and idle checks. Scope changes terminate the connection;
reconnection must obtain fresh authority. Idle checks run every five seconds;
data operations revalidate immediately rather than using a five-second cache.
Notification sockets cannot subscribe to conversation topics. Dashboard
conversation sockets require conversation-view permission. Customer connections
recheck token expiry, channel policy, customer state and exact identity mapping.
This implementation deliberately makes database reads at authorization
boundaries. Measure the cost under realistic fanout; any later caching must have
complete revocation invalidation rather than silently reintroducing stale access.
Channel configuration updates and token-secret resets compare the previously read
configuration bytes and update timestamp before writing. A stale writer receives
a conflict instead of silently overwriting a newly enabled signed-identity policy.
Reload the channel and reapply the intended change after reviewing that conflict.

63
docs/hardening/README.md Normal file
View File

@@ -0,0 +1,63 @@
# Security and reliability hardening
This independent fork starts from the current source tree of
[MonoVerseNS/agent-desk-ng](https://github.com/MonoVerseNS/agent-desk-ng), revision
`b06954b0a495389cbb5194e0d388342eabe60636`. It does not import the old Git history.
The upstream Apache-2.0 license and notices remain applicable. This document
records changes made by this fork; it is not a claim of production certification.
## Release gate
The hardening changes must be reviewed and validated before deployment. Source
inspection, a compiler check, a passing isolated test, and live acceptance are
separate kinds of evidence. See `VALIDATION.md` for the actual checks performed.
No production service, database, credentials, or external deployment is changed
by committing this source code.
## Changes being integrated
- Separate guest, signed-user, issuer and channel identities. Use canonical
customer/channel WebSocket topics and recheck recipients instead of treating
a topic subscription as authorization.
- Apply conversation-view permission to staff WebSockets; keep notifications
separate. Revalidate session expiry, revocation, account state and permissions
on long-lived connections. Disabled roles must no longer grant permissions.
- Keep drafts and asynchronous responses bound to their identity and dialogue.
Preserve drafts on failed sends, protect newly typed revisions, and prevent
duplicate concurrent sends. Profile refresh and delayed API errors must not
restore or expire the wrong signed-in account.
- Turn setup repair into an explicit preview/apply operation. Never resurrect
deleted configuration or silently introduce continuous working hours. Apply
reviewed writes transactionally and dispatch only after commit.
- Make empty setup, initial retry and standalone chat dismissal truthful and
recoverable; remove bearer-token-bearing WebSocket URLs from browser logs.
## Upgrade precautions
Customer identity scoping is an additive schema and token-contract change, not
only a frontend patch. Existing unscoped customer tokens will be rejected.
Legacy identities and conversations must be inventoried and reconciled with
trusted issuer/channel provenance before expecting their old history in new
customer sessions. No automatic customer merge or destructive cleanup is
appropriate. See `CUSTOMER_IDENTITY_UPGRADE.md` before any deployment.
New anonymous sessions are server-issued. Knowing an old anonymous external ID
is not sufficient to reclaim its history. Preserve the signed customer-session
credential for normal resume; loss or expiry can require a new anonymous session.
The setup quick-fix API now requires preview and explicit apply. Older callers
must not treat a successful preview response as proof that configuration changed.
See [the setup contract](SETUP_PREVIEW.md) before integrating custom callers.
## Verification priorities
1. Guest/user/channel collisions, unauthorized staff topics, revoked/expired
sockets, disabled roles and concurrent identity registration.
2. Wrong-recipient drafts, failed and duplicate sends, delayed responses after
switching conversations or accounts, and logout during profile refresh.
3. Preview/apply tampering, permission denial, rollback, deleted configuration,
off-hours schedules, empty-channel simulation and dispatch failure reporting.
4. Desktop and narrow-screen interaction checks in Russian, English and Chinese.
Use temporary, isolated data for checks. Do not run migration/repair commands
against a live database merely to validate the code.

View File

@@ -0,0 +1,56 @@
# Reviewed setup repairs
The existing authenticated `POST /api/dashboard/setup/quick_fix` endpoint now uses a two-phase contract. It does not write configuration unless the caller explicitly applies a current preview.
## Contract
1. Request a preview with `action` and, for an existing agent/channel/team, its `entity_id`. Omit `apply` or set it to `false`.
2. Review `changes`. Each entry names the entity, operation, and exact operational fields before and after. New linked entities are identified by their proposed names until IDs are assigned. Audit timestamps and acting-user metadata are maintained automatically.
3. Submit the same action and target with `apply: true` and the returned `preview_token`.
The response includes:
- `applied`: whether an explicit apply request completed. An empty `applied_actions` array means no configuration writes were needed.
- `changes`: the reviewed plan; an empty array is a valid no-op, not a claim that setup is complete.
- `preview_token`: a fingerprint of the acting user, action, target, proposed changes, and current setup configuration.
- `applied_actions`: committed operation/entity identifiers, empty on preview.
- `warning_codes`: manual follow-up requirements or post-commit failures.
- `readiness`: the refreshed diagnostic, omitted if refreshing failed.
Old callers that omit `apply` receive a read-only preview. Callers must not interpret any HTTP success as a successful mutation. There is deliberately no compatibility path that silently applies an unreviewed repair.
## Authorization and freshness
The handler authenticates every request. Setup checks the principal's resolved permission list, matching the normal authorization service. An `admin` or `super_admin` role name alone does not bypass missing permissions. Every planned create or field update also checks its specific permission.
Apply rebuilds the plan inside one transaction and verifies the fingerprint before writing. A stale preview is rejected and must be reviewed again. Transactional reads use separate GORM statements, preserving row locks without leaking a prior query's table or filters. Updates also compare the target's previous update timestamp. The fingerprint is a freshness/intent check, not an authentication credential or signed authorization grant.
## Deliberately narrow behavior
- Broad setup only creates missing defaults. It never rewrites existing configuration, moves existing operators to another team, expands an agent's team pool, or rebinds existing channel routes.
- Deleted records are not update candidates and are never restored. Disabled records and zero-rollout settings are preserved. A targeted “enable my profile” action can enable the authenticated user's non-deleted profile only when the preview explicitly shows the change and the required permissions are present.
- A targeted handoff change affects only the named eligible agent. Missing teams, route choices, disabled configurations, and rollout changes must be chosen in their existing editors.
- Quick fixes never create or modify shifts. Missing schedules and off-hours schedules are distinct diagnostics. Working hours must be entered explicitly in the existing schedule editor.
- A newly created default agent is unpublished. Setup does not publish agents. A default channel can be created only after an eligible agent has a published revision, matching the normal channel editor's publication guard. Readiness reports unpublished agents as incomplete.
Therefore a broad repair is not a promise of a production-ready routing path. Publishing, deliberate routing choices, and explicit schedules may still be required.
## Transactions and dispatch
All configuration writes in an apply operation share one transaction. A failed write rolls back the entire repair. Pending dispatch begins only after the configuration transaction commits.
A dispatch failure produces `dispatch_failed`; it does not undo committed changes or return an error implying that the configuration write should be retried. A diagnostic refresh failure similarly produces `readiness_refresh_failed`. The UI displays these warnings and requires a fresh preview for a subsequent repair.
The pending-queue query now reports database read errors and applies its existing batch limit in SQL. Its existing newest-first priority and periodic retry semantics are unchanged. This repair does not redesign queue fairness.
## Simulation
The simulator is read-only and sends no customer message. `candidate_operators` is always an array, including missing-channel responses; the client also normalizes legacy null values.
`bypass_ai: true` is explicitly rejected because the service does not implement AI bypass. AI-only paths remain blocked. AI-first/unknown service modes and partial channel rollout are reported as unverified, never as successful operator delivery. Only an eligible, published, full-channel-rollout, human-only route can produce a positive current-eligibility result. That result is a point-in-time diagnostic, not a delivery guarantee or an end-to-end AI execution test.
## Validation status
Focused regression tests cover preview-only behavior, explicit apply, stale previews, atomic rollback, deleted/disabled/zero-rollout preservation, named-target scope, off-hours versus missing schedules, role-name permission bypass, status-only permissions, empty arrays, simulation limitations, publication requirements, and post-commit failures.
At implementation handoff these tests have been written but not executed. Go compilation, formatting, dependency installation, application execution, database integration, and browser verification were not performed in this source-review environment. TypeScript source parsing, locale-key parity, and whitespace/diff checks have been performed. Those source checks are not substitutes for the deferred tests.

View File

@@ -0,0 +1,47 @@
# Validation status: review branch, not a release
This source change is published for review. Do not deploy it as a verified
security release until the checks below have been completed.
## Completed source checks
- Independent source review of customer/issuer isolation, staff permissions,
long-lived authorization, channel policy concurrency and session refresh.
- Cross-review of editor draft isolation and customer chat lifecycle boundaries.
- Setup preview/apply permission, transaction and target review.
- Syntax parsing of changed TypeScript/TSX and JavaScript regression sources.
- JSON/YAML parsing, matching new locale keys, and Git whitespace checks.
These checks are not a replacement for compilation or executing the tests.
## Not yet validated
- Go formatting and compilation: the Go toolchain was unavailable during this
source-review pass. Run gofmt on all changed Go files before merging.
- Automated regression tests: authored but not executed.
- Full frontend typecheck, lint and build with declared dependencies: not run.
A limited attempt using unrelated available dependencies was incomplete due
to missing project packages; it is not a successful project typecheck.
- Race detector, actual MySQL CAS/unique-index behavior, and staging additive
schema upgrade/reconciliation: not run.
- Application/browser acceptance on desktop and narrow screens: not run.
- Production migration, service startup and deployment: not performed.
## Checks required before merge or release
Use the repository's declared Go and frontend package versions, isolated temporary
data, and mocked/loopback external services. Inspect test initialization first;
never point regression tests at a live database or real accounts.
1. Format all changed Go files and compile the relevant packages.
2. Run openidentity and backend security/setup tests, including concurrent identity
registration, the actual WebSocket pumps, channel CAS and transaction rollback.
Then run the package suites and race checks where supported.
3. Run the frontend typecheck, lint and build with installed locked dependencies.
4. Run the focused Node tests for customer sessions/store/realtime, message drafts,
account isolation, setup preview and dismissal navigation.
5. Validate SQLite and MySQL schema upgrades and uniqueness/CAS behavior on backups.
6. Exercise the modified UI in Russian, English and Chinese, including failures,
account/conversation switches, pending sends and expired sessions.
Do not treat an unexecuted regression test as proof that a defect is fixed.