valleydam 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.
@@ -0,0 +1,185 @@
1
+ Metadata-Version: 2.4
2
+ Name: valleydam
3
+ Version: 0.1.0
4
+ Summary: A DNS-based cryptographic identity verification protocol for AI Agents.
5
+ Home-page: https://github.com/supra-nlpn/valley-dam
6
+ Author: Supra N.
7
+ Project-URL: Bug Tracker, https://github.com/supra-nlpn/valley-dam/issues
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Topic :: Security
12
+ Classifier: Topic :: Internet :: WWW/HTTP
13
+ Requires-Python: >=3.7
14
+ Description-Content-Type: text/markdown
15
+ Requires-Dist: requests>=2.25.0
16
+ Requires-Dist: cryptography>=3.4.0
17
+ Requires-Dist: dnspython>=2.1.0
18
+ Dynamic: author
19
+ Dynamic: classifier
20
+ Dynamic: description
21
+ Dynamic: description-content-type
22
+ Dynamic: home-page
23
+ Dynamic: project-url
24
+ Dynamic: requires-dist
25
+ Dynamic: requires-python
26
+ Dynamic: summary
27
+
28
+ # ⛰️ ValleyDam
29
+
30
+ ValleyDam is a lightweight, open protocol for verifying the identity of AI agents and web scrapers using **DNS-backed cryptographic proof**.
31
+
32
+ It enables a website to verify that a request *actually* came from `bot.openai.com` (or your startup’s domain) **without** API keys, IP allowlists, or complex authentication handshakes.
33
+
34
+ ---
35
+
36
+ ## The Problem
37
+
38
+ Today, websites have no reliable way to identify automated clients.
39
+
40
+ - **User-Agent strings are lies**
41
+ Anyone can send `User-Agent: Googlebot`.
42
+
43
+ - **IP blocking is messy**
44
+ Legitimate bots often run on shared cloud infrastructure (AWS, GCP).
45
+
46
+ - **API keys don’t scale**
47
+ You can’t safely issue and manage API keys for every website on the internet.
48
+
49
+ ---
50
+
51
+ ## The Solution
52
+
53
+ ValleyDam uses **Ed25519 digital signatures** anchored in **DNS TXT records** to create a verifiable, spoof-resistant identity for bots.
54
+
55
+ ### How it works
56
+
57
+ 1. **The bot signs each request** using a private Ed25519 key.
58
+ 2. **The server retrieves the public key** from the bot’s DNS record
59
+ (e.g. `_agent.yourwebsite.com`).
60
+ 3. **The signature is verified**. If it matches, the bot’s identity is cryptographically proven.
61
+
62
+ No central authority. No shared secrets. No API keys.
63
+
64
+ ---
65
+
66
+ ## 📦 Installation
67
+
68
+ ```bash
69
+ pip install valleydam
70
+ ```
71
+
72
+ ---
73
+
74
+ ## 🚀 Usage
75
+
76
+ ### For Web Scrapper or Agent Developers (The Client)
77
+
78
+ If you are building a scraper or AI agent, use `ValleyDamSession` to automatically sign outgoing HTTP requests.
79
+
80
+ ---
81
+
82
+ #### 1. Generate Your Identity
83
+
84
+ Run the CLI to generate a private key and receive your DNS TXT record value:
85
+
86
+ ```bash
87
+ valleydam-gen
88
+ ```
89
+
90
+ Follow the printed instructions to add the TXT record to your domain’s DNS.
91
+
92
+ ---
93
+
94
+ #### 2. Use ValleyDam in Your Code
95
+
96
+ ValleyDam behaves just like the standard Python `requests` library.
97
+
98
+ ```python
99
+ from valleydam import ValleyDamSession
100
+
101
+ # Initialize your authenticated session
102
+ agent = ValleyDamSession(
103
+ domain="yourwebsite.com", # Your verified domain
104
+ private_key_path="yourwebsite_com_private.pem" # Generated in step 1
105
+ )
106
+
107
+ # Make requests as normal — they are now cryptographically signed
108
+ response = agent.get("https://protected-website.com/api/data")
109
+
110
+ print(response.text)
111
+ ```
112
+
113
+ ---
114
+
115
+ ### For Website Owners (The Server)
116
+
117
+ Use theGuide
118
+
119
+ ValleyDam verifies incoming automated traffic and prevents agent impersonation by validating request signatures against DNS-published public keys.
120
+
121
+ It runs as middleware and works with Flask, Django, FastAPI, and similar frameworks.
122
+
123
+ ---
124
+
125
+
126
+ #### 🔒 Hard Validation (Block)
127
+
128
+ Reject invalid or spoofed requests. Best for protected or agent-only APIs.
129
+
130
+ ```python
131
+ from flask import Flask, request, jsonify
132
+ from valleydam import verify_request
133
+
134
+ app = Flask(__name__)
135
+
136
+ @app.route('/agent-api', methods=['POST'])
137
+ def protected_route():
138
+ try:
139
+ verify_request(request)
140
+ identity = request.headers.get('X-ValleyDam-KeyID')
141
+ return jsonify({
142
+ "status": "Welcome",
143
+ "verified_user": identity
144
+ })
145
+ except ValueError as e:
146
+ return jsonify({
147
+ "error": "Access Denied",
148
+ "reason": str(e)
149
+ }), 403
150
+
151
+ if __name__ == "__main__":
152
+ app.run(port=5000)
153
+ ```
154
+
155
+ #### 📄 Soft Validation (Log Only)
156
+
157
+ Attempt verification, log results, but allow all traffic.
158
+
159
+ ```python
160
+ import logging
161
+ from flask import Flask, request, jsonify
162
+ from valleydam import verify_request
163
+
164
+ app = Flask(__name__)
165
+ logging.basicConfig(level=logging.INFO)
166
+
167
+ @app.route('/public-api', methods=['GET', 'POST'])
168
+ def public_route():
169
+ identity = "Unverified (Anonymous)"
170
+
171
+ try:
172
+ verify_request(request)
173
+ identity = request.headers.get('X-ValleyDam-KeyID')
174
+ logging.info(f"Verified request from: {identity}")
175
+ except ValueError as e:
176
+ logging.warning(f"Verification failed: {e}")
177
+
178
+ return jsonify({
179
+ "data": "This is public data",
180
+ "your_status": identity
181
+ })
182
+
183
+ if __name__ == "__main__":
184
+ app.run(port=5000)
185
+ ```
@@ -0,0 +1,158 @@
1
+ # ⛰️ ValleyDam
2
+
3
+ ValleyDam is a lightweight, open protocol for verifying the identity of AI agents and web scrapers using **DNS-backed cryptographic proof**.
4
+
5
+ It enables a website to verify that a request *actually* came from `bot.openai.com` (or your startup’s domain) **without** API keys, IP allowlists, or complex authentication handshakes.
6
+
7
+ ---
8
+
9
+ ## The Problem
10
+
11
+ Today, websites have no reliable way to identify automated clients.
12
+
13
+ - **User-Agent strings are lies**
14
+ Anyone can send `User-Agent: Googlebot`.
15
+
16
+ - **IP blocking is messy**
17
+ Legitimate bots often run on shared cloud infrastructure (AWS, GCP).
18
+
19
+ - **API keys don’t scale**
20
+ You can’t safely issue and manage API keys for every website on the internet.
21
+
22
+ ---
23
+
24
+ ## The Solution
25
+
26
+ ValleyDam uses **Ed25519 digital signatures** anchored in **DNS TXT records** to create a verifiable, spoof-resistant identity for bots.
27
+
28
+ ### How it works
29
+
30
+ 1. **The bot signs each request** using a private Ed25519 key.
31
+ 2. **The server retrieves the public key** from the bot’s DNS record
32
+ (e.g. `_agent.yourwebsite.com`).
33
+ 3. **The signature is verified**. If it matches, the bot’s identity is cryptographically proven.
34
+
35
+ No central authority. No shared secrets. No API keys.
36
+
37
+ ---
38
+
39
+ ## 📦 Installation
40
+
41
+ ```bash
42
+ pip install valleydam
43
+ ```
44
+
45
+ ---
46
+
47
+ ## 🚀 Usage
48
+
49
+ ### For Web Scrapper or Agent Developers (The Client)
50
+
51
+ If you are building a scraper or AI agent, use `ValleyDamSession` to automatically sign outgoing HTTP requests.
52
+
53
+ ---
54
+
55
+ #### 1. Generate Your Identity
56
+
57
+ Run the CLI to generate a private key and receive your DNS TXT record value:
58
+
59
+ ```bash
60
+ valleydam-gen
61
+ ```
62
+
63
+ Follow the printed instructions to add the TXT record to your domain’s DNS.
64
+
65
+ ---
66
+
67
+ #### 2. Use ValleyDam in Your Code
68
+
69
+ ValleyDam behaves just like the standard Python `requests` library.
70
+
71
+ ```python
72
+ from valleydam import ValleyDamSession
73
+
74
+ # Initialize your authenticated session
75
+ agent = ValleyDamSession(
76
+ domain="yourwebsite.com", # Your verified domain
77
+ private_key_path="yourwebsite_com_private.pem" # Generated in step 1
78
+ )
79
+
80
+ # Make requests as normal — they are now cryptographically signed
81
+ response = agent.get("https://protected-website.com/api/data")
82
+
83
+ print(response.text)
84
+ ```
85
+
86
+ ---
87
+
88
+ ### For Website Owners (The Server)
89
+
90
+ Use theGuide
91
+
92
+ ValleyDam verifies incoming automated traffic and prevents agent impersonation by validating request signatures against DNS-published public keys.
93
+
94
+ It runs as middleware and works with Flask, Django, FastAPI, and similar frameworks.
95
+
96
+ ---
97
+
98
+
99
+ #### 🔒 Hard Validation (Block)
100
+
101
+ Reject invalid or spoofed requests. Best for protected or agent-only APIs.
102
+
103
+ ```python
104
+ from flask import Flask, request, jsonify
105
+ from valleydam import verify_request
106
+
107
+ app = Flask(__name__)
108
+
109
+ @app.route('/agent-api', methods=['POST'])
110
+ def protected_route():
111
+ try:
112
+ verify_request(request)
113
+ identity = request.headers.get('X-ValleyDam-KeyID')
114
+ return jsonify({
115
+ "status": "Welcome",
116
+ "verified_user": identity
117
+ })
118
+ except ValueError as e:
119
+ return jsonify({
120
+ "error": "Access Denied",
121
+ "reason": str(e)
122
+ }), 403
123
+
124
+ if __name__ == "__main__":
125
+ app.run(port=5000)
126
+ ```
127
+
128
+ #### 📄 Soft Validation (Log Only)
129
+
130
+ Attempt verification, log results, but allow all traffic.
131
+
132
+ ```python
133
+ import logging
134
+ from flask import Flask, request, jsonify
135
+ from valleydam import verify_request
136
+
137
+ app = Flask(__name__)
138
+ logging.basicConfig(level=logging.INFO)
139
+
140
+ @app.route('/public-api', methods=['GET', 'POST'])
141
+ def public_route():
142
+ identity = "Unverified (Anonymous)"
143
+
144
+ try:
145
+ verify_request(request)
146
+ identity = request.headers.get('X-ValleyDam-KeyID')
147
+ logging.info(f"Verified request from: {identity}")
148
+ except ValueError as e:
149
+ logging.warning(f"Verification failed: {e}")
150
+
151
+ return jsonify({
152
+ "data": "This is public data",
153
+ "your_status": identity
154
+ })
155
+
156
+ if __name__ == "__main__":
157
+ app.run(port=5000)
158
+ ```
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,37 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ with open("README.md", "r", encoding="utf-8") as fh:
4
+ long_description = fh.read()
5
+
6
+ setup(
7
+ name="valleydam",
8
+ version="0.1.0",
9
+ author="Supra N.",
10
+ description="A DNS-based cryptographic identity verification protocol for AI Agents.",
11
+ long_description=long_description,
12
+ long_description_content_type="text/markdown",
13
+ url="https://github.com/supra-nlpn/valley-dam",
14
+ project_urls={
15
+ "Bug Tracker": "https://github.com/supra-nlpn/valley-dam/issues",
16
+ },
17
+ classifiers=[
18
+ "Programming Language :: Python :: 3",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Operating System :: OS Independent",
21
+ "Topic :: Security",
22
+ "Topic :: Internet :: WWW/HTTP",
23
+ ],
24
+ package_dir={"": "src"},
25
+ packages=find_packages(where="src"),
26
+ python_requires=">=3.7",
27
+ install_requires=[
28
+ "requests>=2.25.0",
29
+ "cryptography>=3.4.0",
30
+ "dnspython>=2.1.0",
31
+ ],
32
+ entry_points={
33
+ 'console_scripts': [
34
+ 'valleydam-gen=valleydam.cli:main',
35
+ ],
36
+ },
37
+ )
@@ -0,0 +1,16 @@
1
+ """
2
+ ValleyDam: DNS-based Identity Verification Protocol.
3
+ """
4
+
5
+ from .client import ValleyDamSession
6
+ from .verifier import verify_request, DnsKeyResolver
7
+ from .core import ValleyDamSigner, ValleyDamVerifier
8
+
9
+ __version__ = "0.1.0"
10
+ __all__ = [
11
+ "ValleyDamSession",
12
+ "verify_request",
13
+ "DnsKeyResolver",
14
+ "ValleyDamSigner",
15
+ "ValleyDamVerifier"
16
+ ]
@@ -0,0 +1,61 @@
1
+ import sys
2
+ import base64
3
+ import os
4
+ from cryptography.hazmat.primitives.asymmetric import ed25519
5
+ from cryptography.hazmat.primitives import serialization
6
+
7
+ def main():
8
+ print("\n" + "="*50)
9
+ print(" ⛰️ VALLEYDAM IDENTITY GENERATOR")
10
+ print("="*50)
11
+ print("This tool will create a cryptographic identity for your AI Agent.\n")
12
+
13
+ # 1. Get Domain
14
+ domain = input("Enter your Agent's Domain (e.g., bot.mysite.com): ").strip()
15
+ if not domain:
16
+ print("❌ Error: Domain is required.")
17
+ sys.exit(1)
18
+
19
+ # 2. Generate Keys
20
+ print(f"\nGenerating Ed25519 keypair for {domain}...")
21
+ private_key = ed25519.Ed25519PrivateKey.generate()
22
+ public_key = private_key.public_key()
23
+
24
+ # 3. Save Private Key
25
+ safe_name = domain.replace('.', '_')
26
+ filename = f"{safe_name}_private.pem"
27
+
28
+ if os.path.exists(filename):
29
+ overwrite = input(f"⚠️ File {filename} exists. Overwrite? (y/N): ")
30
+ if overwrite.lower() != 'y':
31
+ print("Aborted.")
32
+ sys.exit(0)
33
+
34
+ private_bytes = private_key.private_bytes(
35
+ encoding=serialization.Encoding.PEM,
36
+ format=serialization.PrivateFormat.PKCS8,
37
+ encryption_algorithm=serialization.NoEncryption()
38
+ )
39
+
40
+ with open(filename, "wb") as f:
41
+ f.write(private_bytes)
42
+
43
+ # 4. Format Public Key for DNS
44
+ public_bytes = public_key.public_bytes(
45
+ encoding=serialization.Encoding.Raw,
46
+ format=serialization.PublicFormat.Raw
47
+ )
48
+ public_b64 = base64.b64encode(public_bytes).decode('utf-8')
49
+
50
+ # 5. Output Instructions
51
+ print(f"\n✅ SUCCESS! Private key saved to: {os.path.abspath(filename)}")
52
+ print(" (DO NOT share this file. Add it to your .gitignore)")
53
+
54
+ print("\n🌍 === DNS SETUP INSTRUCTIONS ===")
55
+ print(f"Log in to your DNS provider for '{domain}' and add this TXT record:\n")
56
+ print(f" Host: _agent")
57
+ print(f" Value: v=vd1; k=ed25519; p={public_b64};\n")
58
+ print("="*50 + "\n")
59
+
60
+ if __name__ == "__main__":
61
+ main()
@@ -0,0 +1,37 @@
1
+ import requests
2
+ from urllib.parse import urlparse
3
+ from typing import Optional
4
+ from .core import ValleyDamSigner
5
+
6
+ class ValleyDamSession(requests.Session):
7
+ """
8
+ A Requests Session that automatically signs every outgoing request
9
+ with a ValleyDam Identity.
10
+ """
11
+
12
+ def __init__(self, domain: str, private_key_path: str):
13
+ super().__init__()
14
+ self.domain = domain
15
+ self.signer = ValleyDamSigner(private_key_path)
16
+
17
+ def request(self, method: str, url: str, *args, **kwargs):
18
+ """
19
+ Overrides the standard request method to inject authentication headers.
20
+ """
21
+ # Parse URL to get signing components
22
+ parsed = urlparse(url)
23
+ host = parsed.netloc
24
+ path = parsed.path if parsed.path else "/"
25
+
26
+ # Generate Signature
27
+ headers = self.signer.sign(method, host, path)
28
+
29
+ # Add Identity Pointer
30
+ headers['X-ValleyDam-KeyID'] = f"dns:{self.domain}"
31
+
32
+ # Merge with user-provided headers
33
+ if 'headers' not in kwargs:
34
+ kwargs['headers'] = {}
35
+ kwargs['headers'].update(headers)
36
+
37
+ return super().request(method, url, *args, **kwargs)
@@ -0,0 +1,102 @@
1
+ import base64
2
+ import time
3
+ from typing import Dict, Optional
4
+ from cryptography.hazmat.primitives.asymmetric import ed25519
5
+ from cryptography.hazmat.primitives import serialization
6
+
7
+ # Protocol Constants
8
+ SIGNATURE_ALGORITHM = 'ed25519'
9
+ MAX_TIME_SKEW_SECONDS = 30
10
+
11
+ class ValleyDamSigner:
12
+ """Handles the cryptographic signing of HTTP requests."""
13
+
14
+ def __init__(self, private_key_path: str):
15
+ """
16
+ Load an Ed25519 private key from a PEM file.
17
+
18
+ Args:
19
+ private_key_path: Path to the .pem file generated by the CLI.
20
+ """
21
+ try:
22
+ with open(private_key_path, 'rb') as key_file:
23
+ self.private_key = serialization.load_pem_private_key(
24
+ key_file.read(),
25
+ password=None
26
+ )
27
+ except FileNotFoundError:
28
+ raise ValueError(f"Private key not found at: {private_key_path}")
29
+ except ValueError:
30
+ raise ValueError("Invalid PEM file format.")
31
+
32
+ def sign(self, method: str, host: str, path: str) -> Dict[str, str]:
33
+ """
34
+ Generate authentication headers for a request.
35
+
36
+ The payload format is: "{METHOD} {HOST} {PATH} {TIMESTAMP}"
37
+ """
38
+ timestamp = int(time.time())
39
+
40
+ # Canonicalization: Create the immutable string to sign
41
+ payload = f"{method.upper()} {host} {path} {timestamp}".encode('utf-8')
42
+
43
+ # Sign using Ed25519
44
+ sig_bytes = self.private_key.sign(payload)
45
+ sig_b64 = base64.b64encode(sig_bytes).decode('utf-8')
46
+
47
+ return {
48
+ 'X-ValleyDam-Signature': sig_b64,
49
+ 'X-ValleyDam-Time': str(timestamp),
50
+ 'X-ValleyDam-Method': SIGNATURE_ALGORITHM
51
+ }
52
+
53
+
54
+ class ValleyDamVerifier:
55
+ """Handles the verification of incoming request signatures."""
56
+
57
+ def __init__(self, key_resolver_func):
58
+ """
59
+ Args:
60
+ key_resolver_func: A callable that takes a key_id string and
61
+ returns an Ed25519PublicKey object (or None).
62
+ """
63
+ self.resolve_key = key_resolver_func
64
+
65
+ def verify(self, method: str, host: str, path: str, headers: Dict) -> bool:
66
+ """
67
+ Verify that a request was signed by the owner of the DNS record.
68
+
69
+ Raises:
70
+ ValueError: If headers are missing, timestamp is expired, or signature is invalid.
71
+ """
72
+ # 1. Extract Headers
73
+ sig_b64 = headers.get('X-ValleyDam-Signature')
74
+ timestamp = headers.get('X-ValleyDam-Time')
75
+ key_id = headers.get('X-ValleyDam-KeyID')
76
+
77
+ if not all([sig_b64, timestamp, key_id]):
78
+ raise ValueError("Missing required ValleyDam authentication headers.")
79
+
80
+ # 2. Check Timestamp (Replay Attack Prevention)
81
+ try:
82
+ req_time = int(timestamp)
83
+ now = int(time.time())
84
+ if abs(now - req_time) > MAX_TIME_SKEW_SECONDS:
85
+ raise ValueError(f"Request expired. Server time: {now}, Request time: {req_time}")
86
+ except ValueError:
87
+ raise ValueError("Invalid timestamp format.")
88
+
89
+ # 3. Resolve Public Key
90
+ public_key = self.resolve_key(key_id)
91
+ if not public_key:
92
+ raise ValueError(f"Public key not found for identity: {key_id}")
93
+
94
+ # 4. Reconstruct and Verify Payload
95
+ payload = f"{method.upper()} {host} {path} {timestamp}".encode('utf-8')
96
+
97
+ try:
98
+ sig_bytes = base64.b64decode(sig_b64)
99
+ public_key.verify(sig_bytes, payload)
100
+ return True
101
+ except Exception:
102
+ raise ValueError("Cryptographic verification failed. Signature does not match payload.")
@@ -0,0 +1,87 @@
1
+ import dns.resolver
2
+ import base64
3
+ from functools import lru_cache
4
+ from typing import Optional
5
+ from cryptography.hazmat.primitives.asymmetric import ed25519
6
+ from .core import ValleyDamVerifier
7
+
8
+ # --- CACHED LOOKUP FUNCTION ---
9
+ @lru_cache(maxsize=256)
10
+ def _fetch_dns_record(key_id: str) -> Optional[ed25519.Ed25519PublicKey]:
11
+ """
12
+ Fetch and cache the Ed25519 public key from a DNS TXT record.
13
+ Format expected: v=vd1; k=ed25519; p=<BASE64_KEY>;
14
+ """
15
+ if not key_id.startswith("dns:"):
16
+ return None
17
+
18
+ try:
19
+ domain = key_id.split(":", 1)[1]
20
+ except IndexError:
21
+ return None
22
+
23
+ # Use Google and Cloudflare DNS to avoid local ISP caching issues
24
+ resolver = dns.resolver.Resolver(configure=False)
25
+ resolver.nameservers = ['8.8.8.8', '1.1.1.1']
26
+ resolver.lifetime = 2.0 # Timeout after 2 seconds
27
+
28
+ try:
29
+ txt_target = f"_agent.{domain}"
30
+ answers = resolver.resolve(txt_target, 'TXT')
31
+
32
+ for rdata in answers:
33
+ # Clean up the TXT record string
34
+ txt_string = rdata.to_text().replace('"', '').strip()
35
+
36
+ # Parse key-value pairs
37
+ parts = {}
38
+ for item in txt_string.split(';'):
39
+ if '=' in item:
40
+ k, v = item.strip().split('=', 1)
41
+ parts[k] = v
42
+
43
+ # Return the key if found
44
+ if parts.get('p'):
45
+ try:
46
+ pub_bytes = base64.b64decode(parts['p'])
47
+ return ed25519.Ed25519PublicKey.from_public_bytes(pub_bytes)
48
+ except Exception:
49
+ continue # Malformed key, try next record
50
+
51
+ except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, dns.exception.Timeout):
52
+ return None
53
+ except Exception as e:
54
+ # In production, you might want to log this error
55
+ # print(f"DNS Error: {e}")
56
+ return None
57
+
58
+ return None
59
+
60
+
61
+ class DnsKeyResolver:
62
+ """Wrapper class for the cached DNS lookup."""
63
+ def resolve(self, key_id: str):
64
+ return _fetch_dns_record(key_id)
65
+
66
+
67
+ # Singleton instance to maintain cache across requests
68
+ GLOBAL_RESOLVER = DnsKeyResolver()
69
+
70
+
71
+ def verify_request(request) -> bool:
72
+ """
73
+ Helper function for Flask/Django/FastAPI.
74
+
75
+ Usage:
76
+ try:
77
+ verify_request(request)
78
+ except ValueError as e:
79
+ abort(403, str(e))
80
+ """
81
+ verifier = ValleyDamVerifier(GLOBAL_RESOLVER.resolve)
82
+
83
+ # Handle Flask-style request objects
84
+ host = request.host
85
+ path = request.path
86
+
87
+ return verifier.verify(request.method, host, path, request.headers)
@@ -0,0 +1,185 @@
1
+ Metadata-Version: 2.4
2
+ Name: valleydam
3
+ Version: 0.1.0
4
+ Summary: A DNS-based cryptographic identity verification protocol for AI Agents.
5
+ Home-page: https://github.com/supra-nlpn/valley-dam
6
+ Author: Supra N.
7
+ Project-URL: Bug Tracker, https://github.com/supra-nlpn/valley-dam/issues
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Topic :: Security
12
+ Classifier: Topic :: Internet :: WWW/HTTP
13
+ Requires-Python: >=3.7
14
+ Description-Content-Type: text/markdown
15
+ Requires-Dist: requests>=2.25.0
16
+ Requires-Dist: cryptography>=3.4.0
17
+ Requires-Dist: dnspython>=2.1.0
18
+ Dynamic: author
19
+ Dynamic: classifier
20
+ Dynamic: description
21
+ Dynamic: description-content-type
22
+ Dynamic: home-page
23
+ Dynamic: project-url
24
+ Dynamic: requires-dist
25
+ Dynamic: requires-python
26
+ Dynamic: summary
27
+
28
+ # ⛰️ ValleyDam
29
+
30
+ ValleyDam is a lightweight, open protocol for verifying the identity of AI agents and web scrapers using **DNS-backed cryptographic proof**.
31
+
32
+ It enables a website to verify that a request *actually* came from `bot.openai.com` (or your startup’s domain) **without** API keys, IP allowlists, or complex authentication handshakes.
33
+
34
+ ---
35
+
36
+ ## The Problem
37
+
38
+ Today, websites have no reliable way to identify automated clients.
39
+
40
+ - **User-Agent strings are lies**
41
+ Anyone can send `User-Agent: Googlebot`.
42
+
43
+ - **IP blocking is messy**
44
+ Legitimate bots often run on shared cloud infrastructure (AWS, GCP).
45
+
46
+ - **API keys don’t scale**
47
+ You can’t safely issue and manage API keys for every website on the internet.
48
+
49
+ ---
50
+
51
+ ## The Solution
52
+
53
+ ValleyDam uses **Ed25519 digital signatures** anchored in **DNS TXT records** to create a verifiable, spoof-resistant identity for bots.
54
+
55
+ ### How it works
56
+
57
+ 1. **The bot signs each request** using a private Ed25519 key.
58
+ 2. **The server retrieves the public key** from the bot’s DNS record
59
+ (e.g. `_agent.yourwebsite.com`).
60
+ 3. **The signature is verified**. If it matches, the bot’s identity is cryptographically proven.
61
+
62
+ No central authority. No shared secrets. No API keys.
63
+
64
+ ---
65
+
66
+ ## 📦 Installation
67
+
68
+ ```bash
69
+ pip install valleydam
70
+ ```
71
+
72
+ ---
73
+
74
+ ## 🚀 Usage
75
+
76
+ ### For Web Scrapper or Agent Developers (The Client)
77
+
78
+ If you are building a scraper or AI agent, use `ValleyDamSession` to automatically sign outgoing HTTP requests.
79
+
80
+ ---
81
+
82
+ #### 1. Generate Your Identity
83
+
84
+ Run the CLI to generate a private key and receive your DNS TXT record value:
85
+
86
+ ```bash
87
+ valleydam-gen
88
+ ```
89
+
90
+ Follow the printed instructions to add the TXT record to your domain’s DNS.
91
+
92
+ ---
93
+
94
+ #### 2. Use ValleyDam in Your Code
95
+
96
+ ValleyDam behaves just like the standard Python `requests` library.
97
+
98
+ ```python
99
+ from valleydam import ValleyDamSession
100
+
101
+ # Initialize your authenticated session
102
+ agent = ValleyDamSession(
103
+ domain="yourwebsite.com", # Your verified domain
104
+ private_key_path="yourwebsite_com_private.pem" # Generated in step 1
105
+ )
106
+
107
+ # Make requests as normal — they are now cryptographically signed
108
+ response = agent.get("https://protected-website.com/api/data")
109
+
110
+ print(response.text)
111
+ ```
112
+
113
+ ---
114
+
115
+ ### For Website Owners (The Server)
116
+
117
+ Use theGuide
118
+
119
+ ValleyDam verifies incoming automated traffic and prevents agent impersonation by validating request signatures against DNS-published public keys.
120
+
121
+ It runs as middleware and works with Flask, Django, FastAPI, and similar frameworks.
122
+
123
+ ---
124
+
125
+
126
+ #### 🔒 Hard Validation (Block)
127
+
128
+ Reject invalid or spoofed requests. Best for protected or agent-only APIs.
129
+
130
+ ```python
131
+ from flask import Flask, request, jsonify
132
+ from valleydam import verify_request
133
+
134
+ app = Flask(__name__)
135
+
136
+ @app.route('/agent-api', methods=['POST'])
137
+ def protected_route():
138
+ try:
139
+ verify_request(request)
140
+ identity = request.headers.get('X-ValleyDam-KeyID')
141
+ return jsonify({
142
+ "status": "Welcome",
143
+ "verified_user": identity
144
+ })
145
+ except ValueError as e:
146
+ return jsonify({
147
+ "error": "Access Denied",
148
+ "reason": str(e)
149
+ }), 403
150
+
151
+ if __name__ == "__main__":
152
+ app.run(port=5000)
153
+ ```
154
+
155
+ #### 📄 Soft Validation (Log Only)
156
+
157
+ Attempt verification, log results, but allow all traffic.
158
+
159
+ ```python
160
+ import logging
161
+ from flask import Flask, request, jsonify
162
+ from valleydam import verify_request
163
+
164
+ app = Flask(__name__)
165
+ logging.basicConfig(level=logging.INFO)
166
+
167
+ @app.route('/public-api', methods=['GET', 'POST'])
168
+ def public_route():
169
+ identity = "Unverified (Anonymous)"
170
+
171
+ try:
172
+ verify_request(request)
173
+ identity = request.headers.get('X-ValleyDam-KeyID')
174
+ logging.info(f"Verified request from: {identity}")
175
+ except ValueError as e:
176
+ logging.warning(f"Verification failed: {e}")
177
+
178
+ return jsonify({
179
+ "data": "This is public data",
180
+ "your_status": identity
181
+ })
182
+
183
+ if __name__ == "__main__":
184
+ app.run(port=5000)
185
+ ```
@@ -0,0 +1,13 @@
1
+ README.md
2
+ setup.py
3
+ src/valleydam/__init__.py
4
+ src/valleydam/cli.py
5
+ src/valleydam/client.py
6
+ src/valleydam/core.py
7
+ src/valleydam/verifier.py
8
+ src/valleydam.egg-info/PKG-INFO
9
+ src/valleydam.egg-info/SOURCES.txt
10
+ src/valleydam.egg-info/dependency_links.txt
11
+ src/valleydam.egg-info/entry_points.txt
12
+ src/valleydam.egg-info/requires.txt
13
+ src/valleydam.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ valleydam-gen = valleydam.cli:main
@@ -0,0 +1,3 @@
1
+ requests>=2.25.0
2
+ cryptography>=3.4.0
3
+ dnspython>=2.1.0
@@ -0,0 +1 @@
1
+ valleydam