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
3 changes: 2 additions & 1 deletion .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,11 @@

IMPORTANT:

- If this PR touches `apps/desktop-main/**`, `apps/desktop-preload/**`, `libs/shared/contracts/**`, `.github/workflows/**`, or `docs/02-architecture/security-architecture.md`, the two items below MUST be checked to pass CI.
- If this PR touches `apps/desktop-main/**`, `apps/desktop-preload/**`, `libs/shared/contracts/**`, `.github/workflows/**`, or `docs/02-architecture/security-architecture.md`, the three items below MUST be checked to pass CI.

- [ ] Security review completed
- [ ] Threat model updated or N/A explained
- [ ] Confirmed no secrets/sensitive data present in committed files

### Security Notes

Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ TASK.md
.env*
!.env.example
!.env.*.example
runtime-config.env
runtime-config.json

# e2e and test artifacts
playwright-report/
Expand Down
126 changes: 50 additions & 76 deletions CURRENT-SPRINT.md
Original file line number Diff line number Diff line change
@@ -1,114 +1,88 @@
# Current Sprint

Sprint window: 2026-02-13 onward (Sprint 2)
Owner: Platform Engineering + Security + Frontend
Status: Active (core scope complete; stretch pending)
Sprint window: 2026-02-14 onward (Sprint 4)
Owner: Platform Engineering + Security
Status: Active

## Sprint Goal

Advance post-refactor hardening by improving auth lifecycle completeness, IPC integration confidence, and API contract typing safety.
Increase security and runtime determinism in privileged execution paths before additional feature expansion.

## In Scope (Committed)
## In Scope (Committed, Highest Value First)

- `BL-015` Add IdP global sign-out and token revocation flow.
- `BL-023` Expand IPC integration harness for preload-main real handler paths.
- `BL-025` Strengthen compile-time typing for API operation contracts end-to-end.

## Stretch Scope (If Capacity Allows)

- `BL-020` Complete renderer i18n migration for hardcoded user-facing strings.
- `BL-028` Enforce robust file signature validation parity across all privileged ingress paths.
- `BL-033` Centralize privileged file ingress policy across all IPC file routes.
- `BL-029` Standardize official Python runtime distribution for sidecar bundling (artifact + checksum + CI reproducibility).
- `BL-032` Standardize IPC handler failure envelope and correlation guarantees.

## Additional Delivered Work (Unplanned but Completed)
## Out of Scope (This Sprint)

- Production hardening: exclude lab routes/navigation from production bundle surface.
- Update model proof: deterministic bundled-file demo patch cycle (`v1` to `v2`) with integrity check and UI diagnostics.
- `BL-020` Renderer i18n uplift deferred while single-maintainer workflow remains.
- `BL-034` / `BL-035` i18n architecture enhancements deferred.
- `BL-038` sidecar transport ADR deferred unless risk profile changes.

## Highest Priority Follow-Up
## Explicitly Completed (Do Not Re-Scope)

- `BL-028` Enforce robust file signature validation for privileged file ingress (extension + header/magic validation with fail-closed behavior before parser execution).

## Out Of Scope (This Sprint)
- `BL-015` Add IdP global sign-out and token revocation flow.
- `BL-021` Consolidate renderer route/nav metadata into a single typed registry.
- `BL-023` Expand IPC integration harness for preload-main real handler paths.
- `BL-025` Strengthen compile-time typing for API operation contracts end-to-end.
- `BL-026` Exclude lab routes/features from production bundle surface.
- `BL-027` Provide deterministic bundled update demo patch cycle.
- `BL-030` Deterministic packaged Python sidecar runtime baseline.
- CI hardening: targeted `format:check` base fetch now uses `FETCH_HEAD` to avoid non-fast-forward failures.

- `BL-019`, `BL-022`, `BL-024`.
## Blocked / External Dependency

## Execution Plan (Coherent + Individually Testable)
- `BL-014` remains blocked by IdP vendor token audience behavior.

### Workstream A: `BL-015` auth sign-out completeness
## Execution Plan (Coherent + Testable)

1. `BL-015A` Implement explicit sign-out mode handling in main auth service.
1. `BL-033A` Shared ingress policy module

