@synovitec/trellis 0.6.0-rc.2 → 0.6.0-rc.3

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
@@ -4,6 +4,19 @@ All notable changes to this package. The format follows Keep a Changelog; versio
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.6.0-rc.3] - 2026-10-31
8
+
9
+ ### Fixed
10
+
11
+ - **The commit checkpoint is installed without gitleaks.** `init --agents` wrote the git hooks only
12
+ where gitleaks was on, so a machine without it got no `trellis verify` on commit and no
13
+ attestation at all, and said nothing. The hooks are now always written, and init says the secret
14
+ scan is off and how to add it.
15
+ - **A secret scan that could not run warns on this machine where the team did not switch gitleaks
16
+ on**, rather than refusing every commit; where `"gitleaks": true` is set, or the team set
17
+ `secrets-scanned` itself, it still blocks. CI, which brings its own pinned gitleaks, blocks as
18
+ before, and a leak found is never softened.
19
+
7
20
  ## [0.6.0-rc.2] - 2026-10-30
8
21
 
9
22
  The tenth review, on the code the ninth round added, found the secret scan's own reading of team
package/SECURITY.md CHANGED
@@ -34,7 +34,8 @@ is a seatbelt for an agent that cuts corners, not a sandbox for one that attacks
34
34
  does, after quotes and escapes;
35
35
  - every check fails closed: a log that does not verify, a scanner that could not run, a registry
36
36
  that did not answer, a broken config, a hook whose Trellis is gone (it exits 2) is a finding,
37
- never a pass;
37
+ never a pass. One is a warning rather than a block: a secret scan that could not run on a
38
+ developer's machine where the team did not switch gitleaks on; CI, with its own gitleaks, blocks;
38
39
  - the attestation of each commit is signed with the developer's ed25519 key.
39
40
 
40
41
  An agent determined to forge evidence with the user's own permissions can still do it: read the key
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synovitec/trellis",
3
- "version": "0.6.0-rc.2",
3
+ "version": "0.6.0-rc.3",
4
4
  "description": "Proof that a change got the checks your repository asks for, whoever made it: AI coding agents (Claude Code, Cursor, Codex, Copilot, any MCP agent) and people, held at the commit and verified again in CI with signed in-toto attestations.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -13,7 +13,7 @@ import { buildStatement, existingPublicKeyPem, keyIdOf, readNotes, signEnvelope,
13
13
  import { loadConfig, validateConfig } from "../core/config.mjs";
14
14
  import { scanCommit } from "../verify/secrets.mjs";
15
15
  import { checkDependencies, dependencyChanges } from "../verify/deps.mjs";
16
- import { evaluate } from "../verify/rules.mjs";
16
+ import { evaluate, localModes } from "../verify/rules.mjs";
17
17
  import { testCommands, testScriptChanges } from "../verify/test-commands.mjs";
18
18
 
