mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-08 01:38:10 +01:00
* fix(deps): clear the OSV suppressions that expire on 2026-11-30 Delete the hono, @anthropic-ai/sdk (#2754) and sprintf-js ignores, which the lockfiles no longer match. Override image-size to 2.0.4 (#2753) and chokidar to 4.0.3 in lib/diagram-render (drops braces), and delete those ignores. Re-date katex to 2027-01-31: its fix is outside mermaid 11's range (mermaid-js/mermaid#8388). html-to-docx 1.8.0 bundles its own image-size 1.x, which the override cannot reach and whose ICNS parser loops forever. make-pdf --to docx now hands it only PNG, JPEG, GIF, WebP or SVG bytes. A golden docx test proves the Word output is unchanged. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * build(deps-dev): @types/bun 1.4.3, prettier 3.9.10; hold the Anthropic SDKs to patches Replaces dependabot #3228 without its agent-sdk 0.3 and sdk 0.133 bumps, which break typecheck:test and overlay-sdk-cancel-eof. Their 0.x breaking releases are semver-minor to dependabot, so the ignore covers minor and major. @types/bun 1.4.3 retires 18 test type diagnostics; the baseline locks that in. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(ci): exempt dependabot dependency-manifest PRs from Version Gate rule 1 Dependabot never edits VERSION, so hard rule 1 failed every package.json PR it opened. The workflow now passes the PR author and changed-file list; the gate skips rule 1 only for dependabot[bot] when every file is a package.json, bun.lock(b) or .github/dependabot.yml. A failed file lookup keeps the rule. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(make-pdf): guard every html-to-docx sizing path, keep non-ICNS formats Review found three spellings that got past the regex guard and still hung --to docx: a data-src decoy, a percent-encoded separator, and an unquoted encoded colon. html-to-docx sizes images on three paths, and buildImage URI-decodes the whole src first. The guard now mirrors all three, drops ICNS (the only input its bundled image-size routes to the looping parser) instead of allowlisting formats, so BMP and TIFF embed as before, and re-parses the result with html-to-docx's own html-to-vdom to fail closed on anything the regex misreads. The golden test tolerates CRLF checkouts on Windows. Version Gate treats a failed file lookup as an empty list, so a partial paginated result cannot decide the exemption. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * chore(release): stamp v1.91.121.0 Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * chore(release): restamp v1.91.122.0 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>
73 lines
4.0 KiB
TypeScript
73 lines
4.0 KiB
TypeScript
/**
|
|
* OSV suppression expiry (F11). Every [[IgnoredVulns]] entry carries an
|
|
* ignoreUntil, and the weekly scan turns red the first Monday after one
|
|
* passes. This free test fails 14 days before any expiry, so the
|
|
* re-justification lands in a PR instead of surprising the cron. It also pins
|
|
* the workflow's tracking-issue job, so a scheduled red notifies someone.
|
|
*/
|
|
import { describe, expect, test } from 'bun:test';
|
|
import * as fs from 'node:fs';
|
|
import * as path from 'node:path';
|
|
|
|
const ROOT = path.resolve(import.meta.dir, '..');
|
|
const CONFIG = '.osv-scanner.toml';
|
|
const EXPIRY_WARNING_DAYS = 14;
|
|
|
|
interface Suppression { id: string; ignoreUntil: string }
|
|
|
|
function suppressions(toml: string): Suppression[] {
|
|
return toml.split('[[IgnoredVulns]]').slice(1).map(entry => ({
|
|
id: entry.match(/^id = "([^"]+)"/m)?.[1] ?? '(missing id)',
|
|
ignoreUntil: entry.match(/^ignoreUntil = (\S+)/m)?.[1] ?? '',
|
|
}));
|
|
}
|
|
|
|
/** Entries whose ignoreUntil is within `days` of `now` (or already past, or unparseable). */
|
|
function expiringSoon(entries: Suppression[], now: number, days = EXPIRY_WARNING_DAYS): string[] {
|
|
return entries.filter(entry => {
|
|
const until = Date.parse(entry.ignoreUntil);
|
|
return !Number.isFinite(until) || until - now <= days * 86_400_000;
|
|
}).map(entry => `${entry.id} (ignoreUntil ${entry.ignoreUntil || 'missing'})`);
|
|
}
|
|
|
|
const FIX = `Fix: for each entry, re-run the OSV scan (gh workflow run osv-scanner.yml) and check for an upstream fix; upgrade via package.json overrides and delete the entry, or re-justify its reason and move ignoreUntil forward in ${CONFIG}.`;
|
|
|
|
describe('OSV suppression expiry', () => {
|
|
test(`no suppression in ${CONFIG} expires within ${EXPIRY_WARNING_DAYS} days`, () => {
|
|
const entries = suppressions(fs.readFileSync(path.join(ROOT, CONFIG), 'utf8'));
|
|
expect(entries.length).toBeGreaterThan(0);
|
|
const soon = expiringSoon(entries, Date.now());
|
|
if (soon.length) throw new Error(`${soon.length} OSV suppression(s) expire within ${EXPIRY_WARNING_DAYS} days: ${soon.join(', ')}.\n${FIX}`);
|
|
});
|
|
|
|
test('the window is 14 days, inclusive, and a missing or bad date counts as expiring', () => {
|
|
const entries = [{ id: 'GHSA-a', ignoreUntil: '2026-11-30T00:00:00Z' }, { id: 'GHSA-b', ignoreUntil: 'soon' }];
|
|
expect(expiringSoon(entries, Date.parse('2026-11-15T23:59:59Z'))).toEqual(['GHSA-b (ignoreUntil soon)']);
|
|
expect(expiringSoon(entries, Date.parse('2026-11-16T00:00:00Z'))).toEqual(['GHSA-a (ignoreUntil 2026-11-30T00:00:00Z)', 'GHSA-b (ignoreUntil soon)']);
|
|
expect(FIX).toContain('move ignoreUntil forward');
|
|
});
|
|
|
|
test('a failed main scan upserts a tracking issue and a clean one closes it', () => {
|
|
const workflow = Bun.YAML.parse(fs.readFileSync(path.join(ROOT, '.github/workflows/osv-scanner.yml'), 'utf8')) as any;
|
|
const report = workflow.jobs.report;
|
|
expect(report.needs).toBe('scan');
|
|
expect(report.if).toBe("always() && github.ref == 'refs/heads/main'");
|
|
expect(report.permissions).toEqual({ issues: 'write' });
|
|
const run = report.steps[0].run as string;
|
|
expect(report.steps[0].env.SCAN_RESULT).toBe('${{ needs.scan.result }}');
|
|
for (const text of ['"$SCAN_RESULT" = "failure"', 'gh issue create', 'gh issue comment', 'gh issue close']) expect(run).toContain(text);
|
|
});
|
|
|
|
test('image-size is overridden to a fixed 2.x and its suppressions are gone (#2753)', () => {
|
|
const toml = fs.readFileSync(path.join(ROOT, CONFIG), 'utf8');
|
|
expect(toml).not.toMatch(/GHSA-(?:5p2g-fcmc-qvqq|w3rx-r6r6-pgpr)/);
|
|
const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));
|
|
expect(pkg.overrides['image-size']).toBe('2.0.4');
|
|
// html-to-docx still asks for ^1.0.0; the override is what moves the lockfile off the affected 1.x.
|
|
const lock = fs.readFileSync(path.join(ROOT, 'bun.lock'), 'utf8');
|
|
expect(lock).toMatch(/"html-to-docx": \["html-to-docx@[^"]+", "", \{ "dependencies": \{[^}]*"image-size": "\^1\.0\.0"/);
|
|
expect(lock).toMatch(/"image-size": \["image-size@2\.0\.4"/);
|
|
expect(lock).not.toMatch(/"image-size": \["image-size@1\./);
|
|
});
|
|
});
|