ticketlens 0.38.36 → 0.38.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<img src="https://img.shields.io/npm/dm/ticketlens?style=flat-square&color=06b6d4&label=downloads" />
|
|
8
8
|
<img src="https://github.com/ralphmoran/ticket-lens/actions/workflows/test.yml/badge.svg?style=flat-square" />
|
|
9
9
|
<img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" />
|
|
10
|
-
<img src="https://img.shields.io/badge/node-%3E%
|
|
10
|
+
<img src="https://img.shields.io/badge/node-%3E%3D22-brightgreen?style=flat-square" />
|
|
11
11
|
</div>
|
|
12
12
|
|
|
13
13
|
</div>
|
|
@@ -100,7 +100,7 @@ npx ticketlens CNV1-2
|
|
|
100
100
|
|
|
101
101
|
Tip: `tl` works everywhere `ticketlens` does — running `tl`/`ticketlens config` before anything is configured also launches guided setup, no dead end. Pass `--no-input` to force non-interactive behavior even in a terminal (scripts, CI).
|
|
102
102
|
|
|
103
|
-
**Prerequisites:** Node.js >=
|
|
103
|
+
**Prerequisites:** Node.js >=22.6
|
|
104
104
|
|
|
105
105
|
---
|
|
106
106
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ticketlens",
|
|
3
|
-
"version": "0.38.
|
|
3
|
+
"version": "0.38.37",
|
|
4
4
|
"description": "Jira CLI for developers — fetch ticket context, triage your queue, and stop tab-switching. Zero dependencies, all local.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -40,6 +40,6 @@
|
|
|
40
40
|
},
|
|
41
41
|
"homepage": "https://github.com/ralphmoran/ticket-lens",
|
|
42
42
|
"engines": {
|
|
43
|
-
"node": ">=
|
|
43
|
+
"node": ">=22.6.0"
|
|
44
44
|
}
|
|
45
45
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Centralised here to avoid triplicating the regex and warning logic.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
export const DEFAULT_API_BASE = '
|
|
6
|
+
export const DEFAULT_API_BASE = 'https://api.ticketlens.app';
|
|
7
7
|
export const DEFAULT_SITE_BASE = 'https://ticketlens.app';
|
|
8
8
|
|
|
9
9
|
// Matches localhost, 127.0.0.1, and any hostname ending in .test or .local,
|
|
@@ -32,6 +32,30 @@ const MAX_JOINED_CHUNKS = 4;
|
|
|
32
32
|
const MAX_COMPOUND_SEGMENT_LENGTH = 15;
|
|
33
33
|
const HYPHENATED_COMPOUND_RE = /^[A-Za-z]+(-[A-Za-z]+)+$/;
|
|
34
34
|
|
|
35
|
+
// A candidate containing an unstripped '(', ')', '[', or ']' reads as code
|
|
36
|
+
// syntax (an array/list literal element, or a function-call argument — e.g.
|
|
37
|
+
// "['compliance', '--help']" or "matches(x)") rather than a secret fragment.
|
|
38
|
+
// Consulted ONLY from looksLikeCodeSyntax below, which downgrades a matching
|
|
39
|
+
// high-entropy candidate to a warning — deliberately NOT wired into
|
|
40
|
+
// isLabelWord (backlog #14 residual, code review caught this on the first
|
|
41
|
+
// pass): making a bracket-bearing token a hard isLabelWord stop would end a
|
|
42
|
+
// joinedChunkRuns run there unconditionally, the same way GIT_REFERENCE_WORD_RE/
|
|
43
|
+
// isHyphenatedWordCompound/looksLikeFilenameReference already do — but unlike
|
|
44
|
+
// those, a bracket character is trivial for an attacker to insert anywhere
|
|
45
|
+
// ("a(b"), and doing so would fully and SILENTLY stop a genuine fragmented
|
|
46
|
+
// secret split around it from ever being reassembled for the entropy check
|
|
47
|
+
// (confirmed live: a real 36-char secret split into two 18-char halves around
|
|
48
|
+
// a bare "a(b" separator went from rejected:true to a fully silent
|
|
49
|
+
// rejected:false/warnings:[] once isLabelWord treated brackets as a stop).
|
|
50
|
+
// The downgrade-only design below avoids that: the join still happens (a
|
|
51
|
+
// bracket-bearing token is ordinary, never a label word), so any joined
|
|
52
|
+
// candidate spanning real secret content still trips the entropy check —
|
|
53
|
+
// and since that joined candidate necessarily still contains the bracket
|
|
54
|
+
// character too, looksLikeCodeSyntax downgrades it to a WARNING rather than
|
|
55
|
+
// silently exempting it, unlike the fully-silent gap the hard-wall version
|
|
56
|
+
// would have reopened.
|
|
57
|
+
const CODE_SYNTAX_RE = /[()[\]]/;
|
|
58
|
+
|
|
35
59
|
// U+200B (ZERO WIDTH SPACE) is added explicitly: despite the name, it does
|
|
36
60
|
// NOT carry the Unicode White_Space property (General_Category=Cf, not Zs),
|
|
37
61
|
// so it's excluded from JS's native \s (ECMA-262 WhiteSpace production) —
|
|
@@ -147,6 +171,29 @@ function looksLikeFilenameReference(strippedToken) {
|
|
|
147
171
|
return FILENAME_REFERENCE_RE.test(strippedToken);
|
|
148
172
|
}
|
|
149
173
|
|
|
174
|
+
/**
|
|
175
|
+
* True for a candidate — a raw token OR a joinedChunkRuns result — that
|
|
176
|
+
* itself contains an unstripped '(', ')', '[', or ']': code syntax (a
|
|
177
|
+
* function-call argument or array/list-literal element) rather than a secret
|
|
178
|
+
* fragment. Deliberately NOT wired into isLabelWord/joinedChunkRuns (see
|
|
179
|
+
* CODE_SYNTAX_RE's own comment for why that hard-wall approach reopened a
|
|
180
|
+
* silent reassembly bypass) — instead this only downgrades a candidate that
|
|
181
|
+
* ALREADY tripped looksRandom, same never-exempt treatment as
|
|
182
|
+
* looksLikeCodeFilename below. Covers both backlog #14 residual shapes: a
|
|
183
|
+
* token already 20+ chars on its own with no join needed (e.g.
|
|
184
|
+
* "matches(['compliance'," — whitespace-split with no space after '(' or
|
|
185
|
+
* '[', so it's one raw token from the very first split), and a joined run
|
|
186
|
+
* that only crosses the entropy threshold once several bracket-literal
|
|
187
|
+
* tokens glue together (e.g. "['compliance','--help','-h','debug']") — the
|
|
188
|
+
* bracket character survives into the joined string either way, so this
|
|
189
|
+
* still catches it. Fully exempting this shape would let a 20+ char secret
|
|
190
|
+
* dodge rejection just by wrapping it in a fake "f(" / "[" — see the
|
|
191
|
+
* security regression test alongside looksLikeCodeFilename's.
|
|
192
|
+
*/
|
|
193
|
+
function looksLikeCodeSyntax(rawToken) {
|
|
194
|
+
return CODE_SYNTAX_RE.test(stripEdgePunctuation(rawToken));
|
|
195
|
+
}
|
|
196
|
+
|
|
150
197
|
function shannonEntropy(token) {
|
|
151
198
|
const counts = new Map();
|
|
152
199
|
for (const ch of token) counts.set(ch, (counts.get(ch) ?? 0) + 1);
|
|
@@ -396,12 +443,15 @@ export function scanForSecrets({ title = '', tags = [], body = '' } = {}) {
|
|
|
396
443
|
// Downgrading to a warning — never silently dropping the signal — matches
|
|
397
444
|
// how an email address is already handled below.
|
|
398
445
|
const randomCandidates = candidates.filter(token => !EMAIL_RE.test(token) && looksRandom(token, combined));
|
|
399
|
-
if (randomCandidates.some(token => !looksLikeCodeFilename(token))) {
|
|
446
|
+
if (randomCandidates.some(token => !looksLikeCodeFilename(token) && !looksLikeCodeSyntax(token))) {
|
|
400
447
|
reasons.push('Contains a long, random-looking string that could be a secret.');
|
|
401
448
|
}
|
|
402
449
|
if (randomCandidates.some(token => looksLikeCodeFilename(token))) {
|
|
403
450
|
warnings.push('Contains a code-filename-shaped token that also reads as high-entropy — double-check it is not a credential.');
|
|
404
451
|
}
|
|
452
|
+
if (randomCandidates.some(token => looksLikeCodeSyntax(token))) {
|
|
453
|
+
warnings.push('Contains a code-syntax-shaped token (brackets or parentheses) that also reads as high-entropy — double-check it is not a credential.');
|
|
454
|
+
}
|
|
405
455
|
|
|
406
456
|
if (EMAIL_RE.test(combined)) {
|
|
407
457
|
warnings.push('Contains an email address.');
|