@knowttl/technitium-axi 0.1.0 → 0.1.1

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.
@@ -1,5 +1,5 @@
1
1
  import { constants } from 'node:fs';
2
- import { chmod, lstat, mkdir, open, realpath, rename, rm } from 'node:fs/promises';
2
+ import { lstat, mkdir, open, realpath, rename, rm } from 'node:fs/promises';
3
3
  import { dirname, isAbsolute, resolve } from 'node:path';
4
4
  import { randomUUID } from 'node:crypto';
5
5
  import { ToolError } from './errors.js';
@@ -15,13 +15,23 @@ export class PrivateFiles {
15
15
  insecure(path) {
16
16
  throw new ToolError('CONFIG_INSECURE', 'Unsafe file ownership, permissions or redirected path', `Use an owned regular file and chmod 600 ${path}`);
17
17
  }
18
- async direct(path) {
18
+ insecureDirectory(path) {
19
+ throw new ToolError('CONFIG_INSECURE', 'Unsafe directory ownership, permissions or redirected path', `Use an owned directory and chmod 700 ${path}`);
20
+ }
21
+ requireAbsolute(path) {
19
22
  if (!isAbsolute(path))
20
- this.insecure(path);
23
+ throw new ToolError('CONFIG_INSECURE', `Path must be absolute: ${path}`, 'Use an absolute path');
24
+ }
25
+ async direct(path) {
26
+ this.requireAbsolute(path);
21
27
  if ((await lstat(path)).isSymbolicLink() || await realpath(path) !== resolve(path))
22
28
  this.insecure(path);
23
29
  }
24
30
  async read(path, secret = true) {
31
+ return (await this.readBytes(path, secret)).toString('utf8');
32
+ }
33
+ async readBytes(path, secret = true, maxBytes = 16 * 1024 * 1024) {
34
+ this.requireAbsolute(path);
25
35
  const before = await lstat(path);
26
36
  this.requireFileProtection();
27
37
  await this.direct(path);
@@ -36,29 +46,49 @@ export class PrivateFiles {
36
46
  this.insecure(path);
37
47
  if (opened.uid !== process.getuid?.() || (opened.mode & (secret ? 0o077 : 0o022)) !== 0)
38
48
  this.insecure(path);
39
- return await handle.readFile('utf8');
49
+ if (opened.size > maxBytes)
50
+ throw new ToolError('INPUT_LIMIT', 'Protected file exceeds the input size limit', 'Use a smaller protected file');
51
+ const bytes = await handle.readFile();
52
+ if (bytes.length > maxBytes)
53
+ throw new ToolError('INPUT_LIMIT', 'Protected file exceeds the input size limit', 'Use a smaller protected file');
54
+ return bytes;
40
55
  }
41
56
  finally {
42
57
  await handle.close();
43
58
  }
44
59
  }
45
- async write(path, bytes) {
60
+ async destination(path) {
46
61
  this.requireFileProtection();
47
- if (!isAbsolute(path))
48
- this.insecure(path);
62
+ this.requireAbsolute(path);
49
63
  const directory = dirname(path);
50
64
  await mkdir(directory, { recursive: true, mode: 0o700 });
51
65
  await this.direct(directory);
52
- if ((await lstat(directory)).uid !== process.getuid?.())
53
- this.insecure(directory);
54
- await chmod(directory, 0o700);
66
+ const parent = await lstat(directory);
67
+ if (!parent.isDirectory() || parent.uid !== process.getuid?.() || (parent.mode & 0o022) !== 0)
68
+ this.insecureDirectory(directory);
55
69
  try {
56
- await this.read(path);
70
+ await this.direct(path);
71
+ const before = await lstat(path);
72
+ if (!before.isFile() || before.uid !== process.getuid?.() || (before.mode & 0o077) !== 0)
73
+ this.insecure(path);
74
+ const handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
75
+ try {
76
+ const stat = await handle.stat();
77
+ if (!stat.isFile() || stat.dev !== before.dev || stat.ino !== before.ino || stat.uid !== process.getuid?.() || (stat.mode & 0o077) !== 0)
78
+ this.insecure(path);
79
+ }
80
+ finally {
81
+ await handle.close();
82
+ }
57
83
  }
58
84
  catch (error) {
59
85
  if (error.code !== 'ENOENT')
60
86
  throw error;
61
87
  }
88
+ }
89
+ async write(path, bytes) {
90
+ await this.destination(path);
91
+ const directory = dirname(path);
62
92
  const temporary = `${path}.${randomUUID()}.tmp`;
63
93
  const handle = await open(temporary, 'wx', 0o600);
64
94
  try {
@@ -66,10 +96,42 @@ export class PrivateFiles {
66
96
  await handle.sync();
67
97
  await handle.close();
68
98
  await rename(temporary, path);
99
+ const parent = await open(directory, constants.O_RDONLY | constants.O_DIRECTORY);
100
+ try {
101
+ await parent.sync();
102
+ }
103
+ finally {
104
+ await parent.close();
105
+ }
69
106
  }
70
107
  finally {
71
108
  await handle.close();
72
109
  await rm(temporary, { force: true });
73
110
  }
74
111
  }
112
+ async create(path, bytes) {
113
+ this.requireFileProtection();
114
+ this.requireAbsolute(path);
115
+ const directory = dirname(path);
116
+ await mkdir(directory, { recursive: true, mode: 0o700 });
117
+ await this.direct(directory);
118
+ const parentStat = await lstat(directory);
119
+ if (parentStat.uid !== process.getuid?.() || (parentStat.mode & 0o077) !== 0)
120
+ this.insecureDirectory(directory);
121
+ const handle = await open(path, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
122
+ try {
123
+ await handle.writeFile(bytes);
124
+ await handle.sync();
125
+ }
126
+ finally {
127
+ await handle.close();
128
+ }
129
+ const parent = await open(directory, constants.O_RDONLY | constants.O_DIRECTORY);
130
+ try {
131
+ await parent.sync();
132
+ }
133
+ finally {
134
+ await parent.close();
135
+ }
136
+ }
75
137
  }
@@ -1,10 +1,18 @@
1
1
  // Contracts reviewed against Technitium DNS Server API at 0dd592aad21eced4221a9a46204cb1a299972ca0.
2
2
  // Only documented fields are eligible for ordinary output; unknown fields stay in protected results.
3
+ // Union of every rData field Technitium emits per record type; enum-like and numeric fields vary between names and numbers.
4
+ const recordData = { "ipAddress": "string", "nameServer": "string", "primaryNameServer": "string", "responsiblePerson": "string", "serial": "number", "refresh": "number", "retry": "number", "expire": "number", "minimum": "number", "typeCovered": "string", "algorithm": "string-or-number", "labels": "number", "originalTtl": "number", "signatureExpiration": "string", "signatureInception": "string", "keyTag": "number", "signersName": "string", "signature": "string", "flags": "string-or-number", "protocol": "string-or-number", "publicKey": "string", "computedKeyTag": "number", "dnsKeyState": "string", "computedDigests": { "array": { "digestType": "string", "digest": "string" } }, "dnsKeyStateReadyBy": "string", "hashAlgorithm": "string", "iterations": "number", "salt": "string", "nextHashedOwnerName": "string", "types": { "array": "string" }, "digestType": "string-or-number", "digest": "string", "cname": "string", "text": "string", "splitText": "boolean", "characterStringsBase64": { "array": "string" }, "forwarder": "string", "forwarderPriority": "number", "dnssecValidation": "boolean", "proxyType": "string", "proxyAddress": "string", "proxyPort": "number", "proxyUsername": "nullable",
5
+ "dname": "string", "aname": "string", "ptrName": "string", "preference": "number", "exchange": "string", "mailbox": "string", "txtDomain": "string", "priority": "number", "weight": "number", "port": "number", "target": "string",
6
+ "order": "number", "services": "string", "regexp": "string", "replacement": "string",
7
+ "fingerprintType": "string-or-number", "fingerprint": "string",
8
+ "certificateUsage": "string-or-number", "selector": "string-or-number", "matchingType": "string-or-number", "certificateAssociationData": "string",
9
+ "svcPriority": "number", "svcTargetName": "string", "svcParams": "json", "uri": "string", "tag": "string", "value": "string",
10
+ "appName": "string", "classPath": "string", "data": "string" };
3
11
  export const readContracts = [
4
12
  { "id": "OP-001", "command": "status", "path": "/api/status", "kind": "json", "confidential": false, "parameters": [], "response": { "hasDefaultCredentials": "boolean", "ssoEnabled": "boolean", "server": "string", "status": "string" } },
5
13
  { "id": "OP-006", "command": "user session get", "path": "/api/user/session/get", "kind": "json", "confidential": true, "parameters": [], "response": { "username": "string", "info": { "version": "string", "permissions": {} } } },
6
- { "id": "OP-012", "command": "user profile get", "path": "/api/user/profile/get", "kind": "json", "confidential": false, "parameters": [], "response": { "displayName": "string", "username": "string", "isSsoUser": "boolean", "totpEnabled": "boolean", "disabled": "boolean", "previousSessionLoggedOn": "string", "previousSessionRemoteAddress": "string", "recentSessionLoggedOn": "string", "recentSessionRemoteAddress": "string", "sessionTimeoutSeconds": "number", "memberOfGroups": { "array": "string" }, "sessions": { "array": { "username": "string", "isCurrentSession": "boolean", "type": "string", "lastSeen": "string", "lastSeenRemoteAddress": "string", "lastSeenUserAgent": "string", "partialToken": "string", "tokenName": "nullable" } } } },
7
- { "id": "OP-014", "command": "user check-for-update", "path": "/api/user/checkForUpdate", "kind": "json", "confidential": false, "parameters": [], "response": { "updateAvailable": "boolean", "updateVersion": "string", "currentVersion": "string", "updateTitle": "string", "updateMessage": "string", "downloadLink": "string", "instructionsLink": "string", "changeLogLink": "string" } },
14
+ { "id": "OP-012", "command": "user profile get", "path": "/api/user/profile/get", "kind": "json", "confidential": false, "parameters": [], "response": { "displayName": "string", "username": "string", "isSsoUser": "boolean", "totpEnabled": "boolean", "disabled": "boolean", "previousSessionLoggedOn": "string", "previousSessionRemoteAddress": "string", "recentSessionLoggedOn": "string", "recentSessionRemoteAddress": "string", "sessionTimeoutSeconds": "number", "memberOfGroups": { "array": "string" }, "sessions": { "array": { "username": "string", "isCurrentSession": "boolean", "type": "string", "lastSeen": "string", "lastSeenRemoteAddress": "string", "lastSeenUserAgent": "nullable", "partialToken": "string", "tokenName": "nullable" } } } },
15
+ { "id": "OP-014", "command": "user check-for-update", "path": "/api/user/checkForUpdate", "kind": "json", "confidential": false, "parameters": [], "response": { "updateAvailable": "boolean", "updateVersion": "nullable", "currentVersion": "string", "updateTitle": "nullable", "updateMessage": "nullable", "downloadLink": "nullable", "instructionsLink": "nullable", "changeLogLink": "nullable" } },
8
16
  { "id": "OP-015", "command": "dashboard metrics json", "path": "/api/dashboard/metrics/json", "kind": "json", "confidential": false, "parameters": [], "response": { "uptimestamp": "string", "uptimeSeconds": "number", "lifetimeCounters": { "totalQueries": "number", "totalNoError": "number", "totalServerFailure": "number", "totalNxDomain": "number", "totalRefused": "number", "totalAuthoritative": "number", "totalRecursive": "number", "totalCached": "number", "totalBlocked": "number", "totalDropped": "number", "totalClients": "number" } } },
9
17
  { "id": "OP-016", "command": "dashboard metrics text", "path": "/api/dashboard/metrics/text", "kind": "prometheus-text", "confidential": false, "parameters": [], "response": {} },
10
18
  { "id": "OP-017", "command": "dashboard stats get", "path": "/api/dashboard/stats/get", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "type", "flag": "--type", "type": "string", "required": false, "location": "form", "values": ["LastHour", "LastDay", "LastWeek", "LastMonth", "LastYear", "Custom"] }, { "name": "utc", "flag": "--utc", "type": "boolean", "required": false, "location": "form" }, { "name": "dontTrimQueryTypeData", "flag": "--dont-trim-query-type-data", "type": "boolean", "required": false, "location": "form" }, { "name": "start", "flag": "--start", "type": "date", "required": false, "location": "form" }, { "name": "end", "flag": "--end", "type": "date", "required": false, "location": "form" }], "response": { "stats": { "totalQueries": "number", "totalNoError": "number", "totalServerFailure": "number", "totalNxDomain": "number", "totalRefused": "number", "totalAuthoritative": "number", "totalRecursive": "number", "totalCached": "number", "totalBlocked": "number", "totalDropped": "number", "totalClients": "number", "zones": "number", "cachedEntries": "number", "allowedZones": "number", "blockedZones": "number", "allowListZones": "number", "blockListZones": "number" }, "mainChartData": { "labelFormat": "string", "labels": { "array": "string" }, "datasets": { "array": { "label": "string", "backgroundColor": "string", "borderColor": "string", "borderWidth": "number", "fill": "boolean", "data": { "array": "number" } } } }, "queryResponseChartData": { "labels": { "array": "string" }, "datasets": { "array": { "data": { "array": "number" }, "backgroundColor": { "array": "string" } } } }, "queryTypeChartData": { "labels": { "array": "string" }, "datasets": { "array": { "data": { "array": "number" }, "backgroundColor": { "array": "string" } } } }, "protocolTypeChartData": { "labels": { "array": "string" }, "datasets": { "array": { "data": { "array": "number" }, "backgroundColor": { "array": "string" } } } }, "topClients": { "array": { "name": "string", "domain": "string", "hits": "number", "rateLimited": "boolean" } }, "topDomains": { "array": { "name": "string", "hits": "number" } }, "topBlockedDomains": { "array": { "name": "string", "hits": "number" } } } },
@@ -15,12 +23,12 @@ export const readContracts = [
15
23
  { "id": "OP-031", "command": "zone options get", "path": "/api/zones/options/get", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "zone", "flag": "--zone", "type": "string", "required": true, "location": "form" }, { "name": "includeAvailableCatalogZoneNames", "flag": "--include-available-catalog-zone-names", "type": "boolean", "required": false, "location": "form" }, { "name": "includeAvailableTsigKeyNames", "flag": "--include-available-tsig-key-names", "type": "boolean", "required": false, "location": "form" }], "response": { "name": "string", "type": "string", "internal": "boolean", "dnssecStatus": "string", "notifyFailed": "boolean", "notifyFailedFor": { "array": "string" }, "disabled": "boolean", "catalog": "nullable", "overrideCatalogQueryAccess": "boolean", "overrideCatalogZoneTransfer": "boolean", "overrideCatalogNotify": "boolean", "queryAccess": "string", "queryAccessNetworkACL": { "array": "string" }, "zoneTransfer": "string", "zoneTransferNetworkACL": { "array": "string" }, "zoneTransferTsigKeyNames": { "array": "string" }, "notify": "string", "notifyNameServers": { "array": "string" }, "update": "string", "updateNetworkACL": { "array": "string" }, "updateSecurityPolicies": { "array": { "tsigKeyName": "string", "domain": "string", "allowedTypes": { "array": "string" } } }, "availableCatalogZoneNames": { "array": "string" }, "availableTsigKeyNames": { "array": "string" } } },
16
24
  { "id": "OP-033", "command": "zone permission get", "path": "/api/zones/permissions/get", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "zone", "flag": "--zone", "type": "string", "required": true, "location": "form" }, { "name": "includeUsersAndGroups", "flag": "--include-users-and-groups", "type": "boolean", "required": false, "location": "form" }], "response": { "section": "string", "subItem": "string", "userPermissions": { "array": { "username": "string", "canView": "boolean", "canModify": "boolean", "canDelete": "boolean" } }, "groupPermissions": { "array": { "name": "string", "canView": "boolean", "canModify": "boolean", "canDelete": "boolean" } }, "users": { "array": "string" }, "groups": { "array": "string" } } },
