oydid 0.9.4 → 0.9.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0f96270f4b2465c3dadb499841094e20c27c1e6512e18f69c2e2f7dbb2c6559f
4
- data.tar.gz: 0aff913041f3105b4569c41c88918439b50754431e24c85e51197f851fd436d9
3
+ metadata.gz: 3aa2cc9a77716310c41f0bda9dae80ca4939602b778384c79d874e63e8a27000
4
+ data.tar.gz: cef9d037771c87c0bb4fb3614696c53c6a4b9d358f016f074defc37d88c810bd
5
5
  SHA512:
6
- metadata.gz: 2505aeb0d16fc4a7786b24bbd1dcd607e7ac71122738ddc0bff9c6880d813813c5e6f2c134977a4bbf92b765565b8899822ae5d66dfe1977f214b89960ed6733
7
- data.tar.gz: 49b7a640acf6793d33a504b45c4f69b30767a86d95fe4f7fd3236c26e4494456ccf4e799effe12c856b85a2ab675fa9887664c934bdcdd93710381466af27145
6
+ metadata.gz: 43b1ef66a0a911c316a9c0a20d5018595ece344545faf4b986040210b10245f3557da15c5ade1681402ad816d918c75fd788fae5d788a5ba8a2469a39e5b7ee1
7
+ data.tar.gz: c08146faf1769b6f782e50bbd23468629e7a9069c9e20e9c6bee90cf0dce9a68f494aeceeb57b23d972bd331a73c1643a58bcac91cb73df2238eff33df0061e5
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.9.4
1
+ 0.9.5
data/lib/oydid/log.rb CHANGED
@@ -90,6 +90,21 @@ class Oydid
90
90
  end
91
91
  end
92
92
 
93
+ # collapse byte-identical log entries (keep first), keyed on the entry hash
94
+ # that `previous` references resolve to. A replayed CREATE or tangling
95
+ # TERMINATE would otherwise trip dag_did's CREATE/terminate counts and make
96
+ # the DID unresolvable - reachable through the unauthenticated append path
97
+ # with no signing key. Genuinely distinct fork entries (different hash) are
98
+ # kept and still fail closed as ambiguous.
99
+ def self.dedup_log(logs)
100
+ return logs unless logs.is_a?(Array)
101
+ seen = {}
102
+ logs.select do |el|
103
+ key = (canonical(el.slice("ts","op","doc","sig","previous")) rescue el.inspect)
104
+ seen.key?(key) ? false : (seen[key] = true)
105
+ end
106
+ end
107
+
93
108
  def self.dag_did(logs, options)
94
109
  dag = DAG.new
95
110
  dag_log = []
data/lib/oydid.rb CHANGED
@@ -142,6 +142,14 @@ class Oydid
142
142
  log_hash = hash_split[0]
143
143
  log_location = hash_split[1]
144
144
  end
145
+ # D11: the log reference may carry its location as %40 (the
146
+ # W3C-conform form of @); split on both, exactly as the DID split
147
+ # above does.
148
+ if log_hash.include?(CGI.escape LOCATION_PREFIX)
149
+ hash_split = log_hash.split(CGI.escape LOCATION_PREFIX)
150
+ log_hash = hash_split[0]
151
+ log_location = hash_split[1]
152
+ end
145
153
  end
146
154
  if log_location == ""
147
155
  log_location = DEFAULT_LOCATION
@@ -152,6 +160,7 @@ class Oydid
152
160
  if log_array.nil?
153
161
  return [nil, msg]
154
162
  else
163
+ log_array = dedup_log(log_array)
155
164
  if options[:trace]
156
165
  puts " .. Log retrieved"
157
166
  end
data/spec/oydid_spec.rb CHANGED
@@ -646,6 +646,37 @@ describe "OYDID handling" do
646
646
  end
647
647
  end
648
648
 
649
+ # SECURITY regression: the append endpoint is unauthenticated, so a byte-identical
650
+ # log entry can be replayed with no signing key. A duplicated CREATE trips dag_did's
651
+ # "wrong number of CREATE entries" and a duplicated tangling TERMINATE its terminate
652
+ # count, making the DID unresolvable. dedup_log collapses byte-identical replays
653
+ # (keep-first) at ingestion; genuinely distinct fork entries survive and still fail
654
+ # closed as ambiguous.
655
+ describe "dedup_log collapses byte-identical replayed entries" do
656
+ let(:create_e) { { "ts" => 1, "op" => 2, "doc" => "zCreate", "sig" => "z", "previous" => [] } }
657
+ let(:term_e) { { "ts" => 1, "op" => 0, "doc" => "zTerm", "sig" => "z", "previous" => [] } }
658
+
659
+ it "removes a byte-identical duplicate, keeping the first" do
660
+ deduped = Oydid.dedup_log([create_e, term_e, create_e.dup])
661
+ expect(deduped.length).to eq 2
662
+ expect(deduped.count { |e| e["op"] == 2 }).to eq 1
663
+ end
664
+
665
+ it "lets a de-duplicated log resolve where the raw one fails" do
666
+ logs = [create_e, term_e, create_e.dup]
667
+ expect(Oydid.dag_did(logs, { silent: true }).last).to match(/wrong number of CREATE/)
668
+ dag, create_index, _t, msg = Oydid.dag_did(Oydid.dedup_log(logs), { silent: true })
669
+ expect(msg).to eq ""
670
+ expect(dag).not_to be_nil
671
+ expect(create_index).to eq 0
672
+ end
673
+
674
+ it "keeps genuinely distinct entries (a real fork is not collapsed)" do
675
+ fork = create_e.merge("doc" => "zCreate2")
676
+ expect(Oydid.dedup_log([create_e, fork, term_e]).length).to eq 3
677
+ end
678
+ end
679
+
649
680
  # a broken repository (5xx) has to stay distinguishable from a DID that is
650
681
  # not stored there - otherwise the caller reports "not found" and thereby
651
682
  # claims the identifier never existed
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oydid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.4
4
+ version: 0.9.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christoph Fabianek
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-09-01 00:00:00.000000000 Z
10
+ date: 2026-09-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: simple_dag