bridgebench 3.1.0-alpha.1 → 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 (112) hide show
  1. package/README.md +141 -173
  2. package/dist/{chunk-UXFKTSQS.js → chunk-AY45YLYL.js} +105 -4
  3. package/dist/{chunk-MBC4C3VA.js → chunk-CJGHBY54.js} +7 -6
  4. package/dist/{chunk-3CX4FIFZ.js → chunk-DVMGL3L7.js} +80 -5
  5. package/dist/{chunk-KGZEQ6R5.js → chunk-IUPFMGUL.js} +152 -15
  6. package/dist/{chunk-XPFZBHV2.cjs → chunk-KCXQ5SAU.cjs} +21 -20
  7. package/dist/{chunk-SMG3PORS.cjs → chunk-QMOPRKWD.cjs} +89 -14
  8. package/dist/{chunk-5OZGHLDH.cjs → chunk-VAS6KNJA.cjs} +216 -79
  9. package/dist/{chunk-5PCDG7VA.cjs → chunk-X3LPZGHS.cjs} +106 -5
  10. package/dist/cli.cjs +51 -48
  11. package/dist/cli.js +9 -6
  12. package/dist/client.cjs +5 -5
  13. package/dist/client.d.cts +3 -3
  14. package/dist/client.d.ts +3 -3
  15. package/dist/client.js +4 -4
  16. package/dist/contracts/index.cjs +6 -2
  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 -3
  20. package/dist/index.cjs +8 -4
  21. package/dist/index.d.cts +9 -8
  22. package/dist/index.d.ts +9 -8
  23. package/dist/index.js +8 -4
  24. package/dist/{logger-QJU7SBDz.d.ts → logger-BByta-7V.d.cts} +23 -23
  25. package/dist/{logger-CCR9Mg1c.d.cts → logger-BQf29BLe.d.ts} +23 -23
  26. package/dist/{reports-4CejmOHf.d.cts → reports-B8TCJtPr.d.ts} +57 -13
  27. package/dist/{reports-s2CTnGN8.d.ts → reports-DPpOoOux.d.cts} +57 -13
  28. package/dist/{tasks-CpaCJ6JE.d.cts → tasks-BmhWuMBD.d.cts} +24 -22
  29. package/dist/{tasks-CpaCJ6JE.d.ts → tasks-BmhWuMBD.d.ts} +24 -22
  30. package/dist/tasks.cjs +3 -3
  31. package/dist/tasks.d.cts +1 -1
  32. package/dist/tasks.d.ts +1 -1
  33. package/dist/tasks.js +2 -2
  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