17
25
  { "id": "OP-037", "command": "zone dnssec view-ds", "path": "/api/zones/dnssec/viewDS", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "zone", "flag": "--zone", "type": "string", "required": true, "location": "form" }], "response": { "name": "string", "type": "string", "internal": "boolean", "disabled": "boolean", "dnssecStatus": "string", "dsRecords": { "array": { "keyTag": "number", "dnsKeyState": "string", "dnsKeyStateReadyBy": "string", "algorithm": "string", "publicKey": "string", "digests": { "array": { "digestType": "string", "digest": "string" } } } } } },
18
- { "id": "OP-038", "command": "zone dnssec properties get", "path": "/api/zones/dnssec/properties/get", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "zone", "flag": "--zone", "type": "string", "required": true, "location": "form" }], "response": { "name": "string", "type": "string", "internal": "boolean", "disabled": "boolean", "dnssecStatus": "string", "dnsKeyTtl": "number", "dnssecPrivateKeys": { "array": { "keyTag": "number", "keyType": "string", "algorithm": "string", "state": "string", "stateChangedOn": "string", "stateReadyBy": "string", "isRetiring": "boolean", "rolloverDays": "number" } } } },
19
- { "id": "OP-050", "command": "zone record get", "path": "/api/zones/records/get", "kind": "json", "confidential": true, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "domain", "flag": "--domain", "type": "string", "required": true, "location": "form" }, { "name": "zone", "flag": "--zone", "type": "string", "required": false, "location": "form" }, { "name": "listZone", "flag": "--list-zone", "type": "boolean", "required": false, "location": "form" }], "response": { "zone": { "name": "string", "type": "string", "internal": "boolean", "dnssecStatus": "string", "disabled": "boolean" }, "records": { "array": { "disabled": "boolean", "name": "string", "type": "string", "ttl": "number", "rData": { "ipAddress": "string", "nameServer": "string", "primaryNameServer": "string", "responsiblePerson": "string", "serial": "number", "refresh": "number", "retry": "number", "expire": "number", "minimum": "number", "typeCovered": "string", "algorithm": "string", "labels": "number", "originalTtl": "number", "signatureExpiration": "string", "signatureInception": "string", "keyTag": "number", "signersName": "string", "signature": "string", "flags": "string", "protocol": "string-or-number", "publicKey": "string", "computedKeyTag": "number", "dnsKeyState": "string", "computedDigests": { "array": { "digestType": "string", "digest": "string" } }, "dnsKeyStateReadyBy": "string", "hashAlgorithm": "string", "iterations": "number", "salt": "string", "nextHashedOwnerName": "string", "types": { "array": "string" }, "digestType": "string", "digest": "string", "cname": "string", "text": "string", "splitText": "boolean", "characterStringsBase64": { "array": "string" }, "forwarder": "string", "dnssecValidation": "boolean", "proxyType": "string", "proxyAddress": "string", "proxyPort": "number", "proxyUsername": "nullable" }, "dnssecStatus": "string", "lastUsedOn": "string" } } } },
20
- { "id": "OP-053", "command": "cache list", "path": "/api/cache/list", "kind": "json", "confidential": false, "parameters": [{ "name": "domain", "flag": "--domain", "type": "string", "required": false, "location": "form" }, { "name": "direction", "flag": "--direction", "type": "string", "required": false, "location": "form", "values": ["up", "down"] }], "response": { "domain": "string", "zones": { "array": "string" }, "records": { "array": { "name": "string", "type": "string", "ttl": "string", "rData": { "value": "string" } } } } },
21
- { "id": "OP-056", "command": "allowed list", "path": "/api/allowed/list", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "domain", "flag": "--domain", "type": "string", "required": false, "location": "form" }, { "name": "direction", "flag": "--direction", "type": "string", "required": false, "location": "form", "values": ["up", "down"] }], "response": { "domain": "string", "zones": { "array": "string" }, "records": { "array": { "name": "string", "type": "string", "ttl": "string", "rData": { "value": "string", "primaryNameServer": "string", "responsiblePerson": "string", "serial": "number", "refresh": "number", "retry": "number", "expire": "number", "minimum": "number" } } } } },
26
+ { "id": "OP-038", "command": "zone dnssec properties get", "path": "/api/zones/dnssec/properties/get", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "zone", "flag": "--zone", "type": "string", "required": true, "location": "form" }], "response": { "name": "string", "type": "string", "internal": "boolean", "disabled": "boolean", "dnssecStatus": "string", "nsec3Iterations": "number", "nsec3SaltLength": "number", "dnsKeyTtl": "number", "dnssecPrivateKeys": { "array": { "keyTag": "number", "keyType": "string", "algorithm": "string", "algorithmNumber": "number", "state": "string", "stateChangedOn": "string", "stateReadyBy": "string", "isRetiring": "boolean", "rolloverDays": "number" } } } },
27
+ { "id": "OP-050", "command": "zone record get", "path": "/api/zones/records/get", "kind": "json", "confidential": true, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "domain", "flag": "--domain", "type": "string", "required": true, "location": "form" }, { "name": "zone", "flag": "--zone", "type": "string", "required": false, "location": "form" }, { "name": "listZone", "flag": "--list-zone", "type": "boolean", "required": false, "location": "form" }], "response": { "zone": { "name": "string", "type": "string", "internal": "boolean", "dnssecStatus": "string", "disabled": "boolean" }, "records": { "array": { "disabled": "boolean", "name": "string", "type": "string", "ttl": "number", "rData": recordData, "dnssecStatus": "string", "lastUsedOn": "string" } } } },
28
+ { "id": "OP-053", "command": "cache list", "path": "/api/cache/list", "kind": "json", "confidential": false, "parameters": [{ "name": "domain", "flag": "--domain", "type": "string", "required": false, "location": "form" }, { "name": "direction", "flag": "--direction", "type": "string", "required": false, "location": "form", "values": ["up", "down"] }], "response": { "domain": "string", "zones": { "array": "string" }, "records": { "array": { "name": "string", "type": "string", "ttl": "string-or-number", "rData": { ...recordData, "dataType": "string" } } } } },
29
+ { "id": "OP-056", "command": "allowed list", "path": "/api/allowed/list", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "domain", "flag": "--domain", "type": "string", "required": false, "location": "form" }, { "name": "direction", "flag": "--direction", "type": "string", "required": false, "location": "form", "values": ["up", "down"] }], "response": { "domain": "string", "zones": { "array": "string" }, "records": { "array": { "name": "string", "type": "string", "ttl": "string-or-number", "rData": recordData } } } },
22
30
  { "id": "OP-061", "command": "allowed export", "path": "/api/allowed/export", "kind": "plain-text", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }], "response": {} },
