fluidattacks_core_resolves 1.0.0__tar.gz → 2.0.0__tar.gz
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.
- {fluidattacks_core_resolves-1.0.0 → fluidattacks_core_resolves-2.0.0}/PKG-INFO +1 -1
- {fluidattacks_core_resolves-1.0.0 → fluidattacks_core_resolves-2.0.0}/fluidattacks_core/resolves/__init__.py +5 -2
- {fluidattacks_core_resolves-1.0.0 → fluidattacks_core_resolves-2.0.0}/fluidattacks_core/resolves/_entries.py +16 -6
- {fluidattacks_core_resolves-1.0.0 → fluidattacks_core_resolves-2.0.0}/fluidattacks_core/resolves/_instants.py +1 -1
- {fluidattacks_core_resolves-1.0.0 → fluidattacks_core_resolves-2.0.0}/fluidattacks_core/resolves/_schema.py +3 -3
- {fluidattacks_core_resolves-1.0.0 → fluidattacks_core_resolves-2.0.0}/fluidattacks_core/resolves/schemas/resolves_output.schema.json +69 -60
- {fluidattacks_core_resolves-1.0.0 → fluidattacks_core_resolves-2.0.0}/pyproject.toml +1 -1
- fluidattacks_core_resolves-2.0.0/tests/data/invalid_evidence_date_non_ascii_digits.json +21 -0
- fluidattacks_core_resolves-2.0.0/tests/data/invalid_refused_address_not_an_address.json +17 -0
- fluidattacks_core_resolves-1.0.0/tests/data/invalid_bidi_host.json +0 -17
- fluidattacks_core_resolves-1.0.0/tests/data/invalid_empty_host.json +0 -17
- fluidattacks_core_resolves-1.0.0/tests/data/invalid_empty_label_host.json +0 -17
- fluidattacks_core_resolves-1.0.0/tests/data/invalid_non_ascii_host.json +0 -17
- fluidattacks_core_resolves-1.0.0/tests/data/invalid_trailing_dot_host.json +0 -21
- {fluidattacks_core_resolves-1.0.0 → fluidattacks_core_resolves-2.0.0}/.envrc +0 -0
- {fluidattacks_core_resolves-1.0.0 → fluidattacks_core_resolves-2.0.0}/.gitignore +0 -0
- {fluidattacks_core_resolves-1.0.0 → fluidattacks_core_resolves-2.0.0}/fluidattacks_core/resolves/_envelope.py +5 -5
- {fluidattacks_core_resolves-1.0.0 → fluidattacks_core_resolves-2.0.0}/fluidattacks_core/resolves/_hosts.py +0 -0
- {fluidattacks_core_resolves-1.0.0 → fluidattacks_core_resolves-2.0.0}/fluidattacks_core/resolves/py.typed +0 -0
- {fluidattacks_core_resolves-1.0.0 → fluidattacks_core_resolves-2.0.0}/import-linter.cfg +0 -0
- {fluidattacks_core_resolves-1.0.0 → fluidattacks_core_resolves-2.0.0}/mypy.ini +0 -0
- {fluidattacks_core_resolves-1.0.0 → fluidattacks_core_resolves-2.0.0}/ruff.toml +0 -0
- {fluidattacks_core_resolves-1.0.0 → fluidattacks_core_resolves-2.0.0}/tests/__init__.py +0 -0
- {fluidattacks_core_resolves-1.0.0 → fluidattacks_core_resolves-2.0.0}/tests/data/invalid_coerced_schema_version.json +0 -0
- {fluidattacks_core_resolves-1.0.0 → fluidattacks_core_resolves-2.0.0}/tests/data/invalid_duplicate_entry.json +0 -0
- {fluidattacks_core_resolves-1.0.0 → fluidattacks_core_resolves-2.0.0}/tests/data/invalid_empty_seeds.json +0 -0
- {fluidattacks_core_resolves-1.0.0 → fluidattacks_core_resolves-2.0.0}/tests/data/invalid_entry_of_an_undeclared_seed.json +0 -0
- {fluidattacks_core_resolves-1.0.0 → fluidattacks_core_resolves-2.0.0}/tests/data/invalid_evidence_date_spelling.json +0 -0
- {fluidattacks_core_resolves-1.0.0 → fluidattacks_core_resolves-2.0.0}/tests/data/invalid_extra_field.json +0 -0
- {fluidattacks_core_resolves-1.0.0 → fluidattacks_core_resolves-2.0.0}/tests/data/invalid_missing_seeds.json +0 -0
- {fluidattacks_core_resolves-1.0.0 → fluidattacks_core_resolves-2.0.0}/tests/data/invalid_outcome_on_a_record.json +0 -0
- {fluidattacks_core_resolves-1.0.0 → fluidattacks_core_resolves-2.0.0}/tests/data/invalid_port_out_of_range.json +0 -0
- {fluidattacks_core_resolves-1.0.0 → fluidattacks_core_resolves-2.0.0}/tests/data/invalid_ports_on_a_name_record.json +0 -0
- {fluidattacks_core_resolves-1.0.0 → fluidattacks_core_resolves-2.0.0}/tests/data/invalid_record_without_value.json +0 -0
- {fluidattacks_core_resolves-1.0.0 → fluidattacks_core_resolves-2.0.0}/tests/data/invalid_refused_without_address.json +0 -0
- {fluidattacks_core_resolves-1.0.0 → fluidattacks_core_resolves-2.0.0}/tests/data/invalid_resolution_with_record.json +0 -0
- {fluidattacks_core_resolves-1.0.0 → fluidattacks_core_resolves-2.0.0}/tests/data/invalid_resolution_without_outcome.json +0 -0
- {fluidattacks_core_resolves-1.0.0 → fluidattacks_core_resolves-2.0.0}/tests/data/invalid_sighting_with_record.json +0 -0
- {fluidattacks_core_resolves-1.0.0 → fluidattacks_core_resolves-2.0.0}/tests/data/invalid_unknown_kind.json +0 -0
- {fluidattacks_core_resolves-1.0.0 → fluidattacks_core_resolves-2.0.0}/tests/data/invalid_uppercase_host.json +0 -0
- {fluidattacks_core_resolves-1.0.0 → fluidattacks_core_resolves-2.0.0}/tests/data/invalid_uppercase_seed.json +0 -0
- {fluidattacks_core_resolves-1.0.0 → fluidattacks_core_resolves-2.0.0}/tests/data/resolves_output.json +0 -0
- {fluidattacks_core_resolves-1.0.0 → fluidattacks_core_resolves-2.0.0}/tests/test_schema.py +0 -0
- {fluidattacks_core_resolves-1.0.0 → fluidattacks_core_resolves-2.0.0}/uv.lock +0 -0
|
@@ -4,18 +4,20 @@ from fluidattacks_core.resolves._entries import (
|
|
|
4
4
|
EntryKind,
|
|
5
5
|
NameRecord,
|
|
6
6
|
Outcome,
|
|
7
|
+
Record,
|
|
7
8
|
RecordType,
|
|
8
9
|
Refusal,
|
|
9
10
|
Resolution,
|
|
10
11
|
SeenBy,
|
|
11
12
|
Sighting,
|
|
13
|
+
Unresolved,
|
|
12
14
|
)
|
|
13
15
|
from fluidattacks_core.resolves._envelope import (
|
|
14
16
|
SCHEMA_VERSION,
|
|
15
17
|
ResolvesOutput,
|
|
16
18
|
SeedStatus,
|
|
17
19
|
)
|
|
18
|
-
from fluidattacks_core.resolves._hosts import HOST_PATTERN, MAX_HOST_LENGTH, Host
|
|
20
|
+
from fluidattacks_core.resolves._hosts import HOST_PATTERN, MAX_HOST_LENGTH, Host
|
|
19
21
|
from fluidattacks_core.resolves._instants import EVIDENCE_DATE_PATTERN, EvidenceDate
|
|
20
22
|
from fluidattacks_core.resolves._schema import (
|
|
21
23
|
SCHEMA_PATH,
|
|
@@ -37,6 +39,7 @@ __all__ = [
|
|
|
37
39
|
"Host",
|
|
38
40
|
"NameRecord",
|
|
39
41
|
"Outcome",
|
|
42
|
+
"Record",
|
|
40
43
|
"RecordType",
|
|
41
44
|
"Refusal",
|
|
42
45
|
"Resolution",
|
|
@@ -44,8 +47,8 @@ __all__ = [
|
|
|
44
47
|
"SeedStatus",
|
|
45
48
|
"SeenBy",
|
|
46
49
|
"Sighting",
|
|
50
|
+
"Unresolved",
|
|
47
51
|
"build_schema",
|
|
48
|
-
"ensure_host",
|
|
49
52
|
"load_schema",
|
|
50
53
|
"render_schema",
|
|
51
54
|
]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from enum import StrEnum
|
|
2
2
|
from typing import Annotated, ClassVar, Literal
|
|
3
3
|
|
|
4
|
-
from pydantic import BaseModel, ConfigDict, Field, StringConstraints
|
|
4
|
+
from pydantic import BaseModel, ConfigDict, Field, IPvAnyAddress, StringConstraints, WithJsonSchema
|
|
5
5
|
|
|
6
6
|
from fluidattacks_core.resolves._hosts import Host
|
|
7
7
|
from fluidattacks_core.resolves._instants import EvidenceDate
|
|
@@ -59,8 +59,18 @@ class Outcome(StrEnum):
|
|
|
59
59
|
RecordValue = Annotated[str, StringConstraints(min_length=1, max_length=4096)]
|
|
60
60
|
Port = Annotated[int, Field(ge=1, le=65535)]
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
RefusedAddress = Annotated[
|
|
63
|
+
IPvAnyAddress,
|
|
64
|
+
WithJsonSchema(
|
|
65
|
+
{
|
|
66
|
+
"type": "string",
|
|
67
|
+
"minLength": 2,
|
|
68
|
+
"maxLength": 45,
|
|
69
|
+
"pattern": "^[0-9a-fA-F.:]+$",
|
|
70
|
+
"anyOf": [{"format": "ipv4"}, {"format": "ipv6"}],
|
|
71
|
+
}
|
|
72
|
+
),
|
|
73
|
+
]
|
|
64
74
|
|
|
65
75
|
|
|
66
76
|
class _Observed(BaseModel):
|
|
@@ -101,7 +111,7 @@ class NameRecord(_Observed):
|
|
|
101
111
|
record_value: RecordValue
|
|
102
112
|
|
|
103
113
|
|
|
104
|
-
class
|
|
114
|
+
class Unresolved(_Observed):
|
|
105
115
|
"""A name that was asked and yielded no usable record, and why."""
|
|
106
116
|
|
|
107
117
|
kind: Literal[EntryKind.RESOLUTION]
|
|
@@ -122,8 +132,8 @@ class Refusal(_Observed):
|
|
|
122
132
|
|
|
123
133
|
|
|
124
134
|
Record = Annotated[AddressRecord | NameRecord, Field(discriminator="record_type")]
|
|
125
|
-
|
|
135
|
+
Resolution = Annotated[Unresolved | Refusal, Field(discriminator="outcome")]
|
|
126
136
|
|
|
127
137
|
# Which type an entry is follows from `kind`, then from `record_type` or
|
|
128
138
|
# `outcome`, so a field foreign to the kind is an unknown field, not a rule.
|
|
129
|
-
Entry = Annotated[Sighting | Record |
|
|
139
|
+
Entry = Annotated[Sighting | Record | Resolution, Field(discriminator="kind")]
|
|
@@ -4,6 +4,6 @@ from pydantic import StringConstraints
|
|
|
4
4
|
|
|
5
5
|
# The one shape the producer's own type emits, and a strict subset of the
|
|
6
6
|
# platform's `#isoDate`, so nothing downstream has to guess a spelling.
|
|
7
|
-
EVIDENCE_DATE_PATTERN = r"
|
|
7
|
+
EVIDENCE_DATE_PATTERN = r"^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$"
|
|
8
8
|
|
|
9
9
|
EvidenceDate = Annotated[str, StringConstraints(pattern=EVIDENCE_DATE_PATTERN)]
|
|
@@ -15,9 +15,9 @@ _SCHEMA_DIALECT = "https://json-schema.org/draft/2020-12/schema"
|
|
|
15
15
|
def build_schema() -> dict[str, object]:
|
|
16
16
|
"""Render the canonical JSONSchema the Rust producer validates against.
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
Two rules hold across entries, which a schema reads one at a time: an
|
|
19
|
+
entry names a seed the run declares, and a run states each fact once. The
|
|
20
|
+
producer implements them and the fixtures prove the implementations agree.
|
|
21
21
|
"""
|
|
22
22
|
schema = ResolvesOutput.model_json_schema()
|
|
23
23
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"description": "An A or AAAA answer, the only kind of record a port probe can follow.\n\n`open_ports` absent means the round did not probe; empty means it probed\nand found none. The envelope's seed status says whether it got to ask.",
|
|
6
6
|
"properties": {
|
|
7
7
|
"evidence_date": {
|
|
8
|
-
"pattern": "
|
|
8
|
+
"pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}Z$",
|
|
9
9
|
"title": "Evidence Date",
|
|
10
10
|
"type": "string"
|
|
11
11
|
},
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"description": "An answer that names something else, so there is nothing to probe.",
|
|
81
81
|
"properties": {
|
|
82
82
|
"evidence_date": {
|
|
83
|
-
"pattern": "
|
|
83
|
+
"pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}Z$",
|
|
84
84
|
"title": "Evidence Date",
|
|
85
85
|
"type": "string"
|
|
86
86
|
},
|
|
@@ -140,7 +140,7 @@
|
|
|
140
140
|
"description": "A name whose address the scan declined to dial.\n\nThe address travels apart from any `record_value`, which only ever holds\nan answer a scan may act on: a reader walking entries can never mistake an\naddress the screen withheld for one it may publish.",
|
|
141
141
|
"properties": {
|
|
142
142
|
"evidence_date": {
|
|
143
|
-
"pattern": "
|
|
143
|
+
"pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}Z$",
|
|
144
144
|
"title": "Evidence Date",
|
|
145
145
|
"type": "string"
|
|
146
146
|
},
|
|
@@ -162,8 +162,17 @@
|
|
|
162
162
|
"type": "string"
|
|
163
163
|
},
|
|
164
164
|
"refused_address": {
|
|
165
|
+
"anyOf": [
|
|
166
|
+
{
|
|
167
|
+
"format": "ipv4"
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"format": "ipv6"
|
|
171
|
+
}
|
|
172
|
+
],
|
|
165
173
|
"maxLength": 45,
|
|
166
|
-
"minLength":
|
|
174
|
+
"minLength": 2,
|
|
175
|
+
"pattern": "^[0-9a-fA-F.:]+$",
|
|
167
176
|
"title": "Refused Address",
|
|
168
177
|
"type": "string"
|
|
169
178
|
},
|
|
@@ -190,12 +199,35 @@
|
|
|
190
199
|
"title": "Refusal",
|
|
191
200
|
"type": "object"
|
|
192
201
|
},
|
|
193
|
-
"
|
|
202
|
+
"SeedStatus": {
|
|
203
|
+
"description": "How much of one seed's surface the run covered.\n\nA consumer that treats absence as a removal must be able to refuse\nanything but `complete`. It is per seed because a run takes several and\ngives up on the ones it cannot enumerate, so one status for the file would\nmake a whole run unusable when a single seed failed.",
|
|
204
|
+
"enum": [
|
|
205
|
+
"complete",
|
|
206
|
+
"failed",
|
|
207
|
+
"partial"
|
|
208
|
+
],
|
|
209
|
+
"title": "SeedStatus",
|
|
210
|
+
"type": "string"
|
|
211
|
+
},
|
|
212
|
+
"SeenBy": {
|
|
213
|
+
"description": "Which part of the run saw a name, not what it later found about it.\n\nNamed as the contextualizer contract names it, and deliberately not\n`source`: the platform spells an organization's source entity that way,\nand this is a module of the scanner, not one of those.",
|
|
214
|
+
"enum": [
|
|
215
|
+
"axfr",
|
|
216
|
+
"crt.name",
|
|
217
|
+
"crt.sh",
|
|
218
|
+
"records",
|
|
219
|
+
"seed",
|
|
220
|
+
"urlscan.io"
|
|
221
|
+
],
|
|
222
|
+
"title": "SeenBy",
|
|
223
|
+
"type": "string"
|
|
224
|
+
},
|
|
225
|
+
"Sighting": {
|
|
194
226
|
"additionalProperties": false,
|
|
195
|
-
"description": "A name
|
|
227
|
+
"description": "A name a module saw, before anything was asked of it.",
|
|
196
228
|
"properties": {
|
|
197
229
|
"evidence_date": {
|
|
198
|
-
"pattern": "
|
|
230
|
+
"pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}Z$",
|
|
199
231
|
"title": "Evidence Date",
|
|
200
232
|
"type": "string"
|
|
201
233
|
},
|
|
@@ -207,20 +239,10 @@
|
|
|
207
239
|
"type": "string"
|
|
208
240
|
},
|
|
209
241
|
"kind": {
|
|
210
|
-
"const": "
|
|
242
|
+
"const": "sighting",
|
|
211
243
|
"title": "Kind",
|
|
212
244
|
"type": "string"
|
|
213
245
|
},
|
|
214
|
-
"outcome": {
|
|
215
|
-
"enum": [
|
|
216
|
-
"absent",
|
|
217
|
-
"delegated",
|
|
218
|
-
"nonexistent",
|
|
219
|
-
"unknown"
|
|
220
|
-
],
|
|
221
|
-
"title": "Outcome",
|
|
222
|
-
"type": "string"
|
|
223
|
-
},
|
|
224
246
|
"seed": {
|
|
225
247
|
"maxLength": 253,
|
|
226
248
|
"minLength": 1,
|
|
@@ -237,41 +259,17 @@
|
|
|
237
259
|
"host",
|
|
238
260
|
"seen_by",
|
|
239
261
|
"evidence_date",
|
|
240
|
-
"kind"
|
|
241
|
-
"outcome"
|
|
262
|
+
"kind"
|
|
242
263
|
],
|
|
243
|
-
"title": "
|
|
264
|
+
"title": "Sighting",
|
|
244
265
|
"type": "object"
|
|
245
266
|
},
|
|
246
|
-
"
|
|
247
|
-
"description": "How much of one seed's surface the run covered.\n\nA consumer that treats absence as a removal must be able to refuse\nanything but `complete`. It is per seed because a run takes several and\ngives up on the ones it cannot enumerate, so one status for the file would\nmake a whole run unusable when a single seed failed.",
|
|
248
|
-
"enum": [
|
|
249
|
-
"complete",
|
|
250
|
-
"failed",
|
|
251
|
-
"partial"
|
|
252
|
-
],
|
|
253
|
-
"title": "SeedStatus",
|
|
254
|
-
"type": "string"
|
|
255
|
-
},
|
|
256
|
-
"SeenBy": {
|
|
257
|
-
"description": "Which part of the run saw a name, not what it later found about it.\n\nNamed as the contextualizer contract names it, and deliberately not\n`source`: the platform spells an organization's source entity that way,\nand this is a module of the scanner, not one of those.",
|
|
258
|
-
"enum": [
|
|
259
|
-
"axfr",
|
|
260
|
-
"crt.name",
|
|
261
|
-
"crt.sh",
|
|
262
|
-
"records",
|
|
263
|
-
"seed",
|
|
264
|
-
"urlscan.io"
|
|
265
|
-
],
|
|
266
|
-
"title": "SeenBy",
|
|
267
|
-
"type": "string"
|
|
268
|
-
},
|
|
269
|
-
"Sighting": {
|
|
267
|
+
"Unresolved": {
|
|
270
268
|
"additionalProperties": false,
|
|
271
|
-
"description": "A name
|
|
269
|
+
"description": "A name that was asked and yielded no usable record, and why.",
|
|
272
270
|
"properties": {
|
|
273
271
|
"evidence_date": {
|
|
274
|
-
"pattern": "
|
|
272
|
+
"pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}Z$",
|
|
275
273
|
"title": "Evidence Date",
|
|
276
274
|
"type": "string"
|
|
277
275
|
},
|
|
@@ -283,10 +281,20 @@
|
|
|
283
281
|
"type": "string"
|
|
284
282
|
},
|
|
285
283
|
"kind": {
|
|
286
|
-
"const": "
|
|
284
|
+
"const": "resolution",
|
|
287
285
|
"title": "Kind",
|
|
288
286
|
"type": "string"
|
|
289
287
|
},
|
|
288
|
+
"outcome": {
|
|
289
|
+
"enum": [
|
|
290
|
+
"absent",
|
|
291
|
+
"delegated",
|
|
292
|
+
"nonexistent",
|
|
293
|
+
"unknown"
|
|
294
|
+
],
|
|
295
|
+
"title": "Outcome",
|
|
296
|
+
"type": "string"
|
|
297
|
+
},
|
|
290
298
|
"seed": {
|
|
291
299
|
"maxLength": 253,
|
|
292
300
|
"minLength": 1,
|
|
@@ -303,9 +311,10 @@
|
|
|
303
311
|
"host",
|
|
304
312
|
"seen_by",
|
|
305
313
|
"evidence_date",
|
|
306
|
-
"kind"
|
|
314
|
+
"kind",
|
|
315
|
+
"outcome"
|
|
307
316
|
],
|
|
308
|
-
"title": "
|
|
317
|
+
"title": "Unresolved",
|
|
309
318
|
"type": "object"
|
|
310
319
|
}
|
|
311
320
|
},
|
|
@@ -343,17 +352,17 @@
|
|
|
343
352
|
"resolution": {
|
|
344
353
|
"discriminator": {
|
|
345
354
|
"mapping": {
|
|
346
|
-
"absent": "#/$defs/
|
|
347
|
-
"delegated": "#/$defs/
|
|
348
|
-
"nonexistent": "#/$defs/
|
|
355
|
+
"absent": "#/$defs/Unresolved",
|
|
356
|
+
"delegated": "#/$defs/Unresolved",
|
|
357
|
+
"nonexistent": "#/$defs/Unresolved",
|
|
349
358
|
"refused": "#/$defs/Refusal",
|
|
350
|
-
"unknown": "#/$defs/
|
|
359
|
+
"unknown": "#/$defs/Unresolved"
|
|
351
360
|
},
|
|
352
361
|
"propertyName": "outcome"
|
|
353
362
|
},
|
|
354
363
|
"oneOf": [
|
|
355
364
|
{
|
|
356
|
-
"$ref": "#/$defs/
|
|
365
|
+
"$ref": "#/$defs/Unresolved"
|
|
357
366
|
},
|
|
358
367
|
{
|
|
359
368
|
"$ref": "#/$defs/Refusal"
|
|
@@ -392,17 +401,17 @@
|
|
|
392
401
|
{
|
|
393
402
|
"discriminator": {
|
|
394
403
|
"mapping": {
|
|
395
|
-
"absent": "#/$defs/
|
|
396
|
-
"delegated": "#/$defs/
|
|
397
|
-
"nonexistent": "#/$defs/
|
|
404
|
+
"absent": "#/$defs/Unresolved",
|
|
405
|
+
"delegated": "#/$defs/Unresolved",
|
|
406
|
+
"nonexistent": "#/$defs/Unresolved",
|
|
398
407
|
"refused": "#/$defs/Refusal",
|
|
399
|
-
"unknown": "#/$defs/
|
|
408
|
+
"unknown": "#/$defs/Unresolved"
|
|
400
409
|
},
|
|
401
410
|
"propertyName": "outcome"
|
|
402
411
|
},
|
|
403
412
|
"oneOf": [
|
|
404
413
|
{
|
|
405
|
-
"$ref": "#/$defs/
|
|
414
|
+
"$ref": "#/$defs/Unresolved"
|
|
406
415
|
},
|
|
407
416
|
{
|
|
408
417
|
"$ref": "#/$defs/Refusal"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"seeds": {
|
|
4
|
+
"example.com": "complete"
|
|
5
|
+
},
|
|
6
|
+
"entries": [
|
|
7
|
+
{
|
|
8
|
+
"seed": "example.com",
|
|
9
|
+
"host": "api.example.com",
|
|
10
|
+
"kind": "record",
|
|
11
|
+
"seen_by": "records",
|
|
12
|
+
"evidence_date": "٢٠٢٦-01-01T00:00:00.000Z",
|
|
13
|
+
"record_type": "A",
|
|
14
|
+
"record_value": "192.0.2.10",
|
|
15
|
+
"open_ports": [
|
|
16
|
+
80,
|
|
17
|
+
443
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"seeds": {
|
|
4
|
+
"example.com": "complete"
|
|
5
|
+
},
|
|
6
|
+
"entries": [
|
|
7
|
+
{
|
|
8
|
+
"seed": "example.com",
|
|
9
|
+
"host": "internal.example.com",
|
|
10
|
+
"kind": "resolution",
|
|
11
|
+
"seen_by": "records",
|
|
12
|
+
"evidence_date": "2026-01-01T00:00:03.000Z",
|
|
13
|
+
"outcome": "refused",
|
|
14
|
+
"refused_address": "not-an-address"
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"schema_version": 1,
|
|
3
|
-
"seeds": {
|
|
4
|
-
"example.com": "complete"
|
|
5
|
-
},
|
|
6
|
-
"entries": [
|
|
7
|
-
{
|
|
8
|
-
"seed": "example.com",
|
|
9
|
-
"host": "api.example.com",
|
|
10
|
-
"kind": "record",
|
|
11
|
-
"seen_by": "records",
|
|
12
|
-
"evidence_date": "2026-01-01T00:00:01.000Z",
|
|
13
|
-
"record_type": "A",
|
|
14
|
-
"record_value": "192.0.2.10"
|
|
15
|
-
}
|
|
16
|
-
]
|
|
17
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"schema_version": 1,
|
|
3
|
-
"seeds": {
|
|
4
|
-
"example.com": "complete"
|
|
5
|
-
},
|
|
6
|
-
"entries": [
|
|
7
|
-
{
|
|
8
|
-
"seed": "example.com",
|
|
9
|
-
"host": "",
|
|
10
|
-
"kind": "record",
|
|
11
|
-
"seen_by": "records",
|
|
12
|
-
"evidence_date": "2026-01-01T00:00:01.000Z",
|
|
13
|
-
"record_type": "A",
|
|
14
|
-
"record_value": "192.0.2.10"
|
|
15
|
-
}
|
|
16
|
-
]
|
|
17
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"schema_version": 1,
|
|
3
|
-
"seeds": {
|
|
4
|
-
"example.com": "complete"
|
|
5
|
-
},
|
|
6
|
-
"entries": [
|
|
7
|
-
{
|
|
8
|
-
"seed": "example.com",
|
|
9
|
-
"host": "api..example.com",
|
|
10
|
-
"kind": "record",
|
|
11
|
-
"seen_by": "records",
|
|
12
|
-
"evidence_date": "2026-01-01T00:00:01.000Z",
|
|
13
|
-
"record_type": "A",
|
|
14
|
-
"record_value": "192.0.2.10"
|
|
15
|
-
}
|
|
16
|
-
]
|
|
17
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"schema_version": 1,
|
|
3
|
-
"seeds": {
|
|
4
|
-
"example.com": "complete"
|
|
5
|
-
},
|
|
6
|
-
"entries": [
|
|
7
|
-
{
|
|
8
|
-
"seed": "example.com",
|
|
9
|
-
"host": "apí.example.com",
|
|
10
|
-
"kind": "record",
|
|
11
|
-
"seen_by": "records",
|
|
12
|
-
"evidence_date": "2026-01-01T00:00:01.000Z",
|
|
13
|
-
"record_type": "A",
|
|
14
|
-
"record_value": "192.0.2.10"
|
|
15
|
-
}
|
|
16
|
-
]
|
|
17
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"schema_version": 1,
|
|
3
|
-
"seeds": {
|
|
4
|
-
"example.com": "complete"
|
|
5
|
-
},
|
|
6
|
-
"entries": [
|
|
7
|
-
{
|
|
8
|
-
"seed": "example.com",
|
|
9
|
-
"host": "api.example.com.",
|
|
10
|
-
"kind": "record",
|
|
11
|
-
"seen_by": "records",
|
|
12
|
-
"evidence_date": "2026-01-01T00:00:01.000Z",
|
|
13
|
-
"record_type": "A",
|
|
14
|
-
"record_value": "192.0.2.10",
|
|
15
|
-
"open_ports": [
|
|
16
|
-
80,
|
|
17
|
-
443
|
|
18
|
-
]
|
|
19
|
-
}
|
|
20
|
-
]
|
|
21
|
-
}
|
|
File without changes
|
|
File without changes
|
|
@@ -24,6 +24,11 @@ class SeedStatus(StrEnum):
|
|
|
24
24
|
PARTIAL = "partial"
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
def _asserted(entry: Entry) -> frozenset[tuple[str, object]]:
|
|
28
|
+
# What an entry claims about its host, not how or when it was learned.
|
|
29
|
+
return frozenset(entry.model_dump(exclude={"evidence_date", "open_ports"}).items())
|
|
30
|
+
|
|
31
|
+
|
|
27
32
|
class ResolvesOutput(BaseModel):
|
|
28
33
|
"""One scan's report over the seeds it was given.
|
|
29
34
|
|
|
@@ -66,8 +71,3 @@ class ResolvesOutput(BaseModel):
|
|
|
66
71
|
raise ValueError(msg)
|
|
67
72
|
|
|
68
73
|
return self
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def _asserted(entry: Entry) -> frozenset[tuple[str, object]]:
|
|
72
|
-
# What an entry claims about its host, not how or when it was learned.
|
|
73
|
-
return frozenset(entry.model_dump(exclude={"evidence_date", "open_ports"}).items())
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|