Skip to content

Commit 3612e76

Browse files
authored
Chore/audit fixes 20260601 2130 (#85)
* fix(ui): remove dead resource-architecture ESLint plugin registration Plugin was imported and registered but had zero rules enabled; removed import, plugins-map entry, and the now-unused devDependency. Audit: F001 * fix(docs): add orphaned CSRF stance page to architecture sidebar csrf-stance.mdx existed but was reachable only by direct URL; now linked under Architecture. Audit: F002 * fix(infra): mount WUD docker socket read-only by default Notify-only watching needs only read access; auto-redeploy must explicitly drop :ro. Limits blast radius of a compromised watched image/GHCR token. Audit: F003 * fix(infra): reject GlitchTip dev-default superuser password in prod Prod startup now requires GLITCHTIP_SUPERUSER_PASSWORD and refuses the dev default (admin123456), matching the existing prod guards for secret key / host / basic-auth. Audit: F004 * fix(ci): warn on gitleaks version drift in local pre-push gate Local hook now reads the CI-pinned GITLEAKS_VERSION from the workflow and warns when the installed gitleaks differs, closing the local/CI ruleset parity gap. Self-sourcing the pin keeps it from going stale on a CI bump. Audit: F005 * feat(api): add lint-meta rule enforcing shared tool version parity across apps New supply-chain guardrail fails when a shared dev tool (ESLint, TypeScript, Prettier, knip, …) is pinned to different versions across apps that declare it. Closes the drift class that previously had to be fixed reactively. Includes drift/clean fixtures and tests; RULES.md regenerated. Audit: F006 * fix(docs): pin @types/react overrides to match the UI app Adds @types/react/@types/react-dom overrides (19.2.15 / 19.2.3) matching apps/ui so React types resolve identically; documented in the //overrides note alongside the existing security pins. Audit: F012 * fix(api): surface non-positive rate-limit duration instead of failing open silently ValkeyRateLimitContext.init() now logs a structured warning (rate_limit_misconfigured) when duration is missing or <= 0, since that silently disables limiting. Adds the event to the logger registry. Audit: F008 * fix(docs): add error context around sharp OG-image rasterization Wraps the sharp PNG conversion so libvips/OOM/bad-SVG failures report the SVG size and underlying cause instead of an opaque stack in CI. Audit: F011 * fix(ui): refresh bun.lock after removing resource-architecture dep F001 removed the devDependency from package.json but not the lockfile; CI installs --frozen-lockfile (bun 1.3.14) so every ui-touching workflow failed at install. Lock now matches package.json. Audit: F001
1 parent 0bd8eb6 commit 3612e76

22 files changed

Lines changed: 264 additions & 15 deletions

File tree

apps/api/scripts/lint-meta/RULES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Run `bun run lint:meta --list-rules` for the machine-readable list from the regi
1616
| ----------------------------------- | ------------ | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
1717
| `package-json-exact-deps` | supply-chain | no | dependencies and devDependencies must use exact versions (no ranges). |
1818
| `no-overlapping-libs` | supply-chain | no | package.json must not list forbidden overlapping library pairs. |
19+
| `shared-tool-version-parity` | supply-chain | no | Shared dev tooling (ESLint, TypeScript, Prettier, knip, …) must be pinned to the same version in every app that declares it. |
1920
| `github-actions-permissions` | ci | no | GitHub Actions workflows require permissions block and SHA-pinned uses: refs. |
2021
| `github-actions-permissions:verify` | ci | no | Pinned action SHAs resolve on github.com (lint:meta:verify only). |
2122
| `pre-push-ci-parity` | ci | no | CI workflow must include every command listed in scripts/ci/pre-push.manifest.json. |

apps/api/scripts/lint-meta/cli.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { META_RULES } from "./registry";
2020
import { printRuleCatalog, runMetaRules, runMetaRulesAsync } from "./runner";
2121
import { checkDependencyPairs } from "./rules/supply-chain/no-overlapping-libs";
2222
import { checkExactDependencyVersions } from "./rules/supply-chain/package-json-exact-deps";
23+
import { checkSharedToolVersionParity } from "./rules/supply-chain/shared-tool-version-parity";
2324
import { checkEslintConfigNoWarn } from "./rules/config/eslint-config-no-warn";
2425
import { checkEnvSchemaDrift } from "./rules/env/env-cascade-drift";
2526
import { checkNoDirectProcessEnv } from "./rules/env/no-direct-process-env";
@@ -99,6 +100,7 @@ export {
99100
checkNoRawRoleLiterals,
100101
checkPrePushParity,
101102
checkRouteFilesHaveTests,
103+
checkSharedToolVersionParity,
102104
checkTouchedTests,
103105
checkWorkflowShas,
104106
};

apps/api/scripts/lint-meta/registry.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { forbiddenTextRule } from "./rules/source-text/forbidden-text";
1010
import { noRawRoleLiteralsRule } from "./rules/source-text/no-raw-role-literals";
1111
import { noOverlappingLibsRule } from "./rules/supply-chain/no-overlapping-libs";
1212
import { packageJsonExactDepsRule } from "./rules/supply-chain/package-json-exact-deps";
13+
import { sharedToolVersionParityRule } from "./rules/supply-chain/shared-tool-version-parity";
1314
import { logicFilesRequireTestSiblingRule } from "./rules/testing/logic-files-require-test-sibling";
1415
import { routesRequireTestSiblingRule } from "./rules/testing/routes-require-test-sibling";
1516
import { skippedTestsNeedTrackingRule } from "./rules/testing/skipped-tests-need-tracking";
@@ -19,6 +20,7 @@ import type { IMetaRule } from "./types";
1920
export const META_RULES: readonly IMetaRule[] = [
2021
packageJsonExactDepsRule,
2122
noOverlappingLibsRule,
23+
sharedToolVersionParityRule,
2224
githubActionsPermissionsRule,
2325
prePushCiParityRule,
2426
enginePinParityRule,
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import { readFileSync, readdirSync, statSync } from "node:fs";
2+
import { join } from "node:path";
3+
4+
import { parsePackageJson } from "../../parsers/package-json";
5+
import type { IMetaRule, IViolation } from "../../types";
6+
7+
/*
8+
* Dev tooling that must stay in lockstep across every app that declares it.
9+
* Drift here means two apps lint/format/typecheck with different rule sets —
10+
* a class of defect that has bitten this repo before. Only apps that actually
11+
* declare a tool are compared, so an app legitimately omitting one (e.g. the
12+
* static docs site has no ESLint) is never forced to add it.
13+
*/
14+
const SHARED_TOOLS = [
15+
"eslint",
16+
"typescript",
17+
"prettier",
18+
"knip",
19+
"typescript-eslint",
20+
"eslint-config-prettier",
21+
"eslint-plugin-import",
22+
"eslint-plugin-promise",
23+
"eslint-plugin-sonarjs",
24+
"eslint-plugin-unicorn",
25+
"@eslint/js",
26+
"husky",
27+
] as const;
28+
29+
interface IAppDeps {
30+
readonly app: string;
31+
readonly file: string;
32+
readonly deps: Record<string, string>;
33+
}
34+
35+
function readApps(appsDir: string): IAppDeps[] {
36+
const out: IAppDeps[] = [];
37+
let entries: string[];
38+
39+
try {
40+
entries = readdirSync(appsDir);
41+
} catch {
42+
return out;
43+
}
44+
45+
for (const entry of entries) {
46+
const dir = join(appsDir, entry);
47+
48+
let isDir: boolean;
49+
50+
try {
51+
isDir = statSync(dir).isDirectory();
52+
} catch {
53+
continue;
54+
}
55+
56+
if (!isDir) {
57+
continue;
58+
}
59+
60+
const file = join(dir, "package.json");
61+
let text: string;
62+
63+
try {
64+
text = readFileSync(file, "utf8");
65+
} catch {
66+
continue;
67+
}
68+
69+
const pkg = parsePackageJson(text);
70+
71+
if (pkg === null) {
72+
continue;
73+
}
74+
75+
out.push({
76+
app: entry,
77+
file,
78+
deps: { ...pkg.dependencies, ...pkg.devDependencies },
79+
});
80+
}
81+
82+
return out;
83+
}
84+
85+
interface IDeclarer {
86+
readonly app: string;
87+
readonly file: string;
88+
readonly version: string;
89+
}
90+
91+
export function checkSharedToolVersionParity(appsDir: string): IViolation[] {
92+
const violations: IViolation[] = [];
93+
const apps = readApps(appsDir);
94+
95+
for (const tool of SHARED_TOOLS) {
96+
const declarers: IDeclarer[] = apps
97+
.map((app) => ({ app: app.app, file: app.file, version: app.deps[tool] }))
98+
.filter((entry): entry is IDeclarer => typeof entry.version === "string");
99+
100+
if (declarers.length < 2) {
101+
continue;
102+
}
103+
104+
const versions = new Set(declarers.map((entry) => entry.version));
105+
106+
if (versions.size === 1) {
107+
continue;
108+
}
109+
110+
const summary = declarers
111+
.map((entry) => `${entry.app}@${entry.version}`)
112+
.join(", ");
113+
114+
for (const declarer of declarers) {
115+
violations.push({
116+
file: declarer.file,
117+
rule: "shared-tool-version-parity",
118+
message: `${tool} version drifts across apps (${summary}) — shared dev tooling must be pinned to one version in every app that declares it.`,
119+
});
120+
}
121+
}
122+
123+
return violations;
124+
}
125+
126+
/** Shared dev tooling must be pinned to the same version across apps. */
127+
export const sharedToolVersionParityRule: IMetaRule = {
128+
id: "shared-tool-version-parity",
129+
category: "supply-chain",
130+
description:
131+
"Shared dev tooling (ESLint, TypeScript, Prettier, knip, …) must be pinned to the same version in every app that declares it.",
132+
run({ root }) {
133+
return checkSharedToolVersionParity(join(root, ".."));
134+
},
135+
};

apps/api/src/config/logger/logger.events.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export const LOG_EVENTS = [
6060
"cache_initialized",
6161
"cache_parse_error",
6262
"cache_valkey_error",
63+
"rate_limit_misconfigured",
6364
"db_connection_close",
6465
"db_connection_closed",
6566
"db.connected",

apps/api/src/lib/rate-limit/valkey-context.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,28 @@ export class ValkeyRateLimitContext implements RateLimitContext {
5959
}
6060

6161
init(options: Omit<RateLimitOptions, "context">): void {
62-
this.durationMs =
63-
typeof options.duration === "number" ? options.duration : 0;
62+
const { duration } = options;
63+
64+
if (typeof duration === "number" && duration > 0) {
65+
this.durationMs = duration;
66+
67+
return;
68+
}
69+
70+
/*
71+
* A non-positive/missing duration makes every request take the
72+
* permissive fallback path below — i.e. rate limiting is silently off.
73+
* That is a misconfiguration, not an infra blip, so surface it loudly
74+
* instead of failing open without a trace.
75+
*/
76+
this.durationMs = 0;
77+
logger.warn(
78+
"Rate-limit duration is not a positive number; Valkey rate limiting is disabled",
79+
{
80+
event: "rate_limit_misconfigured",
81+
duration: typeof duration === "number" ? duration : null,
82+
}
83+
);
6484
}
6585

6686
async increment(
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"name": "fixture-clean-a",
3+
"devDependencies": {
4+
"eslint": "10.4.0",
5+
"typescript": "6.0.3"
6+
}
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"name": "fixture-clean-b",
3+
"devDependencies": {
4+
"eslint": "10.4.0",
5+
"knip": "6.14.1"
6+
}
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"name": "fixture-app-a",
3+
"devDependencies": {
4+
"eslint": "10.4.0",
5+
"typescript": "6.0.3"
6+
}
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"name": "fixture-app-b",
3+
"devDependencies": {
4+
"eslint": "10.3.0",
5+
"typescript": "6.0.3"
6+
}
7+
}

0 commit comments

Comments
 (0)