@pikku/inspector 0.12.103 → 0.12.106

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.
Files changed (62) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/dist/add/add-cli.js +21 -11
  3. package/dist/add/add-functions.js +8 -8
  4. package/dist/add/add-virtual-users.d.ts +13 -0
  5. package/dist/add/{add-personas.js → add-virtual-users.js} +33 -33
  6. package/dist/add/add-workflow.js +14 -10
  7. package/dist/error-codes.d.ts +4 -3
  8. package/dist/error-codes.js +4 -3
  9. package/dist/inspector.js +5 -3
  10. package/dist/types.d.ts +3 -3
  11. package/dist/utils/extract-function-name.js +1 -1
  12. package/dist/utils/get-property-value.js +1 -1
  13. package/dist/utils/load-addon-functions-meta.js +1 -0
  14. package/dist/utils/post-process.d.ts +36 -36
  15. package/dist/utils/post-process.js +198 -173
  16. package/dist/utils/serialize-inspector-state.d.ts +2 -2
  17. package/dist/utils/serialize-inspector-state.js +6 -6
  18. package/dist/utils/single-declaration.d.ts +3 -3
  19. package/dist/utils/single-declaration.js +3 -3
  20. package/dist/utils/validate-single-step-workflows.d.ts +3 -0
  21. package/dist/utils/validate-single-step-workflows.js +32 -0
  22. package/dist/utils/workflow/dsl/extract-dsl-workflow.js +3 -3
  23. package/dist/utils/workflow/dsl/patterns.d.ts +3 -3
  24. package/dist/utils/workflow/dsl/patterns.js +4 -4
  25. package/dist/utils/workflow/graph/convert-dsl-to-graph.js +6 -6
  26. package/dist/utils/workflow/graph/workflow-graph.types.d.ts +5 -5
  27. package/dist/visit.js +2 -2
  28. package/knowledge/decisions/a-channel-with-auth-false-marks-its-connect-and-disconnect-sessionless.md +22 -0
  29. package/knowledge/decisions/a-declared-scope-id-never-shadows-a-definition.md +20 -0
  30. package/knowledge/decisions/a-dsl-step-virtual-user-is-the-literal-name-in-its-options.md +27 -0
  31. package/knowledge/decisions/a-feature-flags-anyof-is-read-from-the-ast-and-an-empty-list-is-refused.md +20 -0
  32. package/knowledge/decisions/a-non-literal-string-array-reads-as-absent.md +25 -0
  33. package/knowledge/decisions/a-permission-with-an-underscore-data-parameter-takes-no-data.md +20 -0
  34. package/knowledge/decisions/a-provider-login-virtual-user-is-never-runnable.md +27 -0
  35. package/knowledge/decisions/a-remote-addon-bound-credential-or-secret-must-be-wired.md +27 -0
  36. package/knowledge/decisions/a-route-auth-overrides-its-groups-auth.md +22 -0
  37. package/knowledge/decisions/a-route-requires-a-session-when-any-auth-signal-says-so.md +29 -0
  38. package/knowledge/decisions/a-webhook-source-with-verify-gets-a-singleton-secret-credential.md +21 -0
  39. package/knowledge/decisions/a-wire-with-auth-disabled-cannot-point-at-a-pikkufunc.md +21 -0
  40. package/knowledge/decisions/addon-contracts-cannot-wire-or-declare-handlers.md +25 -0
  41. package/knowledge/decisions/addon-declarations-name-the-secrets-and-credentials-the-app-lends-it.md +19 -0
  42. package/knowledge/decisions/an-inline-wiring-function-is-sessionless-unless-built-with-pikkufunc.md +23 -0
  43. package/knowledge/decisions/better-auth-admin-plugin-is-refused.md +24 -0
  44. package/knowledge/decisions/define-screens-scopes-must-be-string-literals.md +18 -0
  45. package/knowledge/decisions/index.md +38 -0
  46. package/knowledge/decisions/inspector-records-a-non-literal-global-grant-as-source-text.md +20 -0
  47. package/knowledge/decisions/only-one-pikku-better-auth-export-per-codebase.md +19 -0
  48. package/knowledge/decisions/scope-references-and-secret-aliases-are-critical-diagnostics.md +23 -0
  49. package/knowledge/decisions/scope-segments-may-not-be-wildcards-or-contain-colons.md +23 -0
  50. package/knowledge/decisions/shorthand-permission-entries-are-extracted.md +22 -0
  51. package/knowledge/decisions/system-roles-declare-literal-unqualified-scopes.md +28 -0
  52. package/knowledge/decisions/the-inspector-records-agent-permissions-only-to-aggregate-services.md +14 -0
  53. package/knowledge/decisions/vault-secrets-reach-no-sink-and-pii-stays-out-of-logs-and-webhooks.md +30 -0
  54. package/knowledge/decisions/webhook-gateway-wrappers-are-sessionless-and-gateway-auth-runs-inside-the-handler.md +26 -0
  55. package/knowledge/index.md +14 -0
  56. package/knowledge/questions/does-a-non-literal-remote-addon-auth-get-checked.md +26 -0
  57. package/knowledge/questions/index.md +16 -0
  58. package/knowledge/questions/is-an-incomplete-oauth2-block-dropped-without-a-log.md +23 -0
  59. package/knowledge/questions/should-app-functions-named-like-scenario-instrumentation-be-refused.md +28 -0
  60. package/knowledge/questions/should-the-openapi-document-default-every-operation-to-a-bearer-or-api-key.md +22 -0
  61. package/package.json +3 -3
  62. package/dist/add/add-personas.d.ts +0 -13