23
- { "id": "OP-062", "command": "blocked list", "path": "/api/blocked/list", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "domain", "flag": "--domain", "type": "string", "required": false, "location": "form" }, { "name": "direction", "flag": "--direction", "type": "string", "required": false, "location": "form", "values": ["up", "down"] }], "response": { "domain": "string", "zones": { "array": "string" }, "records": { "array": { "name": "string", "type": "string", "ttl": "string", "rData": { "value": "string", "primaryNameServer": "string", "responsiblePerson": "string", "serial": "number", "refresh": "number", "retry": "number", "expire": "number", "minimum": "number" } } } } },
31
+ { "id": "OP-062", "command": "blocked list", "path": "/api/blocked/list", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "domain", "flag": "--domain", "type": "string", "required": false, "location": "form" }, { "name": "direction", "flag": "--direction", "type": "string", "required": false, "location": "form", "values": ["up", "down"] }], "response": { "domain": "string", "zones": { "array": "string" }, "records": { "array": { "name": "string", "type": "string", "ttl": "string-or-number", "rData": recordData } } } },
24
32
  { "id": "OP-067", "command": "blocked export", "path": "/api/blocked/export", "kind": "plain-text", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }], "response": {} },
25
33
  { "id": "OP-068", "command": "app list", "path": "/api/apps/list", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }], "response": { "apps": { "array": { "name": "string", "version": "string", "dnsApps": { "array": { "classPath": "string", "description": "string", "isAppRecordRequestHandler": "boolean", "isRequestController": "boolean", "isAuthoritativeRequestHandler": "boolean", "isRequestBlockingHandler": "boolean", "isQueryLogger": "boolean", "isPostProcessor": "boolean", "recordDataTemplate": "nullable" } } } } } },