- Scope: introduce local-only vs provider/global sign-out behavior, including revocation/end-session where supported by IdP metadata/config.
- Done when: sign-out path can deterministically return local clear success and provider sign-out status without exposing secrets.
- Scope: introduce one shared policy for extension/signature/size validation and consume it from all privileged file ingress handlers.
- Proof:
- `pnpm nx run desktop-main:test`
- `pnpm nx run desktop-main:build`

2. `BL-015B` Surface sign-out mode + outcome through preload and renderer UX.

- Scope: extend preload/renderer flow to request mode and render user-safe outcomes (local cleared, provider signed out, provider not supported).
- Done when: Auth Session Lab can execute both paths and show accurate status transitions.
- Proof:
- `pnpm nx run renderer:test`
- `pnpm nx run renderer:build`

### Workstream B: `BL-023` IPC integration hardening
- `pnpm nx run desktop-preload:build`

3. `BL-023A` Add unauthorized sender integration tests with real handlers.
2. `BL-028A` Close parity gaps + fail-closed behavior

- Scope: test real handler registration path rejects wrong window/frame sender consistently across privileged channels.
- Done when: unauthorized sender rejection is covered by integration tests, not only unit-level wrapper tests.
- Scope: remove remaining route-by-route differences, enforce consistent rejection semantics, and add structured security events.
- Proof:
- `pnpm nx run desktop-main:test`
- `pnpm docs-lint`

4. `BL-023B` Add correlation-id and timeout propagation integration tests.
3. `BL-029A` Official artifact + checksum flow

- Scope: verify correlation-id continuity and timeout envelope behavior across preload invoke client and main IPC handlers.
- Done when: tests assert stable error codes/correlation behavior for timeout and malformed/failed invoke cases.
- Scope: source Python runtime from pinned official artifact, verify checksum, and prepare deterministic runtime bundle inputs.
- Proof:
- `pnpm nx run desktop-main:test`
- `pnpm nx run desktop-preload:build`

### Workstream C: `BL-025` API typing end-to-end
- `pnpm run python-runtime:prepare-local`
- `pnpm run python-runtime:assert`

5. `BL-025A` Introduce operation-to-request/response type map in contracts.
4. `BL-029B` CI reproducible runtime assembly

- Scope: define typed operation map and export helper types for operation params/result payloads.
- Done when: operations can be referenced by key with compile-time request/response inference.
- Scope: guarantee package builds use prepared runtime artifact path and pinned runtime requirements in CI.
- Proof:
- `pnpm nx run contracts:test`
- `pnpm nx run contracts:build`
- `pnpm run build-desktop-main`
- `pnpm forge:make:staging`

6. `BL-025B` Consume typed operation map in preload + main API gateway interfaces.
5. `BL-032A` IPC failure envelope normalization

- Scope: remove stringly-typed call sites in preload and gateway boundaries where operation payload types can be inferred.
- Done when: `desktop.api.invoke` and main gateway wiring compile with mapped operation types and unchanged runtime behavior.
- Scope: ensure validated handler factory normalizes validation and unexpected runtime failures into a single safe contract with correlation IDs.
- Proof:
- `pnpm nx run desktop-preload:build`
- `pnpm nx run desktop-main:test`
- `pnpm nx run renderer:build`

### Cross-cut verification gate (after each merged unit)

- `pnpm unit-test`
- `pnpm integration-test`
- `pnpm runtime:smoke`
- `pnpm nx run desktop-preload:test`

## Exit Criteria

- `BL-015`, `BL-023`, and `BL-025` merged through PR workflow with security checklist completed.
- Existing CI quality gates remain green.
- Docs updated for any changed contracts/flows.
- `BL-028` and `BL-033` moved to `Done`.
- `BL-029` moved to `Done` or `In Progress` with artifact/checksum path merged and CI proof complete.
- `BL-032` moved to `Done` with integration test coverage proving envelope consistency.
- CI remains green on PR and post-merge paths.

## Progress Log

