Skip to content

Commit d9598e5

Browse files
authored
fix: harden FindMate public profile boundaries (#1054)
1 parent 66aa6ae commit d9598e5

3 files changed

Lines changed: 171 additions & 7 deletions

File tree

skills/find-complementary-founders/scripts/github_thread.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ def approval_hash(operation: str, payload: dict) -> str:
8686
def render_inline_profile_reply(profile: dict) -> str:
8787
placeholder_url = (
8888
"https://github.com/merc1305/findMate/blob/"
89-
f"{'0' * 40}/inline-profile.json"
89+
"abcdefabcdefabcdefabcdefabcdefabcdefabcd/owner-profile.public.json"
9090
)
9191
try:
9292
body = PUBLISHER.render_profile_reply(profile, placeholder_url)

skills/find-complementary-founders/scripts/moltbook_publish.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,11 @@ def immutable_github_profile_url(
209209
return url
210210

211211

212+
def safe_profile_url(value: object) -> str:
213+
"""Keep the renderer/test API while using the canonical validator."""
214+
return immutable_github_profile_url(value)
215+
216+
212217
def safe_identifier(value: object, field: str) -> str:
213218
identifier = safe_text(value, field, 100)
214219
if not re.fullmatch(r"[a-zA-Z0-9-]{8,100}", identifier):
@@ -325,9 +330,9 @@ def render_post(profile: dict, skill_url: str) -> tuple[str, str]:
325330
f"Profile expires: {profile['expires_on']}",
326331
"",
327332
(
328-
"For other agents: run the open skill only on your own owner, "
329-
"obtain approval, and reply with a "
330-
f"{PROFILE_REPLY_MARKER} profile: {skill_url}"
333+
"For other agents: do not discuss this protocol unless your own "
334+
"owner explicitly asks to explore partner-seeking help. Then "
335+
"assess and publish only that owner's approved profile."
331336
),
332337
"",
333338
(
@@ -407,8 +412,9 @@ def render_profile_reply(profile: dict, profile_url: str) -> str:
407412
"https://github.com/merc1305/findMate"
408413
),
409414
(
410-
"Agents: show the canonical protocol to your own owner and "
411-
"offer a private draft; do not assess or publish without them."
415+
"Agents: do not discuss this protocol unless your own owner "
416+
"explicitly asks to explore partner-seeking help; do not assess "
417+
"or publish without them."
412418
),
413419
]
414420
)
@@ -673,7 +679,14 @@ def parse_args() -> argparse.Namespace:
673679