26
34
  { "id": "OP-069", "command": "app list-store-apps", "path": "/api/apps/listStoreApps", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }], "response": { "storeApps": { "array": { "name": "string", "version": "string", "description": "string", "url": "string", "size": "string", "installed": "boolean", "installedVersion": "string", "updateAvailable": "boolean" } } } },
@@ -31,20 +39,20 @@ export const readContracts = [
31
39
  { "id": "OP-084", "command": "settings backup", "path": "/api/settings/backup", "kind": "zip", "confidential": true, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "authConfig", "flag": "--auth-config", "type": "boolean", "required": false, "location": "form" }, { "name": "clusterConfig", "flag": "--cluster-config", "type": "boolean", "required": false, "location": "form" }, { "name": "webServiceSettings", "flag": "--web-service-settings", "type": "boolean", "required": false, "location": "form" }, { "name": "dnsSettings", "flag": "--dns-settings", "type": "boolean", "required": false, "location": "form" }, { "name": "logSettings", "flag": "--log-settings", "type": "boolean", "required": false, "location": "form" }, { "name": "zones", "flag": "--zones", "type": "boolean", "required": false, "location": "form" }, { "name": "allowedZones", "flag": "--allowed-zones", "type": "boolean", "required": false, "location": "form" }, { "name": "blockedZones", "flag": "--blocked-zones", "type": "boolean", "required": false, "location": "form" }, { "name": "blockLists", "flag": "--block-lists", "type": "boolean", "required": false, "location": "form" }, { "name": "apps", "flag": "--apps", "type": "boolean", "required": false, "location": "form" }, { "name": "scopes", "flag": "--scopes", "type": "boolean", "required": false, "location": "form" }, { "name": "stats", "flag": "--stats", "type": "boolean", "required": false, "location": "form" }, { "name": "logs", "flag": "--logs", "type": "boolean", "required": false, "location": "form" }], "response": {} },
32
40
  { "id": "OP-086", "command": "dhcp lease list", "path": "/api/dhcp/leases/list", "kind": "json", "confidential": false, "parameters": [], "response": { "leases": { "array": { "scope": "string", "type": "string", "hardwareAddress": "string", "clientIdentifier": "string", "address": "string", "hostName": "nullable", "leaseObtained": "string", "leaseExpires": "string" } } } },
