Skip to content

Commit 718a456

Browse files
authored
feat: block sensitive information logs and add the. agentkit folder to .dockerignore (#37)
2 parents bc6e96d + e8ba230 commit 718a456

File tree

9 files changed

+185
-118
lines changed

9 files changed

+185
-118
lines changed

agentkit/toolkit/builders/local_docker.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from datetime import datetime
2626
from agentkit.toolkit.config import CommonConfig
2727
from agentkit.toolkit.config.dataclass_utils import AutoSerializableMixin
28+
from agentkit.toolkit.docker.utils import create_dockerignore_file
2829
from agentkit.toolkit.models import BuildResult, ImageInfo
2930
from agentkit.toolkit.reporter import Reporter
3031
from agentkit.toolkit.errors import ErrorCode
@@ -334,9 +335,7 @@ def generate_dockerfile_content() -> str:
334335
force_regenerate=force_regenerate,
335336
)
336337

337-
dockerignore_path = self.workdir / ".dockerignore"
338-
if not dockerignore_path.exists():
339-
renderer.create_dockerignore(str(dockerignore_path))
338+
create_dockerignore_file(str(self.workdir))
340339
image_name = f"{docker_config.image_name or 'agentkit-app'}"
341340
image_tag = f"{docker_config.image_tag or 'latest'}"
342341

agentkit/toolkit/config/dataclass_utils.py

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,40 @@
2121

2222
T = TypeVar("T")
2323

24+
SENSITIVE_FIELDS = {
25+
"runtime_envs",
26+
"runtime_apikey_name",
27+
"runtime_apikey",
28+
"runtime_jwt_discovery_url",
29+
"runtime_jwt_allowed_clients",
30+
}
31+
32+
33+
def _get_safe_value(field_name: str, value: Any) -> Any:
34+
if field_name in SENSITIVE_FIELDS:
35+
return "******"
36+
return value
37+
38+
39+
def _sanitize_dict(data: Dict[str, Any]) -> Dict[str, Any]:
40+
"""Sanitize sensitive fields in a dictionary."""
41+
if not isinstance(data, dict):
42+
return data
43+
return {k: _get_safe_value(k, v) for k, v in data.items()}
44+
45+
46+
def _sanitize_diff(diff: Dict[str, Any]) -> Dict[str, Any]:
47+
"""Sanitize sensitive fields in a diff dictionary."""
48+
if not isinstance(diff, dict):
49+
return diff
50+
sanitized = {}
51+
for k, v in diff.items():
52+
if k in SENSITIVE_FIELDS:
53+
sanitized[k] = ("******", "******")
54+
else:
55+
sanitized[k] = v
56+
return sanitized
57+
2458

2559
class DataclassSerializer:
2660
@staticmethod
@@ -50,7 +84,7 @@ def from_dict(cls: Type[T], data: Dict[str, Any]) -> T:
5084
logger.debug(
5185
"[DataclassSerializer] source=local field=%s value=%r",
5286
field_name,
53-
kwargs[field_name],
87+
_get_safe_value(field_name, kwargs[field_name]),
5488
)
5589
else:
5690
# Try aliases (backward compatibility)
@@ -65,7 +99,7 @@ def from_dict(cls: Type[T], data: Dict[str, Any]) -> T:
6599
"[DataclassSerializer] source=alias(%s) -> local field=%s value=%r",
66100
alias,
67101
field_name,
68-
kwargs[field_name],
102+
_get_safe_value(field_name, kwargs[field_name]),
69103
)
70104
break
71105

@@ -77,15 +111,15 @@ def from_dict(cls: Type[T], data: Dict[str, Any]) -> T:
77111
logger.debug(
78112
"[DataclassSerializer] source=default_factory field=%s value=%r",
79113
field_name,
80-
kwargs[field_name],
114+
_get_safe_value(field_name, kwargs[field_name]),
81115
)
82116
elif field.default is not MISSING:
83117
kwargs[field_name] = field.default
84118
_sources[field_name] = "default"
85119
logger.debug(
86120
"[DataclassSerializer] source=default field=%s value=%r",
87121
field_name,
88-
kwargs[field_name],
122+
_get_safe_value(field_name, kwargs[field_name]),
89123
)
90124
else:
91125
kwargs[field_name] = None
@@ -157,7 +191,11 @@ def from_dict(cls: Type[T], data: Dict[str, Any], skip_render: bool = False) ->
157191
from .global_config import apply_global_config_defaults
158192

