@mccaskie-labs/workbench-config 0.1.22 → 0.1.24

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @mccaskie-labs/workbench-config
2
2
 
3
+ ## 0.1.24
4
+
5
+ ### Patch Changes
6
+
7
+ - The database manifest gains sign-in: `createSessions`, `safeReturnPath` and `sameOrigin` (workbench-supabase 0.3.0), the optional `SESSION_SECRET`, two rules (sign-in through the app's own endpoints; session writes same-origin) and the probes `database.sign-in` and `database.session-cookie`.
8
+
9
+ ## 0.1.23
10
+
11
+ ### Patch Changes
12
+
13
+ - Probes for the sign-in kit. `email`: `email.account.kit-clean`, `email.account.sign-in-code`, `email.hook.unsigned-refused`, `email.hook.signed-refused-action` and `email.brand.logo-public`. `database`: `database.plain-error`.
14
+
3
15
  ## 0.1.22
4
16
 
5
17
  ### Patch Changes
@@ -12,7 +12,10 @@
12
12
  "createServerClient",
13
13
  "createUserClient",
14
14
  "createServiceClient",
15
- "WorkbenchSupabaseError"
15
+ "WorkbenchSupabaseError",
16
+ "createSessions",
17
+ "safeReturnPath",
18
+ "sameOrigin"
16
19
  ],
17
20
  "tools": [
18
21
  "sb_project_create",
@@ -56,6 +59,17 @@
56
59
  "production"
57
60
  ],
58
61
  "description": "The sb_secret_ key, only for code that must bypass RLS (createServiceClient); written by cf_worker_secret_put source=supabase_secret, always two-step"
62
+ },
63
+ {
64
+ "name": "SESSION_SECRET",
65
+ "required": false,
66
+ "secret": true,
67
+ "environments": [
68
+ "development",
69
+ "staging",
70
+ "production"
71
+ ],
72
+ "description": "Seals the sign-in session cookie (createSessions). 32 random bytes written by cf_worker_secret_put source=generate:32; replacing it signs everybody out, so it is two-step. Only needed by an app with sign-in"
59
73
  }
60
74
  ],
61
75
  "actions": [
@@ -142,6 +156,20 @@
142
156
  "text": "Keep Supabase's sign-in settings at the Workbench baseline (workbench-config VENDOR_SETTINGS), or record a project's change in vendor-settings.json with a reason. Control applies it with vendor_settings_apply and checks it every day.",
143
157
  "enforced_by": "control",
144
158
  "probe": "database.vendor-settings"
159
+ },
160
+ {
161
+ "id": "database.sign-in-server-side",
162
+ "kind": "must",
163
+ "text": "Sign people in through the app's own endpoints with createSessions: the browser never talks to Supabase Auth and never holds the access token, and codes are typed, never links.",
164
+ "enforced_by": "review",
165
+ "probe": "database.sign-in"
166
+ },
167
+ {
168
+ "id": "database.session-writes-same-origin",
169
+ "kind": "must",
170
+ "text": "Refuse a write (anything but GET or HEAD) made with the session cookie unless sameOrigin(request) is true.",
171
+ "enforced_by": "review",
172
+ "probe": "database.session-cookie"
145
173
  }
146
174
  ],
147
175
  "examples": [
@@ -202,6 +230,28 @@
202
230
  "expect": "pass",
203
231
  "cost_hint_usd": 0,
204
232
  "note": "vendor_settings_apply check_only reports that both databases match the baseline, the Send Email hook included"
233
+ },
234
+ {
235
+ "id": "database.plain-error",
236
+ "half": "runtime",
237
+ "expect": "pass",
238
+ "smoke": true,
239
+ "cost_hint_usd": 0,
240
+ "note": "plainError and plainAuthError (workbench-supabase) turn a raw database error and an email_exists auth error into plain words that name the app and never say whether an account exists"
241
+ },
242
+ {
243
+ "id": "database.sign-in",
244
+ "half": "runtime",
245
+ "expect": "pass",
246
+ "cost_hint_usd": 0,
247
+ "note": "Ask for a code, read it from the captured sign-in email, type it, and get a session that reads the user's own rows; a wrong code gets the plain line"
248
+ },
249
+ {
250
+ "id": "database.session-cookie",
251
+ "half": "runtime",
252
+ "expect": "pass",
253
+ "cost_hint_usd": 0,
254
+ "note": "The session cookie is __Host-, Secure, HttpOnly, SameSite=Lax and sealed; a cross-site write is refused; sign-out clears it with every attribute"
205
255
  }