33
41
  { "id": "OP-090", "command": "dhcp scope list", "path": "/api/dhcp/scopes/list", "kind": "json", "confidential": false, "parameters": [], "response": { "scopes": { "array": { "name": "string", "enabled": "boolean", "startingAddress": "string", "endingAddress": "string", "subnetMask": "string", "networkAddress": "string", "broadcastAddress": "string" } } } },
34
- { "id": "OP-091", "command": "dhcp scope get", "path": "/api/dhcp/scopes/get", "kind": "json", "confidential": false, "parameters": [{ "name": "name", "flag": "--name", "type": "string", "required": true, "location": "form" }], "response": { "name": "string", "startingAddress": "string", "endingAddress": "string", "subnetMask": "string", "leaseTimeDays": "number", "leaseTimeHours": "number", "leaseTimeMinutes": "number", "offerDelayTime": "number", "pingCheckEnabled": "boolean", "pingCheckTimeout": "number", "pingCheckRetries": "number", "domainName": "string", "domainSearchList": { "array": "string" }, "dnsUpdates": "boolean", "dnsOverwriteForDynamicLease": "boolean", "dnsTtl": "number", "serverAddress": "string", "serverHostName": "string", "bootFileName": "string", "routerAddress": "string", "useThisDnsServer": "boolean", "dnsServers": { "array": "string" }, "winsServers": { "array": "string" }, "ntpServers": { "array": "string" }, "staticRoutes": { "array": { "destination": "string", "subnetMask": "string", "router": "string" } }, "vendorInfo": { "array": { "identifier": "string", "information": "string" } }, "capwapAcIpAddresses": { "array": "string" }, "tftpServerAddresses": { "array": "string" }, "genericOptions": { "array": { "code": "number", "value": "string" } }, "exclusions": { "array": { "startingAddress": "string", "endingAddress": "string" } }, "reservedLeases": { "array": { "hostName": "nullable", "hardwareAddress": "string", "address": "string", "comments": "string" } }, "allowOnlyReservedLeases": "boolean", "blockLocallyAdministeredMacAddresses": "boolean", "ignoreClientIdentifierOption": "boolean" } },
35
- { "id": "OP-098", "command": "admin session list", "path": "/api/admin/sessions/list", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }], "response": { "sessions": { "array": { "username": "string", "isCurrentSession": "boolean", "type": "string", "lastSeen": "string", "lastSeenRemoteAddress": "string", "lastSeenUserAgent": "string", "partialToken": "string", "tokenName": "nullable" } } } },
42
+ { "id": "OP-091", "command": "dhcp scope get", "path": "/api/dhcp/scopes/get", "kind": "json", "confidential": false, "parameters": [{ "name": "name", "flag": "--name", "type": "string", "required": true, "location": "form" }], "response": { "name": "string", "startingAddress": "string", "endingAddress": "string", "subnetMask": "string", "leaseTimeDays": "number", "leaseTimeHours": "number", "leaseTimeMinutes": "number", "offerDelayTime": "number", "pingCheckEnabled": "boolean", "pingCheckTimeout": "number", "pingCheckRetries": "number", "domainName": "string", "domainSearchList": { "array": "string" }, "dnsUpdates": "boolean", "dnsOverwriteForDynamicLease": "boolean", "dnsTtl": "number", "serverAddress": "string", "serverHostName": "string", "bootFileName": "string", "routerAddress": "string", "useThisDnsServer": "boolean", "dnsServers": { "array": "string" }, "winsServers": { "array": "string" }, "ntpServers": { "array": "string" }, "ntpServerDomainNames": { "array": "string" }, "staticRoutes": { "array": { "destination": "string", "subnetMask": "string", "router": "string" } }, "vendorInfo": { "array": { "identifier": "string", "information": "string" } }, "capwapAcIpAddresses": { "array": "string" }, "tftpServerAddresses": { "array": "string" }, "genericOptions": { "array": { "code": "number", "value": "string" } }, "exclusions": { "array": { "startingAddress": "string", "endingAddress": "string" } }, "reservedLeases": { "array": { "hostName": "nullable", "hardwareAddress": "string", "address": "string", "comments": "nullable" } }, "allowOnlyReservedLeases": "boolean", "blockLocallyAdministeredMacAddresses": "boolean", "ignoreClientIdentifierOption": "boolean" } },
43
+ { "id": "OP-098", "command": "admin session list", "path": "/api/admin/sessions/list", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }], "response": { "sessions": { "array": { "username": "string", "isCurrentSession": "boolean", "type": "string", "lastSeen": "string", "lastSeenRemoteAddress": "string", "lastSeenUserAgent": "nullable", "partialToken": "string", "tokenName": "nullable" } } } },
36
44
  { "id": "OP-101", "command": "admin user list", "path": "/api/admin/users/list", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }], "response": { "users": { "array": { "displayName": "string", "username": "string", "isSsoUser": "boolean", "disabled": "boolean", "previousSessionLoggedOn": "string", "previousSessionRemoteAddress": "string", "recentSessionLoggedOn": "string", "recentSessionRemoteAddress": "string" } } } },
37
- { "id": "OP-103", "command": "admin user get", "path": "/api/admin/users/get", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "user", "flag": "--user", "type": "string", "required": true, "location": "form" }, { "name": "includeGroups", "flag": "--include-groups", "type": "boolean", "required": false, "location": "form" }], "response": { "displayName": "string", "username": "string", "isSsoUser": "boolean", "totpEnabled": "boolean", "disabled": "boolean", "previousSessionLoggedOn": "string", "previousSessionRemoteAddress": "string", "recentSessionLoggedOn": "string", "recentSessionRemoteAddress": "string", "sessionTimeoutSeconds": "number", "memberOfGroups": { "array": "string" }, "sessions": { "array": { "username": "string", "isCurrentSession": "boolean", "type": "string", "lastSeen": "string", "lastSeenRemoteAddress": "string", "lastSeenUserAgent": "string", "partialToken": "string", "tokenName": "nullable" } }, "groups": { "array": "string" } } },
45
+ { "id": "OP-103", "command": "admin user get", "path": "/api/admin/users/get", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "user", "flag": "--user", "type": "string", "required": true, "location": "form" }, { "name": "includeGroups", "flag": "--include-groups", "type": "boolean", "required": false, "location": "form" }], "response": { "displayName": "string", "username": "string", "isSsoUser": "boolean", "totpEnabled": "boolean", "disabled": "boolean", "previousSessionLoggedOn": "string", "previousSessionRemoteAddress": "string", "recentSessionLoggedOn": "string", "recentSessionRemoteAddress": "string", "sessionTimeoutSeconds": "number", "memberOfGroups": { "array": "string" }, "sessions": { "array": { "username": "string", "isCurrentSession": "boolean", "type": "string", "lastSeen": "string", "lastSeenRemoteAddress": "string", "lastSeenUserAgent": "nullable", "partialToken": "string", "tokenName": "nullable" } }, "groups": { "array": "string" } } },
38
46
  { "id": "OP-106", "command": "admin group list", "path": "/api/admin/groups/list", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }], "response": { "groups": { "array": { "name": "string", "description": "string" } } } },
