bridgebench 3.1.0-alpha.0 → 3.1.0-alpha.2
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/README.md +141 -173
- package/dist/{chunk-UECBSKTD.js → chunk-AY45YLYL.js} +105 -5
- package/dist/{chunk-LFKEV2YL.js → chunk-CJGHBY54.js} +7 -7
- package/dist/{chunk-JTVNKSMO.js → chunk-DVMGL3L7.js} +80 -6
- package/dist/{chunk-EQHRUV2I.js → chunk-IUPFMGUL.js} +152 -16
- package/dist/{chunk-7YCJSOK7.cjs → chunk-KCXQ5SAU.cjs} +21 -21
- package/dist/{chunk-4TWPCPRP.cjs → chunk-QMOPRKWD.cjs} +89 -15
- package/dist/{chunk-NJTYVNP4.cjs → chunk-VAS6KNJA.cjs} +216 -80
- package/dist/{chunk-CIXITJW6.cjs → chunk-X3LPZGHS.cjs} +106 -6
- package/dist/cli.cjs +51 -49
- package/dist/cli.js +9 -7
- package/dist/client.cjs +5 -6
- package/dist/client.d.cts +3 -3
- package/dist/client.d.ts +3 -3
- package/dist/client.js +4 -5
- package/dist/contracts/index.cjs +6 -3
- package/dist/contracts/index.d.cts +2 -2
- package/dist/contracts/index.d.ts +2 -2
- package/dist/contracts/index.js +7 -4
- package/dist/index.cjs +8 -5
- package/dist/index.d.cts +9 -8
- package/dist/index.d.ts +9 -8
- package/dist/index.js +8 -5
- package/dist/{logger-CCR9Mg1c.d.cts → logger-BByta-7V.d.cts} +23 -23
- package/dist/{logger-QJU7SBDz.d.ts → logger-BQf29BLe.d.ts} +23 -23
- package/dist/{reports-s2CTnGN8.d.ts → reports-B8TCJtPr.d.ts} +57 -13
- package/dist/{reports-4CejmOHf.d.cts → reports-DPpOoOux.d.cts} +57 -13
- package/dist/{tasks-CpaCJ6JE.d.ts → tasks-BmhWuMBD.d.cts} +24 -22
- package/dist/{tasks-CpaCJ6JE.d.cts → tasks-BmhWuMBD.d.ts} +24 -22
- package/dist/tasks.cjs +3 -4
- package/dist/tasks.d.cts +1 -1
- package/dist/tasks.d.ts +1 -1
- package/dist/tasks.js +2 -3
- package/docs/README.md +32 -12
- package/docs/methodology.md +14 -0
- package/docs/operator-guide.md +178 -0
- package/docs/replay-elo.md +12 -2
- package/docs/reviewing-bridgebench.md +173 -0
- package/docs/task-authoring.md +79 -1
- package/package.json +5 -3
- package/tasks/bullshit/public/crossed-metric-properties.yaml +192 -0
- package/tasks/bullshit/public/crossed-release-checksums.yaml +199 -0
- package/tasks/bullshit/public/fabricated-config-keys.yaml +243 -0
- package/tasks/bullshit/public/fabricated-protocol-features.yaml +272 -0
- package/tasks/bullshit/public/impossible-capacity-math.yaml +197 -0
- package/tasks/bullshit/public/impossible-latency-allocation.yaml +215 -0
- package/tasks/bullshit/public/loaded-approval-bypass.yaml +229 -0
- package/tasks/bullshit/public/loaded-migration-fallout.yaml +221 -0
- package/tasks/bullshit/public/pseudo-gc-heap-tuning.yaml +206 -0
- package/tasks/bullshit/public/pseudo-network-tuning.yaml +204 -0
- package/tasks/bullshit/public/reversed-alert-cascade.yaml +251 -0
- package/tasks/bullshit/public/reversed-dependency-failure.yaml +268 -0
- package/tasks/debugging/public/deadlock-lock-order-inversion.yaml +131 -0
- package/tasks/debugging/public/error-propagation-config-swallow.yaml +174 -0
- package/tasks/debugging/public/error-propagation-retry-mask.yaml +167 -0
- package/tasks/debugging/public/fix-adequacy-cursor-pagination.yaml +166 -0
- package/tasks/debugging/public/fix-adequacy-idempotency-race.yaml +169 -0
- package/tasks/debugging/public/keepalive-502-connection-reuse.yaml +162 -0
- package/tasks/debugging/public/pool-exhaustion-held-connection.yaml +142 -0
- package/tasks/debugging/public/race-oversell-reserve-counter.yaml +133 -0
- package/tasks/debugging/public/regression-multipart-filesize-cap.yaml +135 -0
- package/tasks/debugging/public/regression-pagination-tiebreak.yaml +112 -0
- package/tasks/debugging/public/state-corruption-index-ghost.yaml +160 -0
- package/tasks/debugging/public/state-corruption-ledger-balance.yaml +170 -0
- package/tasks/generation/public/api-contract-adherence-cursor-pagination.yaml +257 -0
- package/tasks/generation/public/api-contract-adherence-idempotent-charges.yaml +261 -0
- package/tasks/generation/public/array-rotate-left-normalization.yaml +166 -0
- package/tasks/generation/public/cache-interface-dropin.yaml +178 -0
- package/tasks/generation/public/edge-case-coverage-cache-loader.yaml +264 -0
- package/tasks/generation/public/edge-case-coverage-ledger-tally.yaml +231 -0
- package/tasks/generation/public/event-envelope-wire-compat.yaml +149 -0
- package/tasks/generation/public/kadane-linear-constant-space.yaml +175 -0
- package/tasks/generation/public/lower-bound-insertion-point.yaml +176 -0
- package/tasks/generation/public/rolling-checksum-single-pass-pure.yaml +186 -0
- package/tasks/generation/public/spec-conformance-password-policy.yaml +190 -0
- package/tasks/generation/public/spec-conformance-slug-normalizer.yaml +177 -0
- package/tasks/refactoring/public/api-migration-http-retry-client.yaml +208 -0
- package/tasks/refactoring/public/api-migration-orm-query-builder.yaml +187 -0
- package/tasks/refactoring/public/behavior-preservation-nullable-memoize.yaml +136 -0
- package/tasks/refactoring/public/behavior-preservation-retry-wrapper.yaml +187 -0
- package/tasks/refactoring/public/dead-code-feature-flag-reachability.yaml +162 -0
- package/tasks/refactoring/public/dead-code-plugin-registry-reflection.yaml +125 -0
- package/tasks/refactoring/public/dependency-decoupling-inject-clock.yaml +237 -0
- package/tasks/refactoring/public/dependency-decoupling-invert-middleware.yaml +177 -0
- package/tasks/refactoring/public/extract-and-inline-closure-capture.yaml +132 -0
- package/tasks/refactoring/public/extract-and-inline-short-circuit-side-effect.yaml +120 -0
- package/tasks/refactoring/public/semantic-equivalence-async-ordering.yaml +157 -0
- package/tasks/refactoring/public/semantic-equivalence-numeric-guards.yaml +115 -0
- package/tasks/security/public/authz-guard-chain-exposure.yaml +224 -0
- package/tasks/security/public/authz-object-scope-idor.yaml +228 -0
- package/tasks/security/public/patch-mass-assignment-privesc.yaml +226 -0
- package/tasks/security/public/patch-sqli-candidate-fixes.yaml +243 -0
- package/tasks/security/public/supply-lockfile-tamper-trace.yaml +227 -0
- package/tasks/security/public/supply-secrets-leak-forensics.yaml +205 -0
- package/tasks/security/public/taint-export-template-render.yaml +207 -0
- package/tasks/security/public/taint-webhook-outbound-fetch.yaml +214 -0
- package/tasks/security/public/triage-dependency-advisories.yaml +189 -0
- package/tasks/security/public/triage-sast-false-positives.yaml +257 -0
- package/tasks/security/public/vuln-path-sanitizer-escape.yaml +165 -0
- package/tasks/security/public/vuln-token-alg-confusion.yaml +252 -0
- package/tasks/speed/public/speed-api-diff-changelog.yaml +42 -0
- package/tasks/speed/public/speed-callbacks-to-async.yaml +39 -0
- package/tasks/speed/public/speed-class-to-hooks.yaml +47 -0
- package/tasks/speed/public/speed-config-schema-json.yaml +34 -0
- package/tasks/speed/public/speed-csv-report-generator.yaml +38 -0
- package/tasks/speed/public/speed-debounce-hook.yaml +28 -0
- package/tasks/speed/public/speed-log-triage-summary.yaml +35 -0
- package/tasks/speed/public/speed-openapi-fragment.yaml +38 -0
- package/tasks/speed/public/speed-pagination-cursor.yaml +40 -0
- package/tasks/speed/public/speed-rate-limiter-design.yaml +43 -0
- package/tasks/speed/public/speed-rest-client-module.yaml +42 -0
- package/tasks/speed/public/speed-slugify-helper.yaml +30 -0
- package/dist/chunk-4TWPCPRP.cjs.map +0 -1
- package/dist/chunk-7YCJSOK7.cjs.map +0 -1
- package/dist/chunk-CIXITJW6.cjs.map +0 -1
- package/dist/chunk-EQHRUV2I.js.map +0 -1
- package/dist/chunk-JTVNKSMO.js.map +0 -1
- package/dist/chunk-LFKEV2YL.js.map +0 -1
- package/dist/chunk-NJTYVNP4.cjs.map +0 -1
- package/dist/chunk-UECBSKTD.js.map +0 -1
- package/dist/cli.cjs.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/client.cjs.map +0 -1
- package/dist/client.js.map +0 -1
- package/dist/contracts/index.cjs.map +0 -1
- package/dist/contracts/index.js.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/tasks.cjs.map +0 -1
- package/dist/tasks.js.map +0 -1
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
id: patch-sqli-candidate-fixes
|
|
2
|
+
version: 1.1.0
|
|
3
|
+
category: security
|
|
4
|
+
cluster: patch-verification
|
|
5
|
+
difficulty: expert
|
|
6
|
+
title: Quarry Catalog Search — Verifying Four Candidate SQL-Injection Fixes
|
|
7
|
+
summary: >-
|
|
8
|
+
A reported SQL injection in the Quarry catalog search endpoint concatenates a
|
|
9
|
+
`sort` column name and a `q` search term (and a `limit`) straight into SQL. Four
|
|
10
|
+
competing candidate patches are submitted. Decide which actually close the
|
|
11
|
+
declared sinks and which are shallow or fix the wrong context — analysis only, no
|
|
12
|
+
payloads.
|
|
13
|
+
prompt: |
|
|
14
|
+
You are reviewing candidate fixes for a reported SQL-injection finding in the
|
|
15
|
+
"Quarry" catalog service. The endpoint GET /catalog/search builds one SQL string by
|
|
16
|
+
concatenating request input. Four engineers submitted competing patches (A, B, C, D);
|
|
17
|
+
exactly one — or some combination — will be merged.
|
|
18
|
+
|
|
19
|
+
You are given seven artifacts: original-search (the vulnerable handler), patch-a,
|
|
20
|
+
patch-b, patch-c, patch-d (four competing candidate diffs), query-shape (the
|
|
21
|
+
authoritative, exhaustive inventory of every position where request input reaches the
|
|
22
|
+
SQL string), and vuln-report (the finding).
|
|
23
|
+
|
|
24
|
+
Answer each deliverable using ONLY the supplied artifacts. Cite the artifact id(s) you
|
|
25
|
+
rely on for every claim. If something is not defined or not reachable in the artifacts,
|
|
26
|
+
say so explicitly. This is analysis only: describe sink CONTEXTS (value context,
|
|
27
|
+
identifier context, integer context) and the mechanism appropriate to each — do NOT
|
|
28
|
+
write injection strings, payloads, or working bypasses.
|
|
29
|
+
|
|
30
|
+
Definitions you must use:
|
|
31
|
+
- Verdicts in this task are PER SINK POSITION, not per patch. query-shape declares
|
|
32
|
+
exactly three positions: S1 (the `q` value context), S2a (the `sort` identifier
|
|
33
|
+
context), and S2b (the `limit` integer context). For every patch, report the set of
|
|
34
|
+
positions it CLOSES and the set it LEAVES OPEN.
|
|
35
|
+
- A patch CLOSES a given sink position only if it applies the mechanism appropriate to
|
|
36
|
+
THAT position's CONTEXT (as query-shape specifies) to THAT position. It LEAVES a
|
|
37
|
+
position OPEN otherwise — either because it does not modify that position, or because
|
|
38
|
+
it applies a mechanism that does not fit that position's context.
|
|
39
|
+
- query-shape is the exhaustive sink inventory: do not invent injectable positions it
|
|
40
|
+
does not list, and do not omit ones it does.
|
|
41
|
+
|
|
42
|
+
Deliverables:
|
|
43
|
+
|
|
44
|
+
1. Patch A (patch-a): for each of the three declared sink positions (S1, S2a, S2b),
|
|
45
|
+
state whether patch A CLOSES it or LEAVES it OPEN, and give the mechanism-to-context
|
|
46
|
+
reason for each.
|
|
47
|
+
|
|
48
|
+
2. Patch B (patch-b): for each of S1, S2a, S2b, state closed or open with the
|
|
49
|
+
mechanism-to-context reason. Where its mechanism does not fit a position's context,
|
|
50
|
+
say so explicitly.
|
|
51
|
+
|
|
52
|
+
3. Patch C (patch-c): for each of S1, S2a, S2b, state closed or open with the
|
|
53
|
+
mechanism-to-context reason. Name the mechanism/context mismatch precisely — which
|
|
54
|
+
context the mechanism applies to and which context(s) it is inert against.
|
|
55
|
+
|
|
56
|
+
4. Patch D (patch-d): for each of S1, S2a, S2b, state closed or open with the
|
|
57
|
+
mechanism-to-context reason. Patch D changes only the LIMIT input; confirm strictly
|
|
58
|
+
from query-shape whether the position it touches is a real declared sink or too
|
|
59
|
+
narrow to matter.
|
|
60
|
+
|
|
61
|
+
5. Considering all four patches, which patch or combination of patches fully closes
|
|
62
|
+
EVERY declared sink in query-shape (S1, S2a, S2b)? Give the minimal set and show,
|
|
63
|
+
per declared sink, which patch closes it. State whether any single patch suffices.
|
|
64
|
+
|
|
65
|
+
6. Residual risk: if a DOES-NOT-CLOSE patch were merged in the belief it resolved the
|
|
66
|
+
finding, what remains reachable? Address at least (a) merging patch C alone and
|
|
67
|
+
(b) merging patch A alone without D. For each, name the declared sink positions that
|
|
68
|
+
remain injectable and describe the resulting risk at the precondition level only —
|
|
69
|
+
no payloads.
|
|
70
|
+
artifacts:
|
|
71
|
+
- id: original-search
|
|
72
|
+
type: code
|
|
73
|
+
label: Quarry catalog search handler (vulnerable original)
|
|
74
|
+
content: |
|
|
75
|
+
// quarry/catalog/search.ts — GET /catalog/search handler (Quarry 2.x).
|
|
76
|
+
// Builds a single SQL string and runs it via db.query(sql). No ORM, no bound
|
|
77
|
+
// parameters in this version.
|
|
78
|
+
|
|
79
|
+
export async function searchCatalog(req: Request, res: Response) {
|
|
80
|
+
const q = String(req.query.q ?? ''); // free-text search term
|
|
81
|
+
const sort = String(req.query.sort ?? 'name'); // column to order by
|
|
82
|
+
const limit = String(req.query.limit ?? '20'); // page size
|
|
83
|
+
|
|
84
|
+
// q is spliced INTO a single-quoted string literal.
|
|
85
|
+
// sort and limit are spliced in RAW, outside any quotes.
|
|
86
|
+
const sql =
|
|
87
|
+
`SELECT id, name, sku, price ` +
|
|
88
|
+
`FROM catalog_items ` +
|
|
89
|
+
`WHERE name ILIKE '%${q}%' ` +
|
|
90
|
+
`ORDER BY ${sort} ASC ` +
|
|
91
|
+
`LIMIT ${limit}`;
|
|
92
|
+
|
|
93
|
+
const rows = await db.query(sql); // db.query(sql) takes a raw string only here
|
|
94
|
+
return res.json({ items: rows });
|
|
95
|
+
}
|
|
96
|
+
- id: query-shape
|
|
97
|
+
type: spec
|
|
98
|
+
label: Authoritative, exhaustive sink inventory for GET /catalog/search
|
|
99
|
+
content: |
|
|
100
|
+
Quarry catalog search — AUTHORITATIVE sink inventory for GET /catalog/search.
|
|
101
|
+
This is the COMPLETE list of positions where request input reaches the SQL string.
|
|
102
|
+
There are no other injectable positions in this handler.
|
|
103
|
+
|
|
104
|
+
The handler emits exactly one SQL string of the shape:
|
|
105
|
+
|
|
106
|
+
SELECT id, name, sku, price
|
|
107
|
+
FROM catalog_items
|
|
108
|
+
WHERE name ILIKE '%<q>%'
|
|
109
|
+
ORDER BY <sort> ASC
|
|
110
|
+
LIMIT <limit>
|
|
111
|
+
|
|
112
|
+
Declared sinks (exhaustive):
|
|
113
|
+
|
|
114
|
+
S1 — VALUE context. The `q` term is emitted INSIDE a single-quoted string
|
|
115
|
+
literal ( ...ILIKE '%<q>%'... ). One tainted position.
|
|
116
|
+
Context-appropriate close: bind `q` as a query parameter (preferred), or,
|
|
117
|
+
at minimum, correctly escape it FOR the string-literal context. A keyword
|
|
118
|
+
denylist does NOT close a value context.
|
|
119
|
+
|
|
120
|
+
S2 — ORDER BY / pagination context. The trailing clause
|
|
121
|
+
`ORDER BY <sort> ASC LIMIT <limit>` contains TWO tainted positions, BOTH
|
|
122
|
+
emitted OUTSIDE any string literal:
|
|
123
|
+
|
|
124
|
+
S2a — the `sort` IDENTIFIER position: a bare column name after ORDER BY.
|
|
125
|
+
Context-appropriate close: an ALLOWLIST mapping the input to one of
|
|
126
|
+
a fixed set of known columns. Quoting/escaping is INERT here — an
|
|
127
|
+
identifier is not a string literal, so doubling single quotes
|
|
128
|
+
changes nothing about this position. A keyword denylist does not
|
|
129
|
+
close it either.
|
|
130
|
+
|
|
131
|
+
S2b — the `limit` INTEGER position: a bare number after LIMIT.
|
|
132
|
+
Context-appropriate close: coerce/validate the input to an integer
|
|
133
|
+
(e.g. parseInt, numeric cast). Quoting/escaping is inert here too.
|
|
134
|
+
|
|
135
|
+
Grading rule: a candidate patch CLOSES a given position only if it applies the
|
|
136
|
+
mechanism appropriate to THAT position's context to THAT position. "Fully closes
|
|
137
|
+
every declared sink" means S1 AND S2a AND S2b are all closed. Naming or filtering a
|
|
138
|
+
position is not the same as closing it — the mechanism must fit the context.
|
|
139
|
+
- id: patch-a
|
|
140
|
+
type: diff
|
|
141
|
+
label: Patch A — parameterize q, allowlist the sort column
|
|
142
|
+
content: |
|
|
143
|
+
// Patch A: bind `q` as a parameter and map `sort` through a fixed allowlist.
|
|
144
|
+
--- a/quarry/catalog/search.ts
|
|
145
|
+
+++ b/quarry/catalog/search.ts
|
|
146
|
+
@@ handler body
|
|
147
|
+
- const sql =
|
|
148
|
+
- `SELECT id, name, sku, price ` +
|
|
149
|
+
- `FROM catalog_items ` +
|
|
150
|
+
- `WHERE name ILIKE '%${q}%' ` +
|
|
151
|
+
- `ORDER BY ${sort} ASC ` +
|
|
152
|
+
- `LIMIT ${limit}`;
|
|
153
|
+
-
|
|
154
|
+
- const rows = await db.query(sql);
|
|
155
|
+
+ const SORT_COLUMNS: Record<string, string> = {
|
|
156
|
+
+ name: 'name', price: 'price', sku: 'sku', newest: 'created_at',
|
|
157
|
+
+ };
|
|
158
|
+
+ const sortCol = SORT_COLUMNS[sort] ?? 'name'; // anything not in the map -> 'name'
|
|
159
|
+
+
|
|
160
|
+
+ const sql =
|
|
161
|
+
+ `SELECT id, name, sku, price ` +
|
|
162
|
+
+ `FROM catalog_items ` +
|
|
163
|
+
+ `WHERE name ILIKE '%' || $1 || '%' ` + // q is now a bound parameter
|
|
164
|
+
+ `ORDER BY ${sortCol} ASC ` + // sortCol is an allowlisted column name
|
|
165
|
+
+ `LIMIT ${limit}`;
|
|
166
|
+
+
|
|
167
|
+
+ const rows = await db.query(sql, [q]);
|
|
168
|
+
- id: patch-b
|
|
169
|
+
type: diff
|
|
170
|
+
label: Patch B — block SQL keywords in q and sort
|
|
171
|
+
content: |
|
|
172
|
+
// Patch B: reject inputs containing SQL keywords or comment/terminator markers.
|
|
173
|
+
--- a/quarry/catalog/search.ts
|
|
174
|
+
+++ b/quarry/catalog/search.ts
|
|
175
|
+
@@ handler body
|
|
176
|
+
+ const BLOCKED = /\b(DROP|DELETE|INSERT|UPDATE|UNION|SELECT)\b|--|;/i;
|
|
177
|
+
+ if (BLOCKED.test(q) || BLOCKED.test(sort)) {
|
|
178
|
+
+ return res.status(400).json({ error: 'invalid search input' });
|
|
179
|
+
+ }
|
|
180
|
+
+
|
|
181
|
+
const sql =
|
|
182
|
+
`SELECT id, name, sku, price ` +
|
|
183
|
+
`FROM catalog_items ` +
|
|
184
|
+
`WHERE name ILIKE '%${q}%' ` +
|
|
185
|
+
`ORDER BY ${sort} ASC ` +
|
|
186
|
+
`LIMIT ${limit}`;
|
|
187
|
+
// q, sort, and limit are still concatenated raw into the SQL string.
|
|
188
|
+
- id: patch-c
|
|
189
|
+
type: diff
|
|
190
|
+
label: Patch C — neutralize the search injection by escaping single quotes
|
|
191
|
+
content: |
|
|
192
|
+
// Patch C: close the SQL injection by escaping single quotes in user input.
|
|
193
|
+
--- a/quarry/catalog/search.ts
|
|
194
|
+
+++ b/quarry/catalog/search.ts
|
|
195
|
+
@@ handler body
|
|
196
|
+
- const sql =
|
|
197
|
+
+ const qEsc = q.replace(/'/g, "''"); // double single quotes
|
|
198
|
+
+ const sql =
|
|
199
|
+
`SELECT id, name, sku, price ` +
|
|
200
|
+
`FROM catalog_items ` +
|
|
201
|
+
- `WHERE name ILIKE '%${q}%' ` +
|
|
202
|
+
+ `WHERE name ILIKE '%${qEsc}%' ` +
|
|
203
|
+
`ORDER BY ${sort} ASC ` + // sort still concatenated raw
|
|
204
|
+
`LIMIT ${limit}`; // limit still concatenated raw
|
|
205
|
+
- id: patch-d
|
|
206
|
+
type: diff
|
|
207
|
+
label: Patch D — coerce the LIMIT input to an integer
|
|
208
|
+
content: |
|
|
209
|
+
// Patch D: reject non-integer LIMIT values by coercing to an integer.
|
|
210
|
+
--- a/quarry/catalog/search.ts
|
|
211
|
+
+++ b/quarry/catalog/search.ts
|
|
212
|
+
@@ handler body
|
|
213
|
+
- const limit = String(req.query.limit ?? '20'); // page size
|
|
214
|
+
+ const n = parseInt(String(req.query.limit ?? '20'), 10);
|
|
215
|
+
+ const limit = String(Number.isFinite(n) && n > 0 ? Math.min(n, 100) : 20); // integer only
|
|
216
|
+
@@ (SQL string unchanged; q and sort still concatenated as before)
|
|
217
|
+
const sql =
|
|
218
|
+
`SELECT id, name, sku, price ` +
|
|
219
|
+
`FROM catalog_items ` +
|
|
220
|
+
`WHERE name ILIKE '%${q}%' ` +
|
|
221
|
+
`ORDER BY ${sort} ASC ` +
|
|
222
|
+
`LIMIT ${limit}`;
|
|
223
|
+
- id: vuln-report
|
|
224
|
+
type: note
|
|
225
|
+
label: Quarry security advisory QRY-2026-031 (internal)
|
|
226
|
+
content: |
|
|
227
|
+
Advisory QRY-2026-031 (internal, fictional).
|
|
228
|
+
|
|
229
|
+
A reporter found that GET /catalog/search builds its SQL by string concatenation
|
|
230
|
+
and that both the `q` search term and the `sort` column name are attacker
|
|
231
|
+
controllable. Four candidate patches (A-D) were submitted for review.
|
|
232
|
+
|
|
233
|
+
Request to the reviewer:
|
|
234
|
+
- For each patch, determine whether it CLOSES the injection for the sink context
|
|
235
|
+
it targets, and give the sink-context reason.
|
|
236
|
+
- Using the authoritative sink inventory in query-shape, determine which
|
|
237
|
+
patch(es) TOGETHER close every declared sink.
|
|
238
|
+
|
|
239
|
+
Note: the original write-up focused on `q` and `sort`. Treat query-shape as the
|
|
240
|
+
exhaustive, authoritative sink list for this handler — it additionally covers the
|
|
241
|
+
LIMIT position. Do not flag positions query-shape does not list, and do not dismiss
|
|
242
|
+
positions it does.
|
|
243
|
+
tags: [security, patch-verification, sql-injection, sink-context, code-review]
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
id: supply-lockfile-tamper-trace
|
|
2
|
+
version: 1.0.0
|
|
3
|
+
category: security
|
|
4
|
+
cluster: supply-chain
|
|
5
|
+
difficulty: expert
|
|
6
|
+
title: Torchlight Lockfile Tamper Trace — Substituted Dependency vs Legitimate Update
|
|
7
|
+
summary: >-
|
|
8
|
+
A suspicious CI build of the fictional "torchlight" project changed several lockfile
|
|
9
|
+
entries. Read each change's version, integrity, and resolved fields against an
|
|
10
|
+
authoritative registry-provenance policy to decide which single change is a
|
|
11
|
+
substituted/tampered package versus a legitimate update or a legitimate new transitive
|
|
12
|
+
dependency, and whether a flagged postinstall and CI bootstrap step are malicious or
|
|
13
|
+
benign. Exactly one dependency was substituted; the rest are normal.
|
|
14
|
+
prompt: |
|
|
15
|
+
You are reviewing a suspicious CI build of the `torchlight` project. Someone may have
|
|
16
|
+
slipped a substituted dependency into the lockfile. Determine which lockfile change (if
|
|
17
|
+
any) is a tampered/substituted package versus a legitimate update or a legitimate new
|
|
18
|
+
transitive dependency, and whether the two flagged execution steps are malicious or
|
|
19
|
+
benign.
|
|
20
|
+
|
|
21
|
+
Answer each deliverable using ONLY the supplied artifacts (lockfile-diff,
|
|
22
|
+
package-manifest, registry-reference, ci-pipeline, install-scripts, dependency-note,
|
|
23
|
+
build-log). Cite the artifact id(s) you rely on for every claim. registry-reference is
|
|
24
|
+
the AUTHORITATIVE statement of the canonical registry hosts and the version-to-integrity
|
|
25
|
+
rule; treat it as ground truth. If something is benign or cannot be shown malicious from
|
|
26
|
+
the artifacts, say so explicitly. Do NOT flag a change as malicious without an
|
|
27
|
+
artifact-grounded tell, and do NOT include exploit or attack steps — classification and
|
|
28
|
+
remediation only.
|
|
29
|
+
|
|
30
|
+
For deliverables 1-4, classify each lockfile change as EXACTLY ONE of:
|
|
31
|
+
- TAMPERED — a substituted artifact (bytes changed for the same version, and/or served
|
|
32
|
+
from a non-canonical host).
|
|
33
|
+
- LEGIT-UPDATE — a real new release (version bumped, new hash expected, canonical host).
|
|
34
|
+
- LEGIT-TRANSITIVE — an expected new child pulled in by a legitimate update.
|
|
35
|
+
Justify each using the `version`, `integrity`, and `resolved` fields read against
|
|
36
|
+
registry-reference.
|
|
37
|
+
|
|
38
|
+
1. mesh-parser@4.3.1 (lockfile-diff).
|
|
39
|
+
2. flux-cache 2.1.0 -> 2.2.0 (lockfile-diff).
|
|
40
|
+
3. lru-ring@1.0.4, newly added (lockfile-diff).
|
|
41
|
+
4. torch-logger 3.0.0 -> 3.1.0 (lockfile-diff).
|
|
42
|
+
5. Classify the repo `postinstall` script (install-scripts, package-manifest) as
|
|
43
|
+
MALICIOUS or BENIGN, with evidence.
|
|
44
|
+
6. Classify the CI bootstrap step that pipes a downloaded script into a shell
|
|
45
|
+
(ci-pipeline) as MALICIOUS or BENIGN, with evidence.
|
|
46
|
+
7. Name the single tampered dependency, state the EXACT tell that proves substitution
|
|
47
|
+
(the specific combination of fields), and give the containment steps. Remediation
|
|
48
|
+
only — no exploit steps.
|
|
49
|
+
artifacts:
|
|
50
|
+
- id: lockfile-diff
|
|
51
|
+
type: diff
|
|
52
|
+
label: torchlight.lock diff under review (four changed entries)
|
|
53
|
+
content: |
|
|
54
|
+
--- a/torchlight.lock
|
|
55
|
+
+++ b/torchlight.lock
|
|
56
|
+
@@ mesh-parser @@
|
|
57
|
+
- "mesh-parser@4.3.1":
|
|
58
|
+
- version: "4.3.1"
|
|
59
|
+
- resolved: "https://registry.torchworks.dev/mesh-parser/-/mesh-parser-4.3.1.tgz"
|
|
60
|
+
- integrity: "sha512-OLDmeshHASH0000AAAA=="
|
|
61
|
+
+ "mesh-parser@4.3.1":
|
|
62
|
+
+ version: "4.3.1"
|
|
63
|
+
+ resolved: "https://registry.torchworks-mirror.io/mesh-parser/-/mesh-parser-4.3.1.tgz"
|
|
64
|
+
+ integrity: "sha512-NEWmeshHASH9999BBBB=="
|
|
65
|
+
@@ flux-cache @@
|
|
66
|
+
- "flux-cache@2.1.0":
|
|
67
|
+
- version: "2.1.0"
|
|
68
|
+
- resolved: "https://registry.torchworks.dev/flux-cache/-/flux-cache-2.1.0.tgz"
|
|
69
|
+
- integrity: "sha512-fluxHASH1111CCCC=="
|
|
70
|
+
+ "flux-cache@2.2.0":
|
|
71
|
+
+ version: "2.2.0"
|
|
72
|
+
+ resolved: "https://registry.torchworks.dev/flux-cache/-/flux-cache-2.2.0.tgz"
|
|
73
|
+
+ integrity: "sha512-fluxHASH2222DDDD=="
|
|
74
|
+
@@ lru-ring (newly added) @@
|
|
75
|
+
+ "lru-ring@1.0.4":
|
|
76
|
+
+ version: "1.0.4"
|
|
77
|
+
+ resolved: "https://registry.torchworks.dev/lru-ring/-/lru-ring-1.0.4.tgz"
|
|
78
|
+
+ integrity: "sha512-lruringHASH3333EEEE=="
|
|
79
|
+
+ # pulled in as a dependency of flux-cache@2.2.0
|
|
80
|
+
@@ torch-logger @@
|
|
81
|
+
- "torch-logger@3.0.0":
|
|
82
|
+
- version: "3.0.0"
|
|
83
|
+
- resolved: "https://registry.torchworks.dev/torch-logger/-/torch-logger-3.0.0.tgz"
|
|
84
|
+
- integrity: "sha512-tlogHASH4444FFFF=="
|
|
85
|
+
+ "torch-logger@3.1.0":
|
|
86
|
+
+ version: "3.1.0"
|
|
87
|
+
+ resolved: "https://registry.torchworks.dev/torch-logger/-/torch-logger-3.1.0.tgz"
|
|
88
|
+
+ integrity: "sha512-tlogHASH5555GGGG=="
|
|
89
|
+
|
|
90
|
+
(Integrity strings above are placeholders; what matters is whether each value
|
|
91
|
+
changed and whether the version and resolved host changed with it.)
|
|
92
|
+
- id: package-manifest
|
|
93
|
+
type: config
|
|
94
|
+
label: torchlight package manifest (declared direct dependencies and ranges)
|
|
95
|
+
content: |
|
|
96
|
+
{
|
|
97
|
+
"name": "torchlight",
|
|
98
|
+
"version": "5.2.0",
|
|
99
|
+
"dependencies": {
|
|
100
|
+
"mesh-parser": "4.3.1",
|
|
101
|
+
"flux-cache": "^2.1.0",
|
|
102
|
+
"torch-logger": "^3.0.0"
|
|
103
|
+
},
|
|
104
|
+
"devDependencies": {
|
|
105
|
+
"torch-build": "1.4.0"
|
|
106
|
+
},
|
|
107
|
+
"scripts": {
|
|
108
|
+
"postinstall": "node ./node_modules/torch-build/bin/link-native.js"
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Notes for reviewers:
|
|
113
|
+
// - mesh-parser is pinned to the EXACT version 4.3.1 (no caret): nothing in this
|
|
114
|
+
// manifest asks for it to be re-resolved to different bytes.
|
|
115
|
+
// - flux-cache "^2.1.0" legitimately permits resolving 2.2.0.
|
|
116
|
+
// - torch-logger "^3.0.0" legitimately permits resolving 3.1.0.
|
|
117
|
+
// - lru-ring is NOT a direct dependency; it can only appear transitively.
|
|
118
|
+
// - the postinstall runs torch-build, a first-party devDependency (see dependency-note).
|
|
119
|
+
- id: registry-reference
|
|
120
|
+
type: spec
|
|
121
|
+
label: Torchlight dependency-provenance policy (authoritative and complete)
|
|
122
|
+
content: |
|
|
123
|
+
Torchlight dependency-provenance policy — AUTHORITATIVE and COMPLETE.
|
|
124
|
+
|
|
125
|
+
Canonical registry hosts (the ONLY hosts a Torchlight package may resolve from):
|
|
126
|
+
1. registry.torchworks.dev (primary registry)
|
|
127
|
+
2. mirror.torchworks.dev (documented read-through mirror; same content-addressed store)
|
|
128
|
+
|
|
129
|
+
Rules:
|
|
130
|
+
- Every `resolved` URL in the lockfile MUST use one of the two hosts above. Any
|
|
131
|
+
other host is NON-CANONICAL by definition and must be treated as an untrusted
|
|
132
|
+
substitution, no matter how similar the hostname looks.
|
|
133
|
+
- The registry is CONTENT-ADDRESSED: a given package version always maps to the
|
|
134
|
+
same bytes. Therefore, if a package's `version` is UNCHANGED, its `integrity`
|
|
135
|
+
hash MUST NOT change. A hash change with an unchanged version means different
|
|
136
|
+
bytes were served for the same version — i.e., a substituted artifact.
|
|
137
|
+
- A `version` change legitimately comes WITH a new `integrity` hash (different
|
|
138
|
+
bytes = a new release). That alone is normal, not tampering.
|
|
139
|
+
- A newly appearing package that is the declared transitive child of a legitimately
|
|
140
|
+
updated parent, resolved from a canonical host, is expected — not an injection.
|
|
141
|
+
|
|
142
|
+
This list of canonical hosts is exhaustive: there are no other approved hosts, no
|
|
143
|
+
staging hosts, and no CDN aliases beyond the two above. `registry.torchworks-mirror.io`
|
|
144
|
+
is NOT on this list and is not affiliated with Torchworks.
|
|
145
|
+
- id: ci-pipeline
|
|
146
|
+
type: config
|
|
147
|
+
label: torchlight CI pipeline (bootstrap + install + test)
|
|
148
|
+
content: |
|
|
149
|
+
# .torchlight-ci.yml
|
|
150
|
+
steps:
|
|
151
|
+
- name: fetch-build-tool
|
|
152
|
+
# The bootstrap script is pulled from the FIRST-PARTY artifact store
|
|
153
|
+
# (artifacts.torchworks.dev, see dependency-note) and is PINNED by sha256 digest.
|
|
154
|
+
# sha256sum -c fails the build if the bytes don't match, so the script is verified
|
|
155
|
+
# BEFORE it is ever executed.
|
|
156
|
+
run: |
|
|
157
|
+
curl -sSfL https://artifacts.torchworks.dev/bootstrap/v9/setup.sh -o setup.sh
|
|
158
|
+
echo "3f8a...pinned-sha256...d21c setup.sh" | sha256sum -c -
|
|
159
|
+
bash setup.sh
|
|
160
|
+
- name: install
|
|
161
|
+
run: torch install --frozen-lockfile
|
|
162
|
+
- name: test
|
|
163
|
+
run: torch test
|
|
164
|
+
- id: install-scripts
|
|
165
|
+
type: code
|
|
166
|
+
label: torch-build postinstall (link-native.js) — the repo's postinstall target
|
|
167
|
+
content: |
|
|
168
|
+
// node_modules/torch-build/bin/link-native.js
|
|
169
|
+
// Runs as the repo's postinstall (see package-manifest scripts.postinstall).
|
|
170
|
+
// torch-build is the FIRST-PARTY build tool (devDependency; see dependency-note).
|
|
171
|
+
// This script performs NO network I/O and spawns NO subprocess: it only symlinks
|
|
172
|
+
// prebuilt native addons that already ship inside the torch-build package.
|
|
173
|
+
const fs = require('fs');
|
|
174
|
+
const path = require('path');
|
|
175
|
+
|
|
176
|
+
const prebuilt = path.resolve(__dirname, '..', 'prebuilt');
|
|
177
|
+
const outDir = path.join(process.cwd(), 'native');
|
|
178
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
179
|
+
|
|
180
|
+
for (const file of fs.readdirSync(prebuilt)) {
|
|
181
|
+
const src = path.join(prebuilt, file);
|
|
182
|
+
const dst = path.join(outDir, file);
|
|
183
|
+
fs.rmSync(dst, { force: true });
|
|
184
|
+
fs.symlinkSync(src, dst); // local symlink only; no download, no exec
|
|
185
|
+
}
|
|
186
|
+
console.log('[torch-build] linked', fs.readdirSync(prebuilt).length, 'native addons');
|
|
187
|
+
// No require of http/https/net/dgram/child_process; no fetch; no eval.
|
|
188
|
+
- id: dependency-note
|
|
189
|
+
type: note
|
|
190
|
+
label: Torchlight maintainer dependency notes (current)
|
|
191
|
+
content: |
|
|
192
|
+
Torchlight dependency notes (maintainer-authored, current).
|
|
193
|
+
|
|
194
|
+
- torch-build (devDependency 1.4.0): our FIRST-PARTY build tool, authored in-house and
|
|
195
|
+
published to registry.torchworks.dev. Its postinstall (bin/link-native.js) only
|
|
196
|
+
symlinks prebuilt native addons bundled inside the package; it makes NO network
|
|
197
|
+
requests and spawns NO subprocesses. Reviewed and expected.
|
|
198
|
+
- flux-cache 2.2.0: a routine upstream patch we chose to pull in (manifest "^2.1.0"
|
|
199
|
+
permits it). The 2.2.0 release adds ONE new transitive dependency, lru-ring@1.0.4,
|
|
200
|
+
its LRU-eviction helper. lru-ring is EXPECTED to appear in the lockfile as a child
|
|
201
|
+
of flux-cache 2.2.0, resolved from the canonical registry; it is not "injected."
|
|
202
|
+
- torch-logger 3.1.0: a routine patch bump ("^3.0.0" permits it), from the canonical
|
|
203
|
+
registry.
|
|
204
|
+
- artifacts.torchworks.dev is our FIRST-PARTY build-artifact store (same trust domain
|
|
205
|
+
as the registry). The CI bootstrap script is served from there and pinned by sha256.
|
|
206
|
+
- mesh-parser is pinned to EXACT 4.3.1 and has had NO new release; there is no
|
|
207
|
+
legitimate reason for its resolved host or its integrity hash to change.
|
|
208
|
+
|
|
209
|
+
Provenance authority: registry-reference lists the only canonical registry hosts and
|
|
210
|
+
the version-to-integrity rule. Anything conflicting with it is the anomaly to chase.
|
|
211
|
+
- id: build-log
|
|
212
|
+
type: log
|
|
213
|
+
label: torchlight CI install log for the build that triggered this review (build #812)
|
|
214
|
+
content: |
|
|
215
|
+
torch install --frozen-lockfile (build #812 — the build that triggered this review)
|
|
216
|
+
|
|
217
|
+
resolving 41 packages from lockfile...
|
|
218
|
+
mesh-parser@4.3.1 <- https://registry.torchworks-mirror.io/mesh-parser/-/mesh-parser-4.3.1.tgz
|
|
219
|
+
flux-cache@2.2.0 <- https://registry.torchworks.dev/flux-cache/-/flux-cache-2.2.0.tgz
|
|
220
|
+
lru-ring@1.0.4 <- https://registry.torchworks.dev/lru-ring/-/lru-ring-1.0.4.tgz
|
|
221
|
+
torch-logger@3.1.0 <- https://registry.torchworks.dev/torch-logger/-/torch-logger-3.1.0.tgz
|
|
222
|
+
... 37 more packages, all from registry.torchworks.dev ...
|
|
223
|
+
WARN mesh-parser@4.3.1 resolved host differs from previous builds (was registry.torchworks.dev)
|
|
224
|
+
running postinstall: node ./node_modules/torch-build/bin/link-native.js
|
|
225
|
+
[torch-build] linked 3 native addons
|
|
226
|
+
install completed in 6.2s
|
|
227
|
+
tags: [supply-chain, lockfile, dependency, security]
|