concordia-protocol 0.1.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.
Files changed (82) hide show
  1. concordia_protocol-0.1.0/.github/workflows/ci.yml +32 -0
  2. concordia_protocol-0.1.0/.github/workflows/publish.yml +21 -0
  3. concordia_protocol-0.1.0/.github/workflows/test-publish.yml +22 -0
  4. concordia_protocol-0.1.0/.gitignore +22 -0
  5. concordia_protocol-0.1.0/.mcp.json +9 -0
  6. concordia_protocol-0.1.0/BUG_REPORT.md +312 -0
  7. concordia_protocol-0.1.0/CLAUDE.md +205 -0
  8. concordia_protocol-0.1.0/CNAME +1 -0
  9. concordia_protocol-0.1.0/CONCORDIA_NEXT_FOUR_STAGES.md +448 -0
  10. concordia_protocol-0.1.0/CONTRIBUTING.md +49 -0
  11. concordia_protocol-0.1.0/COWORK_CONTEXT.md +62 -0
  12. concordia_protocol-0.1.0/KNOWN_ISSUES.md +30 -0
  13. concordia_protocol-0.1.0/LICENSE +190 -0
  14. concordia_protocol-0.1.0/MERGE_GATE_REPORT.md +147 -0
  15. concordia_protocol-0.1.0/PKG-INFO +238 -0
  16. concordia_protocol-0.1.0/README.md +209 -0
  17. concordia_protocol-0.1.0/REVIEW_MAP.md +543 -0
  18. concordia_protocol-0.1.0/SECURITY_AUDIT.md +256 -0
  19. concordia_protocol-0.1.0/SPEC.md +1091 -0
  20. concordia_protocol-0.1.0/SPRINT_CONTRACT.md +33 -0
  21. concordia_protocol-0.1.0/SPRINT_EVAL.md +1004 -0
  22. concordia_protocol-0.1.0/SPRINT_RESULT.md +70 -0
  23. concordia_protocol-0.1.0/attestation.schema.json +406 -0
  24. concordia_protocol-0.1.0/concordia/__init__.py +101 -0
  25. concordia_protocol-0.1.0/concordia/__main__.py +50 -0
  26. concordia_protocol-0.1.0/concordia/agent.py +314 -0
  27. concordia_protocol-0.1.0/concordia/attestation.py +136 -0
  28. concordia_protocol-0.1.0/concordia/auth.py +119 -0
  29. concordia_protocol-0.1.0/concordia/degradation.py +389 -0
  30. concordia_protocol-0.1.0/concordia/discovery.py +160 -0
  31. concordia_protocol-0.1.0/concordia/mcp_server.py +2613 -0
  32. concordia_protocol-0.1.0/concordia/message.py +105 -0
  33. concordia_protocol-0.1.0/concordia/offer.py +148 -0
  34. concordia_protocol-0.1.0/concordia/receipt_bundle.py +547 -0
  35. concordia_protocol-0.1.0/concordia/registry.py +365 -0
  36. concordia_protocol-0.1.0/concordia/relay.py +598 -0
  37. concordia_protocol-0.1.0/concordia/reputation/__init__.py +21 -0
  38. concordia_protocol-0.1.0/concordia/reputation/query.py +314 -0
  39. concordia_protocol-0.1.0/concordia/reputation/scorer.py +390 -0
  40. concordia_protocol-0.1.0/concordia/reputation/store.py +372 -0
  41. concordia_protocol-0.1.0/concordia/sanctuary_bridge.py +350 -0
  42. concordia_protocol-0.1.0/concordia/schema_validator.py +122 -0
  43. concordia_protocol-0.1.0/concordia/session.py +311 -0
  44. concordia_protocol-0.1.0/concordia/signing.py +242 -0
  45. concordia_protocol-0.1.0/concordia/types.py +193 -0
  46. concordia_protocol-0.1.0/concordia/want_registry.py +636 -0
  47. concordia_protocol-0.1.0/examples/demo_camera_negotiation.py +328 -0
  48. concordia_protocol-0.1.0/examples/demo_quick_negotiation.py +36 -0
  49. concordia_protocol-0.1.0/examples/freelance_design_negotiation.md +308 -0
  50. concordia_protocol-0.1.0/examples/furniture_dormant_negotiation.md +285 -0
  51. concordia_protocol-0.1.0/launch/hackernews_post.md +40 -0
  52. concordia_protocol-0.1.0/launch/pitch_devrel.md +46 -0
  53. concordia_protocol-0.1.0/launch/twitter_thread.md +112 -0
  54. concordia_protocol-0.1.0/nonexistent.lock +0 -0
  55. concordia_protocol-0.1.0/plugin/.claude-plugin/plugin.json +19 -0
  56. concordia_protocol-0.1.0/plugin/.mcp.json +9 -0
  57. concordia_protocol-0.1.0/plugin/README.md +24 -0
  58. concordia_protocol-0.1.0/plugin/skills/concordia/SKILL.md +173 -0
  59. concordia_protocol-0.1.0/pyproject.toml +54 -0
  60. concordia_protocol-0.1.0/requirements.lock +38 -0
  61. concordia_protocol-0.1.0/schemas/attestation.schema.json +406 -0
  62. concordia_protocol-0.1.0/schemas/receipt_bundle.schema.json +82 -0
  63. concordia_protocol-0.1.0/tests/__init__.py +0 -0
  64. concordia_protocol-0.1.0/tests/conftest.py +195 -0
  65. concordia_protocol-0.1.0/tests/test_attestation.py +131 -0
  66. concordia_protocol-0.1.0/tests/test_attestation_signature_verification.py +263 -0
  67. concordia_protocol-0.1.0/tests/test_authentication.py +432 -0
  68. concordia_protocol-0.1.0/tests/test_discovery.py +719 -0
  69. concordia_protocol-0.1.0/tests/test_integration.py +636 -0
  70. concordia_protocol-0.1.0/tests/test_mcp_server.py +945 -0
  71. concordia_protocol-0.1.0/tests/test_offer.py +100 -0
  72. concordia_protocol-0.1.0/tests/test_prompt_injection.py +322 -0
  73. concordia_protocol-0.1.0/tests/test_receipt_bundle.py +774 -0
  74. concordia_protocol-0.1.0/tests/test_relay.py +836 -0
  75. concordia_protocol-0.1.0/tests/test_reputation.py +1161 -0
  76. concordia_protocol-0.1.0/tests/test_sanctuary_bridge.py +682 -0
  77. concordia_protocol-0.1.0/tests/test_schema.py +113 -0
  78. concordia_protocol-0.1.0/tests/test_security.py +697 -0
  79. concordia_protocol-0.1.0/tests/test_session.py +238 -0
  80. concordia_protocol-0.1.0/tests/test_session_signature_verification.py +286 -0
  81. concordia_protocol-0.1.0/tests/test_signing.py +238 -0
  82. concordia_protocol-0.1.0/tests/test_want_registry.py +761 -0