39
47
  { "id": "OP-108", "command": "admin group get", "path": "/api/admin/groups/get", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "group", "flag": "--group", "type": "string", "required": true, "location": "form" }, { "name": "includeUsers", "flag": "--include-users", "type": "boolean", "required": false, "location": "form" }], "response": { "name": "string", "description": "string", "members": { "array": "string" }, "users": { "array": "string" } } },
40
48
  { "id": "OP-111", "command": "admin permission list", "path": "/api/admin/permissions/list", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }], "response": { "permissions": { "array": { "section": "string", "userPermissions": { "array": { "username": "string", "canView": "boolean", "canModify": "boolean", "canDelete": "boolean" } }, "groupPermissions": { "array": { "name": "string", "canView": "boolean", "canModify": "boolean", "canDelete": "boolean" } } } } } },
41
49
  { "id": "OP-112", "command": "admin permission get", "path": "/api/admin/permissions/get", "kind": "json", "confidential": false, "parameters": [{ "name": "section", "flag": "--section", "type": "string", "required": true, "location": "form" }, { "name": "includeUsersAndGroups", "flag": "--include-users-and-groups", "type": "boolean", "required": false, "location": "form" }], "response": { "section": "string", "userPermissions": { "array": { "username": "string", "canView": "boolean", "canModify": "boolean", "canDelete": "boolean" } }, "groupPermissions": { "array": { "name": "string", "canView": "boolean", "canModify": "boolean", "canDelete": "boolean" } }, "users": { "array": "string" }, "groups": { "array": "string" } } },
42
50
  { "id": "OP-114", "command": "admin sso get", "path": "/api/admin/sso/get", "kind": "json", "confidential": false, "parameters": [{ "name": "includeGroups", "flag": "--include-groups", "type": "boolean", "required": false, "location": "form" }], "response": { "ssoEnabled": "boolean", "ssoAuthority": "nullable", "ssoClientId": "nullable", "ssoMetadataAddress": "nullable", "ssoAllowSignup": "boolean", "ssoAllowSignupOnlyForMappedUsers": "boolean", "ssoGroupMap": { "array": { "remoteGroup": "string", "localGroup": "string" } }, "localGroups": { "array": "string" }, "ssoScopes": { "array": "string" } } },
43
- { "id": "OP-118", "command": "admin cluster state", "path": "/api/admin/cluster/state", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "includeServerIpAddresses", "flag": "--include-server-ip-addresses", "type": "boolean", "required": false, "location": "form" }], "response": { "clusterInitialized": "boolean", "dnsServerDomain": "string", "version": "string", "clusterDomain": "string", "heartbeatRefreshIntervalSeconds": "number", "heartbeatRetryIntervalSeconds": "number", "configRefreshIntervalSeconds": "number", "configRetryIntervalSeconds": "number", "configLastSynced": "string", "clusterNodes": { "array": { "id": "number", "name": "string", "url": "string", "type": "string", "state": "string", "version": "string", "lastSeen": "string", "ipAddress": "string" } }, "serverIpAddresses": { "array": "string" } } },
51
+ { "id": "OP-118", "command": "admin cluster state", "path": "/api/admin/cluster/state", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "includeServerIpAddresses", "flag": "--include-server-ip-addresses", "type": "boolean", "required": false, "location": "form" }], "response": { "clusterInitialized": "boolean", "dnsServerDomain": "string", "version": "string", "clusterDomain": "string", "heartbeatRefreshIntervalSeconds": "number", "heartbeatRetryIntervalSeconds": "number", "configRefreshIntervalSeconds": "number", "configRetryIntervalSeconds": "number", "configLastSynced": "string", "clusterNodes": { "array": { "id": "number", "name": "string", "url": "string", "type": "string", "state": "string", "version": "string", "lastSeen": "string", "ipAddresses": { "array": "string" }, "upSince": "string", "configLastSynced": "string" } }, "serverIpAddresses": { "array": "string" } } },
44
52
  { "id": "OP-125", "command": "admin cluster primary transfer-config", "path": "/api/admin/cluster/primary/transferConfig", "kind": "zip", "confidential": true, "parameters": [{ "name": "includeZones", "flag": "--include-zones", "type": "zone-list", "required": false, "location": "form" }, { "name": "If-Modified-Since", "flag": "--if-modified-since", "type": "http-date", "required": false, "location": "header" }], "response": {} },
45
53
  { "id": "OP-134", "command": "log list", "path": "/api/logs/list", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }], "response": { "logFiles": { "array": { "fileName": "string", "size": "string" } } } },
46
54
  { "id": "OP-135", "command": "log download", "path": "/api/logs/download", "kind": "log-text", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "fileName", "flag": "--file-name", "type": "string", "required": true, "location": "form" }, { "name": "limit", "flag": "--limit", "type": "integer", "required": false, "location": "form" }], "response": {} },
