@pikku/core 0.12.122 → 0.12.123
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 +39 -0
- package/dist/dev/module-runner.js +9 -0
- package/dist/function/function-meta.types.d.ts +5 -4
- package/dist/types/state.types.d.ts +6 -1
- package/dist/wirings/agent/agent-rpc.d.ts +2 -5
- package/dist/wirings/agent/agent-rpc.js +10 -0
- package/dist/wirings/agent/agent.types.d.ts +13 -0
- package/dist/wirings/agent/index.d.ts +7 -0
- package/dist/wirings/agent/index.js +6 -0
- package/dist/wirings/rpc/rpc-runner.d.ts +6 -3
- package/dist/wirings/rpc/rpc-runner.js +10 -4
- package/knowledge/decisions/security/a-public-function-says-so-with-auth-false.md +34 -0
- package/knowledge/decisions/security/an-exposed-ungated-function-is-a-codegen-warning.md +4 -1
- package/knowledge/decisions/security/index.md +1 -0
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,42 @@
|
|
|
1
|
+
## 0.12.123
|
|
2
|
+
|
|
3
|
+
### Patch Changes
|
|
4
|
+
|
|
5
|
+
- 50b59a3: Keep the agent runtime out of deployment units that hold no agent.
|
|
6
|
+
|
|
7
|
+
`ContextAwareRPCService.agent` imported `agent-rpc.ts` directly, and every unit
|
|
8
|
+
reaches that class through the function runner, so the agent runner, stream,
|
|
9
|
+
memory and AGUI modules were pinned into every bundle — 51.8 KB raw / 15.7 KB
|
|
10
|
+
gzip a unit. The facade is now resolved through state, registered by
|
|
11
|
+
`@pikku/core/agent` on import, so only a unit that actually holds an agent
|
|
12
|
+
bundles it. A unit that reaches `rpc.agent` without importing the agent entry
|
|
13
|
+
point throws rather than silently pulling the runtime back in.
|
|
14
|
+
|
|
15
|
+
- 2b946e9: fix(dev): keep `import.meta` pointing at the real file in the hot-reload runner
|
|
16
|
+
|
|
17
|
+
The dev module runner transforms each user file to `cjs` before compiling it,
|
|
18
|
+
and esbuild's `cjs` output rewrites `import.meta` to an empty object. Any module
|
|
19
|
+
that resolves its own neighbours through `createRequire(import.meta.url)` —
|
|
20
|
+
sharp, onnxruntime-node, essentially every package with a native binding —
|
|
21
|
+
therefore received `undefined` and failed with `Cannot find module
|
|
22
|
+
'@img/sharp-linux-x64/sharp.node' from ''`. sharp's own loader filters on
|
|
23
|
+
`MODULE_NOT_FOUND` and reports its generic "could not load the sharp module"
|
|
24
|
+
instead, so the empty referrer never appears in the error the developer sees.
|
|
25
|
+
|
|
26
|
+
`import.meta.url`, `import.meta.filename` and `import.meta.dirname` are now
|
|
27
|
+
defined to the file being run, so resolution behaves as it does under Node's
|
|
28
|
+
ESM loader.
|
|
29
|
+
|
|
30
|
+
- bc488cf: fix(inspector): an explicit `auth: false` declares an exposed sessionless function public, so PKU574 no longer warns about it
|
|
31
|
+
|
|
32
|
+
A genuinely public endpoint — a published programme, a health check — had no
|
|
33
|
+
honest way to quiet PKU574: the only options were an always-true permission or
|
|
34
|
+
`permissionsInBody: true`, both of which claim a gate that does not exist. The
|
|
35
|
+
inspector now records `auth` on function meta exactly as written instead of
|
|
36
|
+
dropping `false`, and the check treats an explicit `auth: false` as the author
|
|
37
|
+
declaring the function public on purpose. A sessionless function that leaves
|
|
38
|
+
`auth` out still warns.
|
|
39
|
+
|
|
1
40
|
## 0.12.122
|
|
2
41
|
|
|
3
42
|
### Patch Changes
|
|
@@ -68,6 +68,15 @@ const evaluateModule = (absPath, cache) => {
|
|
|
68
68
|
loader: loaderFor(absPath),
|
|
69
69
|
format: 'cjs',
|
|
70
70
|
sourcefile: absPath,
|
|
71
|
+
// `cjs` output rewrites `import.meta` to an empty object, so a module
|
|
72
|
+
// that resolves its own neighbours through `createRequire(import.meta.url)`
|
|
73
|
+
// -- sharp, onnxruntime-node, any package with a native binding -- gets
|
|
74
|
+
// `undefined` and fails with "Cannot find module ... from ''".
|
|
75
|
+
define: {
|
|
76
|
+
'import.meta.url': JSON.stringify(pathToFileURL(absPath).href),
|
|
77
|
+
'import.meta.filename': JSON.stringify(absPath),
|
|
78
|
+
'import.meta.dirname': JSON.stringify(dirname(absPath)),
|
|
79
|
+
},
|
|
71
80
|
});
|
|
72
81
|
const fn = compileFunction(code, ['require', 'exports', 'module', '__filename', '__dirname'], { filename: absPath });
|
|
73
82
|
const moduleObj = { exports: {} };
|
|
@@ -29,10 +29,11 @@ export type FunctionRuntimeMeta = {
|
|
|
29
29
|
featureFlag?: string;
|
|
30
30
|
expose?: boolean;
|
|
31
31
|
/**
|
|
32
|
-
* A sessionless function's own `auth
|
|
33
|
-
* baseline — a `pikkuFunc` always requires a session — and this carries
|
|
34
|
-
*
|
|
35
|
-
*
|
|
32
|
+
* A sessionless function's own `auth`, as written. `sessionless` carries the
|
|
33
|
+
* baseline — a `pikkuFunc` always requires a session — and this carries what
|
|
34
|
+
* a `pikkuSessionlessFunc` declares about itself: `true` requires a session,
|
|
35
|
+
* `false` says it is public on purpose, absent says nothing. All three are
|
|
36
|
+
* needed to know whether an exposed function is gated without running it.
|
|
36
37
|
*/
|
|
37
38
|
auth?: boolean;
|
|
38
39
|
/**
|
|
@@ -6,7 +6,7 @@ import type { CoreChannel, ChannelsMeta } from '../wirings/channel/channel.types
|
|
|
6
6
|
import type { CLIMeta, CLIProgramState } from '../wirings/cli/cli.types.js';
|
|
7
7
|
import type { HTTPMethod, CoreHTTPFunctionWiring, HTTPWiringsMeta } from '../wirings/http/http.types.js';
|
|
8
8
|
import type { CoreMCPResource, MCPResourceMeta, MCPToolMeta, CoreMCPPrompt, MCPPromptMeta } from '../wirings/mcp/mcp.types.js';
|
|
9
|
-
import type { CoreAgent, AgentsMeta } from '../wirings/agent/agent.types.js';
|
|
9
|
+
import type { CoreAgent, AgentsMeta, AgentRPCFactory } from '../wirings/agent/agent.types.js';
|
|
10
10
|
import type { PikkuAgentScorer, ScorerMeta } from '../wirings/agent-scorer/agent-scorer.types.js';
|
|
11
11
|
import type { CoreGateway, GatewaysMeta } from '../wirings/gateway/gateway.types.js';
|
|
12
12
|
import type { CoreQueueWorker, QueueWorkersMeta } from '../wirings/queue/queue.types.js';
|
|
@@ -116,6 +116,11 @@ export interface PikkuPackageState {
|
|
|
116
116
|
scorersMeta: ScorerMeta;
|
|
117
117
|
/** Alias -> `provider/model`, from the `models` table in pikku.config.json. */
|
|
118
118
|
modelAliases: Record<string, string>;
|
|
119
|
+
/**
|
|
120
|
+
* Set by `@pikku/core/agent` on import. Absent in a deployment unit that
|
|
121
|
+
* holds no agent, which is what keeps the agent runtime out of its bundle.
|
|
122
|
+
*/
|
|
123
|
+
rpcFactory?: AgentRPCFactory;
|
|
119
124
|
};
|
|
120
125
|
gateway: {
|
|
121
126
|
gateways: Map<string, CoreGateway>;
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import type { PikkuRawWire } from '../../types/core.types.js';
|
|
2
|
-
import type { SessionService } from '../../services/user-session-service.js';
|
|
3
|
-
import type { CoreUserSession } from '../../types/core.types.js';
|
|
4
2
|
import type { PikkuRPC } from '../rpc/rpc-types.js';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
};
|
|
3
|
+
import type { AgentRPCOptions } from './agent.types.js';
|
|
4
|
+
export type { AgentRPCOptions };
|
|
8
5
|
/**
|
|
9
6
|
* `wire.rpc.agent`, implemented.
|
|
10
7
|
*
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { pikkuState } from '../../pikku-state.js';
|
|
1
2
|
import { runAgent, resumeAgentSync } from './agent-runner.js';
|
|
2
3
|
import { streamAgent, resumeAgent, interruptAgent } from './agent-stream.js';
|
|
3
4
|
import { wrapChannelWithAGUI } from './agent-agui.js';
|
|
@@ -51,3 +52,12 @@ export const createAgentRPC = (wire, options) => {
|
|
|
51
52
|
approve: async (runId, approvals, expectedAgentName) => asRunResult(await resumeAgentSync(runId, approvals, { sessionService: options.sessionService }, expectedAgentName)),
|
|
52
53
|
};
|
|
53
54
|
};
|
|
55
|
+
/**
|
|
56
|
+
* The RPC primitive offers `rpc.agent` but must not import this file — a static
|
|
57
|
+
* reference from `rpc-runner` pins the whole agent runtime into every
|
|
58
|
+
* deployment unit, including the ones that hold no agent. Registering the
|
|
59
|
+
* factory here inverts that: only a unit that imports `@pikku/core/agent` pays
|
|
60
|
+
* for it, which is the same seam the inspector already uses to decide whether
|
|
61
|
+
* a unit requires the agent services.
|
|
62
|
+
*/
|
|
63
|
+
pikkuState(null, 'agent', 'rpcFactory', createAgentRPC);
|
|
@@ -6,6 +6,19 @@ import type { AIProviderOptions } from '../../services/agent-runner-service.js';
|
|
|
6
6
|
import type { PikkuChannel } from '../channel/channel.types.js';
|
|
7
7
|
import type { CorePikkuChannelMiddleware } from '../channel/channel.types.js';
|
|
8
8
|
import type { ApprovalPolicy } from '../channel/channel-rpc.js';
|
|
9
|
+
import type { PikkuRawWire } from '../../types/core.types.js';
|
|
10
|
+
import type { PikkuRPC } from '../rpc/rpc-types.js';
|
|
11
|
+
import type { SessionService } from '../../services/user-session-service.js';
|
|
12
|
+
import type { CoreUserSession } from '../../types/core.types.js';
|
|
13
|
+
export type AgentRPCOptions = {
|
|
14
|
+
sessionService?: SessionService<CoreUserSession>;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Builds `wire.rpc.agent`. Registered by `agent-rpc.ts` when a unit imports
|
|
18
|
+
* `@pikku/core/agent`, so the RPC primitive can offer the agent surface
|
|
19
|
+
* without naming the agent runtime.
|
|
20
|
+
*/
|
|
21
|
+
export type AgentRPCFactory = (wire: PikkuRawWire, options: AgentRPCOptions) => PikkuRPC['agent'];
|
|
9
22
|
export interface AgentThread {
|
|
10
23
|
id: string;
|
|
11
24
|
resourceId: string;
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registers `wire.rpc.agent`. Imported for the side effect alone so the RPC
|
|
3
|
+
* primitive can offer the agent surface without naming the agent runtime, which
|
|
4
|
+
* would pin it into every deployment unit.
|
|
5
|
+
*/
|
|
6
|
+
import './agent-rpc.js';
|
|
1
7
|
export { agent, agentStream, agentResume, agentApprove, agentInterrupt, } from './agent-helpers.js';
|
|
8
|
+
export type { AgentRPCOptions, AgentRPCFactory } from './agent.types.js';
|
|
2
9
|
export { wrapChannelWithAGUI } from './agent-agui.js';
|
|
3
10
|
export { runAgent, resumeAgentSync } from './agent-runner.js';
|
|
4
11
|
export { resolveModelAlias } from './agent-model-config.js';
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registers `wire.rpc.agent`. Imported for the side effect alone so the RPC
|
|
3
|
+
* primitive can offer the agent surface without naming the agent runtime, which
|
|
4
|
+
* would pin it into every deployment unit.
|
|
5
|
+
*/
|
|
6
|
+
import './agent-rpc.js';
|
|
1
7
|
export { agent, agentStream, agentResume, agentApprove, agentInterrupt, } from './agent-helpers.js';
|
|
2
8
|
export { wrapChannelWithAGUI } from './agent-agui.js';
|
|
3
9
|
export { runAgent, resumeAgentSync } from './agent-runner.js';
|
|
@@ -52,9 +52,12 @@ export declare class ContextAwareRPCService {
|
|
|
52
52
|
/**
|
|
53
53
|
* The agent facade, built on access.
|
|
54
54
|
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
55
|
+
* Resolved through state rather than imported: naming `agent-rpc.ts` here
|
|
56
|
+
* pinned the agent runtime — runner, stream, memory, AGUI — into every
|
|
57
|
+
* deployment unit, because every unit reaches this file through
|
|
58
|
+
* `function-runner`. `@pikku/core/agent` registers the factory on import, so
|
|
59
|
+
* a unit that holds an agent has one and a unit that does not never bundles
|
|
60
|
+
* the tree behind it.
|
|
58
61
|
*/
|
|
59
62
|
get agent(): PikkuRPC['agent'];
|
|
60
63
|
remote<In = any, Out = any>(funcName: string, data: In): Promise<Out>;
|
|
@@ -5,7 +5,6 @@ import { pikkuState } from '../../pikku-state.js';
|
|
|
5
5
|
import { PikkuError, addError } from '../../errors/error-handler.js';
|
|
6
6
|
import { parseVersionedId } from '../../version.js';
|
|
7
7
|
import { resolveRemoteAddonToken } from '../addon/remote-addon-auth.js';
|
|
8
|
-
import { createAgentRPC } from '../agent/agent-rpc.js';
|
|
9
8
|
/**
|
|
10
9
|
* The session for a wire: read through `getSession` when a runner attached one,
|
|
11
10
|
* otherwise whatever was placed on the wire directly.
|
|
@@ -330,11 +329,18 @@ export class ContextAwareRPCService {
|
|
|
330
329
|
/**
|
|
331
330
|
* The agent facade, built on access.
|
|
332
331
|
*
|
|
333
|
-
*
|
|
334
|
-
*
|
|
335
|
-
*
|
|
332
|
+
* Resolved through state rather than imported: naming `agent-rpc.ts` here
|
|
333
|
+
* pinned the agent runtime — runner, stream, memory, AGUI — into every
|
|
334
|
+
* deployment unit, because every unit reaches this file through
|
|
335
|
+
* `function-runner`. `@pikku/core/agent` registers the factory on import, so
|
|
336
|
+
* a unit that holds an agent has one and a unit that does not never bundles
|
|
337
|
+
* the tree behind it.
|
|
336
338
|
*/
|
|
337
339
|
get agent() {
|
|
340
|
+
const createAgentRPC = pikkuState(null, 'agent', 'rpcFactory');
|
|
341
|
+
if (!createAgentRPC) {
|
|
342
|
+
throw new Error('Agent runtime not available: import @pikku/core/agent in this deployment unit to use rpc.agent');
|
|
343
|
+
}
|
|
338
344
|
return createAgentRPC(this.wire, this.options);
|
|
339
345
|
}
|
|
340
346
|
async remote(funcName, data) {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: decision
|
|
3
|
+
title: A public function says so with auth: false
|
|
4
|
+
description: An exposed sessionless function that writes auth: false is public on purpose and PKU574 stays quiet; one that leaves auth out still warns
|
|
5
|
+
tags: authorization, inspector, rpc
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# A public function says so with auth: false
|
|
9
|
+
|
|
10
|
+
PKU574 warns about an exposed sessionless function with no gate. Some of those
|
|
11
|
+
are exactly what the author meant: a published programme, a price list, a health
|
|
12
|
+
check. Before this, the only way to quiet the warning on one was a gate that
|
|
13
|
+
gated nothing — an always-true permission, or `permissionsInBody: true`, which
|
|
14
|
+
claims a check the body does not make. Both put a false statement into meta to
|
|
15
|
+
silence a true one, and the alternative, living with the warning, teaches
|
|
16
|
+
everyone to stop reading it.
|
|
17
|
+
|
|
18
|
+
`auth: false` is already the field that says "no session required". Writing it
|
|
19
|
+
on a `pikkuSessionlessFunc` is redundant at runtime — sessionless already means
|
|
20
|
+
that — which is what makes it useful as a declaration: nobody writes it by
|
|
21
|
+
accident, so its presence is the author deciding the function is public. The
|
|
22
|
+
inspector now records `auth` as written (`true`, `false` or absent) instead of
|
|
23
|
+
folding `false` into absent, and the check treats `false` as a gate the author
|
|
24
|
+
declared, the same way it treats `permissionsInBody`.
|
|
25
|
+
|
|
26
|
+
The case the warning exists for is unchanged. A sessionless function that picked
|
|
27
|
+
up `expose: true` later, with nobody thinking about who may call it, has no
|
|
28
|
+
`auth` at all — and still warns.
|
|
29
|
+
|
|
30
|
+
**What this rules out:** a separate `public: true` flag, which would be a second
|
|
31
|
+
field meaning what `auth: false` already means; reading `readonly` or the
|
|
32
|
+
function's name as evidence it is safe to expose; and silencing PKU574 for every
|
|
33
|
+
sessionless function, which would drop the accidental-exposure case with the
|
|
34
|
+
intentional one.
|
|
@@ -42,7 +42,10 @@ case it would catch.
|
|
|
42
42
|
Severity is `warn`, not `critical`. `expose: true` on an ungated sessionless
|
|
43
43
|
function is legitimate for a genuinely public endpoint — a health check, a
|
|
44
44
|
sign-up — so this cannot block a build without breaking correct programs. It
|
|
45
|
-
fails a build only under `--fail-on-warn`.
|
|
45
|
+
fails a build only under `--fail-on-warn`. And the public endpoint is not left
|
|
46
|
+
warning forever: an explicit `auth: false` on a sessionless function declares it
|
|
47
|
+
public on purpose and silences the check (see
|
|
48
|
+
[public is declared with auth: false](./a-public-function-says-so-with-auth-false.md)).
|
|
46
49
|
|
|
47
50
|
**What this rules out:** checking at boot, where the report arrives after
|
|
48
51
|
deployment; checking in `pikku validate` from `.pikku` JSON, which does not
|
|
@@ -58,6 +58,7 @@ A rule about who may do what, and which way it fails when it is unsure.
|
|
|
58
58
|
- [A scaffolded surface is authenticated unless the config opts out in writing](scaffold-features-are-authenticated-unless-opted-out.md) — SUPERSEDED; retains why the legacy 'auth' | 'no-auth' strings are refused rather than coerced
|
|
59
59
|
- [Scenario-step functions are never externally invocable over RPC](scenario-step-functions-are-never-externally-invocable.md) — rpcExposed requires expose and rejects scenarioStep, so test steps stay reachable only from inside a scenario run
|
|
60
60
|
- [Scope resolution happens at the session boundary and scope sync never deletes](scope-resolution-happens-at-the-session-boundary-and-sync-never-deletes.md) — ScopeService is called when a session is built, never by the function runner, and syncScopes only ever adds — revoking is an explicit operation
|
|
61
|
+
- [A public function says so with auth: false](a-public-function-says-so-with-auth-false.md) — An exposed sessionless function that writes auth: false is public on purpose and PKU574 stays quiet; one that leaves auth out still warns
|
|
61
62
|
- [Self-authentication is declared, not detected](self-authentication-is-declared-not-detected.md) — A function that authorizes callers in its own body says so with selfAuthenticated; codegen never tries to infer it
|
|
62
63
|
- [Signed content URLs bind the request path and verify fail-closed](signed-content-urls-bind-the-request-path.md) — A signature that only carries timestamps authorizes every asset, and a verifier with no key must refuse rather than allow
|
|
63
64
|
- [Webhook bodies are signed before they are enqueued](webhook-bodies-are-signed-before-they-are-enqueued.md) — QueueWebhookService computes the HMAC at enqueue time so the signing key never travels in the queue payload
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/core",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.123",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/pikkujs/pikku.git",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"sideEffects": [
|
|
25
25
|
"./dist/errors/errors.js",
|
|
26
26
|
"./dist/wirings/addon/remote-addon-auth.js",
|
|
27
|
+
"./dist/wirings/agent/agent-rpc.js",
|
|
27
28
|
"./dist/wirings/rpc/rpc-runner.js",
|
|
28
29
|
"./dist/wirings/workflow/pikku-scenario-service.js",
|
|
29
30
|
"./dist/wirings/workflow/workflow-approval-policy.js",
|