159193
before = instance.to_dict()
160-
logger.debug("from_dict: before globals for %s -> %r", cls.__name__, before)
194+
logger.debug(
195+
"from_dict: before globals for %s -> %r",
196+
cls.__name__,
197+
_sanitize_dict(before),
198+
)
161199
instance = apply_global_config_defaults(instance, data)
162200
after = instance.to_dict()
163201
if before != after:
@@ -169,7 +207,7 @@ def from_dict(cls: Type[T], data: Dict[str, Any], skip_render: bool = False) ->
169207
logger.debug(
170208
"from_dict: applied global defaults for %s; changes=%r",
171209
cls.__name__,
172-
diff,
210+
_sanitize_diff(diff),
173211
)
174212
else:
175213
logger.debug(
@@ -254,7 +292,7 @@ def _render_template_fields(self):
254292
"[%s] [template] start field render check: name=%s, value=%r, has_placeholders=%s",
255293
cfg_name,
256294
field_info.name,
257-
field_value,
295+
_get_safe_value(field_info.name, field_value),
258296
(
259297
isinstance(field_value, str)
260298
and ("{{" in field_value and "}}" in field_value)
@@ -269,7 +307,7 @@ def _render_template_fields(self):
269307
"[%s] [template] field %s is Auto/empty -> using default_template=%r",
270308
cfg_name,
271309
field_info.name,
272-
default_template,
310+
_get_safe_value(field_info.name, default_template),
273311
)
274312
field_value = default_template
275313
self._template_originals[field_info.name] = default_template
@@ -300,17 +338,20 @@ def _render_template_fields(self):
300338
"[%s] [template] save original template for %s: %r",
301339
cfg_name,
302340
field_info.name,
303-
field_value,
341+
_get_safe_value(field_info.name, field_value),
304342
)
305343

306344
try:
307-
rendered = render_template(field_value)
345+
is_sensitive = field_info.name in SENSITIVE_FIELDS
346+
rendered = render_template(
347+
field_value, sensitive=is_sensitive
348+
)
308349
logger.debug(
309350
"[%s] [template] rendered field %s: %r -> %r",
310351
cfg_name,
311352
field_info.name,
312-
field_value,
313-
rendered,
353+
_get_safe_value(field_info.name, field_value),
354+
_get_safe_value(field_info.name, rendered),
314355
)
315356
# Fail if unresolved placeholders remain
316357
if "{{" in str(rendered) and "}}" in str(rendered):
@@ -356,7 +397,9 @@ def _render_template_fields(self):
356397
"[%s] [template] field %s is not marked for rendering, value: %r",
357398
cfg_name,
358399
field_info.name,
359-
getattr(self, field_info.name),
400+
_get_safe_value(
401+
field_info.name, getattr(self, field_info.name)
402+
),
360403
)
361404
except ImportError:
362405
# If template utils are not available, no-op

agentkit/toolkit/docker/container.py

Lines changed: 8 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import docker
2020
from docker.errors import DockerException, ImageNotFound
2121

22+
from agentkit.toolkit.docker.utils import create_dockerignore_file
23+
2224
logger = logging.getLogger(__name__)
2325

2426

