Skip to content
Draft
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
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ The product intentionally departs from stricter baselines in a few places. Each
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| **AR-001** | **Provisioning SSH** (cloud-init templates) | **`PermitRootLogin yes`** and **root** `authorized_keys` installed via provisioning scripts (`libs/domains/framework/backend/feature-billing-manager/.../agent-controller.utils.ts`, `agent-manager.utils.ts`) | SSH **key-based** access; **password authentication disabled** in generated `sshd` config; deployers should restrict network access, rotate keys, and monitor instances | **2027-05-06**, or sooner if cloud-init/SSH templates change materially |
| **AR-002** | **Desktop app** (`native-agent-console`) | **No OS-trusted code signing** and **no in-app auto-update** in the Electron Forge pipeline (`apps/native-agent-console/forge.config.js`) | Release artifacts include **`SHA256SUMS`** and **`integrity-manifest.json`** produced by [`tools/release-integrity`](./tools/release-integrity/README.md); CI/release pipelines **generate and verify** these manifests. Users should verify checksums after download. The web browser remains the primary client; the native build is a secondary channel. | **2027-05-06**, or sooner if desktop becomes the primary distribution path |
| **AR-003** | **Web frontends** (`frontend-*`) | **Content Security Policy** allows **`'unsafe-inline'`** and **`'unsafe-eval'`** so **Monaco Editor** and related tooling work; policy is sent as **`Content-Security-Policy-Report-Only`** by default (violations are reported, not blocked) | Set **`CSP_ENFORCE=true`** only in environments where compatibility is validated. Implementation: `libs/domains/framework/frontend/util-express-server/src/lib/security-headers.ts`. Hardening path: stricter CSP with a validated Monaco/worker/nonce strategy. | **2027-05-06**, or sooner if CSP middleware changes materially |
| **AR-003** | **Web frontends** (`frontend-*`) | **Content Security Policy** allows **`'unsafe-inline'`** and **`'unsafe-eval'`** so **Monaco Editor** and related tooling work; policy is sent as **`Content-Security-Policy-Report-Only`** by default (violations are reported, not blocked) | Set **`CSP_ENFORCE=true`** only in environments where compatibility is validated. Implementation: `libs/domains/framework/frontend/util-http-context/src/lib/security-headers.ts`. Hardening path: stricter CSP with a validated Monaco/worker/nonce strategy. | **2027-05-06**, or sooner if CSP middleware changes materially |
| **AR-004** | **Backend authentication mode resolution** (`getAuthenticationMethod` in `libs/domains/identity/backend/util-auth/src/lib/hybrid-auth.guard.ts`) | We do **not** require **`AUTHENTICATION_METHOD`** to always be set. When it is unset: if **`STATIC_API_KEY`** is set β†’ **api-key** mode; otherwise β†’ **keycloak** (OIDC / **Keycloak** integration with the deployer’s IdP). **Protected routes are not anonymous**β€”Keycloak- or users-mode guards still enforce authentication per configuration. | **Default `keycloak`** favors the most integrated, enterprise-typical option (customer IdP). For **api-key** or **users** deployments, set **`AUTHENTICATION_METHOD`** explicitly and treat **`STATIC_API_KEY`** as a high-value secret (rotation, least exposure). | **2027-05-06**, or sooner if hybrid auth resolution changes materially |
| **AR-005** | **Desktop window open policy** (`native-agent-console`) | **`setWindowOpenHandler`** in `apps/native-agent-console/src/main.ts` uses **`action: 'allow'`** so `window.open` / `target=_blank` can open new Electron windows with inherited `webPreferences`. | Compared with a full browser, phishing and popup abuse risk is **lower**: there is **no address bar (omnibox)** and **users cannot install browser extensions/plugins**. **Sandbox** and **contextIsolation** remain enabled. Revisit if the shell gains untrusted browsing or URL-entry UX. | **2027-05-06**, or sooner if main-process window policy changes materially |

Expand Down
6 changes: 6 additions & 0 deletions apps/backend-agent-controller/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@