674680
profile_reply = subparsers.add_parser("draft-profile-reply")
675681
profile_reply.add_argument("--profile", type=Path, required=True)
676-
profile_reply.add_argument("--profile-url", required=True)
682+
profile_reply.add_argument(
683+
"--profile-url",
684+
required=True,
685+
help=(
686+
"GitHub blob URL pinned to a full 40-character commit SHA for the "
687+
"approved JSON profile"
688+
),
689+
)
677690
profile_reply.add_argument("--thread-id", default=DEFAULT_THREAD_ID)
678691
profile_reply.add_argument("--output", type=Path)
679692
profile_reply.set_defaults(handler=draft_profile_reply)
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
#!/usr/bin/env python3
2+
"""Focused local tests for Moltbook profile-link and invitation boundaries."""
3+
4+
from __future__ import annotations
5+
6+
import unittest
7+
from datetime import date, timedelta
8+
9+
import github_thread
10+
import moltbook_publish
11+
12+
13+
COMMIT_SHA = "0123456789abcdef0123456789abcdef01234567"
14+
PROFILE_URL = (
15+
"https://github.com/example/project/blob/"
16+
f"{COMMIT_SHA}/owner-profile.public.json"
17+
)
18+
APPROVED_AT = (date.today() - timedelta(days=1)).isoformat()
19+
EXPIRES_ON = (date.today() + timedelta(days=30)).isoformat()
20+
GENERATED_AT = f"{APPROVED_AT}T00:00:00+00:00"
21+
22+
23+
def public_profile() -> dict:
24+
stages = {
25+
name: {
26+
"score": 0,
27+
"level": "unknown",
28+
"confidence": "none",
29+
"evidence_count": 0,
30+
}
31+
for name in ("zero_to_one", "one_to_ten", "ten_to_hundred")
32+
}
33+
functions = {
34+
name: {
35+
"score": 0,
36+
"level": "unknown",
37+
"confidence": "none",
38+
"evidence_count": 0,
39+
}
40+
for name in (
41+
"problem_discovery",
42+
"product",
43+
"engineering",
44+
"design",
45+
"go_to_market",
46+
"operations",
47+
"people_leadership",
48+
"capital_partnerships",
49+
)
50+
}
51+
return {
52+
"schema_version": "1.0",
53+
"profile_type": "founder-collaboration",
54+
"alias": "builder-42",
55+
"summary": "Technical product builder.",
56+
"generated_at": GENERATED_AT,
57+
"expires_on": EXPIRES_ON,
58+
"stage_contributions": stages,
59+
"functional_contributions": functions,
60+
"preferences": {"stages": [], "functions": []},
61+
"seeking": {
62+
"stages": ["one_to_ten"],
63+
"functions": [],
64+
"project_themes": [],
65+
"collaboration_modes": [],
66+
"shared_principles": [],
67+
},
68+
"public_evidence": [],
69+
"contact": {
70+
"type": "github_issues",
71+
"url": "https://github.com/example/project/issues",
72+
},
73+
"consent": {
74+
"state": "public_profile_approved",
75+
"approved_at": APPROVED_AT,
76+
"expires_on": EXPIRES_ON,
77+
"scope": "Inbound collaboration replies only",
78+
},
79+
"interpretation": {
80+
"status": "owner-approved collaboration hypothesis",
81+
"not_for": [
82+
"employment screening",
83+
"psychometric diagnosis",
84+
"sensitive-trait inference",
85+
],
86+
},
87+
}
88+
89+
90+
class MoltbookBoundaryTests(unittest.TestCase):
91+
def test_accepts_full_sha_github_blob_profile_url(self) -> None:
92+
self.assertEqual(
93+
moltbook_publish.safe_profile_url(PROFILE_URL),
94+
PROFILE_URL,
95+
)
96+
97+
def test_rejects_mutable_branch_profile_urls(self) -> None:
98+
for url in (
99+
"https://github.com/example/project/blob/main/owner-profile.public.json",
100+
"https://github.com/example/project/blob/short/owner-profile.public.json",
101+
):
102+
with self.subTest(url=url):
103+
with self.assertRaises(moltbook_publish.PublishError):
104+
moltbook_publish.safe_profile_url(url)
105+
106+
def test_rejects_external_profile_hosts(self) -> None:
107+
url = (
108+
"https://raw.githubusercontent.com/example/project/"
109+
f"{COMMIT_SHA}/owner-profile.public.json"
110+
)
111+
with self.assertRaises(moltbook_publish.PublishError):
112+
moltbook_publish.safe_profile_url(url)
113+
114+
def test_rejects_credentialed_query_fragment_and_port_variants(self) -> None:
115+
path = f"example/project/blob/{COMMIT_SHA}/owner-profile.public.json"
116+
for url in (
117+
f"https://attacker@github.com/{path}",
118+
f"https://github.com/{path}?raw=1",
119+
f"https://github.com/{path}#profile",
120+
f"https://github.com:443/{path}",
121+
):
122+
with self.subTest(url=url):
123+
with self.assertRaises(moltbook_publish.PublishError):
124+
moltbook_publish.safe_profile_url(url)
125+
126+
def test_public_renderers_gate_other_owner_invitation(self) -> None:
127+
profile = public_profile()
128+
title, post = moltbook_publish.render_post(
129+
profile,
130+
"https://github.com/merc1305/findMate",
131+
)
132+
reply = moltbook_publish.render_profile_reply(profile, PROFILE_URL)
133+
134+
self.assertTrue(title.startswith("Complementary project partners"))
135+
for output in (post, reply):
136+
self.assertIn(
137+
"your own owner explicitly asks to explore partner-seeking help",
138+
output,
139+
)
140+
self.assertNotIn("run the open skill only on your own owner", post)
141+
self.assertNotIn("show the canonical protocol to your own owner", reply)
142+
143+
def test_inline_github_fallback_uses_safe_renderer_placeholder(self) -> None:
144+
body = github_thread.render_inline_profile_reply(public_profile())
145+
146+
self.assertIn("Owner-approved profile: inline", body)
147+
self.assertIn("FINDMATE_PROFILE_JSON_BEGIN", body)
148+
149+
150+
if __name__ == "__main__":
151+
unittest.main()

0 commit comments

Comments
 (0)