Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion desktop/SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,42 @@ Typed adapters separate liveness, readiness, identity and capabilities. Native c

Session observation commands require the same trusted main-window boundary and work independently of connection state. Rust projects only fixed operation/unit/outcome enums, elapsed milliseconds, UTC timestamps, counts, HTTP status and existing safe error codes. It never serializes connection or write state for export; content, queries, credentials, endpoints, private paths and target identities never enter the collector. The preview serializes only that projection. Retention/reset and process-lifetime loss metadata expose incomplete coverage; nonblocking recording cannot change business results or suppress cancellation. HTTP response success and native write certainty are separate measurements. Installer/Ops and local diagnostic output are outside collection. The [session observation contract](../docs/en/development/desktop-session-observations.md) defines the complete field allowlist and coverage.

Error responses are enum codes. Transport exception text, endpoint URLs, bodies and credentials are never returned or logged. No product logging, telemetry, crash uploader or persistent business cache is configured. The app does not read databases, start a Server or inspect process environments. Explicit local diagnostics verify a pinned absolute executable and digest, then run one of two native-defined diagnostic commands. Registration requires no version, and legacy version fields are ignored. Diagnostic responses must match the supported output schema. A subsequent fixed `--version` query collects bounded display metadata; failure, unsupported output or an unavailable version does not reject an otherwise valid diagnostic result. Each invocation has a combined 256 KiB output budget and a deadline. Windows helpers enter an owned kill-on-close Job before resuming. macOS runs a read-only snapshot of the digest-verified launcher in a private process group, which is terminated after completion, cancellation or timeout; raw output is projected to allowlisted status fields and discarded. Normal application exit closes diagnostic admission and waits for active worker process cleanup before Tauri exits. A local CLI pin does not authenticate its publisher or freeze its Python dependencies; the configured installation must be trusted.
Error responses are enum codes. Transport exception text, endpoint URLs, bodies and credentials are never returned or logged. No product logging, telemetry, crash uploader or persistent business cache is configured. The app does not read databases or inspect process environments. Authorized local service changes use the independent Ops boundary described below. Explicit local diagnostics verify a pinned absolute executable and digest, then run one of two native-defined diagnostic commands. Registration requires no version, and legacy version fields are ignored. Diagnostic responses must match the supported output schema. A subsequent fixed `--version` query collects bounded display metadata; failure, unsupported output or an unavailable version does not reject an otherwise valid diagnostic result. Each invocation has a combined 256 KiB output budget and a deadline. Windows helpers enter an owned kill-on-close Job before resuming. macOS runs a read-only snapshot of the digest-verified launcher in a private process group, which is terminated after completion, cancellation or timeout; raw output is projected to allowlisted status fields and discarded. Normal application exit closes diagnostic admission and waits for active worker process cleanup before Tauri exits. A local CLI pin does not authenticate its publisher or freeze its Python dependencies; the configured installation must be trusted.

Tests cover actual TLS fixtures and adversarial HTTP responses, shared loopback policy, mocked Tauri permission resolution with the real capability configuration, navigation rejection, unavailable vault behavior and the explicit session path. The opt-in Windows vault probe separately verifies persistence across processes. See the [validation guide](VALIDATION.md) for packaged checks, CI artifacts and remaining platform qualification.

