@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 +6 -0
- package/dist/cli.js +66 -2
- package/package.json +1 -1
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 [
|
|
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)) {
|