@@ -0,0 +1,32 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.10", "3.11", "3.12"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Set up Python ${{ matrix.python-version }}
20
+ uses: actions/setup-python@v5
21
+ with:
22
+ python-version: ${{ matrix.python-version }}
23
+
24
+ - name: Install dependencies
25
+ run: pip install -e ".[dev]"
26
+
27
+ - name: Audit dependencies for CVEs
28
+ # CVE-2026-4539 (pygments 2.19.2): no fix version available — see KNOWN_ISSUES.md
29
+ run: pip install pip-audit && pip-audit --requirement requirements.lock --ignore-vuln CVE-2026-4539
30
+
31
+ - name: Run tests
32
+ run: pytest -v
@@ -0,0 +1,21 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published, released]
6
+ workflow_dispatch:
7
+
8
+ jobs:
9
+ publish:
10
+ runs-on: ubuntu-latest
11
+ environment: pypi
12
+ permissions:
13
+ id-token: write # Trusted publisher (OIDC)
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: "3.12"
19
+ - run: pip install build
20
+ - run: python -m build
21
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,22 @@
1
+ name: Publish to TestPyPI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+
7
+ jobs:
8
+ test-publish:
9
+ runs-on: ubuntu-latest
10
+ environment: testpypi
11
+ permissions:
12
+ id-token: write # Trusted publisher (OIDC)
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ - uses: actions/setup-python@v5
16
+ with:
17
+ python-version: "3.12"
18
+ - run: pip install build
19
+ - run: python -m build
20
+ - uses: pypa/gh-action-pypi-publish@release/v1
21
+ with:
22
+ repository-url: https://test.pypi.org/legacy/
@@ -0,0 +1,22 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .eggs/
8
+ *.egg
9
+ .venv/
10
+ venv/
11
+ env/
12
+ .env
13
+ .DS_Store
14
+ *.swp
15
+ *.swo
16
+ *~
17
+ .idea/
18
+ .vscode/
19
+ *.iml
20
+
21
+ # Internal documents
22
+ SERVICE_ARCHITECTURE.md
@@ -0,0 +1,9 @@
1
+ {
2
+ "mcpServers": {
3
+ "concordia": {
4
+ "command": "python",
5
+ "args": ["-m", "concordia"],
6
+ "env": {}
7
+ }
8
+ }
9
+ }
@@ -0,0 +1,312 @@
1
+ # BUG_REPORT.md — Functional Bug Sweep
2
+
3
+ **Date:** 2026-03-28
4
+ **Scope:** Sanctuary v0.3.0 (TypeScript) + Concordia v0.1.0-draft (Python)
5
+ **Posture:** QA functional sweep — logic errors, broken features, state corruption, failure modes
6
+ **Exclusions:** Security vulnerabilities logged in SECURITY_AUDIT.md (SEC-001 through SEC-024) are not duplicated here
7
+
8
+ ---
9
+
10
+ ## SWEEP 1: CORE USER PATHS
11
+
12
+ ---
13
+
14
+ ### BUG-001 — state_export() Returns Empty Bundle on Fresh Server Session
15
+
16
+ **Severity:** Blocking
17
+ **File:** `server/src/l1-cognitive/state-store.ts:461-470`
18
+ **Description:** The `export()` method discovers namespaces by iterating `this.contentHashes.keys()`, which is a lazily-populated in-memory cache. The cache is populated only when `state_read()`, `state_write()`, or `state_list()` operations are performed. On a fresh server session — where the user starts the server, stores data in a previous session, and then calls `state_export` without first touching any state — the cache is empty and the export returns a bundle with zero namespaces. The actual encrypted data remains on disk but is invisible to the export function.
19
+
20
+ **Reproduction:**
21
+ 1. Start Sanctuary server with a passphrase, write several state entries across multiple namespaces, stop the server.
22
+ 2. Restart the server with the same passphrase.
23
+ 3. Immediately call `state_export` without calling `state_read` or `state_list` first.
24
+ 4. Receive an export bundle containing zero entries.
25
+ 5. User believes they have a complete backup, but the bundle is empty.
26
+
27
+ **Sovereignty violation:** Contradicts Property #2: "A user can retrieve a complete export of all data the system holds about them."
28
+
29
+ ---
30
+
31
+ ### BUG-002 — state_import() Creates Version Cache Staleness Leading to Monotonicity Violation
32
+
33
+ **Severity:** Major
34
+ **File:** `server/src/l1-cognitive/state-store.ts:558-569`
35
+ **Description:** When `import()` skips entries due to version conflict (using `conflictResolution="version"`), the in-memory version cache is not updated with the skipped entry's version. Subsequent `state_write()` calls use the stale cache value to determine the next version number, potentially producing a version lower than what exists on disk. This violates the monotonic version invariant that rollback detection depends on.
36
+
37
+ **Reproduction:**
38
+ 1. Write entry `ns/key` with version 5 on disk.
39
+ 2. Import a bundle containing `ns/key` at version 4 with `conflictResolution="version"` — entry is correctly skipped (disk version is higher).
40
+ 3. The version cache for `ns/key` still holds the pre-import stale value (e.g., version 2 from a previous read).
41
+ 4. Call `state_write("ns", "key", "new-value")` — the write increments the cache value (2 → 3), producing version 3 on disk.
42
+ 5. Disk now has version 3 overwriting version 5 — monotonicity violated.
43
+
44
+ ---
45
+
46
+ ## SWEEP 2: BOUNDARY CONDITIONS
47
+
48
+ ---
49
+
50
+ ### BUG-003 — Concordia Session apply_message Crashes on Invalid Message Type
51
+
52
+ **Severity:** Blocking
53
+ **File:** `concordia/session.py:124`
54
+ **Description:** The `apply_message()` method converts `message["type"]` directly to a `MessageType` enum via `MessageType(message["type"])` with no prior validation. If the message contains an invalid type string (e.g., `"invalid.type"`), Python raises a raw `ValueError` with no contextual information. The error is not caught and propagates as an unhandled exception, which will crash the MCP tool handler or return an opaque error to the agent.
55
+
56
+ **Reproduction:**
57
+ 1. Create an active Concordia session.
58
+ 2. Call `concordia_send_message` (or directly invoke `apply_message`) with `{"type": "invalid.type", "content": {}}`.
59
+ 3. Receive: `ValueError: 'invalid.type' is not a valid MessageType`.
60
+ 4. The error provides no session context, no suggestion, and no recovery path.
61
+
62
+ **Fix:** Validate before conversion: `if message["type"] not in [mt.value for mt in MessageType]: raise InvalidTransitionError(...)`.
63
+
64
+ ---
65
+
66
+ ### BUG-004 — Relay Capacity Checks Use >= Instead of >, Reducing Effective Capacity by One
67
+
68
+ **Severity:** Minor
69
+ **File:** `concordia/relay.py:349, 378, 240, 499`
70
+ **Description:** All capacity limit checks in the relay module use `>=` rather than `>`:
71
+ - Line 240: `if len(self._sessions) >= self.MAX_SESSIONS` (max 10,000)
72
+ - Line 349: `if len(session.transcript) >= self.MAX_TRANSCRIPT_SIZE` (max 10,000)
73
+ - Line 378: `if len(mailbox) >= self.MAX_MAILBOX_SIZE` (max 1,000)
74
+ - Line 499: `if len(self._archives) >= self.MAX_ARCHIVES` (max 50,000)
75
+
76
+ This means the actual capacity is one less than the named constant in each case (e.g., 9,999 transcript messages instead of 10,000). While functional, the constants are misleading, and users relying on the documented capacity will hit the limit one item early.
77
+
78
+ **Reproduction:**
79
+ 1. Create a relay session and send exactly 9,999 messages.
80
+ 2. Send the 10,000th message — rejected with "Transcript full."
81
+ 3. Expected: 10,000 messages allowed per `MAX_TRANSCRIPT_SIZE = 10_000`.
82
+
83
+ ---
84
+
85
+ ## SWEEP 3: THE OVERLAP SURFACE (BRIDGE)
86
+
87
+ ---
88
+
89
+ ### BUG-005 — Bridge Payload Shape Mismatch: Concordia Produces Generic Proof Payload, Sanctuary Expects ConcordiaOutcome
90
+
91
+ **Severity:** Blocking
92
+ **File:** `concordia/sanctuary_bridge.py:82-127`, `server/src/bridge/tools.ts:115-172`
93
+ **Description:** Concordia's `build_commitment_payload()` produces a generic proof commitment payload shaped for Sanctuary's `proof_commitment` tool: `{"tool": "sanctuary/proof_commitment", "arguments": {"value": "..."}}`. However, Sanctuary's `bridge_commit` tool expects a `ConcordiaOutcome`-shaped object with negotiation-specific fields: `session_id`, `terms`, `parties`, `rounds`, `proposer_did`, `acceptor_did`. These are entirely incompatible schemas. The bridge cannot function as designed — forwarding Concordia's output to Sanctuary's `bridge_commit` will fail schema validation.
94
+
95
+ **Reproduction:**
96
+ 1. Complete a Concordia negotiation to AGREED state.
97
+ 2. Call `concordia_sanctuary_bridge_commit` to generate the bridge payload.
98
+ 3. Forward the returned payload to Sanctuary's `bridge_commit` tool.
99
+ 4. Sanctuary rejects the payload: missing required fields `proposer_did`, `acceptor_did`, `rounds`, etc.
100
+
101
+ **Impact:** The bridge integration path is non-functional end-to-end.
102
+
103
+ ---
104
+
105
+ ### BUG-006 — Bridge Requires proposer_did / acceptor_did That Concordia Cannot Provide
106
+
107
+ **Severity:** Blocking
108
+ **File:** `server/src/bridge/tools.ts:126-132`
109
+ **Description:** Sanctuary's `bridge_commit` tool schema declares `proposer_did` and `acceptor_did` as required string fields. Concordia's agent model uses opaque `agent_id` strings (e.g., `"agent-alice"`), not DIDs. There is no mapping mechanism, no DID generation in Concordia, and no way for the bridge payload builder to populate these fields. The bridge cannot work without manual DID assignment by the client, which is undocumented.
110
+
111
+ **Reproduction:** Inspect `bridge/tools.ts` lines 126-132 for required schema fields. Inspect `concordia/sanctuary_bridge.py` — no DID-related fields are generated.
112
+
113
+ ---
114
+
115
+ ### BUG-007 — Unicode Escaping Divergence Between stableStringify (TS) and canonical_json (Python)
116
+
117
+ **Severity:** Major
118
+ **File:** `server/src/bridge/bridge.ts:53-73`, `concordia/signing.py:70-80`
119
+ **Description:** TypeScript's `JSON.stringify()` escapes non-ASCII characters as `\uXXXX` sequences by default (e.g., `"café"` → `"caf\u00e9"`). Python's `json.dumps(..., ensure_ascii=False)` preserves UTF-8 codepoints as literal bytes (e.g., `"café"` → `"café"`). When negotiation terms contain any non-ASCII text (agent names like "François", product descriptions in non-Latin scripts, currency symbols like "€"), the two serializers produce different byte strings, producing different SHA-256 hashes. This causes `bridge_verify` to fail with `terms_hash_match: false` on any Unicode-containing outcome.
120
+
121
+ **Reproduction:**
122
+ 1. Negotiate terms containing `{"seller": "François"}` in Concordia.
123
+ 2. Concordia computes `canonical_json(terms)` → `{"seller":"François"}` (UTF-8 bytes).
124
+ 3. Forward to Sanctuary's `bridge_commit`. Sanctuary computes `stableStringify(terms)` → `{"seller":"Fran\u00e7ois"}`.
125
+ 4. SHA-256 hashes differ. Bridge verification reports `terms_hash_match: false`.
126
+
127
+ ---
128
+
129
+ ### BUG-008 — Concordia Bridge Reads Wrong Field Name for Transcript Hash
130
+
131
+ **Severity:** Major
132
+ **File:** `concordia/mcp_server.py:1808`
133
+ **Description:** The bridge commit function reads `last_msg.get("previous_hash")` to extract the transcript hash, but the message envelope (defined in `message.py:69`) uses the field name `prev_hash`. This causes `transcript_hash` to always be `None` in every bridge commitment payload.
134
+
135
+ **Reproduction:**
136
+ 1. Complete a Concordia negotiation to AGREED state.
137
+ 2. Call `concordia_sanctuary_bridge_commit`.
138
+ 3. Inspect the returned payload: `transcript_hash` is `None` (or missing).
139
+ 4. Expected: the SHA-256 hash from the last message's `prev_hash` field.
140
+
141
+ **Fix:** Change `last_msg.get("previous_hash")` to `last_msg.get("prev_hash")` at line 1808.
142
+
143
+ ---
144
+
145
+ ## SWEEP 4: AGENT BEHAVIOR
146
+
147
+ ---
148
+
149
+ ### BUG-009 — Concordia Closed-Loop Sybil Detection Has Contradictory Condition — Can Never Trigger
150
+
151
+ **Severity:** Major
152
+ **File:** `concordia/reputation/store.py:83-84`
153
+ **Description:** The closed-loop Sybil detection check contains logically contradictory conditions:
154
+ ```python
155
+ if (len(a_counterparties) > 2 and a_counterparties == {b}
156
+ and len(b_counterparties) > 2 and b_counterparties == {a}):
157
+ self.closed_loop = True
158
+ ```
159
+ If `a_counterparties == {b}` (a set containing only element `b`), then `len(a_counterparties)` is 1, which is not `> 2`. The `len > 2` and `== {single_element}` conditions are mutually exclusive. The `closed_loop` flag can never be set to `True`, meaning this entire Sybil detection signal is dead code.
160
+
161
+ **Reproduction:**
162
+ 1. Create 10 attestations showing agent A and agent B transacting exclusively with each other.
163
+ 2. Ingest all attestations into the reputation store.
164
+ 3. Query Sybil signals for any attestation: `closed_loop` is `False`.
165
+ 4. Expected: `closed_loop` should be `True` for agents that only transact with each other.
166
+
167
+ **Fix:** Change `len(a_counterparties) > 2` to `len(a_counterparties) <= 1` (or similar logic that correctly detects exclusive trading pairs).
168
+
169
+ ---
170
+
171
+ ### BUG-010 — Audit Log Persistence Failure Silently Swallowed
172
+
173
+ **Severity:** Major
174
+ **File:** `server/src/l2-operational/audit-log.ts:58-61`
175
+ **Description:** The `persistEntry()` method's error handler is `.catch(() => {})` — a completely empty catch that swallows all errors silently. If the audit log's encryption or storage write fails (disk full, permission error, encryption failure), the gate still returns its decision, and the audit entry is lost. No warning is emitted, no metric is incremented, and no fallback is attempted. The caller has no indication that audit logging failed.
176
+
177
+ **Reproduction:**
178
+ 1. Fill the filesystem hosting `~/.sanctuary/state/_audit/` to capacity.
179
+ 2. Perform any Sanctuary tool call that triggers an audit log entry.
180
+ 3. The tool call succeeds, but the audit entry is silently dropped.
181
+ 4. Call `monitor_audit_log` — the failed entry is absent with no gap indication.
182
+
183
+ **Sovereignty violation:** Contradicts Property #12: "All gate decisions (approve, deny, auto-allow) are appended to the encrypted audit log." Also violates §"WHAT THESE TOOLS MUST NEVER DO" #5: "Never silently degrade to a less-secure behavior on error."
184
+
185
+ ---
186
+
187
+ ## SWEEP 5: SOVEREIGNTY-SPECIFIC FUNCTIONAL TESTS
188
+
189
+ ---
190
+
191
+ ### BUG-011 — state_export() Does Not Filter Reserved Namespaces
192
+
193
+ **Severity:** Major
194
+ **File:** `server/src/l1-cognitive/state-store.ts:452-513`
195
+ **Description:** When `export()` is called without a specific namespace argument, it iterates all namespaces discovered from the `contentHashes` cache. Unlike the tool-level handlers in `tools.ts` (which call `isReservedNamespace()` before operations), the `export()` method in `StateStore` does not filter reserved namespaces like `_identities`, `_audit`, `_commitments`, `_reputation`, `_principal`, etc. If these namespaces have been accessed during the session (populating the cache), they will be included in the export bundle. This means an export can contain encrypted identity keys, audit logs, and internal system state.
196
+
197
+ **Reproduction:**
198
+ 1. Start Sanctuary server, create identities (populates `_identities` cache), write state, query audit log (populates `_audit` cache).
199
+ 2. Call `state_export` without specifying a namespace.
200
+ 3. Receive an export bundle containing `_identities`, `_audit`, and other reserved namespaces.
201
+ 4. Expected: export should only include user-created namespaces, matching the namespace filtering applied to other state operations.
202
+
203
+ **Note:** The exported data is still AES-256-GCM encrypted, so the raw private keys are not exposed in plaintext. But the export bundle structure reveals reserved namespace names and entry counts, and the bundle can be imported into another instance with the same master key.
204
+
205
+ ---
206
+
207
+ ### BUG-012 — Range Proof Accepts Degenerate Range (min == max), Producing Trivially Valid Proof
208
+
209
+ **Severity:** Major
210
+ **File:** `server/src/l3-disclosure/zk-proofs.ts:308-320`
211
+ **Description:** The `createRangeProof()` function computes `numBits = Math.ceil(Math.log2(range + 1))` where `range = max - min`. When `min == max`, `range = 0`, `numBits = Math.ceil(Math.log2(1)) = 0`. The bit decomposition loop runs zero times, producing empty `bit_commitments` and `bit_proofs` arrays. The verifier (`verifyRangeProof`) checks that the array lengths match (0 == 0), that the reconstructed sum equals the shift (0 == 0), and returns `valid: true`. The proof proves nothing — it doesn't verify that the prover knows the value or that the value equals `min`.
212
+
213
+ **Reproduction:**
214
+ 1. Create a Pedersen commitment to value 50 with a known blinding factor.
215
+ 2. Call `zk_range_prove` with `value=50, min=50, max=50`.
216
+ 3. Receive a proof with empty `bit_commitments: []` and `bit_proofs: []`.
217
+ 4. Call `zk_range_verify` — returns `valid: true`.
218
+ 5. Now call `zk_range_prove` with `value=99, min=50, max=50` — this should fail (99 is not in [50,50]) but the proof generation step at line 326 computes `shifted = value - min = 49`, then tries to decompose 49 into 0 bits, which silently produces an empty array. Verification still succeeds because both sides agree on 0 bits.
219
+
220
+ **Impact:** An agent can claim any value is in any single-point range and produce a "valid" proof.
221
+
222
+ ---
223
+
224
+ ### BUG-013 — Handshake Respond Allows Multiple Sessions for Same Challenge
225
+
226
+ **Severity:** Major
227
+ **File:** `server/src/handshake/tools.ts:108-141`
228
+ **Description:** The `handshake_respond` tool creates a new session for each invocation without checking whether a session already exists for the given challenge. An agent can call `handshake_respond` multiple times with the same challenge, creating multiple independent sessions with different nonces and session IDs. When the initiator completes the handshake with the first response, the additional sessions remain in an incomplete state indefinitely.
229
+
230
+ **Reproduction:**
231
+ 1. Call `handshake_initiate` — receive `challenge` and `session_id_A`.
232
+ 2. Call `handshake_respond` with `challenge` — receive `session_id_B` with `nonce_B`.
233
+ 3. Call `handshake_respond` with the same `challenge` again — receive `session_id_C` with `nonce_C`.
234
+ 4. Complete handshake with `session_id_A` and `nonce_B`.
235
+ 5. `session_id_C` is now orphaned — incomplete, never cleaned up, consuming state.
236
+
237
+ **Impact:** Accumulated orphan sessions consume encrypted storage under `_handshake` namespace with no cleanup mechanism.
238
+
239
+ ---
240
+
241
+ ### BUG-014 — proof_reveal Does Not Mark Commitment as Revealed
242
+
243
+ **Severity:** Minor
244
+ **File:** `server/src/l3-disclosure/tools.ts:109-126`
245
+ **Description:** The `proof_reveal` tool calls `verifyCommitment()` and returns the result, but never calls the `markRevealed()` method on the commitment store. A commitment can be "revealed" (verified) unlimited times. The `revealed` field in the commitment record permanently remains `false`. There is no way for a verifier to distinguish a commitment that has been revealed from one that has not.
246
+
247
+ **Reproduction:**
248
+ 1. Create a commitment via `proof_commitment` — receive `commitment_id`.
249
+ 2. Reveal via `proof_reveal` with correct value and blinding factor — returns `valid: true`.
250
+ 3. Query the commitment — `revealed` is still `false`.
251
+ 4. Reveal again with the same parameters — returns `valid: true` again. No warning or idempotency guard.
252
+
253
+ ---
254
+
255
+ ### BUG-015 — SHR Sovereignty Level Assessment Misclassifies 3-of-4-Layer Active as "Minimal"
256
+
257
+ **Severity:** Minor
258
+ **File:** `server/src/shr/verifier.ts:127-132` (approximate — see SHR assessment logic)
259
+ **Description:** The sovereignty level assessment function checks for the "full" case (all 4 layers active), then checks for "degraded" by only examining L4 status. If L1, L2, and L3 are all active but L4 is disabled (e.g., no reputation data yet), the function falls through to return "minimal" instead of "degraded". An agent with 3 of 4 sovereignty layers fully operational is classified at the same level as one with only L1.
260
+
261
+ **Reproduction:**
262
+ 1. Initialize Sanctuary with L1 (encryption), L2 (approval gate), L3 (commitments) all active.
263
+ 2. Do not create any reputation records (L4 inactive/disabled).
264
+ 3. Generate SHR via `monitor_health`.
265
+ 4. SHR reports sovereignty level: "minimal".
266
+ 5. Expected: "degraded" (3 of 4 layers active).
267
+
268
+ ---
269
+
270
+ ### BUG-016 — Attestation Generation Redundant Condition Masks Intent
271
+
272
+ **Severity:** Minor
273
+ **File:** `concordia/attestation.py:60`
274
+ **Description:** The guard condition `if not session.is_terminal and session.state != SessionState.EXPIRED` is logically redundant because `EXPIRED` is a terminal state. `is_terminal` returns `True` for AGREED, REJECTED, and EXPIRED. The `and session.state != SessionState.EXPIRED` clause can never contribute to the condition — if `session.state == EXPIRED`, then `session.is_terminal == True`, so `not session.is_terminal` is already `False`. This doesn't cause incorrect behavior, but it obscures the intended logic and could mislead maintainers into thinking EXPIRED sessions are explicitly excluded from attestation generation when they are not.
275
+
276
+ ---
277
+
278
+ ## SUMMARY
279
+
280
+ ### Findings by Severity
281
+
282
+ | Severity | Count | IDs |
283
+ |----------|-------|-----|
284
+ | Blocking | 3 | BUG-001, BUG-003, BUG-005/006 |
285
+ | Major | 9 | BUG-002, BUG-007, BUG-008, BUG-009, BUG-010, BUG-011, BUG-012, BUG-013 |
286
+ | Minor | 4 | BUG-004, BUG-014, BUG-015, BUG-016 |
287
+
288
+ ### Findings by Component
289
+
290
+ | Component | Count | IDs |
291
+ |-----------|-------|-----|
292
+ | Sanctuary StateStore (L1) | 3 | BUG-001, BUG-002, BUG-011 |
293
+ | Sanctuary Bridge | 2 | BUG-005, BUG-006 |
294
+ | Sanctuary L3 Disclosure | 2 | BUG-012, BUG-014 |
295
+ | Sanctuary Handshake | 1 | BUG-013 |
296
+ | Sanctuary Audit Log | 1 | BUG-010 |
297
+ | Sanctuary SHR | 1 | BUG-015 |
298
+ | Concordia MCP Server | 2 | BUG-003, BUG-008 |
299
+ | Concordia Reputation | 1 | BUG-009 |
300
+ | Concordia Relay | 1 | BUG-004 |
301
+ | Concordia Attestation | 1 | BUG-016 |
302
+ | Cross-Tool Bridge | 1 | BUG-007 |
303
+
304
+ ---
305
+
306
+ ## STRUCTURAL ASSESSMENT
307
+
308
+ The most structurally concerning pattern across both codebases is **cache-truth divergence**: critical operations depend on in-memory caches that are not guaranteed to reflect the actual state on disk. BUG-001 (export from empty cache) and BUG-002 (stale version cache after import skip) are both symptoms of the same root cause — the StateStore's in-memory maps (`contentHashes`, version cache) are treated as authoritative but are populated lazily and updated inconsistently. Any operation that needs a complete or accurate picture of persisted state (export, import conflict resolution, Merkle integrity) is vulnerable to cache-truth drift. The remediation planner should treat this as a design-level concern, not a collection of individual bugs — the fix is either to make the cache a proper write-through cache that is always consistent, or to have disk-touching operations always read from disk rather than cache.
309
+
310
+ The second systemic pattern is **bridge protocol incompleteness**. BUG-005, BUG-006, BUG-007, and BUG-008 collectively mean the Sanctuary-Concordia bridge does not function end-to-end. The payload shapes don't match, the required identity fields don't exist, the serialization produces different bytes, and a field name typo causes data loss. These are not edge cases — they prevent the bridge from working at all for any input. This suggests the bridge was designed at the schema level but never integration-tested with actual data flowing from Concordia through to Sanctuary.
311
+
312
+ The third pattern is **dead verification code** — functions that exist and are correct but are never called. The commitment `markRevealed()` (BUG-014), the Sybil `closed_loop` detector (BUG-009), and the range proof bit decomposition (BUG-012 when range=0) are all verification mechanisms that either cannot trigger or are not wired into the operational path. The system has more verification infrastructure than it actually uses, which creates a false sense of coverage.
@@ -0,0 +1,205 @@
1
+ # CLAUDE.md — Sanctuary & Concordia Security and Sovereignty Review Context
2
+
3
+ This file is a briefing for every Claude Code session that touches these codebases. Read it before making any changes.
4
+
5
+ ---
6
+
7
+ ## WHAT THESE TOOLS ARE
8
+
9
+ **Sanctuary** is a TypeScript MCP server (~10,400 lines, 40 tools) that gives AI agents four layers of cryptographic sovereignty — without requiring changes to the host agent harness. It ships as an npm package (`@sanctuary-framework/mcp-server`), Docker image, and Claude Code plugin. Version 0.3.0.
10
+
11
+ What it concretely does:
12
+
13
+ - **L1 — Cognitive Sovereignty.** Encrypts all agent-persistent state at rest using AES-256-GCM. Each namespace derives its own key via HKDF-SHA256 from a master key. The master key is derived from a user passphrase (Argon2id, m=64MB, t=3, p=4) or a one-time recovery key. Ed25519 keypairs provide self-custodied identity; private keys are always encrypted and never appear in MCP responses. State reads return Merkle proofs for integrity verification.
14
+ - **L2 — Operational Isolation.** A three-tier Principal Policy gate evaluates every tool call before execution. Tier 1 operations (export, import, key rotation) always require human approval through an out-of-band channel (stderr prompt, web dashboard, or signed webhook). Tier 2 operations trigger approval when a behavioral anomaly is detected (new namespace, unfamiliar counterparty, frequency spike). Tier 3 operations auto-allow with audit logging. The policy file is loaded at startup and is immutable to the agent. Denial responses never reveal policy rules.
15
+ - **L3 — Selective Disclosure.** SHA-256 commitments with random blinding factors, Pedersen commitments on Ristretto255, Schnorr proofs, and bit-decomposition range proofs — allowing an agent to prove claims about its data without revealing the underlying values.
16
+ - **L4 — Verifiable Reputation.** Signed attestations in EAS-compatible format, stored encrypted under L1. Sovereignty-gated tiers weight attestations from verified-sovereign agents higher. Escrow mechanism for trust bootstrapping. Reputation bundles are exportable and portable across instances.
17
+
18
+ Additional subsystems: Sovereignty Health Report (SHR) generation and verification; sovereignty handshake protocol (nonce challenge-response + SHR exchange between two agents); federation registry for MCP-to-MCP peer discovery; and the Concordia bridge module.
19
+
20
+ **Concordia** is a Python SDK and MCP server (~5,000 lines, 50+ tools exposed via FastMCP) implementing a structured multi-attribute negotiation protocol for autonomous agents. Version 0.1.0-draft.
21
+
22
+ What it concretely does:
23
+
24
+ - Defines a six-state session lifecycle: PROPOSED -> ACTIVE -> AGREED / REJECTED / EXPIRED -> DORMANT. State transitions are enforced by a strict transition table.
25
+ - Supports four offer types (Basic, Partial, Conditional, Bundle) and fourteen message types covering negotiation, information exchange, and resolution.
26
+ - Every message is Ed25519-signed over canonical JSON (sorted keys, deterministic serialization). Messages form a hash chain — each message includes the SHA-256 hash of its predecessor, creating a tamper-evident transcript.
27
+ - Generates reputation attestations from concluded sessions — behavioral records (offers made, concession magnitude, reasoning rate, responsiveness) without exposing deal terms.
28
+ - Includes a Want Registry (demand-side discovery with constraint matching), Agent Registry (capability advertising), message relay service, and graceful degradation for non-Concordia peers.
29
+ - All data in the reference implementation is in-memory (Python dicts). No persistent database is included. Production deployment requires swapping storage backends.
30
+
31
+ **How the two tools interact:**
32
+
33
+ The connection is the Concordia Bridge — present in both codebases but architecturally optional in each direction.
34
+
35
+ On the Sanctuary side (`server/src/bridge/`): three MCP tools — `bridge_commit`, `bridge_verify`, `bridge_attest`. When a Concordia negotiation reaches AGREED, the outcome is canonically serialized, committed to Sanctuary's L3 layer (SHA-256 + optional Pedersen commitment), signed by the committer's Ed25519 key, and optionally linked to L4 reputation via a signed attestation.
36
+
37
+ On the Concordia side (`concordia/sanctuary_bridge.py`): a payload builder that produces correctly-shaped requests for Sanctuary's `proof_commitment` and `reputation_record` tools. It does NOT directly call Sanctuary — it generates payloads that a client forwards. This keeps Concordia testable without a running Sanctuary server.
38
+
39
+ The bridge introduces no new cryptographic primitives. Everything delegates to existing L3/L4 infrastructure.
40
+
41
+ ---
42
+
43
+ ## WHAT THESE TOOLS MUST NEVER DO
44
+
45
+ These are hard constraints. Violation of any of these is a security defect.
46
+
47
+ 1. **Never transmit user data to an external endpoint without explicit, confirmed user intent.** Sanctuary's webhook channel sends HMAC-signed approval *requests* to a user-configured URL — but the payload is operation metadata, not state content. Actual state data (encrypted namespaces, private keys, reputation bundles) must never leave the local storage path except through an explicit export operation that has passed the Tier 1 approval gate.
48
+
49
+ 2. **Never persist agent-generated output that the user cannot inspect, export, or delete.** Every piece of persisted state in Sanctuary is in `~/.sanctuary/state/` and is accessible via `state_read`, `state_list`, `state_export`, or `state_delete`. The audit log is queryable. Concordia's in-memory state is ephemeral by design. If a persistent storage backend is added to Concordia, this constraint must carry forward.
50
+
51
+ 3. **Never execute an irreversible operation without a confirmation gate.** Key rotation, identity deletion, state export, state import, and reputation import are all Tier 1 operations — they require human approval before execution. Secure deletion (3-pass random overwrite) is irreversible and must remain gated.
52
+
53
+ 4. **Never assume trust across the Sanctuary-Concordia boundary.** Sanctuary's bridge accepts any object that matches the `ConcordiaOutcome` shape — it does not trust that the object came from a legitimate Concordia session. Verification is cryptographic: signature checks, commitment recomputation, terms hash matching. Concordia's bridge produces payloads but never directly modifies Sanctuary state. Neither tool should implicitly elevate the other's trust level.
54
+
55
+ 5. **Never silently degrade to a less-secure behavior on error.** If encryption fails, the operation must fail — not fall back to plaintext storage. If the approval channel is unreachable, the operation must be denied — not auto-approved. If signature verification fails, the message must be rejected — not accepted without verification. If Argon2id derivation fails, the server must not start with a weaker KDF.
56
+
57
+ 6. **Never expose private keys in any MCP response, log entry, error message, or diagnostic output.** Ed25519 private keys exist only encrypted at rest and decrypted transiently in memory for signing operations. This applies to both Sanctuary's identity keys and Concordia's agent key pairs.
58
+
59
+ 7. **Never allow the agent to read or modify the Principal Policy at runtime.** The policy file (`~/.sanctuary/principal-policy.yaml`) is loaded once at startup and frozen. The agent must not be able to infer policy rules from denial responses — denials return generic messages without revealing which tier or rule triggered them.
60
+
61
+ 8. **Never allow Concordia attestations to include raw deal terms.** Attestations record behavioral signals (offers_made, concession_magnitude, reasoning_provided) — not the actual prices, quantities, or terms of a negotiation. This is a privacy invariant.
62
+
63
+ ---
64
+
65
+ ## ARCHITECTURE IN ONE PAGE
66
+
67
+ ### Entry Points
68
+
69
+ | Tool | Entry Point | What It Starts |
70
+ |------|------------|----------------|
71
+ | Sanctuary | `server/src/cli.ts` | Parses flags, calls `createSanctuaryServer()` from `index.ts`, connects via StdioServerTransport |
72
+ | Concordia | `concordia/__main__.py` | Parses `--transport`, calls `mcp.run()` from `mcp_server.py` (FastMCP) |
73
+
74
+ ### Core Data Flow
75
+
76
+ **Sanctuary:**
77
+ ```
78
+ Tool call from agent harness
79
+ -> MCP SDK (router.ts: schema validation, size caps, enum checks)
80
+ -> ApprovalGate (gate.ts: Tier 1/2/3 evaluation)
81
+ -> Tool handler (L1/L2/L3/L4)
82
+ -> StateStore (state-store.ts: encrypt via AES-256-GCM, sign via Ed25519, compute Merkle root)
83
+ -> StorageBackend (filesystem.ts: write to ~/.sanctuary/state/{namespace}/{key}.enc)
84
+ -> AuditLog (audit-log.ts: append encrypted entry)
85
+ ```
86
+
87
+ **Concordia:**
88
+ ```
89
+ Tool call from agent harness
90
+ -> FastMCP dispatcher (mcp_server.py)
91
+ -> Session state machine (session.py: validate transition, append to hash-chain transcript)
92
+ -> Signing (signing.py: Ed25519 sign over canonical JSON)
93
+ -> In-memory stores (SessionStore, AttestationStore, WantRegistry, AgentRegistry)
94
+ -> [Optional] Sanctuary bridge payload generation (sanctuary_bridge.py)
95
+ ```
96
+
97
+ ### Where the Two Tools Connect
98
+
99
+ ```
100
+ Concordia session reaches AGREED
101
+ -> concordia/sanctuary_bridge.py builds commitment payload
102
+ -> Client forwards payload to Sanctuary MCP server
103
+ -> sanctuary/bridge_commit: canonicalize outcome, create L3 commitment, Ed25519 sign
104
+ -> sanctuary/bridge_verify: recompute hash, verify signature (later, on demand)
105
+ -> sanctuary/bridge_attest: create L4 attestation linking outcome to reputation
106
+ ```
107
+
108
+ The bridge is a payload hand-off, not a direct call. The two servers run as separate processes. There is no shared memory, no shared database, no implicit RPC channel between them.
109
+
110
+ ### Auth and Trust Model
111
+
112
+ **Sanctuary:** Master key (Argon2id from passphrase or random recovery key) -> HKDF per namespace -> AES-256-GCM encryption. Ed25519 for identity, signing, and non-repudiation. Principal Policy for human-in-the-loop approval gating. Sovereignty handshake for mutual agent verification (nonce challenge-response + SHR).
113
+
114
+ **Concordia:** Ed25519 key pairs per agent. Messages signed over canonical JSON. Hash-chain transcript integrity. Sybil detection on reputation attestations (self-dealing, suspiciously fast sessions, symmetric concessions, closed loops).
115
+
116
+ **Cross-boundary:** Sanctuary does not trust Concordia's assertions — it verifies cryptographically. Concordia does not call Sanctuary directly — it produces payloads. Trust is established through signature verification and commitment recomputation, not through any shared secret or implicit channel.
117
+
118
+ ### Key Third-Party Dependencies
119
+
120
+ **Sanctuary (TypeScript):**
121
+ - `@noble/ciphers` — AES-256-GCM (audited, zero transitive deps)
122
+ - `@noble/curves` — Ed25519, Ristretto255 for Pedersen commitments (audited)
123
+ - `@noble/hashes` — SHA-256, HMAC, HKDF (audited)
124
+ - `hash-wasm` — Argon2id key derivation (WASM-based)
125
+ - `@modelcontextprotocol/sdk` — MCP protocol implementation
126
+
127
+ **Concordia (Python):**
128
+ - `cryptography` (>=42.0) — Ed25519 signing and verification
129
+ - `jsonschema` (>=4.20) — Message and attestation schema validation
130
+ - `mcp` (>=1.0) — FastMCP server SDK
131
+
132
+ No blockchain libraries. No external API calls at runtime. No telemetry.
133
+
134
+ ---
135
+
136
+ ## SOVEREIGNTY PROPERTIES THIS SYSTEM IS DESIGNED TO GUARANTEE
137
+
138
+ These are testable assertions. Each should be verifiable by inspection or automated test.
139
+
140
+ **Data sovereignty:**
141
+
142
+ 1. "No plaintext user state is ever written to disk." All values in `~/.sanctuary/state/` are AES-256-GCM ciphertext with unique IVs. *(Tested: `test/security/no-plaintext-leak.test.ts`)*
143
+ 2. "A user can retrieve a complete export of all data the system holds about them" via `state_export` (requires Tier 1 approval). *(Tested: `test/integration/reputation-portability.test.ts`)*
144
+ 3. "A user can delete any piece of stored state, including secure 3-pass overwrite" via `state_delete` with `secure_delete: true`. *(Tested: unit tests for storage backend)*
145
+ 4. "Every encryption operation uses a unique, cryptographically random IV (12 bytes)." *(Tested: `test/security/iv-uniqueness.test.ts`)*
146
+ 5. "File permissions on the storage directory are 0o700 (owner only) and on individual files are 0o600 (owner read/write only)."
147
+
148
+ **Identity sovereignty:**
149
+
150
+ 6. "Private keys never appear in any MCP tool response." *(Tested: `test/security/key-never-in-response.test.ts`)*
151
+ 7. "Identity key rotation produces a signed chain proving authorization — the new key is signed by the old key."
152
+ 8. "A user can export their full identity (public key, DID, attestations) as a portable bundle and import it to another Sanctuary instance."
153
+
154
+ **Operational sovereignty:**
155
+
156
+ 9. "Every tool call is evaluated by the ApprovalGate before execution. There is no code path that bypasses the gate." *(Verified by router.ts wrapping all handlers)*
157
+ 10. "Tier 1 operations (export, import, rotate, delete) require out-of-band human approval. If the approval channel is unreachable, the operation is denied — never auto-approved."
158
+ 11. "The agent cannot read, modify, or infer the contents of the Principal Policy at runtime. Denial messages are generic."
159
+ 12. "All gate decisions (approve, deny, auto-allow) are appended to the encrypted audit log."
160
+
161
+ **Negotiation integrity (Concordia):**
162
+
163
+ 13. "Every Concordia message is Ed25519-signed and hash-chained. Tampering with any message breaks the chain." *(Tested: signing and message hash chain tests)*
164
+ 14. "Session state transitions are enforced by a strict transition table. Invalid transitions raise `InvalidTransitionError`."
165
+ 15. "Reputation attestations contain behavioral signals only — never raw deal terms."
166
+
167
+ **Bridge integrity:**
168
+
169
+ 16. "Bridge commitments use canonical serialization (sorted keys, deterministic JSON) so identical outcomes always produce identical commitment hashes."
170
+ 17. "Non-finite numbers (NaN, Infinity) in outcome terms are rejected at canonicalization time to prevent commitment ambiguity."
171
+ 18. "Bridge verification recomputes the commitment from the revealed outcome and compares — it does not trust the stored hash alone."
172
+
173
+ **Intended but unverified:**
174
+
175
+ 19. "Hardware key protection (FIDO2/WebAuthn) for master key derivation." *(Planned for v0.3.0, config option exists, implementation not yet present.)*
176
+ 20. "TEE-backed execution environment attestation." *(Config accepts `tee` as environment type, but the code uses self-reported attestation only — no TEE integration exists.)*
177
+ 21. "Groth16/PLONK zero-knowledge proof systems for L3." *(Config accepts these as `proof_system` options, but only `commitment-only` is implemented. ZK proofs use Pedersen/Schnorr, not SNARKs.)*
178
+
179
+ ---
180
+
181
+ ## KNOWN COMPLEXITY AND RISK AREAS
182
+
183
+ **Slow down and inspect carefully in these areas:**
184
+
185
+ 1. **Canonical serialization (both codebases).** Sanctuary's `stableStringify` in `bridge/bridge.ts` and Concordia's `canonical_json` in `signing.py` must produce byte-identical output for the same input. They are implemented independently in different languages. Any divergence breaks bridge commitment verification. This is the highest-risk interop surface. Edge cases to watch: Unicode normalization, floating-point representation, key ordering in nested objects, handling of `null`/`undefined`/`None`.
186
+
187
+ 2. **Principal Policy baseline tracker.** The `BaselineTracker` in `principal-policy/baseline.ts` builds a behavioral model over time. On first session there is no baseline, so all Tier 2 operations require approval. As the baseline grows, the anomaly detection thresholds shift. The interaction between baseline state, encrypted baseline persistence, and the approval gate evaluation logic is the most stateful part of Sanctuary. Bugs here could either over-permit (missing anomalies) or over-deny (false positives blocking legitimate operations).
188
+
189
+ 3. **Approval channel failure modes.** Three channels exist: stderr (auto-deny on timeout — safe default), dashboard (SSE-based web UI), and webhook (HMAC-signed HTTP POST). The webhook channel introduces an external network dependency. If the webhook endpoint is slow, unreachable, or returns ambiguous responses, the gate must deny — but the timeout and retry logic is where subtle bugs live.
190
+
191
+ 4. **Concordia's in-memory state model.** The reference implementation stores everything in Python dicts with size caps (10K sessions, 100K attestations). There is no persistence, no crash recovery, no transaction isolation. Any production deployment must swap these stores, and the swap surface is wide — `SessionStore`, `AttestationStore`, `WantRegistry`, `AgentRegistry`, `NegotiationRelay` all hold independent in-memory state.
192
+
193
+ 5. **Sybil detection heuristics.** Concordia's `SybilSignals` in `reputation/store.py` flags self-dealing, suspiciously fast sessions (<5 seconds), symmetric concessions, and closed-loop trading. These are heuristics, not proofs. A sophisticated attacker can craft sessions that evade all four signals. The scorer applies penalties but does not reject flagged attestations outright — the scoring weights are tunable and their security properties are not formally analyzed.
194
+
195
+ 6. **Merkle tree and version monotonicity in StateStore.** Sanctuary computes Merkle roots over namespace entries and tracks monotonic version numbers to detect rollback. The correctness of rollback detection depends on the version counter never being reset, which depends on the encrypted metadata file not being replaced with a stale copy. An attacker with filesystem access could potentially roll back the version metadata file — the defense relies on the master key protecting integrity, but the threat model for filesystem-level adversaries is not fully specified.
196
+
197
+ 7. **Bridge attestation trust bootstrapping.** When a Concordia outcome is bridged to Sanctuary L4, the attestation's weight depends on the counterparty's sovereignty tier (determined by handshake). If the counterparty has not completed a sovereignty handshake, the attestation is tagged `unverified` — but the code still stores it. The boundary between "stored but unverified" and "stored and trusted" attestations could be confusing to consumers of the reputation API.
198
+
199
+ 8. **ZK proof system scope.** The current L3 implementation provides Pedersen commitments, Schnorr proofs, and bit-decomposition range proofs — genuine cryptographic primitives, but not SNARKs. The config schema advertises `groth16` and `plonk` as options, which could mislead reviewers into thinking those systems are available. They are not. Only `commitment-only` is functional.
200
+
201
+ ---
202
+
203
+ ## REVIEW CONTEXT
204
+
205
+ This codebase is currently under a structured security and sovereignty review. Review artifacts (REVIEW_MAP.md, SECURITY_AUDIT.md, BUG_REPORT.md, REMEDIATION_PLAN.md, SPRINT_CONTRACT.md, SPRINT_RESULT.md, SPRINT_EVAL.md) will appear in the working directory as the review progresses. During the review period: do not refactor code outside of explicit sprint sessions, do not add new dependencies without noting them in REVIEW_MAP.md, and do not mark any finding as resolved without a corresponding SPRINT_EVAL.md showing PASS status.
@@ -0,0 +1 @@
1
+ concordiaprotocol.dev