@sweny-ai/core 0.1.146 → 0.1.147

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.
@@ -175,7 +175,8 @@ export declare class ClaudeCodeHarness implements Claude, AgentHarness {
175
175
  * is a read-only run; `policy.deny` classes become native `disallowedTools`
176
176
  * names, merged with `nativeDeny` and the legacy `disallowedTools`; and
177
177
  * `policy.strict` makes MCP exclusive (`strictMcpConfig`) even for a
178
- * write-capable node.
178
+ * write-capable node. Per-request sandbox mode overrides the client default,
179
+ * and policy egress supplies the node hosts without dropping scoped env access.
179
180
  */
180
181
  run(req: HarnessRunRequest): Promise<HarnessRunResult>;
181
182
  private runQuery;
@@ -272,7 +272,8 @@ export class ClaudeCodeHarness {
272
272
  * is a read-only run; `policy.deny` classes become native `disallowedTools`
273
273
  * names, merged with `nativeDeny` and the legacy `disallowedTools`; and
274
274
  * `policy.strict` makes MCP exclusive (`strictMcpConfig`) even for a
275
- * write-capable node.
275
+ * write-capable node. Per-request sandbox mode overrides the client default,
276
+ * and policy egress supplies the node hosts without dropping scoped env access.
276
277
  */
277
278
  async run(req) {
278
279
  const policy = req.policy ?? {
@@ -301,6 +302,8 @@ export class ClaudeCodeHarness {
301
302
  readOnly,
302
303
  disallowedTools: disallowedTools.length > 0 ? disallowedTools : undefined,
303
304
  strictMcp: readOnly || policy.strict,
305
+ sandboxMode: policy.sandbox,
306
+ agentAccess: { envVars: req.agentAccess?.envVars ?? [], domains: policy.egress },
304
307
  });
305
308
  return { ...result, harness: this.info(), degraded: gate.degraded };
306
309
  }
@@ -318,7 +321,7 @@ export class ClaudeCodeHarness {
318
321
  // `strict` fails the node here instead.
319
322
  const sandbox = resolveAgentSandbox({
320
323
  env: process.env,
321
- mode: this.sandboxMode,
324
+ mode: opts.sandboxMode ?? this.sandboxMode,
322
325
  allowedDomains: this.sandboxAllowedDomains,
323
326
  nodeDomains: agentAccess?.domains,
324
327
  probe: this.sandboxProbe,
@@ -326,7 +329,7 @@ export class ClaudeCodeHarness {
326
329
  });
327
330
  if (sandbox.error) {
328
331
  this.logger.error(sandbox.error);
329
- return { status: "failed", data: { error: sandbox.error }, toolCalls: [] };
332
+ return { status: "failed", data: { error: sandbox.error, refused: true }, toolCalls: [] };
330
333
  }
331
334
  if (sandbox.warning && !this.sandboxWarned) {
332
335
  this.sandboxWarned = true;
@@ -47,7 +47,7 @@ export declare function safeOutputsInstruction(declarations: SafeOutputDeclarati
47
47
  export interface ActorInfo {
48
48
  /** GitHub login of whoever triggered the run. */
49
49
  login?: string;
50
- /** Author association of the event's comment, review, issue or PR author. */
50
+ /** Association verified for this login, or explicitly supplied by the trusted caller. */
51
51
  association?: string;
52
52
  }
53
53
  /** Who triggered this run: an explicit override, else GitHub Actions' env and event payload. */
@@ -129,13 +129,16 @@ export function safeOutputsInstruction(declarations) {
129
129
  "Requests outside these types or limits are refused. Do not claim a write happened; say what you requested.",
130
130
  ].join("\n");
131
131
  }
132
- function eventAssociation(path) {
133
- if (!path)
132
+ function eventAssociation(path, login) {
133
+ if (!path || !login)
134
134
  return undefined;
135
135
  try {
136
136
  const ev = JSON.parse(readFileSync(path, "utf-8"));
137
137
  for (const key of ["comment", "review", "issue", "pull_request"]) {
138
- const a = ev?.[key]?.author_association;
138
+ const author = ev?.[key];
139
+ if (typeof author?.user?.login !== "string" || author.user.login.toLowerCase() !== login.toLowerCase())
140
+ continue;
141
+ const a = author.author_association;
139
142
  if (typeof a === "string" && a.length > 0)
140
143
  return a;
141
144
  }
@@ -148,7 +151,7 @@ function eventAssociation(path) {
148
151
  /** Who triggered this run: an explicit override, else GitHub Actions' env and event payload. */
149
152
  export function resolveActor(env, override) {
150
153
  const login = override?.login ?? env.GITHUB_ACTOR ?? undefined;
151
- const association = override?.association ?? eventAssociation(env.GITHUB_EVENT_PATH);
154
+ const association = override?.association ?? eventAssociation(env.GITHUB_EVENT_PATH, login);
152
155
  return { ...(login ? { login } : {}), ...(association ? { association } : {}) };
153
156
  }
154
157
  /** True when no trust list is declared, or the actor matches one. Unknown actor = untrusted. */
@@ -356,11 +359,37 @@ export async function applySafeOutputs(o) {
356
359
  refuse("title too long", at);
357
360
  continue;
358
361
  }
362
+ // A Linear comment's issue ID does not encode or enforce its declared team.
363
+ // Resolve it through the configured read tool and carry its immutable ID to
364
+ // the write. Missing lookup support or unverifiable membership fails closed.
365
+ let number = intent.number;
366
+ if (via === "linear" && type === "comment" && target) {
367
+ const lookup = o.skills.get(via)?.tools.find((t) => t.name === "linear_get_issue" && t.access === "read");
368
+ let issue;
369
+ try {
370
+ const result = await lookup?.handler({ id: number }, { config: o.config, logger: o.logger });
371
+ if (result && typeof result === "object") {
372
+ issue = result.issue;
373
+ }
374
+ }
375
+ catch {
376
+ // A lookup error cannot authorize a write.
377
+ }
378
+ if (typeof issue?.id !== "string" || !issue.id || typeof issue.team?.id !== "string") {
379
+ refuse("cannot verify Linear issue team", at);
380
+ continue;
381
+ }
382
+ if (issue.team.id.toLowerCase() !== target.toLowerCase()) {
383
+ refuse("issue outside the declared team", at);
384
+ continue;
385
+ }
386
+ number = issue.id;
387
+ }
359
388
  const write = {
360
389
  type,
361
390
  via,
362
391
  ...(target ? { target } : {}),
363
- ...(intent.number !== undefined ? { number: intent.number } : {}),
392
+ ...(number !== undefined ? { number } : {}),
364
393
  ...(title ? { title } : {}),
365
394
  ...(type !== "label" ? { body } : {}),
366
395
  ...(labels ? { labels } : {}),
@@ -106,7 +106,7 @@ export const linear = {
106
106
  priority priorityLabel
107
107
  assignee { name email }
108
108
  labels { nodes { name } }
109
- team { key name }
109
+ team { id key name }
110
110
  createdAt updatedAt
111
111
  }
112
112
  }`, { id: input.id }, ctx),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sweny-ai/core",
3
- "version": "0.1.146",
3
+ "version": "0.1.147",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "sweny": "./dist/cli/main.js"