anki-mcp-http 0.20.1 → 0.21.0
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 +32 -5
- package/dist/app.module.js +11 -0
- package/dist/app.module.js.map +1 -1
- package/dist/config/config.schema.d.ts +3 -0
- package/dist/config/config.schema.js +26 -1
- package/dist/config/config.schema.js.map +1 -1
- package/dist/http/guards/host-validation.guard.d.ts +11 -0
- package/dist/http/guards/host-validation.guard.js +78 -0
- package/dist/http/guards/host-validation.guard.js.map +1 -0
- package/dist/http/guards/origin-validation.guard.d.ts +2 -1
- package/dist/http/guards/origin-validation.guard.js +8 -5
- package/dist/http/guards/origin-validation.guard.js.map +1 -1
- package/dist/main-http.js +11 -2
- package/dist/main-http.js.map +1 -1
- package/dist/services/ngrok.service.js +1 -1
- package/dist/services/ngrok.service.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -237,6 +237,8 @@ For client-specific features and troubleshooting, consult your MCP client's docu
|
|
|
237
237
|
|
|
238
238
|
HTTP mode runs the server as a local web server speaking the MCP Streamable HTTP protocol. It's the transport a web-based AI tool talks to when pointed at your machine, and it's also what the [Remote](#remote) options expose to the outside world. On its own, HTTP mode binds to `localhost` only.
|
|
239
239
|
|
|
240
|
+
> **Binding beyond localhost?** If you pass `--host 0.0.0.0` (or run behind a reverse proxy/public domain), the server only accepts loopback `Host` headers by default for DNS-rebinding protection — set `ALLOWED_HOSTS` to the hostname(s) clients use. See [HTTP Mode Configuration](#http-mode-configuration).
|
|
241
|
+
|
|
240
242
|
**Setup - Choose one method:**
|
|
241
243
|
|
|
242
244
|
**Method 1: Using npx (recommended - no installation needed)**
|
|
@@ -330,7 +332,9 @@ If you'd rather expose [local HTTP mode](#http-local-web-based-ai) publicly with
|
|
|
330
332
|
ankimcp --ngrok
|
|
331
333
|
```
|
|
332
334
|
|
|
333
|
-
This route is **unauthenticated** — anyone with the URL can reach your Anki, so it's less secure than [Tunnel](#tunnel--recommended). Prefer Tunnel unless you have a specific reason to manage your own ngrok endpoint. (Requires a global ngrok install and authtoken
|
|
335
|
+
This route is **unauthenticated** — anyone with the URL can reach your Anki, so it's less secure than [Tunnel](#tunnel--recommended). Prefer Tunnel unless you have a specific reason to manage your own ngrok endpoint. (Requires a global ngrok install and authtoken.)
|
|
336
|
+
|
|
337
|
+
The `--ngrok` flag launches ngrok with `--host-header=rewrite`, so ngrok rewrites the upstream `Host` to `localhost` before forwarding. That keeps requests within the loopback Host allowlist (see [DNS-rebinding protection](#http-mode-configuration)) without you having to add the public `*.ngrok` domain to `ALLOWED_HOSTS`. If you instead run ngrok manually, use the same flag — `ngrok http --host-header=rewrite 3000` — otherwise ngrok forwards the public ngrok hostname as `Host` and the server rejects it with `403`.
|
|
334
338
|
|
|
335
339
|
### CLI Options (all modes)
|
|
336
340
|
|
|
@@ -449,6 +453,8 @@ For more details, see the [official MCP documentation](https://modelcontextproto
|
|
|
449
453
|
| `ANKI_CONNECT_API_KEY` | API key if configured in AnkiConnect | - |
|
|
450
454
|
| `ANKI_CONNECT_TIMEOUT` | Request timeout in ms | `5000` |
|
|
451
455
|
| `READ_ONLY` | Enable read-only mode (`true` or `1`) | `false` |
|
|
456
|
+
| `ALLOWED_HOSTS` | HTTP mode: extra `Host` header values to accept beyond loopback (comma-separated hostnames). Required when binding to a LAN/public address or running behind a reverse proxy. See [HTTP Mode Configuration](#http-mode-configuration). | loopback only |
|
|
457
|
+
| `ALLOWED_ORIGINS` | HTTP mode: comma-separated allowlist of browser `Origin`/`Referer` patterns (wildcards supported, e.g. `https://*.ngrok.io`). | `http://localhost:*,http://127.0.0.1:*,https://localhost:*,https://127.0.0.1:*` |
|
|
452
458
|
| `TUNNEL_SERVER_URL` | Tunnel server WebSocket URL (tunnel mode only) | `wss://tunnel.ankimcp.ai` |
|
|
453
459
|
| `MEDIA_ALLOWED_TYPES` | Extra MIME types to allow for file path imports (comma-separated, e.g., `application/pdf`) | - |
|
|
454
460
|
| `MEDIA_IMPORT_DIR` | Restrict file path imports to this directory | - |
|
|
@@ -520,6 +526,14 @@ These protections apply to `storeMediaFile`, `retrieveMediaFile`, `deleteMediaFi
|
|
|
520
526
|
|
|
521
527
|
> Path traversal vulnerability reported by [Hideaki Takahashi](https://github.com/Koukyosyumei).
|
|
522
528
|
|
|
529
|
+
### DNS-Rebinding Protection (HTTP transport)
|
|
530
|
+
|
|
531
|
+
When running in HTTP mode, the server validates the `Host` header on every request. By default only loopback hosts (`localhost`, `127.0.0.1`, `::1`) are accepted, regardless of port. `Host` is a browser-forbidden header, so a malicious web page cannot forge it — this closes the DNS-rebinding path where a rebound page reaches the local server with a spoofed `Host` and no `Origin`, and reaches the MCP tools. A disallowed `Host` is rejected with `403`.
|
|
532
|
+
|
|
533
|
+
If you bind to `0.0.0.0`, run behind a reverse proxy, or expose a public tunnel domain, set `ALLOWED_HOSTS` (comma-separated hostnames) to permit those hosts. When tunneling with ngrok the server uses `--host-header=rewrite`, so the upstream still sees a loopback `Host`. See [HTTP Mode Configuration](#http-mode-configuration) for the full list of options.
|
|
534
|
+
|
|
535
|
+
> DNS-rebinding vulnerability reported by [avishaigo-commits](https://github.com/avishaigo-commits) and [yotampe-pluto](https://github.com/yotampe-pluto).
|
|
536
|
+
|
|
523
537
|
## Privacy Policy
|
|
524
538
|
|
|
525
539
|
This MCP server runs locally on your machine and collects no telemetry, analytics, or usage data.
|
|
@@ -610,13 +624,26 @@ npm run build # Builds once, creates dist/ with all three entry points
|
|
|
610
624
|
**Environment Variables:**
|
|
611
625
|
- `PORT` - HTTP server port (default: 3000)
|
|
612
626
|
- `HOST` - Bind address (default: 127.0.0.1 for localhost-only)
|
|
613
|
-
- `
|
|
627
|
+
- `ALLOWED_HOSTS` - Comma-separated extra `Host` header values to accept beyond the built-in loopback set (`localhost`, `127.0.0.1`, `::1`). Hostname-only and port-agnostic. Default: loopback only.
|
|
628
|
+
- `ALLOWED_ORIGINS` - Comma-separated allowlist of browser `Origin`/`Referer` patterns; wildcards supported (e.g. `https://*.ngrok.io`). Default: `http://localhost:*,http://127.0.0.1:*,https://localhost:*,https://127.0.0.1:*`.
|
|
614
629
|
- `LOG_LEVEL` - Logging level (default: info)
|
|
615
630
|
|
|
616
631
|
**Security:**
|
|
617
|
-
-
|
|
618
|
-
-
|
|
619
|
-
-
|
|
632
|
+
- **Host header validation (DNS-rebinding protection)** — every HTTP request must carry a `Host` header that matches the allowlist. By default only loopback hosts (`localhost`, `127.0.0.1`, `::1`) are accepted, regardless of port. `Host` is a browser-forbidden header, so a malicious web page cannot forge it — this closes the [DNS-rebinding](https://github.com/ankimcp/anki-mcp-server/security/advisories) path where a rebound page reaches the server with a spoofed `Host` and no `Origin`. A disallowed `Host` is rejected with `403`.
|
|
633
|
+
- **Origin header validation** — browser requests with a present-but-disallowed `Origin`/`Referer` are rejected. Requests with **no** `Origin` (curl, Postman, MCP-over-HTTP clients) are allowed; Host validation is the defense against rebinding.
|
|
634
|
+
- Binds to localhost (127.0.0.1) by default.
|
|
635
|
+
- No authentication in current version (OAuth support planned).
|
|
636
|
+
|
|
637
|
+
**Exposing HTTP mode beyond localhost** — if you bind to a LAN/public address or put the server behind a reverse proxy or public domain, you **must** set `ALLOWED_HOSTS` to the hostname(s) clients will use, otherwise every non-loopback request is rejected with `403`:
|
|
638
|
+
|
|
639
|
+
```bash
|
|
640
|
+
# Bind to all interfaces and accept the machine's LAN name + a public domain
|
|
641
|
+
ALLOWED_HOSTS=my-nas.local,anki.example.com PORT=8080 HOST=0.0.0.0 node dist/main-http.js
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
When you bind to `0.0.0.0`/`::` without `ALLOWED_HOSTS`, the server logs a startup warning that only loopback `Host` headers will be accepted.
|
|
645
|
+
|
|
646
|
+
> **Docker / reverse proxy / public domain:** the same rule applies. In Docker, requests usually arrive with the container's published hostname or the proxy's `Host`, so set `ALLOWED_HOSTS` accordingly. A reverse proxy (nginx, Caddy, Traefik) should either forward the original `Host` and have that hostname listed in `ALLOWED_HOSTS`, or rewrite the upstream `Host` to `localhost`. The built-in `--ngrok` integration handles this automatically (see below).
|
|
620
647
|
|
|
621
648
|
**Example: Running Modes**
|
|
622
649
|
```bash
|
package/dist/app.module.js
CHANGED
|
@@ -9,8 +9,11 @@ var AppModule_1;
|
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
10
|
exports.AppModule = void 0;
|
|
11
11
|
const common_1 = require("@nestjs/common");
|
|
12
|
+
const core_1 = require("@nestjs/core");
|
|
12
13
|
const config_1 = require("@nestjs/config");
|
|
13
14
|
const mcp_nest_1 = require("@rekog/mcp-nest");
|
|
15
|
+
const origin_validation_guard_1 = require("./http/guards/origin-validation.guard");
|
|
16
|
+
const host_validation_guard_1 = require("./http/guards/host-validation.guard");
|
|
14
17
|
const essential_1 = require("./mcp/primitives/essential");
|
|
15
18
|
const gui_1 = require("./mcp/primitives/gui");
|
|
16
19
|
const app_config_service_1 = require("./app-config.service");
|
|
@@ -111,6 +114,14 @@ let AppModule = AppModule_1 = class AppModule {
|
|
|
111
114
|
useValue: validatedConfig,
|
|
112
115
|
},
|
|
113
116
|
app_config_service_1.AppConfigService,
|
|
117
|
+
{
|
|
118
|
+
provide: core_1.APP_GUARD,
|
|
119
|
+
useClass: origin_validation_guard_1.OriginValidationGuard,
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
provide: core_1.APP_GUARD,
|
|
123
|
+
useClass: host_validation_guard_1.HostValidationGuard,
|
|
124
|
+
},
|
|
114
125
|
...essential_1.ESSENTIAL_MCP_TOOLS,
|
|
115
126
|
...gui_1.GUI_MCP_TOOLS,
|
|
116
127
|
],
|
package/dist/app.module.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.module.js","sourceRoot":"","sources":["../src/app.module.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"app.module.js","sourceRoot":"","sources":["../src/app.module.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
export declare const DEFAULT_ALLOWED_ORIGINS: string[];
|
|
2
3
|
export declare const configSchema: z.ZodObject<{
|
|
3
4
|
port: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
4
5
|
host: z.ZodDefault<z.ZodString>;
|
|
@@ -7,6 +8,8 @@ export declare const configSchema: z.ZodObject<{
|
|
|
7
8
|
production: "production";
|
|
8
9
|
test: "test";
|
|
9
10
|
}>>;
|
|
11
|
+
allowedHosts: z.ZodDefault<z.ZodPreprocess<z.ZodArray<z.ZodString>>>;
|
|
12
|
+
allowedOrigins: z.ZodDefault<z.ZodPreprocess<z.ZodArray<z.ZodString>>>;
|
|
10
13
|
ankiConnect: z.ZodObject<{
|
|
11
14
|
url: z.ZodDefault<z.ZodString>;
|
|
12
15
|
apiKey: z.ZodOptional<z.ZodString>;
|
|
@@ -1,12 +1,35 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.configSchema = void 0;
|
|
3
|
+
exports.configSchema = exports.DEFAULT_ALLOWED_ORIGINS = void 0;
|
|
4
4
|
exports.transformEnvToConfig = transformEnvToConfig;
|
|
5
5
|
const zod_1 = require("zod");
|
|
6
|
+
exports.DEFAULT_ALLOWED_ORIGINS = [
|
|
7
|
+
"http://localhost:*",
|
|
8
|
+
"http://127.0.0.1:*",
|
|
9
|
+
"https://localhost:*",
|
|
10
|
+
"https://127.0.0.1:*",
|
|
11
|
+
];
|
|
12
|
+
function parseCsvList(val) {
|
|
13
|
+
if (val === undefined || val === null)
|
|
14
|
+
return undefined;
|
|
15
|
+
if (Array.isArray(val))
|
|
16
|
+
return val;
|
|
17
|
+
if (typeof val !== "string")
|
|
18
|
+
return undefined;
|
|
19
|
+
const items = val
|
|
20
|
+
.split(",")
|
|
21
|
+
.map((item) => item.trim())
|
|
22
|
+
.filter((item) => item.length > 0);
|
|
23
|
+
return items;
|
|
24
|
+
}
|
|
6
25
|
exports.configSchema = zod_1.z.object({
|
|
7
26
|
port: zod_1.z.coerce.number().int().positive().default(3000),
|
|
8
27
|
host: zod_1.z.string().default("127.0.0.1"),
|
|
9
28
|
nodeEnv: zod_1.z.enum(["development", "production", "test"]).default("development"),
|
|
29
|
+
allowedHosts: zod_1.z.preprocess(parseCsvList, zod_1.z.array(zod_1.z.string())).default([]),
|
|
30
|
+
allowedOrigins: zod_1.z
|
|
31
|
+
.preprocess(parseCsvList, zod_1.z.array(zod_1.z.string()))
|
|
32
|
+
.default(exports.DEFAULT_ALLOWED_ORIGINS),
|
|
10
33
|
ankiConnect: zod_1.z.object({
|
|
11
34
|
url: zod_1.z.string().url().default("http://localhost:8765"),
|
|
12
35
|
apiKey: zod_1.z.string().optional(),
|
|
@@ -35,6 +58,8 @@ function transformEnvToConfig(env) {
|
|
|
35
58
|
port: env.PORT,
|
|
36
59
|
host: env.HOST,
|
|
37
60
|
nodeEnv: env.NODE_ENV,
|
|
61
|
+
allowedHosts: env.ALLOWED_HOSTS,
|
|
62
|
+
allowedOrigins: env.ALLOWED_ORIGINS,
|
|
38
63
|
ankiConnect: {
|
|
39
64
|
url: env.ANKI_CONNECT_URL,
|
|
40
65
|
apiKey: env.ANKI_CONNECT_API_KEY,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.schema.js","sourceRoot":"","sources":["../../src/config/config.schema.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"config.schema.js","sourceRoot":"","sources":["../../src/config/config.schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { CanActivate, ExecutionContext } from "@nestjs/common";
|
|
2
|
+
import { type AppConfig } from "../../config";
|
|
3
|
+
export declare function shouldWarnLoopbackOnly(host: string, allowedHosts: string[]): boolean;
|
|
4
|
+
export declare class HostValidationGuard implements CanActivate {
|
|
5
|
+
private readonly logger;
|
|
6
|
+
private readonly allowedHosts;
|
|
7
|
+
private static readonly LOOPBACK_HOSTS;
|
|
8
|
+
constructor(config: AppConfig);
|
|
9
|
+
canActivate(context: ExecutionContext): boolean;
|
|
10
|
+
private static extractHostname;
|
|
11
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
var HostValidationGuard_1;
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.HostValidationGuard = void 0;
|
|
17
|
+
exports.shouldWarnLoopbackOnly = shouldWarnLoopbackOnly;
|
|
18
|
+
const common_1 = require("@nestjs/common");
|
|
19
|
+
const config_1 = require("../../config");
|
|
20
|
+
const LOOPBACK_BIND_HOSTS = new Set(["localhost", "127.0.0.1", "::1"]);
|
|
21
|
+
function shouldWarnLoopbackOnly(host, allowedHosts) {
|
|
22
|
+
const normalized = host.trim().toLowerCase();
|
|
23
|
+
const isLoopbackBind = LOOPBACK_BIND_HOSTS.has(normalized);
|
|
24
|
+
return !isLoopbackBind && allowedHosts.length === 0;
|
|
25
|
+
}
|
|
26
|
+
let HostValidationGuard = class HostValidationGuard {
|
|
27
|
+
static { HostValidationGuard_1 = this; }
|
|
28
|
+
logger = new common_1.Logger(HostValidationGuard_1.name);
|
|
29
|
+
allowedHosts;
|
|
30
|
+
static LOOPBACK_HOSTS = ["localhost", "127.0.0.1", "::1"];
|
|
31
|
+
constructor(config) {
|
|
32
|
+
this.allowedHosts = new Set([
|
|
33
|
+
...HostValidationGuard_1.LOOPBACK_HOSTS,
|
|
34
|
+
...config.allowedHosts.map((host) => HostValidationGuard_1.extractHostname(host)),
|
|
35
|
+
].filter((host) => host.length > 0));
|
|
36
|
+
}
|
|
37
|
+
canActivate(context) {
|
|
38
|
+
const request = context.switchToHttp().getRequest();
|
|
39
|
+
const hostHeader = request.headers.host;
|
|
40
|
+
if (!hostHeader || hostHeader.trim().length === 0) {
|
|
41
|
+
this.logger.warn("Rejected request with missing Host header (DNS-rebinding protection)");
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
const hostname = HostValidationGuard_1.extractHostname(hostHeader);
|
|
45
|
+
if (this.allowedHosts.has(hostname)) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
this.logger.warn(`Rejected request with disallowed Host header: "${hostHeader}". ` +
|
|
49
|
+
`Allowed hosts are loopback by default; set ALLOWED_HOSTS ` +
|
|
50
|
+
`(comma-separated hostnames) to permit additional hosts.`);
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
static extractHostname(value) {
|
|
54
|
+
const trimmed = value.trim().toLowerCase();
|
|
55
|
+
if (trimmed.length === 0) {
|
|
56
|
+
return "";
|
|
57
|
+
}
|
|
58
|
+
if (trimmed.startsWith("[")) {
|
|
59
|
+
const closing = trimmed.indexOf("]");
|
|
60
|
+
if (closing !== -1) {
|
|
61
|
+
return trimmed.slice(1, closing);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const firstColon = trimmed.indexOf(":");
|
|
65
|
+
const lastColon = trimmed.lastIndexOf(":");
|
|
66
|
+
if (firstColon !== -1 && firstColon === lastColon) {
|
|
67
|
+
return trimmed.slice(0, firstColon);
|
|
68
|
+
}
|
|
69
|
+
return trimmed;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
exports.HostValidationGuard = HostValidationGuard;
|
|
73
|
+
exports.HostValidationGuard = HostValidationGuard = HostValidationGuard_1 = __decorate([
|
|
74
|
+
(0, common_1.Injectable)(),
|
|
75
|
+
__param(0, (0, common_1.Inject)(config_1.APP_CONFIG)),
|
|
76
|
+
__metadata("design:paramtypes", [Object])
|
|
77
|
+
], HostValidationGuard);
|
|
78
|
+
//# sourceMappingURL=host-validation.guard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-validation.guard.js","sourceRoot":"","sources":["../../../src/http/guards/host-validation.guard.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { CanActivate, ExecutionContext } from "@nestjs/common";
|
|
2
|
+
import { type AppConfig } from "../../config";
|
|
2
3
|
export declare class OriginValidationGuard implements CanActivate {
|
|
3
4
|
private readonly logger;
|
|
4
5
|
private readonly allowedOrigins;
|
|
5
|
-
constructor();
|
|
6
|
+
constructor(config: AppConfig);
|
|
6
7
|
canActivate(context: ExecutionContext): boolean;
|
|
7
8
|
private matchesPattern;
|
|
8
9
|
}
|
|
@@ -8,17 +8,19 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
8
8
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
9
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
10
|
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
11
14
|
var OriginValidationGuard_1;
|
|
12
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
16
|
exports.OriginValidationGuard = void 0;
|
|
14
17
|
const common_1 = require("@nestjs/common");
|
|
18
|
+
const config_1 = require("../../config");
|
|
15
19
|
let OriginValidationGuard = OriginValidationGuard_1 = class OriginValidationGuard {
|
|
16
20
|
logger = new common_1.Logger(OriginValidationGuard_1.name);
|
|
17
21
|
allowedOrigins;
|
|
18
|
-
constructor() {
|
|
19
|
-
|
|
20
|
-
const originsEnv = process.env.ALLOWED_ORIGINS || defaultOrigins;
|
|
21
|
-
this.allowedOrigins = originsEnv.split(",").map((o) => o.trim());
|
|
22
|
+
constructor(config) {
|
|
23
|
+
this.allowedOrigins = config.allowedOrigins;
|
|
22
24
|
}
|
|
23
25
|
canActivate(context) {
|
|
24
26
|
const request = context.switchToHttp().getRequest();
|
|
@@ -49,6 +51,7 @@ let OriginValidationGuard = OriginValidationGuard_1 = class OriginValidationGuar
|
|
|
49
51
|
exports.OriginValidationGuard = OriginValidationGuard;
|
|
50
52
|
exports.OriginValidationGuard = OriginValidationGuard = OriginValidationGuard_1 = __decorate([
|
|
51
53
|
(0, common_1.Injectable)(),
|
|
52
|
-
|
|
54
|
+
__param(0, (0, common_1.Inject)(config_1.APP_CONFIG)),
|
|
55
|
+
__metadata("design:paramtypes", [Object])
|
|
53
56
|
], OriginValidationGuard);
|
|
54
57
|
//# sourceMappingURL=origin-validation.guard.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"origin-validation.guard.js","sourceRoot":"","sources":["../../../src/http/guards/origin-validation.guard.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"origin-validation.guard.js","sourceRoot":"","sources":["../../../src/http/guards/origin-validation.guard.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAMwB;AAExB,yCAAsD;AAmB/C,IAAM,qBAAqB,6BAA3B,MAAM,qBAAqB;IACf,MAAM,GAAG,IAAI,eAAM,CAAC,uBAAqB,CAAC,IAAI,CAAC,CAAC;IAChD,cAAc,CAAW;IAE1C,YAAgC,MAAiB;QAC/C,IAAI,CAAC,cAAc,GAAG,MAAM,CAAC,cAAc,CAAC;IAC9C,CAAC;IAED,WAAW,CAAC,OAAyB;QACnC,MAAM,OAAO,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAW,CAAC;QAC7D,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC;QAGjE,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,IAAI,CAAC;QACd,CAAC;QAGD,MAAM,SAAS,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,aAAa,EAAE,EAAE,CAC3D,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,aAAa,CAAC,CAC3C,CAAC;QAEF,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,8CAA8C,MAAM,EAAE,CAAC,CAAC;QAC3E,CAAC;QAED,OAAO,SAAS,CAAC;IACnB,CAAC;IASO,cAAc,CAAC,MAAc,EAAE,OAAe;QAEpD,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;YACvB,OAAO,IAAI,CAAC;QACd,CAAC;QAGD,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAC1B,MAAM,YAAY,GAAG,OAAO;iBACzB,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC;iBACrB,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;YACxB,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,IAAI,YAAY,GAAG,CAAC,CAAC;YAC9C,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC5B,CAAC;QAED,OAAO,KAAK,CAAC;IACf,CAAC;CACF,CAAA;AArDY,sDAAqB;gCAArB,qBAAqB;IADjC,IAAA,mBAAU,GAAE;IAKE,WAAA,IAAA,eAAM,EAAC,mBAAU,CAAC,CAAA;;GAJpB,qBAAqB,CAqDjC"}
|
package/dist/main-http.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const core_1 = require("@nestjs/core");
|
|
4
|
+
const common_1 = require("@nestjs/common");
|
|
4
5
|
const app_module_1 = require("./app.module");
|
|
5
6
|
const bootstrap_1 = require("./bootstrap");
|
|
6
|
-
const origin_validation_guard_1 = require("./http/guards/origin-validation.guard");
|
|
7
7
|
const cli_1 = require("./cli");
|
|
8
8
|
const cli_2 = require("./cli");
|
|
9
9
|
const ngrok_service_1 = require("./services/ngrok.service");
|
|
10
10
|
const config_1 = require("./config");
|
|
11
|
+
const host_validation_guard_1 = require("./http/guards/host-validation.guard");
|
|
11
12
|
async function bootstrap() {
|
|
12
13
|
(0, cli_1.checkForUpdates)();
|
|
13
14
|
const options = (0, cli_1.parseCliArgs)();
|
|
@@ -28,7 +29,15 @@ async function bootstrap() {
|
|
|
28
29
|
logger: loggerService,
|
|
29
30
|
bufferLogs: true,
|
|
30
31
|
});
|
|
31
|
-
|
|
32
|
+
const validatedConfig = config_1.configSchema.parse((0, config_1.transformEnvToConfig)(configInput));
|
|
33
|
+
const boundHost = options.host;
|
|
34
|
+
if ((0, host_validation_guard_1.shouldWarnLoopbackOnly)(boundHost, validatedConfig.allowedHosts)) {
|
|
35
|
+
new common_1.Logger("HttpBootstrap").warn(`Server is bound to ${boundHost} but ALLOWED_HOSTS is not set. ` +
|
|
36
|
+
`For DNS-rebinding protection, only loopback Host headers ` +
|
|
37
|
+
`(localhost, 127.0.0.1, ::1) are accepted — requests via a LAN or ` +
|
|
38
|
+
`public hostname will be rejected with 403. Set ALLOWED_HOSTS ` +
|
|
39
|
+
`(comma-separated hostnames) to permit them.`);
|
|
40
|
+
}
|
|
32
41
|
await app.listen(options.port, options.host);
|
|
33
42
|
let ngrokUrl;
|
|
34
43
|
if (options.ngrok) {
|
package/dist/main-http.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main-http.js","sourceRoot":"","sources":["../src/main-http.ts"],"names":[],"mappings":";;AAAA,uCAA2C;AAC3C,6CAAyC;AACzC,2CAIqB;AACrB
|
|
1
|
+
{"version":3,"file":"main-http.js","sourceRoot":"","sources":["../src/main-http.ts"],"names":[],"mappings":"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"}
|
|
@@ -18,7 +18,7 @@ class NgrokService {
|
|
|
18
18
|
"Or download from: https://ngrok.com/download\n" +
|
|
19
19
|
"Then setup auth: ngrok config add-authtoken <your-token>");
|
|
20
20
|
}
|
|
21
|
-
this.process = (0, child_process_1.spawn)("ngrok", ["http", port.toString()], {
|
|
21
|
+
this.process = (0, child_process_1.spawn)("ngrok", ["http", "--host-header=rewrite", port.toString()], {
|
|
22
22
|
stdio: "pipe",
|
|
23
23
|
});
|
|
24
24
|
if (!this.cleanupHandlersRegistered) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngrok.service.js","sourceRoot":"","sources":["../../src/services/ngrok.service.ts"],"names":[],"mappings":";;;;;;AAAA,iDAAoD;AACpD,gDAAwB;AACxB,2CAAwC;AAOxC,MAAa,YAAY;IACN,MAAM,GAAG,IAAI,eAAM,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IAChD,OAAO,GAAwB,IAAI,CAAC;IACpC,yBAAyB,GAAG,KAAK,CAAC;IAQ1C,KAAK,CAAC,KAAK,CAAC,IAAY;QAEtB,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC,EAAE,CAAC;YACrC,MAAM,IAAI,KAAK,CACb,2BAA2B;gBACzB,yCAAyC;gBACzC,gDAAgD;gBAChD,0DAA0D,CAC7D,CAAC;QACJ,CAAC;
|
|
1
|
+
{"version":3,"file":"ngrok.service.js","sourceRoot":"","sources":["../../src/services/ngrok.service.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anki-mcp-http",
|
|
3
3
|
"mcpName": "ai.ankimcp/anki-mcp-server",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.21.0",
|
|
5
5
|
"description": "Model Context Protocol server for Anki - enables AI assistants to interact with your Anki flashcards",
|
|
6
6
|
"author": "Anatoly Tarnavsky",
|
|
7
7
|
"private": false,
|