Framework references: [Tauri capabilities](https://v2.tauri.app/security/capabilities/), [Windows installer options](https://v2.tauri.app/distribute/windows-installer/).

## Independent Ops boundary

Only the packaged main window may use the seven fixed Ops commands and the retained-reference reader. Inputs are typed
operation/options or native-issued approval/reference IDs; executable, binding paths, URLs, service names, and complete
plans cannot cross renderer IPC. The main window receives validated progress through `ops-progress`; no global
renderer event-emission permission is granted. Preflight retains the exact plan for ten minutes and apply/resume consume
the approval once against the same distribution and target binding.

Packaged `ops-provision.json` pins the independent bundle manifest and optional managed catalog. Every invocation
verifies the complete bundle inventory and contents, including safe runtime-local file aliases. Missing provision is
unavailable; a candidate manifest cannot authorize itself and system/Server Python is never a fallback. Immutable
digest-named private binding copies prevent an in-flight helper from observing a subsequently selected binding.
The fixed stable root is current-user `PowerContext/Ops` under local application data, independent of Desktop removal.
Native code writes private files atomically (0600/0700 on POSIX; current-user protected ACLs on Windows), rejects path
links/reparse points, and validates ownership/protection on existing control files. This same-user bridge is not a
privilege boundary against another process running as that user.

The native reference file stores only operation IDs and binding fingerprints before dispatch. Ops journals and locks
remain the sole recovery authority. Four ordinary and two reserved status/cancel helpers bound native concurrency;
timed-out children retain their slot until exit. Each invocation bounds JSONL messages to 64 KiB, aggregate output to
1 MiB and 128 messages, with 30-second passive-read, 180-second preflight and 30-minute mutation deadlines. Stderr is discarded. Invalid schema,
identity, sequence or enums never reach the renderer; valid structured nonzero exits preserve unhealthy outcomes.
Nonblocking drains finish at helper exit even when a descendant holds a pipe open.

Timeout/lost response/Quit retain the operation reference and report unknown. Normal Quit closes admission and waits
at most two seconds without signalling Ops, Server or manager processes. Pipe closure can produce a durable safe-boundary
progress failure; accepted work may continue. Cancellation calls Ops and does not imply rollback. Recovery reads the
existing operation, then requires a newly approved plan; no automatic replay or parallel outcome journal exists.

See [Local Server Ops](../docs/en/docs/operate/local-server-ops.md#desktop-native-bridge) for the exact optional Tauri
resource configuration, producer pins, stable paths and recovery API. The bridge does not ship production resources or
an installation wizard. Real service mutations, signing/notarization and clean-machine acceptance require separate
platform evidence.
39 changes: 39 additions & 0 deletions desktop/VALIDATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -631,3 +631,42 @@ and checks digest rejection and allowlisted projection; it does not qualify a re
Use a disposable Server and Scope for installed save/search/read/copy checks. Unit tests do not prove
those installed workflows. Validate actual Chinese IME, accessibility, Gatekeeper, signing and
notarization independently before distributing outside the internal preview.

## Independent Ops bridge checks

The native Ops tests exercise streaming with real child processes, malformed/oversized output, deadlines, inherited
pipe handles, retained references, one-shot approvals, cancellation, resume, binding integrity and Quit continuation.
Synthetic helpers establish these bridge boundaries; they do not qualify native service-manager mutations.

Two opt-in macOS tests add distinct read-only integration evidence. Prepare the checkout's `ops` dependencies and run
from the repository root:

```sh
uv sync --locked --extra ops --extra cli
POWERCONTEXT_OPS_TEST_PYTHON="$PWD/.venv/bin/python" \
cargo test --locked --manifest-path desktop/src-tauri/Cargo.toml \
--lib real_current_ops_inspect_and_preflight_with_missing_server_runtime -- --ignored
```

This test runs the current Python Ops entry in a subprocess with a disposable home, binding and missing Server Python.
An unavailable native-manager ownership observation must remain a rejected preflight, not become successful cleanup.
The following test instead copies a complete independently built Ops bundle into a disposable native bridge layout,
uses the separately supplied approved manifest digest as its packaged pin, and checks inspection/preflight and dependency
tampering through the actual native verifier and process runner:

```sh
POWERCONTEXT_OPS_TEST_BUNDLE=/absolute/approved/ops \
POWERCONTEXT_OPS_TEST_BUNDLE_SHA256="<approved-bundle-json-sha256>" \
cargo test --locked --manifest-path desktop/src-tauri/Cargo.toml \
--lib actual_independent_bundle_runs_through_native_trust_and_read_only_bridge -- --ignored
```

Neither command applies a service operation or touches the real user's Server configuration/data. Supply a trusted
producer artifact and an independently approved digest; calculating a pin from an arbitrary candidate does not establish
release trust. These checks do not establish signing/notarization, Windows Task Scheduler or macOS LaunchAgent mutation,
packaged provisioning, or clean-machine acceptance. The optional packaging hook and stable independent helper location
are documented in [Local Server Ops](../docs/en/docs/operate/local-server-ops.md#provisioning-the-bridge).

上述两项可选检查分别验证源码 Ops 子进程和完整独立分发包与原生桥接的只读兼容性。测试使用临时 home、绑定和状态目录,
不执行服务修改;真实服务管理器归属无法确认时保留预检拒绝。分发包测试要求另行提供已批准的清单摘要,并检查依赖被改动时
原生端拒绝启动。这些证据不代替签名/公证、真实服务修改、随包配置或干净机器验收。
4 changes: 3 additions & 1 deletion desktop/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,13 @@ tempfile = "=3.27.0"
tauri-plugin-single-instance = "=2.4.2"

[target.'cfg(windows)'.dependencies]
windows-sys = { version = "=0.61.2", features = ["Win32_Foundation", "Win32_Security", "Win32_System_Threading", "Win32_System_JobObjects", "Win32_System_Pipes", "Win32_Storage_FileSystem"] }
windows-sys = { version = "=0.61.2", features = ["Win32_Foundation", "Win32_Security", "Win32_Security_Authorization", "Win32_System_Threading", "Win32_System_JobObjects", "Win32_System_Pipes", "Win32_Storage_FileSystem"] }
keyring = { version = "=3.6.3", default-features = false, features = ["windows-native"] }

[target.'cfg(target_os = "macos")'.dependencies]
keyring = { version = "=3.6.3", default-features = false, features = ["apple-native"] }

[target.'cfg(unix)'.dependencies]
libc = "=0.2.189"

[dev-dependencies]
Expand Down
8 changes: 8 additions & 0 deletions desktop/src-tauri/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ fn main() {
tauri_build::AppManifest::new().commands(&[
"foundation_info",
"local_diagnostics",
"ops_references",
"ops_inspect",
"ops_config_validate",
"ops_preflight",
"ops_apply",
"ops_operation_status",
"ops_cancel",
"ops_resume",
"remember_memory",
"search_memory",
"atomic_memory_entry",
Expand Down
14 changes: 12 additions & 2 deletions desktop/src-tauri/capabilities/main.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "main",
"description": "Only the packaged main window can use typed connection, Scope and local diagnostic commands.",
"description": "Only the packaged main window can use typed connection, Scope, diagnostics and constrained Ops commands.",
"windows": [
"main"
],
Expand All @@ -24,6 +24,16 @@
"allow-select-scope",
"allow-session-summary",
"allow-reset-session-observations",
"allow-preview-session-diagnostics"
"allow-preview-session-diagnostics",
"allow-ops-references",
"allow-ops-inspect",
"allow-ops-config-validate",
"allow-ops-preflight",
"allow-ops-apply",
"allow-ops-operation-status",
"allow-ops-cancel",
"allow-ops-resume",
"core:event:allow-listen",
"core:event:allow-unlisten"
]
}
50 changes: 50 additions & 0 deletions desktop/src-tauri/examples/export_ipc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ use powercontext_desktop::{
diagnostics::{DiagnosticItem, DiagnosticKind, DiagnosticReport, HostDiagnostic},
error::SafeError,
ipc::FoundationInfo,
ops_bridge::*,
session_observations::{
ObservationOutcome, ObservationUnit, ObservedOperation, OperationSummary, OutcomeCounts,
SessionCoverage, SessionObservation, SessionSummary,
Expand Down Expand Up @@ -58,6 +59,55 @@ fn main() {
CredentialWriteReceipt::decl(&config)
);
for declaration in [
OpsOperation::decl(&config),
OpsOutcome::decl(&config),
OpsBridgeError::decl(&config),
OpsErrorCode::decl(&config),
OpsAction::decl(&config),
OpsOptions::decl(&config),
OpsResumeReference::decl(&config),
OpsPreflightInput::decl(&config),
OpsApprovalInput::decl(&config),
OpsStatusInput::decl(&config),
OpsCancelInput::decl(&config),
OpsOperationReference::decl(&config),
OpsReferenceBindingStatus::decl(&config),
OpsReferences::decl(&config),
OpsInspectionReply::decl(&config),
OpsValidationReply::decl(&config),
OpsConfigurationState::decl(&config),
OpsPlanApproval::decl(&config),
OpsPreflightReply::decl(&config),
OpsTargetSummary::decl(&config),
OpsPlanEffects::decl(&config),
OpsInstallationSummary::decl(&config),
OpsInstallationClassification::decl(&config),
OpsInstallationResources::decl(&config),
OpsDatabaseClassification::decl(&config),
OpsComponents::decl(&config),
OpsProgress::decl(&config),
OpsOperationReply::decl(&config),
OpsOperationStatus::decl(&config),
OpsExecutor::decl(&config),
OpsObservation::decl(&config),
OpsAttemptSummary::decl(&config),
OpsStoredResult::decl(&config),
OpsMaintenancePhase::decl(&config),
OpsRuntimeResources::decl(&config),
OpsConnectionCompatibility::decl(&config),
OpsSupportState::decl(&config),
OpsRegistrationState::decl(&config),
OpsDefinitionState::decl(&config),
OpsManagerOwnership::decl(&config),
OpsManagerState::decl(&config),
OpsLivenessState::decl(&config),
OpsReadinessState::decl(&config),
OpsServicePhase::decl(&config),
OpsAttemptPhase::decl(&config),
OpsExecutionState::decl(&config),
OpsExecutorObservation::decl(&config),
OpsObservationState::decl(&config),
OpsRecoveryAction::decl(&config),
DiagnosticKind::decl(&config),
DiagnosticItem::decl(&config),
HostDiagnostic::decl(&config),
Expand Down
Loading
Loading