@@ -0,0 +1,23 @@
1
+ ---
2
+ type: decision
3
+ title: An inline wiring function is sessionless unless its helper is pikkuFunc
4
+ description: The inspector derives sessionless for a function inlined into a wiring from the name of the helper that built it
5
+ tags: [inspector, functions, auth]
6
+ ---
7
+
8
+ # An inline wiring function is sessionless unless its helper is pikkuFunc
9
+
10
+ `ensureInlineWiringFunction` in `packages/inspector/src/utils/ensure-function-metadata.ts`
11
+ registers metadata for a function written inline in a wiring (lines 329-344). The comment at
12
+ lines 324-327 says the stub would otherwise carry no session info, and the runtime would then
13
+ require a session for every call.
14
+
15
+ After that, if the metadata's `sessionless` is still `undefined` (line 349) and the initializer
16
+ is a call whose callee is a plain identifier (lines 350-351), `sessionless` is set to
17
+ `callee !== 'pikkuFunc'` (line 353). The comment at lines 324-327 gives the same rule as the
18
+ one `addFunctions` applies to named functions.
19
+
20
+ If the callee is not a plain identifier, `sessionless` is left `undefined`.
21
+
22
+ **What this rules out:** treating an inline wiring function as session-required because it was
23
+ built by a helper other than `pikkuFunc`.
@@ -0,0 +1,24 @@
1
+ ---
2
+ type: decision
3
+ title: better-auth's admin plugin is refused in favour of pikkuBan
4
+ description: Wiring admin() in pikkuBetterAuth throws at build time, and the error names pikkuBan() as the replacement
5
+ tags: [inspector, auth, better-auth]
6
+ ---
7
+
8
+ # better-auth's admin plugin is refused in favour of pikkuBan
9
+
10
+ In `packages/inspector/src/add/add-auth.ts`, a plugin named `admin` inside
11
+ `pikkuBetterAuth` throws rather than warning (lines 342-344). The error text is
12
+ `UNSUPPORTED_ADMIN_PLUGIN` (lines 174-190). It tells the app to use `pikkuBan()`.
13
+
14
+ The doc comment above the constant (lines 158-172) gives the reason. `admin()`
15
+ authorizes against a `user.role` column, and pikku authorizes on scopes. Running
16
+ both means projecting one onto the other, coarsely. The comment says user
17
+ management now lives in `@pikku/addon-admin`, and the ban refusal lives in
18
+ `pikkuBan()`.
19
+
20
+ The comment at lines 170-172 says the throw is deliberate: "the failure it prevents
21
+ is silent". An app that drops `admin()` without wiring `pikkuBan()` keeps its ban
22
+ columns and stops enforcing bans.
23
+
24
+ **What this rules out:** `admin()` as a second authorization model next to scopes.
@@ -0,0 +1,18 @@
1
+ ---
2
+ type: decision
3
+ title: A define-screens scopes list must be string literals, or the build reports a critical error
4
+ description: add-define-screens reads each screen's scopes by AST and reports ADDON_SCREENS_NOT_STATIC when the value is not an array of string literals
5
+ tags: [inspector, screens, scopes]
6
+ ---
7
+
8
+ # A define-screens scopes list must be string literals, or the build reports a critical error
9
+
10
+ In `packages/inspector/src/add/add-define-screens.ts`, each screen's `scopes` value is read with
11
+ `stringArray(value)` (line 110). If that returns nothing, `fail(...)` runs (lines 111-115), with the
12
+ message "A screen's scopes must be an array of string literals".
13
+
14
+ `fail` is defined at lines 60-61 as `logger.critical(ErrorCode.ADDON_SCREENS_NOT_STATIC, message)`.
15
+ The same block requires `component` to be a literal lazy import (lines 116-120).
16
+
17
+ **What this rules out:** a screen whose scopes come from a variable, a computed value or a call.
18
+ The inspector reads scopes statically, so it cannot check a value it cannot see.
@@ -0,0 +1,38 @@
1
+ ---
2
+ type: overview
3
+ title: Decisions
4
+ description: Rules chosen in inspector, and what each one rules out
5
+ ---
6
+
7
+ # Decisions
8
+
9
+ A rule that was chosen, and what it rules out.
10
+
11
+ <!-- pikku:knowledge-index -->
12
+ - [A channel with auth false marks its connect and disconnect handlers sessionless](a-channel-with-auth-false-marks-its-connect-and-disconnect-sessionless.md) — The inspector synthesises metadata for a channel's connect and disconnect functions and sets sessionless from the channel's auth
13
+ - [A declared scope id never shadows a definition](a-declared-scope-id-never-shadows-a-definition.md) — A bare declareScopes id is added only when no definition already holds it, so an addon's richer scope tree is kept
14
+ - [A workflow DSL step's virtual user is the literal name written in its options object](a-dsl-step-virtual-user-is-the-literal-name-in-its-options.md) — The DSL extractor reads a step's virtual user from a property access or string-keyed element access in the options literal, and leaves it undefined when the value is not static
15
+ - [A feature flag's anyOf is read from the AST, and an empty anyOf is refused](a-feature-flags-anyof-is-read-from-the-ast-and-an-empty-list-is-refused.md) — add-feature-flag reads the scope names in anyOf statically, refuses a non-array or empty anyOf, and refuses a flag name containing the scope separator
16
+ - [A string array with one computed entry reads as absent, not as its literal entries](a-non-literal-string-array-reads-as-absent.md) — stringArrayProperty returns undefined unless every element of the array is a string literal
17
+ - [A permission whose data parameter starts with an underscore takes no data](a-permission-with-an-underscore-data-parameter-takes-no-data.md) — An underscore-prefixed data parameter marks a permission as auth-only, and pikkuAuth always has no data
18
+ - [A provider-login virtual user is never runnable, even when declared runnable](a-provider-login-virtual-user-is-never-runnable.md) — Runnable is derived from the account, so a virtual user with a provider login cannot be driven, and an explicit runnable true does not change that
19
+ - [A remote addon's bound credential or secret id must name a wired slot, or validation reports a critical error](a-remote-addon-bound-credential-or-secret-must-be-wired.md) — validateRemoteAddonAuth checks that a literal auth.credentialId or auth.secretId on a wireRemoteAddon names a declared credential or secret, and runs in every inspector pass
20
+ - [A route's own auth overrides its group's, and auth false on a route makes an inline function sessionless](a-route-auth-overrides-its-groups-auth.md) — The route's auth is used when it is true or false, the group's inherited value otherwise; a resolved false sets sessionless on an inline function
21
+ - [A route requires a session when any auth signal says so](a-route-requires-a-session-when-any-auth-signal-says-so.md) — Four places can demand a session for an HTTP route and they combine with OR; a scope gate counts as a session demand
22
+ - [A webhook source with a verify step declares a singleton credential for its secret](a-webhook-source-with-verify-gets-a-singleton-secret-credential.md) — When a webhook source has a verify step, the inspector registers a string credential named by the source unless the author sets credential
23
+ - [A wire with auth disabled cannot point at a pikkuFunc](a-wire-with-auth-disabled-cannot-point-at-a-pikkufunc.md) — A route or wiring with auth false that names a session-requiring pikkuFunc is reported as critical, and the function is not accepted
24
+ - [Addons may not call wiring helpers or declare middleware or permissions](addon-contracts-cannot-wire-or-declare-handlers.md) — Addon packages are banned from transport wirings, and their define* contracts may not carry middleware or permissions
25
+ - [An addon declaration names the secrets and credentials the app lends it](addon-declarations-name-the-secrets-and-credentials-the-app-lends-it.md) — secretGrants and credentialGrants are declared on the addon instance as lists of names the addon reads
26
+ - [An inline wiring function is sessionless unless its helper is pikkuFunc](an-inline-wiring-function-is-sessionless-unless-built-with-pikkufunc.md) — The inspector derives sessionless for a function inlined into a wiring from the name of the helper that built it
27
+ - [better-auth's admin plugin is refused in favour of pikkuBan](better-auth-admin-plugin-is-refused.md) — Wiring admin() in pikkuBetterAuth throws at build time, and the error names pikkuBan() as the replacement
28
+ - [A define-screens scopes list must be string literals, or the build reports a critical error](define-screens-scopes-must-be-string-literals.md) — add-define-screens reads each screen's scopes by AST and reports ADDON_SCREENS_NOT_STATIC when the value is not an array of string literals
29
+ - [A non-literal global grant is recorded as its source text, not dropped](inspector-records-a-non-literal-global-grant-as-source-text.md) — globalSecrets and globalCredentials on a wireAddon keep a computed value as the expression's source text
30
+ - [Only one pikkuBetterAuth export is allowed per codebase](only-one-pikku-better-auth-export-per-codebase.md) — pikkuBetterAuth must be an exported const, and a second definition is refused with the first file named
31
+ - [An undeclared scope and a secret service passed under another name are critical diagnostics](scope-references-and-secret-aliases-are-critical-diagnostics.md) — Post-processing reports a function that requires an undeclared scope, and a function-facing service that is a SecretService under another name
32
+ - [Scope segments may not be wildcards or contain colons](scope-segments-may-not-be-wildcards-or-contain-colons.md) — The inspector refuses an empty scope segment, a segment containing the separator, and a literal wildcard segment
33
+ - [Shorthand permission entries are extracted, so the audit never reads a guarded function as open](shorthand-permission-entries-are-extracted.md) — extractPermissionPikkuNames reads { canAdminOrg } shorthand entries in a permissions record as names, as the runtime does
34
+ - [System roles must declare literal, unqualified scopes](system-roles-declare-literal-unqualified-scopes.md) — A system role needs a literal scopes array of string literals, and its name may not contain the scope separator
35
+ - [The inspector records an agent's permissions only to aggregate the services they need, never to check access](the-inspector-records-agent-permissions-only-to-aggregate-services.md) — addAgent adds the agent's permission names to usedPermissions, and post-process reads that set only to collect each permission's services
36
+ - [Vault secrets reach no sink, and PII is kept out of logs, the console and webhooks only](vault-secrets-reach-no-sink-and-pii-stays-out-of-logs-and-webhooks.md) — check-classified-sinks rejects revealed secrets in every listed sink and PII only in the logger, console and webhook sinks
37
+ - [Webhook gateway wrappers are registered sessionless, and gateway auth is enforced inside the handler call](webhook-gateway-wrappers-are-sessionless-and-gateway-auth-runs-inside-the-handler.md) — The inspector and the runtime both register a webhook gateway's POST and verify routes as sessionless with auth false, so the route never demands a session and the gateway's own auth flag is applied only when the handler runs
38
+ <!-- /pikku:knowledge-index -->
@@ -0,0 +1,20 @@
1
+ ---
2
+ type: decision
3
+ title: A non-literal global grant is recorded as its source text, not dropped
4
+ description: globalSecrets and globalCredentials on a wireAddon keep a computed value as the expression's source text
5
+ tags: [inspector, addons, grants]
6
+ ---
7
+
8
+ # A non-literal global grant is recorded as its source text, not dropped
9
+
10
+ In `packages/inspector/src/add/add-wire-addon.ts`, the `globalSecrets` and
11
+ `globalCredentials` properties of a `wireAddon` call are read in the same loop as the
12
+ other options (lines 216-225). A string literal is stored as its text. Any other
13
+ expression is stored as `prop.initializer.getText()` (lines 219-221 and 223-225).
14
+
15
+ The comment at lines 217-218 gives the reason: "The grant is real at runtime whatever the
16
+ reason evaluates to, so a non-literal is recorded as its source text rather than dropped."
17
+
18
+ **What this rules out:** discarding a global grant because its value is computed. The
19
+ inspector does not evaluate the expression, so the recorded text is what a later reader
20
+ sees.
@@ -0,0 +1,19 @@
1
+ ---
2
+ type: decision
3
+ title: Only one pikkuBetterAuth export is allowed per codebase
4
+ description: pikkuBetterAuth must be an exported const, and a second definition is refused with the first file named
5
+ tags: [inspector, auth, better-auth]
6
+ ---
7
+
8
+ # Only one pikkuBetterAuth export is allowed per codebase
9
+
10
+ `addAuth` in `packages/inspector/src/add/add-auth.ts` requires `pikkuBetterAuth(...)`
11
+ to be assigned to an exported `const` (lines 242-248). The message says the CLI has
12
+ to import it.
13
+
14
+ A second definition is refused as critical `DUPLICATE_AUTH_DEFINITION` (lines
15
+ 250-256). The message says "Only one pikkuBetterAuth(...) is allowed per codebase"
16
+ and names both the new file and the first one.
17
+
18
+ **What this rules out:** two better-auth configurations in one codebase, so the
19
+ CLI always has a single auth definition to read.
@@ -0,0 +1,23 @@
1
+ ---
2
+ type: decision
3
+ title: An undeclared scope and a secret service passed under another name are critical diagnostics
4
+ description: Post-processing reports a function that requires an undeclared scope, and a function-facing service that is a SecretService under another name
5
+ tags: [inspector, scopes, secrets, post-process]
6
+ ---
7
+
8
+ # An undeclared scope and a secret service passed under another name are critical diagnostics
9
+
10
+ Both checks run from `packages/inspector/src/inspector.ts`: `validateNoSecretAliasServices` at line
11
+ 539, and `validateScopeReferences` at line 579.
12
+
13
+ `validateScopeReferences` in `packages/inspector/src/utils/post-process.ts` (lines 1671-1703) checks each
14
+ function's scopes against the set declared with `defineScope`. A trailing `:*` is stripped before the
15
+ lookup (line 1684). A bare `*` is reported (lines 1686-1692), and an undeclared scope is reported with
16
+ the list of available scopes (lines 1694-1700). Both are `logger.critical` with `ErrorCode.INVALID_VALUE`.
17
+
18
+ `validateNoSecretAliasServices` (lines 1484-1522) reports a function-facing service whose type is a
19
+ `SecretService` under another name. The comment at lines 1479-1482 says a name-based rule would miss
20
+ `pikkuServices(async ({ secrets }) => ({ cfg: secrets }))`, so the check uses the type.
21
+
22
+ **What this rules out:** a function that requires a scope no `defineScope` declares, and a function
23
+ receiving a secret service under a different name.
@@ -0,0 +1,23 @@
1
+ ---
2
+ type: decision
3
+ title: Scope segments may not be wildcards or contain colons
4
+ description: The inspector refuses an empty scope segment, a segment containing the separator, and a literal wildcard segment
5
+ tags: [inspector, scopes]
6
+ ---
7
+
8
+ # Scope segments may not be wildcards or contain colons
9
+
10
+ `isValidSegment` in `packages/inspector/src/add/add-scope.ts` checks one segment at a
11
+ time and reports each failure as critical:
12
+
13
+ - an empty segment is refused (lines 21-27);
14
+ - a segment containing `:` is refused, with the message telling the author to nest
15
+ with the `scopes` property instead (lines 28-35);
16
+ - a segment equal to `*` is refused, because `*` is reserved for granting a scope
17
+ and its descendants (lines 36-43).
18
+
19
+ `declareScopes` ids are split on `:` and every segment is checked (line 161). Nested
20
+ `scopes` keys are checked the same way (line 77).
21
+
22
+ **What this rules out:** a declared scope that is itself a wildcard, and a scope
23
+ name that hides a second level of nesting inside one segment.
@@ -0,0 +1,22 @@
1
+ ---
2
+ type: decision
3
+ title: Shorthand permission entries are extracted, so the audit never reads a guarded function as open
4
+ description: extractPermissionPikkuNames reads { canAdminOrg } shorthand entries in a permissions record as names, as the runtime does
5
+ tags: [inspector, permissions, audit]
6
+ ---
7
+
8
+ # Shorthand permission entries are extracted, so the audit never reads a guarded function as open
9
+
10
+ `extractPermissionPikkuNames` in `packages/inspector/src/utils/permissions.ts` (line 15) reads a
11
+ permissions value in two shapes. An array is walked element by element (lines 44-48). An object
12
+ literal is walked property by property (lines 49-62).
13
+
14
+ For a `PropertyAssignment` the initializer is extracted (line 53). For a
15
+ `ShorthandPropertyAssignment`, such as `permissions: { canAdminOrg }`, the property name is
16
+ extracted (lines 54-60).
17
+
18
+ The comment at lines 55-59 says the runtime resolves the shorthand the same way as the longhand
19
+ form, and that dropping it once reported a guarded function as having no permissions.
20
+
21
+ **What this rules out:** an audit that skips shorthand permission entries and so shows a guarded
22
+ function as open.
@@ -0,0 +1,28 @@
1
+ ---
2
+ type: decision
3
+ title: System roles must declare literal, unqualified scopes
4
+ description: A system role needs a literal scopes array of string literals, and its name may not contain the scope separator
5
+ tags: [inspector, roles, scopes]
6
+ ---
7
+
8
+ # System roles must declare literal, unqualified scopes
9
+
10
+ `addSystemRole` in `packages/inspector/src/add/add-system-role.ts` applies these rules
11
+ to each `defineSystemRole` entry:
12
+
13
+ - `scopes` must be present and must be an array literal. A missing or non-literal
14
+ `scopes` is reported as critical and the role is dropped (lines 37-54, and the
15
+ skip at 143-146). The message asks for `scopes: []` when a role grants nothing
16
+ (line 40).
17
+ - Each element of `scopes` must be a string literal. A non-literal element is
18
+ reported as critical and skipped (lines 57-66). The reason given is that scopes
19
+ are read by AST, so a computed value cannot be checked (line 63).
20
+ - A role name containing `:` is refused (lines 126-133), because `:` delimits scope
21
+ ids and "a role named like a scope reads as one" (line 130).
22
+ - An empty role name is refused (lines 118-124).
23
+
24
+ The doc comment on `extractScopes` (lines 20-23) says the empty array and the
25
+ absent property are kept distinct on purpose.
26
+
27
+ **What this rules out:** a role whose scopes cannot be checked statically, and a
28
+ role name that looks like a scope id.
@@ -0,0 +1,14 @@
1
+ ---
2
+ type: decision
3
+ title: The inspector records an agent's permissions only to aggregate the services they need, never to check access
4
+ description: addAgent adds the agent's permission names to usedPermissions, and post-process reads that set only to collect each permission's services
5
+ tags: inspector, agents, permissions
6
+ ---
7
+
8
+ # The inspector records an agent's permissions only to aggregate the services they need, never to check access
9
+
10
+ `addAgent` resolves the agent's permissions and adds their names to `state.serviceAggregation.usedPermissions` (`packages/inspector/src/add/add-agent.ts:612`, `:618-620`). `post-process.ts` reads the same set at `packages/inspector/src/utils/post-process.ts:160`, and at `:206-209` it adds each used permission's `services` to the service list.
11
+
12
+ Within the inspector source those are the only readers. `serialize-inspector-state.ts` copies the set in and out (`packages/inspector/src/utils/serialize-inspector-state.ts:294`, `:522`, `:746`). Nothing in the inspector compares a caller against the set.
13
+
14
+ **What this rules out:** reading the inspector's agent permission list as evidence that an agent is guarded, and adding an access decision to the inspector's aggregation step.
@@ -0,0 +1,30 @@
1
+ ---
2
+ type: decision
3
+ title: Vault secrets reach no sink, and PII is kept out of logs, the console and webhooks only
4
+ description: check-classified-sinks rejects revealed secrets in every listed sink and PII only in the logger, console and webhook sinks
5
+ tags: [inspector, pii, secrets, lint]
6
+ ---
7
+
8
+ # Vault secrets reach no sink, and PII is kept out of logs, the console and webhooks only
9
+
10
+ The sink table in `packages/inspector/src/utils/check-classified-sinks.ts` (lines 44-59) lists
11
+ which methods count as sinks and what each one rejects:
12
+
13
+ - `Logger`, every method, rejects `secret` and `pii` (line 49);
14
+ - `WebhookService.send` rejects `secret` and `pii` (line 50);
15
+ - `QueueService.add` rejects `secret` only (line 51);
16
+ - `EmailService.send` rejects `secret` only (line 52);
17
+ - `AuditService` `audit` and `write` reject `secret` only (lines 53-57);
18
+ - `AuditLog.write` rejects `secret` only (line 58).
19
+
20
+ `console` is handled separately and rejects both (line 62).
21
+
22
+ The comment at lines 23-38 gives the reasons. A vault secret is wrong in every sink (lines 23-24).
23
+ PII is wrong only where it leaves the operator's control or lands somewhere never meant to hold
24
+ it. Lines 37-38 say a queue payload "stays on the operator's own infrastructure", and lines 33-34
25
+ say an email is addressed to the data subject.
26
+
27
+ Lines 40-42 say `private` is absent from the table on purpose.
28
+
29
+ **What this rules out:** a revealed secret reaching any listed sink, and PII reaching a logger,
30
+ the console or a webhook. PII is allowed into queues, email and audit by this table.
@@ -0,0 +1,26 @@
1
+ ---
2
+ type: decision
3
+ title: Webhook gateway wrappers are registered sessionless, and gateway auth is enforced inside the handler call
4
+ description: The inspector and the runtime both register a webhook gateway's POST and verify routes as sessionless with auth false, so the route never demands a session and the gateway's own auth flag is applied only when the handler runs
5
+ tags: inspector, gateway, webhook, auth, sessionless
6
+ ---
7
+
8
+ # Webhook gateway wrappers are registered sessionless, and gateway auth is enforced inside the handler call
9
+
10
+ **Build time.** For a webhook gateway, `addGateway` writes two function metas, `gateway__<name>__post` and `gateway__<name>__verify`, and their HTTP routes (`packages/inspector/src/add/add-gateway.ts:105-109`). Each function meta is written with `sessionless: true` (`add-gateway.ts:117-122`). The comment on the wrapper says "no middleware here — the gateway POST wrapper runs it itself" (`add-gateway.ts:111`).
11
+
12
+ The gateway's own `auth` flag is not used there. It is recorded only on the gateway meta, as `auth` on `state.gateways.meta[name]` (`add-gateway.ts:135`).
13
+
14
+ **Run time.** `wireWebhookGateway` in `packages/core/src/wirings/gateway/gateway-runner.ts` mirrors this. The POST and verify handlers are both registered with `auth: false` (`gateway-runner.ts:117`, `:141`), and so are their routes (`gateway-runner.ts:130`, `:154`).
15
+
16
+ The POST wrapper, `createWebhookPostHandler` (`gateway-runner.ts:163`), does three things in order:
17
+
18
+ 1. If the adapter has `verifyWebhook` and it verifies the request, it returns the adapter's response immediately (`gateway-runner.ts:175-179`). The handler is not reached.
19
+ 2. It parses the body; an unparseable body returns `{ ok: true }` (`gateway-runner.ts:182-185`).
20
+ 3. It invokes the handler through `runPikkuFunc('gateway', name, handlerFuncId, { ..., auth: config.auth, ... })` (`gateway-runner.ts:196-204`), with gateway middleware running first (`gateway-runner.ts:207-211`).
21
+
22
+ So the session check for a webhook is applied from `CoreGateway.auth`, inside the runner call at `gateway-runner.ts:201`. It is not applied on the HTTP route, which is `auth: false` and sessionless.
23
+
24
+ The synthetic handler registration that this call goes through, and how it inherits the wired function's metadata, is described in [Gateway handlers run through the function runner gate](../../../core/knowledge/decisions/security/gateway-handlers-run-through-the-function-runner-gate.md).
25
+
26
+ **What this rules out:** adding `auth: true` to the POST or verify route to "harden" a webhook. That would reject every platform call, which carries no user session. It also rules out assuming that the route-level metadata shows whether a gateway needs a session. Check `CoreGateway.auth` and the handler's own declaration instead.
@@ -0,0 +1,14 @@
1
+ ---
2
+ type: overview
3
+ title: Knowledge
4
+ description: Why the inspector reads and refuses declarations the way it does
5
+ ---
6
+
7
+ # Knowledge
8
+
9
+ Why the inspector reads and refuses declarations the way it does — the reasoning that is not visible in the code.
10
+
11
+ <!-- pikku:knowledge-index -->
12
+ - [decisions](decisions/index.md) — a rule that was chosen, and what it rules out
13
+ - [questions](questions/index.md) — something asked and not yet answered
14
+ <!-- /pikku:knowledge-index -->
@@ -0,0 +1,26 @@
1
+ ---
2
+ type: question
3
+ title: Does a non-literal auth on wireRemoteAddon get checked?
4
+ description: A non-literal auth value is recorded exactly like an omitted one, so the consumer's credential and secret checks never run for it
5
+ tags: [inspector, addon, auth]
6
+ ---
7
+
8
+ # Does a non-literal auth on wireRemoteAddon get checked?
9
+
10
+ `addWireRemoteAddon` in `packages/inspector/src/add/add-wire-remote-addon.ts` reads
11
+ `auth` only when it is an object literal (lines 42-46). Otherwise `hasAuth` stays
12
+ `false` (line 31) and `authCredentialId` and `authSecretId` stay undefined.
13
+
14
+ The comment at lines 27-30 says `hasAuth` separates "public" (omitted) from "bound
15
+ via a custom resolve()" (present, with no static id). A non-literal `auth` value
16
+ therefore looks the same as an omitted one. The declaration is written at lines
17
+ 71-78, with `hasAuth` at line 75.
18
+
19
+ Nothing in the inspector reads `hasAuth`. Its only other mentions are the type at
20
+ `packages/inspector/src/types.ts:749` and serialization at
21
+ `packages/inspector/src/utils/serialize-inspector-state.ts:172`. The post-process
22
+ checks run only when an id is set: the credential id at
23
+ `packages/inspector/src/utils/post-process.ts:841` and the secret id at line 847.
24
+
25
+ Question: should a non-literal `auth` be treated as a binding the consumer has to
26
+ satisfy, or as public? The code currently does neither and logs nothing.
@@ -0,0 +1,16 @@
1
+ ---
2
+ type: overview
3
+ title: Questions
4
+ description: Things asked of inspector and not yet answered
5
+ ---
6
+
7
+ # Questions
8
+
9
+ Something asked and not yet answered. The choice stays, the doubt gets recorded.
10
+
11
+ <!-- pikku:knowledge-index -->
12
+ - [Does a non-literal auth on wireRemoteAddon get checked?](does-a-non-literal-remote-addon-auth-get-checked.md) — A non-literal auth value is recorded exactly like an omitted one, so the consumer's credential and secret checks never run for it
13
+ - [Should an incomplete oauth2 block be dropped without a log?](is-an-incomplete-oauth2-block-dropped-without-a-log.md) — An oauth2 block missing any required field is silently left undefined, and an empty scopes array passes the check
14
+ - [Should an app function named like scenario instrumentation be silently skipped?](should-app-functions-named-like-scenario-instrumentation-be-refused.md) — Five instrumentation names are skipped by name in every project, so an app's own function with one of those names disappears without a log
15
+ - [Should the OpenAPI document apply an ApiKey-or-Bearer security default to every operation, including auth false routes?](should-the-openapi-document-default-every-operation-to-a-bearer-or-api-key.md) — The OpenAPI serializer emits one top-level security default for all operations and does not read the auth or session metadata of any route
16
+ <!-- /pikku:knowledge-index -->
@@ -0,0 +1,23 @@
1
+ ---
2
+ type: question
3
+ title: Should an incomplete oauth2 block be dropped without a log?
4
+ description: An oauth2 block missing any required field is silently left undefined, and an empty scopes array passes the check
5
+ tags: [inspector, credentials, oauth]
6
+ ---
7
+
8
+ # Should an incomplete oauth2 block be dropped without a log?
9
+
10
+ In `packages/inspector/src/add/add-credential.ts`, `oauth2` is set only when
11
+ `appCredentialSecretId`, `authorizationUrl`, `tokenUrl` and `scopes` are all truthy
12
+ (lines 144-155). There is no `else` branch, and nothing in that block calls the
13
+ logger. The credential is still recorded, with `oauth2` undefined.
14
+
15
+ `scopes` comes from `getArrayPropertyValue`, which returns `string[] | null`
16
+ (`packages/inspector/src/utils/get-property-value.ts:22`). An array literal returns
17
+ its string elements even when there are none (lines 31-35). An empty array is
18
+ truthy, so `scopes: []` passes the check.
19
+
20
+ Question: should a half-filled `oauth2` block be reported as critical, as the other
21
+ other validation in the same file is (logger calls at lines 49, 57 and 65)? And should `scopes: []` be
22
+ allowed to satisfy the check at all? The code currently does neither, and no
23
+ comment or test in this path says why.
@@ -0,0 +1,28 @@
1
+ ---
2
+ type: question
3
+ title: Should an app function named like scenario instrumentation be silently skipped?
4
+ description: Five instrumentation names are skipped by name in every project, so an app's own function with one of those names disappears without a log
5
+ tags: [inspector, scenarios, functions]
6
+ ---
7
+
8
+ # Should an app function named like scenario instrumentation be silently skipped?
9
+
10
+ `SCENARIO_INSTRUMENTATION_FUNCTIONS` in
11
+ `packages/inspector/src/add/scenario-instrumentation.ts` lists five names (lines
12
+ 22-28). `isScenarioInstrumentationFunction` matches them on the id before any `@`
13
+ (lines 40-42).
14
+
15
+ In `packages/inspector/src/add/add-functions.ts`, a function whose id or name matches
16
+ is returned before it is registered (lines 571-576). No logger call happens there.
17
+ The file's comment says the function is "not application code" and must not be
18
+ registered.
19
+
20
+ The file comment gives the reason for matching by name: it keeps an existing
21
+ project's checked-in `scenarios.gen.ts` from deploying (lines 12-15). Lines 17-20
22
+ say `pikkuScenarioGradeRun` is reserved "so an application cannot claim it". No
23
+ comment says the same about the other four names.
24
+
25
+ Question: should an application's own function named `pikkuScenarioResetStubs`,
26
+ `pikkuScenarioGetStubCalls`, `pikkuScenarioTakeLiveCoverage` or
27
+ `pikkuScenarioResetLiveCoverage` be refused with a critical, as the reserved name
28
+ would be, rather than dropped silently?
@@ -0,0 +1,22 @@
1
+ ---
2
+ type: question
3
+ title: Should the OpenAPI document apply an ApiKey-or-Bearer security default to every operation, including auth false routes?
4
+ description: The OpenAPI serializer emits one top-level security default for all operations and does not read the auth or session metadata of any route
5
+ tags: [inspector, openapi, auth]
6
+ ---
7
+
8
+ # Should the OpenAPI document apply an ApiKey-or-Bearer security default to every operation, including auth false routes?
9
+
10
+ `generateOpenAPISpec` in `packages/inspector/src/utils/serialize-openapi-json.ts` writes two security
11
+ schemes, `ApiKeyAuth` (an `x-api-key` header) and `BearerAuth`, unless the caller supplies its own
12
+ (lines 254-264). It then writes a top-level `security` array with one entry for each scheme
13
+ (lines 266-273), unless the caller supplies one.
14
+
15
+ In OpenAPI a top-level `security` applies to every operation that does not set its own. This file
16
+ does not write per-operation `security`, and a search of the file for `requiresSession` and
17
+ `sessionless` finds nothing. So an operation for a route marked `auth: false` would carry the same
18
+ default as one that requires a session.
19
+
20
+ Question: should `auth: false` routes and sessionless functions be written with an empty
21
+ `security` list, and should the default be applied per operation? The code does not say whether the
22
+ default is meant for the whole API or only as a placeholder.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/inspector",
3
- "version": "0.12.103",
3
+ "version": "0.12.106",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/pikkujs/pikku.git",
@@ -54,10 +54,10 @@
54
54
  "zod": "^4.4.3"
55
55
  },
56
56
  "peerDependencies": {
57
- "@pikku/core": "^0.12.139"
57
+ "@pikku/core": "^0.12.142"
58
58
  },
59
59
  "devDependencies": {
60
- "@pikku/core": "^0.12.139",
60
+ "@pikku/core": "^0.12.142",
61
61
  "@types/node": "^24.13.3"
62
62
  },
63
63
  "engines": {
@@ -1,13 +0,0 @@
1
- import type { AddWiring } from '../types.js';
2
- /**
3
- * Inspector for `definePersonas()` calls.
4
- *
5
- * A persona is entirely declaration — a person, their roles, what they want —
6
- * so there is no code body and nothing to resolve at runtime. What is read here
7
- * is the whole thing, and it is generated straight to `personas.gen.json` for
8
- * the CLI and the console to read without loading the app.
9
- *
10
- * `name` is the one field that cannot be defaulted. A persona without one is
11
- * not a person, and the console has nothing to show.
12
- */
13
- export declare const addPersonas: AddWiring;