mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-10 23:19:09 +02:00
Merge origin/main (v1.60.1.0) into garrytan/gstack-fix-wave
Semantic reconciliation with the parallel time-attack wave (#2264):
- careful: adopt main's anchored full-command whitelist (stricter — also
catches comment-hiding), re-apply this wave's two hardenings on top
(capital -[rR] in the flag cluster; exclude `(` and backtick from safe
targets so $()/backtick substitution cannot ride the whitelist). Union
of both waves' test batteries passes (main's test.each incl. comment
case + this wave's substitution/capital-R/FP-pin cases).
- one-way-doors: main landed the singular noun unification (a2a447a1);
keep this wave's superset (plural s? + --summary-stdin runtime wiring).
- gbrain-local-status: union of states — main's engine-locked (#2194,
exit 124 PGLite lock) + this wave's thin-client (#2051). --is-ok keeps
main's intent (engine-locked = STOP) and this wave's (thin-client =
usable). Test harness unions both fake behaviors.
- sync-gbrain/setup-gbrain tmpls: both Step 1.5 branches kept; generated
SKILL.md resolved via bun run gen:skill-docs (never hand-edited).
- VERSION/package.json -> 1.61.0.0 per bin/gstack-next-version (main took
1.60.1.0; PR #2470 claims 1.60.2.0). CHANGELOG: wave entry renumbered
1.61.0.0 on top of main's 1.60.1.0; careful/#2024 bullets updated to
describe the delta vs current main. TODOS: union.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+14
-1
@@ -18,10 +18,23 @@ let filterBranch: string | null = null;
|
||||
let filterTier: string | null = null;
|
||||
let limit = 20;
|
||||
|
||||
function parseLimit(raw: string | undefined): number {
|
||||
if (!raw || !/^[1-9]\d*$/.test(raw)) {
|
||||
console.error('eval:list: --limit requires a positive integer');
|
||||
process.exit(1);
|
||||
}
|
||||
const parsed = Number(raw);
|
||||
if (!Number.isSafeInteger(parsed)) {
|
||||
console.error('eval:list: --limit requires a positive integer');
|
||||
process.exit(1);
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (args[i] === '--branch' && args[i + 1]) { filterBranch = args[++i]; }
|
||||
else if (args[i] === '--tier' && args[i + 1]) { filterTier = args[++i]; }
|
||||
else if (args[i] === '--limit' && args[i + 1]) { limit = parseInt(args[++i], 10); }
|
||||
else if (args[i] === '--limit') { limit = parseLimit(args[++i]); }
|
||||
}
|
||||
|
||||
// Read eval files
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
import { ALL_HOST_CONFIGS, getHostConfig, ALL_HOST_NAMES } from '../hosts/index';
|
||||
import { validateAllConfigs } from './host-config';
|
||||
import { RESOLVERS } from './resolvers';
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
const CLI_REGEX = /^[a-z][a-z0-9_-]*$/;
|
||||
@@ -82,7 +83,7 @@ switch (command) {
|
||||
}
|
||||
|
||||
case 'validate': {
|
||||
const errors = validateAllConfigs(ALL_HOST_CONFIGS);
|
||||
const errors = validateAllConfigs(ALL_HOST_CONFIGS, new Set(Object.keys(RESOLVERS)));
|
||||
if (errors.length > 0) {
|
||||
for (const error of errors) {
|
||||
console.error(`ERROR: ${error}`);
|
||||
|
||||
+15
-3
@@ -117,7 +117,7 @@ const NAME_REGEX = /^[a-z][a-z0-9-]*$/;
|
||||
const PATH_REGEX = /^[a-zA-Z0-9_.\/${}~-]+$/;
|
||||
const CLI_REGEX = /^[a-z][a-z0-9_-]*$/;
|
||||
|
||||
export function validateHostConfig(config: HostConfig): string[] {
|
||||
export function validateHostConfig(config: HostConfig, validResolverNames?: ReadonlySet<string>): string[] {
|
||||
const errors: string[] = [];
|
||||
|
||||
if (!NAME_REGEX.test(config.name)) {
|
||||
@@ -152,15 +152,27 @@ export function validateHostConfig(config: HostConfig): string[] {
|
||||
errors.push(`install.linkingStrategy must be 'real-dir-symlink' or 'symlink-generated'`);
|
||||
}
|
||||
|
||||
// Cross-check suppressedResolvers against the known resolver names (injected to avoid a
|
||||
// circular import on the resolver registry). A typo would otherwise silently no-op: the
|
||||
// generator short-circuits suppressed names before the "unknown placeholder" throw, so an
|
||||
// unknown entry never surfaces at generation time either.
|
||||
if (validResolverNames && config.suppressedResolvers) {
|
||||
for (const name of config.suppressedResolvers) {
|
||||
if (!validResolverNames.has(name)) {
|
||||
errors.push(`suppressedResolvers entry '${name}' is not a known resolver`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
export function validateAllConfigs(configs: HostConfig[]): string[] {
|
||||
export function validateAllConfigs(configs: HostConfig[], validResolverNames?: ReadonlySet<string>): string[] {
|
||||
const errors: string[] = [];
|
||||
|
||||
// Per-config validation
|
||||
for (const config of configs) {
|
||||
const configErrors = validateHostConfig(config);
|
||||
const configErrors = validateHostConfig(config, validResolverNames);
|
||||
errors.push(...configErrors.map(e => `[${config.name}] ${e}`));
|
||||
}
|
||||
|
||||
|
||||
@@ -63,11 +63,11 @@ const DESTRUCTIVE_PATTERNS: RegExp[] = [
|
||||
/\brollback\b/i,
|
||||
|
||||
// Credentials / auth — allow filler words ("the", "my") between verb and noun.
|
||||
// All three verbs share ONE noun list (#2024: mismatched alternations let
|
||||
// "reset my secret" / "reset my access key" / "revoke my secret" leak as
|
||||
// two-way), with optional plural (`s?` — \b(...)\b alone cannot match
|
||||
// "credentials"). Keep these parallel: a verb-specific noun list is how
|
||||
// this class of false negative happens.
|
||||
// Keep the noun alternation IDENTICAL across revoke/reset/rotate — a noun in
|
||||
// one but not the others is a false-negative safety hole (#2024: "reset my
|
||||
// secret" / "reset my access key" / "revoke my secret" leaked as two-way).
|
||||
// Optional plural `s?` on the noun: \b(...)\b alone cannot match
|
||||
// "credentials" / "tokens" / "passwords".
|
||||
/\brevoke\s+[\w\s]*\b(api key|token|secret|credential|access key|password)s?\b/i,
|
||||
/\breset\s+[\w\s]*\b(api key|token|secret|credential|access key|password)s?\b/i,
|
||||
/\brotate\s+[\w\s]*\b(api key|token|secret|credential|access key|password)s?\b/i,
|
||||
|
||||
Reference in New Issue
Block a user