active_sanction 1.0.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +538 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/CONTRIBUTING.md +312 -0
- data/LICENSE.txt +21 -0
- data/README.md +616 -0
- data/SECURITY.md +97 -0
- data/active_sanction.gemspec +98 -0
- data/docs/adding_a_source.md +1083 -0
- data/docs/api_stability.md +357 -0
- data/docs/bundle_format.md +407 -0
- data/lib/active_sanction/address.rb +170 -0
- data/lib/active_sanction/client.rb +341 -0
- data/lib/active_sanction/configuration.rb +721 -0
- data/lib/active_sanction/countries.txt +292 -0
- data/lib/active_sanction/country.rb +164 -0
- data/lib/active_sanction/deprecation.rb +168 -0
- data/lib/active_sanction/diff/change.rb +208 -0
- data/lib/active_sanction/diff.rb +348 -0
- data/lib/active_sanction/doctor/checkup.rb +356 -0
- data/lib/active_sanction/doctor/diagnosis.rb +245 -0
- data/lib/active_sanction/doctor/finding.rb +183 -0
- data/lib/active_sanction/doctor/profile.rb +384 -0
- data/lib/active_sanction/doctor/report.rb +227 -0
- data/lib/active_sanction/doctor.rb +347 -0
- data/lib/active_sanction/entity.rb +293 -0
- data/lib/active_sanction/error.rb +332 -0
- data/lib/active_sanction/fetcher/result.rb +116 -0
- data/lib/active_sanction/fetcher.rb +242 -0
- data/lib/active_sanction/http_client/errors.rb +94 -0
- data/lib/active_sanction/http_client/response.rb +129 -0
- data/lib/active_sanction/http_client.rb +324 -0
- data/lib/active_sanction/identifier.rb +212 -0
- data/lib/active_sanction/index/builder.rb +89 -0
- data/lib/active_sanction/index/candidate.rb +63 -0
- data/lib/active_sanction/index/entry.rb +66 -0
- data/lib/active_sanction/index/features.rb +112 -0
- data/lib/active_sanction/index.rb +381 -0
- data/lib/active_sanction/match_result.rb +345 -0
- data/lib/active_sanction/matcher.rb +388 -0
- data/lib/active_sanction/name.rb +183 -0
- data/lib/active_sanction/normalizer/cache.rb +82 -0
- data/lib/active_sanction/normalizer/dictionaries/honorifics.txt +30 -0
- data/lib/active_sanction/normalizer/dictionaries/legal_forms.txt +72 -0
- data/lib/active_sanction/normalizer/dictionaries/organization_stopwords.txt +20 -0
- data/lib/active_sanction/normalizer/dictionaries/particles.txt +35 -0
- data/lib/active_sanction/normalizer/dictionary/stoplist.rb +121 -0
- data/lib/active_sanction/normalizer/dictionary.rb +243 -0
- data/lib/active_sanction/normalizer/form.rb +279 -0
- data/lib/active_sanction/normalizer.rb +137 -0
- data/lib/active_sanction/parsers/column_shape.rb +274 -0
- data/lib/active_sanction/parsers/delimited_table/reader.rb +194 -0
- data/lib/active_sanction/parsers/delimited_table/row.rb +81 -0
- data/lib/active_sanction/parsers/delimited_table.rb +151 -0
- data/lib/active_sanction/parsers/format.rb +107 -0
- data/lib/active_sanction/parsers/join.rb +149 -0
- data/lib/active_sanction/parsers/spreadsheet/archive.rb +258 -0
- data/lib/active_sanction/parsers/spreadsheet/reader.rb +213 -0
- data/lib/active_sanction/parsers/spreadsheet/row.rb +88 -0
- data/lib/active_sanction/parsers/spreadsheet/workbook.rb +314 -0
- data/lib/active_sanction/parsers/spreadsheet.rb +168 -0
- data/lib/active_sanction/parsers/xml_records/backends/nokogiri.rb +134 -0
- data/lib/active_sanction/parsers/xml_records/backends/rexml.rb +157 -0
- data/lib/active_sanction/parsers/xml_records/backends.rb +103 -0
- data/lib/active_sanction/parsers/xml_records/builder.rb +90 -0
- data/lib/active_sanction/parsers/xml_records/reader.rb +134 -0
- data/lib/active_sanction/parsers/xml_records/record.rb +174 -0
- data/lib/active_sanction/parsers/xml_records.rb +140 -0
- data/lib/active_sanction/parsers.rb +94 -0
- data/lib/active_sanction/partial_date/parser.rb +124 -0
- data/lib/active_sanction/partial_date.rb +303 -0
- data/lib/active_sanction/payload_cache/checksum.rb +66 -0
- data/lib/active_sanction/payload_cache/entry.rb +304 -0
- data/lib/active_sanction/payload_cache.rb +366 -0
- data/lib/active_sanction/phonetics/double_metaphone.rb +662 -0
- data/lib/active_sanction/phonetics.rb +40 -0
- data/lib/active_sanction/query.rb +290 -0
- data/lib/active_sanction/rescreen/alert.rb +314 -0
- data/lib/active_sanction/rescreen.rb +332 -0
- data/lib/active_sanction/scorer/adjustments.rb +241 -0
- data/lib/active_sanction/scorer/name_score.rb +279 -0
- data/lib/active_sanction/scorer/reason.rb +136 -0
- data/lib/active_sanction/scorer/result.rb +129 -0
- data/lib/active_sanction/scorer/subject.rb +235 -0
- data/lib/active_sanction/scorer/weights.rb +306 -0
- data/lib/active_sanction/scorer.rb +303 -0
- data/lib/active_sanction/similarity/jaro_winkler.rb +210 -0
- data/lib/active_sanction/similarity/levenshtein.rb +179 -0
- data/lib/active_sanction/similarity/token_set.rb +159 -0
- data/lib/active_sanction/similarity/token_sort.rb +120 -0
- data/lib/active_sanction/similarity.rb +183 -0
- data/lib/active_sanction/snapshot/bundle/header.rb +302 -0
- data/lib/active_sanction/snapshot/bundle/payload.rb +185 -0
- data/lib/active_sanction/snapshot/bundle/signature.rb +201 -0
- data/lib/active_sanction/snapshot/bundle.rb +381 -0
- data/lib/active_sanction/snapshot.rb +305 -0
- data/lib/active_sanction/sources/australia_dfat/published_date.rb +166 -0
- data/lib/active_sanction/sources/australia_dfat/record.rb +319 -0
- data/lib/active_sanction/sources/australia_dfat.rb +291 -0
- data/lib/active_sanction/sources/base.rb +295 -0
- data/lib/active_sanction/sources/canada_sema/record.rb +282 -0
- data/lib/active_sanction/sources/canada_sema/source_ref.rb +89 -0
- data/lib/active_sanction/sources/canada_sema.rb +153 -0
- data/lib/active_sanction/sources/definition.rb +360 -0
- data/lib/active_sanction/sources/eu_fsf/record.rb +467 -0
- data/lib/active_sanction/sources/eu_fsf.rb +203 -0
- data/lib/active_sanction/sources/ofac/record.rb +251 -0
- data/lib/active_sanction/sources/ofac/remarks_parser/coverage.rb +120 -0
- data/lib/active_sanction/sources/ofac/remarks_parser/vocabulary.rb +121 -0
- data/lib/active_sanction/sources/ofac/remarks_parser.rb +312 -0
- data/lib/active_sanction/sources/ofac.rb +248 -0
- data/lib/active_sanction/sources/ofac_consolidated/record.rb +37 -0
- data/lib/active_sanction/sources/ofac_consolidated.rb +203 -0
- data/lib/active_sanction/sources/ofac_sdn.rb +43 -0
- data/lib/active_sanction/sources/remarks.rb +84 -0
- data/lib/active_sanction/sources/uk_sanctions_list/published_date.rb +104 -0
- data/lib/active_sanction/sources/uk_sanctions_list/record.rb +412 -0
- data/lib/active_sanction/sources/uk_sanctions_list.rb +214 -0
- data/lib/active_sanction/sources/un_consolidated/record.rb +279 -0
- data/lib/active_sanction/sources/un_consolidated.rb +140 -0
- data/lib/active_sanction/sources.rb +196 -0
- data/lib/active_sanction/storage/active_record/reader.rb +124 -0
- data/lib/active_sanction/storage/active_record/row.rb +175 -0
- data/lib/active_sanction/storage/active_record/writer.rb +141 -0
- data/lib/active_sanction/storage/active_record.rb +277 -0
- data/lib/active_sanction/storage/base.rb +216 -0
- data/lib/active_sanction/storage/file_system.rb +373 -0
- data/lib/active_sanction/storage/memory.rb +67 -0
- data/lib/active_sanction/storage/meta.rb +188 -0
- data/lib/active_sanction/storage.rb +74 -0
- data/lib/active_sanction/subject.rb +271 -0
- data/lib/active_sanction/sync/report.rb +245 -0
- data/lib/active_sanction/sync/result.rb +296 -0
- data/lib/active_sanction/sync.rb +390 -0
- data/lib/active_sanction/validator_store/file_system.rb +103 -0
- data/lib/active_sanction/validator_store/memory.rb +41 -0
- data/lib/active_sanction/validator_store.rb +119 -0
- data/lib/active_sanction/validators.rb +195 -0
- data/lib/active_sanction/version.rb +23 -0
- data/lib/active_sanction.rb +331 -0
- data/lib/generators/active_sanction/install/install_generator.rb +60 -0
- data/lib/generators/active_sanction/install/templates/create_active_sanction_tables.rb.tt +142 -0
- metadata +236 -0
data/SECURITY.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Security policy
|
|
2
|
+
|
|
3
|
+
## Reporting a vulnerability
|
|
4
|
+
|
|
5
|
+
**Report privately. Do not open a public issue.**
|
|
6
|
+
|
|
7
|
+
Use GitHub's private vulnerability reporting —
|
|
8
|
+
[**Report a vulnerability**](https://github.com/Babystep-Technologies/active_sanction/security/advisories/new)
|
|
9
|
+
on the Security tab — which opens a draft advisory only you and the maintainers
|
|
10
|
+
can read. If that is unavailable to you, email
|
|
11
|
+
**shen.marshall@gmail.com** with `active_sanction` in the subject.
|
|
12
|
+
|
|
13
|
+
Please include the version, the Ruby, the source list involved if there is one,
|
|
14
|
+
and the smallest input that reproduces it. A record from a published government
|
|
15
|
+
file is more useful than a synthetic one, and naming the record is not a
|
|
16
|
+
disclosure — it is already public.
|
|
17
|
+
|
|
18
|
+
## What to expect
|
|
19
|
+
|
|
20
|
+
This project is maintained by one person, so these are commitments about
|
|
21
|
+
communication rather than about engineering capacity.
|
|
22
|
+
|
|
23
|
+
| | |
|
|
24
|
+
|---|---|
|
|
25
|
+
| Acknowledgement | within 3 business days |
|
|
26
|
+
| Initial assessment, in scope or not | within 10 business days |
|
|
27
|
+
| Status update while a fix is open | at least every 14 days |
|
|
28
|
+
| Coordinated disclosure window | 90 days by default, negotiable |
|
|
29
|
+
|
|
30
|
+
A fix ships in a release, with an advisory naming the versions affected and a
|
|
31
|
+
`CHANGELOG.md` entry. **You are credited by name unless you ask not to be.** If
|
|
32
|
+
a report turns out to be a plain bug rather than a vulnerability, we will say so
|
|
33
|
+
and move it to a public issue with your agreement.
|
|
34
|
+
|
|
35
|
+
## What is in scope
|
|
36
|
+
|
|
37
|
+
**A screening library that fails to report a listed name is a security
|
|
38
|
+
problem, not merely an inaccuracy.** Somebody may be relying on an empty result
|
|
39
|
+
to clear a payment, and the liability for a missed designation is real. So the
|
|
40
|
+
scope here is wider than it would be for an ordinary library:
|
|
41
|
+
|
|
42
|
+
- **A systematic screening bypass.** A class of name, a script, a
|
|
43
|
+
transliteration or a crafted input that reliably evades matching — not one
|
|
44
|
+
fuzzy score you disagree with, but a shape of input that gets a whole
|
|
45
|
+
category of listed records past the matcher.
|
|
46
|
+
- **Integrity of a snapshot or a bundle.** Anything that makes the gem accept a
|
|
47
|
+
modified list as authentic: a signature that verifies when it should not, a
|
|
48
|
+
checksum that can be satisfied by altered content, a bundle that loads
|
|
49
|
+
without the verification the caller asked for.
|
|
50
|
+
- **Provenance of a stamped result.** A screening result carries the snapshot
|
|
51
|
+
checksum, the matcher version and the weights so that a decision can be
|
|
52
|
+
re-derived years later. Anything that lets a result carry a stamp that does
|
|
53
|
+
not describe what actually answered it is in scope.
|
|
54
|
+
- **The fetch path.** Redirect handling that can be steered somewhere the
|
|
55
|
+
publisher did not point, certificate or transport verification that can be
|
|
56
|
+
downgraded, a cached payload that can be poisoned.
|
|
57
|
+
- **Parsing a hostile file.** Denial of service, memory exhaustion, external
|
|
58
|
+
entity resolution or file disclosure triggered by a malformed or malicious
|
|
59
|
+
list, archive or bundle. A government endpoint is a third party, and one that
|
|
60
|
+
has been compromised serves this library its input.
|
|
61
|
+
- **The storage and cache paths.** Path traversal, a file written outside the
|
|
62
|
+
directory the host configured, or predictable temporary files.
|
|
63
|
+
- **Leaking a screened name.** Any path by which a subject's name, or the
|
|
64
|
+
details submitted with it, leaves the host process — a log line, an outbound
|
|
65
|
+
request, an error message. *No name you screen leaves your process* is a
|
|
66
|
+
promise this library makes, and a break in it is a vulnerability.
|
|
67
|
+
|
|
68
|
+
## What is not in scope
|
|
69
|
+
|
|
70
|
+
- **A single missed record or false alert.** That is match quality. It belongs
|
|
71
|
+
in a public issue and in
|
|
72
|
+
[`benchmark/fixtures/labeled_set.yml`](benchmark/fixtures/labeled_set.yml),
|
|
73
|
+
where it becomes a regression test. The line between this and a systematic
|
|
74
|
+
bypass is whether the technique generalises; if you are unsure, report it
|
|
75
|
+
privately and we will sort it out.
|
|
76
|
+
- **A jurisdiction this gem does not cover,** or a list it does not read. The
|
|
77
|
+
README says which seven ship.
|
|
78
|
+
- **The absence of PEP data, adverse media, beneficial ownership or 50 Percent
|
|
79
|
+
Rule resolution.** These are documented non-goals, not gaps.
|
|
80
|
+
- **A publisher's own data being wrong,** or a government endpoint being down.
|
|
81
|
+
Upstream format changes are handled by the canary and belong in a public
|
|
82
|
+
issue.
|
|
83
|
+
- **Vulnerabilities in the commercial hosted service.** Report those to the
|
|
84
|
+
same address; they are not tracked in this repository.
|
|
85
|
+
- **Reports generated by a scanner with no working reproduction,** and findings
|
|
86
|
+
in development-only tooling that never ships in the gem.
|
|
87
|
+
|
|
88
|
+
## Supported versions
|
|
89
|
+
|
|
90
|
+
Pre-1.0, only the most recent release receives fixes. There are no backports to
|
|
91
|
+
earlier 0.x versions. Once 1.0 ships, this section will state a support window.
|
|
92
|
+
|
|
93
|
+
## Dependencies
|
|
94
|
+
|
|
95
|
+
The gem's runtime dependencies are `csv`, `rexml` and `sorbet-runtime`, all
|
|
96
|
+
pure Ruby. A vulnerability in one of those belongs upstream, but tell us anyway
|
|
97
|
+
if this library's use of it is what makes it reachable — that part is ours.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/active_sanction/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "active_sanction"
|
|
7
|
+
spec.version = ActiveSanction::VERSION
|
|
8
|
+
spec.authors = ["Marshall Shen"]
|
|
9
|
+
spec.email = ["shen.marshall@gmail.com"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "A Ruby toolkit that manages sanction lists around the world."
|
|
12
|
+
spec.description = "ActiveSanction fetches government sanctions lists, normalizes them into a single record model, persists them in preferred storage, and screens names against them with explainable fuzzy match scores. Every match carries structured reasons and stamps the snapshot checksum, matcher version, and thresholds so a screening decision can be re-derived during an audit. New sources register through public extension points without forking the gem."
|
|
13
|
+
spec.homepage = "https://github.com/Babystep-Technologies/active_sanction"
|
|
14
|
+
spec.license = "MIT"
|
|
15
|
+
spec.required_ruby_version = ">= 3.1"
|
|
16
|
+
|
|
17
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
|
18
|
+
|
|
19
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
20
|
+
spec.metadata["source_code_uri"] = "https://github.com/Babystep-Technologies/active_sanction/tree/main"
|
|
21
|
+
spec.metadata["changelog_uri"] = "https://github.com/Babystep-Technologies/active_sanction/blob/main/CHANGELOG.md"
|
|
22
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
|
23
|
+
|
|
24
|
+
# `csv` and `rexml` are stdlib, not third parties: both shipped inside Ruby
|
|
25
|
+
# and have since been moved to bundled gems, which means they now have to be
|
|
26
|
+
# named to be requirable. Declaring them keeps the gem loadable without
|
|
27
|
+
# changing what it costs a user -- there is still nothing here to compile and
|
|
28
|
+
# nothing to vendor.
|
|
29
|
+
#
|
|
30
|
+
# Nokogiri is deliberately *not* declared. The XML toolkit can drive libxml2
|
|
31
|
+
# and will if an application asks it to, but a compliance library should not
|
|
32
|
+
# be the reason a deployment starts building native extensions.
|
|
33
|
+
spec.add_dependency "csv", "~> 3.3"
|
|
34
|
+
spec.add_dependency "rexml", "~> 3.3"
|
|
35
|
+
|
|
36
|
+
# Sorbet's runtime, and the only piece of the checker that ships. The `sig`
|
|
37
|
+
# blocks in `lib/` are ordinary method calls, so the library does not load
|
|
38
|
+
# without it. It is pure Ruby with nothing to compile, which is the rule the
|
|
39
|
+
# Nokogiri paragraph above is really about. The static half -- `sorbet` and
|
|
40
|
+
# `tapioca` -- stays in the Gemfile, where a host never sees it.
|
|
41
|
+
#
|
|
42
|
+
# It is not free at call time, so the rule is that a signature on a path
|
|
43
|
+
# which runs per query is declared `.checked(:tests)`: enforced by this
|
|
44
|
+
# gem's suite and inert in a host's process. The normalizer is the first
|
|
45
|
+
# such path and carries it throughout; the scorers join it as they land.
|
|
46
|
+
# A host that wants none of it at all can set
|
|
47
|
+
# `T::Configuration.default_checked_level = :never` before requiring the gem,
|
|
48
|
+
# which spec/sorbet_runtime_spec.rb holds us to.
|
|
49
|
+
spec.add_dependency "sorbet-runtime", "~> 0.6"
|
|
50
|
+
|
|
51
|
+
# What ships. Everything tracked in git, minus the parts of this repository
|
|
52
|
+
# that exist to develop it.
|
|
53
|
+
#
|
|
54
|
+
# `spec/` carries the fixtures, which are trimmed copies of government files
|
|
55
|
+
# and are the largest thing here. `sorbet/` is the checker's working
|
|
56
|
+
# directory -- its config and the RBIs tapioca generates for our
|
|
57
|
+
# dependencies. `benchmark/` measures the machine it runs on and answers a
|
|
58
|
+
# question about this repository, and `canary/` (#69) watches seven
|
|
59
|
+
# government endpoints on this repository's behalf -- both are here to keep
|
|
60
|
+
# the library honest, and neither does anything in an application that
|
|
61
|
+
# installed it. The rest is toolchain: CI, the linter's config, the Rakefile
|
|
62
|
+
# that drives all of them, and `bin/` -- none of which do anything inside an
|
|
63
|
+
# installed gem, and each of which is one more file a security scan has to be
|
|
64
|
+
# told to ignore.
|
|
65
|
+
#
|
|
66
|
+
# `site/` (#103) is the documentation site's sources -- an Astro project, a
|
|
67
|
+
# theme, a link checker and a `node_modules` the moment anybody builds it.
|
|
68
|
+
# It exists to be published at a URL, and an application that installed this
|
|
69
|
+
# gem has no use for the machinery that publishes it.
|
|
70
|
+
#
|
|
71
|
+
# `AGENTS.md` and `.claude/` (#113) are instructions for a coding agent
|
|
72
|
+
# working *on* this repository -- which adapter to copy, which gates to run,
|
|
73
|
+
# which commands reach a government endpoint and must not be run unattended.
|
|
74
|
+
# They are toolchain in the same sense the Rakefile is, and say nothing to an
|
|
75
|
+
# application that installed the gem. The leading-dot rule below already
|
|
76
|
+
# excludes `.claude/`; `AGENTS.md` is named because it sits at the root.
|
|
77
|
+
#
|
|
78
|
+
# What is deliberately kept is `docs/`, which is linked from the README and
|
|
79
|
+
# is as much a part of the library as the code is. That is the whole reason
|
|
80
|
+
# the site is not built out of `docs/`: doing so would ship an Astro config
|
|
81
|
+
# and a set of layouts into every application that installs this gem.
|
|
82
|
+
dev_only = %r{
|
|
83
|
+
\A(?:
|
|
84
|
+
(?:test|spec|features|sorbet|benchmark|canary|bin|site|\.github)/ |
|
|
85
|
+
Gemfile |
|
|
86
|
+
Rakefile |
|
|
87
|
+
AGENTS\.md |
|
|
88
|
+
\.
|
|
89
|
+
)
|
|
90
|
+
}x
|
|
91
|
+
|
|
92
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
93
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(dev_only) }
|
|
94
|
+
end
|
|
95
|
+
spec.bindir = "exe"
|
|
96
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
97
|
+
spec.require_paths = ["lib"]
|
|
98
|
+
end
|