19
19
  /**
@@ -76,13 +76,14 @@ async function judgeCommit(ctx, commit, tree, events, env, opts) {
76
76
  if (errors.length) return { actor, agent, preset, findings: [{ rule: "config-invalid", mode: "block", message: errors.join("; ") }], verdict: "fail" };
77
77
  const changes = commitDiff(ctx.root, commit);
78
78
  const deps = dependencyChanges(ctx.root, changes);
79
+ const secrets = (opts.scanCommit ?? scanCommit)(ctx.root, commit);
79
80
  const findings = evaluate({
80
81
  changes,
81
82
  events,
82
83
  actor,
83
84
  preset,
84
- modes: config?.verify?.rules ?? {},
85
- secrets: (opts.scanCommit ?? scanCommit)(ctx.root, commit),
85
+ modes: localModes(config, secrets),
86
+ secrets,
86
87
  deps: deps.length ? await checkDependencies(deps, { http: opts.http }) : { status: "clean", findings: [], unpinned: [], unchecked: [] },
87
88
  depNames: deps.map((d) => d.name),
88
89
  testCommands: testCommands((p) => ctx.read(p)),
@@ -90,7 +90,9 @@ function planAgentHooks(ctx, enabled, writes, notes, agentList) {
90
90
  ].filter(Boolean);
91
91
  writes.push({ path: SETTINGS, text: settings.text, why: what.join("; ") });
92
92
  }
93
- if (!enabled.some((e) => e.tool.id === "gitleaks")) return;
93
+ // The commit checkpoint holds with or without gitleaks: without it, commits are not scanned for
94
+ // secrets here (CI scans them), and the note says how to add it.
95
+ if (!enabled.some((e) => e.tool.id === "gitleaks")) notes.push('gitleaks is off, so commits are not scanned for secrets on this machine (CI scans them): install it from github.com/gitleaks/gitleaks, then set "gitleaks": true in trellis.config.json');
94
96
  const git = planGitHooks(ctx);
95
97
  for (const w of git.writes) writes.push({ ...w, executable: true });
96
98
  notes.push(...git.notes);
@@ -19,7 +19,7 @@ import { loadConfig, validateConfig } from "../core/config.mjs";
19
19
  import { parseJson } from "../core/context.mjs";
20
20
  import { scanChanges, scanCommit as scanCommitSecrets, scanStaged } from "../verify/secrets.mjs";
21
21
  import { checkDependencies, dependencyChanges } from "../verify/deps.mjs";
22
- import { evaluate } from "../verify/rules.mjs";
22
+ import { evaluate, localModes } from "../verify/rules.mjs";
23
23
  import { isTestCommand, RUNNER_SETTINGS, testCommands, testEnv, testScriptChanges } from "../verify/test-commands.mjs";
24
24
  import { matchesAny } from "../agents/glob.mjs";
25
25
  import { tmpdir } from "node:os";
@@ -88,13 +88,14 @@ export async function verify(ctx, out, opts = {}) {
88
88
  const deps = dependencyChanges(ctx.root, changes);
89
89
  const agent = agentAtWork(window.events, env);
90
90
  const preset = config?.verify?.preset ?? "recommended";
91
+ const secrets = (opts.scanSecrets ?? scanStaged)(ctx.root);
91
92
  const findings = evaluate({
92
93
  changes,
93
94
  events: window.events,
94
95
  actor: agent ? "agent" : "person",
95
96
  preset,
96
- modes: config?.verify?.rules ?? {},
97
- secrets: (opts.scanSecrets ?? scanStaged)(ctx.root),
97
+ modes: localModes(config, secrets),
98
+ secrets,
98
99
  deps: deps.length ? await checkDependencies(deps, { http: opts.http }) : { status: "clean", findings: [], unpinned: [], unchecked: [] },
99
100
  depNames: deps.map((d) => d.name),
100
101
  testCommands: testCommands((p) => ctx.read(p)),
@@ -89,6 +89,20 @@ export const TEST_PATHS = Object.freeze([...TEST_FILES, ...GATES]);
89
89
  /** @param {string[]} paths */
90
90
  const list = (paths) => `${paths.slice(0, 6).join(", ")}${paths.length > 6 ? `, and ${paths.length - 6} more` : ""}`;
91
91
 
92
+ /**
93
+ * The rule modes a commit on this machine is judged under: the team's, except that a secret scan
94
+ * that could not run warns where the team did not switch gitleaks on (and did not set the rule).
95
+ * CI, which brings its own gitleaks, still blocks.
96
+ * @param {any} config trellis.config.json, or null
97
+ * @param {import("./secrets.mjs").SecretScan} secrets
98
+ * @returns {Record<string, Mode>}
99
+ */
100
+ export function localModes(config, secrets) {
101
+ const modes = { ...(config?.verify?.rules ?? {}) };
102
+ if (secrets.status === "unavailable" && !config?.tools?.gitleaks && modes["secrets-scanned"] === undefined) modes["secrets-scanned"] = "warn";
103
+ return modes;
104
+ }
105
+
92
106
  /**
93
107
  * @param {object} input
94
108
  * @param {import("../core/diff.mjs").Change[]} input.changes