@secureport/sdk 0.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/LICENSE +21 -0
- package/README.md +73 -0
- package/dist/client.d.ts +135 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +191 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +80 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +118 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/pagination.d.ts +27 -0
- package/dist/pagination.d.ts.map +1 -0
- package/dist/pagination.js +19 -0
- package/dist/pagination.js.map +1 -0
- package/dist/resources/branding.d.ts +49 -0
- package/dist/resources/branding.d.ts.map +1 -0
- package/dist/resources/branding.js +38 -0
- package/dist/resources/branding.js.map +1 -0
- package/dist/resources/issues.d.ts +248 -0
- package/dist/resources/issues.d.ts.map +1 -0
- package/dist/resources/issues.js +100 -0
- package/dist/resources/issues.js.map +1 -0
- package/dist/resources/reports.d.ts +83 -0
- package/dist/resources/reports.d.ts.map +1 -0
- package/dist/resources/reports.js +50 -0
- package/dist/resources/reports.js.map +1 -0
- package/dist/resources/runs.d.ts +474 -0
- package/dist/resources/runs.d.ts.map +1 -0
- package/dist/resources/runs.js +281 -0
- package/dist/resources/runs.js.map +1 -0
- package/dist/resources/suppression-rules.d.ts +43 -0
- package/dist/resources/suppression-rules.d.ts.map +1 -0
- package/dist/resources/suppression-rules.js +33 -0
- package/dist/resources/suppression-rules.js.map +1 -0
- package/dist/resources/targets.d.ts +126 -0
- package/dist/resources/targets.d.ts.map +1 -0
- package/dist/resources/targets.js +83 -0
- package/dist/resources/targets.js.map +1 -0
- package/package.json +55 -0
- package/src/client.ts +328 -0
- package/src/errors.ts +120 -0
- package/src/index.ts +100 -0
- package/src/pagination.ts +41 -0
- package/src/resources/branding.ts +59 -0
- package/src/resources/issues.ts +344 -0
- package/src/resources/reports.ts +104 -0
- package/src/resources/runs.ts +635 -0
- package/src/resources/suppression-rules.ts +69 -0
- package/src/resources/targets.ts +179 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { parseSnapshot } from '@secureport/core';
|
|
2
|
+
import { paginate } from '../pagination.js';
|
|
3
|
+
const DEFAULT_POLL_INTERVAL_MS = 5_000;
|
|
4
|
+
const DEFAULT_WAIT_TIMEOUT_MS = 900_000;
|
|
5
|
+
/**
|
|
6
|
+
* Runs — one execution against a target, and the scan lifecycle around it
|
|
7
|
+
* (`00-DOMAIN.md` §3): started, evidence attached, finished, then
|
|
8
|
+
* reconciled into a summary and a snapshot.
|
|
9
|
+
*/
|
|
10
|
+
export class RunsResource {
|
|
11
|
+
request;
|
|
12
|
+
constructor(request) {
|
|
13
|
+
this.request = request;
|
|
14
|
+
}
|
|
15
|
+
list(query) {
|
|
16
|
+
return this.request({ method: 'GET', path: '/runs', query: { ...query } });
|
|
17
|
+
}
|
|
18
|
+
/** Every run matching `filters`, across every page, as an async iterator. */
|
|
19
|
+
listAll(filters) {
|
|
20
|
+
return paginate((page) => this.list({ ...filters, page }));
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Starts a run against a target. It is created `running` and stays that way
|
|
24
|
+
* until {@link RunsResource.finish}: upload scanner output in between with
|
|
25
|
+
* {@link RunsResource.import}.
|
|
26
|
+
*
|
|
27
|
+
* @throws {SecureportError} `terms_required` for every kind if the
|
|
28
|
+
* organisation has not accepted the current terms — a human accepts them at
|
|
29
|
+
* `POST /orgs/{orgId}/terms`; an API key cannot. `verification_required` for
|
|
30
|
+
* `kind: 'scan'` if the target has no currently-passing verification,
|
|
31
|
+
* re-checked fresh as part of this same request rather than trusted from a
|
|
32
|
+
* stored flag. Neither is retried by the transport: both are settled
|
|
33
|
+
* refusals, not transient ones.
|
|
34
|
+
*/
|
|
35
|
+
create(input) {
|
|
36
|
+
return this.request({ method: 'POST', path: '/runs', body: input });
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* One run by id. What it found is {@link RunsResource.summary}, once it has finished —
|
|
40
|
+
* this method never carries a summary or findings itself.
|
|
41
|
+
*/
|
|
42
|
+
get(id) {
|
|
43
|
+
return this.request({ method: 'GET', path: `/runs/${encodeURIComponent(id)}` });
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Ends a run and, for `status: 'finished'`, reconciles it: the findings
|
|
47
|
+
* uploaded against it become new, still-open, resolved or regressed
|
|
48
|
+
* issues, and the run gets a summary. **`status: 'failed'` reconciles
|
|
49
|
+
* nothing** — see {@link FinishRunInput.status}.
|
|
50
|
+
*
|
|
51
|
+
* @remarks Uploads and manual runs only: a `kind: 'scan'` run is refused here — its engine reports its own end, with evidence this route cannot supply. Stop a scan early with {@link RunsResource.cancel} instead.
|
|
52
|
+
* @throws {SecureportError} `conflict` if any upload is still being
|
|
53
|
+
* parsed — poll {@link RunsResource.listArtifacts} and finish once every
|
|
54
|
+
* one is `done` or `failed` — or if the run had already ended;
|
|
55
|
+
* `validation` for a `kind: 'scan'` run.
|
|
56
|
+
*/
|
|
57
|
+
finish(id, input) {
|
|
58
|
+
return this.request({
|
|
59
|
+
method: 'POST',
|
|
60
|
+
path: `/runs/${encodeURIComponent(id)}/finish`,
|
|
61
|
+
body: input,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Stops a running scan: the run ends as `'cancelled'` immediately, and the
|
|
66
|
+
* scan job behind it is cancelled best-effort.
|
|
67
|
+
*
|
|
68
|
+
* @remarks If the job cancel fails, the engine simply runs out its time budget and its late reports are discarded, so `'cancelled'` is final either way. Nothing is reconciled — a cancelled run resolves no issues, and findings already ingested stay recorded as partial evidence. That intent is what distinguishes it from `'failed'`, which means the scan itself went wrong.
|
|
69
|
+
* @throws {SecureportError} `conflict` if the run had already ended;
|
|
70
|
+
* `validation` for a run that is not a `kind: 'scan'` — an upload is ended
|
|
71
|
+
* by its own caller via {@link RunsResource.finish}.
|
|
72
|
+
*/
|
|
73
|
+
cancel(id) {
|
|
74
|
+
return this.request({ method: 'POST', path: `/runs/${encodeURIComponent(id)}/cancel` });
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Where a scan is: its phase, how long it has run, its time budget, and
|
|
78
|
+
* when it will have ended by — see {@link ScanProgress} for why no percentage.
|
|
79
|
+
*
|
|
80
|
+
* @remarks Combine with {@link RunsResource.waitUntilFinished} for a poll that also knows when to give up: `expectedBy` is the honest bound.
|
|
81
|
+
* @throws {SecureportError} `validation` for a run that is not a
|
|
82
|
+
* `kind: 'scan'` — an upload's progress is whatever its caller is doing.
|
|
83
|
+
*/
|
|
84
|
+
progress(id) {
|
|
85
|
+
return this.request({ method: 'GET', path: `/runs/${encodeURIComponent(id)}/progress` });
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Archives a raw scanner artifact against a run. Nothing is parsed on the
|
|
89
|
+
* client side — the whole file is handed to the API as `input.body`, with
|
|
90
|
+
* `input.contentType` sent verbatim, and the API decides how (and when) to
|
|
91
|
+
* parse it.
|
|
92
|
+
*
|
|
93
|
+
* **The response shape depends on size, not just success or failure.** A
|
|
94
|
+
* file under 5 MiB is parsed inline and answers `201` with
|
|
95
|
+
* `processingStatus: 'done'` and `findingCount`/`skippedLines` filled in. At
|
|
96
|
+
* or over 5 MiB the parse is queued and the API answers `202` with
|
|
97
|
+
* `processingStatus: 'queued'` instead — poll {@link RunsResource.listArtifacts}
|
|
98
|
+
* for the outcome. Both are success: check `processingStatus` on the result rather
|
|
99
|
+
* than assuming the synchronous shape. **25 MiB is a hard cap**, refused
|
|
100
|
+
* with `payload_too_large` rather than accepted and truncated.
|
|
101
|
+
*
|
|
102
|
+
* **Idempotent by content, not by request.** Re-uploading the exact same
|
|
103
|
+
* bytes against the same run answers `200` with the original row, verbatim
|
|
104
|
+
* — no re-parse — so a retried upload after a flaky network is never
|
|
105
|
+
* double-counted.
|
|
106
|
+
*
|
|
107
|
+
* @throws {SecureportError} `conflict` if the run has already ended;
|
|
108
|
+
* `payload_too_large` over 25 MiB; `unsupported_media_type` for a
|
|
109
|
+
* `contentType` the API does not accept, or a compressed body.
|
|
110
|
+
*/
|
|
111
|
+
import(id, input) {
|
|
112
|
+
return this.request({
|
|
113
|
+
method: 'POST',
|
|
114
|
+
path: `/runs/${encodeURIComponent(id)}/import`,
|
|
115
|
+
query: { engine: input.engine, filename: input.filename },
|
|
116
|
+
rawBody: { data: input.body, contentType: input.contentType },
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* The first half of the signed-URL upload path for a file too large for
|
|
121
|
+
* {@link RunsResource.import} — Cloud Run caps that route's request body at
|
|
122
|
+
* 32 MiB whatever the API wants, so a genuinely large scan (a big Nessus or
|
|
123
|
+
* Burp export) goes here instead: mint a URL, `PUT` the file straight to
|
|
124
|
+
* the bucket yourself, then {@link RunsResource.completeUpload}.
|
|
125
|
+
*
|
|
126
|
+
* **Returns one of two shapes, told apart structurally.** If the same
|
|
127
|
+
* `sha256` is already archived on this run, this resolves with the
|
|
128
|
+
* existing {@link RunArtifact} directly — nothing minted, nothing to
|
|
129
|
+
* upload. Otherwise it resolves with a {@link RunUploadUrl}. Check for
|
|
130
|
+
* `'uploadUrl' in result` (or any field {@link RunUploadUrl} has and
|
|
131
|
+
* {@link RunArtifact} does not) to tell them apart.
|
|
132
|
+
*
|
|
133
|
+
* **You must `PUT` the bytes yourself, with a plain `fetch` — not through
|
|
134
|
+
* this SDK's own transport.** `uploadUrl` is a signed Cloud Storage URL:
|
|
135
|
+
* this client's `request`/`requestRaw` always attach your API key as a
|
|
136
|
+
* bearer token and always resolve paths against the API's own base URL,
|
|
137
|
+
* neither of which belongs on a request to Cloud Storage. Send every
|
|
138
|
+
* header in `result.requiredHeaders` verbatim — the bucket's signature
|
|
139
|
+
* check refuses the upload otherwise, silently from this SDK's point of
|
|
140
|
+
* view (it never sees that request).
|
|
141
|
+
*
|
|
142
|
+
* @throws {SecureportError} `conflict` if the run has already ended;
|
|
143
|
+
* `unsupported_media_type` for a `contentType` the API does not accept.
|
|
144
|
+
*/
|
|
145
|
+
createUploadUrl(id, input) {
|
|
146
|
+
return this.request({
|
|
147
|
+
method: 'POST',
|
|
148
|
+
path: `/runs/${encodeURIComponent(id)}/import-url`,
|
|
149
|
+
body: input,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* The second half of the signed-URL upload path — call once your own
|
|
154
|
+
* `PUT` to {@link RunUploadUrl.uploadUrl} has succeeded. Archives the
|
|
155
|
+
* object as a run artifact and queues it for parsing; unlike
|
|
156
|
+
* {@link RunsResource.import}, the result is never parsed synchronously
|
|
157
|
+
* here — anything that needed a signed URL is already well past the size
|
|
158
|
+
* the API parses inline. Poll {@link RunsResource.listArtifacts} (or use
|
|
159
|
+
* {@link RunsResource.waitForArtifact}) the same way a `202` from
|
|
160
|
+
* {@link RunsResource.import} is polled.
|
|
161
|
+
*
|
|
162
|
+
* **Idempotent by content, same as {@link RunsResource.import}.** Calling
|
|
163
|
+
* this twice for the same object answers `200` with the original row the
|
|
164
|
+
* second time.
|
|
165
|
+
*
|
|
166
|
+
* @throws {SecureportError} `conflict` if the run has already ended, or if
|
|
167
|
+
* nothing was actually uploaded to the signed URL (it may have expired).
|
|
168
|
+
*/
|
|
169
|
+
completeUpload(id, input) {
|
|
170
|
+
return this.request({
|
|
171
|
+
method: 'POST',
|
|
172
|
+
path: `/runs/${encodeURIComponent(id)}/import-url/complete`,
|
|
173
|
+
body: input,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* The uploads archived against a run, with where each parse stands — what
|
|
178
|
+
* a client polls after a `202` from {@link RunsResource.import}. Not
|
|
179
|
+
* paginated: a run holds the handful of files one scan produced.
|
|
180
|
+
*/
|
|
181
|
+
listArtifacts(id) {
|
|
182
|
+
return this.request({ method: 'GET', path: `/runs/${encodeURIComponent(id)}/artifacts` });
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* What the run did, in the terms a report opens with.
|
|
186
|
+
*
|
|
187
|
+
* @throws {SecureportError} `conflict` if the run has not been reconciled
|
|
188
|
+
* — still running, or ended `failed` — rather than a zeroed summary that
|
|
189
|
+
* would read as "nothing found".
|
|
190
|
+
*/
|
|
191
|
+
summary(id) {
|
|
192
|
+
return this.request({ method: 'GET', path: `/runs/${encodeURIComponent(id)}/summary` });
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* The snapshot a report is rendered from: the run summary, the target,
|
|
196
|
+
* every issue with what this run changed about it, and the suppressed
|
|
197
|
+
* appendix.
|
|
198
|
+
*
|
|
199
|
+
* **Returns `@secureport/core`'s own `Snapshot` type, with real `Date`s —
|
|
200
|
+
* the one method in this resource that does.** Every other method here
|
|
201
|
+
* returns ISO 8601 strings, mirroring the wire exactly; this one instead
|
|
202
|
+
* runs the response text through core's own `parseSnapshot`, since that
|
|
203
|
+
* is the exact function a `Snapshot` written to disk is read back
|
|
204
|
+
* with, and the API's wire shape is asserted `Exact<>` against core's type
|
|
205
|
+
* server-side. That is what makes `@secureport/core` this package's first
|
|
206
|
+
* real dependency, rather than a structural mirror.
|
|
207
|
+
*
|
|
208
|
+
* @throws {SecureportError} `conflict` if the run has not been reconciled
|
|
209
|
+
* — still running, or ended `failed`.
|
|
210
|
+
*/
|
|
211
|
+
async snapshot(id) {
|
|
212
|
+
const body = await this.request({
|
|
213
|
+
method: 'GET',
|
|
214
|
+
path: `/runs/${encodeURIComponent(id)}/snapshot`,
|
|
215
|
+
});
|
|
216
|
+
return parseSnapshot(JSON.stringify(body));
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Polls {@link RunsResource.get} until the run leaves `'running'`,
|
|
220
|
+
* resolving with whatever it finished as. **Does not itself call
|
|
221
|
+
* {@link RunsResource.summary} or {@link RunsResource.snapshot}** — those
|
|
222
|
+
* still 409 until reconciliation completes, which is why this only
|
|
223
|
+
* watches `status`.
|
|
224
|
+
*
|
|
225
|
+
* Shaped for a long-running hosted scan from the start: today an upload
|
|
226
|
+
* run is finished by the caller directly, so this helper's payoff is
|
|
227
|
+
* mostly forward-looking until scans actually execute.
|
|
228
|
+
*
|
|
229
|
+
* @throws {Error} if `status` is still `'running'` once `timeoutMs` elapses.
|
|
230
|
+
*/
|
|
231
|
+
async waitUntilFinished(id, options) {
|
|
232
|
+
const pollIntervalMs = options?.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
233
|
+
const timeoutMs = options?.timeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS;
|
|
234
|
+
const deadline = Date.now() + timeoutMs;
|
|
235
|
+
for (;;) {
|
|
236
|
+
const run = await this.get(id);
|
|
237
|
+
if (run.status !== 'running')
|
|
238
|
+
return run;
|
|
239
|
+
if (Date.now() + pollIntervalMs > deadline) {
|
|
240
|
+
throw new Error(`run ${id} did not finish within ${String(timeoutMs)}ms (still running).`);
|
|
241
|
+
}
|
|
242
|
+
await sleep(pollIntervalMs);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Polls {@link RunsResource.listArtifacts} until one artifact's
|
|
247
|
+
* `processingStatus` leaves `'queued'`/`'processing'`, resolving with it —
|
|
248
|
+
* the artifact-level completion {@link RunsResource.finish} itself waits
|
|
249
|
+
* on (its own TSDoc: "poll listArtifacts and finish once every one is done
|
|
250
|
+
* or failed"). A large upload answers {@link RunsResource.import} with
|
|
251
|
+
* `202 queued` before the parse has even started; this is what a caller
|
|
252
|
+
* that started one polls instead of hand-rolling the same loop.
|
|
253
|
+
*
|
|
254
|
+
* @throws {Error} if the artifact never appears on the run (id mismatch),
|
|
255
|
+
* or if it is still `queued`/`processing` once `timeoutMs` elapses.
|
|
256
|
+
*/
|
|
257
|
+
async waitForArtifact(runId, artifactId, options) {
|
|
258
|
+
const pollIntervalMs = options?.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
259
|
+
const timeoutMs = options?.timeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS;
|
|
260
|
+
const deadline = Date.now() + timeoutMs;
|
|
261
|
+
for (;;) {
|
|
262
|
+
const { data } = await this.listArtifacts(runId);
|
|
263
|
+
const artifact = data.find((a) => a.id === artifactId);
|
|
264
|
+
if (artifact === undefined) {
|
|
265
|
+
throw new Error(`artifact ${artifactId} not found on run ${runId}.`);
|
|
266
|
+
}
|
|
267
|
+
if (artifact.processingStatus !== 'queued' && artifact.processingStatus !== 'processing') {
|
|
268
|
+
return artifact;
|
|
269
|
+
}
|
|
270
|
+
if (Date.now() + pollIntervalMs > deadline) {
|
|
271
|
+
throw new Error(`artifact ${artifactId} did not finish processing within ${String(timeoutMs)}ms ` +
|
|
272
|
+
`(still ${artifact.processingStatus}).`);
|
|
273
|
+
}
|
|
274
|
+
await sleep(pollIntervalMs);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
function sleep(ms) {
|
|
279
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
280
|
+
}
|
|
281
|
+
//# sourceMappingURL=runs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runs.js","sourceRoot":"","sources":["../../src/resources/runs.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAIjD,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAyU5C,MAAM,wBAAwB,GAAG,KAAK,CAAC;AACvC,MAAM,uBAAuB,GAAG,OAAO,CAAC;AAExC;;;;GAIG;AACH,MAAM,OAAO,YAAY;IACM;IAA7B,YAA6B,OAAkB;QAAlB,YAAO,GAAP,OAAO,CAAW;IAAG,CAAC;IAEnD,IAAI,CAAC,KAA8B;QACjC,OAAO,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,GAAG,KAAK,EAAE,EAAE,CAAC,CAAC;IAC7E,CAAC;IAED,6EAA6E;IAC7E,OAAO,CAAC,OAAoB;QAC1B,OAAO,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC7D,CAAC;IAED;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,KAAa;QAClB,OAAO,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACtE,CAAC;IAED;;;OAGG;IACH,GAAG,CAAC,EAAU;QACZ,OAAO,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,kBAAkB,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;IAClF,CAAC;IAED;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,EAAU,EAAE,KAAqB;QACtC,OAAO,IAAI,CAAC,OAAO,CAAC;YAClB,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,SAAS,kBAAkB,CAAC,EAAE,CAAC,SAAS;YAC9C,IAAI,EAAE,KAAK;SACZ,CAAC,CAAC;IACL,CAAC;IAED;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAU;QACf,OAAO,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,kBAAkB,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,CAAC;IAC1F,CAAC;IAED;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAU;QACjB,OAAO,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,kBAAkB,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;IAC3F,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,MAAM,CAAC,EAAU,EAAE,KAAmB;QACpC,OAAO,IAAI,CAAC,OAAO,CAAC;YAClB,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,SAAS,kBAAkB,CAAC,EAAE,CAAC,SAAS;YAC9C,KAAK,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE;YACzD,OAAO,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE;SAC9D,CAAC,CAAC;IACL,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,eAAe,CAAC,EAAU,EAAE,KAAsB;QAChD,OAAO,IAAI,CAAC,OAAO,CAAC;YAClB,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,SAAS,kBAAkB,CAAC,EAAE,CAAC,aAAa;YAClD,IAAI,EAAE,KAAK;SACZ,CAAC,CAAC;IACL,CAAC;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,cAAc,CAAC,EAAU,EAAE,KAAwB;QACjD,OAAO,IAAI,CAAC,OAAO,CAAC;YAClB,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,SAAS,kBAAkB,CAAC,EAAE,CAAC,sBAAsB;YAC3D,IAAI,EAAE,KAAK;SACZ,CAAC,CAAC;IACL,CAAC;IAED;;;;OAIG;IACH,aAAa,CAAC,EAAU;QACtB,OAAO,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,kBAAkB,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC,CAAC;IAC5F,CAAC;IAED;;;;;;OAMG;IACH,OAAO,CAAC,EAAU;QAChB,OAAO,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,kBAAkB,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC,CAAC;IAC1F,CAAC;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,KAAK,CAAC,QAAQ,CAAC,EAAU;QACvB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAAU;YACvC,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,SAAS,kBAAkB,CAAC,EAAE,CAAC,WAAW;SACjD,CAAC,CAAC;QACH,OAAO,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;IAC7C,CAAC;IAED;;;;;;;;;;;;OAYG;IACH,KAAK,CAAC,iBAAiB,CAAC,EAAU,EAAE,OAAkC;QACpE,MAAM,cAAc,GAAG,OAAO,EAAE,cAAc,IAAI,wBAAwB,CAAC;QAC3E,MAAM,SAAS,GAAG,OAAO,EAAE,SAAS,IAAI,uBAAuB,CAAC;QAChE,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;QAExC,SAAS,CAAC;YACR,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAC/B,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS;gBAAE,OAAO,GAAG,CAAC;YAEzC,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,cAAc,GAAG,QAAQ,EAAE,CAAC;gBAC3C,MAAM,IAAI,KAAK,CAAC,OAAO,EAAE,0BAA0B,MAAM,CAAC,SAAS,CAAC,qBAAqB,CAAC,CAAC;YAC7F,CAAC;YACD,MAAM,KAAK,CAAC,cAAc,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC;IAED;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,eAAe,CACnB,KAAa,EACb,UAAkB,EAClB,OAAgC;QAEhC,MAAM,cAAc,GAAG,OAAO,EAAE,cAAc,IAAI,wBAAwB,CAAC;QAC3E,MAAM,SAAS,GAAG,OAAO,EAAE,SAAS,IAAI,uBAAuB,CAAC;QAChE,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;QAExC,SAAS,CAAC;YACR,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;YACjD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,UAAU,CAAC,CAAC;YACvD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;gBAC3B,MAAM,IAAI,KAAK,CAAC,YAAY,UAAU,qBAAqB,KAAK,GAAG,CAAC,CAAC;YACvE,CAAC;YACD,IAAI,QAAQ,CAAC,gBAAgB,KAAK,QAAQ,IAAI,QAAQ,CAAC,gBAAgB,KAAK,YAAY,EAAE,CAAC;gBACzF,OAAO,QAAQ,CAAC;YAClB,CAAC;YAED,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,cAAc,GAAG,QAAQ,EAAE,CAAC;gBAC3C,MAAM,IAAI,KAAK,CACb,YAAY,UAAU,qCAAqC,MAAM,CAAC,SAAS,CAAC,KAAK;oBAC/E,UAAU,QAAQ,CAAC,gBAAgB,IAAI,CAC1C,CAAC;YACJ,CAAC;YACD,MAAM,KAAK,CAAC,cAAc,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC;CACF;AAED,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Requester } from '../client.js';
|
|
2
|
+
import type { Page, PageQuery } from '../pagination.js';
|
|
3
|
+
/**
|
|
4
|
+
* A pre-emptive suppression rule. `createdAt` is an ISO 8601 string, as the
|
|
5
|
+
* API sends it — parse it yourself if you need a `Date`.
|
|
6
|
+
*/
|
|
7
|
+
export interface SuppressionRule {
|
|
8
|
+
readonly id: string;
|
|
9
|
+
readonly orgId: string;
|
|
10
|
+
/** `null` means the rule covers every target in the organisation. */
|
|
11
|
+
readonly targetId: string | null;
|
|
12
|
+
/** Matched against an issue's vulnerability key; `*` is the only wildcard. */
|
|
13
|
+
readonly vulnKeyGlob: string;
|
|
14
|
+
readonly reason: string;
|
|
15
|
+
/** The authenticated actor that created it — never supplied by the caller. */
|
|
16
|
+
readonly createdBy: string;
|
|
17
|
+
readonly createdAt: string;
|
|
18
|
+
}
|
|
19
|
+
export type SuppressionRulePage = Page<SuppressionRule>;
|
|
20
|
+
export interface NewSuppressionRule {
|
|
21
|
+
/** Omit to cover the whole organisation. */
|
|
22
|
+
readonly targetId?: string;
|
|
23
|
+
/** Anchored at both ends, so `xss-*` does not match `not-xss-reflected`. */
|
|
24
|
+
readonly vulnKeyGlob: string;
|
|
25
|
+
readonly reason: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Pre-emptive "never open an issue for this class" rules — the blunt
|
|
29
|
+
* instrument; per-issue ignore is the default. No update: delete and recreate.
|
|
30
|
+
*/
|
|
31
|
+
export declare class SuppressionRulesResource {
|
|
32
|
+
private readonly request;
|
|
33
|
+
constructor(request: Requester);
|
|
34
|
+
list(query?: PageQuery): Promise<SuppressionRulePage>;
|
|
35
|
+
/** Every rule, across every page, as an async iterator. */
|
|
36
|
+
listAll(): AsyncIterable<SuppressionRule>;
|
|
37
|
+
/** @throws {SecureportError} `not_found` if `targetId` names no target of yours. */
|
|
38
|
+
create(input: NewSuppressionRule): Promise<SuppressionRule>;
|
|
39
|
+
get(id: string): Promise<SuppressionRule>;
|
|
40
|
+
/** Issues the rule already caused to open as ignored stay ignored. */
|
|
41
|
+
delete(id: string): Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=suppression-rules.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"suppression-rules.d.ts","sourceRoot":"","sources":["../../src/resources/suppression-rules.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAGxD;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAEvB,qEAAqE;IACrE,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAEjC,8EAA8E;IAC9E,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAExB,8EAA8E;IAC9E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,MAAM,mBAAmB,GAAG,IAAI,CAAC,eAAe,CAAC,CAAC;AAExD,MAAM,WAAW,kBAAkB;IACjC,4CAA4C;IAC5C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAE3B,4EAA4E;IAC5E,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,qBAAa,wBAAwB;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,SAAS;IAE/C,IAAI,CAAC,KAAK,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAIrD,2DAA2D;IAC3D,OAAO,IAAI,aAAa,CAAC,eAAe,CAAC;IAIzC,oFAAoF;IACpF,MAAM,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,eAAe,CAAC;IAI3D,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAIzC,sEAAsE;IACtE,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAMlC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { paginate } from '../pagination.js';
|
|
2
|
+
/**
|
|
3
|
+
* Pre-emptive "never open an issue for this class" rules — the blunt
|
|
4
|
+
* instrument; per-issue ignore is the default. No update: delete and recreate.
|
|
5
|
+
*/
|
|
6
|
+
export class SuppressionRulesResource {
|
|
7
|
+
request;
|
|
8
|
+
constructor(request) {
|
|
9
|
+
this.request = request;
|
|
10
|
+
}
|
|
11
|
+
list(query) {
|
|
12
|
+
return this.request({ method: 'GET', path: '/suppression-rules', query: { ...query } });
|
|
13
|
+
}
|
|
14
|
+
/** Every rule, across every page, as an async iterator. */
|
|
15
|
+
listAll() {
|
|
16
|
+
return paginate((page) => this.list({ page }));
|
|
17
|
+
}
|
|
18
|
+
/** @throws {SecureportError} `not_found` if `targetId` names no target of yours. */
|
|
19
|
+
create(input) {
|
|
20
|
+
return this.request({ method: 'POST', path: '/suppression-rules', body: input });
|
|
21
|
+
}
|
|
22
|
+
get(id) {
|
|
23
|
+
return this.request({ method: 'GET', path: `/suppression-rules/${encodeURIComponent(id)}` });
|
|
24
|
+
}
|
|
25
|
+
/** Issues the rule already caused to open as ignored stay ignored. */
|
|
26
|
+
delete(id) {
|
|
27
|
+
return this.request({
|
|
28
|
+
method: 'DELETE',
|
|
29
|
+
path: `/suppression-rules/${encodeURIComponent(id)}`,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=suppression-rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"suppression-rules.js","sourceRoot":"","sources":["../../src/resources/suppression-rules.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAiC5C;;;GAGG;AACH,MAAM,OAAO,wBAAwB;IACN;IAA7B,YAA6B,OAAkB;QAAlB,YAAO,GAAP,OAAO,CAAW;IAAG,CAAC;IAEnD,IAAI,CAAC,KAAiB;QACpB,OAAO,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,oBAAoB,EAAE,KAAK,EAAE,EAAE,GAAG,KAAK,EAAE,EAAE,CAAC,CAAC;IAC1F,CAAC;IAED,2DAA2D;IAC3D,OAAO;QACL,OAAO,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACjD,CAAC;IAED,oFAAoF;IACpF,MAAM,CAAC,KAAyB;QAC9B,OAAO,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,oBAAoB,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACnF,CAAC;IAED,GAAG,CAAC,EAAU;QACZ,OAAO,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,sBAAsB,kBAAkB,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;IAC/F,CAAC;IAED,sEAAsE;IACtE,MAAM,CAAC,EAAU;QACf,OAAO,IAAI,CAAC,OAAO,CAAC;YAClB,MAAM,EAAE,QAAQ;YAChB,IAAI,EAAE,sBAAsB,kBAAkB,CAAC,EAAE,CAAC,EAAE;SACrD,CAAC,CAAC;IACL,CAAC;CACF"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { Requester } from '../client.js';
|
|
2
|
+
import type { Page, PageQuery } from '../pagination.js';
|
|
3
|
+
/** A target: one thing runs are executed against. */
|
|
4
|
+
export interface Target {
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly orgId: string;
|
|
7
|
+
/** As it appears on a report cover. */
|
|
8
|
+
readonly name: string;
|
|
9
|
+
/** A URL, host or repository. */
|
|
10
|
+
readonly url: string;
|
|
11
|
+
}
|
|
12
|
+
export type TargetPage = Page<Target>;
|
|
13
|
+
export interface NewTarget {
|
|
14
|
+
readonly name: string;
|
|
15
|
+
readonly url: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* At least one of `name`/`url` is required — the API answers `{}` with a
|
|
19
|
+
* `422 validation`, not a no-op.
|
|
20
|
+
*/
|
|
21
|
+
export interface TargetPatch {
|
|
22
|
+
readonly name?: string;
|
|
23
|
+
readonly url?: string;
|
|
24
|
+
}
|
|
25
|
+
export type VerificationMethod = 'dns_txt' | 'http_file';
|
|
26
|
+
export type VerificationState = 'unverified' | 'verified';
|
|
27
|
+
/**
|
|
28
|
+
* The target's current verification state. Never carries the token — see
|
|
29
|
+
* {@link IssuedTargetVerification}, the one response that does.
|
|
30
|
+
*
|
|
31
|
+
* Date fields are ISO 8601 strings, as the API sends them — this type is
|
|
32
|
+
* SDK-local (no `@secureport/core` equivalent exists), so there is no
|
|
33
|
+
* shared revival logic to lean on; parse them yourself if you need `Date`.
|
|
34
|
+
*/
|
|
35
|
+
export interface TargetVerification {
|
|
36
|
+
readonly method: VerificationMethod | null;
|
|
37
|
+
readonly state: VerificationState;
|
|
38
|
+
readonly issuedAt: string | null;
|
|
39
|
+
/** Set only while a rotated token's grace period is still active. */
|
|
40
|
+
readonly previousExpiresAt: string | null;
|
|
41
|
+
}
|
|
42
|
+
export interface NewTargetVerification {
|
|
43
|
+
readonly method: VerificationMethod;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The response to issuing (or rotating) a verification token.
|
|
47
|
+
*
|
|
48
|
+
* **The token appears here and nowhere else** — {@link TargetVerification}
|
|
49
|
+
* never carries it, and there is no recovery endpoint. Losing it means
|
|
50
|
+
* issuing a new one, which may reset `state` to `unverified` (see
|
|
51
|
+
* {@link TargetsResource.issueVerification}).
|
|
52
|
+
*
|
|
53
|
+
* `recordName`/`recordValue` are present for `method: 'dns_txt'`;
|
|
54
|
+
* `filePath`/`fileContents` for `method: 'http_file'` — a discriminated
|
|
55
|
+
* pair the wire schema does not enforce, so narrow on `method` yourself.
|
|
56
|
+
*/
|
|
57
|
+
export interface IssuedTargetVerification extends Omit<TargetVerification, 'method'> {
|
|
58
|
+
readonly method: VerificationMethod;
|
|
59
|
+
readonly token: string;
|
|
60
|
+
readonly summary: string;
|
|
61
|
+
readonly recordName?: string;
|
|
62
|
+
readonly recordValue?: string;
|
|
63
|
+
readonly filePath?: string;
|
|
64
|
+
readonly fileContents?: string;
|
|
65
|
+
}
|
|
66
|
+
/** The result of one verification check — evidence, not a status flag. */
|
|
67
|
+
export interface TargetVerificationCheckResult {
|
|
68
|
+
/** This check's own evidence row id — a fresh one every call, whether it passed or failed. */
|
|
69
|
+
readonly id: string;
|
|
70
|
+
readonly verified: boolean;
|
|
71
|
+
readonly state: VerificationState;
|
|
72
|
+
readonly method: VerificationMethod;
|
|
73
|
+
readonly checkedAt: string;
|
|
74
|
+
readonly expected: string;
|
|
75
|
+
readonly observed: string | null;
|
|
76
|
+
readonly message: string;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Targets, and the per-target verification flow a hosted scan (`kind:
|
|
80
|
+
* 'scan'`) requires before it can start — see `POST /runs`'s
|
|
81
|
+
* `verification_required` gate.
|
|
82
|
+
*/
|
|
83
|
+
export declare class TargetsResource {
|
|
84
|
+
private readonly request;
|
|
85
|
+
constructor(request: Requester);
|
|
86
|
+
list(query?: PageQuery): Promise<TargetPage>;
|
|
87
|
+
/** Every target, across every page, as an async iterator. */
|
|
88
|
+
listAll(): AsyncIterable<Target>;
|
|
89
|
+
create(input: NewTarget): Promise<Target>;
|
|
90
|
+
get(id: string): Promise<Target>;
|
|
91
|
+
/**
|
|
92
|
+
* **Changing `url` silently un-verifies the target** — the stored
|
|
93
|
+
* verification named the old host, and the API drops `state` to
|
|
94
|
+
* `unverified` as part of this same call. A caller relying on the target
|
|
95
|
+
* staying verified across a URL change needs to re-verify afterwards.
|
|
96
|
+
*/
|
|
97
|
+
update(id: string, patch: TargetPatch): Promise<Target>;
|
|
98
|
+
/** @throws {SecureportError} `conflict` if the target has run or issue history. */
|
|
99
|
+
delete(id: string): Promise<void>;
|
|
100
|
+
getVerification(id: string): Promise<TargetVerification>;
|
|
101
|
+
/**
|
|
102
|
+
* Issues a fresh verification token, or rotates the current one — decided
|
|
103
|
+
* server-side, not by this method: calling with the **same `method`** as
|
|
104
|
+
* an already-verified target **rotates** (the target stays `verified`,
|
|
105
|
+
* and the old token keeps working for a 24-hour grace period). Calling
|
|
106
|
+
* with any other method, or while unverified, **re-issues** and resets
|
|
107
|
+
* `state` to `unverified` until the new token is checked.
|
|
108
|
+
*/
|
|
109
|
+
issueVerification(id: string, input: NewTargetVerification): Promise<IssuedTargetVerification>;
|
|
110
|
+
/**
|
|
111
|
+
* Runs the verification check now, fresh — never trusts a stored flag.
|
|
112
|
+
* **Promote-only**: a match sets `state: 'verified'`; a mismatch or
|
|
113
|
+
* unreachable record is recorded as evidence but never demotes a target
|
|
114
|
+
* that is already verified. Writes a new evidence row every call, whether
|
|
115
|
+
* it passes or fails — not a no-op check.
|
|
116
|
+
*
|
|
117
|
+
* @throws {SecureportError} `conflict` if no token has been issued yet.
|
|
118
|
+
*/
|
|
119
|
+
verify(id: string): Promise<TargetVerificationCheckResult>;
|
|
120
|
+
/**
|
|
121
|
+
* Revokes the current token outright — idempotent; revoking an
|
|
122
|
+
* already-unverified target is a clean no-op, not an error.
|
|
123
|
+
*/
|
|
124
|
+
revokeVerification(id: string): Promise<void>;
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=targets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"targets.d.ts","sourceRoot":"","sources":["../../src/resources/targets.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { paginate } from '../pagination.js';
|
|
2
|
+
/**
|
|
3
|
+
* Targets, and the per-target verification flow a hosted scan (`kind:
|
|
4
|
+
* 'scan'`) requires before it can start — see `POST /runs`'s
|
|
5
|
+
* `verification_required` gate.
|
|
6
|
+
*/
|
|
7
|
+
export class TargetsResource {
|
|
8
|
+
request;
|
|
9
|
+
constructor(request) {
|
|
10
|
+
this.request = request;
|
|
11
|
+
}
|
|
12
|
+
list(query) {
|
|
13
|
+
return this.request({ method: 'GET', path: '/targets', query: { ...query } });
|
|
14
|
+
}
|
|
15
|
+
/** Every target, across every page, as an async iterator. */
|
|
16
|
+
listAll() {
|
|
17
|
+
return paginate((page) => this.list({ page }));
|
|
18
|
+
}
|
|
19
|
+
create(input) {
|
|
20
|
+
return this.request({ method: 'POST', path: '/targets', body: input });
|
|
21
|
+
}
|
|
22
|
+
get(id) {
|
|
23
|
+
return this.request({ method: 'GET', path: `/targets/${encodeURIComponent(id)}` });
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* **Changing `url` silently un-verifies the target** — the stored
|
|
27
|
+
* verification named the old host, and the API drops `state` to
|
|
28
|
+
* `unverified` as part of this same call. A caller relying on the target
|
|
29
|
+
* staying verified across a URL change needs to re-verify afterwards.
|
|
30
|
+
*/
|
|
31
|
+
update(id, patch) {
|
|
32
|
+
return this.request({
|
|
33
|
+
method: 'PATCH',
|
|
34
|
+
path: `/targets/${encodeURIComponent(id)}`,
|
|
35
|
+
body: patch,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/** @throws {SecureportError} `conflict` if the target has run or issue history. */
|
|
39
|
+
delete(id) {
|
|
40
|
+
return this.request({ method: 'DELETE', path: `/targets/${encodeURIComponent(id)}` });
|
|
41
|
+
}
|
|
42
|
+
getVerification(id) {
|
|
43
|
+
return this.request({ method: 'GET', path: `/targets/${encodeURIComponent(id)}/verification` });
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Issues a fresh verification token, or rotates the current one — decided
|
|
47
|
+
* server-side, not by this method: calling with the **same `method`** as
|
|
48
|
+
* an already-verified target **rotates** (the target stays `verified`,
|
|
49
|
+
* and the old token keeps working for a 24-hour grace period). Calling
|
|
50
|
+
* with any other method, or while unverified, **re-issues** and resets
|
|
51
|
+
* `state` to `unverified` until the new token is checked.
|
|
52
|
+
*/
|
|
53
|
+
issueVerification(id, input) {
|
|
54
|
+
return this.request({
|
|
55
|
+
method: 'POST',
|
|
56
|
+
path: `/targets/${encodeURIComponent(id)}/verification`,
|
|
57
|
+
body: input,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Runs the verification check now, fresh — never trusts a stored flag.
|
|
62
|
+
* **Promote-only**: a match sets `state: 'verified'`; a mismatch or
|
|
63
|
+
* unreachable record is recorded as evidence but never demotes a target
|
|
64
|
+
* that is already verified. Writes a new evidence row every call, whether
|
|
65
|
+
* it passes or fails — not a no-op check.
|
|
66
|
+
*
|
|
67
|
+
* @throws {SecureportError} `conflict` if no token has been issued yet.
|
|
68
|
+
*/
|
|
69
|
+
verify(id) {
|
|
70
|
+
return this.request({ method: 'POST', path: `/targets/${encodeURIComponent(id)}/verify` });
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Revokes the current token outright — idempotent; revoking an
|
|
74
|
+
* already-unverified target is a clean no-op, not an error.
|
|
75
|
+
*/
|
|
76
|
+
revokeVerification(id) {
|
|
77
|
+
return this.request({
|
|
78
|
+
method: 'DELETE',
|
|
79
|
+
path: `/targets/${encodeURIComponent(id)}/verification`,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=targets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"targets.js","sourceRoot":"","sources":["../../src/resources/targets.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAyF5C;;;;GAIG;AACH,MAAM,OAAO,eAAe;IACG;IAA7B,YAA6B,OAAkB;QAAlB,YAAO,GAAP,OAAO,CAAW;IAAG,CAAC;IAEnD,IAAI,CAAC,KAAiB;QACpB,OAAO,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,EAAE,GAAG,KAAK,EAAE,EAAE,CAAC,CAAC;IAChF,CAAC;IAED,6DAA6D;IAC7D,OAAO;QACL,OAAO,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACjD,CAAC;IAED,MAAM,CAAC,KAAgB;QACrB,OAAO,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACzE,CAAC;IAED,GAAG,CAAC,EAAU;QACZ,OAAO,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,kBAAkB,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;IACrF,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,EAAU,EAAE,KAAkB;QACnC,OAAO,IAAI,CAAC,OAAO,CAAC;YAClB,MAAM,EAAE,OAAO;YACf,IAAI,EAAE,YAAY,kBAAkB,CAAC,EAAE,CAAC,EAAE;YAC1C,IAAI,EAAE,KAAK;SACZ,CAAC,CAAC;IACL,CAAC;IAED,mFAAmF;IACnF,MAAM,CAAC,EAAU;QACf,OAAO,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,kBAAkB,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;IACxF,CAAC;IAED,eAAe,CAAC,EAAU;QACxB,OAAO,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,kBAAkB,CAAC,EAAE,CAAC,eAAe,EAAE,CAAC,CAAC;IAClG,CAAC;IAED;;;;;;;OAOG;IACH,iBAAiB,CAAC,EAAU,EAAE,KAA4B;QACxD,OAAO,IAAI,CAAC,OAAO,CAAC;YAClB,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,YAAY,kBAAkB,CAAC,EAAE,CAAC,eAAe;YACvD,IAAI,EAAE,KAAK;SACZ,CAAC,CAAC;IACL,CAAC;IAED;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAU;QACf,OAAO,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,kBAAkB,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,CAAC;IAC7F,CAAC;IAED;;;OAGG;IACH,kBAAkB,CAAC,EAAU;QAC3B,OAAO,IAAI,CAAC,OAAO,CAAC;YAClB,MAAM,EAAE,QAAQ;YAChB,IAAI,EAAE,YAAY,kBAAkB,CAAC,EAAE,CAAC,eAAe;SACxD,CAAC,CAAC;IACL,CAAC;CACF"}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@secureport/sdk",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Typed client for the Secureport hosted API.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"security",
|
|
7
|
+
"pentest",
|
|
8
|
+
"penetration-testing",
|
|
9
|
+
"vulnerability",
|
|
10
|
+
"vulnerability-management",
|
|
11
|
+
"appsec",
|
|
12
|
+
"devsecops",
|
|
13
|
+
"security-reporting",
|
|
14
|
+
"sdk",
|
|
15
|
+
"api-client"
|
|
16
|
+
],
|
|
17
|
+
"homepage": "https://secureport.io/",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"email": "hello@secureport.io"
|
|
20
|
+
},
|
|
21
|
+
"type": "module",
|
|
22
|
+
"main": "./dist/index.js",
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"default": "./dist/index.js"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"src",
|
|
33
|
+
"LICENSE"
|
|
34
|
+
],
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@secureport/core": "^2.4.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^22.20.3",
|
|
40
|
+
"typescript": "^5.7.2"
|
|
41
|
+
},
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "git+https://github.com/agbjordan/secureport.git",
|
|
46
|
+
"directory": "packages/sdk"
|
|
47
|
+
},
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
53
|
+
"typecheck": "tsc --noEmit"
|
|
54
|
+
}
|
|
55
|
+
}
|