@@ -84,10 +86,8 @@ def render_dockerfile(
8486

8587
# Create .dockerignore file if requested
8688
if create_dockerignore:
87-
dockerignore_path = os.path.join(
88-
output_dir or os.path.dirname(output_path), ".dockerignore"
89-
)
90-
self.create_dockerignore(dockerignore_path, dockerignore_entries)
89+
target_dir = output_dir or os.path.dirname(output_path)
90+
create_dockerignore_file(target_dir, dockerignore_entries)
9191

9292
return rendered_content
9393

@@ -106,69 +106,17 @@ def create_dockerignore(
106106
"""
107107
Create .dockerignore file with default and additional entries.
108108
109+
Deprecated: Use agentkit.toolkit.docker.utils.create_dockerignore_file instead.
110+
109111
Args:
110112
dockerignore_path: Path to .dockerignore file
111113
additional_entries: Additional entries to add to .dockerignore
112114
113115
Raises:
114116
IOError: When file write fails
115117
"""
116-
try:
117-
# Check if .dockerignore already exists
118-
if os.path.exists(dockerignore_path):
119-
logger.info(
120-
f".dockerignore already exists at: {dockerignore_path}, skipping creation"
121-
)
122-
return
123-
124-
# Default entries to exclude
125-
default_entries = [
126-
"# AgentKit configuration",
127-
"agentkit.yaml",
128-
"agentkit*.yaml",
129-
"",
130-
"# Python cache",
131-
"__pycache__/",
132-
"*.py[cod]",
133-
"*$py.class",
134-
"",
135-
"# Virtual environments",
136-
".venv/",
137-
"venv/",
138-
"ENV/",
139-
"env/",
140-
"",
141-
"# IDE",
142-
".vscode/",
143-
".idea/",
144-
".windsurf/",
145-
"",
146-
"# Git",
147-
".git/",
148-
".gitignore",
149-
"",
150-
"# Docker",
151-
"Dockerfile*",
152-
".dockerignore",
153-
]
154-
155-
# Combine default and additional entries
156-
all_entries = default_entries.copy()
157-
if additional_entries:
158-
all_entries.append("")
159-
all_entries.append("# Additional entries")
160-
all_entries.extend(additional_entries)
161-
162-
# Write .dockerignore file
163-
with open(dockerignore_path, "w", encoding="utf-8") as f:
164-
f.write("\n".join(all_entries))
165-
f.write("\n") # End with newline
166-
167-
logger.info(f"Successfully created .dockerignore at: {dockerignore_path}")
168-
169-
except Exception as e:
170-
logger.error(f"Error creating .dockerignore: {str(e)}")
171-
raise
118+
target_dir = os.path.dirname(dockerignore_path)
119+
create_dockerignore_file(target_dir, additional_entries)
172120

173121

174122
class DockerManager:

agentkit/toolkit/docker/utils.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
16+
import os
17+
import logging
18+
from typing import List, Optional
19+
20+
logger = logging.getLogger(__name__)
21+
22+
23+
def create_dockerignore_file(
24+
target_dir: str, additional_entries: Optional[List[str]] = None
25+
) -> bool:
26+
"""
27+
Create .dockerignore file with default and additional entries.
28+
29+
Args:
30+
target_dir: Directory where .dockerignore should be created
31+
additional_entries: Additional entries to add to .dockerignore
32+
33+
Returns:
34+
bool: True if file was created, False if it already existed
35+
36+
Raises:
37+
IOError: When file write fails
38+
"""
39+
dockerignore_path = os.path.join(target_dir, ".dockerignore")
40+
41+
try:
42+
# Check if .dockerignore already exists
43+
if os.path.exists(dockerignore_path):
44+
logger.info(
45+
f".dockerignore already exists at: {dockerignore_path}, skipping creation"
46+
)
47+
return False
48+
49+
# Default entries to exclude
50+
default_entries = [
51+
"# AgentKit configuration",
52+
"agentkit.yaml",
53+
"agentkit*.yaml",
54+
".agentkit/",
55+
"",
56+
"# Python cache",
57+
"__pycache__/",
58+
"*.py[cod]",
59+
"*$py.class",
60+
"",
61+
"# Virtual environments",
62+
".venv/",
63+
"venv/",
64+
"ENV/",
65+
"env/",
66+
"",
67+
"# IDE",
68+
".vscode/",
69+
".idea/",
70+
".windsurf/",
71+
"",
72+
"# Git",
73+
".git/",
74+
".gitignore",
75+
"",
76+
"# Docker",
77+
"Dockerfile*",
78+
".dockerignore",
79+
]
80+
81+
# Combine default and additional entries
82+
all_entries = default_entries.copy()
83+
if additional_entries:
84+
all_entries.append("")
85+
all_entries.append("# Additional entries")
86+
all_entries.extend(additional_entries)
87+
88+
# Write .dockerignore file
89+
with open(dockerignore_path, "w", encoding="utf-8") as f:
90+
f.write("\n".join(all_entries))
91+
f.write("\n") # End with newline
92+
93+
logger.info(f"Successfully created .dockerignore at: {dockerignore_path}")
94+
return True
95+
96+
except Exception as e:
97+
logger.error(f"Error creating .dockerignore: {str(e)}")
98+
raise

0 commit comments

Comments
 (0)