206
256
  ]
207
257
  }
@@ -57,6 +57,11 @@
57
57
  { "id": "email.send.dev-key-recipient-refused", "half": "runtime", "expect": "refused_as_expected", "refusal": "recipient_not_allowed", "cost_hint_usd": 0 },
58
58
  { "id": "email.send.sender-not-allowed", "half": "runtime", "expect": "refused_as_expected", "refusal": "sender_not_allowed", "cost_hint_usd": 0 },
59
59
  { "id": "email.capture-refused-in-staging", "half": "runtime", "expect": "pass", "cost_hint_usd": 0, "note": "createMailer with transport capture and WORKBENCH_ENV=staging throws invalid_config" },
60
- { "id": "email.send.new-sender", "half": "runtime", "expect": "pass", "cost_hint_usd": 0.00035, "note": "Runs after the Control drill's gw_sender_add step; skipped without it" }
60
+ { "id": "email.send.new-sender", "half": "runtime", "expect": "pass", "cost_hint_usd": 0.00035, "note": "Runs after the Control drill's gw_sender_add step; skipped without it" },
61
+ { "id": "email.account.kit-clean", "half": "runtime", "expect": "pass", "smoke": true, "cost_hint_usd": 0, "note": "emailKitProblems over the app's catalogue (ACCOUNT_EMAILS), strings and brand.json returns []; runs in the Worker and sends nothing" },
62
+ { "id": "email.account.sign-in-code", "half": "runtime", "expect": "pass", "cost_hint_usd": 0.00035, "note": "renderTemplate account_sign_in_code with the app's brand.json, sent to Robert through the Gateway; check the logo, colours and code, and that there is no link. Not in the daily smoke run" },
63
+ { "id": "email.hook.unsigned-refused", "half": "runtime", "expect": "refused_as_expected", "refusal": "unauthorised", "smoke": true, "cost_hint_usd": 0, "note": "POST /auth/send-email from outside with no signature answers 401, not a sign-in redirect: proves the Access path bypass and the handler. Needs no secret" },
64
+ { "id": "email.hook.signed-refused-action", "half": "runtime", "expect": "refused_as_expected", "refusal": "refused", "cost_hint_usd": 0, "note": "A request signed with SEND_EMAIL_HOOK_SECRET for email_action_type recovery answers 422 and sends nothing; skipped while the Worker has no hook secret" },
65
+ { "id": "email.brand.logo-public", "half": "runtime", "expect": "pass", "smoke": true, "cost_hint_usd": 0, "note": "GET brand.json's logo.url from outside with no sign-in answers 200 with an image content type" }
61
66
  ]
62
67
  }