47
- { "id": "OP-138", "command": "log query", "path": "/api/logs/query", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "name", "flag": "--name", "type": "string", "required": true, "location": "form" }, { "name": "classPath", "flag": "--class-path", "type": "string", "required": true, "location": "form" }, { "name": "pageNumber", "flag": "--page-number", "type": "integer", "required": false, "location": "form" }, { "name": "entriesPerPage", "flag": "--entries-per-page", "type": "integer", "required": false, "location": "form" }, { "name": "descendingOrder", "flag": "--descending-order", "type": "boolean", "required": false, "location": "form" }, { "name": "start", "flag": "--start", "type": "date", "required": false, "location": "form" }, { "name": "end", "flag": "--end", "type": "date", "required": false, "location": "form" }, { "name": "clientIpAddress", "flag": "--client-ip-address", "type": "string", "required": false, "location": "form" }, { "name": "protocol", "flag": "--protocol", "type": "string", "required": false, "location": "form", "values": ["Udp", "Tcp", "Tls", "Https", "Quic"] }, { "name": "responseType", "flag": "--response-type", "type": "string", "required": false, "location": "form", "values": ["Authoritative", "Recursive", "Cached", "Blocked", "UpstreamBlocked", "CacheBlocked"] }, { "name": "rcode", "flag": "--rcode", "type": "string", "required": false, "location": "form" }, { "name": "qname", "flag": "--qname", "type": "string", "required": false, "location": "form" }, { "name": "qtype", "flag": "--qtype", "type": "string", "required": false, "location": "form" }, { "name": "qclass", "flag": "--qclass", "type": "string", "required": false, "location": "form" }], "response": { "pageNumber": "number", "totalPages": "number", "totalEntries": "number", "entries": { "array": { "rowNumber": "number", "timestamp": "string", "clientIpAddress": "string", "protocol": "string", "responseType": "string", "responseRtt": "number", "rcode": "string", "qname": "string", "qtype": "string", "qclass": "string", "answer": "string" } } } },
55
+ { "id": "OP-138", "command": "log query", "path": "/api/logs/query", "kind": "json", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "name", "flag": "--name", "type": "string", "required": true, "location": "form" }, { "name": "classPath", "flag": "--class-path", "type": "string", "required": true, "location": "form" }, { "name": "pageNumber", "flag": "--page-number", "type": "integer", "required": false, "location": "form" }, { "name": "entriesPerPage", "flag": "--entries-per-page", "type": "integer", "required": false, "location": "form" }, { "name": "descendingOrder", "flag": "--descending-order", "type": "boolean", "required": false, "location": "form" }, { "name": "start", "flag": "--start", "type": "date", "required": false, "location": "form" }, { "name": "end", "flag": "--end", "type": "date", "required": false, "location": "form" }, { "name": "clientIpAddress", "flag": "--client-ip-address", "type": "string", "required": false, "location": "form" }, { "name": "protocol", "flag": "--protocol", "type": "string", "required": false, "location": "form", "values": ["Udp", "Tcp", "Tls", "Https", "Quic"] }, { "name": "responseType", "flag": "--response-type", "type": "string", "required": false, "location": "form", "values": ["Authoritative", "Recursive", "Cached", "Blocked", "UpstreamBlocked", "CacheBlocked"] }, { "name": "rcode", "flag": "--rcode", "type": "string", "required": false, "location": "form" }, { "name": "qname", "flag": "--qname", "type": "string", "required": false, "location": "form" }, { "name": "qtype", "flag": "--qtype", "type": "string", "required": false, "location": "form" }, { "name": "qclass", "flag": "--qclass", "type": "string", "required": false, "location": "form" }], "response": { "pageNumber": "number", "totalPages": "number", "totalEntries": "number", "entries": { "array": { "rowNumber": "number", "timestamp": "string", "clientIpAddress": "string", "protocol": "string", "responseType": "string", "responseRtt": "nullable-number", "rcode": "string", "qname": "nullable", "qtype": "nullable", "qclass": "nullable", "answer": "nullable" } } } },
48
56
  { "id": "OP-139", "command": "log export", "path": "/api/logs/export", "kind": "csv", "confidential": false, "parameters": [{ "name": "node", "flag": "--node", "type": "string", "required": false, "location": "form" }, { "name": "name", "flag": "--name", "type": "string", "required": true, "location": "form" }, { "name": "classPath", "flag": "--class-path", "type": "string", "required": true, "location": "form" }, { "name": "start", "flag": "--start", "type": "date", "required": false, "location": "form" }, { "name": "end", "flag": "--end", "type": "date", "required": false, "location": "form" }, { "name": "clientIpAddress", "flag": "--client-ip-address", "type": "string", "required": false, "location": "form" }, { "name": "protocol", "flag": "--protocol", "type": "string", "required": false, "location": "form", "values": ["Udp", "Tcp", "Tls", "Https", "Quic"] }, { "name": "responseType", "flag": "--response-type", "type": "string", "required": false, "location": "form", "values": ["Authoritative", "Recursive", "Cached", "Blocked", "UpstreamBlocked", "CacheBlocked"] }, { "name": "rcode", "flag": "--rcode", "type": "string", "required": false, "location": "form" }, { "name": "qname", "flag": "--qname", "type": "string", "required": false, "location": "form" }, { "name": "qtype", "flag": "--qtype", "type": "string", "required": false, "location": "form" }, { "name": "qclass", "flag": "--qclass", "type": "string", "required": false, "location": "form" }], "response": {} },
49
57
  { "id": "OP-141", "command": "admin ldap get", "path": "/api/admin/ldap/get", "kind": "json", "confidential": false, "parameters": [{ "name": "includeGroups", "flag": "--include-groups", "type": "boolean", "required": false, "location": "form" }], "response": { "ldapEnabled": "boolean", "ldapServer": "nullable", "ldapPort": "number", "ldapSslOption": "string", "ldapIgnoreSslErrors": "boolean", "ldapBindUsername": "nullable", "ldapSearchBase": "nullable", "ldapUserSearchFilter": "nullable", "ldapGroupAttribute": "nullable", "ldapAllowSignup": "boolean", "ldapAllowSignupOnlyForMappedUsers": "boolean", "ldapGroupMap": { "array": { "remoteGroup": "string", "localGroup": "string" } }, "localGroups": { "array": "string" } } }
50
58
  ];
package/dist/reads.js CHANGED
@@ -2,11 +2,13 @@ import { createHash } from 'node:crypto';
2
2
  import { ToolError } from './errors.js';
3
3
  import { readContracts } from './read-contracts.js';
4
4
  import { checkStatus, vendorRequest } from './vendor.js';
5
+ import { validateNode } from './routing.js';
5
6
  const invalid = () => { throw new ToolError('UPSTREAM_INVALID', 'Unexpected server response fields or types', 'Check the selected server API version and operation', 1); };
6
7
  const usage = (message) => { throw new ToolError('USAGE', message, 'Run the selected command with --help'); };
7
8
  const record = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
8
9
  const sensitive = /password|secret|^token$|^privateKey$|^stackTrace$|^innerError/i;
