wow-secret-lint 1.4.2 → 1.5.0
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/CHANGELOG.md +74 -0
- package/README.md +129 -13
- package/action/index.mjs +4 -4
- package/action.yml +2 -2
- package/bin/wow-secret-lint.mjs +42 -9
- package/data/api-snapshot.json +1 -1
- package/package.json +1 -1
- package/src/analyze.mjs +216 -16
- package/src/apidata.mjs +89 -13
- package/src/index.mjs +26 -8
- package/src/rules.mjs +137 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,79 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.5.0 - 2026-09-07
|
|
4
|
+
|
|
5
|
+
Patch 12.1.5 (build 69594) went live on 2026-09-03. This catches the linter up to it: the
|
|
6
|
+
snapshot, three new rules, and a new default `--patch` surface. `--patch=12.0` and
|
|
7
|
+
`--patch=12.1` produce v1.2.0's and v1.4.2's output byte for byte, which is now enforced by
|
|
8
|
+
recorded baselines rather than asserted.
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **WSL019** (error): querying the progress or running state of an animation that carries
|
|
13
|
+
the new `QueryAnimationProgress` forbidden aspect, which *"prevents querying the progress
|
|
14
|
+
of an animation or whether it is running"*.
|
|
15
|
+
- **WSL020** (error): adding an animation to, or reparenting one onto, a group that carries
|
|
16
|
+
the new `AddAnimations` forbidden aspect, which *"prevents animations from being added or
|
|
17
|
+
reparented"*. The reparenting half is `SimpleAnim:SetParent`, where the aspect is checked
|
|
18
|
+
on the `parent` argument rather than on the receiver.
|
|
19
|
+
- **WSL021** (error): *"the `SetCooldown` and `Clear` cooldown APIs can no longer be called
|
|
20
|
+
from tainted code when the cooldown frame itself is protected."* Blizzard marked six
|
|
21
|
+
`FrameAPICooldown` methods `IsProtectedFunction = true` in 12.1.5 and none in 12.1, so the
|
|
22
|
+
method list is read from the snapshot rather than transcribed.
|
|
23
|
+
- **`--patch=12.1.5`, and it is the new default.** `PATCHES` is ordered now, so rules gate on
|
|
24
|
+
"at or after" a surface instead of a not-equals check against `12.0`.
|
|
25
|
+
- **`--patch=auto`** reads the addon's own `## Interface` number and checks the surface it
|
|
26
|
+
actually ships against: 120105 and up gets 12.1.5, 120100 gets 12.1, older gets 12.0. It
|
|
27
|
+
says so and falls back to the default when there is no `.toc` to read.
|
|
28
|
+
- **`--refresh-ref=<ref>`** rebuilds the snapshot from any branch or tag of the mirror, and
|
|
29
|
+
`--force` overrides the new guard that stops a refresh walking the snapshot backwards onto
|
|
30
|
+
an older client build. The mirror tags a build before it moves `live`, which is how 12.1.5
|
|
31
|
+
arrived here on the day it shipped; the weekly refresh workflow now reports that as a
|
|
32
|
+
no-op instead of a downgrade.
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- Refreshed `data/api-snapshot.json` from `Gethe/wow-ui-source@12.1.5`: 10,250 documented
|
|
37
|
+
functions and 760 structures, up from 10,099 and 752, with 20 `SecretReturns` (unchanged)
|
|
38
|
+
and 314 conditionally secret (up from 310). Every count is regenerated, not carried over.
|
|
39
|
+
The new globals are in it, so WSL006 and WSL007 stop analysing 12.1.5 code against a
|
|
40
|
+
snapshot that has never heard of `math.clamp`, `string.startswith`, `table.keys`,
|
|
41
|
+
`C_Intl`, `C_Weather`, `C_Timer.NewTimedSignalMap` or `CreateFrameWithOptions`. A file
|
|
42
|
+
passing a secret to `math.clamp`, `C_Intl.ToUpper` and `string.startswith` reports three
|
|
43
|
+
WSL006 errors against this snapshot and zero against 1.4.2's, which is the whole point of
|
|
44
|
+
the refresh. It also brought in a new conditional marker with no code change:
|
|
45
|
+
`SecretWhenLuaTableHasSecretKeys` on `table.count`, `table.getcountinfo` and
|
|
46
|
+
`table.isempty`.
|
|
47
|
+
- The snapshot now carries two more Blizzard markers, keyed by widget system because method
|
|
48
|
+
names collide across widget types: `ChecksForbiddenAspects` (which forbidden aspect a
|
|
49
|
+
method checks, and on which argument) and `IsProtectedFunction`. It also records the patch,
|
|
50
|
+
build, commit and ref it was generated from.
|
|
51
|
+
- A `local` bound to a folded string constant (`"PetActionButton" .. 4 .. "Cooldown"`) now
|
|
52
|
+
resolves the same way a plain string literal always did, and a numeric for-loop counter
|
|
53
|
+
inside one folds to a digit, so `_G["ActionButton" .. i .. "Cooldown"]` is recognised in
|
|
54
|
+
the loop every action-bar addon writes. A local or parameter that shares a Blizzard frame's
|
|
55
|
+
name is never taken for the frame.
|
|
56
|
+
- The GitHub Action's `patch` input defaults to `12.1.5` and accepts `auto`.
|
|
57
|
+
|
|
58
|
+
### Verified
|
|
59
|
+
|
|
60
|
+
- 230 tests pass. `--patch=12.0` and `--patch=12.1`, with and without
|
|
61
|
+
`--strict --conditional=warn`, reproduce v1.4.2's output over the whole fixture corpus
|
|
62
|
+
byte for byte, recorded before any of this work started and checked by
|
|
63
|
+
`test/patch1215.test.mjs`.
|
|
64
|
+
- On the 12-addon corpus the README measures, `--patch=12.1.5` reports the same 379 errors
|
|
65
|
+
and 121 warnings as `--patch=12.1`: the three new rules add nothing and take nothing away.
|
|
66
|
+
A second pass over each addon's current HEAD plus OmniCC, Dominos and Blizzard's own
|
|
67
|
+
interface code, 4,076 further files, also reports nothing from them. Four days in, nobody
|
|
68
|
+
has adopted the Pandemic animation APIs yet.
|
|
69
|
+
- One candidate finding was found and thrown out. An early draft treated a `Cooldown` created
|
|
70
|
+
under a secure-template button as protected, which flagged SpartanUI's quest button.
|
|
71
|
+
`ScriptRegion:IsProtected` says protection runs the other way: *"anchoring or parenting a
|
|
72
|
+
protected frame to another frame makes that frame implicitly protected as well"*, so the
|
|
73
|
+
child is not the one that changes. The inference was dropped and the rule now fires only
|
|
74
|
+
where the file names one of Blizzard's action-button cooldowns or builds a `Cooldown` from
|
|
75
|
+
a secure template.
|
|
76
|
+
|
|
3
77
|
## 1.4.2 - 2026-08-31
|
|
4
78
|
|
|
5
79
|
Data only. No rule, message or analysis behaviour changed.
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Static analysis for World of Warcraft **retail** addons. It maps where your Lua touches Blizzard's secret-value API, and hard-fails your build on the things Blizzard documents as certain to break.
|
|
4
4
|
|
|
5
|
-
Patch 12.0 introduced [secret values](https://warcraft.wiki.gg/wiki/Secret_Values). A lot of the API now hands your addon a value you are allowed to store, pass around and print, but not to do arithmetic on, compare, index, call, or measure with `#`. Patch 12.1 (Curse of Ula'tek, live 2026-08-11) [widened that surface enormously](https://warcraft.wiki.gg/wiki/Patch_12.1.0/API_changes): every `UnitAura` API returns full secrets or nil while auras are secret, seventeen unit identity APIs joined them, and the new AuraButton/AuraContainer frames carry forbidden aspects. When tainted code does something disallowed, the wiki is blunt about the result:
|
|
5
|
+
Patch 12.0 introduced [secret values](https://warcraft.wiki.gg/wiki/Secret_Values). A lot of the API now hands your addon a value you are allowed to store, pass around and print, but not to do arithmetic on, compare, index, call, or measure with `#`. Patch 12.1 (Curse of Ula'tek, live 2026-08-11) [widened that surface enormously](https://warcraft.wiki.gg/wiki/Patch_12.1.0/API_changes): every `UnitAura` API returns full secrets or nil while auras are secret, seventeen unit identity APIs joined them, and the new AuraButton/AuraContainer frames carry forbidden aspects. Patch 12.1.5 (build 69594, live 2026-09-03) [added two more forbidden aspects and closed the cooldown APIs](https://warcraft.wiki.gg/wiki/Patch_12.1.5/API_changes). When tainted code does something disallowed, the wiki is blunt about the result:
|
|
6
6
|
|
|
7
7
|
> When an operation that is not allowed is performed, the result will be an **immediate** Lua error.
|
|
8
8
|
|
|
@@ -17,7 +17,7 @@ That one is [KkthnxUI#121](https://github.com/Kkthnx-Wow/KkthnxUI/issues/121), f
|
|
|
17
17
|
|
|
18
18
|
`wow-secret-lint` reads Blizzard's own generated API documentation, tracks which of your locals hold a secret, and tells you where you touch one.
|
|
19
19
|
|
|
20
|
-
**It reports, it does not accuse.** The rules that fail your build by default are the ones Blizzard states outright: `WSL008` (combat log registration errors),
|
|
20
|
+
**It reports, it does not accuse.** The rules that fail your build by default are the ones Blizzard states outright: `WSL008` (combat log registration errors), the 12.1 rules `WSL012`/`WSL013` (aura and identity APIs "will now either return full secrets or nil when called by addons"), `WSL014` (the symbol no longer exists) and `WSL017` (forbidden aspects), and the 12.1.5 rules `WSL019`/`WSL020` (the two new forbidden aspects) and `WSL021` (protected cooldown frames). The 12.0-era `SecretReturns` tier stays a warning, because whether those APIs really hand a secret to tainted code on every call is not something static analysis can confirm; run `--strict` to gate on it. [The reasoning is spelled out below](#the-open-question-on-severity) and it is worth two minutes before you wire this into CI.
|
|
21
21
|
|
|
22
22
|
## Install
|
|
23
23
|
|
|
@@ -93,6 +93,47 @@ Exit code 1. Under `--patch=12.0` the same file reports zero findings and exits
|
|
|
93
93
|
|
|
94
94
|
None of this comes from Blizzard's generated documentation, which carries no marker for aura secrecy. The 12.1 behaviour is published only in the patch notes, so these rules seed their taint from a list transcribed there, and `--patch` selects whether that list is active.
|
|
95
95
|
|
|
96
|
+
### The 12.1.5 surface
|
|
97
|
+
|
|
98
|
+
Patch 12.1.5 gave `CustomAuraButton` Pandemic-triggered animations, and paid for them with two new forbidden aspects: *"`QueryAnimationProgress`: prevents querying the progress of an animation or whether it is running"* and *"`AddAnimations`: prevents animations from being added or reparented."* The same notes close the cooldown APIs: *"the `SetCooldown` and `Clear` cooldown APIs can no longer be called from tainted code when the cooldown frame itself is protected."*
|
|
99
|
+
|
|
100
|
+
`Core/Pandemic.lua` builds a glow the documented way and then keeps poking it:
|
|
101
|
+
|
|
102
|
+
```lua
|
|
103
|
+
local group = glow:CreateAnimationGroup()
|
|
104
|
+
local fade = group:CreateAnimation("Alpha")
|
|
105
|
+
fade:SetDuration(0.5)
|
|
106
|
+
|
|
107
|
+
button:AddPandemicActiveAnimation(group)
|
|
108
|
+
|
|
109
|
+
if group:IsPlaying() then group:Stop() end
|
|
110
|
+
group:CreateAnimation("Scale")
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`Core/Cooldowns.lua` drives one of Blizzard's action buttons:
|
|
114
|
+
|
|
115
|
+
```lua
|
|
116
|
+
local cooldown = _G["ActionButton1Cooldown"]
|
|
117
|
+
cooldown:SetCooldown(GetTime(), 10)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
$ npx wow-secret-lint ./PandemicGlow
|
|
122
|
+
Core/Cooldowns.lua:2:1 error WSL021 cooldown:SetCooldown() cannot be called from tainted code in 12.1.5 because the cooldown frame itself is protected; drive your own Cooldown frame instead, or leave the protected one to Blizzard
|
|
123
|
+
Core/Pandemic.lua:10:4 error WSL019 querying the progress or running state of an animation group registered with a Pandemic trigger is disallowed by its QueryAnimationProgress forbidden aspect: group:IsPlaying()
|
|
124
|
+
Core/Pandemic.lua:11:1 error WSL020 adding an animation to an animation group registered with a Pandemic trigger is disallowed by its AddAnimations forbidden aspect: group:CreateAnimation()
|
|
125
|
+
3 errors, 0 warnings
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Exit code 1. The first five lines of `Core/Pandemic.lua` are Blizzard's own sample from the 12.1.5 notes and stay silent: building a group and filling it is fine, and the aspects arrive with the `AddPandemicActiveAnimation` call. `--patch=12.1` reports nothing at all in either file.
|
|
129
|
+
|
|
130
|
+
If your `.toc` is still on an older interface, `--patch=auto` reads the number and checks the surface that addon actually ships against:
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
$ npx wow-secret-lint --patch=auto ./OldAddon # ## Interface: 120100
|
|
134
|
+
0 errors, 0 warnings
|
|
135
|
+
```
|
|
136
|
+
|
|
96
137
|
### Exit codes
|
|
97
138
|
|
|
98
139
|
| Code | Meaning |
|
|
@@ -126,7 +167,7 @@ Inputs:
|
|
|
126
167
|
| Input | Default | Description |
|
|
127
168
|
| --- | --- | --- |
|
|
128
169
|
| `path` | `.` | addon folder, `.toc`, or `.lua`. Several allowed, space separated |
|
|
129
|
-
| `patch` | `12.1` | patch surface to check; `
|
|
170
|
+
| `patch` | `12.1.5` | patch surface to check; an older value pins that rule set, `auto` reads the addon `.toc` |
|
|
130
171
|
| `args` | `""` | extra CLI arguments, e.g. `--conditional=warn --disable=WSL011` |
|
|
131
172
|
| `format` | `github` | `github`, `stylish`, or `json` |
|
|
132
173
|
|
|
@@ -134,7 +175,7 @@ Outputs: `errors`, `warnings`.
|
|
|
134
175
|
|
|
135
176
|
## Rules
|
|
136
177
|
|
|
137
|
-
Every rule maps to one sentence Blizzard publishes. `wow-secret-lint --rules` prints the table with the source for each. WSL008, WSL012, WSL013, WSL014, WSL017 and
|
|
178
|
+
Every rule maps to one sentence Blizzard publishes. `wow-secret-lint --rules` prints the table with the source for each. WSL008, WSL012, WSL013, WSL014, WSL017, WSL018, WSL019, WSL020 and WSL021 fail a build by default; the rest are warnings.
|
|
138
179
|
|
|
139
180
|
| Rule | Severity | What it catches |
|
|
140
181
|
| --- | --- | --- |
|
|
@@ -156,6 +197,9 @@ Every rule maps to one sentence Blizzard publishes. `wow-secret-lint --rules` pr
|
|
|
156
197
|
| WSL016 | warning | 12.1: `showCountdownFrame` passed to a private-aura API |
|
|
157
198
|
| WSL017 | error | 12.1: forbidden-aspect operation on an AuraButton or AuraContainer |
|
|
158
199
|
| WSL018 | error | 12.1: call of an aura API reached by index, slot or instance id |
|
|
200
|
+
| WSL019 | error | 12.1.5: querying the progress or running state of an animation that carries `QueryAnimationProgress` |
|
|
201
|
+
| WSL020 | error | 12.1.5: adding or reparenting an animation on a group that carries `AddAnimations` |
|
|
202
|
+
| WSL021 | error | 12.1.5: `SetCooldown`/`Clear` on a cooldown frame Blizzard protects |
|
|
159
203
|
|
|
160
204
|
WSL008 comes from the [12.0.0 API changes](https://warcraft.wiki.gg/wiki/Patch_12.0.0/API_changes): *"COMBAT_LOG_EVENT and COMBAT_LOG_EVENT_UNFILTERED will error when trying to register them."* Use `COMBAT_LOG_EVENT_INTERNAL_UNFILTERED` or the `C_CombatLog` namespace.
|
|
161
205
|
|
|
@@ -185,6 +229,35 @@ The rename is the minimal fix, but it is probably not the one you want. Reportin
|
|
|
185
229
|
|
|
186
230
|
**WSL017** knows a local is an AuraContainer or AuraButton when it comes from `CreateFrame("AuraContainer", ...)`/`CreateFrame("AuraButton", ...)` or arrives as the `initializeFrame` callback parameter of `AddAuraGroup`/`AddAuraSlot`/`AddItemEnchantment`, and it follows the value into a table field, so `self.buttons[i] = button` keeps the type. On those it flags `SetScript`/`HookScript`, event registration, input calls (`EnableMouse`, `RegisterForClicks`, `Click`, ...) and focus queries (`IsMouseOver`, ...), naming the forbidden aspect each one trips. Construction itself is never flagged.
|
|
187
231
|
|
|
232
|
+
### The 12.1.5 rules in detail
|
|
233
|
+
|
|
234
|
+
**WSL019** and **WSL020** are the two forbidden aspects 12.1.5 added. The notes name them outright: *"`QueryAnimationProgress`: prevents querying the progress of an animation or whether it is running"* and *"`AddAnimations`: prevents animations from being added or reparented"*. Unlike the 12.1 aura rules, this surface is in Blizzard's generated documentation, so the method lists are not transcribed by hand: every affected method carries `ChecksForbiddenAspects`, and the snapshot keeps them keyed by widget system. That is 8 methods on `SimpleAnimAPI` and 8 on `SimpleAnimGroupAPI` for `QueryAnimationProgress`, and `SimpleAnimGroupAPI:CreateAnimation` plus `SimpleAnimAPI:SetParent` for `AddAnimations`. The second of those is the "or reparented" half, and it is checked on the `parent` argument rather than on the receiver.
|
|
235
|
+
|
|
236
|
+
The aspects are applied when an animation group is registered with a Pandemic trigger, which is what the notes mean by *"two new forbidden aspects were added to support this"* and *"animations used by Pandemic triggers also gain the existing `ChangeAnimationTarget` forbidden aspect"*. Blizzard's own sample in those notes builds the group, adds its animations, and registers it last, so an addon that follows that order is silent. Everything after `AddPandemicEnterAnimation`/`AddPandemicActiveAnimation`/`AddPandemicLeaveAnimation` is not:
|
|
237
|
+
|
|
238
|
+
```lua
|
|
239
|
+
local group = glow:CreateAnimationGroup()
|
|
240
|
+
local fade = group:CreateAnimation("Alpha") -- fine, the group is still yours
|
|
241
|
+
button:AddPandemicActiveAnimation(group) -- from here the client owns it
|
|
242
|
+
|
|
243
|
+
if group:IsPlaying() then end -- WSL019
|
|
244
|
+
group:CreateAnimation("Scale") -- WSL020
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Tracking follows the group through aliases, into a table field, and onto the animations that were already inside it when it was registered, and drops it the moment the local is reassigned. Guards do not silence either rule, for the same reason they do not silence WSL018: the call itself is what errors.
|
|
248
|
+
|
|
249
|
+
**WSL021** covers *"the `SetCooldown` and `Clear` cooldown APIs can no longer be called from tainted code when the cooldown frame itself is protected"*. The six methods are the ones Blizzard newly marked `IsProtectedFunction = true` on `FrameAPICooldown` in 12.1.5 and marked on none of them in 12.1: `SetCooldown`, `SetCooldownDuration`, `SetCooldownFromDurationObject`, `SetCooldownFromExpirationTime`, `SetCooldownUNIX` and `Clear`. Reading a protected cooldown is untouched, and so is a cooldown frame your addon created.
|
|
250
|
+
|
|
251
|
+
A cooldown counts as protected in two cases. It is one of Blizzard's action-button cooldowns, named directly, reached through `_G[...]` including a name built by concatenation with a numeric loop counter (`"ActionButton" .. i .. "Cooldown"` resolves), or reached as the `cooldown`/`chargeCooldown`/`lossOfControlCooldown` field of the button. A local or parameter that happens to share one of those names is left alone. The button names come from `Blizzard_ActionBar/Shared/ActionBar.lua`, which names each one `"ActionButton"..i`, `"StanceButton"..i`, `"PetActionButton"..i`, `"PossessButton"..i` or `actionBarName.."Button"..i` for the seven MultiBars, and `ActionButtonTemplate.xml` names the cooldown child `$parentCooldown` with `parentKey="cooldown"`. Or your own `Cooldown` inherits a `Secure*Template`, which is what "explicitly specified as protected at the time of creation" means for an addon.
|
|
252
|
+
|
|
253
|
+
A cooldown your addon parents to a secure button is **not** protected, and that took a false positive to get right. `ScriptRegion:IsProtected` says protection travels the other way: *"anchoring or parenting a protected frame to another frame makes that frame implicitly protected as well"*, recursively, so the child is not the one that changes.
|
|
254
|
+
|
|
255
|
+
```lua
|
|
256
|
+
local cd = _G["ActionButton1Cooldown"]
|
|
257
|
+
cd:SetCooldown(GetTime(), 10) -- WSL021
|
|
258
|
+
cd:IsPaused() -- silent, reading is not a protected function
|
|
259
|
+
```
|
|
260
|
+
|
|
188
261
|
### What it will never flag
|
|
189
262
|
|
|
190
263
|
The wiki has an explicit allowed list, and flagging any of it is a false positive that gets a linter uninstalled. Each of these has a passing negative test in `test/`:
|
|
@@ -216,7 +289,7 @@ npx wow-secret-lint ./MyAddon --secret-guard=IsLocked --access-guard=CanRead
|
|
|
216
289
|
|
|
217
290
|
Blizzard marks a second tier in the generated docs: functions that return a secret only while a specific restriction is active. `C_Spell.GetSpellCooldown` carries `SecretWhenCooldownsRestricted`; `C_LFGList.GetSearchResultInfo` carries `SecretInChatMessagingLockdown`; `UnitGUID`, `UnitName` and `UnitClass` carry `SecretWhenUnitIdentityRestricted`. In practice that means "secret in PvP, in restricted instances, and for non-player or pet units in combat". Your code works fine right up until it does not.
|
|
218
291
|
|
|
219
|
-
|
|
292
|
+
From `--patch=12.1` on, the identity APIs WSL013 covers are promoted out of this tier and report as errors regardless of the `--conditional` setting. The rest of the tier is **off by default**, because measured against 12 real addons it produces about one warning per Lua file, which is a tax rather than a signal. Turn it on for a deeper audit:
|
|
220
293
|
|
|
221
294
|
```bash
|
|
222
295
|
npx wow-secret-lint ./MyAddon --conditional=warn # report as warnings
|
|
@@ -249,12 +322,13 @@ Run against 12 real retail addons (BigWigs, LittleWigs, DBM, WeakAuras, Details,
|
|
|
249
322
|
|
|
250
323
|
| Mode | Errors | Warnings | Addons that would fail CI |
|
|
251
324
|
| --- | --- | --- | --- |
|
|
252
|
-
| `--patch=12.1` (default) | 379 | 121 | 9 of 12 |
|
|
325
|
+
| `--patch=12.1.5` (default) | 379 | 121 | 9 of 12 |
|
|
326
|
+
| `--patch=12.1` | 379 | 121 | 9 of 12 |
|
|
253
327
|
| `--patch=12.1 --strict` | 489 | 11 | 9 of 12 |
|
|
254
328
|
| `--patch=12.0` | 20 | 90 | 3 of 12 |
|
|
255
329
|
| `--patch=12.0 --strict` | 110 | 0 | 5 of 12 |
|
|
256
330
|
|
|
257
|
-
The two `12.0` rows are identical to what v1.2.0 reported, which is the point of the flag. Everything above them is the 12.1 surface landing, concentrated exactly where the [PTR forum thread](https://us.forums.blizzard.com/en/wow/t/minicc-and-similar-addons-might-be-partially-broken-in-121/2310937) predicted breakage:
|
|
331
|
+
The two `12.0` rows are identical to what v1.2.0 reported, and the `12.1` rows to what v1.4.2 reported, which is the point of the flag. Everything above them is the 12.1 surface landing, concentrated exactly where the [PTR forum thread](https://us.forums.blizzard.com/en/wow/t/minicc-and-similar-addons-might-be-partially-broken-in-121/2310937) predicted breakage:
|
|
258
332
|
|
|
259
333
|
| Rule | Count | Where |
|
|
260
334
|
| --- | --- | --- |
|
|
@@ -264,9 +338,12 @@ The two `12.0` rows are identical to what v1.2.0 reported, which is the point of
|
|
|
264
338
|
| WSL014 | 10 | mostly `GetInventorySlotInfo`, plus `GetInspectSpecialization`, `GetWeaponEnchantInfo` and SpartanUI's `UIParentLoadAddOn` |
|
|
265
339
|
| WSL016 | 3 | oUF `privateauras.lua:133` and both copies vendored into KkthnxUI and SpartanUI |
|
|
266
340
|
| WSL017 | 0 | no addon in the corpus uses AuraContainers yet; exercised by fixtures |
|
|
341
|
+
| WSL019, WSL020, WSL021 | 0 | see below |
|
|
267
342
|
|
|
268
343
|
Two numbers are worth reading together. **DBM and BigWigs report no WSL012 at all**, because both already wrap every aura lookup in an `issecretvalue` guard and the analysis credits that idiom. **BigWigs still has four WSL018 findings**, because guarding the result does not help when the call itself is what errors. That pair is the honest summary of where the ecosystem is: the careful addons did the guard work, and the guard work is not enough.
|
|
269
344
|
|
|
345
|
+
The three 12.1.5 rules report **nothing** on this corpus, and nothing on a second pass on 2026-09-07 over each addon's current HEAD plus OmniCC, Dominos and Blizzard's own interface code: 4,218 more files, zero findings. Four days after the patch, no addon in reach has adopted the Pandemic animation APIs, and none of them drives Blizzard's action-button cooldowns in a way a static reader can see. OmniCC gets closest and is the honest limit of the rule: it hooks `SetCooldown` on `getmetatable(ActionButton1Cooldown).__index` and dispatches through a proxy with the method name as a string, so the frame is never named at the call site. WSL021 sees a protected cooldown only where the file names one.
|
|
346
|
+
|
|
270
347
|
A sample of the WSL012/WSL013 findings across every affected repo was read against its source by hand; each one performs an operation the 12.1 notes document as disallowed, on a value those notes document as secret, with no guard in scope. The pre-12.1 findings are unchanged from v1.2.0, where all 110 strict findings were read by hand.
|
|
271
348
|
|
|
272
349
|
Contamination from Classic-flavour code is 0, checked by resolving every retail `.toc` in each repo and matching it against the findings.
|
|
@@ -295,8 +372,8 @@ Settling it needs someone to run a flagged line in game and watch what happens,
|
|
|
295
372
|
```
|
|
296
373
|
--format=<stylish|json|github> output format (default: stylish)
|
|
297
374
|
--game=<retail|classic> classic has no secret values and exits 0 immediately
|
|
298
|
-
--patch=<12.0|12.1>
|
|
299
|
-
|
|
375
|
+
--patch=<12.0|12.1|12.1.5|auto> patch surface the built-in rules check (default: 12.1.5);
|
|
376
|
+
an older value pins that rule set, auto reads the addon .toc
|
|
300
377
|
--strict raise SecretReturns findings from warning to error
|
|
301
378
|
--conditional=<off|warn|error> conditionally secret APIs (default: off)
|
|
302
379
|
--secret-guard=<names> extra is-secret wrapper functions, comma separated
|
|
@@ -305,12 +382,36 @@ Settling it needs someone to run a flagged line in game and watch what happens,
|
|
|
305
382
|
--max-warnings=<n> exit 1 when warnings exceed n
|
|
306
383
|
--snapshot=<path> use a different API snapshot
|
|
307
384
|
--refresh rebuild the vendored API snapshot (the only networked command)
|
|
385
|
+
--refresh-ref=<ref> branch or tag of the mirror to rebuild from (default: live)
|
|
386
|
+
--force let --refresh write an older client build than the vendored one
|
|
308
387
|
--rules print the rule table with its sources
|
|
309
388
|
```
|
|
310
389
|
|
|
311
390
|
## Where the data comes from
|
|
312
391
|
|
|
313
|
-
`data/api-snapshot.json` is built from Blizzard's generated API documentation, mirrored at [Gethe/wow-ui-source](https://github.com/Gethe/wow-ui-source)
|
|
392
|
+
`data/api-snapshot.json` is built from Blizzard's generated API documentation, mirrored at [Gethe/wow-ui-source](https://github.com/Gethe/wow-ui-source). The current snapshot is patch **12.1.5, build 69594**, rebuilt from that mirror's `12.1.5` tag, and carries **10,250 documented functions and 760 structures**: 20 with `SecretReturns = true`, 314 conditionally secret, and per-field `NeverSecret` markers on 20 structures. It also carries the widget markers the 12.1.5 rules read: `ChecksForbiddenAspects` and `IsProtectedFunction`, keyed by widget system because method names collide across widget types.
|
|
393
|
+
|
|
394
|
+
This is the half of the release with no new rules in it and it still changes results. WSL006 and WSL007 consult the snapshot before they fire, so an addon that has already adopted a 12.1.5 API was being analysed against a documentation set that had never heard of the symbol:
|
|
395
|
+
|
|
396
|
+
```
|
|
397
|
+
$ npx wow-secret-lint --strict --snapshot=./api-snapshot-1.4.2.json Adopter.lua
|
|
398
|
+
0 errors, 0 warnings
|
|
399
|
+
|
|
400
|
+
$ npx wow-secret-lint --strict Adopter.lua
|
|
401
|
+
Adopter.lua:2:24 error WSL006 secret value passed to math.clamp(), which is documented SecretArguments = "AllowedWhenUntainted" and addon code is always tainted: 'hp' derives from UnitHealth() (SecretReturns=true)
|
|
402
|
+
Adopter.lua:3:29 error WSL006 secret value passed to C_Intl.ToUpper(), which is documented SecretArguments = "AllowedWhenUntainted" and addon code is always tainted: derives from UnitSpellTargetName() (SecretReturns=true)
|
|
403
|
+
Adopter.lua:4:22 error WSL006 secret value passed to string.startswith(), which is documented SecretArguments = "AllowedWhenUntainted" and addon code is always tainted: derives from UnitSpellTargetName() (SecretReturns=true)
|
|
404
|
+
3 errors, 0 warnings
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
The refresh also picked up a new conditional marker with no code change needed: `table.count`, `table.getcountinfo` and `table.isempty` carry `SecretWhenLuaTableHasSecretKeys`, so `--conditional=warn` reports them like any other `SecretWhen*` API.
|
|
408
|
+
|
|
409
|
+
Every count on this page comes out of the snapshot itself:
|
|
410
|
+
|
|
411
|
+
```bash
|
|
412
|
+
node -p "const s=require('./data/api-snapshot.json'); [s.patch, s.build, s.functionCount, s.structureCount, s.secretReturnCount, s.conditionalCount, s.annotatedStructureCount].join(' ')"
|
|
413
|
+
12.1.5 69594 10250 760 20 314 20
|
|
414
|
+
```
|
|
314
415
|
|
|
315
416
|
It is parsed with `luaparse`, not regexed, so nested tables and multi-line entries cannot skew it. Rebuild it any time:
|
|
316
417
|
|
|
@@ -320,7 +421,22 @@ npx wow-secret-lint --refresh
|
|
|
320
421
|
|
|
321
422
|
A scheduled workflow in this repo does that weekly and opens a pull request when the docs move.
|
|
322
423
|
|
|
323
|
-
The
|
|
424
|
+
The mirror tags a client build before it moves its `live` branch, which is how 12.1.5 was picked up here on the day it shipped:
|
|
425
|
+
|
|
426
|
+
```bash
|
|
427
|
+
npx wow-secret-lint --refresh --refresh-ref=12.1.5
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
The snapshot records the patch and build it came from, read off the mirror's own commit message, and `--refresh` will not walk it backwards onto an older build without `--force`. Point it at a stale ref and it says so and leaves the file alone:
|
|
431
|
+
|
|
432
|
+
```
|
|
433
|
+
$ npx wow-secret-lint --refresh --refresh-ref=12.1.0
|
|
434
|
+
rebuilding API snapshot from Gethe/wow-ui-source@12.1.0 ...
|
|
435
|
+
wow-secret-lint: @12.1.0 is still 12.1.0 (build 69587), older than the vendored 12.1.5
|
|
436
|
+
(build 69594); left the snapshot alone. Pass --force to write it anyway.
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
The 12.1 aura and identity secrecy is deliberately **not** part of the snapshot: Blizzard's generated documentation carries no marker for it, only the patch notes describe it, so the WSL012/WSL013 API lists live in `src/rules.mjs` with the source quoted, and `--patch` gates them. The 12.1.5 rules are the other way round. Blizzard does mark those in the generated docs, so WSL019 and WSL020 read their method lists out of the snapshot and WSL021 reads the six `IsProtectedFunction` cooldown methods out of it; only the question of *which objects* carry the aspects lives in `src/rules.mjs`. There is one snapshot, and `--snapshot`/`--refresh` behave the same under every patch.
|
|
324
440
|
|
|
325
441
|
## Limitations and non-goals
|
|
326
442
|
|
|
@@ -330,7 +446,7 @@ The 12.1 aura and identity secrecy is deliberately **not** part of the snapshot:
|
|
|
330
446
|
- **It cannot diagnose a taint-spread crash.** Some of the worst reports look like `attempt to perform arithmetic on local 'textHeight' (a secret number value, while execution tainted by 'YourAddon')` where every frame of the stack is a Blizzard file. Your addon tainted execution, and then Blizzard's code did the arithmetic. There is no forbidden operation in your Lua to point at, so this tool reports nothing. Running it over the real [aura-questor](https://github.com/lucascodev/aura-questor) source, which has exactly that open report, gives a clean run across 114 files. The regression fixture named after that issue reproduces the shape of the trace, not a finding in their code.
|
|
331
447
|
- **No cross-file interprocedural analysis in v1.** Taint follows plain assignment, table field stores, the return value of a file-local function, and one level of intra-file call-argument passing. A secret that leaves through a global and comes back in another file is not tracked.
|
|
332
448
|
- **No LuaJIT or Lua 5.4 syntax.** Files are parsed as Lua 5.1. WoW accepts a semicolon after `break`, which stock 5.1 does not, so a file that fails on 5.1 gets one retry under the 5.2 grammar before it is reported as a parse error.
|
|
333
|
-
- **Method calls are not resolved to a widget type**, so WSL006 only applies to plain and namespaced calls (`UnitHealth(...)`, `C_CVar.SetCVar(...)`), never to `frame:SetText(...)`. WSL017
|
|
449
|
+
- **Method calls are not resolved to a widget type**, so WSL006 only applies to plain and namespaced calls (`UnitHealth(...)`, `C_CVar.SetCVar(...)`), never to `frame:SetText(...)`. WSL017 and WSL019-WSL021 are the exceptions, and their typing is deliberately narrow: a local counts as an AuraContainer/AuraButton only when it comes straight from `CreateFrame` with a literal type string or from an `initializeFrame` callback, an animation group carries the 12.1.5 aspects only from the `AddPandemic*Animation` call that registers it, and a cooldown is protected only where the file names Blizzard's frame or builds one from a secure template. Values that arrive across files, through a metatable hook, or through a proxy that takes the method name as a string are not tracked.
|
|
334
450
|
- **XML is followed for `<Script>`/`<Include>` and checked only for removed templates.** WSL014 reads `inherits` attributes in every `.xml` the `.toc` pulls in, so a declarative aura header is caught. Nothing else in the markup is analysed: widget scripts written inline in XML are not parsed as Lua.
|
|
335
451
|
- **`string.format` output is not tracked as secret.** The wiki names it as the sanctioned way to render a secret, and following it would flood every `SetText` call site. The trade is a known blind spot on `#string.format(...)`.
|
|
336
452
|
- A `.toc` listing a file that is not on disk warns and keeps going. A file that will not parse is reported and the run exits 2.
|
|
@@ -341,7 +457,7 @@ The 12.1 aura and identity secrecy is deliberately **not** part of the snapshot:
|
|
|
341
457
|
npm test
|
|
342
458
|
```
|
|
343
459
|
|
|
344
|
-
|
|
460
|
+
230 tests. The suite covers every rule, the guard forms, the permitted-operations negative cases, the three reporters, the CLI surface, one violating and one clean fixture per 12.1 and 12.1.5 rule (`test/fixtures/rules-121/`, `test/fixtures/rules-1215/`), recorded baselines proving `--patch=12.0` reproduces the v1.2.0 output and `--patch=12.1` the v1.4.2 output byte for byte over the whole fixture corpus (`test/fixtures/patch/`), and eight regression fixtures reconstructed from real shipped traces:
|
|
345
461
|
|
|
346
462
|
| Fixture | Issue |
|
|
347
463
|
| --- | --- |
|
package/action/index.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
8
8
|
import process from 'node:process';
|
|
9
9
|
import { lintPaths, VERSION } from '../src/index.mjs';
|
|
10
10
|
import { format, FORMATS, counts } from '../src/report.mjs';
|
|
11
|
-
import { RULE_IDS, PATCHES, DEFAULT_PATCH } from '../src/rules.mjs';
|
|
11
|
+
import { RULE_IDS, PATCHES, DEFAULT_PATCH, patchList } from '../src/rules.mjs';
|
|
12
12
|
|
|
13
13
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
14
14
|
|
|
@@ -105,8 +105,8 @@ if (!['off', 'warn', 'error'].includes(options.conditional)) {
|
|
|
105
105
|
if (!['retail', 'classic'].includes(options.game)) {
|
|
106
106
|
fail(`unknown --game "${options.game}" (expected retail or classic)`);
|
|
107
107
|
}
|
|
108
|
-
if (!PATCHES.includes(options.patch)) {
|
|
109
|
-
fail(`unknown patch "${options.patch}" (expected ${
|
|
108
|
+
if (options.patch !== 'auto' && !PATCHES.includes(options.patch)) {
|
|
109
|
+
fail(`unknown patch "${options.patch}" (expected ${patchList()}, or auto)`);
|
|
110
110
|
}
|
|
111
111
|
for (const id of options.disable) {
|
|
112
112
|
if (!RULE_IDS.includes(id)) fail(`unknown rule id "${id}" in --disable`);
|
|
@@ -142,7 +142,7 @@ const rows = merged.findings
|
|
|
142
142
|
|
|
143
143
|
await summary(
|
|
144
144
|
`## wow-secret-lint ${VERSION}\n\n` +
|
|
145
|
-
`${merged.filesScanned} Lua file(s) scanned against ${merged.snapshot.functionCount ?? 0} documented APIs ` +
|
|
145
|
+
`${merged.filesScanned} Lua file(s) scanned on the ${merged.patch} surface against ${merged.snapshot.functionCount ?? 0} documented APIs ` +
|
|
146
146
|
`(${merged.snapshot.secretReturnCount ?? 0} with \`SecretReturns=true\`).\n\n` +
|
|
147
147
|
`**${errors} error(s), ${warnings} warning(s)**` +
|
|
148
148
|
(merged.parseErrors.length ? `, ${merged.parseErrors.length} parse error(s)` : '') +
|
package/action.yml
CHANGED
|
@@ -11,9 +11,9 @@ inputs:
|
|
|
11
11
|
required: false
|
|
12
12
|
default: '.'
|
|
13
13
|
patch:
|
|
14
|
-
description: 'Patch surface to check: 12.1 (default)
|
|
14
|
+
description: 'Patch surface to check: 12.1.5 (default), 12.1, 12.0, or auto to read the addon .toc.'
|
|
15
15
|
required: false
|
|
16
|
-
default: '12.1'
|
|
16
|
+
default: '12.1.5'
|
|
17
17
|
args:
|
|
18
18
|
description: 'Extra CLI arguments, e.g. "--conditional=warn --disable=WSL011".'
|
|
19
19
|
required: false
|
package/bin/wow-secret-lint.mjs
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
import process from 'node:process';
|
|
7
7
|
import { lintPaths, VERSION } from '../src/index.mjs';
|
|
8
8
|
import { format, FORMATS, counts } from '../src/report.mjs';
|
|
9
|
-
import { refreshSnapshot, writeSnapshot, SNAPSHOT_PATH } from '../src/apidata.mjs';
|
|
10
|
-
import { RULES, RULE_IDS, PATCHES, DEFAULT_PATCH } from '../src/rules.mjs';
|
|
9
|
+
import { refreshSnapshot, writeSnapshot, loadSnapshot, SNAPSHOT_PATH, DEFAULT_REF } from '../src/apidata.mjs';
|
|
10
|
+
import { RULES, RULE_IDS, PATCHES, DEFAULT_PATCH, patchList, patchAtLeast } from '../src/rules.mjs';
|
|
11
11
|
|
|
12
12
|
const USAGE = `wow-secret-lint ${VERSION}
|
|
13
13
|
Static analysis for World of Warcraft retail addons: finds Secret Value violations
|
|
@@ -21,9 +21,10 @@ Usage:
|
|
|
21
21
|
Options:
|
|
22
22
|
--format=<stylish|json|github> output format (default: stylish)
|
|
23
23
|
--game=<retail|classic> classic has no secret values and exits 0 immediately
|
|
24
|
-
--patch=<12.0|12.1>
|
|
25
|
-
(default: 12.1).
|
|
26
|
-
addons still targeting
|
|
24
|
+
--patch=<12.0|12.1|12.1.5|auto> which patch surface the built-in rules check
|
|
25
|
+
(default: 12.1.5). An older value pins the rule set for
|
|
26
|
+
addons still targeting an older client, byte for byte;
|
|
27
|
+
auto reads the addon's own .toc Interface number
|
|
27
28
|
--strict raise SecretReturns findings from warning to error.
|
|
28
29
|
Off by default: see "the open question on severity"
|
|
29
30
|
in the README before you gate CI on them.
|
|
@@ -38,6 +39,10 @@ Options:
|
|
|
38
39
|
--snapshot=<path> use a different API snapshot
|
|
39
40
|
--refresh rebuild the vendored API snapshot from the public mirror
|
|
40
41
|
(the only command that uses the network)
|
|
42
|
+
--refresh-ref=<ref> branch or tag of the mirror to rebuild from
|
|
43
|
+
(default: ${DEFAULT_REF})
|
|
44
|
+
--force let --refresh write a snapshot of an older client build
|
|
45
|
+
than the one already vendored
|
|
41
46
|
--rules print the rule table and exit
|
|
42
47
|
--version print the version and exit
|
|
43
48
|
-h, --help print this help and exit
|
|
@@ -64,6 +69,8 @@ function parseArgs(argv) {
|
|
|
64
69
|
maxWarnings: Infinity,
|
|
65
70
|
snapshot: undefined,
|
|
66
71
|
refresh: false,
|
|
72
|
+
refreshRef: DEFAULT_REF,
|
|
73
|
+
force: false,
|
|
67
74
|
rules: false,
|
|
68
75
|
help: false,
|
|
69
76
|
version: false,
|
|
@@ -78,6 +85,7 @@ function parseArgs(argv) {
|
|
|
78
85
|
if (arg === '-h' || arg === '--help') opts.help = true;
|
|
79
86
|
else if (arg === '--version' || arg === '-v') opts.version = true;
|
|
80
87
|
else if (arg === '--refresh') opts.refresh = true;
|
|
88
|
+
else if (arg === '--force') opts.force = true;
|
|
81
89
|
else if (arg === '--rules') opts.rules = true;
|
|
82
90
|
else if (arg === '--strict') opts.strict = true;
|
|
83
91
|
else if (arg.startsWith('--format')) opts.format = value(arg, argv, () => i++);
|
|
@@ -88,6 +96,7 @@ function parseArgs(argv) {
|
|
|
88
96
|
else if (arg.startsWith('--access-guard')) opts.accessGuards.push(...value(arg, argv, () => i++).split(',').map((s) => s.trim()).filter(Boolean));
|
|
89
97
|
else if (arg.startsWith('--disable')) opts.disable = value(arg, argv, () => i++).split(',').map((s) => s.trim()).filter(Boolean);
|
|
90
98
|
else if (arg.startsWith('--max-warnings')) opts.maxWarnings = Number(value(arg, argv, () => i++));
|
|
99
|
+
else if (arg.startsWith('--refresh-ref')) opts.refreshRef = value(arg, argv, () => i++);
|
|
91
100
|
else if (arg.startsWith('--snapshot')) opts.snapshot = value(arg, argv, () => i++);
|
|
92
101
|
else if (arg.startsWith('-')) throw new Error(`unknown option "${arg}"`);
|
|
93
102
|
else opts.paths.push(arg);
|
|
@@ -129,11 +138,12 @@ async function main() {
|
|
|
129
138
|
}
|
|
130
139
|
|
|
131
140
|
if (opts.refresh) {
|
|
132
|
-
process.stderr.write(
|
|
141
|
+
process.stderr.write(`rebuilding API snapshot from Gethe/wow-ui-source@${opts.refreshRef} ...\n`);
|
|
133
142
|
let index;
|
|
134
143
|
try {
|
|
135
144
|
let last = 0;
|
|
136
145
|
index = await refreshSnapshot({
|
|
146
|
+
ref: opts.refreshRef,
|
|
137
147
|
onProgress: ({ done, total }) => {
|
|
138
148
|
if (done - last >= 50 || done === total) {
|
|
139
149
|
last = done;
|
|
@@ -144,9 +154,22 @@ async function main() {
|
|
|
144
154
|
} catch (err) {
|
|
145
155
|
fail(`--refresh failed: ${err.message}`);
|
|
146
156
|
}
|
|
147
|
-
const path =
|
|
157
|
+
const path = opts.snapshot ?? SNAPSHOT_PATH;
|
|
158
|
+
const vendored = await loadSnapshot(path).catch(() => null);
|
|
159
|
+
// The mirror tags a new patch before it moves the live branch, so a scheduled refresh
|
|
160
|
+
// can otherwise walk the snapshot back onto a client build that is no longer current.
|
|
161
|
+
if (!opts.force && vendored && vendored.build && index.build && index.build < vendored.build) {
|
|
162
|
+
process.stderr.write(
|
|
163
|
+
`wow-secret-lint: @${opts.refreshRef} is still ${index.patch ?? '?'} (build ${index.build}), older than ` +
|
|
164
|
+
`the vendored ${vendored.patch ?? '?'} (build ${vendored.build}); left the snapshot alone. ` +
|
|
165
|
+
`Pass --force to write it anyway.\n`
|
|
166
|
+
);
|
|
167
|
+
return 0;
|
|
168
|
+
}
|
|
169
|
+
await writeSnapshot(index, path);
|
|
148
170
|
process.stderr.write(
|
|
149
|
-
`wrote ${path}: ${index.
|
|
171
|
+
`wrote ${path}: patch ${index.patch ?? 'unknown'} build ${index.build ?? 'unknown'}, ` +
|
|
172
|
+
`${index.functionCount} functions, ${index.secretReturnCount} with SecretReturns=true, ` +
|
|
150
173
|
`${index.conditionalCount} conditionally secret, ${index.structureCount} structures\n`
|
|
151
174
|
);
|
|
152
175
|
if (index.failures && index.failures.length) {
|
|
@@ -157,7 +180,9 @@ async function main() {
|
|
|
157
180
|
|
|
158
181
|
if (!FORMATS.includes(opts.format)) fail(`unknown format "${opts.format}" (expected one of: ${FORMATS.join(', ')})`);
|
|
159
182
|
if (!['retail', 'classic'].includes(opts.game)) fail(`unknown game "${opts.game}" (expected retail or classic)`);
|
|
160
|
-
if (!PATCHES.includes(opts.patch))
|
|
183
|
+
if (opts.patch !== 'auto' && !PATCHES.includes(opts.patch)) {
|
|
184
|
+
fail(`unknown --patch "${opts.patch}" (expected ${patchList()}, or auto)`);
|
|
185
|
+
}
|
|
161
186
|
if (!['warn', 'error', 'off'].includes(opts.conditional)) {
|
|
162
187
|
fail(`unknown --conditional "${opts.conditional}" (expected warn, error or off)`);
|
|
163
188
|
}
|
|
@@ -177,6 +202,14 @@ async function main() {
|
|
|
177
202
|
return 0;
|
|
178
203
|
}
|
|
179
204
|
|
|
205
|
+
const snapshot = await loadSnapshot(opts.snapshot).catch(() => null);
|
|
206
|
+
if (snapshot && snapshot.patch && opts.patch !== 'auto' && !patchAtLeast(snapshot.patch, opts.patch)) {
|
|
207
|
+
process.stderr.write(
|
|
208
|
+
`wow-secret-lint: the vendored snapshot is patch ${snapshot.patch} but --patch is ${opts.patch}; ` +
|
|
209
|
+
`the rules that read the snapshot are checking an older API surface. Run --refresh.\n`
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
180
213
|
let merged;
|
|
181
214
|
try {
|
|
182
215
|
merged = await lintPaths(opts.paths, {
|