@@ -0,0 +1,157 @@
1
+ id: semantic-equivalence-async-ordering
2
+ version: 1.0.0
3
+ category: refactoring
4
+ cluster: semantic-equivalence
5
+ difficulty: expert
6
+ title: Are these six async refactors behavior-preserving under Promise ordering and error semantics?
7
+ summary: Six async functions each get a rewrite that changes how promises are sequenced. Some are equivalent; others diverge on ordering of observable side effects, how many metered calls run on the error path, whether a non-idempotent write always fires, or whether rejections still propagate. Using the cited Promise semantics and the declared side effects, classify each rewrite as behavior-preserving or changes-behavior.
8
+ prompt: |
9
+ A refactor PR rewrites six async functions (originals A1..A6) into forms that sequence their
10
+ promises differently (proposed B1..B6). Judge each rewrite for FULL observable equivalence across
11
+ BOTH the success path and the error path, accounting for the declared side effects.
12
+
13
+ Answer each deliverable using ONLY the supplied artifacts (async-spec, side-effects, originals,
14
+ proposed). Cite the artifact id(s) for every claim; ground each divergence in async-spec and in
15
+ the observable side effect declared in side-effects.
16
+
17
+ For each rewrite below, classify it as exactly one of:
18
+ - behavior-preserving (same observable behavior as the original on every path: same results,
19
+ same ordering of observable side effects, same set of calls actually
20
+ made, same rejection/throw behavior)
21
+ - changes-behavior (differs on some path; name the single divergence and a condition that
22
+ witnesses it)
23
+
24
+ 1. Classify B1 (rewrite of A1).
25
+ 2. Classify B2 (rewrite of A2).
26
+ 3. Classify B3 (rewrite of A3).
27
+ 4. Classify B4 (rewrite of A4).
28
+ 5. Classify B5 (rewrite of A5).
29
+ 6. Classify B6 (rewrite of A6).
30
+
31
+ Exactly one classification is defensible per deliverable; it is fully determined by the async
32
+ semantics and the declared side effects.
33
+ artifacts:
34
+ - id: async-spec
35
+ type: spec
36
+ label: Promise / async semantics (authoritative for this task)
37
+ content: |
38
+ Semantics you must apply:
39
+
40
+ A. `await x` suspends the async function until x settles. Statements after it run ONLY after x
41
+ resolves. If x rejects, execution stops there as if `throw` were reached and the function's
42
+ returned promise rejects; later statements do not run.
43
+
44
+ B. Sequential awaits `await f(); await g();` invoke g() only AFTER f() has resolved. If f()
45
+ rejects, g() is never invoked.
46
+
47
+ C. `Promise.all([e1, e2, ...])`: the array is built EAGERLY, so every expression e1, e2, ...
48
+ is invoked immediately (all underlying operations start concurrently, before any awaiting).
49
+ It resolves to results in INPUT ORDER once all resolve. It rejects as soon as ANY input
50
+ rejects, with that first rejection; the other already-started promises keep running (their
51
+ side effects still occur) and their results are discarded.
52
+
53
+ D. `Promise.allSettled([...])` NEVER rejects; it resolves once all inputs settle, to an array
54
+ of { status, value | reason }.
55
+
56
+ E. `arr.map(fn)` invokes fn for every element eagerly during the map; for an async fn, all
57
+ calls start concurrently.
58
+
59
+ F. Operand order of `+` is left-to-right: in `(await f()) + (await g())`, f() is invoked and
60
+ its await fully resolves BEFORE g() is invoked — identical ordering to two sequential
61
+ `const` awaits.
62
+
63
+ G. `p.then(cb)` returns a promise that resolves to cb's return value, or rejects if p rejects
64
+ or cb throws. `const u = await p; return cb(u);` has the SAME settle behavior (same
65
+ resolution value, same rejection).
66
+ - id: side-effects
67
+ type: note
68
+ label: observable side effects of the helper calls (authoritative)
69
+ content: |
70
+ What is observable about each helper (this determines what "same behavior" requires):
71
+
72
+ - log(x): appends x to an append-only audit stream. The server assigns each entry a sequence
73
+ number BY ARRIVAL ORDER, so the order in which log() calls arrive is OBSERVABLE. log()
74
+ returns nothing meaningful.
75
+ - fetchOne(id): a METERED network GET. Each invocation consumes one unit of a rate quota and
76
+ is recorded, so the NUMBER of fetchOne() invocations is OBSERVABLE. It may reject.
77
+ - exists(id): a read with no observable side effect. create(id): INSERTS a row; it is
78
+ non-idempotent — a second create for an id that already exists is an observable duplicate/
79
+ error. Whether create() runs is OBSERVABLE.
80
+ - save(item): a write that may reject. Whether the enclosing function THROWS to its caller is
81
+ OBSERVABLE.
82
+ - getBase(id), getBonus(id), getUser(id): pure reads; no ordering-sensitive side effects.
83
+ - id: originals
84
+ type: code
85
+ label: original async functions (A1..A6)
86
+ content: |
87
+ // A1 — write two audit entries in order
88
+ async function audit(a: string, b: string): Promise<void> {
89
+ await log(a);
90
+ await log(b);
91
+ }
92
+
93
+ // A2 — sum of two fetched amounts
94
+ async function total(id: string): Promise<number> {
95
+ return (await getBase(id)) + (await getBonus(id));
96
+ }
97
+
98
+ // A3 — load every record; stop at the first failure
99
+ async function loadAll(ids: string[]): Promise<Rec[]> {
100
+ const out: Rec[] = [];
101
+ for (const id of ids) out.push(await fetchOne(id));
102
+ return out;
103
+ }
104
+
105
+ // A4 — fetch a user's name
106
+ function fetchName(id: string): Promise<string> {
107
+ return getUser(id).then((u) => u.name);
108
+ }
109
+
110
+ // A5 — create the record only if it does not already exist
111
+ async function ensure(id: string): Promise<void> {
112
+ if (await exists(id)) return;
113
+ await create(id);
114
+ }
115
+
116
+ // A6 — persist every item; fail if any save fails
117
+ async function saveAll(items: Item[]): Promise<void> {
118
+ await Promise.all(items.map(save));
119
+ }
120
+ - id: proposed
121
+ type: code
122
+ label: proposed async rewrites (B1..B6)
123
+ content: |
124
+ // B1 — rewrite of A1
125
+ async function audit(a: string, b: string): Promise<void> {
126
+ await Promise.all([log(a), log(b)]);
127
+ }
128
+
129
+ // B2 — rewrite of A2
130
+ async function total(id: string): Promise<number> {
131
+ const base = await getBase(id);
132
+ const bonus = await getBonus(id);
133
+ return base + bonus;
134
+ }
135
+
136
+ // B3 — rewrite of A3
137
+ async function loadAll(ids: string[]): Promise<Rec[]> {
138
+ return Promise.all(ids.map((id) => fetchOne(id)));
139
+ }
140
+
141
+ // B4 — rewrite of A4
142
+ async function fetchName(id: string): Promise<string> {
143
+ const u = await getUser(id);
144
+ return u.name;
145
+ }
146
+
147
+ // B5 — rewrite of A5
148
+ async function ensure(id: string): Promise<void> {
149
+ const [present] = await Promise.all([exists(id), create(id)]);
150
+ if (present) return;
151
+ }
152
+
153
+ // B6 — rewrite of A6
154
+ async function saveAll(items: Item[]): Promise<void> {
155
+ await Promise.allSettled(items.map(save));
156
+ }
157
+ tags: [semantic-equivalence, refactoring, async, promises, ordering]
@@ -0,0 +1,115 @@
1
+ id: semantic-equivalence-numeric-guards
2
+ version: 1.1.0
3
+ category: refactoring
4
+ cluster: semantic-equivalence
5
+ difficulty: expert
6
+ title: Are these six JavaScript simplifications behavior-preserving across all inputs?
7
+ summary: Six small pure functions each have a proposed one-line simplification. Some are equivalent for every input; others break on a specific edge case (falsy zero vs nullish, 32-bit shift truncation, IEEE-754 rounding and overflow, floor vs truncation of negatives). Using the cited language semantics, classify each rewrite as behavior-preserving or changes-behavior and give a witness input for every change.
8
+ prompt: |
9
+ A "simplify" PR rewrites six pure JavaScript functions (originals O1..O6) into shorter forms
10
+ (proposed P1..P6). Judge each rewrite for FULL semantic equivalence: it is behavior-preserving
11
+ only if it returns the same value as the original for EVERY input in the parameter's type, with
12
+ the same numeric result and sign. Do not assume a narrower input domain than the type allows.
13
+
14
+ Answer each deliverable using ONLY the supplied artifacts (language-spec, originals, proposed).
15
+ Cite the artifact id(s) for every claim; ground each edge case in the language-spec.
16
+
17
+ For each rewrite below, classify it as exactly one of:
18
+ - behavior-preserving (equal to the original for every input)
19
+ - changes-behavior (differs on some input; give a concrete witness input and the resulting
20
+ two values, and cite the language-spec rule that explains the divergence)
21
+
22
+ 1. Classify P1 (rewrite of O1).
23
+ 2. Classify P2 (rewrite of O2).
24
+ 3. Classify P3 (rewrite of O3).
25
+ 4. Classify P4 (rewrite of O4).
26
+ 5. Classify P5 (rewrite of O5).
27
+ 6. Classify P6 (rewrite of O6).
28
+
29
+ Exactly one classification is defensible per deliverable; it is fully determined by the language
30
+ semantics in language-spec applied to the two forms.
31
+ artifacts:
32
+ - id: language-spec
33
+ type: spec
34
+ label: relevant JavaScript evaluation semantics (authoritative for this task)
35
+ content: |
36
+ Semantics you must apply (these govern every deliverable):
37
+
38
+ A. Logical OR `a || b` -> returns `a` if `a` is TRUTHY, otherwise returns `b`.
39
+ The FALSY values are exactly: false, 0, -0, 0n, "", null, undefined, NaN.
40
+
41
+ B. Nullish coalescing `a ?? b` -> returns `a` UNLESS `a` is null or undefined, in which case
42
+ it returns `b`. 0, -0, "", NaN, and false are NOT nullish, so `a ?? b` returns them.
43
+
44
+ C. Logical AND `a && b` -> returns `a` if `a` is falsy, otherwise returns `b`.
45
+ Logical NOT `!x` -> a boolean, true iff `x` is falsy. Consequently the De Morgan identity
46
+ `!(a && b)` === `!a || !b` holds as booleans for all `a`, `b`.
47
+
48
+ D. Left shift `x << k` -> `x` is first converted with ToInt32: any fractional part is
49
+ truncated toward zero and the value is reduced modulo 2^32 into the signed range
50
+ [-2147483648, 2147483647]; THEN it is shifted. The result is a 32-bit signed integer.
51
+
52
+ E. Bitwise OR with zero `x | 0` -> applies ToInt32 to `x` (truncates the fraction toward zero,
53
+ reduces modulo 2^32 into the signed 32-bit range). For non-integers it truncates TOWARD
54
+ ZERO; for |x| >= 2^31 it wraps.
55
+
56
+ F. `Math.floor(x)` -> the greatest integer <= x, i.e. it rounds toward -Infinity. For negative
57
+ non-integers this differs from truncation toward zero (e.g. floor(-1.5) = -2, trunc = -1).
58
+
59
+ G. Numbers are IEEE-754 doubles. `*` and `/` round each intermediate result to the nearest
60
+ representable double; there is no integer wraparound, but a magnitude above
61
+ Number.MAX_VALUE becomes Infinity. Because each operation rounds independently,
62
+ `a/2 + b/2` and `(a + b)/2` are NOT guaranteed to be the same double, and `a + b` can be
63
+ Infinity while `a/2 + b/2` is finite.
64
+
65
+ H. Operand evaluation order for `+`, `&&`, `||`, `??` is left-to-right; `&&`, `||`, and `??`
66
+ short-circuit (do not evaluate the right operand) when the left already determines the
67
+ result.
68
+ - id: originals
69
+ type: code
70
+ label: original functions (O1..O6), each pure
71
+ content: |
72
+ // Each parameter may be ANY value of its annotated type; assume no narrower domain.
73
+
74
+ // O1 — choose the configured port, else default 8080
75
+ function port(p: number): number { return p || 8080; }
76
+
77
+ // O2 — should we skip when NOT both a and b hold
78
+ function shouldSkip(a: unknown, b: unknown): boolean { return !(a && b); }
79
+
80
+ // O3 — double an integer count
81
+ function double(n: number): number { return n * 2; }
82
+
83
+ // O4 — first of two values that is truthy, else the second
84
+ function firstTruthy(a: unknown, b: unknown): unknown {
85
+ if (a) return a;
86
+ return b;
87
+ }
88
+
89
+ // O5 — midpoint of two finite numbers
90
+ function avg(a: number, b: number): number { return a / 2 + b / 2; }
91
+
92
+ // O6 — integer half, rounding down
93
+ function half(n: number): number { return Math.floor(n / 2); }
94
+ - id: proposed
95
+ type: code
96
+ label: proposed simplifications (P1..P6)
97
+ content: |
98
+ // P1 — rewrite of O1
99
+ function port(p: number): number { return p ?? 8080; }
100
+
101
+ // P2 — rewrite of O2
102
+ function shouldSkip(a: unknown, b: unknown): boolean { return !a || !b; }
103
+
104
+ // P3 — rewrite of O3
105
+ function double(n: number): number { return n << 1; }
106
+
107
+ // P4 — rewrite of O4
108
+ function firstTruthy(a: unknown, b: unknown): unknown { return a || b; }
109
+
110
+ // P5 — rewrite of O5
111
+ function avg(a: number, b: number): number { return (a + b) / 2; }
112
+
113
+ // P6 — rewrite of O6
114
+ function half(n: number): number { return (n / 2) | 0; }
115
+ tags: [semantic-equivalence, refactoring, javascript, floating-point, edge-cases]
@@ -0,0 +1,224 @@
1
+ id: authz-guard-chain-exposure
2
+ version: 1.1.0
3
+ category: security
4
+ cluster: authz-boundary
5
+ difficulty: expert
6
+ title: Cobalt API Global Guard Chain — Route Exposure Analysis
7
+ summary: >-
8
+ The Cobalt service protects routes with a global guard chain (authentication →
9
+ role → tier → CSRF) plus per-controller @UseGuards and route decorators.
10
+ Several admin-looking routes appear risky; exactly one is a genuine
11
+ unauthenticated exposure. Classify five named routes against declared intent,
12
+ identify the single real exposure, and state the fix — using only the artifacts.
13
+ prompt: |
14
+ You are reviewing authorization on the "Cobalt" API. Guards are registered
15
+ globally and also, redundantly, per controller; individual routes carry
16
+ decorators like @Public(), @Roles(), @RequireTier(), and @FeatureEnabled().
17
+ You are given seven artifacts: admin-controller, billing-controller,
18
+ public-controller, guard-registration, decorator-reference, feature-flags, and
19
+ exposure-note.
20
+
21
+ Answer each item using ONLY the supplied artifacts. Cite the artifact id(s) you
22
+ rely on for every claim. If something is not defined/reachable/exploitable in
23
+ the artifacts, say so explicitly. Do NOT write exploit code, requests, or
24
+ bypass strings — this is an analysis-only task.
25
+
26
+ For items 1–5, state (a) who can successfully call the route (an unauthenticated
27
+ caller, any authenticated user, an authenticated user of a given tier, an
28
+ authenticated ADMIN, or no one in production), and (b) whether that matches the
29
+ declared intent. Justify every answer by citing the specific decorators on the
30
+ route, the global guard chain and its ORDER, and the relevant declarations.
31
+
32
+ 1. DELETE /admin/workspaces/:id (AdminController.deleteWorkspace)
33
+ 2. POST /admin/keys/rotate (AdminController.rotateSigningKeys)
34
+ 3. GET /billing/invoices (BillingController.getInvoices)
35
+ 4. POST /admin/audit/purge (AdminController.purgeAuditLog)
36
+ 5. GET /public/health (PublicController.health)
37
+
38
+ 6. Exactly one route in this service is an UNINTENDED exposure — reachable by a
39
+ caller who should not reach it. Name that single route and state the
40
+ destructive action it lets that caller trigger. Ground the claim in the
41
+ decorators, the guard chain, and the intended-public declaration.
42
+
43
+ 7. State the one-line fix that closes the exposure from item 6, and explain why
44
+ it restores the intended protection (cite the guard chain / decorator
45
+ semantics).
46
+ artifacts:
47
+ - id: admin-controller
48
+ type: code
49
+ label: cobalt-api/src/admin/admin.controller.ts
50
+ content: |
51
+ // The @UseGuards + @Roles here are class-level; the authentication and role
52
+ // guards are ALSO registered globally (see guard-registration), so these are
53
+ // defense-in-depth. This file lists EVERY route on the /admin controller;
54
+ // there are no other admin routes.
55
+
56
+ @Controller('admin')
57
+ @UseGuards(JwtAuthGuard, RoleGuard) // redundant with the global chain
58
+ @Roles(Role.ADMIN) // class default: /admin is admin-only...
59
+ export class AdminController {
60
+ // Permanently and irreversibly deletes a workspace together with ALL of
61
+ // its projects, members, invitations, and billing history.
62
+ @Delete('workspaces/:id')
63
+ @Public()
64
+ deleteWorkspace(@Param('id') id: string) {
65
+ return this.admin.deleteWorkspace(id);
66
+ }
67
+
68
+ // Rotates the JWT signing keys for the entire tenant.
69
+ @Post('keys/rotate')
70
+ rotateSigningKeys() {
71
+ return this.admin.rotateSigningKeys();
72
+ }
73
+
74
+ // Hard-deletes the append-only audit log. Gated behind a feature flag.
75
+ @Post('audit/purge')
76
+ @FeatureEnabled('beta-shredder')
77
+ purgeAuditLog() {
78
+ return this.admin.purgeAuditLog();
79
+ }
80
+ }
81
+ - id: billing-controller
82
+ type: code
83
+ label: cobalt-api/src/billing/billing.controller.ts
84
+ content: |
85
+ // NOTE: this controller declares NO class-level @UseGuards of its own.
86
+ // This file lists every route on the /billing controller.
87
+
88
+ @Controller('billing')
89
+ export class BillingController {
90
+ // Lists the caller's own invoices. Requires an active PRO (or higher) tier.
91
+ @Get('invoices')
92
+ @RequireTier('PRO')
93
+ getInvoices(@GetUser('id') userId: string) {
94
+ return this.billing.listInvoices(userId);
95
+ }
96
+
97
+ // Opens the billing provider's portal session for the caller.
98
+ @Post('portal-session')
99
+ @RequireTier('BASIC')
100
+ createPortalSession(@GetUser('id') userId: string) {
101
+ return this.billing.createPortalSession(userId);
102
+ }
103
+ }
104
+ - id: public-controller
105
+ type: code
106
+ label: cobalt-api/src/public/public.controller.ts
107
+ content: |
108
+ // This file lists every route on the /public controller.
109
+
110
+ @Controller('public')
111
+ export class PublicController {
112
+ // Liveness probe for the load balancer. Intentionally unauthenticated.
113
+ @Get('health')
114
+ @Public()
115
+ health() {
116
+ return { status: 'ok' };
117
+ }
118
+
119
+ // Pricing table rendered by the unauthenticated marketing site.
120
+ @Get('pricing')
121
+ @Public()
122
+ pricing() {
123
+ return this.catalog.publicPricing();
124
+ }
125
+ }
126
+ - id: guard-registration
127
+ type: code
128
+ label: cobalt-api/src/app.module.ts (global guard registration — complete)
129
+ content: |
130
+ // The COMPLETE set of globally-registered guards. APP_GUARD guards run for
131
+ // EVERY route in the application — including controllers that declare no
132
+ // @UseGuards of their own — in the order they are registered below. There
133
+ // are no other global guards.
134
+
135
+ providers: [
136
+ { provide: APP_GUARD, useClass: JwtAuthGuard }, // 1. authentication
137
+ { provide: APP_GUARD, useClass: RoleGuard }, // 2. role check (needs the
138
+ // principal from step 1)
139
+ { provide: APP_GUARD, useClass: TierGuard }, // 3. subscription tier
140
+ { provide: APP_GUARD, useClass: CsrfGuard }, // 4. CSRF (mutating verbs)
141
+ ];
142
+
143
+ // Ordering guarantee: Nest executes APP_GUARD providers in registration
144
+ // order. Authentication (1) therefore ALWAYS runs before the role check (2),
145
+ // which always runs before the tier check (3). A caller with no valid token
146
+ // is rejected at step 1 and never reaches steps 2–4 — UNLESS the route opts
147
+ // out of authentication via @Public() (see decorator-reference).
148
+ - id: decorator-reference
149
+ type: spec
150
+ label: Cobalt authorization decorator reference (authoritative)
151
+ content: |
152
+ Authoritative meaning of every authorization decorator in Cobalt. This is
153
+ the complete list; no other authorization decorators exist in the codebase.
154
+
155
+ @UseGuards(...guards)
156
+ Attaches controller/route-scoped guards. In Cobalt these DUPLICATE guards
157
+ that are also registered globally (see guard-registration); they are
158
+ defense-in-depth and add no protection beyond the global chain. A
159
+ controller WITHOUT @UseGuards is still fully covered by the global chain.
160
+
161
+ @Public()
162
+ Marks a route (or controller) as NOT requiring authentication. When
163
+ present, the authentication guard (step 1) short-circuits to ALLOW. Because
164
+ no authenticated principal is then established, the role guard (step 2)
165
+ ALSO allows the request — it has no principal to evaluate and treats the
166
+ route as public. Net effect: a @Public() route is reachable by ANY caller,
167
+ with no token and no role, REGARDLESS of any @Roles present on the same
168
+ route or its controller.
169
+
170
+ @Roles(role)
171
+ Requires the authenticated principal to hold `role`. Enforced by the role
172
+ guard (step 2), which runs AFTER authentication (step 1). It therefore can
173
+ never be reached by an unauthenticated caller UNLESS the route is @Public().
174
+ A class-level @Roles applies to every route on the controller that is not
175
+ @Public().
176
+
177
+ @RequireTier(tier)
178
+ Requires the authenticated principal's subscription tier to be `tier` or
179
+ higher (FREE < BASIC < PRO < ULTRA). Enforced by the tier guard (step 3).
180
+ Implies authentication: an unauthenticated caller is rejected at step 1
181
+ before the tier guard runs.
182
+
183
+ @FeatureEnabled(flag)
184
+ The route responds 404 (as though it did not exist) whenever `flag` is not
185
+ enabled in the running environment (see feature-flags). A disabled flag
186
+ makes the route unreachable in that environment.
187
+
188
+ @SkipCsrf()
189
+ Exempts a route from the CSRF guard (step 4) ONLY. It has NO effect on the
190
+ authentication, role, or tier checks.
191
+
192
+ CsrfGuard is NOT an authentication or authorization control. It exists only
193
+ to reject FORGED state-changing requests that ride ambient session
194
+ credentials — e.g. a browser that auto-attaches session cookies — when a
195
+ valid CSRF token is absent. A direct, credential-less API call carries no
196
+ ambient session to forge against, so CsrfGuard does NOT block it, on ANY HTTP
197
+ verb (including DELETE). CsrfGuard can therefore never turn a @Public() route
198
+ back into a protected one: a route left reachable by @Public() is reachable by
199
+ an unauthenticated direct caller regardless of verb.
200
+ - id: feature-flags
201
+ type: config
202
+ label: cobalt feature flags (resolved values, production)
203
+ content: |
204
+ # Resolved feature-flag values. This file is the COMPLETE flag set; any flag
205
+ # not listed here is treated as disabled in every environment.
206
+ flags:
207
+ beta-shredder: { prod: false, staging: true } # audit-log purge (admin)
208
+ new-invoice-pdf: { prod: true, staging: true }
209
+ workspace-export:{ prod: true, staging: true }
210
+ - id: exposure-note
211
+ type: note
212
+ label: Cobalt intended-public route inventory (authoritative)
213
+ content: |
214
+ Intended-public route inventory. The routes below are the ONLY routes in the
215
+ service intended to be reachable WITHOUT authentication. Every other route
216
+ must require a valid token.
217
+
218
+ - GET /public/health (load-balancer liveness probe)
219
+ - GET /public/pricing (marketing pricing table)
220
+
221
+ Any route that carries @Public() but is NOT in this list is a
222
+ misconfiguration: per the decorator-reference, @Public() disables BOTH the
223
+ authentication and the role checks for that route.
224
+ tags: [authz, guards, nestjs, access-control, authz-boundary, security]