Skip to content

Commit 8301883

Browse files
committed
feat(ai): route to send number of secrets detected by ai agent hook
1 parent 1433d26 commit 8301883

6 files changed

Lines changed: 174 additions & 0 deletions

File tree

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<!--
2+
A new scriv changelog fragment.
3+
4+
Uncomment the section that is right (remove the HTML comment wrapper).
5+
-->
6+
7+
<!--
8+
### Removed
9+
10+
- A bullet item for the Removed category.
11+
12+
-->
13+
14+
### Added
15+
16+
- New endpoint log_secret_block to send metadata when a ggshield AI agent hook catches a secret.
17+
18+
<!--
19+
### Changed
20+
21+
- A bullet item for the Changed category.
22+
23+
-->
24+
<!--
25+
### Deprecated
26+
27+
- A bullet item for the Deprecated category.
28+
29+
-->
30+
<!--
31+
### Fixed
32+
33+
- A bullet item for the Fixed category.
34+
35+
-->
36+
<!--
37+
### Security
38+
39+
- A bullet item for the Security category.
40+
41+
-->

pygitguardian/client.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
QuotaResponse,
5050
RemediationMessages,
5151
ScanResult,
52+
SecretBlockRequest,
5253
SecretIncident,
5354
SecretScanPreferences,
5455
ServerMetadata,
@@ -1261,6 +1262,25 @@ def log_mcp_activities_bulk(
12611262
obj.status_code = response.status_code
12621263
return obj
12631264

1265+
def log_secret_block(
1266+
self,
1267+
block: SecretBlockRequest,
1268+
extra_headers: Optional[Dict[str, str]] = None,
1269+
) -> Optional[Detail]:
1270+
"""Report a secret blocked by an AI hook.
1271+
1272+
The endpoint returns 204 No Content on success, so this returns None on
1273+
success and a Detail on error.
1274+
"""
1275+
response = self.post(
1276+
endpoint="agent-activity/secret-block",
1277+
data=block.to_dict(),
1278+
extra_headers=extra_headers,
1279+
)
1280+
1281+
if not is_delete_ok(response):
1282+
return load_detail(response)
1283+
12641284
def send_agent_activity(
12651285
self,
12661286
events: List[Dict[str, Any]],

pygitguardian/models.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1837,6 +1837,34 @@ def make_mcp_activity_request(self, data: Dict[str, Any], **kwargs: Any):
18371837
MCPActivityRequest.SCHEMA = MCPActivityRequestSchema()
18381838

18391839

1840+
@dataclass
1841+
class SecretBlockRequest(FromDictMixin, ToDictMixin):
1842+
user: UserInfo
1843+
tool: str
1844+
agent: str
1845+
cwd: str
1846+
secret_count: int
1847+
detectors: List[str] = field(default_factory=list)
1848+
timestamp: Optional[datetime] = None
1849+
1850+
1851+
class SecretBlockRequestSchema(BaseSchema):
1852+
user = fields.Nested(UserInfoSchema, required=True)
1853+
tool = fields.Str(required=True)
1854+
agent = fields.Str(required=True)
1855+
cwd = fields.Str(required=True)
1856+
secret_count = fields.Int(required=True)
1857+
detectors = fields.List(fields.Str(), load_default=[], dump_default=[])
1858+
timestamp = fields.DateTime(load_default=None, allow_none=True)
1859+
1860+
@post_load
1861+
def make_secret_block_request(self, data: Dict[str, Any], **kwargs: Any):
1862+
return SecretBlockRequest(**data)
1863+
1864+
1865+
SecretBlockRequest.SCHEMA = SecretBlockRequestSchema()
1866+
1867+
18401868
@dataclass
18411869
class MCPActivityResponse(FromDictWithBase):
18421870
allowed: bool
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
interactions:
2+
- request:
3+
body: '{"user": {"hostname": "toto-laptop", "username": "toto", "machine_id":
4+
"1234567890", "user_email": "toto@gitguardian.com"}, "tool": "Write", "agent":
5+
"claude", "cwd": "/home/user/project", "secret_count": 2, "detectors": ["AWS
6+
Keys", "GitHub Token"]}'
7+
headers:
8+
Accept:
9+
- '*/*'
10+
Accept-Encoding:
11+
- gzip, deflate
12+
Connection:
13+
- keep-alive
14+
Content-Type:
15+
- application/json
16+
User-Agent:
17+
- pygitguardian/1.29.0 (Linux;py3.11.15)
18+
method: POST
19+
uri: https://api.gitguardian.com/v1/agent-activity/secret-block
20+
response:
21+
body:
22+
string: ''
23+
headers:
24+
Allow:
25+
- POST, OPTIONS
26+
Connection:
27+
- keep-alive
28+
Content-Length:
29+
- '0'
30+
Date:
31+
- Tue, 21 Apr 2026 09:57:44 GMT
32+
Server:
33+
- nginx/1.29.1
34+
X-App-Version:
35+
- dev
36+
status:
37+
code: 204
38+
message: No Content
39+
version: 1

tests/test_client.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
MultiScanResult,
5454
QuotaResponse,
5555
ScanResult,
56+
SecretBlockRequest,
5657
Source,
5758
SourceParameters,
5859
Team,
@@ -1974,6 +1975,32 @@ def test_log_mcp_activity(client: GGClient):
19741975
assert isinstance(result, MCPActivityResponse), result
19751976

19761977

1978+
@my_vcr.use_cassette("test_log_secret_block.yaml", ignore_localhost=False)
1979+
def test_log_secret_block(client: GGClient):
1980+
"""
1981+
GIVEN a client
1982+
WHEN calling POST /agent-activity/secret-block endpoint
1983+
THEN the secret block is logged and None is returned on success (204)
1984+
"""
1985+
result = client.log_secret_block(
1986+
SecretBlockRequest(
1987+
user=UserInfo(
1988+
user_email="toto@gitguardian.com",
1989+
hostname="toto-laptop",
1990+
username="toto",
1991+
machine_id="1234567890",
1992+
),
1993+
tool="Write",
1994+
agent="claude",
1995+
cwd="/home/user/project",
1996+
secret_count=2,
1997+
detectors=["AWS Keys", "GitHub Token"],
1998+
)
1999+
)
2000+
2001+
assert result is None, result
2002+
2003+
19772004
@my_vcr.use_cassette(
19782005
"test_log_mcp_activities_bulk_posts_to_correct_endpoint.yaml",
19792006
ignore_localhost=False,

tests/test_models.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
QuotaSchema,
3333
ScanResult,
3434
ScanResultSchema,
35+
SecretBlockRequest,
36+
SecretBlockRequestSchema,
3537
SecretIncident,
3638
SecretIncidentSchema,
3739
SecretOccurrence,
@@ -528,6 +530,23 @@ def test_document_handle_surrogates(self):
528530
"reason": "test",
529531
},
530532
),
533+
(
534+
SecretBlockRequestSchema,
535+
SecretBlockRequest,
536+
{
537+
"user": {
538+
"user_email": "toto@gitguardian.com",
539+
"machine_id": "1234567890",
540+
"hostname": "toto-laptop",
541+
"username": "toto",
542+
},
543+
"tool": "Write",
544+
"agent": "claude",
545+
"cwd": "/home/user/project",
546+
"secret_count": 2,
547+
"detectors": ["AWS Keys", "GitHub Token"],
548+
},
549+
),
531550
(
532551
AgentActivityResponseSchema,
533552
AgentActivityResponse,

0 commit comments

Comments
 (0)