import { assertProductionClientEndpointAllowlistConfigured } from '@forepath/framework/backend/feature-agent-controller';
import {
applyNestExpressTrustProxyAndFingerprintAsync,
CorrelationAwareConsoleLogger,
CorrelationAwareSocketIoAdapter,
createCorrelationIdMiddleware,
registerAxiosCorrelationIdPropagation,
useNestApiSecurityHeadersMiddleware,
} from '@forepath/framework/backend/util-http-context';
import { createOriginAllowlistMiddleware } from '@forepath/identity/backend';
import { assertProductionEncryptionKeyOrExit } from '@forepath/shared/backend';
Expand All @@ -32,13 +34,17 @@ async function bootstrap() {
const app = await NestFactory.create(AppModule, {
logger: appLogger,
});

await applyNestExpressTrustProxyAndFingerprintAsync(app);

const httpLogger = new Logger('HTTP');

app.use(
createCorrelationIdMiddleware({
log: (message: string) => httpLogger.log(message),
}),
);
useNestApiSecurityHeadersMiddleware(app);
app.use(createOriginAllowlistMiddleware(new Logger('OriginAllowlist')));
// Configure CORS
// In production: CORS is restricted by default (requires CORS_ORIGIN to be set)
Expand Down
6 changes: 6 additions & 0 deletions apps/backend-agent-manager/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import {
type WorkspaceConfigurationSettingKey,
} from '@forepath/framework/backend/feature-agent-manager';
import {
applyNestExpressTrustProxyAndFingerprintAsync,
CorrelationAwareConsoleLogger,
CorrelationAwareSocketIoAdapter,
createCorrelationIdMiddleware,
registerAxiosCorrelationIdPropagation,
useNestApiSecurityHeadersMiddleware,
} from '@forepath/framework/backend/util-http-context';
import { createOriginAllowlistMiddleware } from '@forepath/identity/backend';
import { assertProductionEncryptionKeyOrExit } from '@forepath/shared/backend';
Expand Down Expand Up @@ -73,13 +75,17 @@ async function bootstrap() {
const app = await NestFactory.create(AppModule, {
logger: appLogger,
});

await applyNestExpressTrustProxyAndFingerprintAsync(app);

const httpLogger = new Logger('HTTP');

app.use(
createCorrelationIdMiddleware({
log: (message: string) => httpLogger.log(message),
}),
);
useNestApiSecurityHeadersMiddleware(app);
app.use(createOriginAllowlistMiddleware(new Logger('OriginAllowlist')));
// Configure CORS
// In production: CORS is restricted by default (requires CORS_ORIGIN to be set)
Expand Down
6 changes: 6 additions & 0 deletions apps/backend-billing-manager/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import {
applyNestExpressTrustProxyAndFingerprintAsync,
CorrelationAwareConsoleLogger,
CorrelationAwareSocketIoAdapter,
createCorrelationIdMiddleware,
registerAxiosCorrelationIdPropagation,
useNestApiSecurityHeadersMiddleware,
} from '@forepath/framework/backend/util-http-context';
import { createOriginAllowlistMiddleware } from '@forepath/identity/backend';
import { assertProductionEncryptionKeyOrExit } from '@forepath/shared/backend';
Expand All @@ -25,13 +27,17 @@ async function bootstrap() {
const app = await NestFactory.create(AppModule, {
logger: appLogger,
});

await applyNestExpressTrustProxyAndFingerprintAsync(app);

const httpLogger = new Logger('HTTP');

app.use(
createCorrelationIdMiddleware({
log: (message: string) => httpLogger.log(message),
}),
);
useNestApiSecurityHeadersMiddleware(app);
app.use(createOriginAllowlistMiddleware(new Logger('OriginAllowlist')));

app.useWebSocketAdapter(new CorrelationAwareSocketIoAdapter(app));
Expand Down
5 changes: 4 additions & 1 deletion apps/frontend-agent-console/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ import { dirname, join, resolve } from 'path';
import { fileURLToPath } from 'url';

import {
applyExpressServerHardeningAsync,
createSecurityHeadersMiddleware,
registerRuntimeConfigEndpoint,
} from '@forepath/framework/frontend/util-express-server';
} from '@forepath/framework/frontend/util-http-context';
import express from 'express';

const app = express();
const port = parseInt(process.env['PORT'] || '4200', 10);

await applyExpressServerHardeningAsync(app);

app.use(createSecurityHeadersMiddleware());
registerRuntimeConfigEndpoint(app);

Expand Down
5 changes: 4 additions & 1 deletion apps/frontend-billing-console/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ import { dirname, join, resolve } from 'path';
import { fileURLToPath } from 'url';

import {
applyExpressServerHardeningAsync,
createSecurityHeadersMiddleware,
registerRuntimeConfigEndpoint,
} from '@forepath/framework/frontend/util-express-server';
} from '@forepath/framework/frontend/util-http-context';
import express from 'express';

const app = express();
const port = parseInt(process.env['PORT'] || '4200', 10);

await applyExpressServerHardeningAsync(app);

app.use(createSecurityHeadersMiddleware());
registerRuntimeConfigEndpoint(app);

Expand Down
6 changes: 5 additions & 1 deletion apps/frontend-docs/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import { fileURLToPath } from 'node:url';
import { APP_BASE_HREF } from '@angular/common';
import { CommonEngine, isMainModule } from '@angular/ssr/node';
import {
applyExpressServerHardeningAsync,
createSecurityHeadersMiddleware,
registerRuntimeConfigEndpoint,
} from '@forepath/framework/frontend/util-express-server';
} from '@forepath/framework/frontend/util-http-context';
import express from 'express';

import bootstrap from './main.server';
Expand All @@ -15,6 +16,9 @@ const serverDistFolder = dirname(fileURLToPath(import.meta.url));
const browserDistFolder = resolve(serverDistFolder, '../browser');
const indexHtml = join(serverDistFolder, 'index.server.html');
const app = express();

await applyExpressServerHardeningAsync(app);

const commonEngine = new CommonEngine();

