@secureport/core 0.2.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 +59 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/issue.d.ts +16 -0
- package/dist/issue.d.ts.map +1 -0
- package/dist/issue.js +2 -0
- package/dist/issue.js.map +1 -0
- package/dist/reconcile.d.ts +15 -0
- package/dist/reconcile.d.ts.map +1 -0
- package/dist/reconcile.js +23 -0
- package/dist/reconcile.js.map +1 -0
- package/package.json +35 -0
- package/src/index.ts +2 -0
- package/src/issue.ts +17 -0
- package/src/reconcile.ts +32 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Andrew Jordan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# @secureport/core
|
|
2
|
+
|
|
3
|
+
The shared domain model for [Secureport](https://github.com/agbjordan/secureport).
|
|
4
|
+
The hosted API imports this package the same way a third party would — one
|
|
5
|
+
model, one implementation.
|
|
6
|
+
|
|
7
|
+
**MIT licensed. Zero runtime dependencies.**
|
|
8
|
+
|
|
9
|
+
## Status: early scaffold
|
|
10
|
+
|
|
11
|
+
`0.2.x` is a deliberately small first release, published to prove the release
|
|
12
|
+
path rather than to be built against. **The domain shapes below are being
|
|
13
|
+
replaced wholesale in `0.3.0`** with the full model — `Finding`, `Issue`,
|
|
14
|
+
`Run`, `RunSummary`, `Snapshot` and `Coverage`, content-addressed fingerprints,
|
|
15
|
+
a CVSS-derived severity model, and a `reconcile()` that matches findings to
|
|
16
|
+
issues by fingerprint rather than by title.
|
|
17
|
+
|
|
18
|
+
If you are evaluating Secureport, wait for `0.3.0`. If you depend on `0.2.x`
|
|
19
|
+
anyway, pin it exactly.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
npm install @secureport/core
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { reconcile, type Finding, type Issue } from '@secureport/core';
|
|
31
|
+
|
|
32
|
+
const open: Issue[] = [
|
|
33
|
+
{ id: 'ISS-1', title: 'Missing HSTS header', severity: 'medium', status: 'open' },
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const findings: Finding[] = [
|
|
37
|
+
{ title: 'Missing HSTS header', severity: 'medium' },
|
|
38
|
+
{ title: 'Directory listing enabled', severity: 'low' },
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
let n = 2;
|
|
42
|
+
const issues = reconcile(findings, open, () => `ISS-${n++}`);
|
|
43
|
+
// ISS-1 is reused for the HSTS finding; ISS-2 opens for the directory listing.
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## The model in one paragraph
|
|
47
|
+
|
|
48
|
+
The tracked entity is the **issue**, not the finding. A **run** is one
|
|
49
|
+
execution against a target. A **finding** is one detection within one run and
|
|
50
|
+
is immutable — it never carries a status. Findings reconcile into **issues**,
|
|
51
|
+
which persist across runs and carry status, severity and history. That
|
|
52
|
+
distinction is the whole point of the package: two scanners reporting the same
|
|
53
|
+
weakness produce one issue with two sources, not two rows.
|
|
54
|
+
|
|
55
|
+
## Contracts
|
|
56
|
+
|
|
57
|
+
Every export is a semver contract. From `0.3.0`, `fingerprint_version` and the
|
|
58
|
+
report JSON schema are versioned public contracts in their own right, and a
|
|
59
|
+
deprecation policy lands before `1.0.0`.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC"}
|
package/dist/issue.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Severity of an {@link Issue}, ordered from most to least urgent by convention. */
|
|
2
|
+
export type Severity = 'critical' | 'high' | 'medium' | 'low' | 'info';
|
|
3
|
+
/** Lifecycle state of an {@link Issue}. */
|
|
4
|
+
export type IssueStatus = 'open' | 'triaging' | 'accepted' | 'resolved';
|
|
5
|
+
/**
|
|
6
|
+
* A tracked issue — the entity Secureport reports on. One or more findings
|
|
7
|
+
* from one or more scanners can be merged into a single issue; the issue,
|
|
8
|
+
* not the finding, is what carries status and severity through review.
|
|
9
|
+
*/
|
|
10
|
+
export interface Issue {
|
|
11
|
+
id: string;
|
|
12
|
+
title: string;
|
|
13
|
+
severity: Severity;
|
|
14
|
+
status: IssueStatus;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=issue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"issue.d.ts","sourceRoot":"","sources":["../src/issue.ts"],"names":[],"mappings":"AAAA,qFAAqF;AACrF,MAAM,MAAM,QAAQ,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;AAEvE,2CAA2C;AAC3C,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,UAAU,GAAG,UAAU,GAAG,UAAU,CAAC;AAExE;;;;GAIG;AACH,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,WAAW,CAAC;CACrB"}
|
package/dist/issue.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"issue.js","sourceRoot":"","sources":["../src/issue.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Issue } from './issue.js';
|
|
2
|
+
/** A raw scanner result, not yet folded into the tracked issue set. */
|
|
3
|
+
export interface Finding {
|
|
4
|
+
title: string;
|
|
5
|
+
severity: Issue['severity'];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Merges incoming findings into the tracked issue set. A finding whose title
|
|
9
|
+
* matches an open issue is folded into it; anything new opens an issue.
|
|
10
|
+
*
|
|
11
|
+
* Pure: no I/O, no clock, no randomness. Callers supply the id for any issue
|
|
12
|
+
* this opens via `nextId`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function reconcile(findings: Finding[], openIssues: Issue[], nextId: () => string): Issue[];
|
|
15
|
+
//# sourceMappingURL=reconcile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../src/reconcile.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExC,uEAAuE;AACvE,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;CAC7B;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,MAAM,GAAG,KAAK,EAAE,CAgBjG"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merges incoming findings into the tracked issue set. A finding whose title
|
|
3
|
+
* matches an open issue is folded into it; anything new opens an issue.
|
|
4
|
+
*
|
|
5
|
+
* Pure: no I/O, no clock, no randomness. Callers supply the id for any issue
|
|
6
|
+
* this opens via `nextId`.
|
|
7
|
+
*/
|
|
8
|
+
export function reconcile(findings, openIssues, nextId) {
|
|
9
|
+
const byTitle = new Map(openIssues.map((issue) => [issue.title, issue]));
|
|
10
|
+
for (const finding of findings) {
|
|
11
|
+
const existing = byTitle.get(finding.title);
|
|
12
|
+
if (!existing) {
|
|
13
|
+
byTitle.set(finding.title, {
|
|
14
|
+
id: nextId(),
|
|
15
|
+
title: finding.title,
|
|
16
|
+
severity: finding.severity,
|
|
17
|
+
status: 'open',
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return [...byTitle.values()];
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=reconcile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconcile.js","sourceRoot":"","sources":["../src/reconcile.ts"],"names":[],"mappings":"AAQA;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,QAAmB,EAAE,UAAmB,EAAE,MAAoB;IACtF,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAEzE,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC5C,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE;gBACzB,EAAE,EAAE,MAAM,EAAE;gBACZ,KAAK,EAAE,OAAO,CAAC,KAAK;gBACpB,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,MAAM,EAAE,MAAM;aACf,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;AAC/B,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@secureport/core",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Shared domain model for Secureport. Imported by the hosted API the same way a third party would.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"src",
|
|
17
|
+
"LICENSE"
|
|
18
|
+
],
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"typescript": "^5.7.2"
|
|
21
|
+
},
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/agbjordan/secureport.git",
|
|
26
|
+
"directory": "packages/core"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsc -p tsconfig.json",
|
|
33
|
+
"typecheck": "tsc --noEmit"
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/index.ts
ADDED
package/src/issue.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Severity of an {@link Issue}, ordered from most to least urgent by convention. */
|
|
2
|
+
export type Severity = 'critical' | 'high' | 'medium' | 'low' | 'info';
|
|
3
|
+
|
|
4
|
+
/** Lifecycle state of an {@link Issue}. */
|
|
5
|
+
export type IssueStatus = 'open' | 'triaging' | 'accepted' | 'resolved';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A tracked issue — the entity Secureport reports on. One or more findings
|
|
9
|
+
* from one or more scanners can be merged into a single issue; the issue,
|
|
10
|
+
* not the finding, is what carries status and severity through review.
|
|
11
|
+
*/
|
|
12
|
+
export interface Issue {
|
|
13
|
+
id: string;
|
|
14
|
+
title: string;
|
|
15
|
+
severity: Severity;
|
|
16
|
+
status: IssueStatus;
|
|
17
|
+
}
|
package/src/reconcile.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Issue } from './issue.js';
|
|
2
|
+
|
|
3
|
+
/** A raw scanner result, not yet folded into the tracked issue set. */
|
|
4
|
+
export interface Finding {
|
|
5
|
+
title: string;
|
|
6
|
+
severity: Issue['severity'];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Merges incoming findings into the tracked issue set. A finding whose title
|
|
11
|
+
* matches an open issue is folded into it; anything new opens an issue.
|
|
12
|
+
*
|
|
13
|
+
* Pure: no I/O, no clock, no randomness. Callers supply the id for any issue
|
|
14
|
+
* this opens via `nextId`.
|
|
15
|
+
*/
|
|
16
|
+
export function reconcile(findings: Finding[], openIssues: Issue[], nextId: () => string): Issue[] {
|
|
17
|
+
const byTitle = new Map(openIssues.map((issue) => [issue.title, issue]));
|
|
18
|
+
|
|
19
|
+
for (const finding of findings) {
|
|
20
|
+
const existing = byTitle.get(finding.title);
|
|
21
|
+
if (!existing) {
|
|
22
|
+
byTitle.set(finding.title, {
|
|
23
|
+
id: nextId(),
|
|
24
|
+
title: finding.title,
|
|
25
|
+
severity: finding.severity,
|
|
26
|
+
status: 'open',
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return [...byTitle.values()];
|
|
32
|
+
}
|