@haus-tech/haus-workflow 5.6.0 → 5.6.1

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
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [5.6.1](https://github.com/WeAreHausTech/haus-workflow/compare/v5.6.0...v5.6.1) (2026-09-16)
4
+
5
+ ### Bug Fixes
6
+
7
+ - **security:** allow gh pr merge and read-only gh verbs, deny the --admin bypass ([#562](https://github.com/WeAreHausTech/haus-workflow/issues/562)) ([00a8c95](https://github.com/WeAreHausTech/haus-workflow/commit/00a8c95dbc49dea5863d15b9812f038dfde036bb)), closes [#545](https://github.com/WeAreHausTech/haus-workflow/issues/545)
8
+
3
9
  ## [5.6.0](https://github.com/WeAreHausTech/haus-workflow/compare/v5.5.0...v5.6.0) (2026-09-13)
4
10
 
5
11
  ### Bug Fixes
package/dist/cli.js CHANGED
@@ -3383,8 +3383,42 @@ var ALLOWED_SUBCOMMANDS = [
3383
3383
  "workspace worktree list",
3384
3384
  "workspace worktree doctor"
3385
3385
  ];
3386
+ var ALLOWED_COMMANDS = [
3387
+ // Merging the PR is the last step of the house loop, and where a deploy builds `main`'s
3388
+ // SHA it is a precondition of deploying at all. The auto mode classifier blocked it
3389
+ // mid-run with no rule available to pre-authorize it (#545). Its dangerous form —
3390
+ // `--admin`, the only way past branch protection and required reviews — is deny-tier in
3391
+ // `DENY_COMMANDS` and caught in any spelling by the runtime guard, which outranks this
3392
+ // rule. The repo's own branch protection stays the review gate.
3393
+ "gh pr merge",
3394
+ // Read-only verbs. `gh` is absent from Claude Code's built-in read-only command set
3395
+ // (`ls`, `cat`, `grep`, read-only `git`, …), so each of these otherwise costs a
3396
+ // classifier round-trip mid-run — on `gh run watch`, once per CI wait. Nothing under
3397
+ // these prefixes writes, so there is no dangerous form left to gate.
3398
+ "gh pr view",
3399
+ "gh pr list",
3400
+ "gh pr diff",
3401
+ "gh pr checks",
3402
+ "gh run list",
3403
+ "gh run view",
3404
+ "gh run watch",
3405
+ "gh issue view",
3406
+ "gh issue list",
3407
+ "gh repo view",
3408
+ "gh label list",
3409
+ // Prints who you are authenticated as; `gh auth token`, which prints the credential
3410
+ // itself, is a different leaf and stays out.
3411
+ "gh auth status",
3412
+ "gh search prs",
3413
+ "gh search issues"
3414
+ ];
3386
3415
  function buildAllowRules() {
3387
- return [...new Set(ALLOWED_SUBCOMMANDS.map((sub) => `Bash(haus ${sub}:*)`))];
3416
+ return [
3417
+ .../* @__PURE__ */ new Set([
3418
+ ...ALLOWED_SUBCOMMANDS.map((sub) => `Bash(haus ${sub}:*)`),
3419
+ ...ALLOWED_COMMANDS.map((command) => `Bash(${command}:*)`)
3420
+ ])
3421
+ ];
3388
3422
  }
3389
3423
 
3390
3424
  // src/install/settings-merge.ts
@@ -3722,6 +3756,9 @@ var DENY_COMMANDS = [
3722
3756
  // Skipping the hooks is a NEVER rule in WORKFLOW.md and was missing from every tier.
3723
3757
  "git commit --no-verify",
3724
3758
  "git push --no-verify",
3759
+ // `gh pr merge` is allow-tier so an unattended run can land its own PR; this is the
3760
+ // one form that merges past branch protection and required reviews (#545, ADR-0126).
3761
+ "gh pr merge --admin",
3725
3762
  ...RUNTIME_ONLY_DENY_COMMANDS,
3726
3763
  "npm publish",
3727
3764
  // Classic v1 syntax alongside the Berry `yarn npm publish` form.
@@ -11868,6 +11905,32 @@ function isPackagePublish(words) {
11868
11905
  if (RUN_SCRIPT_VERBS.some((verb) => hasWord(words, verb))) return false;
11869
11906
  return !words.some((word) => word === "--dry-run" || word === "--dry-run=true");
11870
11907
  }
11908
+ var GH_TEXT_FLAGS = /* @__PURE__ */ new Set([
11909
+ "-b",
11910
+ "--body",
11911
+ "-F",
11912
+ "--body-file",
11913
+ "-t",
11914
+ "--subject",
11915
+ "-R",
11916
+ "--repo"
11917
+ ]);
11918
+ var ADMIN_MERGE_FLAGS = /* @__PURE__ */ new Set(["--admin", "--admin=true", "--admin=1"]);
11919
+ function isProtectionBypassMerge(words) {
11920
+ if (commandName(words) !== "gh") return false;
11921
+ const args = [];
11922
+ for (let i = 1; i < words.length; i += 1) {
11923
+ const word = words[i];
11924
+ if (GH_TEXT_FLAGS.has(word)) {
11925
+ i += 1;
11926
+ continue;
11927
+ }
11928
+ args.push(word);
11929
+ }
11930
+ const operands = args.filter((word) => !word.startsWith("-")).map((word) => word.toLowerCase());
11931
+ if (operands[0] !== "pr" || operands[1] !== "merge") return false;
11932
+ return args.some((word) => ADMIN_MERGE_FLAGS.has(word.toLowerCase()));
11933
+ }
11871
11934
  var PRIVILEGE_ESCALATORS = /* @__PURE__ */ new Set(["sudo", "doas", "pkexec"]);
11872
11935
  function isPrivilegeEscalation(words) {
11873
11936
  return PRIVILEGE_ESCALATORS.has(commandName(words));
@@ -11887,7 +11950,8 @@ var SEGMENT_MATCHERS = [
11887
11950
  { name: "a force push", matches: isForcePush },
11888
11951
  { name: "a commit or push that skips the hooks", matches: isHookBypass },
11889
11952
  { name: "a recursive permission change", matches: isRecursiveChmod },
11890
- { name: "a package publish", matches: isPackagePublish }
11953
+ { name: "a package publish", matches: isPackagePublish },
11954
+ { name: "a merge that bypasses branch protection", matches: isProtectionBypassMerge }
11891
11955
  ];
11892
11956
  function matchDangerousCommandShape(command) {
11893
11957
  for (const segment of parseShellSegments(command)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haus-tech/haus-workflow",
3
- "version": "5.6.0",
3
+ "version": "5.6.1",
4
4
  "description": "Haus AI workflow CLI for Claude Code.",
5
5
  "type": "module",
6
6
  "bin": {