app.use(createSecurityHeadersMiddleware());
Expand Down
6 changes: 5 additions & 1 deletion apps/frontend-portal/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import { fileURLToPath } from 'node:url';
import { APP_BASE_HREF } from '@angular/common';
import { CommonEngine, isMainModule } from '@angular/ssr/node';
import {
applyExpressServerHardeningAsync,
createSecurityHeadersMiddleware,
registerRuntimeConfigEndpoint,
} from '@forepath/framework/frontend/util-express-server';
} from '@forepath/framework/frontend/util-http-context';
import express from 'express';

import bootstrap from './main.server';
Expand All @@ -15,6 +16,9 @@ const serverDistFolder = dirname(fileURLToPath(import.meta.url));
const browserDistFolder = resolve(serverDistFolder, '../browser');
const indexHtml = join(serverDistFolder, 'index.server.html');
const app = express();

await applyExpressServerHardeningAsync(app);

const commonEngine = new CommonEngine();

app.use(createSecurityHeadersMiddleware());
Expand Down
9 changes: 8 additions & 1 deletion docs/agenstra/applications/frontend-agent-console.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ See [Authentication](../features/authentication.md) for environment variables an

## Environment Configuration

Configure the application via environment variables. The **Express** runtime (`/config` proxy, CSP, and related variables) is shared with **frontend-billing-console**, **frontend-portal**, and **frontend-docs**; see [Environment configuration](../deployment/environment-configuration.md) for the full list and billing-manager provisioning defaults.
Configure the application via environment variables. The **Express** runtime (`/config` proxy, **`EXPRESS_TRUST_PROXY`**, CSP, and related variables) is shared with **frontend-billing-console**, **frontend-portal**, and **frontend-docs**; see [Environment configuration](../deployment/environment-configuration.md) for the full list and billing-manager provisioning defaults.

### Runtime Configuration (Docker Containers)

Expand All @@ -244,6 +244,13 @@ When `CONFIG` is set, the frontend server fetches and validates the remote JSON
- `CONFIG_JSON_MAX_DEPTH` - Maximum JSON traversal depth for key counting (default: `12`, min: `1`, max: `32`)
- `CONFIG_JSON_MAX_KEYS` - Maximum total JSON keys across all objects/arrays up to `CONFIG_JSON_MAX_DEPTH` (default: `512`, min: `1`, max: `10000`)

#### Express application hardening

Shared with **frontend-billing-console**, **frontend-portal**, and **frontend-docs** (implementation: `@forepath/framework/frontend/util-http-context`).

- **`X-Powered-By`** is disabled; **`X-DNS-Prefetch-Control: off`** is set with the security-headers middleware.
- **`EXPRESS_TRUST_PROXY`** (optional) β€” Sets Express **`trust proxy`** when the server runs behind an ingress or load balancer. Values: `true` / `1` / `yes` β†’ trust **one** hop; all-digit string β†’ hop count **`n`**; or a comma-separated list of IPs, CIDRs, named subnets (`loopback`, `linklocal`, `uniquelocal`), or hostnames (resolved to an IP at startup). See [Environment configuration](../deployment/environment-configuration.md#express-application-hardening) and the [Express behind proxies](https://expressjs.com/en/guide/behind-proxies.html) guide.

#### Content Security Policy (Express)

- `CSP_ENFORCE` - When `true`, sends enforcing `Content-Security-Policy`. Otherwise sends `Content-Security-Policy-Report-Only` (default).
Expand Down
1 change: 1 addition & 0 deletions docs/agenstra/deployment/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Complete environment variables reference:
- Database configuration
- Authentication configuration
- CORS and rate limiting
- Frontend Express hardening (`EXPRESS_TRUST_PROXY`, CSP, security headers)
- Server provisioning

## Deployment Architecture
Expand Down
7 changes: 7 additions & 0 deletions docs/agenstra/deployment/docker-deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,13 @@ When `CONFIG` is set, the frontend server also supports the following optional h

Frontend Express servers (agent console, billing console, portal, docs) also support:

**Express application hardening** (see [Environment configuration β€” Express application hardening](./environment-configuration.md#express-application-hardening)):

- Baseline: **`X-Powered-By`** disabled; **`X-DNS-Prefetch-Control: off`** with the shared security-headers middleware.
- **`EXPRESS_TRUST_PROXY`** (optional) β€” Configures **`trust proxy`** behind a reverse proxy (`true` / `1` / `yes` β†’ one hop; all-digit string β†’ hop count; comma-separated IPs, CIDRs, named subnets `loopback` / `linklocal` / `uniquelocal`, or hostnames resolved at startup). See the [Express behind proxies](https://expressjs.com/en/guide/behind-proxies.html) guide.

**Content Security Policy:**

- `CSP_ENFORCE` - Set to `true` to enforce Content Security Policy (sends `Content-Security-Policy`), otherwise report-only (`Content-Security-Policy-Report-Only`).
- `CSP_DEFAULT_SRC_EXTRA` - Extra origins appended to `default-src` (same URL list rules as `CSP_CONNECT_SRC_EXTRA`).
- `CSP_BASE_URI_EXTRA` - Extra origins appended to `base-uri` (same URL list rules).
Expand Down
Loading
Loading