- 2026-02-13: Sprint 1 closure confirmed (`BL-016`, `BL-017`, `BL-018` complete with cross-cut verification).
- 2026-02-13: Sprint 2 initialized with committed scope (`BL-015`, `BL-023`, `BL-025`) and stretch (`BL-020`).
- 2026-02-13: Completed `BL-015A` by introducing explicit sign-out mode (`local` or `global`) and detailed sign-out outcomes in auth contracts, desktop-main service flow, and IPC handling.
- 2026-02-13: Completed `BL-015B` baseline by propagating sign-out mode through preload and Auth Session Lab UX with separate local/global controls and provider outcome messaging.
- 2026-02-13: Completed `BL-023A` by adding real-handler unauthorized-sender integration coverage in `apps/desktop-main/src/ipc/register-ipc-handlers.spec.ts`.
- 2026-02-13: Completed `BL-023B` by adding preload invoke-client tests for malformed responses, timeout behavior, and invoke failures with correlation-id assertions (`apps/desktop-preload/src/invoke-client.spec.ts`) and wiring `desktop-preload:test` target.
- 2026-02-13: Completed `BL-025A` and `BL-025B` baseline by adding operation type maps in contracts and consuming typed operation params/result signatures in desktop API/preload invoke surfaces.
- 2026-02-13: Auth lifecycle stabilization pass completed: bounded OIDC network timeouts in main auth service, auth-page initialization now surfaces true IPC errors, token diagnostics sequencing fixed to avoid startup race, and auth-lab redirect behavior corrected to honor only explicit external `returnUrl`.
- 2026-02-13: Production hardening completed by replacing production route/shell config to exclude lab routes and lab navigation/toggle from production artifacts.
- 2026-02-13: Added bundled update demo proof flow: app startup seeds local runtime demo file to `1.0.0-demo`, update check detects bundled `1.0.1-demo`, apply action validates sha256 and overwrites local demo file, and renderer surfaces source/version/path diagnostics.
- 2026-02-13: Completed `BL-021` by adding a typed renderer route registry (`app-route-registry.ts`) that derives both `app.routes.ts` and `APP_SHELL_CONFIG.navLinks`, removing duplicated route/nav metadata while retaining production route/shell file replacements.
- 2026-02-14: Sprint reprioritized to security/runtime determinism; i18n work deferred.
- 2026-02-14: Implemented shared file token consumption and centralized ingress policy (`BL-033` / `BL-028` progress) with new handler tests and successful `desktop:dev:win` verification.
- 2026-02-14: Implemented validated handler exception normalization (`BL-032` progress): unexpected sync/async handler failures now return `IPC/HANDLER_FAILED` with correlation IDs and structured logging.
- 2026-02-14: Started `BL-029A` implementation: added pinned official Python artifact catalog + SHA256 verification flow in runtime prep/assert scripts, regenerated runtime bundle from official source, and validated `python-runtime:assert` + `build-desktop-main`.
3 changes: 2 additions & 1 deletion PR_DRAFT.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,11 @@

IMPORTANT:

- If this PR touches `apps/desktop-main/**`, `apps/desktop-preload/**`, `libs/shared/contracts/**`, `.github/workflows/**`, or `docs/02-architecture/security-architecture.md`, the two items below MUST be checked to pass CI.
- If this PR touches `apps/desktop-main/**`, `apps/desktop-preload/**`, `libs/shared/contracts/**`, `.github/workflows/**`, or `docs/02-architecture/security-architecture.md`, the three items below MUST be checked to pass CI.

- [x] Security review completed
- [x] Threat model updated or N/A explained
- [x] Confirmed no secrets/sensitive data present in committed files

### Security Notes

Expand Down
68 changes: 63 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,60 @@ Token persistence behavior:
- If `keytar` native binding is missing, run:
- `pnpm native:rebuild:keytar`

### Packaged Runtime Config File (Windows)

Packaged builds do not read `.env.local` automatically.
Instead, place one runtime config file at:

- `%APPDATA%\Angulectron\config\runtime-config.env`
- or `%APPDATA%\Angulectron\config\runtime-config.json`

Supported keys (allowlisted):

- `OIDC_ISSUER`
- `OIDC_CLIENT_ID`
- `OIDC_REDIRECT_URI`
- `OIDC_SCOPES` (must include `openid`)

Optional:

- `OIDC_AUDIENCE`
- `OIDC_ALLOWED_SIGNOUT_ORIGINS` (space/comma separated allowlist for global sign-out redirects)
- `API_SECURE_ENDPOINT_URL_TEMPLATE`
- `API_SECURE_ENDPOINT_CLAIM_MAP`

Example `runtime-config.env`:

