@jimhoyd/urlcode 0.3.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/CONTRIBUTING.md +80 -0
- package/LICENSE +202 -0
- package/NOTICE +20 -0
- package/README.md +342 -0
- package/ROADMAP.md +248 -0
- package/SECURITY.md +40 -0
- package/data/agents/LICENSES/ai-robots-txt.txt +24 -0
- package/data/agents/LICENSES/crawler-user-agents.txt +23 -0
- package/data/agents/ai-crawlers.json +1240 -0
- package/data/agents/crawlers.json +10515 -0
- package/data/agents/index.d.ts +3 -0
- package/data/agents/index.js +2453 -0
- package/data/agents/monitoring.json +1520 -0
- package/data/agents/seo.json +3865 -0
- package/dist/BUILD-MANIFEST.json +60 -0
- package/dist/adapters.js +59 -0
- package/dist/agent-lists.js +59 -0
- package/dist/assets.js +129 -0
- package/dist/authoring.js +68 -0
- package/dist/aws.js +110 -0
- package/dist/build-cloudflare.js +164 -0
- package/dist/cli.js +185 -0
- package/dist/client-address.js +85 -0
- package/dist/cloudflare.js +165 -0
- package/dist/compliance-rules/baseline.js +98 -0
- package/dist/compliance-rules/privacy.js +38 -0
- package/dist/compliance-rules/shared.js +62 -0
- package/dist/compliance-rules/strict.js +61 -0
- package/dist/compliance.js +219 -0
- package/dist/config-worker.js +9 -0
- package/dist/config.js +172 -0
- package/dist/errors.js +8 -0
- package/dist/function-sources.js +54 -0
- package/dist/function-worker.js +105 -0
- package/dist/functions.js +174 -0
- package/dist/guest-api.js +114 -0
- package/dist/header-validation.js +18 -0
- package/dist/http-policy.js +74 -0
- package/dist/http-response.js +84 -0
- package/dist/index.js +11 -0
- package/dist/link-api.js +136 -0
- package/dist/link-cli.js +141 -0
- package/dist/link-events.js +76 -0
- package/dist/link-records.js +31 -0
- package/dist/link-store-worker.js +150 -0
- package/dist/link-store.js +250 -0
- package/dist/logging.js +22 -0
- package/dist/management-policy.js +41 -0
- package/dist/match.js +124 -0
- package/dist/observability.js +242 -0
- package/dist/plugins.js +74 -0
- package/dist/policies/agents.js +248 -0
- package/dist/policies/cache.js +297 -0
- package/dist/policies/compression.js +187 -0
- package/dist/policies/security.js +0 -0
- package/dist/policies/throttle.js +131 -0
- package/dist/policies.js +142 -0
- package/dist/policy.js +55 -0
- package/dist/prerender.js +165 -0
- package/dist/project-tests.js +23 -0
- package/dist/readiness.js +216 -0
- package/dist/router.js +166 -0
- package/dist/runtime.js +244 -0
- package/dist/scaffold.js +0 -0
- package/dist/scripts/operational-drills.js +75 -0
- package/dist/server.js +270 -0
- package/dist/site.js +0 -0
- package/dist/sqlite-version.js +6 -0
- package/dist/types/adapters.d.ts +12 -0
- package/dist/types/agent-lists.d.ts +5 -0
- package/dist/types/assets.d.ts +13 -0
- package/dist/types/authoring.d.ts +2 -0
- package/dist/types/aws.d.ts +35 -0
- package/dist/types/build-cloudflare.d.ts +14 -0
- package/dist/types/cli.d.ts +2 -0
- package/dist/types/client-address.d.ts +9 -0
- package/dist/types/cloudflare.d.ts +75 -0
- package/dist/types/compliance-rules/baseline.d.ts +12 -0
- package/dist/types/compliance-rules/privacy.d.ts +6 -0
- package/dist/types/compliance-rules/shared.d.ts +24 -0
- package/dist/types/compliance-rules/strict.d.ts +9 -0
- package/dist/types/compliance.d.ts +119 -0
- package/dist/types/config-worker.d.ts +1 -0
- package/dist/types/config.d.ts +21 -0
- package/dist/types/errors.d.ts +7 -0
- package/dist/types/function-sources.d.ts +21 -0
- package/dist/types/function-worker.d.ts +1 -0
- package/dist/types/functions.d.ts +92 -0
- package/dist/types/guest-api.d.ts +16 -0
- package/dist/types/header-validation.d.ts +2 -0
- package/dist/types/http-policy.d.ts +39 -0
- package/dist/types/http-response.d.ts +43 -0
- package/dist/types/index.d.ts +11 -0
- package/dist/types/link-api.d.ts +30 -0
- package/dist/types/link-cli.d.ts +36 -0
- package/dist/types/link-events.d.ts +27 -0
- package/dist/types/link-records.d.ts +11 -0
- package/dist/types/link-store-worker.d.ts +1 -0
- package/dist/types/link-store.d.ts +130 -0
- package/dist/types/logging.d.ts +9 -0
- package/dist/types/management-policy.d.ts +9 -0
- package/dist/types/match.d.ts +81 -0
- package/dist/types/observability.d.ts +104 -0
- package/dist/types/plugins.d.ts +27 -0
- package/dist/types/policies/agents.d.ts +86 -0
- package/dist/types/policies/cache.d.ts +82 -0
- package/dist/types/policies/compression.d.ts +41 -0
- package/dist/types/policies/security.d.ts +37 -0
- package/dist/types/policies/throttle.d.ts +55 -0
- package/dist/types/policies.d.ts +36 -0
- package/dist/types/policy.d.ts +21 -0
- package/dist/types/prerender.d.ts +31 -0
- package/dist/types/project-tests.d.ts +13 -0
- package/dist/types/readiness.d.ts +121 -0
- package/dist/types/router.d.ts +11 -0
- package/dist/types/runtime.d.ts +91 -0
- package/dist/types/scaffold.d.ts +17 -0
- package/dist/types/server.d.ts +37 -0
- package/dist/types/site.d.ts +12 -0
- package/dist/types/sqlite-version.d.ts +1 -0
- package/dist/types/types.d.ts +332 -0
- package/dist/types/vercel.d.ts +12 -0
- package/dist/types.js +177 -0
- package/dist/vercel.js +65 -0
- package/docs/AI-AUTHORING.md +113 -0
- package/docs/ASSETS.md +106 -0
- package/docs/AWS.md +77 -0
- package/docs/BEST-PRACTICES.md +267 -0
- package/docs/CAPACITY.md +208 -0
- package/docs/CLOUDFLARE.md +110 -0
- package/docs/COMPLIANCE.md +242 -0
- package/docs/DYNAMIC-LINKS.md +561 -0
- package/docs/FUNCTION-SECURITY.md +113 -0
- package/docs/HTTP.md +129 -0
- package/docs/INSTALL.md +90 -0
- package/docs/LOAD-TESTING.md +91 -0
- package/docs/LOCAL-DEVELOPMENT.md +99 -0
- package/docs/MANAGEMENT-SECURITY.md +82 -0
- package/docs/MIDDLEWARE.md +83 -0
- package/docs/MONITORING.md +132 -0
- package/docs/OBSERVABILITY.md +229 -0
- package/docs/OPERATIONAL-PROOF.md +42 -0
- package/docs/OPERATIONS.md +207 -0
- package/docs/ORGANIZATION.md +135 -0
- package/docs/PERFORMANCE.md +72 -0
- package/docs/PLUGINS.md +234 -0
- package/docs/POLICIES.md +489 -0
- package/docs/PRERENDER.md +193 -0
- package/docs/PROJECT-DIRECTION.md +51 -0
- package/docs/READINESS.md +125 -0
- package/docs/README.md +58 -0
- package/docs/RELEASE-READINESS.md +95 -0
- package/docs/RELEASE-SECURITY.md +72 -0
- package/docs/RESILIENCE.md +160 -0
- package/docs/ROUTING.md +95 -0
- package/docs/SANDBOX-REVIEW.md +59 -0
- package/docs/SCAFFOLDING.md +72 -0
- package/docs/SECURITY-AUDIT.md +124 -0
- package/docs/SITE.md +150 -0
- package/docs/SPECIFICATION.md +232 -0
- package/docs/SPIKE-EXTENSIONS.md +475 -0
- package/docs/STANDARDS.md +303 -0
- package/docs/STARTERS.md +51 -0
- package/docs/TUNNELS.md +75 -0
- package/docs/TYPESCRIPT.md +102 -0
- package/docs/VERCEL.md +107 -0
- package/docs/YAML-GUIDE.md +521 -0
- package/docs/YAML-REFERENCE.md +328 -0
- package/docs/policies/agents.md +182 -0
- package/docs/policies/cache.md +139 -0
- package/docs/policies/compression.md +168 -0
- package/docs/policies/security.md +160 -0
- package/docs/policies/throttle.md +103 -0
- package/examples/assets/.env.example +1 -0
- package/examples/assets/.gitattributes +2 -0
- package/examples/assets/Makefile +30 -0
- package/examples/assets/README.md +18 -0
- package/examples/assets/functions/hello.mjs +3 -0
- package/examples/assets/gitignore.template +7 -0
- package/examples/assets/public/about.html +2 -0
- package/examples/assets/public/assets/example.txt +1 -0
- package/examples/assets/public/guide.txt +1 -0
- package/examples/assets/tests/requests.json +44 -0
- package/examples/assets/urlcode.yaml +27 -0
- package/examples/aws/README.md +13 -0
- package/examples/aws/handler.mjs +5 -0
- package/examples/aws/public/index.html +3 -0
- package/examples/aws/public/notes.txt +1 -0
- package/examples/aws/template.yaml +33 -0
- package/examples/aws/tests/requests.json +9 -0
- package/examples/aws/urlcode.yaml +19 -0
- package/examples/cloudflare/README.md +18 -0
- package/examples/cloudflare/package.json +14 -0
- package/examples/cloudflare/tests/requests.json +9 -0
- package/examples/cloudflare/urlcode.yaml +24 -0
- package/examples/cloudflare/wrangler.toml +5 -0
- package/examples/compliance/README.md +20 -0
- package/examples/compliance/rules.mjs +65 -0
- package/examples/cookbook/README.md +21 -0
- package/examples/cookbook/functions/choice.mjs +4 -0
- package/examples/cookbook/functions/echo.mjs +3 -0
- package/examples/cookbook/functions/hello.mjs +3 -0
- package/examples/cookbook/functions/text.mjs +3 -0
- package/examples/cookbook/middleware/headers.mjs +6 -0
- package/examples/cookbook/public/about.html +1 -0
- package/examples/cookbook/public/assets/index.html +1 -0
- package/examples/cookbook/public/assets/site.css +1 -0
- package/examples/cookbook/public/favicon.svg +1 -0
- package/examples/cookbook/public/guide.txt +1 -0
- package/examples/cookbook/public/llms.txt +6 -0
- package/examples/cookbook/routes/code.yaml +55 -0
- package/examples/cookbook/routes/files.yaml +17 -0
- package/examples/cookbook/routes/policies.yaml +28 -0
- package/examples/cookbook/routes/redirects.yaml +38 -0
- package/examples/cookbook/routes/responses.yaml +26 -0
- package/examples/cookbook/tests/requests.json +254 -0
- package/examples/cookbook/urlcode.yaml +22 -0
- package/examples/live-links/README.md +11 -0
- package/examples/live-links/tests/requests.json +6 -0
- package/examples/live-links/urlcode.yaml +16 -0
- package/examples/monitoring/blackbox-jobs.yaml +37 -0
- package/examples/monitoring/prometheus-rules.yaml +88 -0
- package/examples/monitoring/prometheus-scrape.yaml +20 -0
- package/examples/monitoring/vector.toml +65 -0
- package/examples/prerender/README.md +48 -0
- package/examples/prerender/functions/page.mjs +6 -0
- package/examples/prerender/middleware/template.mjs +29 -0
- package/examples/prerender/prerender.d.mts +4 -0
- package/examples/prerender/prerender.mjs +63 -0
- package/examples/prerender/tests/requests.json +39 -0
- package/examples/prerender/urlcode.yaml +50 -0
- package/examples/tunnel/dev-with-ngrok.sh +61 -0
- package/examples/vercel/README.md +13 -0
- package/examples/vercel/api/index.js +5 -0
- package/examples/vercel/package.json +8 -0
- package/examples/vercel/public/index.html +3 -0
- package/examples/vercel/public/notes.txt +1 -0
- package/examples/vercel/tests/requests.json +48 -0
- package/examples/vercel/urlcode.yaml +19 -0
- package/examples/vercel/vercel.json +10 -0
- package/llms.txt +46 -0
- package/package.json +123 -0
- package/schemas/urlcode.schema.json +1230 -0
- package/starters/default/.gitattributes +1 -0
- package/starters/default/Makefile +30 -0
- package/starters/default/README.md +34 -0
- package/starters/default/functions/hello.mjs +3 -0
- package/starters/default/gitignore.template +8 -0
- package/starters/default/middleware/headers.mjs +6 -0
- package/starters/default/routes/functions.yaml +20 -0
- package/starters/default/routes/marketing/links.yaml +7 -0
- package/starters/default/starter.json +5 -0
- package/starters/default/tests/requests.json +56 -0
- package/starters/default/urlcode.yaml +8 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# Compliance rules
|
|
2
|
+
|
|
3
|
+
A compliance rule is a standards-referenced check over what a project declares
|
|
4
|
+
and what the runtime knows about it: the parsed YAML, the route inventory and
|
|
5
|
+
policy table from `testPlan()`, the effective policy per route, the public
|
|
6
|
+
origin and the logging settings the operator states for the deployment. The
|
|
7
|
+
runtime ships three built-in profiles; an operator adds, overrides or disables
|
|
8
|
+
rules with code kept outside the project, the same trust boundary as
|
|
9
|
+
[plugins](PLUGINS.md) and the `--policy` binding grant; `urlcode audit` prints
|
|
10
|
+
every finding with the reference it rests on and sets the exit code.
|
|
11
|
+
|
|
12
|
+
## What it is and is not
|
|
13
|
+
|
|
14
|
+
- It checks **declared configuration and runtime facts**. No rule sends a
|
|
15
|
+
request, reads a binding, runs guest code or inspects a running deployment.
|
|
16
|
+
`oshp/hsts-origin` cannot know your TLS terminator; it knows the origin you
|
|
17
|
+
declared with `--origin`, so declare it truthfully.
|
|
18
|
+
- A passing run is **not a certification**, an audit opinion or evidence that
|
|
19
|
+
a deployment is secure or lawful. It says the declared configuration matches
|
|
20
|
+
a rule set whose references are listed beside each finding. Deployment,
|
|
21
|
+
soak and recovery proof live in [release readiness](RELEASE-READINESS.md).
|
|
22
|
+
- Rules never change behavior. The runtime serves the same responses whether a
|
|
23
|
+
finding exists or not; a rule set is an operator's review checklist in code.
|
|
24
|
+
- The built-in rules only check what the runtime can see. Anything a rule
|
|
25
|
+
cannot verify (an undeclared log level, an unknown origin) is reported as an
|
|
26
|
+
`info` finding saying so, never assumed to pass.
|
|
27
|
+
|
|
28
|
+
## The rule contract
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
export const rules = [{
|
|
32
|
+
id: 'acme/redirect-hosts', // ^[a-z][a-z0-9-]{0,31}/[a-z][a-z0-9-]{0,63}$, unique per run
|
|
33
|
+
title: 'Redirects only leave for approved hosts',
|
|
34
|
+
standard: { name: 'ACME link policy', reference: 'https://example.com/policies/links', section: 'Outbound' },
|
|
35
|
+
severity: 'high', // high | medium | low | info
|
|
36
|
+
appliesTo: 'route', // project | route
|
|
37
|
+
check(context) { return []; }, // findings[]; may be async
|
|
38
|
+
}];
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`standard.reference` is a URL, an RFC number (`RFC 9110`) or a path under
|
|
42
|
+
`docs/`. `check` returns an array of findings (an empty array, `undefined`,
|
|
43
|
+
a single finding or an array); a thrown error fails the run with the rule
|
|
44
|
+
named, so a rule cannot silently pass by crashing.
|
|
45
|
+
|
|
46
|
+
A **project** rule runs once with:
|
|
47
|
+
|
|
48
|
+
| Field | Value |
|
|
49
|
+
|---|---|
|
|
50
|
+
| `document` | The parsed and validated `urlcode.yaml` (includes are merged into `routes`) |
|
|
51
|
+
| `routes` | Route configuration by pattern, as written in YAML |
|
|
52
|
+
| `plan` | `testPlan()`: `inventory[]` (`path`, `handler`, `methods`, `middleware`, `policies`, `state`), `policies` (the per-route describe map), `dynamicLinks` |
|
|
53
|
+
| `policies` | `effectivePolicies(document, route)` by pattern: the merged configuration of every policy on each route |
|
|
54
|
+
| `origin` | The declared public origin, or `null` |
|
|
55
|
+
| `target` | `node` unless the caller states another |
|
|
56
|
+
| `host` | `{ requestLog, linkEvents, includeCode }` as declared for the deployment; `null` where undeclared |
|
|
57
|
+
|
|
58
|
+
A **route** rule runs once per inventory entry and additionally receives
|
|
59
|
+
`route` (the inventory entry), `config` (that route's YAML), `policy` (the
|
|
60
|
+
runtime's describe map for the route: `security.emits`, `agents.deny[].revision`,
|
|
61
|
+
`cache.cacheControl`, …) and `effective` (the merged policy configuration).
|
|
62
|
+
|
|
63
|
+
A **finding** is `{ rule, severity, route?, message, remediation, standard }`.
|
|
64
|
+
`severity` defaults to the rule's own; a check may lower or raise it for one
|
|
65
|
+
finding (the privacy rules report an undeclared setting as `info`). The
|
|
66
|
+
runtime fills `rule` and `standard` and the route pattern for route rules.
|
|
67
|
+
|
|
68
|
+
## Built-in profiles
|
|
69
|
+
|
|
70
|
+
`strict` contains every `baseline` rule plus its own; `privacy` stands alone;
|
|
71
|
+
`none` runs only operator rules. Check derivations reference the runtime's
|
|
72
|
+
own code: the security profile tables in `src/policies/security.ts`, the
|
|
73
|
+
cache and compression secrets handling in `src/policies/cache.ts` and
|
|
74
|
+
`src/policies/compression.ts`, the `no-store` default in
|
|
75
|
+
`src/http-response.ts`, the 16 KiB header cap in `src/http-policy.ts`.
|
|
76
|
+
|
|
77
|
+
### `baseline`
|
|
78
|
+
|
|
79
|
+
| Rule | Standard | Severity | Checks | Remediation |
|
|
80
|
+
|---|---|---|---|---|
|
|
81
|
+
| `oshp/security-headers` | [OWASP Secure Headers Project](https://owasp.org/www-project-secure-headers/) | medium | Every active route has an effective `policies.security` | Declare `policies.security: { headers: oshp }` on the project or the route |
|
|
82
|
+
| `oshp/hsts-origin` | OWASP Secure Headers Project, Strict-Transport-Security | low | When any active route's security profile emits HSTS, the declared origin is `https:`; the runtime emits HSTS only then | Serve behind TLS and declare `--origin https://…` |
|
|
83
|
+
| `breach/secrets-compression` | [BREACH](https://www.breachattack.com/) | high | No route binding `secrets` sets `policies.compression.allowWithSecrets` | Remove `allowWithSecrets` (the runtime then skips compression on secret routes) |
|
|
84
|
+
| `rfc9111/secrets-no-store` | [RFC 9111](https://www.rfc-editor.org/rfc/rfc9111) §5.2.2.5, §5.2.2.7 | medium | A route binding `secrets` declares no cache strategy, Cache-Control or handler `cacheControl` other than `no-store`/`private` | Use `no-store` or `private` on secret routes |
|
|
85
|
+
| `rfc9111/cache-control-declared` | RFC 9111 §5.2 | low | `respond` and `redirect` routes declare a `Cache-Control` header or a cache policy; asset handlers declare `cacheControl` (otherwise the runtime defaults `no-store` / assets `no-cache`) | State the intent in `response.headers`, the handler or `policies.cache` |
|
|
86
|
+
| `rfc6585/throttle-functions` | [RFC 6585](https://www.rfc-editor.org/rfc/rfc6585) §4 | medium | Every active function or middleware route has an effective `policies.throttle` | Declare a throttle on the route or the project |
|
|
87
|
+
| `rfc9309/robots` | [RFC 9309](https://www.rfc-editor.org/rfc/rfc9309) | low | Some active route declares `policies.agents`, or an active `/robots.txt` `respond` route exists | Add a `/robots.txt` route or an agents deny list |
|
|
88
|
+
| `rfc9110/expired-routes` | [RFC 9110](https://www.rfc-editor.org/rfc/rfc9110) §15.5.11 | info | Lists routes past `expires` that still answer 410 | Remove them once the 410 window has served its purpose |
|
|
89
|
+
| `ops/management-private` | [Management security](MANAGEMENT-SECURITY.md) | info | Reminder when `dynamicLinks: true`: the management API and `/_urlcode` probes belong on a private bind | Run `links api` privately with an auth file; keep probes internal |
|
|
90
|
+
|
|
91
|
+
### `strict` (baseline plus)
|
|
92
|
+
|
|
93
|
+
| Rule | Standard | Severity | Checks | Remediation |
|
|
94
|
+
|---|---|---|---|---|
|
|
95
|
+
| `oshp/csp` | OWASP Secure Headers Project, Content-Security-Policy | medium | Every active route emits `Content-Security-Policy` (security profile minus `unset`, plus `set`, or a YAML response header) | Use the `oshp` profile or set the header |
|
|
96
|
+
| `rfc6585/throttle-all` | RFC 6585 §4 | medium | Every active non-function route has an effective throttle (function routes are covered by `throttle-functions`) | Declare `policies.throttle` at the project level |
|
|
97
|
+
| `agents/lists-pinned` | [Bundled agent lists](policies/agents.md) | low | Every agents deny/allow list is a bundled list that carries an upstream revision; project `.json` list files report `revision: project` | Prefer bundled lists or record the revision a list was built from |
|
|
98
|
+
| `rfc9110/redirect-https` | RFC 9110 §15.4 | medium | No active `redirect.url` starts with `http:` | Redirect to https |
|
|
99
|
+
| `http/header-budget` | RFC 9110 §5.4 | low | YAML `response.headers` bytes plus the security profile's static bytes stay at or under 8192, half the runtime's 16 KiB response header cap | Trim headers or the security set |
|
|
100
|
+
|
|
101
|
+
### `privacy`
|
|
102
|
+
|
|
103
|
+
These rules check deployment settings, so `audit` takes `--request-log` to
|
|
104
|
+
declare the level the deployment uses (the audit process itself always logs
|
|
105
|
+
nothing). The CLI cannot enable link events, so it declares `linkEvents:
|
|
106
|
+
false`; embedders pass `host` themselves. The references are the runtime's
|
|
107
|
+
own [logging guarantees](MONITORING.md): records carry no URL, query, header,
|
|
108
|
+
body or binding, and `detailed` adds only the method and route pattern.
|
|
109
|
+
|
|
110
|
+
| Rule | Standard | Severity | Checks | Remediation |
|
|
111
|
+
|---|---|---|---|---|
|
|
112
|
+
| `privacy/request-log-minimal` | [Monitoring](MONITORING.md), Log records | medium (`info` when undeclared) | `host.requestLog` is `minimal` | Use the default log unless per-route rates are required |
|
|
113
|
+
| `privacy/link-events-off` | [Monitoring](MONITORING.md), The link event channel | medium; `high` with `includeCode` (`info` when undeclared and `dynamicLinks` is on) | The link event channel is off, or on without `includeCode` | Enable it only for a declared purpose; never disclose codes |
|
|
114
|
+
| `privacy/detailed-log-parameters` | [Monitoring](MONITORING.md), Log records | low | With `detailed` logging, no active route takes parameters (records name the pattern and method, never values) | Keep `minimal` on parameterised deployments |
|
|
115
|
+
|
|
116
|
+
## Writing custom rules
|
|
117
|
+
|
|
118
|
+
[`examples/compliance/rules.mjs`](../examples/compliance/rules.mjs) is a
|
|
119
|
+
complete operator module; it runs against the cookbook:
|
|
120
|
+
|
|
121
|
+
```sh
|
|
122
|
+
node src/cli.ts audit --project examples/cookbook \
|
|
123
|
+
--compliance baseline --compliance-rules "$PWD/examples/compliance/rules.mjs" --compliance-warn
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
A module exports up to three names:
|
|
127
|
+
|
|
128
|
+
```js
|
|
129
|
+
export const rules = [ /* rules to add; an id already in the profile is an error */ ];
|
|
130
|
+
export const disable = ['rfc9110/expired-routes']; // remove built-in or added rules by id
|
|
131
|
+
export const override = { 'oshp/security-headers': { severity: 'high' } }; // shallow-merge fields of an existing rule
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Order is fixed: profile rules, then `rules` added, then `override` applied,
|
|
135
|
+
then `disable` removed, then `--compliance-ignore`. The result is validated
|
|
136
|
+
as one set, so an override cannot produce an invalid rule.
|
|
137
|
+
|
|
138
|
+
The module must be an absolute path to an `.mjs`/`.js` file **outside** the
|
|
139
|
+
audited project, checked the way `--policy` is (`realpath` of both, the file
|
|
140
|
+
may not resolve inside the project root). It is imported as trusted operator
|
|
141
|
+
code with the host's privileges, the same standing as a plugin: a project
|
|
142
|
+
cannot ship its own rule set and grade itself. Rules read the context they
|
|
143
|
+
are given; a rule that reaches for the filesystem or the network is a plugin
|
|
144
|
+
in the wrong place.
|
|
145
|
+
|
|
146
|
+
## CLI
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
urlcode audit [--project dir] [--compliance baseline|strict|privacy|none]
|
|
150
|
+
[--compliance-rules /absolute/rules.mjs] [--compliance-ignore id,id]
|
|
151
|
+
[--compliance-warn] [--origin https://links.example] [--request-log minimal|detailed]
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Without any compliance flag the audit report is unchanged apart from
|
|
155
|
+
`compliance: null`. With one, the report gains a `compliance` section:
|
|
156
|
+
|
|
157
|
+
```json
|
|
158
|
+
{ "profile": "baseline", "rules": 9, "ruleIds": ["oshp/security-headers", "…"], "ignored": [],
|
|
159
|
+
"findings": [{ "rule": "rfc6585/throttle-functions", "severity": "medium", "route": "/hello/{name}",
|
|
160
|
+
"message": "…", "remediation": "…", "standard": { "name": "RFC 6585 …", "reference": "…", "section": "…" } }],
|
|
161
|
+
"counts": { "high": 0, "medium": 4, "low": 11, "info": 1 }, "pass": true,
|
|
162
|
+
"evidence": { "routes": 21, "active": 19, "dynamicLinks": false, "policies": ["agents", "cache", "security", "throttle"],
|
|
163
|
+
"files": ["urlcode.yaml", "routes/code.yaml"], "origin": null, "target": "node",
|
|
164
|
+
"host": { "requestLog": "minimal", "linkEvents": false, "includeCode": null },
|
|
165
|
+
"scope": "declared configuration and runtime facts; not a deployment or certification" } }
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
`--compliance-rules` alone implies `--compliance baseline`. `--origin` and
|
|
169
|
+
`--request-log` describe the deployment under review and are echoed in
|
|
170
|
+
`evidence`. Findings are sorted by severity, rule and route.
|
|
171
|
+
|
|
172
|
+
### Exit codes
|
|
173
|
+
|
|
174
|
+
| Condition | Exit |
|
|
175
|
+
|---|---|
|
|
176
|
+
| Readiness failed (`ready: false`) | 1, as before |
|
|
177
|
+
| A `high` finding and no `--compliance-warn` | 1 |
|
|
178
|
+
| `--compliance-warn`: findings are printed, `pass` is still `false` | 0 unless readiness failed |
|
|
179
|
+
| Unknown profile, rules file inside the project, malformed rule or ignore id | 1 with an `error` event on stderr |
|
|
180
|
+
|
|
181
|
+
## Programmatic API
|
|
182
|
+
|
|
183
|
+
```js
|
|
184
|
+
import { runCompliance, builtinProfiles, validateRules, resolveRules, loadComplianceRules } from '@jimhoyd/urlcode/compliance';
|
|
185
|
+
import { createRuntime } from '@jimhoyd/urlcode';
|
|
186
|
+
|
|
187
|
+
const runtime = await createRuntime('./site');
|
|
188
|
+
const report = await runCompliance(runtime, {
|
|
189
|
+
profile: 'strict', // baseline | strict | privacy | none
|
|
190
|
+
rules: [], override: {}, disable: [], // as a rules module would export them
|
|
191
|
+
ignore: ['rfc9110/expired-routes'],
|
|
192
|
+
origin: 'https://links.example',
|
|
193
|
+
host: { requestLog: 'minimal', linkEvents: false }, // what the deployment is configured with
|
|
194
|
+
});
|
|
195
|
+
await runtime.close();
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
The declarations ship with the package: `ComplianceRule` (with `ProjectRule`
|
|
199
|
+
and `RouteRule`, and `ProjectContext`/`RouteContext` for what `check`
|
|
200
|
+
receives), `RawFinding` and `Finding`, `ComplianceOptions`, `ComplianceReport`
|
|
201
|
+
and `ComplianceProfileName` are all exported from `@jimhoyd/urlcode/compliance`, so a
|
|
202
|
+
rules module written in TypeScript is checked against the same contract the
|
|
203
|
+
runtime validates at load time:
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
import type { ComplianceRule, ComplianceReport } from '@jimhoyd/urlcode/compliance';
|
|
207
|
+
import { runCompliance } from '@jimhoyd/urlcode/compliance';
|
|
208
|
+
|
|
209
|
+
export const rules: ComplianceRule[] = [{
|
|
210
|
+
id: 'acme/redirect-hosts',
|
|
211
|
+
title: 'Redirects only leave for approved hosts',
|
|
212
|
+
standard: { name: 'ACME link policy', reference: 'https://example.com/policies/links', section: 'Outbound' },
|
|
213
|
+
severity: 'high',
|
|
214
|
+
appliesTo: 'route',
|
|
215
|
+
check(context) {
|
|
216
|
+
if (context.config.redirect?.url.startsWith('https://acme.example/')) return [];
|
|
217
|
+
return [{ message: `${context.route.path} redirects outside the approved hosts`, remediation: 'Point the redirect at an approved host' }];
|
|
218
|
+
},
|
|
219
|
+
}];
|
|
220
|
+
const report: ComplianceReport = await runCompliance(runtime, { profile: 'strict', rules });
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
`runCompliance` accepts a started server from `startServer` or a runtime from
|
|
224
|
+
`createRuntime`; it re-reads the YAML from the runtime's `root` and takes the
|
|
225
|
+
plan from `testPlan()`, so rules see what the runtime compiled.
|
|
226
|
+
`auditProject(app, { compliance })` runs the same and attaches the report
|
|
227
|
+
under `compliance`. `builtinProfiles` maps profile names to their frozen rule
|
|
228
|
+
arrays for reuse or inspection; `validateRules` checks a rule array;
|
|
229
|
+
`resolveRules` builds the final set from a profile and operator additions.
|
|
230
|
+
|
|
231
|
+
## Extending with a plugin-style workflow
|
|
232
|
+
|
|
233
|
+
Keep rule modules where you keep plugins: in the operator application, under
|
|
234
|
+
version control, reviewed like code, outside every audited project. A shared
|
|
235
|
+
module can export rules that read the same policy tables a plugin's
|
|
236
|
+
`onActivate(runtime)` sees through `testPlan()`, so one review of "what does
|
|
237
|
+
this runtime enforce" serves both. Compose organisation rules on top of a
|
|
238
|
+
built-in profile with `override` for stricter severities and `disable` for
|
|
239
|
+
rules that a documented decision replaces, and run `urlcode audit
|
|
240
|
+
--compliance strict --compliance-rules …` in CI with the exit code as the
|
|
241
|
+
gate. Record the report beside the readiness and benchmark evidence for the
|
|
242
|
+
revision; a report proves what was declared at that commit, nothing more.
|