mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-31 22:14:25 +01:00
v1.91.346.0 fix(sealed): local_path flags a home path inside a shell default (${VAR:-/home/...}) (#3569)
* fix(sealed): local_path flags a home path after a shell default (${VAR:-/home/...})
Fixes #3568
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix: pre-landing review fixes (array, indirect and special parameter defaults; docs limit; changelog example)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix: bound the array subscript in LOCAL_PATH_RE so a hostile line stays linear
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* docs(changelog): end the two colon sentences the prose lint flags
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
---------
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
21277291c8
commit
514daebf5f
7 files changed
+48
-6
No files matched your search
@@ -1,5 +1,15 @@
|
||||
# Changelog
|
||||
|
||||
## [1.91.346.0] - 2026-10-31
|
||||
|
||||
**`sealed.local_path` now flags a home path inside a shell default, `${UBI_RUNNER:-/home/<user>…}`, as it already flagged the same path after `?? "/home/<user>…"` in TypeScript.** gstack-evals `quick/laneb/runner/launch.sh:9` passed the scan because the `-` before the path counted as part of a word (#3568).
|
||||
|
||||
### Fixed
|
||||
- **`sealed.local_path`** (`lib/sealed-guard.ts`): a home path directly after a `${…:-` or `${…-` default now matches, for a name, an array element (`${arr[0]:-`, a subscript of up to 64 characters, so a long line of unclosed `${a[` cannot make the scan quadratic), an indirect name (`${!ref:-`), a positional number or a special parameter (`${@:-`). `:=`, `:+` and `:?` already matched, because `=`, `+` and `?` were never word characters. A hyphen anywhere else still excludes the match, so a path glued to a word (`docs-…`, `$VAR-…`, `${VAR}-…`) stays unflagged; docs/pregate.md lists that as a known limit.
|
||||
|
||||
### Gate integrity
|
||||
- This is a gate edit that only tightens. The guard now refuses lines it passed before. No existing test, threshold or allowlist changed. A new case in `test/sealed-guard.test.ts` covers all five operators, a positional parameter, `${@`, an array element, an indirect name, a Windows path and the TypeScript `??` form. It also checks six negative controls, which are two hyphenated paths, a hyphenated sentence, `$VAR-`, `${VAR}-` and a relative default. Without the fix it misses every `-` form.
|
||||
|
||||
## [1.91.345.0] - 2026-10-31
|
||||
|
||||
**A capped `/ship` on Capy now spends fewer agent turns and counts only its own tokens, while every reviewer still gets exactly the brief 1.91.331.0 gives it.** Two new commands do `/ship`'s fixed shell work, a short driver file replaces reading the whole skill when a parent agent runs `/ship`, and the budget measures each reviewer batch on its own instead of across the parent's work. Replays of gstack-evals #34 and #36 (GSTA-167) spent 12.19M tokens on Steps 12-17 and 0.25-0.4M on each parent turn; these changes cut turns, not reviewer context.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# gstack digest v1.91.345.0 — regenerate/re-copy after upgrading gstack
|
||||
# gstack digest v1.91.346.0 — regenerate/re-copy after upgrading gstack
|
||||
|
||||
Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed
|
||||
for agent hosts without a full skill install. The full skills add workflows,
|
||||
|
||||
+4
-2
@@ -805,8 +805,10 @@ gstack-pregate sealed [--range <a..b>] [--remote origin] [--base <branch>] [--js
|
||||
- **Rules.** `sealed.path` and `sealed.transcript` check every path a commit
|
||||
adds, renames or copies, whatever its content (binary, empty, deleted again
|
||||
in a later commit); `sealed.answer`, `sealed.seed` and `sealed.local_path`
|
||||
(`/home/<user>/`, `/Users/<user>/`, `C:\Users\<user>\`) check each commit's added
|
||||
lines. Findings print rule, `file[:line]` and commit, never the text.
|
||||
(`/home/<user>/`, `/Users/<user>/`, `C:\Users\<user>\`, including after a shell
|
||||
default such as `${VAR:-…}` or `${VAR-…}`) check each commit's added lines.
|
||||
Known limit: a path glued to the text before it, by a hyphen (`docs-…`,
|
||||
`$VAR-…`, `${VAR}-…`) or a letter (`-I…`, `-L…`), is not matched. Findings print rule, `file[:line]` and commit, never the text.
|
||||
- **Globs.** `paths`, `transcripts` and `allow_paths` match the whole
|
||||
repo-relative path from the repository root. `*` and `?` stay inside one
|
||||
path segment; `**/` spans zero or more directories; a `**` not followed by
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@ export type Visibility = 'public' | 'private' | 'unknown';
|
||||
const KEYS = ['paths', 'transcripts', 'answer_markers', 'seed_patterns', 'local_paths', 'allow_paths', 'case_insensitive', 'visibility'];
|
||||
const MAX_ENTRIES = 64;
|
||||
const MAX_REGEX = 256;
|
||||
const LOCAL_PATH_RE = /(?:^|[^A-Za-z0-9_.~-])(?:\/home\/[^/\s'"`]+\/|\/Users\/[^/\s'"`]+\/|[A-Za-z]:\\+Users\\+[^\\\s'"`]+\\)/;
|
||||
const LOCAL_PATH_RE = /(?:^|[^A-Za-z0-9_.~-]|\$\{!?(?:[A-Za-z_][A-Za-z0-9_]*(?:\[[^\]\s]{0,64}\])?|[0-9]+|[@*#?$!-]):?-)(?:\/home\/[^/\s'"`]+\/|\/Users\/[^/\s'"`]+\/|[A-Za-z]:\\+Users\\+[^\\\s'"`]+\\)/;
|
||||
const PUBLIC_TTL_MS = 24 * 3600_000;
|
||||
const PRIVATE_TTL_MS = 3600_000;
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gstack",
|
||||
"version": "1.91.345",
|
||||
"version": "1.91.346",
|
||||
"description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
|
||||
@@ -102,6 +102,36 @@ describe('pre-push hook: public destination refuses each kind of sealed output',
|
||||
}
|
||||
});
|
||||
|
||||
test('local_path matches a home path after every shell parameter-expansion operator, and still skips hyphenated words', () => {
|
||||
const positives = [
|
||||
'RUNNER="${UBI_RUNNER:-/home/user/.capy/drive/runner}"',
|
||||
'RUNNER="${UBI_RUNNER-/home/user/runner}"',
|
||||
'RUNNER="${UBI_RUNNER:=/home/user/runner}"',
|
||||
'RUNNER="${UBI_RUNNER:+/Users/bob/runner}"',
|
||||
'RUNNER="${UBI_RUNNER:?/home/user/runner}"',
|
||||
'ROOT="${1:-/Users/bob/work}"',
|
||||
'ROOT="${@:-/home/user/work}"',
|
||||
'ROOT="${arr[0]:-/home/user/work}"',
|
||||
'ROOT="${!ref:-/home/user/work}"',
|
||||
'ROOT="${WIN_ROOT:-C:\\Users\\bob\\work}"',
|
||||
'const dir = process.env.UBI_WORK ?? "/home/ubi/work";',
|
||||
];
|
||||
const negatives = [
|
||||
'see docs-/home/user/x for the old layout',
|
||||
'mount at data-/Users/bob/cache',
|
||||
'RUNNER="$UBI_RUNNER-/home/user/runner"',
|
||||
'RUNNER="${UBI_RUNNER}-/home/user/runner"',
|
||||
'RUNNER="${UBI_RUNNER:-./runner}"',
|
||||
'a well-known, self-hosted, run-to-completion job',
|
||||
];
|
||||
const s = scratch({ '.gstack/pregate.json': policy(SEALED) });
|
||||
commit(s, { 'runner/launch.sh': [...positives, ...negatives].join('\n') + '\n' });
|
||||
const r = hook(s);
|
||||
expect(r.code).toBe(1);
|
||||
const flagged = [...r.err.matchAll(/sealed\.local_path\s+runner\/launch\.sh:(\d+)/g)].map(m => Number(m[1]));
|
||||
expect(flagged).toEqual(positives.map((_, i) => i + 1));
|
||||
});
|
||||
|
||||
test('path rules ignore content: a binary file, an empty transcript, an add-then-delete and a rename into a sealed path all refuse', () => {
|
||||
const binary = scratch({ '.gstack/pregate.json': policy(SEALED) });
|
||||
commit(binary, { 'heldout/questions.bin': Buffer.from([0, 1, 2, 0, 255, 0]) });
|
||||
|
||||
Reference in new issue
Block a user