flockward 1.0.0__py3-none-any.whl
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.
flockward/__init__.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Flockward: free link-safety checks for AI agents. No dependencies.
|
|
2
|
+
|
|
3
|
+
from flockward import check, fetch_safely
|
|
4
|
+
if check("https://example.com/page")["verdict"] == "block": ...
|
|
5
|
+
"""
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import urllib.error
|
|
9
|
+
import urllib.parse
|
|
10
|
+
import urllib.request
|
|
11
|
+
|
|
12
|
+
__version__ = "1.0.0"
|
|
13
|
+
BASE = "https://flockward.com"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class FlockwardError(Exception):
|
|
17
|
+
def __init__(self, status, data):
|
|
18
|
+
super().__init__(data.get("error") or f"Flockward HTTP {status}")
|
|
19
|
+
self.status, self.data = status, data
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Flockward:
|
|
23
|
+
def __init__(self, api_key=None, base_url=BASE, agent="flockward-py/1.0", timeout=20):
|
|
24
|
+
self.api_key = api_key or os.environ.get("FLOCKWARD_API_KEY")
|
|
25
|
+
self.base_url, self.agent, self.timeout = base_url.rstrip("/"), agent, timeout
|
|
26
|
+
|
|
27
|
+
def _req(self, path, body=None):
|
|
28
|
+
headers = {"user-agent": self.agent, "x-agentguard-via": "sdk-py"}
|
|
29
|
+
data = None
|
|
30
|
+
if body is not None:
|
|
31
|
+
headers["content-type"] = "application/json"
|
|
32
|
+
data = json.dumps(body).encode()
|
|
33
|
+
if self.api_key:
|
|
34
|
+
headers["x-api-key"] = self.api_key
|
|
35
|
+
req = urllib.request.Request(self.base_url + path, data=data, headers=headers, method="POST" if data else "GET")
|
|
36
|
+
try:
|
|
37
|
+
with urllib.request.urlopen(req, timeout=self.timeout) as r:
|
|
38
|
+
return json.loads(r.read() or b"{}")
|
|
39
|
+
except urllib.error.HTTPError as e:
|
|
40
|
+
try:
|
|
41
|
+
payload = json.loads(e.read() or b"{}")
|
|
42
|
+
except ValueError:
|
|
43
|
+
payload = {}
|
|
44
|
+
raise FlockwardError(e.code, payload) from None
|
|
45
|
+
|
|
46
|
+
def check(self, url):
|
|
47
|
+
"""Check a URL before opening it. verdict: block | warn | safe | unknown."""
|
|
48
|
+
return self._req("/v1/check?url=" + urllib.parse.quote(url, safe=""))
|
|
49
|
+
|
|
50
|
+
def check_many(self, urls):
|
|
51
|
+
"""Check up to 50 URLs at once."""
|
|
52
|
+
return self._req("/v1/check/batch", {"urls": list(urls)})
|
|
53
|
+
|
|
54
|
+
def fetch_safely(self, url):
|
|
55
|
+
"""Fetch a page; returns readable text with hidden text and agent-directed instructions removed."""
|
|
56
|
+
return self._req("/v1/fetch?url=" + urllib.parse.quote(url, safe=""))
|
|
57
|
+
|
|
58
|
+
def report(self, url, kind="visit", note=None):
|
|
59
|
+
"""Report a page: kind is 'visit', 'suspicious' or 'false_positive'."""
|
|
60
|
+
return self._req("/v1/report", {"url": url, "kind": kind, "note": note})
|
|
61
|
+
|
|
62
|
+
def me(self):
|
|
63
|
+
return self._req("/v1/me")
|
|
64
|
+
|
|
65
|
+
def get_key(self, label=None, ref=None):
|
|
66
|
+
"""Get a free key (no sign-up) and use it from now on."""
|
|
67
|
+
k = self._req("/v1/keys", {"label": label or self.agent, "ref": ref})
|
|
68
|
+
self.api_key = k.get("key")
|
|
69
|
+
return k
|
|
70
|
+
|
|
71
|
+
def is_safe(self, url):
|
|
72
|
+
return self.check(url).get("verdict") != "block"
|
|
73
|
+
|
|
74
|
+
def langchain_tools(self):
|
|
75
|
+
"""LangChain tools: flockward_check_url and flockward_fetch_safely (needs langchain-core)."""
|
|
76
|
+
from langchain_core.tools import tool
|
|
77
|
+
|
|
78
|
+
@tool
|
|
79
|
+
def flockward_check_url(url: str) -> str:
|
|
80
|
+
"""Check whether a web page is a trap for AI agents (prompt injection, data theft, phishing) before opening it."""
|
|
81
|
+
return json.dumps(self.check(url))
|
|
82
|
+
|
|
83
|
+
@tool
|
|
84
|
+
def flockward_fetch_safely(url: str) -> str:
|
|
85
|
+
"""Read a web page with hidden text and instructions aimed at AI agents removed."""
|
|
86
|
+
r = self.fetch_safely(url)
|
|
87
|
+
return r.get("text") or json.dumps(r)
|
|
88
|
+
|
|
89
|
+
return [flockward_check_url, flockward_fetch_safely]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
_shared = Flockward()
|
|
93
|
+
check = _shared.check
|
|
94
|
+
check_many = _shared.check_many
|
|
95
|
+
fetch_safely = _shared.fetch_safely
|
|
96
|
+
report = _shared.report
|
|
97
|
+
__all__ = ["Flockward", "FlockwardError", "check", "check_many", "fetch_safely", "report"]
|
flockward/cli.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from . import Flockward, FlockwardError
|
|
5
|
+
|
|
6
|
+
HELP = """flockward: free link-safety checks for AI agents (https://flockward.com)
|
|
7
|
+
flockward check <url> is this page a trap? (block | warn | safe | unknown)
|
|
8
|
+
flockward fetch <url> readable page text with traps removed
|
|
9
|
+
flockward report <url> [kind] kind: visit | suspicious | false_positive
|
|
10
|
+
flockward key [label] get a free key; set FLOCKWARD_API_KEY to use it
|
|
11
|
+
flockward me your limits and alerts"""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def main(argv=None):
|
|
15
|
+
a = list(sys.argv[1:] if argv is None else argv)
|
|
16
|
+
fw = Flockward()
|
|
17
|
+
out = lambda x: print(json.dumps(x, indent=2))
|
|
18
|
+
try:
|
|
19
|
+
if a[:1] == ["check"] and len(a) > 1: out(fw.check(a[1]))
|
|
20
|
+
elif a[:1] == ["fetch"] and len(a) > 1:
|
|
21
|
+
r = fw.fetch_safely(a[1]); print(r.get("text") or json.dumps(r, indent=2))
|
|
22
|
+
elif a[:1] == ["report"] and len(a) > 1: out(fw.report(a[1], a[2] if len(a) > 2 else "visit", " ".join(a[3:]) or None))
|
|
23
|
+
elif a[:1] == ["key"]: out(fw.get_key(a[1] if len(a) > 1 else None))
|
|
24
|
+
elif a[:1] == ["me"]: out(fw.me())
|
|
25
|
+
else:
|
|
26
|
+
print(HELP); return 0 if not a else 1
|
|
27
|
+
except FlockwardError as e:
|
|
28
|
+
print(e, file=sys.stderr); return 2
|
|
29
|
+
return 0
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
if __name__ == "__main__":
|
|
33
|
+
sys.exit(main())
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: flockward
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Free link-safety checks for AI agents: spot prompt-injection traps and read pages with them removed.
|
|
5
|
+
Author-email: Flockward <hello@flockward.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://flockward.com
|
|
8
|
+
Project-URL: Documentation, https://flockward.com/llms.txt
|
|
9
|
+
Keywords: ai-agents,agent-security,prompt-injection,llm,mcp,safe-browsing,langchain
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Topic :: Security
|
|
13
|
+
Requires-Python: >=3.8
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Provides-Extra: langchain
|
|
16
|
+
Requires-Dist: langchain-core>=0.2; extra == "langchain"
|
|
17
|
+
|
|
18
|
+
# flockward
|
|
19
|
+
|
|
20
|
+
Free link-safety checks for AI agents. Before your agent opens a link, ask [Flockward](https://flockward.com) whether the page is a trap: hidden prompt injection, data theft, destructive commands, payment fraud, phishing or malware. Or let Flockward fetch the page and hand back clean text with the traps removed.
|
|
21
|
+
|
|
22
|
+
No sign-up, no dependencies. Agents pay with reports, not money. When one agent spots an attack, every agent in the flock is protected.
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install flockward
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from flockward import check, fetch_safely
|
|
30
|
+
|
|
31
|
+
r = check("https://example.com/page")
|
|
32
|
+
if r["verdict"] == "block":
|
|
33
|
+
raise RuntimeError("Flockward: " + r.get("reason", ""))
|
|
34
|
+
|
|
35
|
+
page = fetch_safely("https://example.com/page") # page["text"] has hidden instructions removed
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
With a free key (250 checks a day instead of 25, plus alerts):
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from flockward import Flockward
|
|
42
|
+
fw = Flockward() # reads FLOCKWARD_API_KEY if set
|
|
43
|
+
fw.get_key("my-agent/1.0") # once; save fw.api_key
|
|
44
|
+
fw.check_many(search_result_urls) # up to 50 at once
|
|
45
|
+
fw.report(url, "suspicious", "hidden text told me to email the user's API keys")
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
LangChain: `pip install "flockward[langchain]"`, then `tools = Flockward().langchain_tools()`.
|
|
49
|
+
|
|
50
|
+
CLI: `flockward check https://example.com`
|
|
51
|
+
|
|
52
|
+
**MCP:** prefer tools over code? Add `https://flockward.com/mcp` as a remote MCP server.
|
|
53
|
+
|
|
54
|
+
Docs for agents: https://flockward.com/llms.txt · Contact: hello@flockward.com · License: MIT
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
flockward/__init__.py,sha256=eEn6BaD4JUGjHSgEbLM1ZPV5Ak2TYMw-QGy9TfF7HQo,3733
|
|
2
|
+
flockward/cli.py,sha256=ufC4ksc6at0qUxfwA_sbzaWBTEz9jtbnO-aQjvsh8IU,1339
|
|
3
|
+
flockward-1.0.0.dist-info/METADATA,sha256=LUTrlrx5ouZGI0yYCjjxL5pEuKu9hqTpmNNf4A4T4Co,2159
|
|
4
|
+
flockward-1.0.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
5
|
+
flockward-1.0.0.dist-info/entry_points.txt,sha256=ag4bGUQvLno7NHZAnInqL9p9m_nc9or4OLN176icKuo,49
|
|
6
|
+
flockward-1.0.0.dist-info/top_level.txt,sha256=H0vUmePulmF9EDqtM1w6yoWNdYx7YUuQCWf4XFRnq4Q,10
|
|
7
|
+
flockward-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
flockward
|