blun-king-cli 9.1.571 → 9.1.572

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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 9.1.572
4
+
5
+ - Research evidence rejects source URLs containing recognized credential parameter
6
+ names in queries or parameter-shaped fragments, including nested names and
7
+ fragment routes. Rejection applies to failed sources as well as usable sources.
8
+ - Previously saved reports with these URLs are withheld during recall with the
9
+ existing `partial` / `report-invalid` result. Original snapshots are not modified
10
+ or deleted, and source addresses are never silently stripped or rewritten.
11
+ - Ordinary documentation queries and anchors retain their exact bytes. This is
12
+ a credential-name boundary, not general secret detection or a history cleanup.
13
+
14
+
3
15
  ## 9.1.571
4
16
 
5
17
  - Bind Telegram queue ranges to their owning session. A new session no longer automatically consumes work left pending by an older session; resuming a known session restores only its own pending ranges.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "9.1.571",
3
+ "version": "9.1.572",
4
4
  "description": "BLUN CLI - your own AI agent with a Telegram channel. Get it done. With BLUN.",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -80,3 +80,25 @@ unread global corpus. `limited:true` also flags result-count or output truncatio
80
80
  continuation is for the read budget, not pagination of every lexical hit. Narrow
81
81
  the query when necessary. Unavailable or partial results must remain visible as
82
82
  limitations; they do not prevent ordinary authorized research.
83
+
84
+ ## Source URL Credential Boundary
85
+
86
+ All source statuses reject URL userinfo and recognized credential parameter names
87
+ in queries and parameter-shaped fragments, including fragment routes. Names are
88
+ decoded with URLSearchParams, compared case-insensitively, and checked within
89
+ bracket/dot nesting. Examples include access_token, refresh_token, api_key,
90
+ client_secret, authorization, session_id and signed-URL credential/signature fields.
91
+
92
+ The source address is never silently stripped or rewritten. Normal queries such
93
+ as `q=access_token`, bare `key` / `code` parameters, and ordinary documentation
94
+ anchors are not classified as credentials. This is not a general secret detector:
95
+ custom keys, opaque path values, nested encoded URLs and report text still require
96
+ the explicit review described above. Passing validation does not prove public
97
+ reachability, permission to share, or absence of secrets.
98
+
99
+ New invalid reports return `saved:false` / `report-invalid` before collection
100
+ creation. Reading an older invalid report withholds that snapshot and reports
101
+ `partial` / `report-invalid`, while leaving its original bytes on disk. Other valid
102
+ snapshots may still be returned. This is not automatic cleanup or credential
103
+ revocation. Do not remove history or rewrite URLs to make validation pass.
104
+
@@ -5,6 +5,25 @@ const ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,99}$/;
5
5
  const HASH = /^[a-f0-9]{64}$/;
6
6
  const STATES = new Set(['usable', 'failed', 'challenge', 'irrelevant', 'stale']);
7
7
  const VERDICTS = new Set(['supported', 'unsupported', 'uncertain']);
8
+ const CREDENTIAL_PARAMETER = /^(?:(?:access|refresh|id|oauth)[_-]?token|token|api[_-]?key|client[_-]?secret|password|passwd|authorization|bearer|jwt|session[_-]?(?:id|token)|secret|signature|sig|x-(?:amz|goog)-(?:signature|credential|security-token))$/i;
9
+ function validateSourceUrl(value) {
10
+ let url;
11
+ try { url = new URL(value); } catch { assert.fail('source URL'); }
12
+ assert(['http:', 'https:'].includes(url.protocol) && !url.username && !url.password, 'source URL');
13
+ const parameters = [url.searchParams];
14
+ // OAuth-style fragments can hold credentials; ordinary documentation anchors stay intact.
15
+ const fragment = url.hash.slice(1);
16
+ if (fragment.includes('=')) {
17
+ parameters.push(new URLSearchParams(fragment));
18
+ const queryStart = fragment.indexOf('?');
19
+ if (queryStart !== -1) parameters.push(new URLSearchParams(fragment.slice(queryStart + 1)));
20
+ }
21
+ for (const params of parameters) {
22
+ for (const key of params.keys()) {
23
+ assert(!key.split(/[\[\].]+/).some(part => CREDENTIAL_PARAMETER.test(part)), 'source URL');
24
+ }
25
+ }
26
+ }
8
27
  function boundedArray(value, maximum, label) {
9
28
  assert(Array.isArray(value) && value.length <= maximum, label);
10
29
  return value;
@@ -38,8 +57,7 @@ function score(report) {
38
57
  const sourceMap = new Map();
39
58
  const sourceCounts = Object.fromEntries([...STATES].map(status => [status, 0]));
40
59
  for (const source of sources) {
41
- const url = new URL(source.url);
42
- assert(['http:', 'https:'].includes(url.protocol) && !url.username && !url.password, 'source URL');
60
+ validateSourceUrl(source.url);
43
61
  assert(STATES.has(source.status), 'source status');
44
62
  assert(source.retrievedAt === null || timestamp(source.retrievedAt), 'source time');
45
63
  assert(source.contentSha256 === null || HASH.test(source.contentSha256), 'source hash');