@nifrajs/core 4.0.2 → 5.1.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 +6 -2
- package/dist/assurance.d.ts +86 -6
- package/dist/assurance.d.ts.map +1 -1
- package/dist/assurance.js +186 -95
- package/dist/assurance.js.map +1 -1
- package/dist/capabilities.d.ts +8 -0
- package/dist/capabilities.d.ts.map +1 -1
- package/dist/capabilities.js +53 -1
- package/dist/capabilities.js.map +1 -1
- package/dist/durable-effect.js +1 -1
- package/dist/durable-effect.js.map +1 -1
- package/dist/durable-saga.d.ts +6 -0
- package/dist/durable-saga.d.ts.map +1 -1
- package/dist/durable-saga.js +83 -10
- package/dist/durable-saga.js.map +1 -1
- package/dist/edge-kit.d.ts +8 -3
- package/dist/edge-kit.d.ts.map +1 -1
- package/dist/edge-kit.js +7 -2
- package/dist/edge-kit.js.map +1 -1
- package/dist/effect-scope.d.ts.map +1 -1
- package/dist/effect-scope.js +16 -5
- package/dist/effect-scope.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/evidence.d.ts +2 -0
- package/dist/evidence.d.ts.map +1 -1
- package/dist/evidence.js +9 -1
- package/dist/evidence.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/internal/durable-shared.d.ts +1 -2
- package/dist/internal/durable-shared.d.ts.map +1 -1
- package/dist/internal/durable-shared.js +25 -44
- package/dist/internal/durable-shared.js.map +1 -1
- package/dist/internal/durable-types.d.ts +3 -0
- package/dist/internal/durable-types.d.ts.map +1 -1
- package/dist/internal/hook-origins.d.ts +67 -0
- package/dist/internal/hook-origins.d.ts.map +1 -0
- package/dist/internal/hook-origins.js +86 -0
- package/dist/internal/hook-origins.js.map +1 -0
- package/dist/internal/route-assurance.d.ts +6 -3
- package/dist/internal/route-assurance.d.ts.map +1 -1
- package/dist/internal/route-assurance.js +29 -14
- package/dist/internal/route-assurance.js.map +1 -1
- package/dist/internal/route-compiler.d.ts +9 -0
- package/dist/internal/route-compiler.d.ts.map +1 -1
- package/dist/internal/route-compiler.js +95 -13
- package/dist/internal/route-compiler.js.map +1 -1
- package/dist/internal/route-program.d.ts +1 -1
- package/dist/internal/route-program.d.ts.map +1 -1
- package/dist/internal/route-program.js +59 -16
- package/dist/internal/route-program.js.map +1 -1
- package/dist/internal/same-origin.d.ts +11 -1
- package/dist/internal/same-origin.d.ts.map +1 -1
- package/dist/internal/same-origin.js +8 -0
- package/dist/internal/same-origin.js.map +1 -1
- package/dist/reconciliation-worker.d.ts.map +1 -1
- package/dist/reconciliation-worker.js +12 -2
- package/dist/reconciliation-worker.js.map +1 -1
- package/dist/reflection.d.ts +53 -2
- package/dist/reflection.d.ts.map +1 -1
- package/dist/reflection.js +352 -5
- package/dist/reflection.js.map +1 -1
- package/dist/router/pattern.d.ts +1 -1
- package/dist/router/pattern.d.ts.map +1 -1
- package/dist/router/pattern.js +6 -3
- package/dist/router/pattern.js.map +1 -1
- package/dist/security/baseline.d.ts +3 -0
- package/dist/security/baseline.d.ts.map +1 -1
- package/dist/security/baseline.js +9 -5
- package/dist/security/baseline.js.map +1 -1
- package/dist/server/auth.d.ts +32 -3
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +23 -0
- package/dist/server/auth.js.map +1 -1
- package/dist/server/body-lane.d.ts +8 -2
- package/dist/server/body-lane.d.ts.map +1 -1
- package/dist/server/body-lane.js +27 -2
- package/dist/server/body-lane.js.map +1 -1
- package/dist/server/body.d.ts +7 -0
- package/dist/server/body.d.ts.map +1 -1
- package/dist/server/body.js +34 -1
- package/dist/server/body.js.map +1 -1
- package/dist/server/client-ip.d.ts +3 -0
- package/dist/server/client-ip.d.ts.map +1 -1
- package/dist/server/client-ip.js +6 -5
- package/dist/server/client-ip.js.map +1 -1
- package/dist/server/context.d.ts +2 -1
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/cookies.d.ts.map +1 -1
- package/dist/server/cookies.js +13 -1
- package/dist/server/cookies.js.map +1 -1
- package/dist/server/edge.d.ts.map +1 -1
- package/dist/server/edge.js +2 -1
- package/dist/server/edge.js.map +1 -1
- package/dist/server/http.d.ts.map +1 -1
- package/dist/server/http.js +5 -1
- package/dist/server/http.js.map +1 -1
- package/dist/server/idempotency-lane.d.ts +6 -3
- package/dist/server/idempotency-lane.d.ts.map +1 -1
- package/dist/server/idempotency-lane.js +38 -17
- package/dist/server/idempotency-lane.js.map +1 -1
- package/dist/server/install.d.ts +2 -0
- package/dist/server/install.d.ts.map +1 -1
- package/dist/server/install.js +2 -0
- package/dist/server/install.js.map +1 -1
- package/dist/server/logger.d.ts.map +1 -1
- package/dist/server/logger.js +3 -1
- package/dist/server/logger.js.map +1 -1
- package/dist/server/methods.d.ts +1 -0
- package/dist/server/methods.d.ts.map +1 -1
- package/dist/server/methods.js +1 -0
- package/dist/server/methods.js.map +1 -1
- package/dist/server/node-outcome.d.ts.map +1 -1
- package/dist/server/node-outcome.js +29 -11
- package/dist/server/node-outcome.js.map +1 -1
- package/dist/server/platform-adapters.d.ts.map +1 -1
- package/dist/server/platform-adapters.js +3 -2
- package/dist/server/platform-adapters.js.map +1 -1
- package/dist/server/request-context.d.ts +2 -1
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/request-context.js +4 -1
- package/dist/server/request-context.js.map +1 -1
- package/dist/server/respond.d.ts +44 -11
- package/dist/server/respond.d.ts.map +1 -1
- package/dist/server/respond.js +192 -79
- package/dist/server/respond.js.map +1 -1
- package/dist/server/response-contract-lane.d.ts +21 -6
- package/dist/server/response-contract-lane.d.ts.map +1 -1
- package/dist/server/response-contract-lane.js +139 -14
- package/dist/server/response-contract-lane.js.map +1 -1
- package/dist/server/response-observer-runtime.d.ts.map +1 -1
- package/dist/server/response-observer-runtime.js +10 -41
- package/dist/server/response-observer-runtime.js.map +1 -1
- package/dist/server/runtime-core.d.ts +2 -0
- package/dist/server/runtime-core.d.ts.map +1 -1
- package/dist/server/runtime-core.js +2 -0
- package/dist/server/runtime-core.js.map +1 -1
- package/dist/server/server-types.d.ts +45 -10
- package/dist/server/server-types.d.ts.map +1 -1
- package/dist/server/server.d.ts +67 -32
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +481 -334
- package/dist/server/server.js.map +1 -1
- package/dist/server/sse.d.ts +11 -1
- package/dist/server/sse.d.ts.map +1 -1
- package/dist/server/sse.js +18 -3
- package/dist/server/sse.js.map +1 -1
- package/dist/server/transport-lane.d.ts +7 -1
- package/dist/server/transport-lane.d.ts.map +1 -1
- package/dist/server/transport-lane.js.map +1 -1
- package/dist/server/webhook.d.ts +5 -2
- package/dist/server/webhook.d.ts.map +1 -1
- package/dist/server/webhook.js +17 -1
- package/dist/server/webhook.js.map +1 -1
- package/dist/server/websocket.d.ts +33 -14
- package/dist/server/websocket.d.ts.map +1 -1
- package/dist/server/websocket.js +47 -12
- package/dist/server/websocket.js.map +1 -1
- package/dist/server/ws-bun.d.ts +7 -1
- package/dist/server/ws-bun.d.ts.map +1 -1
- package/dist/server/ws-bun.js +42 -2
- package/dist/server/ws-bun.js.map +1 -1
- package/dist/server/ws-hook.d.ts +15 -3
- package/dist/server/ws-hook.d.ts.map +1 -1
- package/dist/server.d.ts +4 -3
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +3 -2
- package/dist/server.js.map +1 -1
- package/dist/single-copy.d.ts +6 -0
- package/dist/single-copy.d.ts.map +1 -1
- package/dist/single-copy.js +55 -6
- package/dist/single-copy.js.map +1 -1
- package/dist/tool-contract.d.ts.map +1 -1
- package/dist/tool-contract.js +71 -40
- package/dist/tool-contract.js.map +1 -1
- package/dist/wire.d.ts +5 -0
- package/dist/wire.d.ts.map +1 -1
- package/dist/wire.js +5 -0
- package/dist/wire.js.map +1 -1
- package/dist/ws.d.ts.map +1 -1
- package/dist/ws.js +4 -1
- package/dist/ws.js.map +1 -1
- package/package.json +1 -1
- package/src/assurance.ts +299 -111
- package/src/capabilities.ts +62 -1
- package/src/durable-effect.ts +1 -1
- package/src/durable-saga.ts +94 -10
- package/src/edge-kit.ts +8 -3
- package/src/effect-scope.ts +15 -5
- package/src/errors.ts +1 -0
- package/src/evidence.ts +12 -3
- package/src/index.ts +7 -2
- package/src/internal/durable-shared.ts +25 -37
- package/src/internal/durable-types.ts +3 -0
- package/src/internal/hook-origins.ts +153 -0
- package/src/internal/route-assurance.ts +32 -18
- package/src/internal/route-compiler.ts +107 -16
- package/src/internal/route-program.ts +98 -44
- package/src/internal/same-origin.ts +14 -1
- package/src/reconciliation-worker.ts +10 -2
- package/src/reflection.ts +455 -4
- package/src/router/pattern.ts +6 -3
- package/src/security/baseline.ts +10 -5
- package/src/server/auth.ts +45 -3
- package/src/server/body-lane.ts +36 -1
- package/src/server/body.ts +34 -1
- package/src/server/client-ip.ts +8 -5
- package/src/server/context.ts +2 -1
- package/src/server/cookies.ts +12 -1
- package/src/server/edge.ts +2 -1
- package/src/server/http.ts +7 -1
- package/src/server/idempotency-lane.ts +64 -22
- package/src/server/install.ts +3 -0
- package/src/server/logger.ts +3 -1
- package/src/server/methods.ts +1 -0
- package/src/server/node-outcome.ts +34 -12
- package/src/server/platform-adapters.ts +3 -2
- package/src/server/request-context.ts +5 -0
- package/src/server/respond.ts +222 -69
- package/src/server/response-contract-lane.ts +162 -10
- package/src/server/response-observer-runtime.ts +16 -38
- package/src/server/runtime-core.ts +2 -0
- package/src/server/server-types.ts +45 -10
- package/src/server/server.ts +707 -539
- package/src/server/sse.ts +84 -55
- package/src/server/transport-lane.ts +7 -1
- package/src/server/webhook.ts +22 -3
- package/src/server/websocket.ts +83 -18
- package/src/server/ws-bun.ts +48 -1
- package/src/server/ws-hook.ts +14 -2
- package/src/server.ts +6 -1
- package/src/single-copy.ts +55 -5
- package/src/tool-contract.ts +67 -39
- package/src/wire.ts +14 -0
- package/src/ws.ts +6 -0
package/README.md
CHANGED
|
@@ -120,7 +120,9 @@ route. Split groups before the compiler reports `TS2589`, rather than weakening
|
|
|
120
120
|
client's `transport` option with the same registry. `@nifrajs/core/transport-codec` negotiates
|
|
121
121
|
bounded HTTP representations and supplies the same frame/loader adapters for WebSockets and
|
|
122
122
|
deferred data. Import `richWireCodec()` from `@nifrajs/core/transport-codec-rich`; the separate
|
|
123
|
-
subpath keeps rich-wire code out of plain JSON bundles.
|
|
123
|
+
subpath keeps rich-wire code out of plain JSON bundles. The plugin decodes a codec body before
|
|
124
|
+
routing, so a route's `bodyLimit` and its authentication run after the decode; its own `maxBytes`
|
|
125
|
+
is the bound on that work.
|
|
124
126
|
- **Typed data seam (opt in).** `@nifrajs/core/data` defines token-only operation contracts,
|
|
125
127
|
`db.read`/`db.write` capability names, an opaque request-local `RlsScope`, typed adapter requests,
|
|
126
128
|
drift snapshots, and `createDataPort(contract, adapter, { beacon: useCapability })`, which emits the
|
|
@@ -158,7 +160,9 @@ contract-first registry.
|
|
|
158
160
|
|
|
159
161
|
`response` and `errors` are typed by default but are not runtime enforcement by themselves. Install
|
|
160
162
|
`responseContract("enforce")` before the routes it should cover to validate JSON/status payloads and
|
|
161
|
-
prevent extra fields from reaching the wire. `
|
|
163
|
+
prevent extra fields from reaching the wire. That covers a `status(...)` or `c.json(...)` reply a
|
|
164
|
+
handler returns or throws, a hook returns, or `onError` answers with. `warn` preserves the original
|
|
165
|
+
payload during migration.
|
|
162
166
|
Raw `Response`, streams, redirects, and `mountFetch` remain explicit transport escape hatches and
|
|
163
167
|
must be governed by assurance/static review.
|
|
164
168
|
|
package/dist/assurance.d.ts
CHANGED
|
@@ -15,6 +15,68 @@ export type { AssuranceAttachment, AssuranceDeclaration, AssuranceEvidence, Assu
|
|
|
15
15
|
export { withRouteAssurance };
|
|
16
16
|
/** Isolated request executor used by adversarial contract verification. */
|
|
17
17
|
export type InvariantExecutor = (request: Request) => Response | Promise<Response>;
|
|
18
|
+
export type ContractTarget = "headers" | "cookies" | "params" | "body" | "query" | "response";
|
|
19
|
+
export type ContractCaseKind = "input-rejection" | "response-conformance";
|
|
20
|
+
export type ContractAuthContext = "authenticated" | "unauthenticated";
|
|
21
|
+
export interface ContractExpectedResponse {
|
|
22
|
+
readonly status: number;
|
|
23
|
+
readonly contentType?: "json" | "text";
|
|
24
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
25
|
+
/** When present, the decoded body is compared byte-structure-wise to this value. */
|
|
26
|
+
readonly body?: unknown;
|
|
27
|
+
}
|
|
28
|
+
/** A known-good request. Missing body/query values are synthesized from inspectable JSON Schema. */
|
|
29
|
+
export interface ContractWitness {
|
|
30
|
+
readonly params?: Readonly<Record<string, string>>;
|
|
31
|
+
readonly query?: unknown;
|
|
32
|
+
readonly body?: unknown;
|
|
33
|
+
readonly headers?: ConstructorParameters<typeof Headers>[0];
|
|
34
|
+
/** Explicit auth state for the prepareRequest hook; the harness never invents credentials. */
|
|
35
|
+
readonly auth?: ContractAuthContext;
|
|
36
|
+
/** Expected response for a guard/error witness. Omit for the normal success witness. */
|
|
37
|
+
readonly expected?: ContractExpectedResponse;
|
|
38
|
+
}
|
|
39
|
+
/** Stable context passed to request/rejection hooks. It contains no request payloads or secrets. */
|
|
40
|
+
export interface ContractCaseContext {
|
|
41
|
+
readonly seed: number;
|
|
42
|
+
readonly caseId: string;
|
|
43
|
+
readonly route: ReflectedRoute;
|
|
44
|
+
readonly runtime: string;
|
|
45
|
+
readonly kind: ContractCaseKind;
|
|
46
|
+
readonly target: ContractTarget;
|
|
47
|
+
readonly auth?: ContractAuthContext;
|
|
48
|
+
readonly mutation?: string;
|
|
49
|
+
}
|
|
50
|
+
/** What `invariants.setup` provisions for one L4 run. */
|
|
51
|
+
export interface InvariantSetupResult {
|
|
52
|
+
/** The isolated target for this run. Replaces `invariants.executor`. */
|
|
53
|
+
readonly executor?: InvariantExecutor;
|
|
54
|
+
/** Called once the run ends, after a failure too. */
|
|
55
|
+
readonly teardown?: () => void | Promise<void>;
|
|
56
|
+
}
|
|
57
|
+
/** Provisions a throwaway L4 target. Called once, before the first L4 request - never on import. */
|
|
58
|
+
export type InvariantSetup = () => Promise<InvariantSetupResult | undefined | void>;
|
|
59
|
+
/** Fixture options handed to the L4 contract runner (`runAdversarialContract` in `@nifrajs/testing`). */
|
|
60
|
+
export interface InvariantRunOptions {
|
|
61
|
+
/** Known-good request values keyed by `"METHOD /path"`. Required for opaque Standard Schemas. */
|
|
62
|
+
readonly witnesses?: Readonly<Record<string, ContractWitness>>;
|
|
63
|
+
/** Attach a session, real row ids or signed tokens. Return a new Request. */
|
|
64
|
+
readonly prepareRequest?: (request: Request, context: ContractCaseContext) => Request | Promise<Request>;
|
|
65
|
+
/** Override rejection semantics, for an app with a custom validation response. */
|
|
66
|
+
readonly isRejected?: (response: Response, context: ContractCaseContext) => boolean | Promise<boolean>;
|
|
67
|
+
/** Run L4 on a subset of the reflected routes. */
|
|
68
|
+
readonly include?: (route: ReflectedRoute) => boolean;
|
|
69
|
+
/** Maximum validator-proven mutations per body/query schema. Default 32. */
|
|
70
|
+
readonly maxMutationsPerInput?: number;
|
|
71
|
+
}
|
|
72
|
+
/** L4 configuration: an isolated `executor`, a `setup` that provisions one, or both. */
|
|
73
|
+
export type InvariantsConfig = InvariantRunOptions & ({
|
|
74
|
+
readonly executor: InvariantExecutor;
|
|
75
|
+
readonly setup?: InvariantSetup;
|
|
76
|
+
} | {
|
|
77
|
+
readonly executor?: InvariantExecutor;
|
|
78
|
+
readonly setup: InvariantSetup;
|
|
79
|
+
});
|
|
18
80
|
/** Canonical evidence ids emitted by Nifra's official middleware modules. */
|
|
19
81
|
export declare const NIFRA_ASSURANCE: Readonly<{
|
|
20
82
|
readonly AUTHENTICATED: "nifra.authenticated";
|
|
@@ -46,8 +108,14 @@ export declare const NIFRA_ASSURANCE: Readonly<{
|
|
|
46
108
|
*/
|
|
47
109
|
export declare function assure(app: unknown, evidence: AssuranceAttachment | readonly AssuranceAttachment[]): void;
|
|
48
110
|
export interface AssuranceRouteSelector {
|
|
49
|
-
/** Omit for every method. */
|
|
50
|
-
readonly methods?: readonly Method[];
|
|
111
|
+
/** Omit for every method. `"WS"` selects WebSocket routes. */
|
|
112
|
+
readonly methods?: readonly (Method | "WS")[];
|
|
113
|
+
/**
|
|
114
|
+
* Skip routes under these methods; `"WS"` skips WebSocket routes. Every other method matches, a
|
|
115
|
+
* custom one such as `PURGE` included, so `["GET", "HEAD", "OPTIONS", "WS"]` selects every route
|
|
116
|
+
* that can change state.
|
|
117
|
+
*/
|
|
118
|
+
readonly exceptMethods?: readonly (Method | "WS")[];
|
|
51
119
|
/** Absolute route globs. `*` matches one segment; final `**` matches zero or more. */
|
|
52
120
|
readonly paths?: readonly string[];
|
|
53
121
|
/** Restrict the rule to MCP tool routes (`true`) or non-tool routes (`false`). */
|
|
@@ -90,6 +158,12 @@ export interface AssuranceRouteSelector {
|
|
|
90
158
|
* already carries `schema.body`, so this reads what the route declared, not where it lives.
|
|
91
159
|
*/
|
|
92
160
|
readonly hasBody?: boolean;
|
|
161
|
+
/**
|
|
162
|
+
* Match routes by whether they declare a success `response` schema - the routes a response contract
|
|
163
|
+
* can hold to anything. Pair it with `require: ["nifra.response-contract"]` to say "every route
|
|
164
|
+
* that declares a response enforces it" without listing the routes that declare none.
|
|
165
|
+
*/
|
|
166
|
+
readonly hasResponse?: boolean;
|
|
93
167
|
/**
|
|
94
168
|
* Match routes by their effective transport body policy. `"unlimited"` is the explicit
|
|
95
169
|
* streaming/upload exemption; `"bounded"` is any finite cap (an explicit number or the inherited
|
|
@@ -137,6 +211,13 @@ export interface AssuranceRule {
|
|
|
137
211
|
export interface AssurancePolicy {
|
|
138
212
|
/** First matching rule owns a route. Put exceptions before broad defaults. */
|
|
139
213
|
readonly rules: readonly AssuranceRule[];
|
|
214
|
+
/**
|
|
215
|
+
* Rules checked on EVERY route they match, in addition to the first-match rule that owns it - for a
|
|
216
|
+
* requirement that holds across classes ("every route declaring a response enforces it"), which
|
|
217
|
+
* first-match-wins would otherwise make you repeat in each rule. A baseline rule does not classify a
|
|
218
|
+
* route: one no `rules` entry matches is still unclassified. Names share one namespace with `rules`.
|
|
219
|
+
*/
|
|
220
|
+
readonly baseline?: readonly AssuranceRule[];
|
|
140
221
|
/** Default `error`: an unclassified route fails closed. */
|
|
141
222
|
readonly unmatched?: "error" | "ignore";
|
|
142
223
|
/** Default false: reject an empty reflected source so a wrong import cannot pass CI silently. */
|
|
@@ -202,9 +283,7 @@ export interface AssuranceConfig {
|
|
|
202
283
|
readonly signer?: (keyRef: string) => NifraManifestSigner | Promise<NifraManifestSigner>;
|
|
203
284
|
};
|
|
204
285
|
/** Dynamic invariant execution is opt-in and must use a disposable/sandboxed target. */
|
|
205
|
-
readonly invariants?:
|
|
206
|
-
readonly executor: InvariantExecutor;
|
|
207
|
-
};
|
|
286
|
+
readonly invariants?: InvariantsConfig;
|
|
208
287
|
/** Optional application-supplied rule packs appended after built-in verification rules. */
|
|
209
288
|
readonly rulePacks?: readonly AssuranceRulePack[];
|
|
210
289
|
/** Optional sink for an assurance bundle. The CLI validates the callable shape at runtime. */
|
|
@@ -219,6 +298,7 @@ export declare function defineAssurancePolicy(policy: AssurancePolicy): Assuranc
|
|
|
219
298
|
/** Identity helper for a `nifra.assurance.ts` default export. */
|
|
220
299
|
export declare function defineAssuranceConfig(config: AssuranceConfig): AssuranceConfig;
|
|
221
300
|
export declare function matchesAssuranceSelector(route: Pick<ReflectedRoute, "method" | "path" | "tool" | "capabilities" | "classification" | "schema">, selector: AssuranceRouteSelector, definitions?: ReadonlyMap<string, CapabilityDefinition>): boolean;
|
|
222
|
-
/** Evaluate reflected route evidence against the first matching policy rule
|
|
301
|
+
/** Evaluate reflected route evidence against the first matching policy rule, plus every matching
|
|
302
|
+
* baseline rule. */
|
|
223
303
|
export declare function evaluateRouteAssurance(source: unknown, policyInput: AssurancePolicy, options?: AssuranceEvaluationOptions): AssuranceReport;
|
|
224
304
|
//# sourceMappingURL=assurance.d.ts.map
|
package/dist/assurance.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assurance.d.ts","sourceRoot":"","sources":["../src/assurance.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,yBAAyB,EAC9B,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EAGpB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EACL,KAAK,kBAAkB,EAGxB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,KAAK,EACV,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,cAAc,EACf,MAAM,+BAA+B,CAAA;AACtC,OAAO,EAOL,kBAAkB,EACnB,MAAM,+BAA+B,CAAA;AACtC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AACrD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AACxD,OAAO,EAAE,KAAK,cAAc,EAAiB,MAAM,iBAAiB,CAAA;AACpE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAEhD,OAAO,EACL,KAAK,qBAAqB,EAC1B,KAAK,uBAAuB,EAC5B,gBAAgB,GACjB,MAAM,wBAAwB,CAAA;AAC/B,YAAY,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,cAAc,EAAE,CAAA;AAC5F,OAAO,EAAE,kBAAkB,EAAE,CAAA;AAE7B,2EAA2E;AAC3E,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;AAElF,6EAA6E;AAC7E,eAAO,MAAM,eAAe;;;;;;;;;;EAAsB,CAAA;AAOlD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,MAAM,CACpB,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,mBAAmB,GAAG,SAAS,mBAAmB,EAAE,GAC7D,IAAI,CAwBN;AAED,MAAM,WAAW,sBAAsB;IACrC,
|
|
1
|
+
{"version":3,"file":"assurance.d.ts","sourceRoot":"","sources":["../src/assurance.ts"],"names":[],"mappings":"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"}
|
package/dist/assurance.js
CHANGED
|
@@ -55,12 +55,27 @@ const CLASS_SELECTOR_KEYS = ["access", "zone"];
|
|
|
55
55
|
const usesClassSelector = (selector) => selector.access !== undefined || selector.zone !== undefined;
|
|
56
56
|
/** The rules whose selector needs capability definitions to mean anything. */
|
|
57
57
|
function rulesNeedingDefinitions(policy) {
|
|
58
|
-
return policy.rules
|
|
58
|
+
return [...policy.rules, ...(policy.baseline ?? [])]
|
|
59
|
+
.filter((rule) => usesClassSelector(rule.match))
|
|
60
|
+
.map((rule) => rule.name);
|
|
59
61
|
}
|
|
60
62
|
function definitionMap(definitions) {
|
|
61
63
|
return new Map((definitions ?? []).map((definition) => [definition.id, definition]));
|
|
62
64
|
}
|
|
63
65
|
const nonEmpty = (value) => value.trim() !== "";
|
|
66
|
+
/** A selector's method list, upper-cased. Standard methods and `"WS"` only, so a typo throws rather
|
|
67
|
+
* than leaving a rule that never matches. */
|
|
68
|
+
function selectorMethods(values) {
|
|
69
|
+
if (values === undefined)
|
|
70
|
+
return undefined;
|
|
71
|
+
return Object.freeze(values.map((value) => {
|
|
72
|
+
const method = value.toUpperCase();
|
|
73
|
+
if (method !== "WS" && !validMethod(method)) {
|
|
74
|
+
throw new Error(`route assurance: unsupported HTTP method ${JSON.stringify(method)}`);
|
|
75
|
+
}
|
|
76
|
+
return method;
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
64
79
|
function normalizeEvidenceIds(values, label) {
|
|
65
80
|
const out = [];
|
|
66
81
|
const seen = new Set();
|
|
@@ -89,8 +104,11 @@ export function defineAssurancePolicy(policy) {
|
|
|
89
104
|
typeof policy.flagClassifiedWithoutEvidence !== "boolean") {
|
|
90
105
|
throw new Error(`route assurance: flagClassifiedWithoutEvidence must be boolean`);
|
|
91
106
|
}
|
|
107
|
+
if (policy.baseline !== undefined && !Array.isArray(policy.baseline)) {
|
|
108
|
+
throw new Error("route assurance: baseline must be an array of rules");
|
|
109
|
+
}
|
|
92
110
|
const names = new Set();
|
|
93
|
-
const
|
|
111
|
+
const normalize = (rule) => {
|
|
94
112
|
if (typeof rule.name !== "string" || !nonEmpty(rule.name)) {
|
|
95
113
|
throw new Error("route assurance: rule name must be non-empty");
|
|
96
114
|
}
|
|
@@ -103,12 +121,14 @@ export function defineAssurancePolicy(policy) {
|
|
|
103
121
|
// swallow everything after it. A misspelled key is a policy hole, so it is refused here.
|
|
104
122
|
const unknown = Object.keys(rule.match).filter((key) => ![
|
|
105
123
|
"methods",
|
|
124
|
+
"exceptMethods",
|
|
106
125
|
"paths",
|
|
107
126
|
"tools",
|
|
108
127
|
"capabilities",
|
|
109
128
|
...CLASS_SELECTOR_KEYS,
|
|
110
129
|
"classificationAtLeast",
|
|
111
130
|
"hasBody",
|
|
131
|
+
"hasResponse",
|
|
112
132
|
"bodyLimit",
|
|
113
133
|
].includes(key));
|
|
114
134
|
if (unknown.length > 0) {
|
|
@@ -119,8 +139,10 @@ export function defineAssurancePolicy(policy) {
|
|
|
119
139
|
if (rule.match.tools !== undefined && typeof rule.match.tools !== "boolean") {
|
|
120
140
|
throw new Error(`route assurance: rule ${JSON.stringify(name)} tools selector must be boolean`);
|
|
121
141
|
}
|
|
122
|
-
|
|
123
|
-
|
|
142
|
+
for (const key of ["hasBody", "hasResponse"]) {
|
|
143
|
+
if (rule.match[key] !== undefined && typeof rule.match[key] !== "boolean") {
|
|
144
|
+
throw new Error(`route assurance: rule ${JSON.stringify(name)} ${key} selector must be boolean`);
|
|
145
|
+
}
|
|
124
146
|
}
|
|
125
147
|
if (rule.match.bodyLimit !== undefined &&
|
|
126
148
|
rule.match.bodyLimit !== "bounded" &&
|
|
@@ -130,18 +152,14 @@ export function defineAssurancePolicy(policy) {
|
|
|
130
152
|
}
|
|
131
153
|
// An empty list matches nothing, so the rule is inert and its routes fall through to whatever
|
|
132
154
|
// laxer rule comes next - the same hole as a dropped key, the other way round.
|
|
133
|
-
for (const key of ["methods", "paths"]) {
|
|
155
|
+
for (const key of ["methods", "exceptMethods", "paths"]) {
|
|
134
156
|
const list = rule.match[key];
|
|
135
157
|
if (list !== undefined && (!Array.isArray(list) || list.length === 0)) {
|
|
136
158
|
throw new Error(`route assurance: rule ${JSON.stringify(name)} ${key} selector must be a non-empty array`);
|
|
137
159
|
}
|
|
138
160
|
}
|
|
139
|
-
const methods = rule.match.methods
|
|
140
|
-
|
|
141
|
-
if (!validMethod(method)) {
|
|
142
|
-
throw new Error(`route assurance: unsupported HTTP method ${JSON.stringify(method)}`);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
161
|
+
const methods = selectorMethods(rule.match.methods);
|
|
162
|
+
const exceptMethods = selectorMethods(rule.match.exceptMethods);
|
|
145
163
|
const paths = rule.match.paths?.map((path) => {
|
|
146
164
|
routeGlob(path);
|
|
147
165
|
return path;
|
|
@@ -189,21 +207,33 @@ export function defineAssurancePolicy(policy) {
|
|
|
189
207
|
if (overlap !== undefined) {
|
|
190
208
|
throw new Error(`route assurance: rule ${JSON.stringify(name)} both requires and forbids ${overlap}`);
|
|
191
209
|
}
|
|
210
|
+
const match = {};
|
|
211
|
+
if (methods !== undefined)
|
|
212
|
+
match.methods = methods;
|
|
213
|
+
if (exceptMethods !== undefined)
|
|
214
|
+
match.exceptMethods = exceptMethods;
|
|
215
|
+
if (paths !== undefined)
|
|
216
|
+
match.paths = Object.freeze(paths);
|
|
217
|
+
if (rule.match.tools !== undefined)
|
|
218
|
+
match.tools = rule.match.tools;
|
|
219
|
+
if (capabilities !== undefined)
|
|
220
|
+
match.capabilities = Object.freeze([...capabilities]);
|
|
221
|
+
if (access !== undefined)
|
|
222
|
+
match.access = access;
|
|
223
|
+
if (zone !== undefined)
|
|
224
|
+
match.zone = zone;
|
|
225
|
+
if (rule.match.classificationAtLeast !== undefined) {
|
|
226
|
+
match.classificationAtLeast = rule.match.classificationAtLeast;
|
|
227
|
+
}
|
|
228
|
+
if (rule.match.hasBody !== undefined)
|
|
229
|
+
match.hasBody = rule.match.hasBody;
|
|
230
|
+
if (rule.match.hasResponse !== undefined)
|
|
231
|
+
match.hasResponse = rule.match.hasResponse;
|
|
232
|
+
if (rule.match.bodyLimit !== undefined)
|
|
233
|
+
match.bodyLimit = rule.match.bodyLimit;
|
|
192
234
|
return Object.freeze({
|
|
193
235
|
name,
|
|
194
|
-
match: Object.freeze(
|
|
195
|
-
...(methods !== undefined ? { methods: Object.freeze(methods) } : {}),
|
|
196
|
-
...(paths !== undefined ? { paths: Object.freeze(paths) } : {}),
|
|
197
|
-
...(rule.match.tools !== undefined ? { tools: rule.match.tools } : {}),
|
|
198
|
-
...(capabilities !== undefined ? { capabilities: Object.freeze([...capabilities]) } : {}),
|
|
199
|
-
...(access !== undefined ? { access } : {}),
|
|
200
|
-
...(zone !== undefined ? { zone } : {}),
|
|
201
|
-
...(rule.match.classificationAtLeast !== undefined
|
|
202
|
-
? { classificationAtLeast: rule.match.classificationAtLeast }
|
|
203
|
-
: {}),
|
|
204
|
-
...(rule.match.hasBody !== undefined ? { hasBody: rule.match.hasBody } : {}),
|
|
205
|
-
...(rule.match.bodyLimit !== undefined ? { bodyLimit: rule.match.bodyLimit } : {}),
|
|
206
|
-
}),
|
|
236
|
+
match: Object.freeze(match),
|
|
207
237
|
require: required,
|
|
208
238
|
forbid: forbidden,
|
|
209
239
|
...(rule.requireProvenance !== undefined
|
|
@@ -211,14 +241,61 @@ export function defineAssurancePolicy(policy) {
|
|
|
211
241
|
: {}),
|
|
212
242
|
...(rule.requireCsrfWithAuthenticated === true ? { requireCsrfWithAuthenticated: true } : {}),
|
|
213
243
|
});
|
|
214
|
-
}
|
|
244
|
+
};
|
|
245
|
+
const rules = policy.rules.map(normalize);
|
|
246
|
+
const baseline = (policy.baseline ?? []).map(normalize);
|
|
215
247
|
return Object.freeze({
|
|
216
248
|
rules: Object.freeze(rules),
|
|
249
|
+
...(baseline.length > 0 ? { baseline: Object.freeze(baseline) } : {}),
|
|
217
250
|
unmatched: policy.unmatched ?? "error",
|
|
218
251
|
allowEmpty: policy.allowEmpty ?? false,
|
|
219
252
|
flagClassifiedWithoutEvidence: policy.flagClassifiedWithoutEvidence ?? false,
|
|
220
253
|
});
|
|
221
254
|
}
|
|
255
|
+
const INVARIANT_FUNCTIONS = [
|
|
256
|
+
"executor",
|
|
257
|
+
"setup",
|
|
258
|
+
"prepareRequest",
|
|
259
|
+
"isRejected",
|
|
260
|
+
"include",
|
|
261
|
+
];
|
|
262
|
+
function assertInvariantsConfig(invariants) {
|
|
263
|
+
if (invariants === null || typeof invariants !== "object") {
|
|
264
|
+
throw new TypeError("route assurance: invariants must be an object");
|
|
265
|
+
}
|
|
266
|
+
for (const name of INVARIANT_FUNCTIONS) {
|
|
267
|
+
const value = invariants[name];
|
|
268
|
+
if (value !== undefined && typeof value !== "function") {
|
|
269
|
+
throw new TypeError(name === "executor"
|
|
270
|
+
? "route assurance: invariant executor must be a function"
|
|
271
|
+
: `route assurance: invariants.${name} must be a function`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (invariants.executor === undefined && invariants.setup === undefined) {
|
|
275
|
+
throw new TypeError("route assurance: invariants needs an executor or a setup");
|
|
276
|
+
}
|
|
277
|
+
const witnesses = invariants.witnesses;
|
|
278
|
+
if (witnesses !== undefined) {
|
|
279
|
+
if (witnesses === null || typeof witnesses !== "object" || Array.isArray(witnesses)) {
|
|
280
|
+
throw new TypeError('route assurance: invariants.witnesses must map "METHOD /path" to a witness');
|
|
281
|
+
}
|
|
282
|
+
for (const [key, witness] of Object.entries(witnesses)) {
|
|
283
|
+
if (witness === null || typeof witness !== "object" || Array.isArray(witness)) {
|
|
284
|
+
throw new TypeError(`route assurance: invariants.witnesses[${JSON.stringify(key)}] must be an object`);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const max = invariants.maxMutationsPerInput;
|
|
289
|
+
if (max !== undefined && (!Number.isSafeInteger(max) || max < 1)) {
|
|
290
|
+
throw new RangeError("route assurance: invariants.maxMutationsPerInput must be a positive integer");
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
function frozenInvariantsConfig(invariants) {
|
|
294
|
+
const witnesses = invariants.witnesses;
|
|
295
|
+
return Object.freeze(witnesses === undefined
|
|
296
|
+
? { ...invariants }
|
|
297
|
+
: { ...invariants, witnesses: Object.freeze({ ...witnesses }) });
|
|
298
|
+
}
|
|
222
299
|
/** Identity helper for a `nifra.assurance.ts` default export. */
|
|
223
300
|
export function defineAssuranceConfig(config) {
|
|
224
301
|
if (config.manifest?.path !== undefined && config.manifest.path.trim() === "") {
|
|
@@ -227,9 +304,8 @@ export function defineAssuranceConfig(config) {
|
|
|
227
304
|
if (config.manifest?.signer !== undefined && typeof config.manifest.signer !== "function") {
|
|
228
305
|
throw new Error("route assurance: manifest signer must be a function");
|
|
229
306
|
}
|
|
230
|
-
if (config.invariants !== undefined
|
|
231
|
-
|
|
232
|
-
}
|
|
307
|
+
if (config.invariants !== undefined)
|
|
308
|
+
assertInvariantsConfig(config.invariants);
|
|
233
309
|
if (config.rulePacks !== undefined) {
|
|
234
310
|
if (!Array.isArray(config.rulePacks))
|
|
235
311
|
throw new TypeError("route assurance: rulePacks must be an array");
|
|
@@ -286,7 +362,7 @@ export function defineAssuranceConfig(config) {
|
|
|
286
362
|
// that passes capability assurance, so the rule would match nothing.
|
|
287
363
|
if (capabilities !== undefined) {
|
|
288
364
|
const defined = new Set(capabilities.definitions.map((definition) => definition.id));
|
|
289
|
-
for (const rule of policy.rules) {
|
|
365
|
+
for (const rule of [...policy.rules, ...(policy.baseline ?? [])]) {
|
|
290
366
|
const undefinedToken = rule.match.capabilities?.find((token) => !defined.has(token));
|
|
291
367
|
if (undefinedToken !== undefined) {
|
|
292
368
|
throw new Error(`route assurance: rule ${JSON.stringify(rule.name)} selects capability ${JSON.stringify(undefinedToken)}, which the capability policy does not define`);
|
|
@@ -312,7 +388,7 @@ export function defineAssuranceConfig(config) {
|
|
|
312
388
|
}
|
|
313
389
|
: {}),
|
|
314
390
|
...(config.invariants !== undefined
|
|
315
|
-
? { invariants:
|
|
391
|
+
? { invariants: frozenInvariantsConfig(config.invariants) }
|
|
316
392
|
: {}),
|
|
317
393
|
...(config.rulePacks !== undefined ? { rulePacks: Object.freeze([...config.rulePacks]) } : {}),
|
|
318
394
|
...(config.assureSink !== undefined ? { assureSink: config.assureSink } : {}),
|
|
@@ -345,15 +421,19 @@ function effectiveBodyLimit(schema) {
|
|
|
345
421
|
}
|
|
346
422
|
export function matchesAssuranceSelector(route, selector, definitions) {
|
|
347
423
|
const { methods, paths, tools, capabilities, access, zone, classificationAtLeast } = selector;
|
|
348
|
-
const { hasBody, bodyLimit } = selector;
|
|
424
|
+
const { exceptMethods, hasBody, hasResponse, bodyLimit } = selector;
|
|
349
425
|
if (methods !== undefined && !methods.includes(route.method))
|
|
350
426
|
return false;
|
|
427
|
+
if (exceptMethods?.some((method) => method === route.method) === true)
|
|
428
|
+
return false;
|
|
351
429
|
if (paths !== undefined && !paths.some((pattern) => routeGlob(pattern).test(route.path)))
|
|
352
430
|
return false;
|
|
353
431
|
if (tools !== undefined && (route.tool !== undefined) !== tools)
|
|
354
432
|
return false;
|
|
355
433
|
if (hasBody !== undefined && (route.schema?.body !== undefined) !== hasBody)
|
|
356
434
|
return false;
|
|
435
|
+
if (hasResponse !== undefined && (route.schema?.response !== undefined) !== hasResponse)
|
|
436
|
+
return false;
|
|
357
437
|
if (bodyLimit !== undefined && effectiveBodyLimit(route.schema) !== bodyLimit)
|
|
358
438
|
return false;
|
|
359
439
|
if (classificationAtLeast !== undefined &&
|
|
@@ -381,7 +461,49 @@ export function matchesAssuranceSelector(route, selector, definitions) {
|
|
|
381
461
|
}
|
|
382
462
|
return true;
|
|
383
463
|
}
|
|
384
|
-
/**
|
|
464
|
+
/** One rule's missing and forbidden evidence on one route, each also reported as a finding. */
|
|
465
|
+
function checkAssuranceRule(rule, route, evidence, evidenceIds, findings) {
|
|
466
|
+
const requiredProvenance = rule.requireProvenance ?? "any";
|
|
467
|
+
const missing = (rule.require ?? []).filter((id) => {
|
|
468
|
+
if (!evidenceIds.has(id))
|
|
469
|
+
return true;
|
|
470
|
+
if (requiredProvenance === "any")
|
|
471
|
+
return false;
|
|
472
|
+
return !evidence.some((item) => item.id === id && evidenceProvenance(item) === requiredProvenance);
|
|
473
|
+
});
|
|
474
|
+
const forbidden = (rule.forbid ?? []).filter((id) => evidenceIds.has(id));
|
|
475
|
+
if (rule.requireCsrfWithAuthenticated === true &&
|
|
476
|
+
evidenceIds.has(NIFRA_ASSURANCE_IDS.AUTHENTICATED) &&
|
|
477
|
+
!evidence.some((item) => item.id === NIFRA_ASSURANCE_IDS.CSRF && evidenceProvenance(item) === "runtime") &&
|
|
478
|
+
!missing.includes(NIFRA_ASSURANCE_IDS.CSRF)) {
|
|
479
|
+
missing.push(NIFRA_ASSURANCE_IDS.CSRF);
|
|
480
|
+
}
|
|
481
|
+
for (const id of missing) {
|
|
482
|
+
findings.push({
|
|
483
|
+
code: "missing-evidence",
|
|
484
|
+
method: route.method,
|
|
485
|
+
path: route.path,
|
|
486
|
+
rule: rule.name,
|
|
487
|
+
evidence: id,
|
|
488
|
+
message: requiredProvenance === "any"
|
|
489
|
+
? `${route.method} ${route.path} (${rule.name}) is missing ${id}`
|
|
490
|
+
: `${route.method} ${route.path} (${rule.name}) is missing ${requiredProvenance} evidence for ${id}`,
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
for (const id of forbidden) {
|
|
494
|
+
findings.push({
|
|
495
|
+
code: "forbidden-evidence",
|
|
496
|
+
method: route.method,
|
|
497
|
+
path: route.path,
|
|
498
|
+
rule: rule.name,
|
|
499
|
+
evidence: id,
|
|
500
|
+
message: `${route.method} ${route.path} (${rule.name}) carries forbidden ${id}`,
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
return { missing, forbidden };
|
|
504
|
+
}
|
|
505
|
+
/** Evaluate reflected route evidence against the first matching policy rule, plus every matching
|
|
506
|
+
* baseline rule. */
|
|
385
507
|
export function evaluateRouteAssurance(source, policyInput, options) {
|
|
386
508
|
const policy = defineAssurancePolicy(policyInput);
|
|
387
509
|
const definitions = definitionMap(options?.definitions);
|
|
@@ -408,6 +530,17 @@ export function evaluateRouteAssurance(source, policyInput, options) {
|
|
|
408
530
|
const rule = policy.rules.find((candidate) => matchesAssuranceSelector(route, candidate.match, definitions));
|
|
409
531
|
const evidence = route.assurance ?? [];
|
|
410
532
|
const evidenceIds = new Set(evidence.map((item) => item.id));
|
|
533
|
+
const missing = [];
|
|
534
|
+
const forbidden = [];
|
|
535
|
+
const hold = (candidate) => {
|
|
536
|
+
const outcome = checkAssuranceRule(candidate, route, evidence, evidenceIds, findings);
|
|
537
|
+
for (const id of outcome.missing)
|
|
538
|
+
if (!missing.includes(id))
|
|
539
|
+
missing.push(id);
|
|
540
|
+
for (const id of outcome.forbidden)
|
|
541
|
+
if (!forbidden.includes(id))
|
|
542
|
+
forbidden.push(id);
|
|
543
|
+
};
|
|
411
544
|
if (rule === undefined) {
|
|
412
545
|
if (policy.unmatched !== "ignore") {
|
|
413
546
|
findings.push({
|
|
@@ -417,75 +550,33 @@ export function evaluateRouteAssurance(source, policyInput, options) {
|
|
|
417
550
|
message: `${route.method} ${route.path} is not classified by an assurance rule`,
|
|
418
551
|
});
|
|
419
552
|
}
|
|
420
|
-
routes.push({
|
|
421
|
-
method: route.method,
|
|
422
|
-
path: route.path,
|
|
423
|
-
evidence,
|
|
424
|
-
missing: [],
|
|
425
|
-
forbidden: [],
|
|
426
|
-
...(route.schema?.response !== undefined || route.schema?.errors !== undefined
|
|
427
|
-
? { responseContract: route.responseContract ?? "unchecked" }
|
|
428
|
-
: {}),
|
|
429
|
-
});
|
|
430
|
-
continue;
|
|
431
|
-
}
|
|
432
|
-
const requiredProvenance = rule.requireProvenance ?? "any";
|
|
433
|
-
let missing = (rule.require ?? []).filter((id) => {
|
|
434
|
-
if (!evidenceIds.has(id))
|
|
435
|
-
return true;
|
|
436
|
-
if (requiredProvenance === "any")
|
|
437
|
-
return false;
|
|
438
|
-
return !evidence.some((item) => item.id === id && evidenceProvenance(item) === requiredProvenance);
|
|
439
|
-
});
|
|
440
|
-
const forbidden = (rule.forbid ?? []).filter((id) => evidenceIds.has(id));
|
|
441
|
-
if (rule.requireCsrfWithAuthenticated === true &&
|
|
442
|
-
evidenceIds.has(NIFRA_ASSURANCE_IDS.AUTHENTICATED) &&
|
|
443
|
-
!evidence.some((item) => item.id === NIFRA_ASSURANCE_IDS.CSRF && evidenceProvenance(item) === "runtime")) {
|
|
444
|
-
const id = NIFRA_ASSURANCE_IDS.CSRF;
|
|
445
|
-
if (!missing.includes(id))
|
|
446
|
-
missing = [...missing, id];
|
|
447
|
-
}
|
|
448
|
-
for (const id of missing) {
|
|
449
|
-
findings.push({
|
|
450
|
-
code: "missing-evidence",
|
|
451
|
-
method: route.method,
|
|
452
|
-
path: route.path,
|
|
453
|
-
rule: rule.name,
|
|
454
|
-
evidence: id,
|
|
455
|
-
message: requiredProvenance === "any"
|
|
456
|
-
? `${route.method} ${route.path} (${rule.name}) is missing ${id}`
|
|
457
|
-
: `${route.method} ${route.path} (${rule.name}) is missing ${requiredProvenance} evidence for ${id}`,
|
|
458
|
-
});
|
|
459
553
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
evidence
|
|
467
|
-
|
|
468
|
-
|
|
554
|
+
else {
|
|
555
|
+
hold(rule);
|
|
556
|
+
// Opt-in visibility: a pure-classification rule (no require + no forbid) matching a route with
|
|
557
|
+
// zero evidence is a "label without proof". Surface it so the gap isn't silently green. A rule
|
|
558
|
+
// that expects or forbids evidence already speaks for itself, so it's excluded here.
|
|
559
|
+
if (policy.flagClassifiedWithoutEvidence === true &&
|
|
560
|
+
evidence.length === 0 &&
|
|
561
|
+
(rule.require ?? []).length === 0 &&
|
|
562
|
+
(rule.forbid ?? []).length === 0) {
|
|
563
|
+
findings.push({
|
|
564
|
+
code: "classified-no-evidence",
|
|
565
|
+
method: route.method,
|
|
566
|
+
path: route.path,
|
|
567
|
+
rule: rule.name,
|
|
568
|
+
message: `${route.method} ${route.path} (${rule.name}) is classified but carries no enforcement evidence`,
|
|
569
|
+
});
|
|
570
|
+
}
|
|
469
571
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
if (policy.flagClassifiedWithoutEvidence === true &&
|
|
474
|
-
evidence.length === 0 &&
|
|
475
|
-
(rule.require ?? []).length === 0 &&
|
|
476
|
-
(rule.forbid ?? []).length === 0) {
|
|
477
|
-
findings.push({
|
|
478
|
-
code: "classified-no-evidence",
|
|
479
|
-
method: route.method,
|
|
480
|
-
path: route.path,
|
|
481
|
-
rule: rule.name,
|
|
482
|
-
message: `${route.method} ${route.path} (${rule.name}) is classified but carries no enforcement evidence`,
|
|
483
|
-
});
|
|
572
|
+
for (const candidate of policy.baseline ?? []) {
|
|
573
|
+
if (matchesAssuranceSelector(route, candidate.match, definitions))
|
|
574
|
+
hold(candidate);
|
|
484
575
|
}
|
|
485
576
|
routes.push({
|
|
486
577
|
method: route.method,
|
|
487
578
|
path: route.path,
|
|
488
|
-
rule: rule.name,
|
|
579
|
+
...(rule === undefined ? {} : { rule: rule.name }),
|
|
489
580
|
evidence,
|
|
490
581
|
missing: Object.freeze(missing),
|
|
491
582
|
forbidden: Object.freeze(forbidden),
|