@sweny-ai/core 0.1.164 → 0.1.166

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/dist/schema.js CHANGED
@@ -9,6 +9,7 @@
9
9
  import { z } from "zod";
10
10
  import { AUTHOR_ASSOCIATIONS, CONTEXT_MODES, EVALUATOR_KINDS, EVAL_POLICIES, MCP_TRANSPORTS, NODE_ACCESS, NODE_ON_FAIL, REQUIRES_ON_FAIL, SAFE_OUTPUT_APPLIERS, SAFE_OUTPUT_EXPIRES_PATTERN, SAFE_OUTPUT_MAX_CEILING, SAFE_OUTPUT_STATES, SAFE_OUTPUT_TYPES, SKILL_CATEGORIES, SKILL_ID_MAX_LENGTH, SKILL_ID_PATTERN, TOOL_ACCESS, TOOL_CLASSES, WORKFLOW_TYPES, } from "./types.js";
11
11
  import { sourceZ } from "./sources.js";
12
+ import { resolveNodePermissions } from "./node-policy.js";
12
13
  import { workflowInputsZ, WORKFLOW_INPUT_TYPES } from "./inputs.js";
13
14
  import { checkExpression, isWhenExpression, WHEN_EXPR_MAX_LENGTH } from "./when.js";
14
15
  // Studio and other schema-only consumers label edges without importing the root entry.
@@ -326,6 +327,8 @@ export const safeOutputsPolicyZ = z
326
327
  screen: z.boolean().optional(),
327
328
  })
328
329
  .strict();
330
+ /** An environment variable name (`agent_env` entries). */
331
+ export const ENV_VAR_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
329
332
  export const nodeZ = z
330
333
  .object({
331
334
  name: z.string().min(1),
@@ -335,6 +338,7 @@ export const nodeZ = z
335
338
  max_turns: z.number().int().min(1).optional(),
336
339
  budget: budgetZ.optional(),
337
340
  disallowed_tools: z.array(z.string().min(1)).optional(),
341
+ agent_env: z.array(z.string().regex(ENV_VAR_NAME_PATTERN)).min(1).optional(),
338
342
  tools: nodeToolsZ.optional(),
339
343
  fail_soft: z.boolean().optional(),
340
344
  on_fail: z.enum(NODE_ON_FAIL).optional(),
@@ -719,6 +723,17 @@ export function validateWorkflow(workflow, knownSkills) {
719
723
  nodeId,
720
724
  });
721
725
  }
726
+ // A read-only node's agent never holds a credential: a filesystem mount
727
+ // does not stop an API write (security review 2026-09-30).
728
+ if ((node.agent_env?.length ?? 0) > 0 && resolveNodePermissions(node, workflow).access === "read") {
729
+ errors.push({
730
+ code: "AGENT_ENV_READ_ONLY",
731
+ message: `Node "${nodeId}" grants agent_env [${node.agent_env.join(", ")}] but its permissions are read. ` +
732
+ `A read-only node's agent never holds a credential; its skill tools already have them. ` +
733
+ `Remove agent_env, or make the node permissions: write.`,
734
+ nodeId,
735
+ });
736
+ }
722
737
  // Budgets (#449): the workflow's `budget` is the ceiling for every node.
723
738
  const wfBudget = workflow.budget;
724
739
  const nodeBudget = node.budget;
@@ -1286,6 +1301,12 @@ export const workflowJsonSchema = {
1286
1301
  items: { type: "string", minLength: 1 },
1287
1302
  description: "Built-in tool names the agent cannot use at this node, in the harness's own names (Claude Code: ['Bash']). Passed through to the harness: Claude Code removes them from the model context; Codex maps known names to tool classes and reports what it cannot deny as degraded. For portable workflows prefer tool classes in tools.deny.",
1288
1303
  },
1304
+ agent_env: {
1305
+ type: "array",
1306
+ items: { type: "string", pattern: ENV_VAR_NAME_PATTERN.source },
1307
+ minItems: 1,
1308
+ description: "Secrets this node's agent process gets in its env. Skill tools run in sweny, so by default the agent never holds a skill credential (GITHUB_TOKEN, LINEAR_API_KEY, ...). Name one here only when the agent's own shell needs it: the agent can use it outside sweny's opinions (any API call, any push). Not allowed on a permissions: read node; withheld in a staged or dry run.",
1309
+ },
1289
1310
  tools: {
1290
1311
  type: "object",
1291
1312
  description: "Per-node filter over skill-provided tools. 'allow' keeps only the listed skill tools; 'deny' removes the listed skill tools (applied after 'allow'). Filtered tools are never registered for the node's run. Absent field = all skill tools exposed. A 'deny' entry that names a portable tool class (shell, write, edit, net, subagent) also denies that class of built-in agent tools on every harness.",
package/dist/types.d.ts CHANGED
@@ -331,6 +331,15 @@ export interface Node {
331
331
  * `git push`. Names follow the SDK's tool naming (e.g. `Bash`, `Write`).
332
332
  */
333
333
  disallowed_tools?: string[];
334
+ /**
335
+ * Secrets this node's agent process receives in its env. Skill tools run
336
+ * in the sweny process, so by default the agent never holds a skill
337
+ * credential (`GITHUB_TOKEN`, `LINEAR_API_KEY`, ...) on any harness. Name a
338
+ * variable here only when the agent's own shell needs it: the agent can use
339
+ * it outside sweny's opinions (any API call, any push). Refused on a
340
+ * `permissions: read` node; withheld in a staged or dry run.
341
+ */
342
+ agent_env?: string[];
334
343
  /**
335
344
  * Per-node filter over skill-provided tools. Complements
336
345
  * `disallowed_tools` (which covers built-in agent tools only): this field
@@ -816,13 +825,16 @@ export interface Claude {
816
825
  /** Caller-supplied abort signal. Aborting it interrupts the query. */
817
826
  signal?: AbortSignal;
818
827
  /**
819
- * What this node's agent may see (#360): env var names declared by the
820
- * node's skills (added to the scoped subprocess env) and the provider
821
- * hosts its sandboxed commands may reach. Absent = allowlist only.
828
+ * What this node's agent may see (#360): `envVars` are the names the node
829
+ * granted with `agent_env` (the only skill credentials that reach the
830
+ * agent), `withhold` the run's skill credential names (never passed
831
+ * otherwise), and `domains` the provider hosts its sandboxed commands may
832
+ * reach. Absent = allowlist only, known credentials withheld.
822
833
  */
823
834
  agentAccess?: {
824
835
  envVars: string[];
825
836
  domains: string[];
837
+ withhold?: string[];
826
838
  };
827
839
  /**
828
840
  * Dry-run: the node must not change anything. `tools` is already filtered
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sweny-ai/core",
3
- "version": "0.1.164",
3
+ "version": "0.1.166",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "sweny": "./dist/cli/main.js"