```env
OIDC_ISSUER=https://your-issuer.example.com
OIDC_CLIENT_ID=your-client-id
OIDC_REDIRECT_URI=http://127.0.0.1:42813/callback
OIDC_SCOPES=openid profile email offline_access
OIDC_AUDIENCE=api.your-domain.example
API_SECURE_ENDPOINT_URL_TEMPLATE=https://api.your-domain.example/users/{{user_id}}/portfolio
API_SECURE_ENDPOINT_CLAIM_MAP={"user_id":"sub"}
```

Example `runtime-config.json`:

```json
{
"OIDC_ISSUER": "https://your-issuer.example.com",
"OIDC_CLIENT_ID": "your-client-id",
"OIDC_REDIRECT_URI": "http://127.0.0.1:42813/callback",
"OIDC_SCOPES": "openid profile email offline_access",
"OIDC_AUDIENCE": "api.your-domain.example",
"API_SECURE_ENDPOINT_URL_TEMPLATE": "https://api.your-domain.example/users/{{user_id}}/portfolio",
"API_SECURE_ENDPOINT_CLAIM_MAP": "{\"user_id\":\"sub\"}"
}
```

Notes:

- Restart the app after changing runtime config files.
- Process environment variables still take precedence over file values if both are set.
- Do not put client secrets into renderer or checked-in files; this flow assumes desktop public-client PKCE.

## Bring Your Own Secure API Endpoint

The `call.secure-endpoint` API operation is endpoint-configurable and does not rely on a hardcoded private URL.
Expand Down Expand Up @@ -212,17 +266,21 @@ How to run/verify:

Deterministic packaged runtime (staging/production):

- Provide a local bundled runtime payload at:
- Runtime source is pinned via official artifact catalog:
- `tools/python-runtime-artifacts.json`
- Prepare runtime payload from pinned artifact:
- `pnpm run python-runtime:prepare-local`
- Prepared payload is written to:
- `build/python-runtime/<platform>-<arch>/`
- example: `build/python-runtime/win32-x64/`
- Pin sidecar dependencies in:
- `apps/desktop-main/python-sidecar/requirements-runtime.txt`
- Fast local bootstrap from your current Python install:
- `pnpm run python-runtime:prepare-local`
- Add `manifest.json` with `executableRelativePath` (see `build/python-runtime/README.md`).
- Run validation:
- `pnpm run python-runtime:assert`
- assertion verifies interpreter exists and imports `fitz` when PyMuPDF is declared in manifest
- assertion verifies interpreter exists, source policy is `official-artifact` by default, and imports `fitz` when PyMuPDF is declared in manifest
- Optional local emergency override:
- `PYTHON_RUNTIME_SOURCE_DIR=...` to provide a local source directory
- `PYTHON_RUNTIME_ALLOW_UNOFFICIAL_SOURCE=1` if assertion must permit non-official source manifests
- Runtime payload is copied into desktop build artifacts by:
- `pnpm run build-desktop-main`
- `pnpm run forge:make:staging`
Expand Down
53 changes: 53 additions & 0 deletions apps/desktop-main/src/ipc/consume-selected-file-token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import path from 'node:path';
import { BrowserWindow, type IpcMainInvokeEvent } from 'electron';
import {
asFailure,
asSuccess,
type DesktopResult,
} from '@electron-foundation/contracts';
import type { MainIpcContext } from './handler-context';

export type ConsumedSelectedFileToken = {
filePath: string;
fileName: string;
};

export const consumeSelectedFileToken = (
event: IpcMainInvokeEvent,
fileToken: string,
context: MainIpcContext,
correlationId?: string,
): DesktopResult<ConsumedSelectedFileToken> => {
const selected = context.selectedFileTokens.get(fileToken);
if (!selected || selected.expiresAt <= Date.now()) {
context.selectedFileTokens.delete(fileToken);
return asFailure(
'FS/INVALID_TOKEN',
'The selected file token is invalid or expired.',
undefined,
false,
correlationId,
);
}

const senderWindowId = BrowserWindow.fromWebContents(event.sender)?.id;
if (senderWindowId !== selected.windowId) {
context.selectedFileTokens.delete(fileToken);
return asFailure(
'FS/INVALID_TOKEN_SCOPE',
'Selected file token was issued for a different window.',
{
senderWindowId: senderWindowId ?? null,
tokenWindowId: selected.windowId,
},
false,
correlationId,
);
}

context.selectedFileTokens.delete(fileToken);
return asSuccess({
filePath: selected.filePath,
fileName: path.basename(selected.filePath),
});
};
Loading