actual-mcp-server 0.7.13 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/package.json +3 -3
- package/dist/src/config.js +5 -0
- package/dist/src/lib/oidc-discovery.js +87 -13
- package/dist/src/server/httpServer.js +5 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -473,6 +473,7 @@ All configuration is via environment variables. Copy `.env.example` to `.env` to
|
|
|
473
473
|
| `OIDC_ALLOW_INSECURE_ISSUER` | `false` | No | Allow a plaintext (http) OIDC issuer on a trusted network (#244). Off by default (http issuer refused at startup); set `true` only for local/LAN testing |
|
|
474
474
|
| `OIDC_RESOURCE` | _(none)_ | No | Expected `aud` claim in JWT (your client ID) |
|
|
475
475
|
| `OIDC_ACCEPTED_AUDIENCES` | _(none)_ | No | Extra accepted `aud` values beyond `OIDC_RESOURCE`, comma-separated (#245). For IdPs that put the client-id in `aud` (e.g. Authentik). Strict allowlist, never a wildcard |
|
|
476
|
+
| `OIDC_JWKS_TRUSTED_HOSTS` | _(none)_ | No | Opt-in cross-origin JWKS hosts, comma-separated `host` or `host:port` (#254). For IdPs whose `jwks_uri` lives on another host, e.g. Google needs `www.googleapis.com`. Exact match, no wildcards; empty default keeps same-origin-only |
|
|
476
477
|
| `OIDC_SCOPES` | _(none)_ | No | Comma-separated required scopes; leave empty for Casdoor |
|
|
477
478
|
| `AUTH_BUDGET_ACL` | _(none)_ | No | Per-user budget ACL; see [AI Client Setup](docs/guides/AI_CLIENT_SETUP.md#oidc-authentication-multi-user) |
|
|
478
479
|
| `MCP_ENABLE_HTTPS` | `false` | No | Enable native TLS. Requires `MCP_HTTPS_CERT` and `MCP_HTTPS_KEY` |
|
|
@@ -718,4 +719,4 @@ The software is provided **as-is**, without warranty of any kind. The author acc
|
|
|
718
719
|
|
|
719
720
|
---
|
|
720
721
|
|
|
721
|
-
**Version:** 0.
|
|
722
|
+
**Version:** 0.8.1 | **Tool Count:** 71 (verified LibreChat-compatible)
|
package/dist/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "actual-mcp-server",
|
|
3
3
|
"displayName": "Actual MCP Server",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.8.1",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=22.0.0",
|
|
7
7
|
"npm": ">=10.0.0"
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"verify-tools": "npm run build && node scripts/verify-tools.js",
|
|
31
31
|
"check:coverage": "node scripts/list-actual-api-methods.mjs",
|
|
32
32
|
"direct-sync": "node scripts/direct-sync/bank-sync-direct.mjs",
|
|
33
|
-
"test:unit-js": "node tests/unit/transactions_create.test.js && node tests/unit/generated_tools.smoke.test.js && node tests/unit/schema_validation.test.js && node tests/unit/config_https_validation.test.js && node tests/unit/config_data_dir.test.js && node tests/unit/config_drift.test.js && node tests/unit/port_alignment.test.js && node tests/unit/dockerfile_data_dir_alignment.test.js && node tests/unit/unraid_template_alignment.test.js && node tests/unit/entrypoint_invariants.test.js && node tests/unit/compose_profile_sync.test.js && node tests/unit/advertised_tools_sync.test.js && node tests/unit/knip_config.test.js && node tests/unit/readme_stats_sync.test.js && node tests/unit/config_insecure_upstream.test.js && node tests/unit/auth_posture.test.js && node tests/unit/auth_posture_wiring.test.js && node tests/unit/auth-acl.test.js && node tests/unit/bug76.test.js && node tests/unit/budgets_setAmount.test.js && node tests/unit/budgets_transfer.test.js && node tests/unit/transactions_uncategorized.test.js && node tests/unit/httpServer_session_init.test.js && node tests/unit/httpServer_session_not_found.test.js && node tests/unit/manual_mcp_client_retry.test.js && node tests/unit/manual_mcp_client_session.test.js && node tests/unit/manual_mcp_client_circuit.test.js && node tests/unit/manual_runner_killswitch.test.js && node tests/unit/adapter_auth_rate_limit.test.js && node tests/unit/adapter_session_reuse.test.js && node tests/unit/retry_classifier.test.js && node tests/unit/adapter_module_surface.test.js && node tests/unit/adapter_nonidempotent_no_retry.test.js && node tests/unit/pool_liveness.test.js && node tests/unit/pool_shutdown_all.test.js && node tests/unit/query_where_operators.test.js && node tests/unit/query_run_validation.test.js && node tests/unit/adapter_with_write_session.test.js && node tests/unit/category_groups_delete.test.js && node tests/unit/rules_delete.test.js && node tests/unit/schedules_delete.test.js && node tests/unit/payees_delete.test.js && node tests/unit/rules_create_or_update.test.js && node tests/unit/unhandled-rejection.test.js && node tests/unit/rejection-allowlist-purity.test.js && node tests/unit/httpServer_bearer_auth.test.js && node tests/unit/httpServer_oidc_audience.test.js && node tests/unit/httpServer_oidc_auth_verification.test.js && node tests/unit/httpServer_oidc_jwks_discovery.test.js && node tests/unit/httpServer_body_limit.test.js && node tests/unit/httpServer_bind_host.test.js && node tests/unit/adapter_write_pool_cooperation.test.js && node tests/unit/budget_acl_enforcement.test.js && node tests/unit/budget_preference_store.test.js && node tests/unit/budget_preference_restore.test.js && node tests/unit/tags_list.test.js && node tests/unit/tags_create.test.js && node tests/unit/tags_update.test.js && node tests/unit/tags_delete.test.js && node tests/unit/notes_get.test.js && node tests/unit/notes_update.test.js && node tests/unit/payees_common_list.test.js && node tests/unit/transactions_update_guard.test.js && node tests/unit/entities_search.test.js && node tests/unit/transactions_import_schema.test.js && node tests/unit/tool_schema_shape.test.js && node tests/unit/zod_error_format.test.js && node tests/unit/tool_count_sync.test.js && node tests/unit/stdio_transport.test.js && node tests/unit/logger_structured.test.js && node tests/unit/logger_redaction.test.js && node tests/unit/logger_correlation.test.js && node tests/unit/check_coverage.test.js",
|
|
33
|
+
"test:unit-js": "node tests/unit/transactions_create.test.js && node tests/unit/generated_tools.smoke.test.js && node tests/unit/schema_validation.test.js && node tests/unit/config_https_validation.test.js && node tests/unit/config_data_dir.test.js && node tests/unit/config_drift.test.js && node tests/unit/port_alignment.test.js && node tests/unit/dockerfile_data_dir_alignment.test.js && node tests/unit/unraid_template_alignment.test.js && node tests/unit/entrypoint_invariants.test.js && node tests/unit/compose_profile_sync.test.js && node tests/unit/advertised_tools_sync.test.js && node tests/unit/knip_config.test.js && node tests/unit/readme_stats_sync.test.js && node tests/unit/config_insecure_upstream.test.js && node tests/unit/workflow_release_guards.test.js && node tests/unit/bot_target_branch.test.js && node tests/unit/auth_posture.test.js && node tests/unit/auth_posture_wiring.test.js && node tests/unit/auth-acl.test.js && node tests/unit/bug76.test.js && node tests/unit/budgets_setAmount.test.js && node tests/unit/budgets_transfer.test.js && node tests/unit/transactions_uncategorized.test.js && node tests/unit/httpServer_session_init.test.js && node tests/unit/httpServer_session_not_found.test.js && node tests/unit/manual_mcp_client_retry.test.js && node tests/unit/manual_mcp_client_session.test.js && node tests/unit/manual_mcp_client_circuit.test.js && node tests/unit/manual_runner_killswitch.test.js && node tests/unit/adapter_auth_rate_limit.test.js && node tests/unit/adapter_session_reuse.test.js && node tests/unit/retry_classifier.test.js && node tests/unit/adapter_module_surface.test.js && node tests/unit/adapter_nonidempotent_no_retry.test.js && node tests/unit/pool_liveness.test.js && node tests/unit/pool_shutdown_all.test.js && node tests/unit/query_where_operators.test.js && node tests/unit/query_run_validation.test.js && node tests/unit/adapter_with_write_session.test.js && node tests/unit/category_groups_delete.test.js && node tests/unit/rules_delete.test.js && node tests/unit/schedules_delete.test.js && node tests/unit/payees_delete.test.js && node tests/unit/rules_create_or_update.test.js && node tests/unit/unhandled-rejection.test.js && node tests/unit/rejection-allowlist-purity.test.js && node tests/unit/httpServer_bearer_auth.test.js && node tests/unit/httpServer_oidc_audience.test.js && node tests/unit/httpServer_oidc_auth_verification.test.js && node tests/unit/httpServer_oidc_jwks_discovery.test.js && node tests/unit/httpServer_body_limit.test.js && node tests/unit/httpServer_bind_host.test.js && node tests/unit/adapter_write_pool_cooperation.test.js && node tests/unit/budget_acl_enforcement.test.js && node tests/unit/budget_preference_store.test.js && node tests/unit/budget_preference_restore.test.js && node tests/unit/tags_list.test.js && node tests/unit/tags_create.test.js && node tests/unit/tags_update.test.js && node tests/unit/tags_delete.test.js && node tests/unit/notes_get.test.js && node tests/unit/notes_update.test.js && node tests/unit/payees_common_list.test.js && node tests/unit/transactions_update_guard.test.js && node tests/unit/entities_search.test.js && node tests/unit/transactions_import_schema.test.js && node tests/unit/tool_schema_shape.test.js && node tests/unit/zod_error_format.test.js && node tests/unit/tool_count_sync.test.js && node tests/unit/stdio_transport.test.js && node tests/unit/logger_structured.test.js && node tests/unit/logger_redaction.test.js && node tests/unit/logger_correlation.test.js && node tests/unit/check_coverage.test.js",
|
|
34
34
|
"test:adapter": "npm run build && node dist/src/tests_adapter_runner.js",
|
|
35
35
|
"test:e2e": "npx playwright test",
|
|
36
36
|
"test:e2e:docker": "./tests/e2e/run-docker-e2e.sh",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"devDependencies": {
|
|
79
79
|
"@playwright/test": "^1.60.0",
|
|
80
80
|
"@types/express": "^5.0.3",
|
|
81
|
-
"@types/node": "^
|
|
81
|
+
"@types/node": "^26.1.0",
|
|
82
82
|
"knip": "^6.16.1",
|
|
83
83
|
"node-fetch": "^3.3.2",
|
|
84
84
|
"tsconfig-paths": "^4.2.0",
|
package/dist/src/config.js
CHANGED
|
@@ -46,6 +46,11 @@ export const configSchema = z.object({
|
|
|
46
46
|
// Strict closed allowlist: the accepted set is OIDC_RESOURCE plus these; never a
|
|
47
47
|
// wildcard. Default empty, so single-audience deployments are unchanged.
|
|
48
48
|
OIDC_ACCEPTED_AUDIENCES: z.string().optional(),
|
|
49
|
+
// #254: opt-in allowlist of cross-origin JWKS hosts, comma-separated `host` or
|
|
50
|
+
// `host:port` entries (e.g. "www.googleapis.com" for a Google issuer). Raw string
|
|
51
|
+
// here; parsed fail-fast by buildTrustedJwksHosts (oidc-discovery.ts) at the
|
|
52
|
+
// composition root, per the #245 pattern. Default empty: same-origin only.
|
|
53
|
+
OIDC_JWKS_TRUSTED_HOSTS: z.string().optional(),
|
|
49
54
|
// Comma-separated required scopes (e.g. "read,write"). Optional.
|
|
50
55
|
OIDC_SCOPES: z.string().optional(),
|
|
51
56
|
// JSON map of principal → budget sync-ID list for per-user budget ACL.
|
|
@@ -14,12 +14,14 @@ import { createModuleLogger } from './loggerFactory.js';
|
|
|
14
14
|
* - the resolved `jwks_uri` must be https and on the SAME host as the issuer, so
|
|
15
15
|
* a tampered or misconfigured discovery document cannot point key fetching at
|
|
16
16
|
* an attacker-controlled host. Cross-host issuers (for example Google, which
|
|
17
|
-
* serves keys from googleapis.com) are
|
|
18
|
-
*
|
|
17
|
+
* serves keys from googleapis.com) are never auto-trusted; the operator can
|
|
18
|
+
* allowlist specific cross-origin JWKS hosts via OIDC_JWKS_TRUSTED_HOSTS
|
|
19
|
+
* (#254, opt-in, exact host:port match, https required unconditionally).
|
|
19
20
|
*
|
|
20
|
-
* The
|
|
21
|
-
* network
|
|
22
|
-
*
|
|
21
|
+
* The helpers (assertSecureIssuer, buildTrustedJwksHosts, resolveJwksUri) are
|
|
22
|
+
* network-free and unit-tested directly (resolveJwksUri logs on the allowlist
|
|
23
|
+
* accept path, otherwise side-effect free); discoverJwksUri wraps them around a
|
|
24
|
+
* single startup fetch and fails closed on any problem.
|
|
23
25
|
*/
|
|
24
26
|
const logger = createModuleLogger('OIDC_DISCOVERY');
|
|
25
27
|
const LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1', '::1', '[::1]']);
|
|
@@ -28,6 +30,47 @@ const DISCOVERY_TIMEOUT_MS = 10_000;
|
|
|
28
30
|
export function isLoopbackHost(host) {
|
|
29
31
|
return host ? LOOPBACK_HOSTS.has(host.toLowerCase()) : false;
|
|
30
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Parse OIDC_JWKS_TRUSTED_HOSTS (comma-separated `host` or `host:port`
|
|
35
|
+
* entries) into a normalized, deduplicated allowlist (#254). Mirrors the #245
|
|
36
|
+
* buildAcceptedAudiences pattern: config stays a raw string, parsing happens
|
|
37
|
+
* here, and the result is threaded as a parameter. Entries are trimmed,
|
|
38
|
+
* lowercased, and empties dropped, then validated by a URL round-trip: an
|
|
39
|
+
* entry must equal `new URL('https://' + entry).host`, which rejects in one
|
|
40
|
+
* rule everything that could never match a real jwks_uri host: schemes,
|
|
41
|
+
* paths, credentials, wildcards, whitespace, empty or non-numeric ports,
|
|
42
|
+
* default-port `:443` suffixes (URL.host elides them, so such an entry would
|
|
43
|
+
* be silently inert), raw-unicode IDN (URL.host is punycode), and unbracketed
|
|
44
|
+
* IPv6. THROWS on the first invalid entry so a misconfigured allowlist fails
|
|
45
|
+
* fast at startup instead of silently failing closed at the first
|
|
46
|
+
* cross-origin issuer.
|
|
47
|
+
*/
|
|
48
|
+
export function buildTrustedJwksHosts(configured) {
|
|
49
|
+
if (!configured)
|
|
50
|
+
return [];
|
|
51
|
+
const entries = configured
|
|
52
|
+
.split(',')
|
|
53
|
+
.map((entry) => entry.trim().toLowerCase())
|
|
54
|
+
.filter(Boolean);
|
|
55
|
+
for (const entry of entries) {
|
|
56
|
+
let canonical;
|
|
57
|
+
try {
|
|
58
|
+
canonical = new URL(`https://${entry}`).host;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
canonical = undefined;
|
|
62
|
+
}
|
|
63
|
+
// WHATWG URL does not forbid "*" in a hostname, so the round-trip alone
|
|
64
|
+
// would let a wildcard through; there is deliberately no wildcard matching.
|
|
65
|
+
if (entry.includes('*') || canonical !== entry) {
|
|
66
|
+
throw new Error(`OIDC_JWKS_TRUSTED_HOSTS entry is invalid: "${entry}". Entries must be canonical URL ` +
|
|
67
|
+
'hosts: a bare lowercase hostname, optionally with a NON-default port (host:port; ' +
|
|
68
|
+
'":443" is elided by URL parsing and would never match), no scheme, path, ' +
|
|
69
|
+
'credentials, or wildcards; IDN as punycode; IPv6 in brackets.');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return Array.from(new Set(entries));
|
|
73
|
+
}
|
|
31
74
|
/**
|
|
32
75
|
* Parse and validate the issuer URL. Throws unless it is https, or an explicit
|
|
33
76
|
* loopback host (local dev). Returns the parsed URL.
|
|
@@ -62,11 +105,16 @@ export function assertSecureIssuer(issuer, allowInsecure = false) {
|
|
|
62
105
|
return url;
|
|
63
106
|
}
|
|
64
107
|
/**
|
|
65
|
-
*
|
|
66
|
-
* against the issuer. Throws (fail closed) if it is missing, not
|
|
67
|
-
* embedded credentials, or is not same-origin (scheme + host +
|
|
108
|
+
* Network-free: resolve and validate the `jwks_uri` from a parsed discovery
|
|
109
|
+
* document against the issuer. Throws (fail closed) if it is missing, not
|
|
110
|
+
* https, carries embedded credentials, or is not same-origin (scheme + host +
|
|
111
|
+
* port) with the issuer, UNLESS the jwks host is explicitly listed in
|
|
112
|
+
* `trustedHosts` (#254): an allowlisted cross-origin jwks_uri is accepted,
|
|
113
|
+
* must still be https (no OIDC_ALLOW_INSECURE_ISSUER or loopback exemption on
|
|
114
|
+
* the cross-origin path), and the acceptance is audit-logged (the one side
|
|
115
|
+
* effect of this function).
|
|
68
116
|
*/
|
|
69
|
-
export function resolveJwksUri(discoveryDoc, issuer, allowInsecure = false) {
|
|
117
|
+
export function resolveJwksUri(discoveryDoc, issuer, allowInsecure = false, trustedHosts = []) {
|
|
70
118
|
const issuerUrl = assertSecureIssuer(issuer, allowInsecure);
|
|
71
119
|
if (!discoveryDoc || typeof discoveryDoc !== 'object') {
|
|
72
120
|
throw new Error('OIDC discovery document is empty or not an object');
|
|
@@ -92,8 +140,34 @@ export function resolveJwksUri(discoveryDoc, issuer, allowInsecure = false) {
|
|
|
92
140
|
// same host is a different service, so comparing origins keeps the guarantee the
|
|
93
141
|
// security review asked for. Both are https here, so this is effectively host+port.
|
|
94
142
|
if (jwks.origin.toLowerCase() !== issuerUrl.origin.toLowerCase()) {
|
|
95
|
-
|
|
96
|
-
|
|
143
|
+
// #254: opt-in escape hatch for legitimately cross-host IdPs (Google serves
|
|
144
|
+
// JWKS from www.googleapis.com). Matching is on URL.host (hostname PLUS any
|
|
145
|
+
// non-default port), exact and lowercased: a bare-host entry does not trust
|
|
146
|
+
// odd ports on that host, preserving this file's origin-level strictness.
|
|
147
|
+
// Empty allowlist (the default) makes this branch behave exactly as before.
|
|
148
|
+
const jwksHost = jwks.host.toLowerCase();
|
|
149
|
+
if (!trustedHosts.includes(jwksHost)) {
|
|
150
|
+
throw new Error(`OIDC "jwks_uri" origin (${jwks.origin}) does not match the issuer origin (${issuerUrl.origin}); ` +
|
|
151
|
+
`refusing a cross-origin key fetch. Checked host "${jwksHost}" against ` +
|
|
152
|
+
`OIDC_JWKS_TRUSTED_HOSTS [${trustedHosts.join(', ')}]; matching is exact on host:port ` +
|
|
153
|
+
'(#254). A cross-host issuer (e.g. Google) needs its JWKS host explicitly allowlisted.');
|
|
154
|
+
}
|
|
155
|
+
// An allowlisted CROSS-ORIGIN fetch must be https, with no exemption: not
|
|
156
|
+
// for OIDC_ALLOW_INSECURE_ISSUER (the #244 opt-out covers a same-origin LAN
|
|
157
|
+
// issuer, never third-party key hosts) and not for loopback (a shared-host
|
|
158
|
+
// process could bind the port). Without this, combining the two opt-ins
|
|
159
|
+
// would newly permit a plaintext cross-origin key fetch that pre-#254 code
|
|
160
|
+
// always refused.
|
|
161
|
+
if (jwks.protocol !== 'https:') {
|
|
162
|
+
throw new Error(`OIDC cross-origin "jwks_uri" allowed by OIDC_JWKS_TRUSTED_HOSTS must use https (got ${jwks.protocol}); ` +
|
|
163
|
+
'the OIDC_ALLOW_INSECURE_ISSUER opt-out never extends to cross-origin key fetches (#254).');
|
|
164
|
+
}
|
|
165
|
+
// Audit trail: a normally-rejected cross-origin trust was relaxed by operator config.
|
|
166
|
+
logger.info('cross-origin JWKS host allowed by OIDC_JWKS_TRUSTED_HOSTS', {
|
|
167
|
+
issuerOrigin: issuerUrl.origin,
|
|
168
|
+
jwksHost,
|
|
169
|
+
jwksUri: jwks.toString(),
|
|
170
|
+
});
|
|
97
171
|
}
|
|
98
172
|
return jwks.toString();
|
|
99
173
|
}
|
|
@@ -102,7 +176,7 @@ export function resolveJwksUri(discoveryDoc, issuer, allowInsecure = false) {
|
|
|
102
176
|
* Fails closed on any problem (non-https issuer, redirect, non-200, bad JSON,
|
|
103
177
|
* missing/invalid/cross-host jwks_uri). `fetchImpl` is injectable for testing.
|
|
104
178
|
*/
|
|
105
|
-
export async function discoverJwksUri(issuer, allowInsecure = false, fetchImpl = fetch) {
|
|
179
|
+
export async function discoverJwksUri(issuer, allowInsecure = false, trustedHosts = [], fetchImpl = fetch) {
|
|
106
180
|
assertSecureIssuer(issuer, allowInsecure);
|
|
107
181
|
const base = issuer.endsWith('/') ? issuer.slice(0, -1) : issuer;
|
|
108
182
|
const discoveryUrl = `${base}/.well-known/openid-configuration`;
|
|
@@ -126,7 +200,7 @@ export async function discoverJwksUri(issuer, allowInsecure = false, fetchImpl =
|
|
|
126
200
|
catch {
|
|
127
201
|
throw new Error(`OIDC discovery document at ${discoveryUrl} is not valid JSON`);
|
|
128
202
|
}
|
|
129
|
-
const jwksUri = resolveJwksUri(doc, issuer, allowInsecure);
|
|
203
|
+
const jwksUri = resolveJwksUri(doc, issuer, allowInsecure, trustedHosts);
|
|
130
204
|
logger.info('Resolved JWKS URI from OIDC discovery', { jwksUri });
|
|
131
205
|
return jwksUri;
|
|
132
206
|
}
|
|
@@ -13,7 +13,7 @@ import config from '../config.js';
|
|
|
13
13
|
import { createRemoteJWKSet, jwtVerify } from 'jose';
|
|
14
14
|
import { MCPAuthTokenVerificationError } from 'mcp-auth';
|
|
15
15
|
import { createMcpAuth } from '../auth/setup.js';
|
|
16
|
-
import { discoverJwksUri } from '../lib/oidc-discovery.js';
|
|
16
|
+
import { discoverJwksUri, buildTrustedJwksHosts } from '../lib/oidc-discovery.js';
|
|
17
17
|
import { buildAcceptedAudiences } from '../lib/oidc-audiences.js';
|
|
18
18
|
import { budgetAclMiddleware } from '../auth/budget-acl.js';
|
|
19
19
|
import * as https from 'node:https';
|
|
@@ -83,7 +83,10 @@ advertisedUrl) {
|
|
|
83
83
|
// closed on a non-https issuer, a cross-origin redirect, a missing/invalid
|
|
84
84
|
// jwks_uri, or a jwks_uri on a different host than the issuer. jose's
|
|
85
85
|
// createRemoteJWKSet then fetches the keys lazily and caches them.
|
|
86
|
-
|
|
86
|
+
// #254: opt-in cross-origin JWKS trusted hosts (e.g. Google serves keys from
|
|
87
|
+
// www.googleapis.com). Parsed fail-fast here at the composition root and
|
|
88
|
+
// threaded as a parameter; empty (the default) keeps same-origin-only.
|
|
89
|
+
const jwksUri = await discoverJwksUri(config.OIDC_ISSUER, config.OIDC_ALLOW_INSECURE_ISSUER, buildTrustedJwksHosts(config.OIDC_JWKS_TRUSTED_HOSTS));
|
|
87
90
|
const jwks = createRemoteJWKSet(new URL(jwksUri));
|
|
88
91
|
const customJwtVerify = async (token) => {
|
|
89
92
|
// Enforce the audience claim (#160, OWASP A07). Without it, any
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "actual-mcp-server",
|
|
3
3
|
"displayName": "Actual MCP Server",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.8.1",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=22.0.0",
|
|
7
7
|
"npm": ">=10.0.0"
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"verify-tools": "npm run build && node scripts/verify-tools.js",
|
|
31
31
|
"check:coverage": "node scripts/list-actual-api-methods.mjs",
|
|
32
32
|
"direct-sync": "node scripts/direct-sync/bank-sync-direct.mjs",
|
|
33
|
-
"test:unit-js": "node tests/unit/transactions_create.test.js && node tests/unit/generated_tools.smoke.test.js && node tests/unit/schema_validation.test.js && node tests/unit/config_https_validation.test.js && node tests/unit/config_data_dir.test.js && node tests/unit/config_drift.test.js && node tests/unit/port_alignment.test.js && node tests/unit/dockerfile_data_dir_alignment.test.js && node tests/unit/unraid_template_alignment.test.js && node tests/unit/entrypoint_invariants.test.js && node tests/unit/compose_profile_sync.test.js && node tests/unit/advertised_tools_sync.test.js && node tests/unit/knip_config.test.js && node tests/unit/readme_stats_sync.test.js && node tests/unit/config_insecure_upstream.test.js && node tests/unit/auth_posture.test.js && node tests/unit/auth_posture_wiring.test.js && node tests/unit/auth-acl.test.js && node tests/unit/bug76.test.js && node tests/unit/budgets_setAmount.test.js && node tests/unit/budgets_transfer.test.js && node tests/unit/transactions_uncategorized.test.js && node tests/unit/httpServer_session_init.test.js && node tests/unit/httpServer_session_not_found.test.js && node tests/unit/manual_mcp_client_retry.test.js && node tests/unit/manual_mcp_client_session.test.js && node tests/unit/manual_mcp_client_circuit.test.js && node tests/unit/manual_runner_killswitch.test.js && node tests/unit/adapter_auth_rate_limit.test.js && node tests/unit/adapter_session_reuse.test.js && node tests/unit/retry_classifier.test.js && node tests/unit/adapter_module_surface.test.js && node tests/unit/adapter_nonidempotent_no_retry.test.js && node tests/unit/pool_liveness.test.js && node tests/unit/pool_shutdown_all.test.js && node tests/unit/query_where_operators.test.js && node tests/unit/query_run_validation.test.js && node tests/unit/adapter_with_write_session.test.js && node tests/unit/category_groups_delete.test.js && node tests/unit/rules_delete.test.js && node tests/unit/schedules_delete.test.js && node tests/unit/payees_delete.test.js && node tests/unit/rules_create_or_update.test.js && node tests/unit/unhandled-rejection.test.js && node tests/unit/rejection-allowlist-purity.test.js && node tests/unit/httpServer_bearer_auth.test.js && node tests/unit/httpServer_oidc_audience.test.js && node tests/unit/httpServer_oidc_auth_verification.test.js && node tests/unit/httpServer_oidc_jwks_discovery.test.js && node tests/unit/httpServer_body_limit.test.js && node tests/unit/httpServer_bind_host.test.js && node tests/unit/adapter_write_pool_cooperation.test.js && node tests/unit/budget_acl_enforcement.test.js && node tests/unit/budget_preference_store.test.js && node tests/unit/budget_preference_restore.test.js && node tests/unit/tags_list.test.js && node tests/unit/tags_create.test.js && node tests/unit/tags_update.test.js && node tests/unit/tags_delete.test.js && node tests/unit/notes_get.test.js && node tests/unit/notes_update.test.js && node tests/unit/payees_common_list.test.js && node tests/unit/transactions_update_guard.test.js && node tests/unit/entities_search.test.js && node tests/unit/transactions_import_schema.test.js && node tests/unit/tool_schema_shape.test.js && node tests/unit/zod_error_format.test.js && node tests/unit/tool_count_sync.test.js && node tests/unit/stdio_transport.test.js && node tests/unit/logger_structured.test.js && node tests/unit/logger_redaction.test.js && node tests/unit/logger_correlation.test.js && node tests/unit/check_coverage.test.js",
|
|
33
|
+
"test:unit-js": "node tests/unit/transactions_create.test.js && node tests/unit/generated_tools.smoke.test.js && node tests/unit/schema_validation.test.js && node tests/unit/config_https_validation.test.js && node tests/unit/config_data_dir.test.js && node tests/unit/config_drift.test.js && node tests/unit/port_alignment.test.js && node tests/unit/dockerfile_data_dir_alignment.test.js && node tests/unit/unraid_template_alignment.test.js && node tests/unit/entrypoint_invariants.test.js && node tests/unit/compose_profile_sync.test.js && node tests/unit/advertised_tools_sync.test.js && node tests/unit/knip_config.test.js && node tests/unit/readme_stats_sync.test.js && node tests/unit/config_insecure_upstream.test.js && node tests/unit/workflow_release_guards.test.js && node tests/unit/bot_target_branch.test.js && node tests/unit/auth_posture.test.js && node tests/unit/auth_posture_wiring.test.js && node tests/unit/auth-acl.test.js && node tests/unit/bug76.test.js && node tests/unit/budgets_setAmount.test.js && node tests/unit/budgets_transfer.test.js && node tests/unit/transactions_uncategorized.test.js && node tests/unit/httpServer_session_init.test.js && node tests/unit/httpServer_session_not_found.test.js && node tests/unit/manual_mcp_client_retry.test.js && node tests/unit/manual_mcp_client_session.test.js && node tests/unit/manual_mcp_client_circuit.test.js && node tests/unit/manual_runner_killswitch.test.js && node tests/unit/adapter_auth_rate_limit.test.js && node tests/unit/adapter_session_reuse.test.js && node tests/unit/retry_classifier.test.js && node tests/unit/adapter_module_surface.test.js && node tests/unit/adapter_nonidempotent_no_retry.test.js && node tests/unit/pool_liveness.test.js && node tests/unit/pool_shutdown_all.test.js && node tests/unit/query_where_operators.test.js && node tests/unit/query_run_validation.test.js && node tests/unit/adapter_with_write_session.test.js && node tests/unit/category_groups_delete.test.js && node tests/unit/rules_delete.test.js && node tests/unit/schedules_delete.test.js && node tests/unit/payees_delete.test.js && node tests/unit/rules_create_or_update.test.js && node tests/unit/unhandled-rejection.test.js && node tests/unit/rejection-allowlist-purity.test.js && node tests/unit/httpServer_bearer_auth.test.js && node tests/unit/httpServer_oidc_audience.test.js && node tests/unit/httpServer_oidc_auth_verification.test.js && node tests/unit/httpServer_oidc_jwks_discovery.test.js && node tests/unit/httpServer_body_limit.test.js && node tests/unit/httpServer_bind_host.test.js && node tests/unit/adapter_write_pool_cooperation.test.js && node tests/unit/budget_acl_enforcement.test.js && node tests/unit/budget_preference_store.test.js && node tests/unit/budget_preference_restore.test.js && node tests/unit/tags_list.test.js && node tests/unit/tags_create.test.js && node tests/unit/tags_update.test.js && node tests/unit/tags_delete.test.js && node tests/unit/notes_get.test.js && node tests/unit/notes_update.test.js && node tests/unit/payees_common_list.test.js && node tests/unit/transactions_update_guard.test.js && node tests/unit/entities_search.test.js && node tests/unit/transactions_import_schema.test.js && node tests/unit/tool_schema_shape.test.js && node tests/unit/zod_error_format.test.js && node tests/unit/tool_count_sync.test.js && node tests/unit/stdio_transport.test.js && node tests/unit/logger_structured.test.js && node tests/unit/logger_redaction.test.js && node tests/unit/logger_correlation.test.js && node tests/unit/check_coverage.test.js",
|
|
34
34
|
"test:adapter": "npm run build && node dist/src/tests_adapter_runner.js",
|
|
35
35
|
"test:e2e": "npx playwright test",
|
|
36
36
|
"test:e2e:docker": "./tests/e2e/run-docker-e2e.sh",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"devDependencies": {
|
|
79
79
|
"@playwright/test": "^1.60.0",
|
|
80
80
|
"@types/express": "^5.0.3",
|
|
81
|
-
"@types/node": "^
|
|
81
|
+
"@types/node": "^26.1.0",
|
|
82
82
|
"knip": "^6.16.1",
|
|
83
83
|
"node-fetch": "^3.3.2",
|
|
84
84
|
"tsconfig-paths": "^4.2.0",
|