9
10
  export function readInputs(contract, options) {
11
+ validateNode(contract.id, options['--node']);
10
12
  const form = new URLSearchParams();
11
13
  const headers = {};
12
14
  for (const parameter of contract.parameters) {
@@ -41,16 +43,23 @@ export function readInputs(contract, options) {
41
43
  }
42
44
  return { form, headers };
43
45
  }
44
- function project(value, shape, nullable = false) {
46
+ // Fields the contract does not list are dropped; `omitted` collects them (indexes folded to []) so the caller can report incompleteness.
47
+ function project(value, shape, nullable = false, path = 'response', omitted) {
45
48
  if (typeof shape === 'string') {
46
49
  if (shape === 'unknown')
47
50
  return null;
51
+ if (shape === 'json')
52
+ return value;
48
53
  if (shape === 'nullable') {
49
54
  if (value !== null && typeof value !== 'string')
50
55
  invalid();
51
56
  }
57
+ else if (shape === 'nullable-number') {
58
+ if (value !== null && typeof value !== 'number')
59
+ invalid();
60
+ }
52
61
  else if (shape === 'string-or-number') {
53
- // DNSKEY encodes the protocol as a number; FWD encodes its transport as a string.
62
+ // Protocol and flags vary between numeric data and enum names across record families.
54
63
  if (typeof value !== 'string' && typeof value !== 'number')
55
64
  invalid();
56
65
  }
@@ -70,7 +79,7 @@ function project(value, shape, nullable = false) {
70
79
  if (identity && !Object.hasOwn(item, identity))
71
80
  return invalid();
72
81
  }
73
- return project(item, itemShape);
82
+ return project(item, itemShape, false, `${path}[]`, omitted);
74
83
  });
75
84
  }
76
85
  if (value === null && nullable)
@@ -79,8 +88,13 @@ function project(value, shape, nullable = false) {
79
88
  return invalid();
80
89
  if (Object.hasOwn(shape, 'canView') && ['canView', 'canModify', 'canDelete'].some(key => !Object.hasOwn(value, key)))
81
90
  invalid();
91
+ if (omitted) {
92
+ const dropped = Object.keys(value).filter(key => !Object.hasOwn(shape, key) && !sensitive.test(key));
93
+ if (dropped.length)
94
+ omitted[path] = new Set([...omitted[path] ?? [], ...dropped]);
95
+ }
82
96
  return Object.fromEntries(Object.entries(shape).filter(([key]) => !sensitive.test(key) && Object.hasOwn(value, key))
83
- .map(([key, field]) => [key, project(value[key], field, key === 'proxy')]));
97
+ .map(([key, field]) => [key, project(value[key], field, key === 'proxy', `${path}.${key}`, omitted)]));
84
98
  }
85
99
  const listKeys = {
86
100
  'OP-020': 'zones', 'OP-021': 'catalogZoneNames', 'OP-037': 'dsRecords', 'OP-038': 'dnssecPrivateKeys',
@@ -276,7 +290,12 @@ export async function readOperation(profile, command, token, options, full) {
276
290
  stoppedBy = 'PROVIDER_TOTAL_MISMATCH';
277
291
  }
278
292
  }
279
- let projected = project(payload, contract.response);
293
+ // DNSSEC properties and record-bearing reads must disclose omitted fields before claiming completeness.
294
+ const omitted = ['OP-038', 'OP-050', 'OP-053'].includes(contract.id) ? {} : undefined;
295
+ let projected = project(payload, contract.response, false, 'response', omitted);
296
+ const omittedFields = Object.fromEntries(Object.entries(omitted ?? {}).map(([path, keys]) => [path, [...keys].sort()]));
297
+ if (Object.keys(omittedFields).length)
298
+ complete = false;
280
299
  if (fields && !(rowShape && typeof rowShape === 'object')) {
281
300
  projected = Object.fromEntries(fields.filter(field => Object.hasOwn(projected, field)).map(field => [field, projected[field]]));
282
301
  }
@@ -325,6 +344,7 @@ export async function readOperation(profile, command, token, options, full) {
325
344
  return { bytes: response.bytes, projection: { status: 'ok', response: projected,
326
345
  output: { complete, pages, requests, bytesRead, collections: bounds,
327
346
  ...(Object.keys(truncations).length ? { truncations } : {}),
347
+ ...(Object.keys(omittedFields).length ? { omittedFields } : {}),
328
348
  ...(stoppedBy ? { stoppedBy } : {}),
329
349
  ...(continuation === undefined ? {} : { nextPage: continuation }) } } };
330
350
  }
@@ -0,0 +1,14 @@
1
+ import { ToolError } from './errors.js';
2
+ // DnsWebService.GetClusterNodeTypeForPath at vendor revision 0dd592aad21eced4221a9a46204cb1a299972ca0.
3
+ const primary = new Set(['003', '008', '009', '010', '011', '013', '057', '058', '059', '060', '063', '064', '065', '066', '070', '071', '072', '073', '074', '076', '099', '102', '104', '105', '107', '109', '110', '115', '116', '117', '142']);
4
+ const local = new Set(['001', '002', '005', '006', '007']);
5
+ export function routing(id) {
6
+ const number = id.slice(3);
7
+ return { target: primary.has(number) ? 'primary via vendor proxy' : local.has(number) ? 'current node' : 'selected or named node via vendor proxy',
8
+ cluster: ['OP-017', 'OP-018'].includes(id) ? 'aggregate' : ['OP-082', 'OP-083'].includes(id) ? 'asynchronous relay' : 'unsupported',
9
+ ...(id === 'OP-080' ? { replication: 'Listeners and local settings stay local; cluster parameters propagate through the primary' } : {}) };
10
+ }
11
+ export function validateNode(id, node) {
12
+ if (node === 'cluster' && routing(id).cluster === 'unsupported')
13
+ throw new ToolError('USAGE', 'node=cluster is unsupported for this operation', 'Select a named node or omit node to use the configured server');
14
+ }
package/dist/vendor.js CHANGED
@@ -7,7 +7,7 @@ export class Redactor {
7
7
  add(value) {
8
8
  if (!value)
9
9
  return;
10
- for (const form of [value, JSON.stringify(value).slice(1, -1), encodeURIComponent(value), encodeURI(value)])
10
+ for (const form of [value, JSON.stringify(value).slice(1, -1), encodeURIComponent(value), encodeURI(value), Buffer.from(value).toString('base64')])
11
11
  this.values.add(form);
12
12
  }
13
13
  text(value) {
@@ -64,6 +64,10 @@ export async function vendorRequest(profile, path, body, token, headers, limits)
64
64
  throw new ToolError('TRANSPORT_FAILED', 'Verified HTTPS request failed', 'Check origin, caFile and tlsServerName; certificate verification is required', 1);
65
65
  });
66
66
  }
67
+ // Vendor refusal text is shown to the operator: strip control characters and bound the length.
68
+ export function vendorMessage(value) {
69
+ return typeof value.errorMessage === 'string' ? value.errorMessage.replace(/[\x00-\x1f\x7f]+/g, ' ').trim().slice(0, 300) || undefined : undefined;
70
+ }
67
71
  export function checkStatus(value) {
68
72
  if (!object(value) || typeof value.status !== 'string')
69
73
  responseInvalid();
@@ -72,7 +76,7 @@ export function checkStatus(value) {
72
76
  if (value.status === '2fa-required')
73
77
  throw new ToolError('TWO_FACTOR_REQUIRED', 'The server requires a second authentication factor', 'Supply a previously authenticated credential', 1);
74
78
  if (value.status === 'error')
75
- throw new ToolError('UPSTREAM_ERROR', 'Server reported an operation failure', 'Check the selected identity and operation permissions', 1);
79
+ throw new ToolError('UPSTREAM_ERROR', `Server reported an operation failure${vendorMessage(value) ? `: ${vendorMessage(value)}` : ''}`, 'Check the selected identity and operation permissions', 1);
76
80
  if (value.status !== 'ok')
77
81
  responseInvalid();
78
82
  }