@@ -1,4 +1,4 @@
1
- export declare const MANIFEST_VERSION: "2216125e22d4";
1
+ export declare const MANIFEST_VERSION: "ecc54c728515";
2
2
  export declare const capabilities: {
3
3
  readonly ai: {
4
4
  readonly $schema: "./schema.json";
@@ -422,7 +422,7 @@ export declare const capabilities: {
422
422
  readonly status: "built";
423
423
  readonly surfaces: {
424
424
  readonly tools: readonly ["sb_project_create", "sb_migration_apply", "sb_migration_rollback", "sb_query", "sb_project_delete", "cf_worker_secret_put", "vendor_settings_apply"];
425
- readonly package: readonly ["supabaseConfig", "createBrowserClient", "createServerClient", "createUserClient", "createServiceClient", "WorkbenchSupabaseError"];
425
+ readonly package: readonly ["supabaseConfig", "createBrowserClient", "createServerClient", "createUserClient", "createServiceClient", "WorkbenchSupabaseError", "createSessions", "safeReturnPath", "sameOrigin"];
426
426
  };
427
427
  readonly config: readonly [{
428
428
  readonly name: "SUPABASE_URL";
@@ -442,6 +442,12 @@ export declare const capabilities: {
442
442
  readonly secret: true;
443
443
  readonly environments: readonly ["staging", "production"];
444
444
  readonly description: "The sb_secret_ key, only for code that must bypass RLS (createServiceClient); written by cf_worker_secret_put source=supabase_secret, always two-step";
445
+ }, {
446
+ readonly name: "SESSION_SECRET";
447
+ readonly required: false;
448
+ readonly secret: true;
449
+ readonly environments: readonly ["development", "staging", "production"];
450
+ readonly description: "Seals the sign-in session cookie (createSessions). 32 random bytes written by cf_worker_secret_put source=generate:32; replacing it signs everybody out, so it is two-step. Only needed by an app with sign-in";
445
451
  }];
446
452
  readonly actions: readonly [{
447
453
  readonly tool: "sb_project_create";
@@ -513,6 +519,18 @@ export declare const capabilities: {
513
519
  readonly text: "Keep Supabase's sign-in settings at the Workbench baseline (workbench-config VENDOR_SETTINGS), or record a project's change in vendor-settings.json with a reason. Control applies it with vendor_settings_apply and checks it every day.";
514
520
  readonly enforced_by: "control";
515
521
  readonly probe: "database.vendor-settings";
522
+ }, {
523
+ readonly id: "database.sign-in-server-side";
524
+ readonly kind: "must";
525
+ readonly text: "Sign people in through the app's own endpoints with createSessions: the browser never talks to Supabase Auth and never holds the access token, and codes are typed, never links.";
526
+ readonly enforced_by: "review";
527
+ readonly probe: "database.sign-in";
528
+ }, {
529
+ readonly id: "database.session-writes-same-origin";
530
+ readonly kind: "must";
531
+ readonly text: "Refuse a write (anything but GET or HEAD) made with the session cookie unless sameOrigin(request) is true.";
532
+ readonly enforced_by: "review";
533
+ readonly probe: "database.session-cookie";
516
534
  }];
517
535
  readonly examples: readonly [{
518
536
  readonly id: "database.example.user-client";
@@ -563,6 +581,25 @@ export declare const capabilities: {
563
581
  readonly expect: "pass";
564
582
  readonly cost_hint_usd: 0;
565
583
  readonly note: "vendor_settings_apply check_only reports that both databases match the baseline, the Send Email hook included";
584
+ }, {
585
+ readonly id: "database.plain-error";
586
+ readonly half: "runtime";
587
+ readonly expect: "pass";
588
+ readonly smoke: true;
589
+ readonly cost_hint_usd: 0;
590
+ readonly note: "plainError and plainAuthError (workbench-supabase) turn a raw database error and an email_exists auth error into plain words that name the app and never say whether an account exists";
591
+ }, {
592
+ readonly id: "database.sign-in";
593
+ readonly half: "runtime";
594
+ readonly expect: "pass";
595
+ readonly cost_hint_usd: 0;
596
+ readonly note: "Ask for a code, read it from the captured sign-in email, type it, and get a session that reads the user's own rows; a wrong code gets the plain line";
597
+ }, {
598
+ readonly id: "database.session-cookie";
599
+ readonly half: "runtime";
600
+ readonly expect: "pass";
601
+ readonly cost_hint_usd: 0;
602
+ readonly note: "The session cookie is __Host-, Secure, HttpOnly, SameSite=Lax and sealed; a cross-site write is refused; sign-out clears it with every attribute";
566
603
  }];
567
604
  };
568
605
  readonly email: {
@@ -771,6 +808,41 @@ export declare const capabilities: {
771
808
  readonly expect: "pass";
772
809
  readonly cost_hint_usd: 0.00035;
773
810
  readonly note: "Runs after the Control drill's gw_sender_add step; skipped without it";
811
+ }, {
812
+ readonly id: "email.account.kit-clean";
813
+ readonly half: "runtime";
814
+ readonly expect: "pass";
815
+ readonly smoke: true;
816
+ readonly cost_hint_usd: 0;
817
+ readonly note: "emailKitProblems over the app's catalogue (ACCOUNT_EMAILS), strings and brand.json returns []; runs in the Worker and sends nothing";
818
+ }, {
819
+ readonly id: "email.account.sign-in-code";
820
+ readonly half: "runtime";
821
+ readonly expect: "pass";
822
+ readonly cost_hint_usd: 0.00035;
823
+ readonly note: "renderTemplate account_sign_in_code with the app's brand.json, sent to Robert through the Gateway; check the logo, colours and code, and that there is no link. Not in the daily smoke run";
824
+ }, {
825
+ readonly id: "email.hook.unsigned-refused";
826
+ readonly half: "runtime";
827
+ readonly expect: "refused_as_expected";
828
+ readonly refusal: "unauthorised";
829
+ readonly smoke: true;
830
+ readonly cost_hint_usd: 0;
831
+ readonly note: "POST /auth/send-email from outside with no signature answers 401, not a sign-in redirect: proves the Access path bypass and the handler. Needs no secret";
832
+ }, {
833
+ readonly id: "email.hook.signed-refused-action";
834
+ readonly half: "runtime";
835
+ readonly expect: "refused_as_expected";
836
+ readonly refusal: "refused";
837
+ readonly cost_hint_usd: 0;
838
+ readonly note: "A request signed with SEND_EMAIL_HOOK_SECRET for email_action_type recovery answers 422 and sends nothing; skipped while the Worker has no hook secret";
839
+ }, {
840
+ readonly id: "email.brand.logo-public";
841
+ readonly half: "runtime";
842
+ readonly expect: "pass";
843
+ readonly smoke: true;
844
+ readonly cost_hint_usd: 0;
845
+ readonly note: "GET brand.json's logo.url from outside with no sign-in answers 200 with an image content type";
774
846
  }];
775
847
  };
776
848
  readonly "email-inbound": {
@@ -1,6 +1,6 @@
1
1
  // Generated by `npm run capabilities:build` from capabilities/*.json. Do not edit.
2
2
  // check:capabilities fails in Checks when this file is stale.
3
- export const MANIFEST_VERSION = "2216125e22d4";
3
+ export const MANIFEST_VERSION = "ecc54c728515";
4
4
  export const capabilities = {
5
5
  "ai": {
6
6
  "$schema": "./schema.json",
@@ -629,7 +629,10 @@ export const capabilities = {
629
629
  "createServerClient",
630
630
  "createUserClient",
631
631
  "createServiceClient",
632
- "WorkbenchSupabaseError"
632
+ "WorkbenchSupabaseError",
633
+ "createSessions",
634
+ "safeReturnPath",
635
+ "sameOrigin"
633
636
  ]
634
637
  },
635
638
  "config": [
@@ -664,6 +667,17 @@ export const capabilities = {
664
667
  "production"
665
668
  ],
666
669
  "description": "The sb_secret_ key, only for code that must bypass RLS (createServiceClient); written by cf_worker_secret_put source=supabase_secret, always two-step"
670
+ },
671
+ {
672
+ "name": "SESSION_SECRET",
673
+ "required": false,
674
+ "secret": true,
675
+ "environments": [
676
+ "development",
677
+ "staging",
678
+ "production"
679
+ ],
680
+ "description": "Seals the sign-in session cookie (createSessions). 32 random bytes written by cf_worker_secret_put source=generate:32; replacing it signs everybody out, so it is two-step. Only needed by an app with sign-in"
667
681
  }
668
682
  ],
669
683
  "actions": [
@@ -750,6 +764,20 @@ export const capabilities = {
750
764
  "text": "Keep Supabase's sign-in settings at the Workbench baseline (workbench-config VENDOR_SETTINGS), or record a project's change in vendor-settings.json with a reason. Control applies it with vendor_settings_apply and checks it every day.",
751
765
  "enforced_by": "control",
752
766
  "probe": "database.vendor-settings"
767
+ },
768
+ {
769
+ "id": "database.sign-in-server-side",
770
+ "kind": "must",
771
+ "text": "Sign people in through the app's own endpoints with createSessions: the browser never talks to Supabase Auth and never holds the access token, and codes are typed, never links.",
772
+ "enforced_by": "review",
773
+ "probe": "database.sign-in"
774
+ },
775
+ {
776
+ "id": "database.session-writes-same-origin",
777
+ "kind": "must",
778
+ "text": "Refuse a write (anything but GET or HEAD) made with the session cookie unless sameOrigin(request) is true.",
779
+ "enforced_by": "review",
780
+ "probe": "database.session-cookie"
753
781
  }
754
782
  ],
755
783
  "examples": [
@@ -810,6 +838,28 @@ export const capabilities = {
810
838
  "expect": "pass",
811
839
  "cost_hint_usd": 0,
812
840
  "note": "vendor_settings_apply check_only reports that both databases match the baseline, the Send Email hook included"
841
+ },
842
+ {
843
+ "id": "database.plain-error",
844
+ "half": "runtime",
845
+ "expect": "pass",
846
+ "smoke": true,
847
+ "cost_hint_usd": 0,
848
+ "note": "plainError and plainAuthError (workbench-supabase) turn a raw database error and an email_exists auth error into plain words that name the app and never say whether an account exists"
849
+ },
850
+ {
851
+ "id": "database.sign-in",
852
+ "half": "runtime",
853
+ "expect": "pass",
854
+ "cost_hint_usd": 0,
855
+ "note": "Ask for a code, read it from the captured sign-in email, type it, and get a session that reads the user's own rows; a wrong code gets the plain line"
856
+ },
857
+ {
858
+ "id": "database.session-cookie",
859
+ "half": "runtime",
860
+ "expect": "pass",
861
+ "cost_hint_usd": 0,
862
+ "note": "The session cookie is __Host-, Secure, HttpOnly, SameSite=Lax and sealed; a cross-site write is refused; sign-out clears it with every attribute"
813
863
  }
814
864
  ]
815
865
  },
@@ -1073,6 +1123,46 @@ export const capabilities = {
1073
1123
  "expect": "pass",
1074
1124
  "cost_hint_usd": 0.00035,
1075
1125
  "note": "Runs after the Control drill's gw_sender_add step; skipped without it"
1126
+ },
1127
+ {
1128
+ "id": "email.account.kit-clean",
1129
+ "half": "runtime",
1130
+ "expect": "pass",
1131
+ "smoke": true,
1132
+ "cost_hint_usd": 0,
1133
+ "note": "emailKitProblems over the app's catalogue (ACCOUNT_EMAILS), strings and brand.json returns []; runs in the Worker and sends nothing"
1134
+ },
1135
+ {
1136
+ "id": "email.account.sign-in-code",
1137
+ "half": "runtime",
1138
+ "expect": "pass",
1139
+ "cost_hint_usd": 0.00035,
1140
+ "note": "renderTemplate account_sign_in_code with the app's brand.json, sent to Robert through the Gateway; check the logo, colours and code, and that there is no link. Not in the daily smoke run"
1141
+ },
1142
+ {
1143
+ "id": "email.hook.unsigned-refused",
1144
+ "half": "runtime",
1145
+ "expect": "refused_as_expected",
1146
+ "refusal": "unauthorised",
1147
+ "smoke": true,
1148
+ "cost_hint_usd": 0,
1149
+ "note": "POST /auth/send-email from outside with no signature answers 401, not a sign-in redirect: proves the Access path bypass and the handler. Needs no secret"
1150
+ },
1151
+ {
1152
+ "id": "email.hook.signed-refused-action",
1153
+ "half": "runtime",
1154
+ "expect": "refused_as_expected",
1155
+ "refusal": "refused",
1156
+ "cost_hint_usd": 0,
1157
+ "note": "A request signed with SEND_EMAIL_HOOK_SECRET for email_action_type recovery answers 422 and sends nothing; skipped while the Worker has no hook secret"
1158
+ },
1159
+ {
1160
+ "id": "email.brand.logo-public",
1161
+ "half": "runtime",
1162
+ "expect": "pass",
1163
+ "smoke": true,
1164
+ "cost_hint_usd": 0,
1165
+ "note": "GET brand.json's logo.url from outside with no sign-in answers 200 with an image content type"
1076
1166
  }
1077
1167
  ]
1078
1168
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mccaskie-labs/workbench-config",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
4
4
  "description": "Workbench app configuration: WORKBENCH_ENV, typed variables that fail by name, secrets that are never printed, and the capabilities manifest.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",