veryfront 0.1.1213 → 0.1.1215
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/esm/deno.js +1 -1
- package/esm/src/agent/conversation/delegation-policy.d.ts +1 -3
- package/esm/src/agent/conversation/delegation-policy.d.ts.map +1 -1
- package/esm/src/agent/conversation/delegation-policy.js +1 -3
- package/esm/src/agent/index.d.ts +1 -1
- package/esm/src/agent/index.d.ts.map +1 -1
- package/esm/src/agent/index.js +1 -1
- package/esm/src/agent/runtime/agent-loop-skill-state.d.ts +10 -12
- package/esm/src/agent/runtime/agent-loop-skill-state.d.ts.map +1 -1
- package/esm/src/agent/runtime/agent-loop-skill-state.js +11 -17
- package/esm/src/agent/runtime/agent-runtime-step.d.ts +0 -1
- package/esm/src/agent/runtime/agent-runtime-step.d.ts.map +1 -1
- package/esm/src/agent/runtime/agent-runtime-step.js +2 -2
- package/esm/src/agent/runtime/index.d.ts +1 -1
- package/esm/src/agent/runtime/index.d.ts.map +1 -1
- package/esm/src/agent/runtime/index.js +7 -13
- package/esm/src/agent/runtime/load-skill-tool.d.ts +2 -2
- package/esm/src/agent/runtime/load-skill-tool.d.ts.map +1 -1
- package/esm/src/agent/runtime/load-skill-tool.js +12 -68
- package/esm/src/agent/runtime/project-skill-catalog.d.ts.map +1 -1
- package/esm/src/agent/runtime/project-skill-catalog.js +1 -6
- package/esm/src/agent/runtime/skill-metadata.d.ts +4 -18
- package/esm/src/agent/runtime/skill-metadata.d.ts.map +1 -1
- package/esm/src/agent/runtime/skill-metadata.js +17 -78
- package/esm/src/agent/runtime/skill-policy-enforcement.d.ts +1 -5
- package/esm/src/agent/runtime/skill-policy-enforcement.d.ts.map +1 -1
- package/esm/src/agent/runtime/skill-policy-enforcement.js +12 -55
- package/esm/src/agent/runtime/skill-prompt.d.ts +2 -2
- package/esm/src/agent/runtime/skill-prompt.d.ts.map +1 -1
- package/esm/src/agent/runtime/skill-prompt.js +8 -44
- package/esm/src/html/hydration-script-builder/hydration-runtime.generated.js +1 -1
- package/esm/src/platform/adapters/index.d.ts +25 -0
- package/esm/src/platform/adapters/index.d.ts.map +1 -0
- package/esm/src/platform/adapters/index.js +23 -0
- package/esm/src/platform/adapters/security/index.d.ts +8 -0
- package/esm/src/platform/adapters/security/index.d.ts.map +1 -0
- package/esm/src/platform/adapters/security/index.js +7 -0
- package/esm/src/platform/adapters/token/factory.d.ts +10 -0
- package/esm/src/platform/adapters/token/factory.d.ts.map +1 -0
- package/esm/src/platform/adapters/token/factory.js +79 -0
- package/esm/src/platform/adapters/token/index.d.ts +9 -0
- package/esm/src/platform/adapters/token/index.d.ts.map +1 -0
- package/esm/src/platform/adapters/token/index.js +8 -0
- package/esm/src/platform/adapters/token/integration.d.ts +6 -0
- package/esm/src/platform/adapters/token/integration.d.ts.map +1 -0
- package/esm/src/platform/adapters/token/integration.js +61 -0
- package/esm/src/platform/adapters/token/veryfront/adapter.d.ts +22 -0
- package/esm/src/platform/adapters/token/veryfront/adapter.d.ts.map +1 -0
- package/esm/src/platform/adapters/token/veryfront/adapter.js +88 -0
- package/esm/src/platform/adapters/token/veryfront/api-client.d.ts +15 -0
- package/esm/src/platform/adapters/token/veryfront/api-client.d.ts.map +1 -0
- package/esm/src/platform/adapters/token/veryfront/api-client.js +212 -0
- package/esm/src/platform/adapters/token/veryfront/index.d.ts +10 -0
- package/esm/src/platform/adapters/token/veryfront/index.d.ts.map +1 -0
- package/esm/src/platform/adapters/token/veryfront/index.js +9 -0
- package/esm/src/platform/adapters/token/veryfront/memory-adapter.d.ts +20 -0
- package/esm/src/platform/adapters/token/veryfront/memory-adapter.d.ts.map +1 -0
- package/esm/src/platform/adapters/token/veryfront/memory-adapter.js +52 -0
- package/esm/src/platform/adapters/token/veryfront/types.d.ts +86 -0
- package/esm/src/platform/adapters/token/veryfront/types.d.ts.map +1 -0
- package/esm/src/platform/adapters/token/veryfront/types.js +81 -0
- package/esm/src/platform/compat/http/pinned-fetch.d.ts +1 -1
- package/esm/src/proxy/handler.d.ts.map +1 -1
- package/esm/src/proxy/handler.js +17 -1
- package/esm/src/security/http/auth.d.ts.map +1 -1
- package/esm/src/security/http/auth.js +7 -0
- package/esm/src/security/http/config.d.ts +5 -0
- package/esm/src/security/http/config.d.ts.map +1 -1
- package/esm/src/security/http/config.js +16 -1
- package/esm/src/security/http/csp-report-endpoint.d.ts +29 -0
- package/esm/src/security/http/csp-report-endpoint.d.ts.map +1 -0
- package/esm/src/security/http/csp-report-endpoint.js +30 -0
- package/esm/src/security/http/csrf/csrf-handler.d.ts.map +1 -1
- package/esm/src/security/http/csrf/csrf-handler.js +6 -0
- package/esm/src/security/http/derived-csp-cache.d.ts +33 -0
- package/esm/src/security/http/derived-csp-cache.d.ts.map +1 -0
- package/esm/src/security/http/derived-csp-cache.js +101 -0
- package/esm/src/security/http/derived-csp-origins.d.ts +54 -0
- package/esm/src/security/http/derived-csp-origins.d.ts.map +1 -0
- package/esm/src/security/http/derived-csp-origins.js +156 -0
- package/esm/src/security/http/response/security-handler.d.ts +1 -2
- package/esm/src/security/http/response/security-handler.d.ts.map +1 -1
- package/esm/src/security/http/response/security-handler.js +25 -9
- package/esm/src/security/sandbox/constants.d.ts +3 -0
- package/esm/src/security/sandbox/constants.d.ts.map +1 -0
- package/esm/src/security/sandbox/constants.js +2 -0
- package/esm/src/security/sandbox/deno-sandbox.d.ts +71 -0
- package/esm/src/security/sandbox/deno-sandbox.d.ts.map +1 -0
- package/esm/src/security/sandbox/deno-sandbox.js +184 -0
- package/esm/src/security/sandbox/permission-system.d.ts +11 -0
- package/esm/src/security/sandbox/permission-system.d.ts.map +1 -0
- package/esm/src/security/sandbox/permission-system.js +85 -0
- package/esm/src/server/handlers/request/csp-report.handler.d.ts +56 -0
- package/esm/src/server/handlers/request/csp-report.handler.d.ts.map +1 -0
- package/esm/src/server/handlers/request/csp-report.handler.js +189 -0
- package/esm/src/server/runtime-handler/index.d.ts +1 -1
- package/esm/src/server/runtime-handler/index.d.ts.map +1 -1
- package/esm/src/server/runtime-handler/index.js +3 -0
- package/esm/src/server/runtime-handler/project-runtime-context.d.ts.map +1 -1
- package/esm/src/server/runtime-handler/project-runtime-context.js +97 -1
- package/esm/src/server/utils/domain-parser.d.ts +9 -0
- package/esm/src/server/utils/domain-parser.d.ts.map +1 -1
- package/esm/src/server/utils/domain-parser.js +11 -0
- package/esm/src/skill/allowed-tools.d.ts +18 -42
- package/esm/src/skill/allowed-tools.d.ts.map +1 -1
- package/esm/src/skill/allowed-tools.js +31 -182
- package/esm/src/skill/index.d.ts +1 -1
- package/esm/src/skill/index.d.ts.map +1 -1
- package/esm/src/skill/index.js +2 -2
- package/esm/src/skill/parser.d.ts.map +1 -1
- package/esm/src/skill/parser.js +6 -37
- package/esm/src/skill/tools.d.ts.map +1 -1
- package/esm/src/skill/tools.js +2 -3
- package/esm/src/skill/types.d.ts +1 -3
- package/esm/src/skill/types.d.ts.map +1 -1
- package/esm/src/skill/validation.d.ts.map +1 -1
- package/esm/src/skill/validation.js +31 -3
- package/esm/src/types/server.d.ts +10 -0
- package/esm/src/types/server.d.ts.map +1 -1
- package/esm/src/utils/cors-policy-limits.d.ts +9 -2
- package/esm/src/utils/cors-policy-limits.d.ts.map +1 -1
- package/esm/src/utils/cors-policy-limits.js +8 -1
- package/esm/src/utils/version-constant.d.ts +1 -1
- package/esm/src/utils/version-constant.js +1 -1
- package/esm/src/workflow/api.d.ts +2 -2
- package/package.json +8 -8
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSP Violation Report Handler
|
|
3
|
+
*
|
|
4
|
+
* Receives the reports the platform CSP asks browsers to send, and writes them
|
|
5
|
+
* to the server log.
|
|
6
|
+
*
|
|
7
|
+
* Without this the report-only floor is inert at the platform level: it does
|
|
8
|
+
* not enforce, so it protects nothing, and with no reporting endpoint the
|
|
9
|
+
* violations it names reach only whoever happens to open devtools on the
|
|
10
|
+
* affected page. That leaves the enforcement rollout with no instrument --
|
|
11
|
+
* no way to answer "which projects would break if we enforced?" short of
|
|
12
|
+
* breaking them and waiting for complaints, which is how the floor shipped
|
|
13
|
+
* the first time.
|
|
14
|
+
*
|
|
15
|
+
* Endpoint: POST /_vf/csp-report
|
|
16
|
+
*/
|
|
17
|
+
import { BaseHandler } from "../response/base.js";
|
|
18
|
+
import type { HandlerContext, HandlerMetadata, HandlerResult } from "../types.js";
|
|
19
|
+
export interface NormalizedViolation {
|
|
20
|
+
documentUri?: string;
|
|
21
|
+
effectiveDirective?: string;
|
|
22
|
+
blockedUri?: string;
|
|
23
|
+
disposition?: string;
|
|
24
|
+
statusCode?: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Two wire formats reach this path and both are still in the field: the legacy
|
|
28
|
+
* `application/csp-report` body with a single `csp-report` key, and the
|
|
29
|
+
* Reporting API's `application/reports+json` array. Normalize to one shape so
|
|
30
|
+
* the log has a single schema regardless of which browser sent it.
|
|
31
|
+
*/
|
|
32
|
+
export declare function normalizeReports(payload: unknown): NormalizedViolation[];
|
|
33
|
+
/**
|
|
34
|
+
* Per-window budget for log records.
|
|
35
|
+
*
|
|
36
|
+
* Charged per record rather than per request: one admission covering a whole
|
|
37
|
+
* batch would let a sender post {@link MAX_REPORTS_PER_REQUEST} violations at a
|
|
38
|
+
* time and write 16x the ceiling. The endpoint is unauthenticated, so this bound
|
|
39
|
+
* is the only thing protecting the log stream.
|
|
40
|
+
*
|
|
41
|
+
* Separate from the handler so the arithmetic can be tested as arithmetic,
|
|
42
|
+
* rather than by intercepting log output.
|
|
43
|
+
*/
|
|
44
|
+
export declare function createLogWindow(maxPerWindow?: number, windowMs?: number): {
|
|
45
|
+
/** @returns how many of `lines` may be written, and what the previous window swallowed */
|
|
46
|
+
reserve: (now: number, lines: number) => {
|
|
47
|
+
allowed: number;
|
|
48
|
+
dropped: number;
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
export declare class CspReportHandler extends BaseHandler {
|
|
52
|
+
#private;
|
|
53
|
+
metadata: HandlerMetadata;
|
|
54
|
+
handle(req: Request, ctx: HandlerContext): Promise<HandlerResult>;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=csp-report.handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csp-report.handler.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/handlers/request/csp-report.handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAmB,aAAa,EAAE,MAAM,aAAa,CAAC;AAkDnG,MAAM,WAAW,mBAAmB;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAYD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,mBAAmB,EAAE,CA6BxE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAC7B,YAAY,GAAE,MAA8B,EAC5C,QAAQ,GAAE,MAAsB,GAC/B;IACD,0FAA0F;IAC1F,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CAC/E,CAuBA;AAED,qBAAa,gBAAiB,SAAQ,WAAW;;IAC/C,QAAQ,EAAE,eAAe,CAIvB;IAOa,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;CA6CjF"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSP Violation Report Handler
|
|
3
|
+
*
|
|
4
|
+
* Receives the reports the platform CSP asks browsers to send, and writes them
|
|
5
|
+
* to the server log.
|
|
6
|
+
*
|
|
7
|
+
* Without this the report-only floor is inert at the platform level: it does
|
|
8
|
+
* not enforce, so it protects nothing, and with no reporting endpoint the
|
|
9
|
+
* violations it names reach only whoever happens to open devtools on the
|
|
10
|
+
* affected page. That leaves the enforcement rollout with no instrument --
|
|
11
|
+
* no way to answer "which projects would break if we enforced?" short of
|
|
12
|
+
* breaking them and waiting for complaints, which is how the floor shipped
|
|
13
|
+
* the first time.
|
|
14
|
+
*
|
|
15
|
+
* Endpoint: POST /_vf/csp-report
|
|
16
|
+
*/
|
|
17
|
+
import { BaseHandler } from "../response/base.js";
|
|
18
|
+
import { HTTP_NO_CONTENT, PRIORITY_HIGH } from "../../../utils/constants/index.js";
|
|
19
|
+
import { serverLogger } from "../../../utils/logger/logger.js";
|
|
20
|
+
import { CSP_REPORT_PATH } from "../../../security/http/csp-report-endpoint.js";
|
|
21
|
+
import { isRequestBodyTooLargeError, readBodyWithLimit } from "../../../security/index.js";
|
|
22
|
+
const logger = serverLogger.component("csp-report");
|
|
23
|
+
/**
|
|
24
|
+
* Reports come from browsers, unauthenticated, and anyone can post whatever
|
|
25
|
+
* they like to this path. Nothing here is trusted: the body is read against a
|
|
26
|
+
* byte budget rather than buffered whole, every logged field is truncated, and
|
|
27
|
+
* the response is always 204 so a hostile poster learns nothing about what was
|
|
28
|
+
* accepted.
|
|
29
|
+
*/
|
|
30
|
+
const MAX_BODY_BYTES = 64 * 1024;
|
|
31
|
+
const MAX_FIELD_LENGTH = 512;
|
|
32
|
+
const MAX_REPORTS_PER_REQUEST = 16;
|
|
33
|
+
/**
|
|
34
|
+
* A busy project can emit violations on every page view. Logging each one would
|
|
35
|
+
* let a single misconfigured site drown the log stream, so each handler keeps a
|
|
36
|
+
* ceiling per window and records how many it dropped.
|
|
37
|
+
*/
|
|
38
|
+
const LOG_WINDOW_MS = 60_000;
|
|
39
|
+
const MAX_LOGGED_PER_WINDOW = 100;
|
|
40
|
+
/** Control characters a poster could use to forge extra log records (CWE-117). */
|
|
41
|
+
// deno-lint-ignore no-control-regex -- intentionally matching control chars to strip them
|
|
42
|
+
const LOG_CONTROL_CHARS = /[\x00-\x1f\x7f-\x9f]/g;
|
|
43
|
+
function readField(value) {
|
|
44
|
+
if (typeof value !== "string")
|
|
45
|
+
return undefined;
|
|
46
|
+
const stripped = value.replace(LOG_CONTROL_CHARS, "");
|
|
47
|
+
if (stripped.length === 0)
|
|
48
|
+
return undefined;
|
|
49
|
+
return stripped.length > MAX_FIELD_LENGTH ? `${stripped.slice(0, MAX_FIELD_LENGTH)}…` : stripped;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Same as {@link readField}, minus the query string. A violating URL carries
|
|
53
|
+
* whatever the page was called with, which can include session identifiers and
|
|
54
|
+
* personal data; the origin and path are what identify the violation.
|
|
55
|
+
*/
|
|
56
|
+
function readUri(value) {
|
|
57
|
+
const field = readField(value);
|
|
58
|
+
if (field === undefined)
|
|
59
|
+
return undefined;
|
|
60
|
+
const cut = field.search(/[?#]/);
|
|
61
|
+
return cut === -1 ? field : field.slice(0, cut);
|
|
62
|
+
}
|
|
63
|
+
function readStatusCode(value) {
|
|
64
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
65
|
+
}
|
|
66
|
+
function readRecord(value) {
|
|
67
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
68
|
+
? value
|
|
69
|
+
: undefined;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Two wire formats reach this path and both are still in the field: the legacy
|
|
73
|
+
* `application/csp-report` body with a single `csp-report` key, and the
|
|
74
|
+
* Reporting API's `application/reports+json` array. Normalize to one shape so
|
|
75
|
+
* the log has a single schema regardless of which browser sent it.
|
|
76
|
+
*/
|
|
77
|
+
export function normalizeReports(payload) {
|
|
78
|
+
const fromBody = (body) => ({
|
|
79
|
+
// Each field has a legacy hyphenated spelling and a Reporting API
|
|
80
|
+
// camel-case one, and `violated-directive` is deprecated in favour of
|
|
81
|
+
// `effective-directive`. Reading only one spelling silently loses the
|
|
82
|
+
// directive and status, which is the data the rollout decision needs.
|
|
83
|
+
documentUri: readUri(body["document-uri"] ?? body.documentURL),
|
|
84
|
+
effectiveDirective: readField(body["effective-directive"] ?? body.effectiveDirective ?? body["violated-directive"]),
|
|
85
|
+
blockedUri: readUri(body["blocked-uri"] ?? body.blockedURL),
|
|
86
|
+
disposition: readField(body.disposition),
|
|
87
|
+
statusCode: readStatusCode(body["status-code"] ?? body.statusCode),
|
|
88
|
+
});
|
|
89
|
+
if (Array.isArray(payload)) {
|
|
90
|
+
// Filter before taking the first N: a batch may carry other report types,
|
|
91
|
+
// and slicing first would discard violations queued behind them.
|
|
92
|
+
return payload
|
|
93
|
+
.filter((entry) => readRecord(entry)?.type === "csp-violation")
|
|
94
|
+
.slice(0, MAX_REPORTS_PER_REQUEST)
|
|
95
|
+
.map((entry) => fromBody(readRecord(readRecord(entry)?.body) ?? {}));
|
|
96
|
+
}
|
|
97
|
+
const record = readRecord(payload);
|
|
98
|
+
if (!record)
|
|
99
|
+
return [];
|
|
100
|
+
const legacy = readRecord(record["csp-report"]);
|
|
101
|
+
return legacy ? [fromBody(legacy)] : [];
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Per-window budget for log records.
|
|
105
|
+
*
|
|
106
|
+
* Charged per record rather than per request: one admission covering a whole
|
|
107
|
+
* batch would let a sender post {@link MAX_REPORTS_PER_REQUEST} violations at a
|
|
108
|
+
* time and write 16x the ceiling. The endpoint is unauthenticated, so this bound
|
|
109
|
+
* is the only thing protecting the log stream.
|
|
110
|
+
*
|
|
111
|
+
* Separate from the handler so the arithmetic can be tested as arithmetic,
|
|
112
|
+
* rather than by intercepting log output.
|
|
113
|
+
*/
|
|
114
|
+
export function createLogWindow(maxPerWindow = MAX_LOGGED_PER_WINDOW, windowMs = LOG_WINDOW_MS) {
|
|
115
|
+
let startedAt = 0;
|
|
116
|
+
let logged = 0;
|
|
117
|
+
let droppedInWindow = 0;
|
|
118
|
+
return {
|
|
119
|
+
reserve(now, lines) {
|
|
120
|
+
let dropped = 0;
|
|
121
|
+
if (now - startedAt >= windowMs) {
|
|
122
|
+
dropped = droppedInWindow;
|
|
123
|
+
startedAt = now;
|
|
124
|
+
logged = 0;
|
|
125
|
+
droppedInWindow = 0;
|
|
126
|
+
}
|
|
127
|
+
const allowed = Math.min(lines, Math.max(0, maxPerWindow - logged));
|
|
128
|
+
logged += allowed;
|
|
129
|
+
droppedInWindow += lines - allowed;
|
|
130
|
+
return { allowed, dropped };
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
export class CspReportHandler extends BaseHandler {
|
|
135
|
+
metadata = {
|
|
136
|
+
name: "CspReportHandler",
|
|
137
|
+
priority: PRIORITY_HIGH,
|
|
138
|
+
patterns: [{ pattern: CSP_REPORT_PATH, exact: true, method: "POST" }],
|
|
139
|
+
};
|
|
140
|
+
// Instance state rather than module state: one handler is built per registry,
|
|
141
|
+
// so production behaviour is the same, and each test gets a fresh window
|
|
142
|
+
// without the handler having to export a reset hook it does not otherwise need.
|
|
143
|
+
#logWindow = createLogWindow();
|
|
144
|
+
async handle(req, ctx) {
|
|
145
|
+
if (!this.shouldHandle(req, ctx))
|
|
146
|
+
return this.continue();
|
|
147
|
+
if (req.method !== "POST")
|
|
148
|
+
return this.continue();
|
|
149
|
+
// Always 204, whatever the body turned out to be. A browser has nothing to
|
|
150
|
+
// do with an error, and a hostile poster should learn nothing from one.
|
|
151
|
+
const accepted = this.respond(new Response(null, { status: HTTP_NO_CONTENT }));
|
|
152
|
+
let raw;
|
|
153
|
+
try {
|
|
154
|
+
// Shared reader: Content-Length is an early hint only, the streamed byte
|
|
155
|
+
// count is authoritative, and tiny transport chunks are coalesced so
|
|
156
|
+
// chunk metadata cannot grow independently of the limit.
|
|
157
|
+
raw = await readBodyWithLimit(req, MAX_BODY_BYTES);
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
if (!isRequestBodyTooLargeError(error)) {
|
|
161
|
+
logger.debug("Unreadable CSP report body", { projectSlug: ctx.projectSlug });
|
|
162
|
+
}
|
|
163
|
+
return accepted;
|
|
164
|
+
}
|
|
165
|
+
if (raw.length === 0)
|
|
166
|
+
return accepted;
|
|
167
|
+
let payload;
|
|
168
|
+
try {
|
|
169
|
+
payload = JSON.parse(raw);
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return accepted;
|
|
173
|
+
}
|
|
174
|
+
const violations = normalizeReports(payload);
|
|
175
|
+
if (violations.length === 0)
|
|
176
|
+
return accepted;
|
|
177
|
+
const { allowed, dropped } = this.#logWindow.reserve(Date.now(), violations.length);
|
|
178
|
+
if (allowed === 0)
|
|
179
|
+
return accepted;
|
|
180
|
+
for (const violation of violations.slice(0, allowed)) {
|
|
181
|
+
logger.warn("CSP violation reported", {
|
|
182
|
+
projectSlug: ctx.projectSlug,
|
|
183
|
+
...violation,
|
|
184
|
+
...(dropped > 0 ? { droppedSincePreviousWindow: dropped } : {}),
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
return accepted;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
@@ -13,7 +13,7 @@ import type { Handler } from "../../types/index.js";
|
|
|
13
13
|
import { ApiHandlerWrapper } from "../handlers/request/api/index.js";
|
|
14
14
|
export { parseProxyEnvironment, type ProxyEnvironment } from "./proxy-environment.js";
|
|
15
15
|
/** Handler names in registration order. */
|
|
16
|
-
export declare const HANDLER_NAMES: readonly ["AuthHandler", "CsrfHandler", "HMRHandler", "CorsHandler", "HealthHandler", "MetricsHandler", "MemoryDebugHandler", "ClientLogHandler", "DevEndpointsHandler", "StylesCSSHandler", "DebugContextHandler", "OpenAPIHandler", "OpenAPIDocsHandler", "InternalAgentsListHandler", "PublicAgentsListHandler", "PublicAgentMetadataHandler", "AgentStreamHandler", "AgentRunResumeHandler", "AgentRunCancelHandler", "ProjectRunExecuteHandler", "ChannelInvokeHandler", "DevDashboardHandler", "ProjectsHandler", "StudioBridgeModulesHandler", "ProdHydrationModuleHandler", "CSSHandler", "DevFileHandler", "SnippetHandler", "StaticHandler", "LibModulesHandler", "RSCHandler", "ModuleHandler", "ApiHandlerWrapper", "MarkdownPreviewHandler", "SSRHandler", "NotFoundHandler"];
|
|
16
|
+
export declare const HANDLER_NAMES: readonly ["AuthHandler", "CsrfHandler", "HMRHandler", "CorsHandler", "HealthHandler", "MetricsHandler", "MemoryDebugHandler", "ClientLogHandler", "DevEndpointsHandler", "StylesCSSHandler", "DebugContextHandler", "OpenAPIHandler", "OpenAPIDocsHandler", "InternalAgentsListHandler", "PublicAgentsListHandler", "PublicAgentMetadataHandler", "AgentStreamHandler", "AgentRunResumeHandler", "AgentRunCancelHandler", "ProjectRunExecuteHandler", "ChannelInvokeHandler", "DevDashboardHandler", "ProjectsHandler", "StudioBridgeModulesHandler", "ProdHydrationModuleHandler", "CSSHandler", "DevFileHandler", "SnippetHandler", "CspReportHandler", "StaticHandler", "LibModulesHandler", "RSCHandler", "ModuleHandler", "ApiHandlerWrapper", "MarkdownPreviewHandler", "SSRHandler", "NotFoundHandler"];
|
|
17
17
|
/** Union of all registered handler names. */
|
|
18
18
|
export type HandlerName = (typeof HANDLER_NAMES)[number];
|
|
19
19
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/src/server/runtime-handler/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAG7D,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAChE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/src/server/runtime-handler/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAG7D,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAChE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAsCpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AA6ErE,OAAO,EAAE,qBAAqB,EAAE,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAMtF,2CAA2C;AAC3C,eAAO,MAAM,aAAa,gxBAsChB,CAAC;AAEX,6CAA6C;AAC7C,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,8CAA8C;IAC9C,SAAS,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;IAClD,mDAAmD;IACnD,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AA4DD;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,cAAc,EACvB,IAAI,GAAE,mBAAwB,GAC7B;IAAE,QAAQ,EAAE,aAAa,CAAC;IAAC,UAAU,EAAE,iBAAiB,CAAA;CAAE,CAuB5D;AAED,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,wEAAwE;IACxE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,0DAA0D;IAC1D,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,oFAAoF;IACpF,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,sFAAsF;IACtF,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oFAAoF;IACpF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,8FAA8F;IAC9F,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,uGAAuG;IACvG,kBAAkB,CAAC,EAAE,SAAS,GAAG,YAAY,CAAC;IAC9C,4EAA4E;IAC5E,6BAA6B,CAAC,EAAE,OAAO,CAAC;CACzC;AAED,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,cAAc,EACvB,IAAI,GAAE,qBAAsC,GAC3C,CAAC,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC,GAAG;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CA+ZnE;AAGD,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC"}
|
|
@@ -31,6 +31,7 @@ import { DevFileHandler } from "../handlers/dev/files/index.js";
|
|
|
31
31
|
import { DebugContextHandler } from "../handlers/dev/debug-context.handler.js";
|
|
32
32
|
import { StylesCSSHandler } from "../handlers/dev/styles-css.handler.js";
|
|
33
33
|
import { StudioBridgeModulesHandler } from "../handlers/studio/bridge-modules.handler.js";
|
|
34
|
+
import { CspReportHandler } from "../handlers/request/csp-report.handler.js";
|
|
34
35
|
import { StaticHandler } from "../handlers/request/static.handler.js";
|
|
35
36
|
import { SnippetHandler } from "../handlers/request/snippet.handler.js";
|
|
36
37
|
import { LibModulesHandler } from "../handlers/request/lib-modules.handler.js";
|
|
@@ -104,6 +105,7 @@ export const HANDLER_NAMES = [
|
|
|
104
105
|
"CSSHandler",
|
|
105
106
|
"DevFileHandler",
|
|
106
107
|
"SnippetHandler",
|
|
108
|
+
"CspReportHandler",
|
|
107
109
|
"StaticHandler",
|
|
108
110
|
"LibModulesHandler",
|
|
109
111
|
"RSCHandler",
|
|
@@ -156,6 +158,7 @@ const handlerFactories = {
|
|
|
156
158
|
CSSHandler: () => new CSSHandler(),
|
|
157
159
|
DevFileHandler: () => new DevFileHandler(),
|
|
158
160
|
SnippetHandler: () => new SnippetHandler(),
|
|
161
|
+
CspReportHandler: () => new CspReportHandler(),
|
|
159
162
|
StaticHandler: () => new StaticHandler(),
|
|
160
163
|
LibModulesHandler: () => new LibModulesHandler(),
|
|
161
164
|
RSCHandler: () => new RSCHandler(),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project-runtime-context.d.ts","sourceRoot":"","sources":["../../../../src/src/server/runtime-handler/project-runtime-context.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAG7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"project-runtime-context.d.ts","sourceRoot":"","sources":["../../../../src/src/server/runtime-handler/project-runtime-context.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,9 @@ import { getBaseLogger } from "../../utils/index.js";
|
|
|
2
2
|
import { getHostEnv } from "../../platform/compat/process.js";
|
|
3
3
|
import { prepareDeclarativeConfigContext } from "../../config/declarative-evaluator.js";
|
|
4
4
|
import { deriveSecurityContext } from "../../security/http/config.js";
|
|
5
|
+
import { getDerivedCspOrigins } from "../../security/http/derived-csp-cache.js";
|
|
6
|
+
import { resolveStyleContentVersion } from "../../html/styles-builder/content-version.js";
|
|
7
|
+
import { isExtendedFSAdapter } from "../../platform/adapters/index.js";
|
|
5
8
|
import { normalizeSourceIntegrationPolicy } from "../../integrations/source-policy.js";
|
|
6
9
|
import { createRequestContext } from "../context/request-context.js";
|
|
7
10
|
import { isProxyTrusted } from "../utils/proxy-trust.js";
|
|
@@ -157,11 +160,55 @@ export async function resolveProjectRuntimeContext(input) {
|
|
|
157
160
|
// deliberately config-less control-plane path config-less: those endpoints
|
|
158
161
|
// authenticate their signed operation envelope and do not expose an
|
|
159
162
|
// application/browser surface.
|
|
163
|
+
// Origins the project's own source references, so a project that never wrote
|
|
164
|
+
// a `security.csp` still loads its own images, video and fonts. Only passive
|
|
165
|
+
// directives are derived; see security/http/derived-csp-origins.ts for why
|
|
166
|
+
// script-src is not.
|
|
167
|
+
//
|
|
168
|
+
// Runs inside runWithContext, not after it. MultiProjectFSAdapter resolves
|
|
169
|
+
// the tenant from AsyncLocalStorage, and outside that context it either
|
|
170
|
+
// throws -- swallowed by getAllSourceFiles into an empty list that would then
|
|
171
|
+
// be cached forever -- or falls back to a default adapter, which would derive
|
|
172
|
+
// one project's origins from another's source.
|
|
173
|
+
//
|
|
174
|
+
// Derived for config-less projects too. Those are the ones this exists for:
|
|
175
|
+
// a project with no veryfront.config.* is exactly the project that never
|
|
176
|
+
// declared a policy, and gating on config would leave it on the bare floor.
|
|
177
|
+
//
|
|
178
|
+
// Excluded on the deferred path. Those control-plane endpoints authenticate a
|
|
179
|
+
// signed operation envelope, expose no browser surface, and deliberately read
|
|
180
|
+
// no outer source -- deriving there would both serve nothing and break that
|
|
181
|
+
// guarantee.
|
|
182
|
+
const derivedCsp = input.isProxyMode && projectRes.projectSlug && reqCtx.token &&
|
|
183
|
+
adapterRes.configOutcome !== "deferred"
|
|
184
|
+
? await deriveProjectCspOrigins({
|
|
185
|
+
adapter: adapterRes.adapter,
|
|
186
|
+
projectSlug: projectRes.projectSlug,
|
|
187
|
+
projectId: projectRes.projectId,
|
|
188
|
+
token: reqCtx.token,
|
|
189
|
+
releaseId: envRes.releaseId,
|
|
190
|
+
// Same precedence as the hosted config source context above. If the two
|
|
191
|
+
// disagree, derivation reads a different branch than the one that
|
|
192
|
+
// produced the config, and the origins describe content other than the
|
|
193
|
+
// page being served.
|
|
194
|
+
branch: reqCtx.branch ?? projectRes.parsedDomain?.branch,
|
|
195
|
+
environmentName: projectRes.environmentName,
|
|
196
|
+
})
|
|
197
|
+
: undefined;
|
|
160
198
|
const requestSecurity = input.isProxyMode && adapterRes.config !== undefined
|
|
161
199
|
? deriveSecurityContext(adapterRes.config, {
|
|
162
200
|
productionDefaults: envRes.resolvedEnvironment === "production",
|
|
201
|
+
derivedCsp,
|
|
163
202
|
})
|
|
164
|
-
|
|
203
|
+
// A hosted project with no config file still gets its own security context
|
|
204
|
+
// rather than the process-wide one: defaults plus whatever its source
|
|
205
|
+
// implies. `deferred` control-plane requests keep the config-less shape.
|
|
206
|
+
: input.isProxyMode && projectRes.projectSlug && adapterRes.configOutcome === "hosted-absent"
|
|
207
|
+
? deriveSecurityContext(undefined, {
|
|
208
|
+
productionDefaults: envRes.resolvedEnvironment === "production",
|
|
209
|
+
derivedCsp,
|
|
210
|
+
})
|
|
211
|
+
: undefined;
|
|
165
212
|
// Falling back here substitutes the process-wide security config for the
|
|
166
213
|
// project's, which serves a 200 whose CSP is the platform floor rather than
|
|
167
214
|
// the project's policy. The response looks correct, so nothing downstream
|
|
@@ -307,3 +354,52 @@ function createProxyGuard(req, isProxyMode, headers, proxyTrusted, identityHeade
|
|
|
307
354
|
}),
|
|
308
355
|
};
|
|
309
356
|
}
|
|
357
|
+
/**
|
|
358
|
+
* Derive a project's passive CSP origins inside its own adapter context.
|
|
359
|
+
*
|
|
360
|
+
* The source read and the snapshot identity are both taken under
|
|
361
|
+
* `runWithContext`, because the multi-project adapter selects the tenant from
|
|
362
|
+
* AsyncLocalStorage and silently yields nothing -- or the wrong project --
|
|
363
|
+
* without it.
|
|
364
|
+
*/
|
|
365
|
+
async function deriveProjectCspOrigins(args) {
|
|
366
|
+
if (!isExtendedFSAdapter(args.adapter.fs) || !args.adapter.fs.runWithContext)
|
|
367
|
+
return undefined;
|
|
368
|
+
const fs = args.adapter.fs;
|
|
369
|
+
const run = async () => {
|
|
370
|
+
const underlying = typeof fs.getUnderlyingAdapter === "function"
|
|
371
|
+
? fs.getUnderlyingAdapter()
|
|
372
|
+
: undefined;
|
|
373
|
+
if (!underlying || typeof underlying.getAllSourceFiles !== "function")
|
|
374
|
+
return undefined;
|
|
375
|
+
// Branch and environment content versions are stable while the content
|
|
376
|
+
// under them changes, so the adapter's snapshot generation is what actually
|
|
377
|
+
// moves when a preview is pushed to. Without it a preview would serve a
|
|
378
|
+
// derivation from before the push until the entry is evicted.
|
|
379
|
+
const snapshot = typeof underlying.getSourceSnapshotVersion === "function"
|
|
380
|
+
? underlying.getSourceSnapshotVersion()
|
|
381
|
+
: 0;
|
|
382
|
+
const contentVersion = `${resolveStyleContentVersion(underlying.getContentContext?.() ?? null, {
|
|
383
|
+
releaseId: args.releaseId,
|
|
384
|
+
branch: args.branch,
|
|
385
|
+
environmentName: args.environmentName,
|
|
386
|
+
})}@${snapshot}`;
|
|
387
|
+
return await getDerivedCspOrigins({
|
|
388
|
+
projectScope: args.projectSlug,
|
|
389
|
+
contentVersion,
|
|
390
|
+
loadSourceFiles: () => underlying.getAllSourceFiles(),
|
|
391
|
+
});
|
|
392
|
+
};
|
|
393
|
+
try {
|
|
394
|
+
return await fs.runWithContext(args.projectSlug, args.token, run, args.projectId, {
|
|
395
|
+
productionMode: Boolean(args.releaseId),
|
|
396
|
+
releaseId: args.releaseId ?? null,
|
|
397
|
+
branch: args.branch ?? null,
|
|
398
|
+
environmentName: args.environmentName ?? null,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
catch {
|
|
402
|
+
// Never fail a response over a CSP nicety.
|
|
403
|
+
return undefined;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
@@ -11,6 +11,15 @@ export interface ParsedDomain {
|
|
|
11
11
|
* Extract project slug and branch from domain/host header
|
|
12
12
|
*/
|
|
13
13
|
export declare function parseProjectDomain(host: string): ParsedDomain;
|
|
14
|
+
/**
|
|
15
|
+
* Whether the host is a hosted veryfront domain (veryfront.com / veryfront.org)
|
|
16
|
+
* rather than one of the local development domains.
|
|
17
|
+
*
|
|
18
|
+
* The two differ in what a project-less host means. Locally it means "no project
|
|
19
|
+
* chosen yet" and the project chooser answers; hosted it means the domain names
|
|
20
|
+
* no project at all and nothing can answer.
|
|
21
|
+
*/
|
|
22
|
+
export declare function isHostedVeryfrontDomain(host: string): boolean;
|
|
14
23
|
/**
|
|
15
24
|
* Check if a domain is a valid veryfront domain (includes veryfront.me and lvh.me for local dev)
|
|
16
25
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"domain-parser.d.ts","sourceRoot":"","sources":["../../../../src/src/server/utils/domain-parser.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,WAAW,EAAE,SAAS,GAAG,aAAa,GAAG,SAAS,GAAG,YAAY,GAAG,IAAI,CAAC;IACzE,iBAAiB,EAAE,OAAO,CAAC;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,yFAAyF;IACzF,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AA0DD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CA6G7D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAMvD;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CA2BpD;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,cAAc,EAAE,MAAM,GACrB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAIrC"}
|
|
1
|
+
{"version":3,"file":"domain-parser.d.ts","sourceRoot":"","sources":["../../../../src/src/server/utils/domain-parser.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,WAAW,EAAE,SAAS,GAAG,aAAa,GAAG,SAAS,GAAG,YAAY,GAAG,IAAI,CAAC;IACzE,iBAAiB,EAAE,OAAO,CAAC;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,yFAAyF;IACzF,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AA0DD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CA6G7D;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE7D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAMvD;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CA2BpD;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,EACZ,cAAc,EAAE,MAAM,GACrB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAIrC"}
|
|
@@ -114,6 +114,17 @@ export function parseProjectDomain(host) {
|
|
|
114
114
|
// and erases the environment from the URL, which the product decision requires.
|
|
115
115
|
return createParsedDomain(null, null, null, false, false);
|
|
116
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Whether the host is a hosted veryfront domain (veryfront.com / veryfront.org)
|
|
119
|
+
* rather than one of the local development domains.
|
|
120
|
+
*
|
|
121
|
+
* The two differ in what a project-less host means. Locally it means "no project
|
|
122
|
+
* chosen yet" and the project chooser answers; hosted it means the domain names
|
|
123
|
+
* no project at all and nothing can answer.
|
|
124
|
+
*/
|
|
125
|
+
export function isHostedVeryfrontDomain(host) {
|
|
126
|
+
return new RegExp(`^(?:.+\\.)?(${PROD_DOMAINS})$`, "i").test(stripPort(host));
|
|
127
|
+
}
|
|
117
128
|
/**
|
|
118
129
|
* Check if a domain is a valid veryfront domain (includes veryfront.me and lvh.me for local dev)
|
|
119
130
|
*/
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Skill Tool Availability
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Gates the skill infrastructure tools by what the active skill actually
|
|
5
|
+
* advertises: `load_skill_reference` needs a reference file, and
|
|
6
|
+
* `execute_skill_script` needs a script. Ordinary tools are never gated here.
|
|
7
|
+
*
|
|
8
|
+
* A skill's `allowed-tools` frontmatter is deliberately *not* enforced. The
|
|
9
|
+
* Agent Skills specification defines that field as pre-approval: tools the
|
|
10
|
+
* agent may run without prompting, not an authorization boundary. See
|
|
11
|
+
* veryfront/veryfront-issue-inbox#406.
|
|
7
12
|
*
|
|
8
13
|
* @module
|
|
9
14
|
*/
|
|
@@ -14,49 +19,20 @@ export type SkillToolAvailability = {
|
|
|
14
19
|
readonly scripts?: readonly string[];
|
|
15
20
|
};
|
|
16
21
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* Supports:
|
|
20
|
-
* - Exact match: "Read" matches "Read"
|
|
21
|
-
* - Prefix wildcard: "api:*" matches "api:list-users"
|
|
22
|
-
*/
|
|
23
|
-
export declare function matchesAllowedTool(toolName: string, pattern: string): boolean;
|
|
24
|
-
/**
|
|
25
|
-
* Layer 1: Filter tool definitions before sending to model.
|
|
22
|
+
* Filter tool definitions before sending them to the model.
|
|
26
23
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
24
|
+
* Only skill infrastructure tools are affected; every other tool passes
|
|
25
|
+
* through untouched.
|
|
29
26
|
*
|
|
30
27
|
* @param tools - Full list of tool definitions
|
|
31
|
-
* @param
|
|
28
|
+
* @param skillToolAvailability - Files the active skill advertises
|
|
32
29
|
* @returns Filtered tool definitions
|
|
33
30
|
*/
|
|
34
31
|
export declare function filterToolsForSkill<T extends {
|
|
35
32
|
name: string;
|
|
36
|
-
}>(tools: T[],
|
|
37
|
-
/**
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
* @param allowedTools - Allowed tool patterns, or undefined for no restrictions
|
|
42
|
-
* @returns true if the tool call is allowed
|
|
43
|
-
*/
|
|
44
|
-
export declare function isToolAllowedBySkill(toolName: string, allowedTools: string[] | undefined, skillToolAvailability?: SkillToolAvailability): boolean;
|
|
45
|
-
/** Filter provider-native or other name-only tool inventories through the same policy boundary. */
|
|
46
|
-
export declare function filterToolNamesForSkill(toolNames: readonly string[], allowedTools: string[] | undefined, skillToolAvailability?: SkillToolAvailability): string[];
|
|
47
|
-
/**
|
|
48
|
-
* Validate allowed-tool patterns at parse time.
|
|
49
|
-
*
|
|
50
|
-
* Ensures each pattern matches the expected format.
|
|
51
|
-
* Rejects unsupported patterns with a descriptive error (fail closed).
|
|
52
|
-
*
|
|
53
|
-
* @param patterns - Array of tool patterns to validate
|
|
54
|
-
* @returns A detached mutable copy of the validated patterns
|
|
55
|
-
* @throws If any pattern is invalid
|
|
56
|
-
*/
|
|
57
|
-
export declare function validateAllowedToolPatterns(patterns: string[]): string[];
|
|
58
|
-
/** Validate bounded allowed-tool patterns at filesystem and runtime trust boundaries. */
|
|
59
|
-
export declare function validateStrictAllowedToolPatterns(patterns: string[]): string[];
|
|
60
|
-
/** Validate, detach, and freeze an active authorization policy. */
|
|
61
|
-
export declare function snapshotAllowedToolPatterns(patterns: readonly string[]): string[];
|
|
33
|
+
}>(tools: T[], skillToolAvailability?: SkillToolAvailability): T[];
|
|
34
|
+
/** Check whether a specific tool call is available at execution time. */
|
|
35
|
+
export declare function isSkillToolAvailable(toolName: string, skillToolAvailability?: SkillToolAvailability): boolean;
|
|
36
|
+
/** Filter provider-native or other name-only tool inventories through the same boundary. */
|
|
37
|
+
export declare function filterToolNamesForSkill(toolNames: readonly string[], skillToolAvailability?: SkillToolAvailability): string[];
|
|
62
38
|
//# sourceMappingURL=allowed-tools.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"allowed-tools.d.ts","sourceRoot":"","sources":["../../../src/src/skill/allowed-tools.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"allowed-tools.d.ts","sourceRoot":"","sources":["../../../src/src/skill/allowed-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,qFAAqF;AACrF,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAClC,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC,CAAC;AA+BF;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,EAC5D,KAAK,EAAE,CAAC,EAAE,EACV,qBAAqB,CAAC,EAAE,qBAAqB,GAC5C,CAAC,EAAE,CAQL;AAED,yEAAyE;AACzE,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,EAChB,qBAAqB,CAAC,EAAE,qBAAqB,GAC5C,OAAO,CAET;AAED,4FAA4F;AAC5F,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,qBAAqB,CAAC,EAAE,qBAAqB,GAC5C,MAAM,EAAE,CAIV"}
|