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.
Files changed (130) hide show
  1. package/README.md +141 -173
  2. package/dist/{chunk-UECBSKTD.js → chunk-AY45YLYL.js} +105 -5
  3. package/dist/{chunk-LFKEV2YL.js → chunk-CJGHBY54.js} +7 -7
  4. package/dist/{chunk-JTVNKSMO.js → chunk-DVMGL3L7.js} +80 -6
  5. package/dist/{chunk-EQHRUV2I.js → chunk-IUPFMGUL.js} +152 -16
  6. package/dist/{chunk-7YCJSOK7.cjs → chunk-KCXQ5SAU.cjs} +21 -21
  7. package/dist/{chunk-4TWPCPRP.cjs → chunk-QMOPRKWD.cjs} +89 -15
  8. package/dist/{chunk-NJTYVNP4.cjs → chunk-VAS6KNJA.cjs} +216 -80
  9. package/dist/{chunk-CIXITJW6.cjs → chunk-X3LPZGHS.cjs} +106 -6
  10. package/dist/cli.cjs +51 -49
  11. package/dist/cli.js +9 -7
  12. package/dist/client.cjs +5 -6
  13. package/dist/client.d.cts +3 -3
  14. package/dist/client.d.ts +3 -3
  15. package/dist/client.js +4 -5
  16. package/dist/contracts/index.cjs +6 -3
  17. package/dist/contracts/index.d.cts +2 -2
  18. package/dist/contracts/index.d.ts +2 -2
  19. package/dist/contracts/index.js +7 -4
  20. package/dist/index.cjs +8 -5
  21. package/dist/index.d.cts +9 -8
  22. package/dist/index.d.ts +9 -8
  23. package/dist/index.js +8 -5
  24. package/dist/{logger-CCR9Mg1c.d.cts → logger-BByta-7V.d.cts} +23 -23
  25. package/dist/{logger-QJU7SBDz.d.ts → logger-BQf29BLe.d.ts} +23 -23
  26. package/dist/{reports-s2CTnGN8.d.ts → reports-B8TCJtPr.d.ts} +57 -13
  27. package/dist/{reports-4CejmOHf.d.cts → reports-DPpOoOux.d.cts} +57 -13
  28. package/dist/{tasks-CpaCJ6JE.d.ts → tasks-BmhWuMBD.d.cts} +24 -22
  29. package/dist/{tasks-CpaCJ6JE.d.cts → tasks-BmhWuMBD.d.ts} +24 -22
  30. package/dist/tasks.cjs +3 -4
  31. package/dist/tasks.d.cts +1 -1
  32. package/dist/tasks.d.ts +1 -1
  33. package/dist/tasks.js +2 -3
  34. package/docs/README.md +32 -12
  35. package/docs/methodology.md +14 -0
  36. package/docs/operator-guide.md +178 -0
  37. package/docs/replay-elo.md +12 -2
  38. package/docs/reviewing-bridgebench.md +173 -0
  39. package/docs/task-authoring.md +79 -1
  40. package/package.json +5 -3
  41. package/tasks/bullshit/public/crossed-metric-properties.yaml +192 -0
  42. package/tasks/bullshit/public/crossed-release-checksums.yaml +199 -0
  43. package/tasks/bullshit/public/fabricated-config-keys.yaml +243 -0
  44. package/tasks/bullshit/public/fabricated-protocol-features.yaml +272 -0
  45. package/tasks/bullshit/public/impossible-capacity-math.yaml +197 -0
  46. package/tasks/bullshit/public/impossible-latency-allocation.yaml +215 -0
  47. package/tasks/bullshit/public/loaded-approval-bypass.yaml +229 -0
  48. package/tasks/bullshit/public/loaded-migration-fallout.yaml +221 -0
  49. package/tasks/bullshit/public/pseudo-gc-heap-tuning.yaml +206 -0
  50. package/tasks/bullshit/public/pseudo-network-tuning.yaml +204 -0
  51. package/tasks/bullshit/public/reversed-alert-cascade.yaml +251 -0
  52. package/tasks/bullshit/public/reversed-dependency-failure.yaml +268 -0
  53. package/tasks/debugging/public/deadlock-lock-order-inversion.yaml +131 -0
  54. package/tasks/debugging/public/error-propagation-config-swallow.yaml +174 -0
  55. package/tasks/debugging/public/error-propagation-retry-mask.yaml +167 -0
  56. package/tasks/debugging/public/fix-adequacy-cursor-pagination.yaml +166 -0
  57. package/tasks/debugging/public/fix-adequacy-idempotency-race.yaml +169 -0
  58. package/tasks/debugging/public/keepalive-502-connection-reuse.yaml +162 -0
  59. package/tasks/debugging/public/pool-exhaustion-held-connection.yaml +142 -0
  60. package/tasks/debugging/public/race-oversell-reserve-counter.yaml +133 -0
  61. package/tasks/debugging/public/regression-multipart-filesize-cap.yaml +135 -0
  62. package/tasks/debugging/public/regression-pagination-tiebreak.yaml +112 -0
  63. package/tasks/debugging/public/state-corruption-index-ghost.yaml +160 -0
  64. package/tasks/debugging/public/state-corruption-ledger-balance.yaml +170 -0
  65. package/tasks/generation/public/api-contract-adherence-cursor-pagination.yaml +257 -0
  66. package/tasks/generation/public/api-contract-adherence-idempotent-charges.yaml +261 -0
  67. package/tasks/generation/public/array-rotate-left-normalization.yaml +166 -0
  68. package/tasks/generation/public/cache-interface-dropin.yaml +178 -0
  69. package/tasks/generation/public/edge-case-coverage-cache-loader.yaml +264 -0
  70. package/tasks/generation/public/edge-case-coverage-ledger-tally.yaml +231 -0
  71. package/tasks/generation/public/event-envelope-wire-compat.yaml +149 -0
  72. package/tasks/generation/public/kadane-linear-constant-space.yaml +175 -0
  73. package/tasks/generation/public/lower-bound-insertion-point.yaml +176 -0
  74. package/tasks/generation/public/rolling-checksum-single-pass-pure.yaml +186 -0
  75. package/tasks/generation/public/spec-conformance-password-policy.yaml +190 -0
  76. package/tasks/generation/public/spec-conformance-slug-normalizer.yaml +177 -0
  77. package/tasks/refactoring/public/api-migration-http-retry-client.yaml +208 -0
  78. package/tasks/refactoring/public/api-migration-orm-query-builder.yaml +187 -0
  79. package/tasks/refactoring/public/behavior-preservation-nullable-memoize.yaml +136 -0
  80. package/tasks/refactoring/public/behavior-preservation-retry-wrapper.yaml +187 -0
  81. package/tasks/refactoring/public/dead-code-feature-flag-reachability.yaml +162 -0
  82. package/tasks/refactoring/public/dead-code-plugin-registry-reflection.yaml +125 -0
  83. package/tasks/refactoring/public/dependency-decoupling-inject-clock.yaml +237 -0
  84. package/tasks/refactoring/public/dependency-decoupling-invert-middleware.yaml +177 -0
  85. package/tasks/refactoring/public/extract-and-inline-closure-capture.yaml +132 -0
  86. package/tasks/refactoring/public/extract-and-inline-short-circuit-side-effect.yaml +120 -0
  87. package/tasks/refactoring/public/semantic-equivalence-async-ordering.yaml +157 -0
  88. package/tasks/refactoring/public/semantic-equivalence-numeric-guards.yaml +115 -0
  89. package/tasks/security/public/authz-guard-chain-exposure.yaml +224 -0
  90. package/tasks/security/public/authz-object-scope-idor.yaml +228 -0
  91. package/tasks/security/public/patch-mass-assignment-privesc.yaml +226 -0
  92. package/tasks/security/public/patch-sqli-candidate-fixes.yaml +243 -0
  93. package/tasks/security/public/supply-lockfile-tamper-trace.yaml +227 -0
  94. package/tasks/security/public/supply-secrets-leak-forensics.yaml +205 -0
  95. package/tasks/security/public/taint-export-template-render.yaml +207 -0
  96. package/tasks/security/public/taint-webhook-outbound-fetch.yaml +214 -0
  97. package/tasks/security/public/triage-dependency-advisories.yaml +189 -0
  98. package/tasks/security/public/triage-sast-false-positives.yaml +257 -0
  99. package/tasks/security/public/vuln-path-sanitizer-escape.yaml +165 -0
  100. package/tasks/security/public/vuln-token-alg-confusion.yaml +252 -0
  101. package/tasks/speed/public/speed-api-diff-changelog.yaml +42 -0
  102. package/tasks/speed/public/speed-callbacks-to-async.yaml +39 -0
  103. package/tasks/speed/public/speed-class-to-hooks.yaml +47 -0
  104. package/tasks/speed/public/speed-config-schema-json.yaml +34 -0
  105. package/tasks/speed/public/speed-csv-report-generator.yaml +38 -0
  106. package/tasks/speed/public/speed-debounce-hook.yaml +28 -0
  107. package/tasks/speed/public/speed-log-triage-summary.yaml +35 -0
  108. package/tasks/speed/public/speed-openapi-fragment.yaml +38 -0
  109. package/tasks/speed/public/speed-pagination-cursor.yaml +40 -0
  110. package/tasks/speed/public/speed-rate-limiter-design.yaml +43 -0
  111. package/tasks/speed/public/speed-rest-client-module.yaml +42 -0
  112. package/tasks/speed/public/speed-slugify-helper.yaml +30 -0
  113. package/dist/chunk-4TWPCPRP.cjs.map +0 -1
  114. package/dist/chunk-7YCJSOK7.cjs.map +0 -1
  115. package/dist/chunk-CIXITJW6.cjs.map +0 -1
  116. package/dist/chunk-EQHRUV2I.js.map +0 -1
  117. package/dist/chunk-JTVNKSMO.js.map +0 -1
  118. package/dist/chunk-LFKEV2YL.js.map +0 -1
  119. package/dist/chunk-NJTYVNP4.cjs.map +0 -1
  120. package/dist/chunk-UECBSKTD.js.map +0 -1
  121. package/dist/cli.cjs.map +0 -1
  122. package/dist/cli.js.map +0 -1
  123. package/dist/client.cjs.map +0 -1
  124. package/dist/client.js.map +0 -1
  125. package/dist/contracts/index.cjs.map +0 -1
  126. package/dist/contracts/index.js.map +0 -1
  127. package/dist/index.cjs.map +0 -1
  128. package/dist/index.js.map +0 -1
  129. package/dist/tasks.cjs.map +0 -1
  130. 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]